TYPO3 API  SVNRelease
class.t3lib_flexformtools.php
Go to the documentation of this file.
00001 <?php
00002 /***************************************************************
00003  *  Copyright notice
00004  *
00005  *  (c) 2006-2011 Kasper Skårhøj (kasperYYYY@typo3.com)
00006  *  All rights reserved
00007  *
00008  *  This script is part of the TYPO3 project. The TYPO3 project is
00009  *  free software; you can redistribute it and/or modify
00010  *  it under the terms of the GNU General Public License as published by
00011  *  the Free Software Foundation; either version 2 of the License, or
00012  *  (at your option) any later version.
00013  *
00014  *  The GNU General Public License can be found at
00015  *  http://www.gnu.org/copyleft/gpl.html.
00016  *  A copy is found in the textfile GPL.txt and important notices to the license
00017  *  from the author is found in LICENSE.txt distributed with these scripts.
00018  *
00019  *
00020  *  This script is distributed in the hope that it will be useful,
00021  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
00022  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00023  *  GNU General Public License for more details.
00024  *
00025  *  This copyright notice MUST APPEAR in all copies of the script!
00026  ***************************************************************/
00027 /**
00028  * Contains functions for manipulating flex form data
00029  *
00030  * $Id: class.t3lib_flexformtools.php 10121 2011-01-18 20:15:30Z ohader $
00031  *
00032  * @author  Kasper Skårhøj <kasperYYYY@typo3.com>
00033  */
00034 /**
00035  * [CLASS/FUNCTION INDEX of SCRIPT]
00036  *
00037  *
00038  *
00039  *   71: class t3lib_flexformtools
00040  *  105:     function traverseFlexFormXMLData($table,$field,$row,&$callBackObj,$callBackMethod_value)
00041  *  203:     function traverseFlexFormXMLData_recurse($dataStruct,$editData,&$PA,$path='')
00042  *  274:     function getAvailableLanguages()
00043  *
00044  *            SECTION: Processing functions
00045  *  323:     function cleanFlexFormXML($table,$field,$row)
00046  *  347:     function cleanFlexFormXML_callBackFunction($dsArr, $data, $PA, $path, &$pObj)
00047  *
00048  *            SECTION: Multi purpose functions
00049  *  374:     function &getArrayValueByPath($pathArray,&$array)
00050  *  403:     function setArrayValueByPath($pathArray,&$array,$value)
00051  *  433:     function flexArray2Xml($array, $addPrologue=FALSE)
00052  *
00053  * TOTAL FUNCTIONS: 8
00054  * (This index is automatically created/updated by the extension "extdeveval")
00055  *
00056  */
00057 
00058 
00059 /**
00060  * Contains functions for manipulating flex form data
00061  *
00062  * @author  Kasper Skårhøj <kasperYYYY@typo3.com>
00063  * @package TYPO3
00064  * @subpackage t3lib
00065  */
00066 class t3lib_flexformtools {
00067 
00068     var $convertCharset = FALSE; // If set, the charset of data XML is converted to system charset.
00069     var $reNumberIndexesOfSectionData = FALSE; // If set, section indexes are re-numbered before processing
00070 
00071     var $traverseFlexFormXMLData_DS = array(); // Contains data structure when traversing flexform
00072     var $traverseFlexFormXMLData_Data = array(); // Contains data array when traversing flexform
00073 
00074         // 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
00075     var $flexArray2Xml_options = array(
00076         'parentTagMap' => array(
00077             'data' => 'sheet',
00078             'sheet' => 'language',
00079             'language' => 'field',
00080             'el' => 'field',
00081             'field' => 'value',
00082             'field:el' => 'el',
00083             'el:_IS_NUM' => 'section',
00084             'section' => 'itemType'
00085         ),
00086         'disableTypeAttrib' => 2
00087     );
00088 
00089         // Internal:
00090     /**
00091      * Reference to object called
00092      */
00093     var $callBackObj = NULL;
00094     var $cleanFlexFormXML = array(); // Used for accumulation of clean XML
00095 
00096     /**
00097      * Handler for Flex Forms
00098      *
00099      * @param   string      The table name of the record
00100      * @param   string      The field name of the flexform field to work on
00101      * @param   array       The record data array
00102      * @param   object      Object (passed by reference) in which the call back function is located
00103      * @param   string      Method name of call back function in object for values
00104      * @return  boolean     If true, error happened (error string returned)
00105      */
00106     function traverseFlexFormXMLData($table, $field, $row, $callBackObj, $callBackMethod_value) {
00107 
00108         if (!is_array($GLOBALS['TCA'][$table]) || !is_array($GLOBALS['TCA'][$table]['columns'][$field])) {
00109             return 'TCA table/field was not defined.';
00110         }
00111 
00112         $this->callBackObj = $callBackObj;
00113 
00114             // Get Data Structure:
00115         $dataStructArray = t3lib_BEfunc::getFlexFormDS($GLOBALS['TCA'][$table]['columns'][$field]['config'], $row, $table);
00116 
00117             // If data structure was ok, proceed:
00118         if (is_array($dataStructArray)) {
00119 
00120                 // Get flexform XML data:
00121             $xmlData = $row[$field];
00122 
00123                 // Convert charset:
00124             if ($this->convertCharset) {
00125                 $xmlHeaderAttributes = t3lib_div::xmlGetHeaderAttribs($xmlData);
00126                 $storeInCharset = strtolower($xmlHeaderAttributes['encoding']);
00127                 if ($storeInCharset) {
00128                     $currentCharset = $GLOBALS['LANG']->charSet;
00129                     $xmlData = $GLOBALS['LANG']->csConvObj->conv($xmlData, $storeInCharset, $currentCharset, 1);
00130                 }
00131             }
00132 
00133             $editData = t3lib_div::xml2array($xmlData);
00134             if (!is_array($editData)) {
00135                 return 'Parsing error: ' . $editData;
00136             }
00137 
00138                 // Language settings:
00139             $langChildren = $dataStructArray['meta']['langChildren'] ? 1 : 0;
00140             $langDisabled = $dataStructArray['meta']['langDisable'] ? 1 : 0;
00141 
00142                 // empty or invalid <meta>
00143             if (!is_array($editData['meta'])) {
00144                 $editData['meta'] = array();
00145             }
00146             $editData['meta']['currentLangId'] = array();
00147             $languages = $this->getAvailableLanguages();
00148 
00149             foreach ($languages as $lInfo) {
00150                 $editData['meta']['currentLangId'][] = $lInfo['ISOcode'];
00151             }
00152             if (!count($editData['meta']['currentLangId'])) {
00153                 $editData['meta']['currentLangId'] = array('DEF');
00154             }
00155             $editData['meta']['currentLangId'] = array_unique($editData['meta']['currentLangId']);
00156 
00157             if ($langChildren || $langDisabled) {
00158                 $lKeys = array('DEF');
00159             } else {
00160                 $lKeys = $editData['meta']['currentLangId'];
00161             }
00162 
00163                 // Tabs sheets
00164             if (is_array($dataStructArray['sheets'])) {
00165                 $sKeys = array_keys($dataStructArray['sheets']);
00166             } else {
00167                 $sKeys = array('sDEF');
00168             }
00169 
00170                 // Traverse languages:
00171             foreach ($lKeys as $lKey) {
00172                 foreach ($sKeys as $sheet) {
00173                     $sheetCfg = $dataStructArray['sheets'][$sheet];
00174                     list ($dataStruct, $sheet) = t3lib_div::resolveSheetDefInDS($dataStructArray, $sheet);
00175 
00176                         // Render sheet:
00177                     if (is_array($dataStruct['ROOT']) && is_array($dataStruct['ROOT']['el'])) {
00178                         $lang = 'l' . $lKey; // Separate language key
00179                         $PA['vKeys'] = $langChildren && !$langDisabled ? $editData['meta']['currentLangId'] : array('DEF');
00180                         $PA['lKey'] = $lang;
00181                         $PA['callBackMethod_value'] = $callBackMethod_value;
00182                         $PA['table'] = $table;
00183                         $PA['field'] = $field;
00184                         $PA['uid'] = $row['uid'];
00185 
00186                         $this->traverseFlexFormXMLData_DS = &$dataStruct;
00187                         $this->traverseFlexFormXMLData_Data = &$editData;
00188 
00189                             // Render flexform:
00190                         $this->traverseFlexFormXMLData_recurse(
00191                             $dataStruct['ROOT']['el'],
00192                             $editData['data'][$sheet][$lang],
00193                             $PA,
00194                                 'data/' . $sheet . '/' . $lang
00195                         );
00196                     } else {
00197                         return 'Data Structure ERROR: No ROOT element found for sheet "' . $sheet . '".';
00198                     }
00199                 }
00200             }
00201         } else {
00202             return 'Data Structure ERROR: ' . $dataStructArray;
00203         }
00204     }
00205 
00206     /**
00207      * Recursively traversing flexform data according to data structure and element data
00208      *
00209      * @param   array       (Part of) data structure array that applies to the sub section of the flexform data we are processing
00210      * @param   array       (Part of) edit data array, reflecting current part of data structure
00211      * @param   array       Additional parameters passed.
00212      * @param   string      Telling the "path" to the element in the flexform XML
00213      * @return  array
00214      */
00215     function traverseFlexFormXMLData_recurse($dataStruct, $editData, &$PA, $path = '') {
00216 
00217         if (is_array($dataStruct)) {
00218             foreach ($dataStruct as $key => $value) {
00219                 if (is_array($value)) { // The value of each entry must be an array.
00220 
00221                     if ($value['type'] == 'array') {
00222                         if ($value['section']) { // Array (Section) traversal:
00223 
00224                             $cc = 0;
00225                             if (is_array($editData[$key]['el'])) {
00226 
00227                                 if ($this->reNumberIndexesOfSectionData) {
00228                                     $temp = array();
00229                                     $c3 = 0;
00230                                     foreach ($editData[$key]['el'] as $v3) {
00231                                         $temp[++$c3] = $v3;
00232                                     }
00233                                     $editData[$key]['el'] = $temp;
00234                                 }
00235 
00236                                 foreach ($editData[$key]['el'] as $k3 => $v3) {
00237                                     if (is_array($v3)) {
00238                                         $cc = $k3;
00239                                         $theType = key($v3);
00240                                         $theDat = $v3[$theType];
00241                                         $newSectionEl = $value['el'][$theType];
00242                                         if (is_array($newSectionEl)) {
00243                                             $this->traverseFlexFormXMLData_recurse(
00244                                                 array($theType => $newSectionEl),
00245                                                 array($theType => $theDat),
00246                                                 $PA,
00247                                                     $path . '/' . $key . '/el/' . $cc
00248                                             );
00249                                         }
00250                                     }
00251                                 }
00252                             }
00253                         } else { // Array traversal:
00254                             $this->traverseFlexFormXMLData_recurse(
00255                                 $value['el'],
00256                                 $editData[$key]['el'],
00257                                 $PA,
00258                                     $path . '/' . $key . '/el'
00259                             );
00260                         }
00261                     } elseif (is_array($value['TCEforms']['config'])) { // Processing a field value:
00262 
00263                         foreach ($PA['vKeys'] as $vKey) {
00264                             $vKey = 'v' . $vKey;
00265 
00266                                 // Call back:
00267                             if ($PA['callBackMethod_value']) {
00268                                 $this->callBackObj->$PA['callBackMethod_value'](
00269                                     $value,
00270                                     $editData[$key][$vKey],
00271                                     $PA,
00272                                         $path . '/' . $key . '/' . $vKey,
00273                                     $this
00274                                 );
00275                             }
00276                         }
00277                     }
00278                 }
00279             }
00280         }
00281     }
00282 
00283     /**
00284      * Returns an array of available languages to use for FlexForm operations
00285      *
00286      * @return  array
00287      */
00288     function getAvailableLanguages() {
00289         $isL = t3lib_extMgm::isLoaded('static_info_tables');
00290 
00291             // Find all language records in the system:
00292         $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery('static_lang_isocode,title,uid', 'sys_language', 'pid=0' . t3lib_BEfunc::deleteClause('sys_language'), '', 'title');
00293 
00294             // Traverse them:
00295         $output = array();
00296         $output[0] = array(
00297             'uid' => 0,
00298             'title' => 'Default language',
00299             'ISOcode' => 'DEF'
00300         );
00301 
00302         while ($row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res)) {
00303             $output[$row['uid']] = $row;
00304 
00305             if ($isL && $row['static_lang_isocode']) {
00306                 $rr = t3lib_BEfunc::getRecord('static_languages', $row['static_lang_isocode'], 'lg_iso_2');
00307                 if ($rr['lg_iso_2']) {
00308                     $output[$row['uid']]['ISOcode'] = $rr['lg_iso_2'];
00309                 }
00310             }
00311 
00312             if (!$output[$row['uid']]['ISOcode']) {
00313                 unset($output[$row['uid']]);
00314             }
00315         }
00316         return $output;
00317     }
00318 
00319 
00320     /***********************************
00321      *
00322      * Processing functions
00323      *
00324      ***********************************/
00325 
00326     /**
00327      * 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.
00328      * 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.
00329      *
00330      * @param   string      Table name
00331      * @param   string      Field name of the flex form field in which the XML is found that should be cleaned.
00332      * @param   array       The record
00333      * @return  string      Clean XML from FlexForm field
00334      */
00335     function cleanFlexFormXML($table, $field, $row) {
00336 
00337             // New structure:
00338         $this->cleanFlexFormXML = array();
00339 
00340             // Create and call iterator object:
00341         $flexObj = t3lib_div::makeInstance('t3lib_flexformtools');
00342         $flexObj->reNumberIndexesOfSectionData = TRUE;
00343         $flexObj->traverseFlexFormXMLData($table, $field, $row, $this, 'cleanFlexFormXML_callBackFunction');
00344 
00345         return $this->flexArray2Xml($this->cleanFlexFormXML, TRUE);
00346     }
00347 
00348     /**
00349      * Call back function for t3lib_flexformtools class
00350      * Basically just setting the value in a new array (thus cleaning because only values that are valid are visited!)
00351      *
00352      * @param   array       Data structure for the current value
00353      * @param   mixed       Current value
00354      * @param   array       Additional configuration used in calling function
00355      * @param   string      Path of value in DS structure
00356      * @param   object      Object reference to caller
00357      * @return  void
00358      */
00359     function cleanFlexFormXML_callBackFunction($dsArr, $data, $PA, $path, $pObj) {
00360         #debug(array($dsArr, $data, $PA),$path);
00361             // Just setting value in our own result array, basically replicating the structure:
00362         $pObj->setArrayValueByPath($path, $this->cleanFlexFormXML, $data);
00363 
00364             // Looking if an "extension" called ".vDEFbase" is found and if so, accept that too:
00365         if ($GLOBALS['TYPO3_CONF_VARS']['BE']['flexFormXMLincludeDiffBase']) {
00366             $vDEFbase = $pObj->getArrayValueByPath($path . '.vDEFbase', $pObj->traverseFlexFormXMLData_Data);
00367             if (isset($vDEFbase)) {
00368                 $pObj->setArrayValueByPath($path . '.vDEFbase', $this->cleanFlexFormXML, $vDEFbase);
00369             }
00370         }
00371     }
00372 
00373 
00374     /***********************************
00375      *
00376      * Multi purpose functions
00377      *
00378      ***********************************/
00379 
00380     /**
00381      * Get a value from a multi-dimensional array by giving a path "../../.." pointing to the element
00382      *
00383      * @param   string      The path pointing to the value field, eg. test/2/title to access $array['test'][2]['title']
00384      * @param   array       Array to get value from. Passed by reference so the value returned can be used to change the value in the array!
00385      * @return  mixed       Value returned
00386      */
00387     function &getArrayValueByPath($pathArray, &$array) {
00388         if (!is_array($pathArray)) {
00389             $pathArray = explode('/', $pathArray);
00390         }
00391         if (is_array($array)) {
00392             if (count($pathArray)) {
00393                 $key = array_shift($pathArray);
00394 
00395                 if (isset($array[$key])) {
00396                     if (!count($pathArray)) {
00397                         return $array[$key];
00398                     } else {
00399                         return $this->getArrayValueByPath($pathArray, $array[$key]);
00400                     }
00401                 } else {
00402                     return NULL;
00403                 }
00404             }
00405         }
00406     }
00407 
00408     /**
00409      * Set a value in a multi-dimensional array by giving a path "../../.." pointing to the element
00410      *
00411      * @param   string      The path pointing to the value field, eg. test/2/title to access $array['test'][2]['title']
00412      * @param   array       Array to set value in. Passed by reference so the value returned can be used to change the value in the array!
00413      * @param   mixed       Value to set
00414      * @return  mixed       Value returned
00415      */
00416     function setArrayValueByPath($pathArray, &$array, $value) {
00417         if (isset($value)) {
00418             if (!is_array($pathArray)) {
00419                 $pathArray = explode('/', $pathArray);
00420             }
00421             if (is_array($array)) {
00422                 if (count($pathArray)) {
00423                     $key = array_shift($pathArray);
00424 
00425                     if (!count($pathArray)) {
00426                         $array[$key] = $value;
00427                         return TRUE;
00428                     } else {
00429                         if (!isset($array[$key])) {
00430                             $array[$key] = array();
00431                         }
00432                         return $this->setArrayValueByPath($pathArray, $array[$key], $value);
00433                     }
00434                 }
00435             }
00436         }
00437     }
00438 
00439     /**
00440      * Convert FlexForm data array to XML
00441      *
00442      * @param   array       Array to output in <T3FlexForms> XML
00443      * @param   boolean     If set, the XML prologue is returned as well.
00444      * @return  string      XML content.
00445      */
00446     function flexArray2Xml($array, $addPrologue = FALSE) {
00447         if ($GLOBALS['TYPO3_CONF_VARS']['BE']['flexformForceCDATA']) {
00448             $this->flexArray2Xml_options['useCDATA'] = 1;
00449         }
00450 
00451         $options = $GLOBALS['TYPO3_CONF_VARS']['BE']['niceFlexFormXMLtags'] ? $this->flexArray2Xml_options : array();
00452         $spaceInd = ($GLOBALS['TYPO3_CONF_VARS']['BE']['compactFlexFormXML'] ? -1 : 4);
00453         $output = t3lib_div::array2xml($array, '', 0, 'T3FlexForms', $spaceInd, $options);
00454 
00455         if ($addPrologue) {
00456             $output = '<?xml version="1.0" encoding="' . $GLOBALS['LANG']->charSet . '" standalone="yes" ?>' . LF . $output;
00457         }
00458 
00459         return $output;
00460     }
00461 }
00462 
00463 if (defined('TYPO3_MODE') && isset($GLOBALS['TYPO3_CONF_VARS'][TYPO3_MODE]['XCLASS']['t3lib/class.t3lib_flexformtools.php'])) {
00464     include_once($GLOBALS['TYPO3_CONF_VARS'][TYPO3_MODE]['XCLASS']['t3lib/class.t3lib_flexformtools.php']);
00465 }
00466 ?>