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