[FEATURE] Allow .ts file extension for static typoscript templates
[Packages/TYPO3.CMS.git] / t3lib / class.t3lib_flexformtools.php
1 <?php
2 /***************************************************************
3 * Copyright notice
4 *
5 * (c) 2006-2011 Kasper Skårhøj (kasperYYYY@typo3.com)
6 * All rights reserved
7 *
8 * This script is part of the TYPO3 project. The TYPO3 project is
9 * free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version.
13 *
14 * The GNU General Public License can be found at
15 * http://www.gnu.org/copyleft/gpl.html.
16 * A copy is found in the textfile GPL.txt and important notices to the license
17 * from the author is found in LICENSE.txt distributed with these scripts.
18 *
19 *
20 * This script is distributed in the hope that it will be useful,
21 * but WITHOUT ANY WARRANTY; without even the implied warranty of
22 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23 * GNU General Public License for more details.
24 *
25 * This copyright notice MUST APPEAR in all copies of the script!
26 ***************************************************************/
27 /**
28 * Contains functions for manipulating flex form data
29 *
30 * @author Kasper Skårhøj <kasperYYYY@typo3.com>
31 */
32
33 /**
34 * Contains functions for manipulating flex form data
35 *
36 * @author Kasper Skårhøj <kasperYYYY@typo3.com>
37 * @package TYPO3
38 * @subpackage t3lib
39 */
40 class t3lib_flexformtools {
41
42 // If set, the charset of data XML is converted to system charset.
43 var $convertCharset = FALSE;
44 // If set, section indexes are re-numbered before processing
45 var $reNumberIndexesOfSectionData = FALSE;
46
47 // Contains data structure when traversing flexform
48 var $traverseFlexFormXMLData_DS = array();
49 // Contains data array when traversing flexform
50 var $traverseFlexFormXMLData_Data = array();
51
52 // Options for array2xml() for flexform.
53 // This will map the weird keys from the internal array to tags that could potentially be checked with a DTD/schema
54 var $flexArray2Xml_options = array(
55 'parentTagMap' => array(
56 'data' => 'sheet',
57 'sheet' => 'language',
58 'language' => 'field',
59 'el' => 'field',
60 'field' => 'value',
61 'field:el' => 'el',
62 'el:_IS_NUM' => 'section',
63 'section' => 'itemType'
64 ),
65 'disableTypeAttrib' => 2
66 );
67
68 /**
69 * Reference to object called
70 */
71 var $callBackObj = NULL;
72 // Used for accumulation of clean XML
73 var $cleanFlexFormXML = array();
74
75 /**
76 * Handler for Flex Forms
77 *
78 * @param string $table The table name of the record
79 * @param string $field The field name of the flexform field to work on
80 * @param array $row The record data array
81 * @param object $callBackObj Object (passed by reference) in which the call back function is located
82 * @param string $callBackMethod_value Method name of call back function in object for values
83 * @return boolean If TRUE, error happened (error string returned)
84 */
85 function traverseFlexFormXMLData($table, $field, $row, $callBackObj, $callBackMethod_value) {
86
87 if (!is_array($GLOBALS['TCA'][$table]) || !is_array($GLOBALS['TCA'][$table]['columns'][$field])) {
88 return 'TCA table/field was not defined.';
89 }
90
91 $this->callBackObj = $callBackObj;
92
93 // Get Data Structure:
94 $dataStructArray = t3lib_BEfunc::getFlexFormDS($GLOBALS['TCA'][$table]['columns'][$field]['config'], $row, $table);
95
96 // If data structure was ok, proceed:
97 if (is_array($dataStructArray)) {
98
99 // Get flexform XML data:
100 $xmlData = $row[$field];
101
102 // Convert charset:
103 if ($this->convertCharset) {
104 $xmlHeaderAttributes = t3lib_div::xmlGetHeaderAttribs($xmlData);
105 $storeInCharset = strtolower($xmlHeaderAttributes['encoding']);
106 if ($storeInCharset) {
107 $currentCharset = $GLOBALS['LANG']->charSet;
108 $xmlData = $GLOBALS['LANG']->csConvObj->conv($xmlData, $storeInCharset, $currentCharset, 1);
109 }
110 }
111
112 $editData = t3lib_div::xml2array($xmlData);
113 if (!is_array($editData)) {
114 return 'Parsing error: ' . $editData;
115 }
116
117 // Language settings:
118 $langChildren = $dataStructArray['meta']['langChildren'] ? 1 : 0;
119 $langDisabled = $dataStructArray['meta']['langDisable'] ? 1 : 0;
120
121 // Empty or invalid <meta>
122 if (!is_array($editData['meta'])) {
123 $editData['meta'] = array();
124 }
125 $editData['meta']['currentLangId'] = array();
126 $languages = $this->getAvailableLanguages();
127
128 foreach ($languages as $lInfo) {
129 $editData['meta']['currentLangId'][] = $lInfo['ISOcode'];
130 }
131 if (!count($editData['meta']['currentLangId'])) {
132 $editData['meta']['currentLangId'] = array('DEF');
133 }
134 $editData['meta']['currentLangId'] = array_unique($editData['meta']['currentLangId']);
135
136 if ($langChildren || $langDisabled) {
137 $lKeys = array('DEF');
138 } else {
139 $lKeys = $editData['meta']['currentLangId'];
140 }
141
142 // Tabs sheets
143 if (is_array($dataStructArray['sheets'])) {
144 $sKeys = array_keys($dataStructArray['sheets']);
145 } else {
146 $sKeys = array('sDEF');
147 }
148
149 // Traverse languages:
150 foreach ($lKeys as $lKey) {
151 foreach ($sKeys as $sheet) {
152 $sheetCfg = $dataStructArray['sheets'][$sheet];
153 list ($dataStruct, $sheet) = t3lib_div::resolveSheetDefInDS($dataStructArray, $sheet);
154
155 // Render sheet:
156 if (is_array($dataStruct['ROOT']) && is_array($dataStruct['ROOT']['el'])) {
157 // Separate language key
158 $lang = 'l' . $lKey;
159 $PA['vKeys'] = $langChildren && !$langDisabled ? $editData['meta']['currentLangId'] : array('DEF');
160 $PA['lKey'] = $lang;
161 $PA['callBackMethod_value'] = $callBackMethod_value;
162 $PA['table'] = $table;
163 $PA['field'] = $field;
164 $PA['uid'] = $row['uid'];
165
166 $this->traverseFlexFormXMLData_DS = &$dataStruct;
167 $this->traverseFlexFormXMLData_Data = &$editData;
168
169 // Render flexform:
170 $this->traverseFlexFormXMLData_recurse(
171 $dataStruct['ROOT']['el'],
172 $editData['data'][$sheet][$lang],
173 $PA,
174 'data/' . $sheet . '/' . $lang
175 );
176 } else {
177 return 'Data Structure ERROR: No ROOT element found for sheet "' . $sheet . '".';
178 }
179 }
180 }
181 } else {
182 return 'Data Structure ERROR: ' . $dataStructArray;
183 }
184 }
185
186 /**
187 * Recursively traversing flexform data according to data structure and element data
188 *
189 * @param array $dataStruct (Part of) data structure array that applies to the sub section of the flexform data we are processing
190 * @param array $editData (Part of) edit data array, reflecting current part of data structure
191 * @param array $PA Additional parameters passed.
192 * @param string $path Telling the "path" to the element in the flexform XML
193 * @return array
194 */
195 function traverseFlexFormXMLData_recurse($dataStruct, $editData, &$PA, $path = '') {
196
197 if (is_array($dataStruct)) {
198 foreach ($dataStruct as $key => $value) {
199 // The value of each entry must be an array.
200 if (is_array($value)) {
201
202 if ($value['type'] == 'array') {
203 // Array (Section) traversal
204 if ($value['section']) {
205
206 $cc = 0;
207 if (is_array($editData[$key]['el'])) {
208
209 if ($this->reNumberIndexesOfSectionData) {
210 $temp = array();
211 $c3 = 0;
212 foreach ($editData[$key]['el'] as $v3) {
213 $temp[++$c3] = $v3;
214 }
215 $editData[$key]['el'] = $temp;
216 }
217
218 foreach ($editData[$key]['el'] as $k3 => $v3) {
219 if (is_array($v3)) {
220 $cc = $k3;
221 $theType = key($v3);
222 $theDat = $v3[$theType];
223 $newSectionEl = $value['el'][$theType];
224 if (is_array($newSectionEl)) {
225 $this->traverseFlexFormXMLData_recurse(
226 array($theType => $newSectionEl),
227 array($theType => $theDat),
228 $PA,
229 $path . '/' . $key . '/el/' . $cc
230 );
231 }
232 }
233 }
234 }
235 } else { // Array traversal:
236 $this->traverseFlexFormXMLData_recurse(
237 $value['el'],
238 $editData[$key]['el'],
239 $PA,
240 $path . '/' . $key . '/el'
241 );
242 }
243 } elseif (is_array($value['TCEforms']['config'])) { // Processing a field value:
244
245 foreach ($PA['vKeys'] as $vKey) {
246 $vKey = 'v' . $vKey;
247
248 // Call back:
249 if ($PA['callBackMethod_value']) {
250 $this->callBackObj->$PA['callBackMethod_value'](
251 $value,
252 $editData[$key][$vKey],
253 $PA,
254 $path . '/' . $key . '/' . $vKey,
255 $this
256 );
257 }
258 }
259 }
260 }
261 }
262 }
263 }
264
265 /**
266 * Returns an array of available languages to use for FlexForm operations
267 *
268 * @return array
269 */
270 function getAvailableLanguages() {
271 $isL = t3lib_extMgm::isLoaded('static_info_tables');
272
273 // Find all language records in the system
274 $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery('static_lang_isocode,title,uid', 'sys_language', 'pid=0' . t3lib_BEfunc::deleteClause('sys_language'), '', 'title');
275
276 // Traverse them
277 $output = array();
278 $output[0] = array(
279 'uid' => 0,
280 'title' => 'Default language',
281 'ISOcode' => 'DEF'
282 );
283
284 while ($row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res)) {
285 $output[$row['uid']] = $row;
286
287 if ($isL && $row['static_lang_isocode']) {
288 $rr = t3lib_BEfunc::getRecord('static_languages', $row['static_lang_isocode'], 'lg_iso_2');
289 if ($rr['lg_iso_2']) {
290 $output[$row['uid']]['ISOcode'] = $rr['lg_iso_2'];
291 }
292 }
293
294 if (!$output[$row['uid']]['ISOcode']) {
295 unset($output[$row['uid']]);
296 }
297 }
298
299 $GLOBALS['TYPO3_DB']->sql_free_result($res);
300
301 return $output;
302 }
303
304 /***********************************
305 *
306 * Processing functions
307 *
308 ***********************************/
309
310 /**
311 * Cleaning up FlexForm XML to hold only the values it may according to its Data Structure. Also the order of tags will follow that of the data structure.
312 * BE CAREFUL: DO not clean records in workspaces unless IN the workspace! The Data Structure might resolve falsely on a workspace record when cleaned from Live workspace.
313 *
314 * @param string $table Table name
315 * @param string $field Field name of the flex form field in which the XML is found that should be cleaned.
316 * @param array $row The record
317 * @return string Clean XML from FlexForm field
318 */
319 function cleanFlexFormXML($table, $field, $row) {
320
321 // New structure:
322 $this->cleanFlexFormXML = array();
323
324 // Create and call iterator object:
325 $flexObj = t3lib_div::makeInstance('t3lib_flexformtools');
326 $flexObj->reNumberIndexesOfSectionData = TRUE;
327 $flexObj->traverseFlexFormXMLData($table, $field, $row, $this, 'cleanFlexFormXML_callBackFunction');
328
329 return $this->flexArray2Xml($this->cleanFlexFormXML, TRUE);
330 }
331
332 /**
333 * Call back function for t3lib_flexformtools class
334 * Basically just setting the value in a new array (thus cleaning because only values that are valid are visited!)
335 *
336 * @param array $dsArr Data structure for the current value
337 * @param mixed $data Current value
338 * @param array $PA Additional configuration used in calling function
339 * @param string $path Path of value in DS structure
340 * @param object $pObj Object reference to caller
341 * @return void
342 */
343 function cleanFlexFormXML_callBackFunction($dsArr, $data, $PA, $path, $pObj) {
344 // Just setting value in our own result array, basically replicating the structure:
345 $pObj->setArrayValueByPath($path, $this->cleanFlexFormXML, $data);
346
347 // Looking if an "extension" called ".vDEFbase" is found and if so, accept that too:
348 if ($GLOBALS['TYPO3_CONF_VARS']['BE']['flexFormXMLincludeDiffBase']) {
349 $vDEFbase = $pObj->getArrayValueByPath($path . '.vDEFbase', $pObj->traverseFlexFormXMLData_Data);
350 if (isset($vDEFbase)) {
351 $pObj->setArrayValueByPath($path . '.vDEFbase', $this->cleanFlexFormXML, $vDEFbase);
352 }
353 }
354 }
355
356 /***********************************
357 *
358 * Multi purpose functions
359 *
360 ***********************************/
361
362 /**
363 * Get a value from a multi-dimensional array by giving a path "../../.." pointing to the element
364 *
365 * @param string $pathArray The path pointing to the value field, eg. test/2/title to access $array['test'][2]['title']
366 * @param array $array Array to get value from. Passed by reference so the value returned can be used to change the value in the array!
367 * @return mixed Value returned
368 */
369 function &getArrayValueByPath($pathArray, &$array) {
370 if (!is_array($pathArray)) {
371 $pathArray = explode('/', $pathArray);
372 }
373 if (is_array($array)) {
374 if (count($pathArray)) {
375 $key = array_shift($pathArray);
376
377 if (isset($array[$key])) {
378 if (!count($pathArray)) {
379 return $array[$key];
380 } else {
381 return $this->getArrayValueByPath($pathArray, $array[$key]);
382 }
383 } else {
384 return NULL;
385 }
386 }
387 }
388 }
389
390 /**
391 * Set a value in a multi-dimensional array by giving a path "../../.." pointing to the element
392 *
393 * @param string $pathArray The path pointing to the value field, eg. test/2/title to access $array['test'][2]['title']
394 * @param array $array Array to set value in. Passed by reference so the value returned can be used to change the value in the array!
395 * @param mixed $value Value to set
396 * @return mixed Value returned
397 */
398 function setArrayValueByPath($pathArray, &$array, $value) {
399 if (isset($value)) {
400 if (!is_array($pathArray)) {
401 $pathArray = explode('/', $pathArray);
402 }
403 if (is_array($array)) {
404 if (count($pathArray)) {
405 $key = array_shift($pathArray);
406
407 if (!count($pathArray)) {
408 $array[$key] = $value;
409 return TRUE;
410 } else {
411 if (!isset($array[$key])) {
412 $array[$key] = array();
413 }
414 return $this->setArrayValueByPath($pathArray, $array[$key], $value);
415 }
416 }
417 }
418 }
419 }
420
421 /**
422 * Convert FlexForm data array to XML
423 *
424 * @param array $array Array to output in <T3FlexForms> XML
425 * @param boolean $addPrologue If set, the XML prologue is returned as well.
426 * @return string XML content.
427 */
428 function flexArray2Xml($array, $addPrologue = FALSE) {
429 if ($GLOBALS['TYPO3_CONF_VARS']['BE']['flexformForceCDATA']) {
430 $this->flexArray2Xml_options['useCDATA'] = 1;
431 }
432
433 $options = $GLOBALS['TYPO3_CONF_VARS']['BE']['niceFlexFormXMLtags'] ? $this->flexArray2Xml_options : array();
434 $spaceInd = ($GLOBALS['TYPO3_CONF_VARS']['BE']['compactFlexFormXML'] ? -1 : 4);
435 $output = t3lib_div::array2xml($array, '', 0, 'T3FlexForms', $spaceInd, $options);
436
437 if ($addPrologue) {
438 $output = '<?xml version="1.0" encoding="' . $GLOBALS['LANG']->charSet . '" standalone="yes" ?>' . LF . $output;
439 }
440
441 return $output;
442 }
443 }
444
445 ?>