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