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