New Feature: Inline Relational Record Editing by Oliver Hader!
authorIngmar Schlecht <ingmar.schlecht@typo3.org>
Thu, 23 Nov 2006 12:04:14 +0000 (12:04 +0000)
committerIngmar Schlecht <ingmar.schlecht@typo3.org>
Thu, 23 Nov 2006 12:04:14 +0000 (12:04 +0000)
git-svn-id: https://svn.typo3.org/TYPO3v4/Core/trunk@1809 709f56b5-9817-0410-a4d7-c38de5d9e867

30 files changed:
ChangeLog
t3lib/class.t3lib_befunc.php
t3lib/class.t3lib_loaddbgroup.php
t3lib/class.t3lib_refindex.php
t3lib/class.t3lib_tceforms.php
t3lib/class.t3lib_tceforms_inline.php [new file with mode: 0644]
t3lib/class.t3lib_tcemain.php
t3lib/class.t3lib_transferdata.php
t3lib/jsfunc.inline.js [new file with mode: 0644]
typo3/alt_doc.php
typo3/alt_doc_ajax.php [new file with mode: 0644]
typo3/gfx/move.gif [new file with mode: 0644]
typo3/json.php [new file with mode: 0644]
typo3/prototype.js
typo3/scriptaculous/builder.js
typo3/scriptaculous/controls.js
typo3/scriptaculous/dragdrop.js
typo3/scriptaculous/effects.js
typo3/scriptaculous/scriptaculous.js
typo3/scriptaculous/slider.js
typo3/scriptaculous/unittest.js
typo3/stylesheet.css
typo3/sysext/lang/locallang_core.xml
typo3/sysext/rtehtmlarea/class.tx_rtehtmlarea_base.php
typo3/sysext/rtehtmlarea/htmlarea/htmlarea.js
typo3/sysext/rtehtmlarea/htmlarea/plugins/DynamicCSS/dynamiccss-compressed.js
typo3/sysext/rtehtmlarea/htmlarea/plugins/DynamicCSS/dynamiccss.js
typo3/sysext/rtehtmlarea/htmlarea/plugins/InlineCSS/inlinecss-compressed.js
typo3/sysext/rtehtmlarea/htmlarea/plugins/InlineCSS/inlinecss.js
typo3/sysext/t3skin/stylesheets/typo3-TCEforms.css

index 9c9dc66..f73e820 100755 (executable)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,6 @@
+2006-11-23  Ingmar Schlecht  <ingmar@typo3.org>
+
+       * New Feature: Inline Relational Record Editing (IRRE). See http://wiki.typo3.org/index.php/Inline_Relational_Record_Editing for details. Thanks a lot to Oliver Hader who programmed this as his diploma thesis and to Sebastian who reviewed the code together with me!
 
 2006-11-21  Martin Kutschker  <martin.t.kutschker@blackbox.net>
 
index 83b1bce..288826f 100755 (executable)
  *
  *
  *
- *  183: class t3lib_BEfunc
+ *  185: class t3lib_BEfunc
  *
  *              SECTION: SQL-related, selecting records, searching
- *  204:     function deleteClause($table,$tableAlias='')
- *  227:     function getRecord($table,$uid,$fields='*',$where='')
- *  245:     function getRecordWSOL($table,$uid,$fields='*',$where='')
- *  278:     function getRecordRaw($table,$where='',$fields='*')
- *  300:     function getRecordsByField($theTable,$theField,$theValue,$whereClause='',$groupBy='',$orderBy='',$limit='')
- *  333:     function searchQuery($searchWords,$fields,$table='')
- *  348:     function listQuery($field,$value)
- *  360:     function splitTable_Uid($str)
- *  375:     function getSQLselectableList($in_list,$tablename,$default_tablename)
- *  403:     function BEenableFields($table,$inv=0)
+ *  206:     function deleteClause($table,$tableAlias='')
+ *  230:     function getRecord($table,$uid,$fields='*',$where='',$useDeleteClause=true)
+ *  253:     function getRecordWSOL($table,$uid,$fields='*',$where='',$useDeleteClause=true)
+ *  286:     function getRecordRaw($table,$where='',$fields='*')
+ *  309:     function getRecordsByField($theTable,$theField,$theValue,$whereClause='',$groupBy='',$orderBy='',$limit='',$useDeleteClause=true)
+ *  342:     function searchQuery($searchWords,$fields,$table='')
+ *  357:     function listQuery($field,$value)
+ *  369:     function splitTable_Uid($str)
+ *  384:     function getSQLselectableList($in_list,$tablename,$default_tablename)
+ *  412:     function BEenableFields($table,$inv=0)
  *
  *              SECTION: SQL-related, DEPRECATED functions
- *  467:     function mm_query($select,$local_table,$mm_table,$foreign_table,$whereClause='',$groupBy='',$orderBy='',$limit='')
- *  489:     function DBcompileInsert($table,$fields_values)
- *  503:     function DBcompileUpdate($table,$where,$fields_values)
+ *  476:     function mm_query($select,$local_table,$mm_table,$foreign_table,$whereClause='',$groupBy='',$orderBy='',$limit='')
+ *  498:     function DBcompileInsert($table,$fields_values)
+ *  512:     function DBcompileUpdate($table,$where,$fields_values)
  *
  *              SECTION: Page tree, TCA related
- *  533:     function BEgetRootLine($uid,$clause='',$workspaceOL=FALSE)
- *  589:     function openPageTree($pid,$clearExpansion)
- *  634:     function getRecordPath($uid, $clause, $titleLimit, $fullTitleLimit=0)
- *  677:     function getExcludeFields()
- *  707:     function getExplicitAuthFieldValues()
- *  778:     function getSystemLanguages()
- *  803:     function readPageAccess($id,$perms_clause)
- *  834:     function getTCAtypes($table,$rec,$useFieldNameAsKey=0)
- *  887:     function getTCAtypeValue($table,$rec)
- *  910:     function getSpecConfParts($str, $defaultExtras)
- *  941:     function getSpecConfParametersFromArray($pArr)
- *  969:     function getFlexFormDS($conf,$row,$table,$fieldName='',$WSOL=TRUE)
+ *  542:     function BEgetRootLine($uid,$clause='',$workspaceOL=FALSE)
+ *  598:     function openPageTree($pid,$clearExpansion)
+ *  643:     function getRecordPath($uid, $clause, $titleLimit, $fullTitleLimit=0)
+ *  686:     function getExcludeFields()
+ *  716:     function getExplicitAuthFieldValues()
+ *  787:     function getSystemLanguages()
+ *  812:     function readPageAccess($id,$perms_clause)
+ *  843:     function getTCAtypes($table,$rec,$useFieldNameAsKey=0)
+ *  896:     function getTCAtypeValue($table,$rec)
+ *  919:     function getSpecConfParts($str, $defaultExtras)
+ *  950:     function getSpecConfParametersFromArray($pArr)
+ *  978:     function getFlexFormDS($conf,$row,$table,$fieldName='',$WSOL=TRUE)
  *
  *              SECTION: Caching related
- * 1096:     function storeHash($hash,$data,$ident)
- * 1116:     function getHash($hash,$expTime=0)
+ * 1105:     function storeHash($hash,$data,$ident)
+ * 1125:     function getHash($hash,$expTime=0)
  *
  *              SECTION: TypoScript related
- * 1152:     function getPagesTSconfig($id,$rootLine='',$returnPartArray=0)
- * 1208:     function updatePagesTSconfig($id,$pageTS,$TSconfPrefix,$impParams='')
- * 1263:     function implodeTSParams($p,$k='')
+ * 1161:     function getPagesTSconfig($id,$rootLine='',$returnPartArray=0)
+ * 1217:     function updatePagesTSconfig($id,$pageTS,$TSconfPrefix,$impParams='')
+ * 1272:     function implodeTSParams($p,$k='')
  *
  *              SECTION: Users / Groups related
- * 1300:     function getUserNames($fields='username,usergroup,usergroup_cached_list,uid',$where='')
- * 1318:     function getGroupNames($fields='title,uid', $where='')
- * 1335:     function getListGroupNames($fields='title,uid')
- * 1354:     function blindUserNames($usernames,$groupArray,$excludeBlindedFlag=0)
- * 1387:     function blindGroupNames($groups,$groupArray,$excludeBlindedFlag=0)
+ * 1309:     function getUserNames($fields='username,usergroup,usergroup_cached_list,uid',$where='')
+ * 1327:     function getGroupNames($fields='title,uid', $where='')
+ * 1344:     function getListGroupNames($fields='title,uid')
+ * 1363:     function blindUserNames($usernames,$groupArray,$excludeBlindedFlag=0)
+ * 1396:     function blindGroupNames($groups,$groupArray,$excludeBlindedFlag=0)
  *
  *              SECTION: Output related
- * 1428:     function daysUntil($tstamp)
- * 1440:     function date($tstamp)
- * 1451:     function datetime($value)
- * 1463:     function time($value)
- * 1479:     function calcAge($seconds,$labels = 'min|hrs|days|yrs')
- * 1505:     function dateTimeAge($tstamp,$prefix=1,$date='')
- * 1523:     function titleAttrib($content='',$hsc=0)
- * 1536:     function titleAltAttrib($content)
- * 1560:     function thumbCode($row,$table,$field,$backPath,$thumbScript='',$uploaddir=NULL,$abs=0,$tparams='',$size='')
- * 1628:     function getThumbNail($thumbScript,$theFile,$tparams='',$size='')
- * 1645:     function titleAttribForPages($row,$perms_clause='',$includeAttrib=1)
- * 1707:     function getRecordIconAltText($row,$table='pages')
- * 1749:     function getLabelFromItemlist($table,$col,$key)
- * 1775:     function getItemLabel($table,$col,$printAllWrap='')
- * 1800:     function getRecordTitle($table,$row,$prep=0)
- * 1838:     function getProcessedValue($table,$col,$value,$fixed_lgd_chars=0,$defaultPassthrough=0,$noRecordLookup=FALSE,$uid=0)
- * 1989:     function getProcessedValueExtra($table,$fN,$fV,$fixed_lgd_chars=0,$uid=0)
- * 2013:     function getFileIcon($ext)
- * 2027:     function getCommonSelectFields($table,$prefix='')
- * 2070:     function makeConfigForm($configArray,$defaults,$dataPrefix)
+ * 1437:     function daysUntil($tstamp)
+ * 1449:     function date($tstamp)
+ * 1460:     function datetime($value)
+ * 1472:     function time($value)
+ * 1488:     function calcAge($seconds,$labels = 'min|hrs|days|yrs')
+ * 1514:     function dateTimeAge($tstamp,$prefix=1,$date='')
+ * 1532:     function titleAttrib($content='',$hsc=0)
+ * 1545:     function titleAltAttrib($content)
+ * 1569:     function thumbCode($row,$table,$field,$backPath,$thumbScript='',$uploaddir=NULL,$abs=0,$tparams='',$size='')
+ * 1637:     function getThumbNail($thumbScript,$theFile,$tparams='',$size='')
+ * 1654:     function titleAttribForPages($row,$perms_clause='',$includeAttrib=1)
+ * 1716:     function getRecordIconAltText($row,$table='pages')
+ * 1758:     function getLabelFromItemlist($table,$col,$key)
+ * 1784:     function getItemLabel($table,$col,$printAllWrap='')
+ * 1809:     function getRecordTitle($table,$row,$prep=0)
+ * 1847:     function getProcessedValue($table,$col,$value,$fixed_lgd_chars=0,$defaultPassthrough=0,$noRecordLookup=FALSE,$uid=0)
+ * 2009:     function getProcessedValueExtra($table,$fN,$fV,$fixed_lgd_chars=0,$uid=0)
+ * 2033:     function getFileIcon($ext)
+ * 2047:     function getCommonSelectFields($table,$prefix='')
+ * 2090:     function makeConfigForm($configArray,$defaults,$dataPrefix)
  *
  *              SECTION: Backend Modules API functions
- * 2145:     function helpTextIcon($table,$field,$BACK_PATH,$force=0)
- * 2167:     function helpText($table,$field,$BACK_PATH,$styleAttrib='')
- * 2219:     function cshItem($table,$field,$BACK_PATH,$wrap='',$onlyIconMode=FALSE, $styleAttrib='')
- * 2257:     function editOnClick($params,$backPath='',$requestUri='')
- * 2276:     function viewOnClick($id,$backPath='',$rootLine='',$anchor='',$altUrl='',$addGetVars='',$switchFocus=TRUE)
- * 2308:     function getModTSconfig($id,$TSref)
- * 2329:     function getFuncMenu($mainParams,$elementName,$currentValue,$menuItems,$script='',$addparams='')
- * 2372:     function getFuncCheck($mainParams,$elementName,$currentValue,$script='',$addparams='',$tagParams='')
- * 2397:     function getFuncInput($mainParams,$elementName,$currentValue,$size=10,$script="",$addparams="")
- * 2418:     function unsetMenuItems($modTSconfig,$itemArray,$TSref)
- * 2441:     function getSetUpdateSignal($set='')
- * 2492:     function getModuleData($MOD_MENU, $CHANGED_SETTINGS, $modName, $type='', $dontValidateList='', $setDefaultList='')
+ * 2165:     function helpTextIcon($table,$field,$BACK_PATH,$force=0)
+ * 2187:     function helpText($table,$field,$BACK_PATH,$styleAttrib='')
+ * 2239:     function cshItem($table,$field,$BACK_PATH,$wrap='',$onlyIconMode=FALSE, $styleAttrib='')
+ * 2277:     function editOnClick($params,$backPath='',$requestUri='')
+ * 2296:     function viewOnClick($id,$backPath='',$rootLine='',$anchor='',$altUrl='',$addGetVars='',$switchFocus=TRUE)
+ * 2328:     function getModTSconfig($id,$TSref)
+ * 2349:     function getFuncMenu($mainParams,$elementName,$currentValue,$menuItems,$script='',$addparams='')
+ * 2392:     function getFuncCheck($mainParams,$elementName,$currentValue,$script='',$addparams='',$tagParams='')
+ * 2417:     function getFuncInput($mainParams,$elementName,$currentValue,$size=10,$script="",$addparams="")
+ * 2438:     function unsetMenuItems($modTSconfig,$itemArray,$TSref)
+ * 2461:     function getSetUpdateSignal($set='')
+ * 2512:     function getModuleData($MOD_MENU, $CHANGED_SETTINGS, $modName, $type='', $dontValidateList='', $setDefaultList='')
  *
  *              SECTION: Core
- * 2565:     function compilePreviewKeyword($getVarsStr, $beUserUid, $ttl=172800)
- * 2593:     function lockRecords($table='',$uid=0,$pid=0)
- * 2622:     function isRecordLocked($table,$uid)
- * 2662:     function exec_foreign_table_where_query($fieldValue,$field='',$TSconfig=array(),$prefix='')
- * 2743:     function getTCEFORM_TSconfig($table,$row)
- * 2794:     function getTSconfig_pidValue($table,$uid,$pid)
- * 2824:     function getPidForModTSconfig($table,$uid,$pid)
- * 2840:     function getTSCpid($table,$uid,$pid)
- * 2856:     function firstDomainRecord($rootLine)
- * 2878:     function getDomainStartPage($domain, $path='')
- * 2908:     function RTEsetup($RTEprop,$table,$field,$type='')
- * 2927:     function &RTEgetObj()
- * 2966:     function &softRefParserObj($spKey)
- * 2998:     function explodeSoftRefParserList($parserList)
- * 3030:     function isModuleSetInTBE_MODULES($modName)
- * 3053:     function referenceCount($table,$ref,$msg='')
+ * 2585:     function compilePreviewKeyword($getVarsStr, $beUserUid, $ttl=172800)
+ * 2613:     function lockRecords($table='',$uid=0,$pid=0)
+ * 2642:     function isRecordLocked($table,$uid)
+ * 2682:     function exec_foreign_table_where_query($fieldValue,$field='',$TSconfig=array(),$prefix='')
+ * 2763:     function getTCEFORM_TSconfig($table,$row)
+ * 2814:     function getTSconfig_pidValue($table,$uid,$pid)
+ * 2844:     function getPidForModTSconfig($table,$uid,$pid)
+ * 2860:     function getTSCpid($table,$uid,$pid)
+ * 2876:     function firstDomainRecord($rootLine)
+ * 2898:     function getDomainStartPage($domain, $path='')
+ * 2928:     function RTEsetup($RTEprop,$table,$field,$type='')
+ * 2947:     function &RTEgetObj()
+ * 2986:     function &softRefParserObj($spKey)
+ * 3018:     function explodeSoftRefParserList($parserList)
+ * 3050:     function isModuleSetInTBE_MODULES($modName)
+ * 3073:     function referenceCount($table,$ref,$msg='')
  *
  *              SECTION: Workspaces / Versioning
- * 3112:     function selectVersionsOfRecord($table, $uid, $fields='*', $workspace=0)
- * 3160:     function fixVersioningPid($table,&$rr,$ignoreWorkspaceMatch=FALSE)
- * 3200:     function workspaceOL($table,&$row,$wsid=-99)
- * 3248:     function getWorkspaceVersionOfRecord($workspace, $table, $uid, $fields='*')
- * 3277:     function getLiveVersionOfRecord($table,$uid,$fields='*')
- * 3299:     function isPidInVersionizedBranch($pid, $table='',$returnStage=FALSE)
- * 3322:     function versioningPlaceholderClause($table)
- * 3336:     function countVersionsOfRecordsOnPage($workspace,$pageId, $allTables=FALSE)
- * 3371:     function wsMapId($table,$uid)
+ * 3132:     function selectVersionsOfRecord($table, $uid, $fields='*', $workspace=0)
+ * 3180:     function fixVersioningPid($table,&$rr,$ignoreWorkspaceMatch=FALSE)
+ * 3220:     function workspaceOL($table,&$row,$wsid=-99)
+ * 3268:     function getWorkspaceVersionOfRecord($workspace, $table, $uid, $fields='*')
+ * 3297:     function getLiveVersionOfRecord($table,$uid,$fields='*')
+ * 3319:     function isPidInVersionizedBranch($pid, $table='',$returnStage=FALSE)
+ * 3342:     function versioningPlaceholderClause($table)
+ * 3356:     function countVersionsOfRecordsOnPage($workspace,$pageId, $allTables=FALSE)
+ * 3391:     function wsMapId($table,$uid)
  *
  *              SECTION: Miscellaneous
- * 3401:     function typo3PrintError($header,$text,$js='',$head=1)
- * 3445:     function TYPO3_copyRightNotice()
- * 3469:     function displayWarningMessages()
- * 3526:     function getPathType_web_nonweb($path)
- * 3538:     function ADMCMD_previewCmds($pageinfo)
- * 3560:     function processParams($params)
- * 3586:     function getListOfBackendModules($name,$perms_clause,$backPath='',$script='index.php')
+ * 3421:     function typo3PrintError($header,$text,$js='',$head=1)
+ * 3465:     function TYPO3_copyRightNotice()
+ * 3489:     function displayWarningMessages()
+ * 3546:     function getPathType_web_nonweb($path)
+ * 3558:     function ADMCMD_previewCmds($pageinfo)
+ * 3580:     function processParams($params)
+ * 3606:     function getListOfBackendModules($name,$perms_clause,$backPath='',$script='index.php')
  *
  * TOTAL FUNCTIONS: 99
  * (This index is automatically created/updated by the extension "extdeveval")
@@ -224,11 +224,16 @@ class t3lib_BEfunc        {
         * @param       integer         UID of record
         * @param       string          List of fields to select
         * @param       string          Additional WHERE clause, eg. " AND blablabla=0"
+        * @param       boolean         Use the deleteClause to check if a record is deleted (default true)
         * @return      array           Returns the row if found, otherwise nothing
         */
-       function getRecord($table,$uid,$fields='*',$where='')   {
+       function getRecord($table,$uid,$fields='*',$where='',$useDeleteClause=true)     {
                if ($GLOBALS['TCA'][$table])    {
-                       $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery($fields, $table, 'uid='.intval($uid).t3lib_BEfunc::deleteClause($table).$where);
+                       $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery(
+                               $fields,
+                               $table,
+                               'uid='.intval($uid).($useDeleteClause ? t3lib_BEfunc::deleteClause($table) : '').$where
+                       );
                        if ($row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res)) {
                                return $row;
                        }
@@ -242,12 +247,13 @@ class t3lib_BEfunc        {
         * @param       integer         UID of record
         * @param       string          List of fields to select
         * @param       string          Additional WHERE clause, eg. " AND blablabla=0"
+        * @param       boolean         Use the deleteClause to check if a record is deleted (default true)
         * @return      array           Returns the row if found, otherwise nothing
         */
-       function getRecordWSOL($table,$uid,$fields='*',$where='') {
+       function getRecordWSOL($table,$uid,$fields='*',$where='',$useDeleteClause=true) {
                if ($fields !== '*') {
                        $internalFields = t3lib_div::uniqueList($fields.',uid,pid'.($table == 'pages' ? ',t3ver_swapmode' : ''));
-                       $row = t3lib_BEfunc::getRecord($table,$uid,$internalFields,$where);
+                       $row = t3lib_BEfunc::getRecord($table,$uid,$internalFields,$where,$useDeleteClause);
                        t3lib_BEfunc::workspaceOL($table,$row);
 
                        if (is_array ($row)) {
@@ -297,16 +303,17 @@ class t3lib_BEfunc        {
         * @param       string          Optional GROUP BY field(s), if none, supply blank string.
         * @param       string          Optional ORDER BY field(s), if none, supply blank string.
         * @param       string          Optional LIMIT value ([begin,]max), if none, supply blank string.
+        * @param       boolean         Use the deleteClause to check if a record is deleted (default true)
         * @return      mixed           Multidimensional array with selected records (if any is selected)
         */
-       function getRecordsByField($theTable,$theField,$theValue,$whereClause='',$groupBy='',$orderBy='',$limit='')     {
+       function getRecordsByField($theTable,$theField,$theValue,$whereClause='',$groupBy='',$orderBy='',$limit='',$useDeleteClause=true)       {
                global $TCA;
                if (is_array($TCA[$theTable])) {
                        $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery(
                                                '*',
                                                $theTable,
                                                $theField.'='.$GLOBALS['TYPO3_DB']->fullQuoteStr($theValue, $theTable).
-                                                       t3lib_BEfunc::deleteClause($theTable).' '.
+                                                       ($useDeleteClause ? t3lib_BEfunc::deleteClause($theTable).' ' : '').
                                                        t3lib_BEfunc::versioningPlaceholderClause($theTable).' '.
                                                        $whereClause,   // whereClauseMightContainGroupOrderBy
                                                $groupBy,
index 9f7e9aa..7a33cb0 100755 (executable)
  *
  *
  *
- *   72: class t3lib_loadDBGroup
- *   99:     function start($itemlist,$tablelist, $MMtable='',$MMuid=0)
- *  140:     function readList($itemlist)
- *  186:     function readMM($tableName,$uid)
- *  215:     function writeMM($tableName,$uid,$prependTableName=0)
- *  251:     function getValueArray($prependTableName='')
- *  279:     function convertPosNeg($valueArray,$fTable,$nfTable)
- *  301:     function getFromDB()
- *  333:     function readyForInterface()
+ *   76: class t3lib_loadDBGroup
+ *  111:     function start($itemlist, $tablelist, $MMtable='', $MMuid=0, $currentTable='', $conf=array())
+ *  179:     function readList($itemlist)
+ *  225:     function readMM($tableName,$uid)
+ *  276:     function writeMM($tableName,$uid,$prependTableName=0)
+ *  352:     function readForeignField($uid, $conf)
+ *  435:     function writeForeignField($conf, $parentUid, $updateToUid=0)
+ *  510:     function getValueArray($prependTableName='')
+ *  538:     function convertPosNeg($valueArray,$fTable,$nfTable)
+ *  560:     function getFromDB()
+ *  595:     function readyForInterface()
+ *  621:     function countItems($returnAsArray = true)
+ *  636:     function updateRefIndex($table,$id)
  *
- * TOTAL FUNCTIONS: 8
+ * TOTAL FUNCTIONS: 12
  * (This index is automatically created/updated by the extension "extdeveval")
  *
  */
@@ -90,6 +94,7 @@ class t3lib_loadDBGroup       {
        var $MM_oppositeFieldConf = ''; // only set if MM_is_foreign is set
        var $MM_isMultiTableRelationship = 0;   // is empty by default; if MM_is_foreign is set and there is more than one table allowed (on the "local" side), then it contains the first table (as a fallback)
        var $currentTable;      // current table => Only needed for reverse relations
+       var $undeleteRecord;            // if a record should be undeleted (so do not use the $useDeleteClause on t3lib_BEfunc)
 
 
        /**
@@ -156,6 +161,9 @@ class t3lib_loadDBGroup     {
                        // Now, populate the internal itemArray and tableArray arrays:
                if ($MMtable)   {       // If MM, then call this function to do that:
                        $this->readMM($MMtable,$MMuid);
+               } elseif ($MMuid && $conf['foreign_field']) {
+                               // If not MM but foreign_field, the read the records by the foreign_field
+                       $this->readForeignField($MMuid, $conf);
                } else {
                                // If not MM, then explode the itemlist by "," and traverse the list:
                        $this->readList($itemlist);
@@ -353,6 +361,166 @@ class t3lib_loadDBGroup   {
        }
 
        /**
+        * Reads items from a foreign_table, that has a foreign_field (uid of the parent record) and
+        * stores the parts in the internal array itemArray and tableArray.
+        *
+        * @param       integer         $uid: The uid of the parent record (this value is also on the foreign_table in the foreign_field)
+        * @param       array           $conf: TCA configuration for current field
+        * @return      void
+        */
+       function readForeignField($uid, $conf) {
+               $key = 0;
+               $uid = intval($uid);
+               $foreign_table = $conf['foreign_table'];
+               $useDeleteClause = $this->undeleteRecord ? false : true;
+
+               if ($conf['foreign_sortby'])                                                                            // specific sortby for data handled by this field
+                       $sortby = $conf['foreign_sortby'];
+               elseif ($GLOBALS['TCA'][$foreign_table]['ctrl']['sortby'])                      // specific sortby for all table records
+                       $sortby = $GLOBALS['TCA'][$foreign_table]['ctrl']['sortby'];
+               elseif ($GLOBALS['TCA'][$foreign_table]['ctrl']['default_sortby'])      // default sortby for all table records
+                       $sortby = $GLOBALS['TCA'][$foreign_table]['ctrl']['default_sortby'];
+
+                       // strip a possible "ORDER BY" in front of the $sortby value
+               $sortby = $GLOBALS['TYPO3_DB']->stripOrderBy($sortby);
+
+                       // get the rows from storage
+               $rows = t3lib_BEfunc::getRecordsByField($foreign_table,$conf['foreign_field'],$uid,'','',$sortby,'',$useDeleteClause);
+
+                       // Handle symmetric relations
+               if ($conf['symmetric_field']) {
+                       $symSortby = $conf['symmetric_sortby'] ? $conf['symmetric_sortby'] : $sortby;
+                       $symRows = t3lib_BEfunc::getRecordsByField($foreign_table,$conf['symmetric_field'],$uid,'','',$symSortby,'',$useDeleteClause);
+
+                       if (count($symRows)) {
+                                       // if there are rows and symRows, we have to merge them, but keeping the sorting order
+                               if (count($rows)) {
+                                       $newRows = array();
+                                       reset($rows);
+                                       reset($symRows);
+
+                                       $row = current($rows);
+                                       $symRow = current($symRows);
+
+                                       while (is_array($row)) {
+                                                       // if the sorting value of the symRow is lower than the row sorting value, insert symRow before
+                                               while (is_array($symRow) && $symRow[$symSortby] <= $row[$sortby]) {
+                                                       $newRows[] = $symRow;
+                                                       $symRow = next($symRows);
+                                               }
+
+                                                       // all better sorted symRows have been processed, now add the row itself
+                                               $newRows[] = $row;
+                                               $row = next($rows);
+
+                                                       // if there are no more rows, paste all remaining symRows
+                                               if ($row == false && $symRow != false) {
+                                                       while (is_array($symRow)) {
+                                                               $newRows[] = $symRow;
+                                                               $symRow = next($symRows);
+                                                       }
+                                               }
+                                       }
+
+                                               // set the rows value to the new ordered array
+                                       $rows = $newRows;
+
+                                       // there are no rows, just symRows, so we use them - sorting comes from database
+                               } else {
+                                       $rows = $symRows;
+
+                               }
+                       }
+               }
+
+               if (count($rows)) {
+                       foreach ($rows as $row) {
+                               $this->itemArray[$key]['id'] = $row['uid'];
+                               $this->itemArray[$key]['table'] = $conf['foreign_table'];
+                               $this->tableArray[$theTable][]= $row['uid'];
+                               $key++;
+                       }
+               }
+       }
+
+       /**
+        * Write the sorting values to a foreign_table, that has a foreign_field (uid of the parent record)
+        *
+        * @param       array           $conf: TCA configuration for current field
+        * @param       integer         $parentUid: The uid of the parent record
+        * @param       boolean         $updateForeignField: Whether to update the foreign field with the $parentUid (on Copy)
+        * @return      void
+        */
+       function writeForeignField($conf, $parentUid, $updateToUid=0) {
+               $c = 0;
+               $foreign_table = $conf['foreign_table'];
+               $foreign_field = $conf['foreign_field'];
+               $symmetric_field = $conf['symmetric_field'];
+
+                       // if there are table items and we have a proper $parentUid
+               if (t3lib_div::testInt($parentUid) && count($this->tableArray)) {
+                               // if updateToUid is not a positive integer, set it to '0', so it will be ignored
+                       if (!(t3lib_div::testInt($updateToUid) && $updateToUid > 0)) $updateToUid = 0;
+                       $fields = 'uid,'.$foreign_field.($symmetric_field ? ','.$symmetric_field : '');
+
+                               // update all items
+                       foreach ($this->itemArray as $val) {
+                               $uid = $val['id'];
+                               $table = $val['table'];
+
+                                       // fetch the current (not overwritten) relation record if we should handle symmetric relations
+                               if ($conf['symmetric_field']) {
+                                       $row = t3lib_BEfunc::getRecord($table,$uid,$fields,'',false);
+                                       $isOnSymmetricSide = self::isOnSymmetricSide($parentUid, $conf, $row);
+                               }
+
+                               $updateValues = array();
+
+                                       // no update to a foreign_field/symmetric_field pointer is requested -> just sorting
+                               if (!$updateToUid) {
+                                               // Always add the pointer to the parent uid
+                                       if ($isOnSymmetricSide) {
+                                               $updateValues[$symmetric_field] = $parentUid;
+                                       } else {
+                                               $updateValues[$foreign_field] = $parentUid;
+                                       }
+
+                                               // specific sortby for data handled by this field
+                                       if ($conf['foreign_sortby']) {
+                                               $sortby = $conf['foreign_sortby'];
+                                       } elseif ($GLOBALS['TCA'][$foreign_table]['ctrl']['sortby']) { // specific sortby for all table records
+                                               $sortby = $GLOBALS['TCA'][$foreign_table]['ctrl']['sortby'];
+                                       }
+                                               // strip a possible "ORDER BY" in front of the $sortby value
+                                       $sortby = $GLOBALS['TYPO3_DB']->stripOrderBy($sortby);
+                                       $symSortby = $conf['symmetric_sortby'];
+
+                                               // set the sorting on the right side, it depends on who created the relation, so what uid is in the symmetric_field
+                                       if ($isOnSymmetricSide && $symSortby) {
+                                               $updateValues[$symSortby] = ++$c;
+                                       } elseif ($sortby) {
+                                               $updateValues[$sortby] = ++$c;
+                                       }
+
+                                       // update to a foreign_field/symmetric_field pointer is requested, normally used on record copies
+                                       // only update the fields, if the old uid is found somewhere - for select fields, TCEmain is doing this already!
+                               } else {
+                                       if ($isOnSymmetricSide) {
+                                               $updateValues[$symmetric_field] = $updateToUid;
+                                       } else {
+                                               $updateValues[$foreign_field] = $updateToUid;
+                                       }
+                               }
+
+                               if (count($updateValues)) {
+                                       $GLOBALS['TYPO3_DB']->exec_UPDATEquery($table, "uid='$uid'", $updateValues);
+                                       $this->updateRefIndex($table, $uid);
+                               }
+                       }
+               }
+       }
+
+       /**
         * After initialization you can extract an array of the elements from the object. Use this function for that.
         *
         * @param       boolean         If set, then table names will ALWAYS be prepended (unless its a _NO_TABLE value)
@@ -462,6 +630,46 @@ class t3lib_loadDBGroup    {
                }
                return implode(',',$output);
        }
+
+       /**
+        * Counts the items in $this->itemArray and puts this value in an array by default.
+        *
+        * @param       boolean         Whether to put the count value in an array
+        * @return      mixed           The plain count as integer or the same inside an array
+        */
+       function countItems($returnAsArray = true) {
+               $count = count($this->itemArray);
+               if ($returnAsArray) $count = array($count);
+               return $count;
+       }
+
+       /**
+        * Update Reference Index (sys_refindex) for a record
+        * Should be called any almost any update to a record which could affect references inside the record.
+        * (copied from TCEmain)
+        *
+        * @param       string          Table name
+        * @param       integer         Record UID
+        * @return      void
+        */
+       function updateRefIndex($table,$id)     {
+               $refIndexObj = t3lib_div::makeInstance('t3lib_refindex');
+               $result = $refIndexObj->updateRefIndexTable($table,$id);
+       }
+
+       /**
+        * Checks, if we're looking from the "other" side, the symmetric side, to a symmetric relation.
+        *
+        * @param       string          $parentUid: The uid of the parent record
+        * @param       array           $parentConf: The TCA configuration of the parent field embedding the child records
+        * @param       array           $childRec: The record row of the child record
+        * @return      boolean         Returns true if looking from the symmetric ("other") side to the relation.
+        */
+       static function isOnSymmetricSide($parentUid, $parentConf, $childRec) {
+               return t3lib_div::testInt($childRec['uid']) && $parentConf['symmetric_field'] && $parentUid == $childRec[$parentConf['symmetric_field']]
+                       ? true
+                       : false;
+       }
 }
 
 
index 83247be..3ecb662 100755 (executable)
 *  This copyright notice MUST APPEAR in all copies of the script!
 ***************************************************************/
 /**
- * Reference index processing
- *
- * $Id$
- *
- * @author     Kasper Skaarhoj <kasperYYYY@typo3.com>
- */
-/**
  * [CLASS/FUNCTION INDEX of SCRIPT]
  *
  *
@@ -209,8 +202,12 @@ class t3lib_refindex {
                                                // Based on type,
                                        switch((string)$dat['type'])    {
                                                case 'db':
+                                               case 'select':
                                                        $this->createEntryData_dbRels($table,$uid,$fieldname,'',$deleted,$dat['itemArray']);
                                                break;
+                                               case 'inline':
+                                                       $this->createEntryData_inlineRels($table,$uid,$fieldname,'',$deleted,$dat['itemArray']);
+                                               break;
                                                case 'file':
                                                        $this->createEntryData_fileRels($table,$uid,$fieldname,'',$deleted,$dat['newValueFiles']);
                                                break;
@@ -418,7 +415,7 @@ class t3lib_refindex {
                                }
 
                                        // Add DB:
-                               if ($result = $this->getRelations_procDB($value, $conf, $uid))  {
+                               if ($result = $this->getRelations_procDB($value, $conf, $uid, $table))  {
                                                // Create an entry for the field with all DB relations:
                                        $outRow[$field] = array(
                                                'type' => 'db',
@@ -589,9 +586,10 @@ class t3lib_refindex {
         * @param       string          Field value
         * @param       array           Field configuration array of type "TCA/columns"
         * @param       integer         Field uid
+        * @param       string          Table name
         * @return      array           If field type is OK it will return an array with the database relations. Else false
         */
-       function getRelations_procDB($value, $conf, $uid)       {
+       function getRelations_procDB($value, $conf, $uid, $table = '')  {
 
                        // DB record lists:
                if ($this->isReferenceField($conf))     {
@@ -603,7 +601,7 @@ class t3lib_refindex {
                        }
 
                        $dbAnalysis = t3lib_div::makeInstance('t3lib_loadDBGroup');
-                       $dbAnalysis->start($value,$allowedTables,$conf['MM'],$uid);
+                       $dbAnalysis->start($value,$allowedTables,$conf['MM'],$uid,$table,$conf);
 
                        return $dbAnalysis->itemArray;
                }
@@ -963,7 +961,7 @@ class t3lib_refindex {
         * @return      boolean         True if DB reference field (group/db or select with foreign-table)
         */
        function isReferenceField($conf)        {
-               return ($conf['type']=='group' && $conf['internal_type']=='db') ||      ($conf['type']=='select' && $conf['foreign_table']);
+               return ($conf['type']=='group' && $conf['internal_type']=='db') || (($conf['type']=='select' || $conf['type']=='inline') && $conf['foreign_table']);
        }
 
        /**
index c70c9a8..b42013a 100755 (executable)
  *
  *
  *
- *  195: class t3lib_TCEforms
- *  300:     function t3lib_TCEforms()
- *  334:     function initDefaultBEmode()
+ *  196: class t3lib_TCEforms
+ *  302:     function t3lib_TCEforms()
+ *  338:     function initDefaultBEmode()
  *
  *              SECTION: Rendering the forms, fields etc
- *  379:     function getSoloField($table,$row,$theFieldToReturn)
- *  418:     function getMainFields($table,$row,$depth=0)
- *  613:     function getListedFields($table,$row,$list)
- *  654:     function getPaletteFields($table,$row,$palette,$header='',$itemList='',$collapsedHeader='')
- *  730:     function getSingleField($table,$field,$row,$altName='',$palette=0,$extra='',$pal=0)
- *  886:     function getSingleField_SW($table,$field,$row,&$PA)
+ *  385:     function getSoloField($table,$row,$theFieldToReturn)
+ *  424:     function getMainFields($table,$row,$depth=0)
+ *  618:     function getListedFields($table,$row,$list)
+ *  660:     function getPaletteFields($table,$row,$palette,$header='',$itemList='',$collapsedHeader='')
+ *  737:     function getSingleField($table,$field,$row,$altName='',$palette=0,$extra='',$pal=0)
+ *  900:     function getSingleField_SW($table,$field,$row,&$PA)
  *
  *              SECTION: Rendering of each TCEform field type
- *  959:     function getSingleField_typeInput($table,$field,$row,&$PA)
- * 1040:     function getSingleField_typeText($table,$field,$row,&$PA)
- * 1153:     function getSingleField_typeCheck($table,$field,$row,&$PA)
- * 1219:     function getSingleField_typeRadio($table,$field,$row,&$PA)
- * 1254:     function getSingleField_typeSelect($table,$field,$row,&$PA)
- * 1334:     function getSingleField_typeSelect_single($table,$field,$row,&$PA,$config,$selItems,$nMV_label)
- * 1451:     function getSingleField_typeSelect_checkbox($table,$field,$row,&$PA,$config,$selItems,$nMV_label)
- * 1570:     function getSingleField_typeSelect_singlebox($table,$field,$row,&$PA,$config,$selItems,$nMV_label)
- * 1680:     function getSingleField_typeSelect_multiple($table,$field,$row,&$PA,$config,$selItems,$nMV_label)
- * 1779:     function getSingleField_typeGroup($table,$field,$row,&$PA)
- * 1948:     function getSingleField_typeNone($table,$field,$row,&$PA)
- * 1964:     function getSingleField_typeNone_render($config,$itemValue)
- * 2026:     function getSingleField_typeFlex($table,$field,$row,&$PA)
- * 2164:     function getSingleField_typeFlex_langMenu($languages,$elName,$selectedLanguage,$multi=1)
- * 2183:     function getSingleField_typeFlex_sheetMenu($sArr,$elName,$sheetKey)
- * 2218:     function getSingleField_typeFlex_draw($dataStruct,$editData,$cmdData,$table,$field,$row,&$PA,$formPrefix='',$level=0,$tRows=array())
- * 2411:     function getSingleField_typeUnknown($table,$field,$row,&$PA)
- * 2426:     function getSingleField_typeUser($table,$field,$row,&$PA)
+ *  976:     function getSingleField_typeInput($table,$field,$row,&$PA)
+ * 1057:     function getSingleField_typeText($table,$field,$row,&$PA)
+ * 1178:     function getSingleField_typeCheck($table,$field,$row,&$PA)
+ * 1244:     function getSingleField_typeRadio($table,$field,$row,&$PA)
+ * 1279:     function getSingleField_typeSelect($table,$field,$row,&$PA)
+ * 1359:     function getSingleField_typeSelect_single($table,$field,$row,&$PA,$config,$selItems,$nMV_label)
+ * 1490:     function getSingleField_typeSelect_checkbox($table,$field,$row,&$PA,$config,$selItems,$nMV_label)
+ * 1609:     function getSingleField_typeSelect_singlebox($table,$field,$row,&$PA,$config,$selItems,$nMV_label)
+ * 1719:     function getSingleField_typeSelect_multiple($table,$field,$row,&$PA,$config,$selItems,$nMV_label)
+ * 1823:     function getSingleField_typeGroup($table,$field,$row,&$PA)
+ * 1992:     function getSingleField_typeNone($table,$field,$row,&$PA)
+ * 2008:     function getSingleField_typeNone_render($config,$itemValue)
+ * 2070:     function getSingleField_typeFlex($table,$field,$row,&$PA)
+ * 2205:     function getSingleField_typeFlex_langMenu($languages,$elName,$selectedLanguage,$multi=1)
+ * 2224:     function getSingleField_typeFlex_sheetMenu($sArr,$elName,$sheetKey)
+ * 2259:     function getSingleField_typeFlex_draw($dataStruct,$editData,$cmdData,$table,$field,$row,&$PA,$formPrefix='',$level=0,$tRows=array())
+ * 2452:     function getSingleField_typeUnknown($table,$field,$row,&$PA)
+ * 2467:     function getSingleField_typeUser($table,$field,$row,&$PA)
  *
  *              SECTION: Field content processing
- * 2455:     function formatValue ($config, $itemValue)
+ * 2496:     function formatValue ($config, $itemValue)
  *
  *              SECTION: "Configuration" fetching/processing functions
- * 2547:     function getRTypeNum($table,$row)
- * 2573:     function rearrange($fields)
- * 2599:     function getExcludeElements($table,$row,$typeNum)
- * 2647:     function getFieldsToAdd($table,$row,$typeNum)
- * 2672:     function mergeFieldsWithAddedFields($fields,$fieldsToAdd)
- * 2704:     function setTSconfig($table,$row,$field='')
- * 2726:     function getSpecConfForField($table,$row,$field)
- * 2747:     function getSpecConfFromString($extraString, $defaultExtras)
+ * 2588:     function getRTypeNum($table,$row)
+ * 2614:     function rearrange($fields)
+ * 2640:     function getExcludeElements($table,$row,$typeNum)
+ * 2688:     function getFieldsToAdd($table,$row,$typeNum)
+ * 2713:     function mergeFieldsWithAddedFields($fields,$fieldsToAdd)
+ * 2745:     function setTSconfig($table,$row,$field='')
+ * 2767:     function getSpecConfForField($table,$row,$field)
+ * 2788:     function getSpecConfFromString($extraString, $defaultExtras)
  *
  *              SECTION: Display of localized content etc.
- * 2775:     function registerDefaultLanguageData($table,$rec)
- * 2807:     function getLanguageOverlayRawValue($table, $row, $field, $fieldConf)
- * 2835:     function renderDefaultLanguageContent($table,$field,$row,$item)
- * 2858:     function renderDefaultLanguageDiff($table,$field,$row,$item)
+ * 2816:     function registerDefaultLanguageData($table,$rec)
+ * 2848:     function getLanguageOverlayRawValue($table, $row, $field, $fieldConf)
+ * 2876:     function renderDefaultLanguageContent($table,$field,$row,$item)
+ * 2899:     function renderDefaultLanguageDiff($table,$field,$row,$item)
  *
  *              SECTION: Form element helper functions
- * 2914:     function dbFileIcons($fName,$mode,$allowed,$itemArray,$selector='',$params=array(),$onFocus='')
- * 3067:     function getClipboardElements($allowed,$mode)
- * 3116:     function getClickMenu($str,$table,$uid='')
- * 3137:     function renderWizards($itemKinds,$wizConf,$table,$row,$field,&$PA,$itemName,$specConf,$RTE=0)
- * 3341:     function getIcon($icon)
- * 3368:     function optionTagStyle($iconString)
- * 3384:     function extractValuesOnlyFromValueLabelList($itemFormElValue)
- * 3406:     function wrapOpenPalette($header,$table,$row,$palette,$retFunc=0)
- * 3430:     function checkBoxParams($itemName,$thisValue,$c,$iCount,$addFunc='')
- * 3444:     function elName($itemName)
- * 3455:     function noTitle($str,$wrapParts=array())
- * 3464:     function blur()
- * 3473:     function thisReturnUrl()
- * 3486:     function getSingleHiddenField($table,$field,$row)
- * 3508:     function formWidth($size=48,$textarea=0)
- * 3535:     function formWidthText($size=48,$wrap='')
- * 3551:     function formElStyle($type)
- * 3562:     function formElClass($type)
- * 3573:     function formElStyleClassValue($type, $class=FALSE)
- * 3595:     function insertDefStyle($type)
- * 3614:     function getDynTabMenu($parts, $idString)
+ * 2955:     function dbFileIcons($fName,$mode,$allowed,$itemArray,$selector='',$params=array(),$onFocus='')
+ * 3108:     function getClipboardElements($allowed,$mode)
+ * 3157:     function getClickMenu($str,$table,$uid='')
+ * 3178:     function renderWizards($itemKinds,$wizConf,$table,$row,$field,&$PA,$itemName,$specConf,$RTE=0)
+ * 3382:     function getIcon($icon)
+ * 3409:     function optionTagStyle($iconString)
+ * 3425:     function extractValuesOnlyFromValueLabelList($itemFormElValue)
+ * 3447:     function wrapOpenPalette($header,$table,$row,$palette,$retFunc=0)
+ * 3471:     function checkBoxParams($itemName,$thisValue,$c,$iCount,$addFunc='')
+ * 3485:     function elName($itemName)
+ * 3496:     function noTitle($str,$wrapParts=array())
+ * 3505:     function blur()
+ * 3514:     function thisReturnUrl()
+ * 3527:     function getSingleHiddenField($table,$field,$row)
+ * 3549:     function formWidth($size=48,$textarea=0)
+ * 3576:     function formWidthText($size=48,$wrap='')
+ * 3592:     function formElStyle($type)
+ * 3603:     function formElClass($type)
+ * 3614:     function formElStyleClassValue($type, $class=FALSE)
+ * 3638:     function insertDefStyle($type)
+ * 3657:     function getDynTabMenu($parts, $idString)
  *
  *              SECTION: Item-array manipulation functions (check/select/radio)
- * 3653:     function initItemArray($fieldValue)
- * 3671:     function addItems($items,$iArray)
- * 3693:     function procItems($items,$iArray,$config,$table,$row,$field)
- * 3717:     function addSelectOptionsToItemArray($items,$fieldValue,$TSconfig,$field)
- * 3937:     function addSelectOptionsToItemArray_makeModuleData($value)
- * 3959:     function foreignTable($items,$fieldValue,$TSconfig,$field,$pFFlag=0)
+ * 3696:     function initItemArray($fieldValue)
+ * 3714:     function addItems($items,$iArray)
+ * 3736:     function procItems($items,$iArray,$config,$table,$row,$field)
+ * 3760:     function addSelectOptionsToItemArray($items,$fieldValue,$TSconfig,$field)
+ * 3980:     function addSelectOptionsToItemArray_makeModuleData($value)
+ * 4002:     function foreignTable($items,$fieldValue,$TSconfig,$field,$pFFlag=0)
  *
  *              SECTION: Template functions
- * 4040:     function setNewBEDesign()
- * 4095:     function intoTemplate($inArr,$altTemplate='')
- * 4119:     function addUserTemplateMarkers($marker,$table,$field,$row,&$PA)
- * 4130:     function wrapLabels($str)
- * 4143:     function wrapTotal($c,$rec,$table)
- * 4156:     function replaceTableWrap($arr,$rec,$table)
- * 4194:     function wrapBorder(&$out_array,&$out_pointer)
- * 4216:     function rplColorScheme($inTemplate)
- * 4236:     function getDivider()
- * 4246:     function printPalette($palArr)
- * 4297:     function helpTextIcon($table,$field,$force=0)
- * 4317:     function helpText($table,$field)
- * 4338:     function setColorScheme($scheme)
- * 4362:     function resetSchemes()
- * 4373:     function storeSchemes()
- * 4385:     function restoreSchemes()
+ * 4083:     function setNewBEDesign()
+ * 4138:     function intoTemplate($inArr,$altTemplate='')
+ * 4162:     function addUserTemplateMarkers($marker,$table,$field,$row,&$PA)
+ * 4173:     function wrapLabels($str)
+ * 4186:     function wrapTotal($c,$rec,$table)
+ * 4199:     function replaceTableWrap($arr,$rec,$table)
+ * 4236:     function wrapBorder(&$out_array,&$out_pointer)
+ * 4258:     function rplColorScheme($inTemplate)
+ * 4278:     function getDivider()
+ * 4288:     function printPalette($palArr)
+ * 4339:     function helpTextIcon($table,$field,$force=0)
+ * 4359:     function helpText($table,$field)
+ * 4380:     function setColorScheme($scheme)
+ * 4404:     function resetSchemes()
+ * 4415:     function storeSchemes()
+ * 4427:     function restoreSchemes()
  *
  *              SECTION: JavaScript related functions
- * 4415:     function JStop()
- * 4466:     function JSbottom($formname='forms[0]')
- * 4780:     function dbFileCon($formObj='document.forms[0]')
- * 4982:     function printNeededJSFunctions()
- * 5009:     function printNeededJSFunctions_top()
+ * 4457:     function JStop()
+ * 4508:     function JSbottom($formname='forms[0]')
+ * 4835:     function dbFileCon($formObj='document.forms[0]')
+ * 5053:     function printNeededJSFunctions()
+ * 5080:     function printNeededJSFunctions_top()
  *
  *              SECTION: Various helper functions
- * 5057:     function getDefaultRecord($table,$pid=0)
- * 5096:     function getRecordPath($table,$rec)
- * 5110:     function readPerms()
- * 5124:     function sL($str)
- * 5137:     function getLL($str)
- * 5158:     function isPalettesCollapsed($table,$palette)
- * 5174:     function isDisplayCondition($displayCond,$row,$ffValueKey='')
- * 5275:     function getTSCpid($table,$uid,$pid)
- * 5289:     function doLoadTableDescr($table)
- * 5301:     function getAvailableLanguages($onlyIsoCoded=1,$setDefault=1)
+ * 5128:     function getDefaultRecord($table,$pid=0)
+ * 5167:     function getRecordPath($table,$rec)
+ * 5181:     function readPerms()
+ * 5195:     function sL($str)
+ * 5208:     function getLL($str)
+ * 5229:     function isPalettesCollapsed($table,$palette)
+ * 5245:     function isDisplayCondition($displayCond,$row,$ffValueKey='')
+ * 5349:     function getTSCpid($table,$uid,$pid)
+ * 5363:     function doLoadTableDescr($table)
+ * 5375:     function getAvailableLanguages($onlyIsoCoded=1,$setDefault=1)
  *
  *
- * 5343: class t3lib_TCEforms_FE extends t3lib_TCEforms
- * 5351:     function wrapLabels($str)
- * 5361:     function printPalette($palArr)
- * 5386:     function setFancyDesign()
+ * 5417: class t3lib_TCEforms_FE extends t3lib_TCEforms
+ * 5425:     function wrapLabels($str)
+ * 5435:     function printPalette($palArr)
+ * 5460:     function setFancyDesign()
  *
  * TOTAL FUNCTIONS: 100
  * (This index is automatically created/updated by the extension "extdeveval")
 
 
 require_once(PATH_t3lib.'class.t3lib_diff.php');
+require_once(PATH_t3lib.'class.t3lib_tceforms_inline.php');
 
 
 
@@ -240,6 +241,7 @@ class t3lib_TCEforms        {
 
                // INTERNAL, static
        var $prependFormFieldNames = 'data';            // The string to prepend formfield names with.
+       var $prependCmdFieldNames = 'cmd';                      // The string to prepend commands for tcemain::process_cmdmap with.
        var $prependFormFieldNames_file = 'data_files';         // The string to prepend FILE form field names with.
        var $formName = 'editform';                                     // The name attribute of the form.
 
@@ -286,7 +288,7 @@ class t3lib_TCEforms        {
        var $additionalJS_post = array();                       // Additional JavaScript printed after the form
        var $additionalJS_submit = array();                     // Additional JavaScript executed on submit; If you set "OK" variable it will raise an error about RTEs not being loaded and offer to block further submission.
 
-
+       var $inline;                                                            // Instance of t3lib_tceforms_inline
 
 
 
@@ -324,6 +326,8 @@ class t3lib_TCEforms        {
                        // Setting the current colorScheme to default.
                $this->defColorScheme = $this->colorScheme;
                $this->defClassScheme = $this->classScheme;
+
+               $this->inline = t3lib_div::makeInstance('t3lib_TCEforms_inline');
        }
 
        /**
@@ -341,6 +345,8 @@ class t3lib_TCEforms        {
 
                $this->edit_docModuleUpload = $BE_USER->uc['edit_docModuleUpload'];
                $this->titleLen = $BE_USER->uc['titleLen'];
+
+               $this->inline->init($this);
        }
 
 
@@ -745,8 +751,11 @@ class t3lib_TCEforms       {
                $PA['fieldConf'] = $TCA[$table]['columns'][$field];
                $PA['fieldConf']['config']['form_type'] = $PA['fieldConf']['config']['form_type'] ? $PA['fieldConf']['config']['form_type'] : $PA['fieldConf']['config']['type'];       // Using "form_type" locally in this script
 
+               $skipThisField = $this->inline->skipField($table, $field, $row, $PA['fieldConf']['config']);
+
                        // Now, check if this field is configured and editable (according to excludefields + other configuration)
                if (    is_array($PA['fieldConf']) &&
+                               !$skipThisField &&
                                (!$PA['fieldConf']['exclude'] || $BE_USER->check('non_exclude_fields',$table.':'.$field)) &&
                                $PA['fieldConf']['config']['form_type']!='passthrough' &&
                                ($this->RTEenabled || !$PA['fieldConf']['config']['showIfRTE']) &&
@@ -812,6 +821,10 @@ class t3lib_TCEforms       {
                                        $PA['fieldChangeFunc']=array();
                                        $PA['fieldChangeFunc']['TBE_EDITOR_fieldChanged'] = "TBE_EDITOR_fieldChanged('".$table."','".$row['uid']."','".$field."','".$PA['itemFormElName']."');";
                                        $PA['fieldChangeFunc']['alert']=$alertMsgOnChange;
+                                               // if this is the child of an inline type and it is the field creating the label
+                                       if ($this->inline->isInlineChildAndLabelField($table, $field)) {
+                                               $PA['fieldChangeFunc']['inline'] = "inline.handleChangedField('".$PA['itemFormElName']."','".$this->inline->inlineNames['object']."[$table][".$row['uid']."]');";
+                                       }
 
                                                // Based on the type of the item, call a render function:
                                        $item = $this->getSingleField_SW($table,$field,$row,$PA);
@@ -906,6 +919,9 @@ class t3lib_TCEforms        {
                        case 'group':
                                $item = $this->getSingleField_typeGroup($table,$field,$row,$PA);
                        break;
+                       case 'inline':
+                               $item = $this->inline->getSingleField_typeInline($table,$field,$row,$PA);
+                       break;
                        case 'none':
                                $item = $this->getSingleField_typeNone($table,$field,$row,$PA);
                        break;
@@ -1341,6 +1357,18 @@ class t3lib_TCEforms     {
         * @see getSingleField_typeSelect()
         */
        function getSingleField_typeSelect_single($table,$field,$row,&$PA,$config,$selItems,$nMV_label) {
+                       // check against inline uniqueness
+               $inlineParent = $this->inline->getStructureLevel(-1);
+               if(is_array($inlineParent) && $inlineParent['uid']) {
+                       if ($inlineParent['config']['foreign_table'] == $table && $inlineParent['config']['foreign_unique'] == $field) {
+                               $uniqueIds = $this->inline->inlineData['unique'][$this->inline->inlineNames['object'].'['.$table.']']['used'];
+                               $PA['fieldChangeFunc']['inlineUnique'] = "inline.updateUnique(this,'".$this->inline->inlineNames['object'].'['.$table."]','".$this->inline->inlineNames['form']."','".$row['uid']."');";
+                       }
+                               // hide uid of parent record for symmetric relations
+                       if ($inlineParent['config']['foreign_table'] == $table && ($inlineParent['config']['foreign_field'] == $field || $inlineParent['config']['symmetric_field'] == $field)) {
+                               $uniqueIds[] = $inlineParent['uid'];
+                       }
+               }
 
                        // Initialization:
                $c = 0;
@@ -1381,11 +1409,13 @@ class t3lib_TCEforms    {
                        }
 
                                // Compiling the <option> tag:
-                       $opt[]= '<option value="'.htmlspecialchars($p[1]).'"'.
-                                               $sM.
-                                               ($styleAttrValue ? ' style="'.htmlspecialchars($styleAttrValue).'"' : '').
-                                               (!strcmp($p[1],'--div--') ? ' class="c-divider"' : '').
-                                               '>'.t3lib_div::deHSCentities(htmlspecialchars($p[0])).'</option>';
+                       if (!($p[1] != $PA['itemFormElValue'] && is_array($uniqueIds) && in_array($p[1], $uniqueIds))) {
+                               $opt[]= '<option value="'.htmlspecialchars($p[1]).'"'.
+                                                       $sM.
+                                                       ($styleAttrValue ? ' style="'.htmlspecialchars($styleAttrValue).'"' : '').
+                                                       (!strcmp($p[1],'--div--') ? ' class="c-divider"' : '').
+                                                       '>'.t3lib_div::deHSCentities(htmlspecialchars($p[0])).'</option>';
+                       }
 
                                // If there is an icon for the selector box (rendered in table under)...:
                        if ($p[2] && !$suppressIcons && (!$onlySelectedIconShown || $sM))       {
@@ -4172,24 +4202,23 @@ class t3lib_TCEforms    {
         */
        function replaceTableWrap($arr,$rec,$table)     {
                global $TCA;
-               reset($arr);
-               while(list($k,$v)=each($arr))   {
-
-                               // Make "new"-label
-                       if (strstr($rec['uid'],'NEW'))  {
-                               $newLabel = ' <span class="typo3-TCEforms-newToken">'.
-                                                       $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.php:labels.new',1).
-                                                       '</span>';
-
-                               #t3lib_BEfunc::fixVersioningPid($table,$rec);   // Kasper: Should not be used here because NEW records are not offline workspace versions...
-                               $truePid = t3lib_BEfunc::getTSconfig_pidValue($table,$rec['uid'],$rec['pid']);
-                               $prec = t3lib_BEfunc::getRecordWSOL('pages',$truePid,'title');
-                               $rLabel = '<em>[PID: '.$truePid.'] '.htmlspecialchars(trim(t3lib_div::fixed_lgd_cs(t3lib_BEfunc::getRecordTitle('pages',$prec),40))).'</em>';
-                       } else {
-                               $newLabel = ' <span class="typo3-TCEforms-recUid">['.$rec['uid'].']</span>';
-                               $rLabel  = htmlspecialchars(trim(t3lib_div::fixed_lgd_cs(t3lib_BEfunc::getRecordTitle($table,$rec),40)));
-                       }
 
+                       // Make "new"-label
+               if (strstr($rec['uid'],'NEW'))  {
+                       $newLabel = ' <span class="typo3-TCEforms-newToken">'.
+                                               $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.php:labels.new',1).
+                                               '</span>';
+
+                       #t3lib_BEfunc::fixVersioningPid($table,$rec);   // Kasper: Should not be used here because NEW records are not offline workspace versions...
+                       $truePid = t3lib_BEfunc::getTSconfig_pidValue($table,$rec['uid'],$rec['pid']);
+                       $prec = t3lib_BEfunc::getRecordWSOL('pages',$truePid,'title');
+                       $rLabel = '<em>[PID: '.$truePid.'] '.htmlspecialchars(trim(t3lib_div::fixed_lgd_cs(t3lib_BEfunc::getRecordTitle('pages',$prec),40))).'</em>';
+               } else {
+                       $newLabel = ' <span class="typo3-TCEforms-recUid">['.$rec['uid'].']</span>';
+                       $rLabel  = htmlspecialchars(trim(t3lib_div::fixed_lgd_cs(t3lib_BEfunc::getRecordTitle($table,$rec),40)));
+               }
+
+               foreach ($arr as $k => $v)      {
                                // Make substitutions:
                        $arr[$k] = str_replace('###ID_NEW_INDICATOR###', $newLabel, $arr[$k]);
                        $arr[$k] = str_replace('###RECORD_LABEL###',$rLabel,$arr[$k]);
@@ -4481,6 +4510,19 @@ class t3lib_TCEforms     {
         * @return      string          A <script></script> section with JavaScript.
         */
        function JSbottom($formname='forms[0]') {
+                               // add JS needed for inline fields
+                       if ($this->inline->inlineCount > 0) {
+                               if (count($this->inline->inlineData)) {
+                                       $inlineData = 'inline.addToDataArray('.$this->inline->getJSON($this->inline->inlineData).');';
+                               }
+
+                               $out .= $this->inline->addJavaScript();
+                               $out .= t3lib_div::wrapJS('
+                                       inline.setPrependFormFieldNames("'.$this->inline->prependNaming.'");
+                                       inline.setNoTitleString("'.addslashes($this->noTitle('')).'");
+                                       '.$inlineData.'
+                               ');
+                       }
 
                                // required
                        $reqLines=array();
diff --git a/t3lib/class.t3lib_tceforms_inline.php b/t3lib/class.t3lib_tceforms_inline.php
new file mode 100644 (file)
index 0000000..7c5dc16
--- /dev/null
@@ -0,0 +1,1429 @@
+<?php
+/***************************************************************
+*  Copyright notice
+*
+*  (c) 2006 Oliver Hader <oh@inpublica.de>
+*  All rights reserved
+*
+*  This script is part of the TYPO3 project. The TYPO3 project is
+*  free software; you can redistribute it and/or modify
+*  it under the terms of the GNU General Public License as published by
+*  the Free Software Foundation; either version 2 of the License, or
+*  (at your option) any later version.
+*
+*  The GNU General Public License can be found at
+*  http://www.gnu.org/copyleft/gpl.html.
+*  A copy is found in the textfile GPL.txt and important notices to the license
+*  from the author is found in LICENSE.txt distributed with these scripts.
+*
+*
+*  This script is distributed in the hope that it will be useful,
+*  but WITHOUT ANY WARRANTY; without even the implied warranty of
+*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*  GNU General Public License for more details.
+*
+*  This copyright notice MUST APPEAR in all copies of the script!
+***************************************************************/
+/**
+ * The Inline-Relational-Record-Editing functions as part of the TCEforms.
+ *
+ * @author     Oliver Hader <oh@inpublica.de>
+ */
+/**
+ * [CLASS/FUNCTION INDEX of SCRIPT]
+ *
+ *
+ *
+ *   88: class t3lib_TCEforms_inline
+ *  109:     function init(&$tceForms)
+ *  127:     function getSingleField_typeInline($table,$field,$row,&$PA)
+ *
+ *              SECTION: Regular rendering of forms, fields, etc.
+ *  263:     function renderForeignRecord($parentUid, $rec, $config = array())
+ *  319:     function renderForeignRecordHeader($parentUid, $foreign_table,$rec,$config = array())
+ *  375:     function renderForeignRecordHeaderControl($table,$row,$config = array())
+ *  506:     function renderCombinationTable(&$rec, $appendFormFieldNames, $config = array())
+ *  560:     function renderPossibleRecordsSelector($selItems, $conf, $uniqueIds=array())
+ *  627:     function addJavaScript()
+ *  643:     function addJavaScriptSortable($objectId)
+ *
+ *              SECTION: Handling of AJAX calls
+ *  665:     function createNewRecord($domObjectId, $foreignUid = 0)
+ *  755:     function getJSON($jsonArray)
+ *  770:     function getNewRecordLink($objectPrefix, $conf = array())
+ *
+ *              SECTION: Get data from database and handle relations
+ *  807:     function getRelatedRecords($table,$field,$row,&$PA,$config)
+ *  839:     function getPossibleRecords($table,$field,$row,$conf,$checkForConfField='foreign_selector')
+ *  885:     function getUniqueIds($records, $conf=array())
+ *  905:     function getRecord($pid, $table, $uid, $cmd='')
+ *  929:     function getNewRecord($pid, $table)
+ *
+ *              SECTION: Structure stack for handling inline objects/levels
+ *  951:     function pushStructure($table, $uid, $field = '', $config = array())
+ *  967:     function popStructure()
+ *  984:     function updateStructureNames()
+ * 1000:     function getStructureItemName($levelData)
+ * 1015:     function getStructureLevel($level)
+ * 1032:     function getStructurePath($structureDepth = -1)
+ * 1057:     function parseStructureString($string, $loadConfig = false)
+ *
+ *              SECTION: Helper functions
+ * 1098:     function checkConfiguration(&$config)
+ * 1123:     function checkAccess($cmd, $table, $theUid)
+ * 1185:     function compareStructureConfiguration($compare)
+ * 1199:     function normalizeUid($string)
+ * 1213:     function wrapFormsSection($section, $styleAttrs = array(), $tableAttrs = array())
+ * 1242:     function isInlineChildAndLabelField($table, $field)
+ * 1258:     function getStructureDepth()
+ * 1295:     function arrayCompareComplex($subjectArray, $searchArray, $type = '')
+ * 1349:     function isAssociativeArray($object)
+ * 1364:     function getPossibleRecordsFlat($possibleRecords)
+ * 1383:     function skipField($table, $field, $row, $config)
+ *
+ * TOTAL FUNCTIONS: 35
+ * (This index is automatically created/updated by the extension "extdeveval")
+ *
+ */
+class t3lib_TCEforms_inline {
+       var $fObj;                                                              // Reference to the calling TCEforms instance
+       var $backPath;                                                  // Reference to $fObj->backPath
+
+       var $inlineStructure = array();                 // the structure/hierarchy where working in, e.g. cascading inline tables
+       var $inlineFirstPid;                                    // the first call of an inline type appeared on this page (pid of record)
+       var $inlineNames = array();                             // keys: form, object -> hold the name/id for each of them
+       var $inlineData = array();                              // inline data array used for JSON output
+       var $inlineCount = 0;                                   // count the number of inline types used
+
+       var $prependNaming = 'data';                    // how the $this->fObj->prependFormFieldNames should be set ('data' is default)
+       var $prependFormFieldNames;                             // reference to $this->fObj->prependFormFieldNames
+       var $prependCmdFieldNames;                              // reference to $this->fObj->prependCmdFieldNames
+
+
+       /**
+        * Intialize an instance of t3lib_TCEforms_inline
+        *
+        * @param       object          $tceForms: Reference to an TCEforms instance
+        * @return      void
+        */
+       function init(&$tceForms) {
+               $this->fObj =& $tceForms;
+               $this->backPath =& $tceForms->backPath;
+               $this->prependFormFieldNames =& $this->fObj->prependFormFieldNames;
+               $this->prependCmdFieldNames =& $this->fObj->prependCmdFieldNames;
+       }
+
+
+       /**
+        * Generation of TCEform elements of the type "inline"
+        * This will render inline-relational-record sets. Relations.
+        *
+        * @param       string          $table: The table name of the record
+        * @param       string          $field: The field name which this element is supposed to edit
+        * @param       array           $row: The record data array where the value(s) for the field can be found
+        * @param       array           $PA: An array with additional configuration options.
+        * @return      string          The HTML code for the TCEform field
+        */
+       function getSingleField_typeInline($table,$field,$row,&$PA) {
+                       // check the TCA configuration - if false is returned, something was wrong
+               if ($this->checkConfiguration($PA['fieldConf']['config']) === false) return false;
+
+                       // count the number of processed inline elements
+               $this->inlineCount++;
+
+                       // Init:
+               $config = $PA['fieldConf']['config'];
+               $foreign_table = $config['foreign_table'];
+
+               // @TODO: Minitems müssen unterstüzt werden und intInRange mit maxitems immer mindestens minItems
+               $minitems = t3lib_div::intInRange($config['minitems'],0);
+               $maxitems = t3lib_div::intInRange($config['maxitems'],0);
+               if (!$maxitems) $maxitems=100000;
+
+                       // remember the page id (pid of record) where inline editing started first
+                       // we need that pid for ajax calls, so that they would know where the action takes place on the page structure
+               if (!isset($this->inlineFirstPid)) {
+                               // if pid is negative, fetch the previous record and take its pid
+                       if ($row['pid'] < 0) {
+                               $prevRec = t3lib_BEfunc::getRecord($table, abs($row['pid']));
+                               $this->inlineFirstPid = $prevRec['pid'];
+                               // take the pid as it is
+                       } else {
+                               $this->inlineFirstPid = $row['pid'];
+                       }
+               }
+                       // add the current inline job to the structure stack
+               $this->pushStructure($table, $row['uid'], $field, $config);
+                       // e.g. inline[<table>][<uid>][<field>]
+               $nameForm = $this->inlineNames['form'];
+                       // e.g. inline[<pid>][<table1>][<uid1>][<field1>][<table2>][<uid2>][<field2>]
+               $nameObject = $this->inlineNames['object'];
+                       // get the records related to this inline record
+               $recordList = $this->getRelatedRecords($table,$field,$row,$PA,$config);
+                       // set the first and last record to the config array
+               $config['inline']['first'] = $recordList[0]['uid'];
+               $config['inline']['last'] = $recordList[count($recordList)-1]['uid'];
+
+                       // tell the browser what we have (using JSON later)
+               $this->inlineData['config'][$nameObject] = array('table' => $foreign_table);
+               $this->inlineData['config'][$nameObject.'['.$foreign_table.']'] = array(
+                       'min' => $minitems,
+                       'max' => $maxitems,
+                       'sortable' => $config['appearance']['useSortable'],
+               );
+
+                       // if relations are required to be unique, get the uids that have already been used on the foreign side of the relation
+               if ($config['foreign_unique']) {
+                       $uniqueIds = $this->getUniqueIds($recordList, $config);
+                       $possibleRecords = $this->getPossibleRecords($table,$field,$row,$config,'foreign_unique');
+                       $uniqueMax = $config['appearance']['useCombination'] ? -1 : count($possibleRecords);
+                       $this->inlineData['unique'][$nameObject.'['.$foreign_table.']'] = array(
+                               'max' => $uniqueMax,
+                               'used' => $uniqueIds,
+                               'table' => $config['foreign_table'],
+                               'field' => $config['foreign_unique'],
+                               'selector' => $config['foreign_selector'] ? true : false,
+                               'possible' => $this->getPossibleRecordsFlat($possibleRecords),
+                       );
+               }
+
+                       // if it's required to select from possible child records (reusable children), add a selector box
+               if ($config['foreign_selector']) {
+                               // if not already set by the foreign_unique, set the possibleRecords here and the uniqueIds to an empty array
+                       if (!$config['foreign_unique']) {
+                               $possibleRecords = $this->getPossibleRecords($table,$field,$row,$config);
+                               $uniqueIds = array();
+                       }
+                       $selectorBox = $this->renderPossibleRecordsSelector($possibleRecords,$config,$uniqueIds);
+                       $item .= $selectorBox;
+               }
+
+                       // wrap all inline fields of a record with a <div> (like a container)
+               $item .= '<div id="'.$nameObject.'">';
+
+                       // define how to show the "Create new record" link - if there are more than maxitems, hide it
+               if (count($recordList) >= $maxitems || ($uniqueMax > 0 && count($recordList) >= $uniqueMax))
+                       $config['inline']['inlineNewButtonStyle'] = 'display: none;';
+
+                       // add the "Create new record" link before all child records
+               if ($config['appearance']['newRecordLinkPosition'] != 'bottom') {
+                       $item .= $this->getNewRecordLink($nameObject.'['.$foreign_table.']', $config);
+               }
+
+               $item .= '<div id="'.$nameObject.'_records">';
+               $relationList = array();
+               if (count($recordList)) {
+                       foreach ($recordList as $rec) {
+                               $item .= $this->renderForeignRecord($row['uid'],$rec,$config);
+                               $relationList[] = $rec['uid'];
+                       }
+               }
+               $item .= '</div>';
+
+                       // add the "Create new record" link after all child records
+               if ($config['appearance']['newRecordLinkPosition'] != 'top') {
+                       $item .= $this->getNewRecordLink($nameObject.'['.$foreign_table.']', $config);
+               }
+
+                       // add Drag&Drop functions for sorting to TCEforms::$additionalJS_post
+               if (count($relationList) > 1 && $config['appearance']['useSortable'])
+                       $this->addJavaScriptSortable($nameObject.'_records');
+                       // publish the uids of the child records in the given order to the browser
+               $item .= '<input type="hidden" name="'.$nameForm.'" value="'.implode(',', $relationList).'" />';
+                       // close the wrap for all inline fields (container)
+               $item .= '</div>';
+
+                       // on finishing this section, remove the last item from the structure stack
+               $this->popStructure();
+
+                       // if this was the first call to the inline type, restore the values
+               if (!$this->getStructureDepth()) {
+                       unset($this->inlineFirstPid);
+               }
+
+               return $item;
+       }
+
+
+       /*******************************************************
+        *
+        * Regular rendering of forms, fields, etc.
+        *
+        *******************************************************/
+
+
+       /**
+        * Render the form-fields of a related (foreign) record.
+        *
+        * @param       string          $parentUid: The uid of the parent (embedding) record (uid or NEW...)
+        * @param       array           $rec: The table record of the child/embedded table (normaly post-processed by t3lib_transferData)
+        * @param       array           $config: content of $PA['fieldConf']['config']
+        * @return      string          The HTML code for this "foreign record"
+        */
+       function renderForeignRecord($parentUid, $rec, $config = array()) {
+               $foreign_table = $config['foreign_table'];
+               $foreign_field = $config['foreign_field'];
+               $foreign_selector = $config['foreign_selector'];
+
+                       // record comes from storage (e.g. database)
+               $isNewRecord = t3lib_div::testInt($rec['uid']) ? false : true;
+                       // if there is a selector field, normalize it
+               if ($foreign_selector) {
+                       $rec[$foreign_selector] = $this->normalizeUid($rec[$foreign_selector]);
+               }
+
+               $hasAccess = $this->checkAccess($isNewRecord?'new':'edit', $foreign_table, $rec['uid']);
+
+               if(!$hasAccess) return false;
+
+                       // get the current prependObjectId
+               $nameObject = $this->inlineNames['object'];
+               $appendFormFieldNames = '['.$foreign_table.']['.$rec['uid'].']';
+               $formFieldNames = $nameObject.$appendFormFieldNames;
+
+               $header = $this->renderForeignRecordHeader($parentUid, $foreign_table, $rec, $config);
+               $combination = $this->renderCombinationTable($rec, $appendFormFieldNames, $config);
+               $fields = $this->fObj->getMainFields($foreign_table,$rec);
+               $fields = $this->wrapFormsSection($fields);
+
+               if ($isNewRecord) {
+                       $fields .= '<input type="hidden" name="'.$this->prependFormFieldNames.$appendFormFieldNames.'[pid]" value="'.$rec['pid'].'"/>';
+               } else {
+                       $fields .= '<input type="hidden" name="'.$this->prependCmdFieldNames.$appendFormFieldNames.'[delete]" value="1" disabled="disabled" />';
+               }
+
+                       // set the appearance style of the records of this table
+               if (is_array($config['appearance']) && count($config['appearance'])) {
+                       if ($config['appearance']['collapseAll']) $appearanceStyleFields = ' style="display: none;"';
+               }
+
+               $out = '<div id="'.$formFieldNames.'_header">'.$header.'</div>';
+               $out .= '<div id="'.$formFieldNames.'_fields"'.$appearanceStyleFields.'>'.$fields.$combination.'</div>';
+                       // wrap the header, fields and combination part of a child record with a div container
+               $out = '<div id="'.$formFieldNames.'_div"'.($isNewRecord ? ' class="inlineIsNewRecord"' : '').'>' . $out . '</div>';
+
+               return $out;
+       }
+
+
+       /**
+        * Renders the HTML header for a foreign record, such as the title, toggle-function, drag'n'drop, etc.
+        * Later on the command-icons are inserted here.
+        *
+        * @param       string          $parentUid: The uid of the parent (embedding) record (uid or NEW...)
+        * @param       string          $foreign_table: The foreign_table we create a header for
+        * @param       array           $rec: The current record of that table
+        * @param       array           $config: content of $PA['fieldConf']['config']
+        * @return      string          The HTML code of the header
+        */
+       function renderForeignRecordHeader($parentUid, $foreign_table,$rec,$config = array()) {
+               $formFieldNames = $this->inlineNames['object'].'['.$foreign_table.']['.$rec['uid'].']';
+               $expandSingle = $config['appearance']['expandSingle'] ? 1 : 0;
+               $onClick = "return inline.expandCollapseRecord('".htmlspecialchars($formFieldNames)."', $expandSingle)";
+
+                       // if an alternative label for the field we render is set, use it
+               $isOnSymmetricSide = t3lib_loadDBGroup::isOnSymmetricSide($parentUid, $config, $rec);
+               if (!$isOnSymmetricSide && $config['foreign_label']) {
+                       $titleCol = $config['foreign_label'];
+               } elseif ($isOnSymmetricSide && $config['symmetric_label']) {
+                       $titleCol = $config['symmetric_label'];
+               }
+
+                       // render the special alternative title
+               if (isset($titleCol)) {
+                       $recTitle = t3lib_BEfunc::getProcessedValueExtra($foreign_table, $titleCol, $rec[$titleCol]);
+                       $recTitle = $this->fObj->noTitle($recTitle);
+                       // render the standard
+               } else {
+                       $recTitle = t3lib_BEfunc::getRecordTitle($foreign_table, $rec);
+               }
+
+               $altText = t3lib_BEfunc::getRecordIconAltText($rec, $foreign_table);
+               $iconImg =
+                       '<a href="#" onclick="'.htmlspecialchars($onClick).'">'.t3lib_iconWorks::getIconImage(
+                               $foreign_table, $rec, $this->backPath,
+                               'title="'.htmlspecialchars($altText).'" class="absmiddle"'
+                       ).'</a>';
+
+               $label =
+                       '<a href="#" onclick="'.htmlspecialchars($onClick).'" style="display: block;">'.
+                       //      '<img'.t3lib_iconWorks::skinImg($this->backPath,'gfx/button_down.gif','width="11" height="10"').' align="absmiddle" /> '.
+                               '<span id="'.$formFieldNames.'_label">'.$recTitle.'</span>'.
+                       '</a>';
+
+               $ctrl = $this->renderForeignRecordHeaderControl($foreign_table,$rec,$config);
+
+               $header =
+                       '<table cellspacing="0" cellpadding="0" border="0" width="100%" style="margin-right: 5px;"'.
+                       ($this->fObj->borderStyle[2] ? ' background="'.htmlspecialchars($this->backPath.$this->fObj->borderStyle[2]).'"':'').
+                       ($this->fObj->borderStyle[3] ? ' class="'.htmlspecialchars($this->fObj->borderStyle[3]).'"':'').'>' .
+                       '<tr class="class-main12"><td width="18">'.$iconImg.'</td><td align="left"><b>'.$label.'</b></td><td align="right">'.$ctrl.'</td></tr></table>';
+
+               return $header;
+       }
+
+
+       /**
+        * Render the control-icons for a record header (create new, sorting, delete, disable/enable).
+        * Most of the parts are copy&paste from class.db_list_extra.inc and modified for the JavaScript calls here
+        *
+        * @param       string          $table: The table (foreign_table) we create control-icons for
+        * @param       array           $row: The current record of that table
+        * @param       array           $config: (modified) TCA configuration of the field
+        * @return      string          The HTML code with the control-icons
+        */
+       function renderForeignRecordHeaderControl($table,$row,$config = array()) {
+               global $TCA, $SOBE;
+
+                       // Initialize:
+               $cells=array();
+               $isNewItem = substr($row['uid'], 0, 3) == 'NEW';
+
+               $nameObjectFt = $this->inlineNames['object'].'['.$table.']';
+               $nameObjectFtId = $nameObjectFt.'['.$row['uid'].']';
+
+               $calcPerms = $GLOBALS['BE_USER']->calcPerms(
+                       t3lib_BEfunc::readPageAccess($row['pid'], $GLOBALS['BE_USER']->getPagePermsClause(1))
+               );
+
+                       // FIXME: Put these calls somewhere else... possibly they arn't needed here
+               $web_list_modTSconfig = t3lib_BEfunc::getModTSconfig($row['pid'],'mod.web_list');
+               $allowedNewTables = t3lib_div::trimExplode(',',$this->fObj->web_list_modTSconfig['properties']['allowedNewTables'],1);
+               $showNewRecLink = !count($allowedNewTables) || in_array($table, $allowedNewTables);
+
+                       // If the listed table is 'pages' we have to request the permission settings for each page:
+               if ($table=='pages')    {
+                       $localCalcPerms = $GLOBALS['BE_USER']->calcPerms(t3lib_BEfunc::getRecord('pages',$row['uid']));
+               }
+
+                       // This expresses the edit permissions for this particular element:
+               $permsEdit = ($table=='pages' && ($localCalcPerms&2)) || ($table!='pages' && ($calcPerms&16));
+
+                       // "Show" link (only pages and tt_content elements)
+               if ($table=='pages' || $table=='tt_content')    {
+                       $params='&edit['.$table.']['.$row['uid'].']=edit';
+                       $cells[]='<a href="#" onclick="'.htmlspecialchars(t3lib_BEfunc::viewOnClick($table=='tt_content'?$this->id.'#'.$row['uid']:$row['uid'])).'">'.
+                                       '<img'.t3lib_iconWorks::skinImg($this->backPath,'gfx/zoom.gif','width="12" height="12"').' title="'.$GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.php:labels.showPage',1).'" alt="" />'.
+                                       '</a>';
+               }
+
+                       // "Info": (All records)
+               if (!$isNewItem)
+                       $cells[]='<a href="#" onclick="'.htmlspecialchars('top.launchView(\''.$table.'\', \''.$row['uid'].'\'); return false;').'">'.
+                               '<img'.t3lib_iconWorks::skinImg($this->backPath,'gfx/zoom2.gif','width="12" height="12"').' title="'.$GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_mod_web_list.xml:showInfo',1).'" alt="" />'.
+                               '</a>';
+
+                       // If the table is NOT a read-only table, then show these links:
+               if (!$TCA[$table]['ctrl']['readOnly'])  {
+
+                               // "New record after" link (ONLY if the records in the table are sorted by a "sortby"-row or if default values can depend on previous record):
+                       if ($TCA[$table]['ctrl']['sortby'] || $TCA[$table]['ctrl']['useColumnsForDefaultValues'])       {
+                               if (
+                                       ($table!='pages' && ($calcPerms&16)) ||         // For NON-pages, must have permission to edit content on this parent page
+                                       ($table=='pages' && ($calcPerms&8))             // For pages, must have permission to create new pages here.
+                                       )       {
+                                       if ($showNewRecLink)    {
+                                               $onClick = "return inline.createNewRecord('".$nameObjectFt."','".$row['uid']."')";
+                                               $params='&edit['.$table.']['.(-$row['uid']).']=new';
+                                               $cells[]='<a href="#" onclick="'.htmlspecialchars($onClick).'" class="inlineNewButton"'.$config['inline']['inlineNewButtonStyle'].'>'.
+                                                               '<img'.t3lib_iconWorks::skinImg($this->backPath,'gfx/new_'.($table=='pages'?'page':'el').'.gif','width="'.($table=='pages'?13:11).'" height="12"').' title="'.$GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_mod_web_list.xml:new'.($table=='pages'?'Page':'Record'),1).'" alt="" />'.
+                                                               '</a>';
+                                       }
+                               }
+                       }
+
+                               // Drag&Drop Sorting: Sortable handler for script.aculo.us
+                       if ($permsEdit && $config['appearance']['useSortable'] && ($TCA[$table]['ctrl']['sortby'] || $config['MM']))    {
+                               $cells[] = '<img'.t3lib_iconWorks::skinImg($this->backPath,'gfx/move.gif','width="16" height="16" hspace="2"').' title="'.$GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.php:labels.move',1).'" alt="" style="cursor: move;" class="sortableHandle" />';
+                       }
+
+                               // "Up/Down" links
+                       if ($permsEdit && ($TCA[$table]['ctrl']['sortby'] || $config['MM']))    {
+                               $onClick = "return inline.changeSorting('".$nameObjectFtId."', '1')";   // Up
+                               $style = $config['inline']['first'] == $row['uid'] ? 'style="visibility: hidden;"' : '';
+                               $cells[]='<a href="#" onclick="'.htmlspecialchars($onClick).'" class="sortingUp" '.$style.'>'.
+                                               '<img'.t3lib_iconWorks::skinImg($this->backPath,'gfx/button_up.gif','width="11" height="10"').' title="'.$GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_mod_web_list.xml:moveUp',1).'" alt="" />'.
+                                               '</a>';
+
+                               $onClick = "return inline.changeSorting('".$nameObjectFtId."', '-1')";  // Down
+                               $style = $config['inline']['last'] == $row['uid'] ? 'style="visibility: hidden;"' : '';
+                               $cells[]='<a href="#" onclick="'.htmlspecialchars($onClick).'" class="sortingDown" '.$style.'>'.
+                                               '<img'.t3lib_iconWorks::skinImg($this->backPath,'gfx/button_down.gif','width="11" height="10"').' title="'.$GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_mod_web_list.xml:moveDown',1).'" alt="" />'.
+                                               '</a>';
+                       }
+
+                               // "Hide/Unhide" links:
+                       $hiddenField = $TCA[$table]['ctrl']['enablecolumns']['disabled'];
+                       if ($permsEdit && $hiddenField && $TCA[$table]['columns'][$hiddenField] && (!$TCA[$table]['columns'][$hiddenField]['exclude'] || $GLOBALS['BE_USER']->check('non_exclude_fields',$table.':'.$hiddenField)))     {
+                               $onClick = "return inline.enableDisableRecord('".$nameObjectFtId."')";
+                               if ($row[$hiddenField]) {
+                                       $params='&data['.$table.']['.$row['uid'].']['.$hiddenField.']=0';
+                                       $cells[]='<a href="#" onclick="'.htmlspecialchars($onClick).'">'.
+                                                       '<img'.t3lib_iconWorks::skinImg($this->backPath,'gfx/button_unhide.gif','width="11" height="10"').' title="'.$GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_mod_web_list.xml:unHide'.($table=='pages'?'Page':''),1).'" alt="" id="'.$nameObjectFtId.'_disabled" />'.
+                                                       '</a>';
+                               } else {
+                                       $params='&data['.$table.']['.$row['uid'].']['.$hiddenField.']=1';
+                                       $cells[]='<a href="#" onclick="'.htmlspecialchars($onClick).'">'.
+                                                       '<img'.t3lib_iconWorks::skinImg($this->backPath,'gfx/button_hide.gif','width="11" height="10"').' title="'.$GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_mod_web_list.xml:hide'.($table=='pages'?'Page':''),1).'" alt="" id="'.$nameObjectFtId.'_disabled" />'.
+                                                       '</a>';
+                               }
+                       }
+
+                               // "Delete" link:
+                       if (
+                               ($table=='pages' && ($localCalcPerms&4)) || ($table!='pages' && ($calcPerms&16))
+                               )       {
+                               $onClick = "inline.deleteRecord('".$nameObjectFtId."');";
+                               $cells[]='<a href="#" onclick="'.htmlspecialchars('if (confirm('.$GLOBALS['LANG']->JScharCode($GLOBALS['LANG']->getLL('deleteWarning').t3lib_BEfunc::referenceCount($table,$row['uid'],' (There are %s reference(s) to this record!)')).')) {   '.$onClick.' } return false;').'">'.
+                                               '<img'.t3lib_iconWorks::skinImg($this->backPath,'gfx/garbage.gif','width="11" height="12"').' title="'.$GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_mod_web_list.xml:delete',1).'" alt="" />'.
+                                               '</a>';
+                       }
+               }
+
+                       // If the record is edit-locked by another user, we will show a little warning sign:
+               if ($lockInfo=t3lib_BEfunc::isRecordLocked($table,$row['uid'])) {
+                       $cells[]='<a href="#" onclick="'.htmlspecialchars('alert('.$GLOBALS['LANG']->JScharCode($lockInfo['msg']).');return false;').'">'.
+                                       '<img'.t3lib_iconWorks::skinImg('','gfx/recordlock_warning3.gif','width="17" height="12"').' title="'.htmlspecialchars($lockInfo['msg']).'" alt="" />'.
+                                       '</a>';
+               }
+
+                       // Compile items into a DIV-element:
+               return '
+                                                                                       <!-- CONTROL PANEL: '.$table.':'.$row['uid'].' -->
+                                                                                       <div class="typo3-DBctrl">'.implode('',$cells).'</div>';
+       }
+
+       /**
+        * Render a table with TCEforms, that occurs on a intermediate table but should be editable directly,
+        * so two tables are combined (the intermediate table with attributes and the sub-embedded table).
+        * -> This is a direct embedding over two levels!
+        *
+        * @param       array           $rec: The table record of the child/embedded table (normaly post-processed by t3lib_transferData)
+        * @param       string          $appendFormFieldNames: The [<table>][<uid>] of the parent record (the intermediate table)
+        * @param       array           $config: content of $PA['fieldConf']['config']
+        * @return      string          A HTML string with <table> tag around.
+        */
+       function renderCombinationTable(&$rec, $appendFormFieldNames, $config = array()) {
+               $foreign_table = $config['foreign_table'];
+               $foreign_selector = $config['foreign_selector'];
+
+               if ($foreign_selector && $config['appearance']['useCombination']) {
+                       $comboConfig = $GLOBALS['TCA'][$foreign_table]['columns'][$foreign_selector]['config'];
+                       $comboRecord = array();
+
+                               // record does already exist, so load it
+                       if (t3lib_div::testInt($rec[$foreign_selector])) {
+                               $comboRecord = $this->getRecord(
+                                       $this->inlineFirstPid,
+                                       $comboConfig['foreign_table'],
+                                       $rec[$foreign_selector]
+                               );
+                               $isNewRecord = false;
+                               // it's a new record, so get some default data
+                       } else {
+                               $comboRecord = $this->getNewRecord(
+                                       $this->inlineFirstPid,
+                                       $comboConfig['foreign_table']
+                               );
+                               $isNewRecord = true;
+                       }
+
+                               // get the TCEforms interpretation of the TCA of the child table
+                       $out = $this->fObj->getMainFields($comboConfig['foreign_table'], $comboRecord);
+                       $out = $this->wrapFormsSection($out, array(), array('class' => 'wrapperAttention'));
+
+                               // if this is a new record, add a pid value to store this record and the pointer value for the intermediate table
+                       if ($isNewRecord) {
+                               $comboFormFieldName = $this->prependFormFieldNames.'['.$comboConfig['foreign_table'].']['.$comboRecord['uid'].'][pid]';
+                               $out .= '<input type="hidden" name="'.$comboFormFieldName.'" value="'.$this->inlineFirstPid.'"/>';
+                       }
+
+                               // if the foreign_selector field is also responsible for uniqueness, tell the browser the uid of the "other" side of the relation
+                       if ($isNewRecord || $config['foreign_unique'] == $foreign_selector) {
+                               $parentFormFieldName = $this->prependFormFieldNames.$appendFormFieldNames.'['.$foreign_selector.']';
+                               $out .= '<input type="hidden" name="'.$parentFormFieldName.'" value="'.$comboRecord['uid'].'" />';
+                       }
+               }
+
+               return $out;
+       }
+
+       /**
+        * Get a selector as used for the select type, to select from all available
+        * records and to create a relation to the embedding record (e.g. like MM).
+        *
+        * @param       array           $selItems: Array of all possible records
+        * @param       array           $conf: TCA configuration of the parent(!) field
+        * @param       array           $uniqueIds: The uids that have already been used and should be unique
+        * @return      string          A HTML <select> box with all possible records
+        */
+       function renderPossibleRecordsSelector($selItems, $conf, $uniqueIds=array()) {
+               $foreign_table = $conf['foreign_table'];
+               $foreign_selector = $conf['foreign_selector'];
+
+               $PA = array();
+               $PA['fieldConf'] = $GLOBALS['TCA'][$foreign_table]['columns'][$foreign_selector];
+               $PA['fieldConf']['config']['form_type'] = $PA['fieldConf']['config']['form_type'] ? $PA['fieldConf']['config']['form_type'] : $PA['fieldConf']['config']['type'];       // Using "form_type" locally in this script
+               $PA['fieldTSConfig'] = $this->fObj->setTSconfig($foreign_table,array(),$foreign_selector);
+               $config = $PA['fieldConf']['config'];
+
+               if(!$disabled) {
+                               // Create option tags:
+                       $opt = array();
+                       $styleAttrValue = '';
+                       foreach($selItems as $p)        {
+                               if ($config['iconsInOptionTags'])       {
+                                       $styleAttrValue = $this->fObj->optionTagStyle($p[2]);
+                               }
+                               if (!in_array($p[1], $uniqueIds)) {
+                                       $opt[]= '<option value="'.htmlspecialchars($p[1]).'"'.
+                                                                       ' style="'.(in_array($p[1], $uniqueIds) ? '' : '').
+                                                                       ($styleAttrValue ? ' style="'.htmlspecialchars($styleAttrValue) : '').'">'.
+                                                                       htmlspecialchars($p[0]).'</option>';
+                               }
+                       }
+
+                               // Put together the selector box:
+                       $selector_itemListStyle = isset($config['itemListStyle']) ? ' style="'.htmlspecialchars($config['itemListStyle']).'"' : ' style="'.$this->fObj->defaultMultipleSelectorStyle.'"';
+                       $size = intval($config['size']);
+                       $size = $config['autoSizeMax'] ? t3lib_div::intInRange(count($itemArray)+1,t3lib_div::intInRange($size,1),$config['autoSizeMax']) : $size;
+                       $sOnChange = "return inline.importNewRecord('".$this->inlineNames['object']."[".$conf['foreign_table']."]')";
+                       $itemsToSelect = '
+                               <select id="'.$this->inlineNames['object'].'['.$conf['foreign_table'].']_selector"'.
+                                                       $this->fObj->insertDefStyle('select').
+                                                       ($size ? ' size="'.$size.'"' : '').
+                                                       ' onchange="'.htmlspecialchars($sOnChange).'"'.
+                                                       $PA['onFocus'].
+                                                       $selector_itemListStyle.
+                                                       ($conf['foreign_unique'] ? ' isunique="isunique"' : '').'>
+                                       '.implode('
+                                       ',$opt).'
+                               </select>';
+
+                               // add a "Create new relation" link for adding new relations
+                               // this is neccessary, if the size of the selector is "1" or if
+                               // there is only one record item in the select-box, that is selected by default
+                               // the selector-box creates a new relation on using a onChange event (see some line above)
+                       $createNewRelationText = $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.php:cm.createNewRelation',1);
+                       $itemsToSelect .=
+                               '<a href="#" onclick="'.htmlspecialchars($sOnChange).'" align="abstop">'.
+                                       '<img'.t3lib_iconWorks::skinImg($this->backPath,'gfx/edit2.gif','width="11" height="12"').' title="'.$createNewRelationText.'" alt="" /> '.$createNewRelationText.
+                               '</a>';
+                               // wrap the selector and add a spacer to the bottom
+                       $itemsToSelect = '<div style="margin-bottom: 20px;">'.$itemsToSelect.'</div>';
+               }
+
+               return $itemsToSelect;
+       }
+
+
+       /**
+        * Get the <script type="text/javascript" src="..."> tags of:
+        * - prototype.js
+        * - script.acolo.us
+        *
+        * @return      string          The HTML code of the <script type="text/javascript" src="..."> tags
+        */
+       function addJavaScript() {
+               $jsCode = array(
+                       '<script src="prototype.js" type="text/javascript"></script>',
+                       '<script src="scriptaculous/scriptaculous.js" type="text/javascript"></script>',
+                       '<script src="../t3lib/jsfunc.inline.js" type="text/javascript"></script>',
+               );
+
+               return implode("\n", $jsCode);
+       }
+
+       /**
+        * Add Sortable functionality using script.acolo.us "Sortable".
+        *
+        * @param       string          $objectId: The container id of the object - elements inside will be sortable
+        * @return      void
+        */
+       function addJavaScriptSortable($objectId) {
+               $this->fObj->additionalJS_post[] = '
+                       inline.createDragAndDropSorting("'.$objectId.'");
+               ';
+       }
+
+
+       /*******************************************************
+        *
+        * Handling of AJAX calls
+        *
+        *******************************************************/
+
+
+       /**
+        * Handle AJAX calls to show a new inline-record of the given table.
+        * Normally this method is never called from inside TYPO3. Always from outside by AJAX.
+        *
+        * @param       mixed           $arguments: What to do and where to add, information from the calling browser.
+        * @param       string          $foreignUid: If set, the new record should be inserted after that one
+        * @return      string          A JSON string
+        */
+       function createNewRecord($domObjectId, $foreignUid = 0) {
+               global $TCA;
+
+                       // parse the DOM identifier (string), add the levels to the structure stack (array) and load the TCA config
+               $this->parseStructureString($domObjectId, true);
+                       // the current table - for this table we should add/import records
+               $current = $this->inlineStructure['unstable'];
+                       // the parent table - this table embeds the current table
+               $parent = $this->getStructureLevel(-1);
+                       // get TCA 'config' of the parent table
+               $config = $parent['config'];
+
+                       // dynamically create a new record using t3lib_transferData
+               if (!$foreignUid || !t3lib_div::testInt($foreignUid) || $config['foreign_selector']) {
+                       $record = $this->getNewRecord($this->inlineFirstPid, $current['table']);
+
+                       // dynamically import an existing record (this could be a call from a select box)
+               } else {
+                       $record = $this->getRecord($this->inlineFirstPid, $current['table'], $foreignUid);
+               }
+
+                       // now there is a foreign_selector, so there is a new record on the intermediate table, but
+                       // this intermediate table holds a field, which is responsible for the foreign_selector, so
+                       // we have to set this field to the uid we get - or if none, to a new uid
+               if ($config['foreign_selector'] && $foreignUid) {
+                       $record[$config['foreign_selector']] = $foreignUid;
+               }
+
+                       // the HTML-object-id's prefix of the dynamically created record
+               $objectPrefix = $this->inlineNames['object'].'['.$current['table'].']';
+               $objectId = $objectPrefix.'['.$record['uid'].']';
+
+                       // render the foreign record that should passed back to browser
+               $item = $this->renderForeignRecord($parent['uid'], $record, $config);
+               if($item === false) {
+                       $jsonArray = array(
+                               'data'  => 'Access denied',
+                               'scriptCall' => array(
+                                       "alert('Access denied');",
+                               )
+                       );
+                       return $this->getJSON($jsonArray);
+               }
+
+               if (!$current['uid']) {
+                       $jsonArray = array(
+                               'data'  => $item,
+                               'scriptCall' => array(
+                                       "inline.domAddNewRecord('bottom','".$this->inlineNames['object']."_records','$objectPrefix',json.data);",
+                                       "inline.memorizeAddRecord('$objectPrefix','".$record['uid']."',null,'$foreignUid');"
+                               )
+                       );
+
+                       // append the HTML data after an existing record in the container
+               } else {
+                       $jsonArray = array(
+                               'data'  => $item,
+                               'scriptCall' => array(
+                                       "inline.domAddNewRecord('after','".$domObjectId.'_div'."','$objectPrefix',json.data);",
+                                       "inline.memorizeAddRecord('$objectPrefix','".$record['uid']."','".$current['uid']."','$foreignUid');"
+                               )
+                       );
+               }
+
+                       // if a new level of child records (child of children) was created, send the JSON array
+               if (count($this->inlineData))
+                       $jsonArray['scriptCall'][] = 'inline.addToDataArray('.$this->getJSON($this->inlineData).');';
+                       // if script.aculo.us Sortable is used, update the Observer to know the the record
+               if ($config['appearance']['useSortable'])
+                       $jsonArray['scriptCall'][] = "inline.createDragAndDropSorting('".$this->inlineNames['object']."_records');";
+                       // if TCEforms has some JavaScript code to be executed, just do it
+               if ($this->fObj->extJSCODE)
+                       $jsonArray['scriptCall'][] = $this->fObj->extJSCODE;
+                       // tell the browser to scroll to the newly created record
+               $jsonArray['scriptCall'][] = "Element.scrollTo('".$objectId."_div');";
+                       // fade out and fade in the new record in the browser view to catch the user's eye
+               $jsonArray['scriptCall'][] = "inline.fadeOutFadeIn('".$objectId."_div');";
+
+                       // return the JSON string
+               return $this->getJSON($jsonArray);
+       }
+
+
+       /**
+        * Creates recursively a JSON literal from a mulidimensional associative array.
+        * Uses Services_JSON (http://mike.teczno.com/JSON/doc/)
+        *
+        * @param       array           $jsonArray: The array (or part of) to be transformed to JSON
+        * @return      string          If $level>0: part of JSON literal; if $level==0: whole JSON literal wrapped with <script> tags
+        */
+       function getJSON($jsonArray) {
+               if (!$GLOBALS['JSON']) {
+                       require_once('json.php');
+                       $GLOBALS['JSON'] = t3lib_div::makeInstance('Services_JSON');
+               }
+               return $GLOBALS['JSON']->encode($jsonArray);
+       }
+
+       /**
+        * Creates a link/button to create new records
+        *
+        * @param       string          $objectPrefix: The "path" to the child record to create (e.g. '[parten_table][parent_uid][parent_field][child_table]')
+        * @param       string          $style: If a style should be added to the link (e.g. 'display: none;')
+        * @return      string          The HTML code for the new record link
+        */
+       function getNewRecordLink($objectPrefix, $conf = array()) {
+               if ($conf['inline']['inlineNewButtonStyle']) $style = ' style="'.$style.'"';
+
+               $onClick = "return inline.createNewRecord('$objectPrefix')";
+               $title = $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.php:cm.createnew',1);
+
+               if ($conf['appearance']['newRecordLinkAddTitle'])
+                       $tableTitle .= ' '.$GLOBALS['LANG']->sL($GLOBALS['TCA'][$conf['foreign_table']]['ctrl']['title'],1);
+
+               $out = '
+                               <div class="typo3-newRecordLink">
+                                       <a href="#" onClick="'.$onClick.'" class="inlineNewButton"'.$style.' title="'.$title.$tableTitle.'">'.
+                                       '<img'.t3lib_iconWorks::skinImg($this->backPath,'gfx/new_el.gif','width="11" height="12"').' alt="'.$title.$tableTitle.'" />'.
+                                       $title.t3lib_div::fixed_lgd_cs($tableTitle, $this->fObj->titleLen).
+                                       '</a>
+                               </div>';
+               return $out;
+       }
+
+
+       /*******************************************************
+        *
+        * Get data from database and handle relations
+        *
+        *******************************************************/
+
+
+       /**
+        * Get the related records of the embedding item, this could be 1:n, m:n.
+        *
+        * @param       string          $table: The table name of the record
+        * @param       string          $field: The field name which this element is supposed to edit
+        * @param       array           $row: The record data array where the value(s) for the field can be found
+        * @param       array           $PA: An array with additional configuration options.
+        * @param       array           $config: (Redundant) content of $PA['fieldConf']['config'] (for convenience)
+        * @return      array           The records related to the parent item
+        */
+       function getRelatedRecords($table,$field,$row,&$PA,$config) {
+               $records = array();
+
+                       // Creating the label for the "No Matching Value" entry.
+               $nMV_label = isset($PA['fieldTSConfig']['noMatchingValue_label']) ? $this->fObj->sL($PA['fieldTSConfig']['noMatchingValue_label']) : '[ '.$this->fObj->getLL('l_noMatchingValue').' ]';
+
+                       // Register the required number of elements:
+               # $this->fObj->requiredElements[$PA['itemFormElName']] = array($minitems,$maxitems,'imgName'=>$table.'_'.$row['uid'].'_'.$field);
+
+                       // Perform modification of the selected items array:
+               $itemArray = t3lib_div::trimExplode(',',$PA['itemFormElValue'],1);
+               foreach($itemArray as $tk => $tv) {
+                       $tvP = explode('|',$tv,2);
+                               // get the records for this uid using t3lib_transferdata
+                       $records[] = $this->getRecord($row['pid'], $config['foreign_table'], $tvP[0]);
+               }
+
+               return $records;
+       }
+
+
+       /**
+        * Get possible records.
+        * Copied from TCEform and modified.
+        *
+        * @param       string          The table name of the record
+        * @param       string          The field name which this element is supposed to edit
+        * @param       array           The record data array where the value(s) for the field can be found
+        * @param       array           An array with additional configuration options.
+        * @param       string          $checkForConfField: For which field in the foreign_table the possible records should be fetched
+        * @return      array           Array of possible record items
+        */
+       function getPossibleRecords($table,$field,$row,$conf,$checkForConfField='foreign_selector') {
+                       // Field configuration from TCA:
+               $foreign_table = $conf['foreign_table'];
+               $foreign_check = $conf[$checkForConfField];
+
+               $PA = array();
+               $PA['fieldConf'] = $GLOBALS['TCA'][$foreign_table]['columns'][$foreign_check];
+               $PA['fieldConf']['config']['form_type'] = $PA['fieldConf']['config']['form_type'] ? $PA['fieldConf']['config']['form_type'] : $PA['fieldConf']['config']['type'];       // Using "form_type" locally in this script
+               $PA['fieldTSConfig'] = $this->fObj->setTSconfig($foreign_table,array(),$foreign_check);
+               $config = $PA['fieldConf']['config'];
+
+                       // Getting the selector box items from the system
+               $selItems = $this->fObj->addSelectOptionsToItemArray($this->fObj->initItemArray($PA['fieldConf']),$PA['fieldConf'],$this->fObj->setTSconfig($table,$row),$field);
+               if ($config['itemsProcFunc']) $selItems = $this->fObj->procItems($selItems,$PA['fieldTSConfig']['itemsProcFunc.'],$config,$table,$row,$field);
+
+                       // Possibly remove some items:
+               $removeItems = t3lib_div::trimExplode(',',$PA['fieldTSConfig']['removeItems'],1);
+               foreach($selItems as $tk => $p) {
+
+                               // Checking languages and authMode:
+                       $languageDeny = $TCA[$table]['ctrl']['languageField'] && !strcmp($TCA[$table]['ctrl']['languageField'], $field) && !$GLOBALS['BE_USER']->checkLanguageAccess($p[1]);
+                       $authModeDeny = $config['form_type']=='select' && $config['authMode'] && !$GLOBALS['BE_USER']->checkAuthMode($table,$field,$p[1],$config['authMode']);
+                       if (in_array($p[1],$removeItems) || $languageDeny || $authModeDeny)     {
+                               unset($selItems[$tk]);
+                       } elseif (isset($PA['fieldTSConfig']['altLabels.'][$p[1]])) {
+                               $selItems[$tk][0]=$this->fObj->sL($PA['fieldTSConfig']['altLabels.'][$p[1]]);
+                       }
+
+                               // Removing doktypes with no access:
+                       if ($table.'.'.$field == 'pages.doktype')       {
+                               if (!($GLOBALS['BE_USER']->isAdmin() || t3lib_div::inList($GLOBALS['BE_USER']->groupData['pagetypes_select'],$p[1])))   {
+                                       unset($selItems[$tk]);
+                               }
+                       }
+               }
+
+               return $selItems;
+       }
+
+       /**
+        * Gets the uids of a select/selector that should be unique an have already been used.
+        *
+        * @param       array           $records: All inline records on this level
+        * @param       array           $conf: The TCA field configuration of the inline field to be rendered
+        * @return      array           The uids, that have been used already and should be used unique
+        */
+       function getUniqueIds($records, $conf=array()) {
+               $uniqueIds = array();
+
+               if ($conf['foreign_unique'] && count($records))
+                       foreach ($records as $rec) $uniqueIds[$rec['uid']] = $rec[$conf['foreign_unique']];
+
+               return $uniqueIds;
+       }
+
+
+       /**
+        * Get a single record row for a TCA table from the database.
+        * t3lib_transferData is used for "upgrading" the values, especially the relations.
+        *
+        * @param       integer         $pid: The pid of the page the record should be stored (only relevant for NEW records)
+        * @param       string          $table: The table to fetch data from (= foreign_table)
+        * @param       string          $uid: The uid of the record to fetch, or empty if a new one should be created
+        * @param       string          $cmd: The command to perform, empty or 'new'
+        * @return      array           A record row from the database post-processed by t3lib_transferData
+        */
+       function getRecord($pid, $table, $uid, $cmd='') {
+               $trData = t3lib_div::makeInstance('t3lib_transferData');
+               $trData->addRawData = TRUE;
+               # $trData->defVals = $this->defVals;
+               $trData->lockRecords=1;
+               $trData->disableRTE = $GLOBALS['SOBE']->MOD_SETTINGS['disableRTE'];
+                       // if a new record should be created
+               $trData->fetchRecord($table, $uid, ($cmd === 'new' ? 'new' : ''));
+               reset($trData->regTableItems_data);
+               $rec = current($trData->regTableItems_data);
+               $rec['uid'] = $cmd == 'new' ? uniqid('NEW') : $uid;
+               if ($cmd=='new') $rec['pid'] = $pid;
+
+               return $rec;
+       }
+
+
+       /**
+        * Wrapper. Calls getRecord in case of a new record should be created.
+        *
+        * @param       integer         $pid: The pid of the page the record should be stored (only relevant for NEW records)
+        * @param       string          $table: The table to fetch data from (= foreign_table)
+        * @return      array           A record row from the database post-processed by t3lib_transferData
+        */
+       function getNewRecord($pid, $table) {
+               return $this->getRecord($pid, $table, '', 'new');
+       }
+
+
+       /*******************************************************
+        *
+        * Structure stack for handling inline objects/levels
+        *
+        *******************************************************/
+
+
+       /**
+        * Add a new level on top of the structure stack. Other functions can access the
+        * stack and determine, if there's possibly a endless loop.
+        *
+        * @param       string          $table: The table name of the record
+        * @param       string          $uid: The uid of the record that embeds the inline data
+        * @param       string          $field: The field name which this element is supposed to edit
+        * @param       array           $config: The TCA-configuration of the inline field
+        * @return      void
+        */
+       function pushStructure($table, $uid, $field = '', $config = array()) {
+               $this->inlineStructure['stable'][] = array(
+                       'table' => $table,
+                       'uid' => $uid,
+                       'field' => $field,
+                       'config' => $config,
+               );
+               $this->updateStructureNames();
+       }
+
+
+       /**
+        * Remove the item on top of the structure stack and return it.
+        *
+        * @return      array           The top item of the structure stack - array(<table>,<uid>,<field>,<config>)
+        */
+       function popStructure() {
+               if (count($this->inlineStructure['stable'])) {
+                       $popItem = array_pop($this->inlineStructure['stable']);
+                       $this->updateStructureNames();
+               }
+               return $popItem;
+       }
+
+
+       /**
+        * For common use of DOM object-ids and form field names of a several inline-level,
+        * these names/identifiers are preprocessed and set to $this->inlineNames.
+        * This function is automatically called if a level is pushed to or removed from the
+        * inline structure stack.
+        *
+        * @return      void
+        */
+       function updateStructureNames() {
+               $current = $this->getStructureLevel(-1);
+               $lastItemName = $this->getStructureItemName($current);
+               $this->inlineNames = array(
+                       'form' => $this->prependFormFieldNames.$lastItemName,
+                       'object' => $this->prependNaming.'['.$this->inlineFirstPid.']'.$this->getStructurePath(),
+               );
+       }
+
+
+       /**
+        * Create a name/id for usage in HTML output of a level of the structure stack.
+        *
+        * @param       array           $levelData: Array of a level of the structure stack (containing the keys table, uid and field)
+        * @return      string          The name/id of that level, to be used for HTML output
+        */
+       function getStructureItemName($levelData) {
+               return  '['.$levelData['table'].']' .
+                               '['.$levelData['uid'].']' .
+                               (isset($levelData['field']) ? '['.$levelData['field'].']' : '');
+       }
+
+
+       /**
+        * Get a level from the stack and return the data.
+        * If the $level value is negative, this function works top-down,
+        * if the $level value is positive, this function works bottom-up.
+        *
+        * @param       integer         $level: Which level to return
+        * @return      array           The item of the stack at the requested level
+        */
+       function getStructureLevel($level) {
+               $inlineStructureCount = count($this->inlineStructure['stable']);
+               if ($level < 0) $level = $inlineStructureCount+$level;
+               if ($level >= 0 && $level < $inlineStructureCount)
+                       return $this->inlineStructure['stable'][$level];
+               else
+                       return false;
+       }
+
+
+       /**
+        * Get the identifiers of a given depth of level, from the top of the stack to the bottom.
+        * An identifier consists looks like [<table>][<uid>][<field>].
+        *
+        * @param       integer         $structureDepth: How much levels to output, beginning from the top of the stack
+        * @return      string          The path of identifiers
+        */
+       function getStructurePath($structureDepth = -1) {
+               $structureCount = count($this->inlineStructure['stable']);
+               if ($structureDepth < 0 || $structureDepth > $structureCount) $structureDepth = $structureCount;
+
+               for ($i = 1; $i <= $structureDepth; $i++) {
+                       $current = $this->getStructureLevel(-$i);
+                       $string = $this->getStructureItemName($current).$string;
+               }
+
+               return $string;
+       }
+
+
+       /**
+        * Convert the DOM object-id of an inline container to an array.
+        * The object-id could look like 'data[inline][tx_mmftest_company][1][employees]'.
+        * The result is written to $this->inlineStructure.
+        * There are two keys:
+        *  - 'stable': Containing full qualified identifiers (table, uid and field)
+        *  - 'unstable': Containting partly filled data (e.g. only table and possibly field)
+        *
+        * @param       string          $domObjectId: The DOM object-id
+        * @param       boolean         $loadConfig: Load the TCA configuration for that level
+        * @return      void
+        */
+       function parseStructureString($string, $loadConfig = false) {
+               global $TCA;
+
+               $unstable = array();
+               $vector = array('table', 'uid', 'field');
+               $pattern = '/^'.$this->prependNaming.'\[(.+?)\]\[(.+)\]$/';
+               if (preg_match($pattern, $string, $match)) {
+                       $this->inlineFirstPid = $match[1];
+                       $parts = explode('][', $match[2]);
+                       $partsCnt = count($parts);
+                       for ($i = 0; $i < $partsCnt; $i++) {
+                               if ($i > 0 && $i % 3 == 0) {
+                                               // load the TCA configuration of the table field and store it in the stack
+                                       if ($loadConfig) {
+                                               t3lib_div::loadTCA($unstable['table']);
+                                               $unstable['config'] = $TCA[$unstable['table']]['columns'][$unstable['field']]['config'];
+                                       }
+                                       $this->inlineStructure['stable'][] = $unstable;
+                                       $unstable = array();
+                               }
+                               $unstable[$vector[$i % 3]] = $parts[$i];
+                       }
+                       $this->updateStructureNames();
+                       if (count($unstable)) $this->inlineStructure['unstable'] = $unstable;
+               }
+       }
+
+
+       /*******************************************************
+        *
+        * Helper functions
+        *
+        *******************************************************/
+
+
+       /**
+        * Does some checks on the TCA configuration of the inline field to render.
+        *
+        * @param       array           $config: Reference to the TCA field configuration
+        * @return      boolean         If critical configuration errors were found, false is returned
+        */
+       function checkConfiguration(&$config) {
+               $foreign_table = $config['foreign_table'];
+
+                       // an inline field must have a foreign_table, if not, stop all further inline actions for this field
+               if (!$foreign_table || !is_array($GLOBALS['TCA'][$foreign_table]))
+                       return false;
+
+               if (!is_array($config['appearance']))
+                       $config['appearance'] = array();
+               if (!in_array($config['appearance']['newRecordLinkPosition'], array('top', 'bottom', 'both')))
+                       $config['appearance']['newRecordLinkPosition'] = 'top';
+
+               return true;
+       }
+
+
+       /**
+        * Checks the page access rights (Code for access check mostly taken from alt_doc.php)
+        * as well as the table access rights of the user.
+        *
+        * @param       string          $cmd: The command that sould be performed ('new' or 'edit')
+        * @param       string          $table: The table to check access for
+        * @param       string          $theUid: The record uid of the table
+        * @return      boolean         Returns true is the user has access, or false if not
+        */
+       function checkAccess($cmd, $table, $theUid) {
+               global $BE_USER;
+
+                       // Checking if the user has permissions? (Only working as a precaution, because the final permission check is always down in TCE. But it's good to notify the user on beforehand...)
+                       // First, resetting flags.
+               $hasAccess = 0;
+               $deniedAccessReason = '';
+
+                       // If the command is to create a NEW record...:
+               if ($cmd=='new') {
+                       $calcPRec = t3lib_BEfunc::getRecord('pages',$this->inlineFirstPid);
+                       if(!is_array($calcPRec)) {
+                               return false;
+                       }
+                       $CALC_PERMS = $BE_USER->calcPerms($calcPRec);   // Permissions for the parent page
+                       if ($table=='pages')    {       // If pages:
+                               $hasAccess = $CALC_PERMS&8 ? 1 : 0; // Are we allowed to create new subpages?
+                       } else {
+                               $hasAccess = $CALC_PERMS&16 ? 1 : 0; // Are we allowed to edit content on this page?
+                       }
+               } else {        // Edit:
+                       $calcPRec = t3lib_BEfunc::getRecord($table,$theUid);
+                       t3lib_BEfunc::fixVersioningPid($table,$calcPRec);
+                       if (is_array($calcPRec))        {
+                               if ($table=='pages')    {       // If pages:
+                                       $CALC_PERMS = $BE_USER->calcPerms($calcPRec);
+                                       $hasAccess = $CALC_PERMS&2 ? 1 : 0;
+                               } else {
+                                       $CALC_PERMS = $BE_USER->calcPerms(t3lib_BEfunc::getRecord('pages',$calcPRec['pid']));   // Fetching pid-record first.
+                                       $hasAccess = $CALC_PERMS&16 ? 1 : 0;
+                               }
+
+                                       // Check internals regarding access:
+                               if ($hasAccess) {
+                                       $hasAccess = $BE_USER->recordEditAccessInternals($table, $calcPRec);
+                               }
+                       }
+               }
+
+               if(!$BE_USER->check('tables_modify', $table)) {
+                       $hasAccess = 0;
+               }
+
+               if(!$hasAccess) {
+                       $deniedAccessReason = $BE_USER->errorMsg;
+                       if($deniedAccessReason) {
+                               debug($deniedAccessReason);
+                       }
+               }
+
+               return $hasAccess ? true : false;
+       }
+
+
+       /**
+        * Check the keys and values in the $compare array against the ['config'] part of the top level of the stack.
+        * A boolean value is return depending on how the comparison was successful.
+        *
+        * @param       array           $compare: keys and values to compare to the ['config'] part of the top level of the stack
+        * @return      boolean         Whether the comparison was successful
+        * @see         arrayCompareComplex
+        */
+       function compareStructureConfiguration($compare) {
+               $level = $this->getStructureLevel(-1);
+               $result = $this->arrayCompareComplex($level, $compare);
+
+               return $result;
+       }
+
+
+       /**
+        * Normalize a relation "uid" published by transferData, like "1|Company%201"
+        *
+        * @param       string          $string: A transferData reference string, containing the uid
+        * @return      string          The normalized uid
+        */
+       function normalizeUid($string) {
+               $parts = explode('|', $string);
+               return $parts[0];
+       }
+
+
+       /**
+        * Wrap the HTML code of a section with a table tag.
+        *
+        * @param       string          $section: The HTML code to be wrapped
+        * @param       array           $styleAttrs: Attributes for the style argument in the table tag
+        * @param       array           $tableAttrs: Attributes for the table tag (like width, border, etc.)
+        * @return      string          The wrapped HTML code
+        */
+       function wrapFormsSection($section, $styleAttrs = array(), $tableAttrs = array()) {
+               if (!$styleAttrs['margin-right']) $styleAttrs['margin-right'] = '5px';
+
+               foreach ($styleAttrs as $key => $value) $style .= ($style?' ':'').$key.': '.htmlspecialchars($value).'; ';
+               if ($style) $style = ' style="'.$style.'"';
+
+               if (!$tableAttrs['background'] && $this->fObj->borderStyle[2]) $tableAttrs['background'] = $this->backPath.$this->borderStyle[2];
+               if (!$tableAttrs['cellspacing']) $tableAttrs['cellspacing'] = '0';
+               if (!$tableAttrs['cellpadding']) $tableAttrs['cellpadding'] = '0';
+               if (!$tableAttrs['border']) $tableAttrs['border'] = '0';
+               if (!$tableAttrs['width']) $tableAttrs['width'] = '100%';
+               if (!$tableAttrs['class'] && $this->borderStyle[3]) $tableAttrs['class'] = $this->borderStyle[3];
+
+               foreach ($tableAttrs as $key => $value) $table .= ($table?' ':'').$key.'="'.htmlspecialchars($value).'"';
+
+               $out = '<table '.$table.$style.'>'.$section.'</table>';
+               return $out;
+       }
+
+
+       /**
+        * Checks if the $table is the child of a inline type AND the $field is the label field of this table.
+        * This function is used to dynamically update the label while editing. This has no effect on labels,
+        * that were processed by a TCEmain-hook on saving.
+        *
+        * @param       string          $table: The table to check
+        * @param       string          $field: The field on this table to check
+        * @return      boolean         is inline child and field is responsible for the label
+        */
+       function isInlineChildAndLabelField($table, $field) {
+               $level = $this->getStructureLevel(-1);
+               if ($level['config']['foreign_label'])
+                       $label = $level['config']['foreign_label'];
+               else
+                       $label = $GLOBALS['TCA'][$table]['ctrl']['label'];
+               return $level['config']['foreign_table'] === $table && $label == $field ? true : false;
+       }
+
+
+       /**
+        * Get the depth of the stable structure stack.
+        * (count($this->inlineStructure['stable'])
+        *
+        * @return      integer         The depth of the structure stack
+        */
+       function getStructureDepth() {
+               return count($this->inlineStructure['stable']);
+       }
+
+
+       /**
+        * Handles complex comparison requests on an array.
+        * A request could look like the following:
+        *
+        * $searchArray = array(
+        *              '%AND'  => array(
+        *                      'key1'  => 'value1',
+        *                      'key2'  => 'value2',
+        *                      '%OR'   => array(
+        *                              'subarray' => array(
+        *                                      'subkey' => 'subvalue'
+        *                              ),
+        *                              'key3'  => 'value3',
+        *                              'key4'  => 'value4'
+        *                      )
+        *              )
+        * );
+        *
+        * It is possible to use the array keys '%AND.1', '%AND.2', etc. to prevent
+        * overwriting the sub-array. It could be neccessary, if you use complex comparisons.
+        *
+        * The example above means, key1 *AND* key2 (and their values) have to match with
+        * the $subjectArray and additional one *OR* key3 or key4 have to meet the same
+        * condition.
+        * It is also possible to compare parts of a sub-array (e.g. "subarray"), so this
+        * function recurses down one level in that sub-array.
+        *
+        * @param       array           $subjectArray: The array to search in
+        * @param       array           $searchArray: The array with keys and values to search for
+        * @param       string          $type: Use '%AND' or '%OR' for comparision
+        * @return      boolean         The result of the comparison
+        */
+       function arrayCompareComplex($subjectArray, $searchArray, $type = '') {
+               $localMatches = 0;
+               $localEntries = 0;
+
+               if (is_array($searchArray) && count($searchArray)) {
+                               // if no type was passed, try to determine
+                       if (!$type) {
+                               reset($searchArray);
+                               $type = key($searchArray);
+                               $searchArray = current($searchArray);
+                       }
+
+                               // we use '%AND' and '%OR' in uppercase
+                       $type = strtoupper($type);
+
+                               // split regular elements from sub elements
+                       foreach ($searchArray as $key => $value) {
+                               $localEntries++;
+
+                                       // process a sub-group of OR-conditions
+                               if ($key == '%OR') {
+                                       $localMatches += $this->arrayCompareComplex($subjectArray, $value, '%OR') ? 1 : 0;
+                                       // process a sub-group of AND-conditions
+                               } elseif ($key == '%AND') {
+                                       $localMatches += $this->arrayCompareComplex($subjectArray, $value, '%AND') ? 1 : 0;
+                                       // a part of an associative array should be compared, so step down in the array hierarchy
+                               } elseif (is_array($value) && $this->isAssociativeArray($searchArray)) {
+                                       $localMatches += $this->arrayCompareComplex($subjectArray[$key], $value, $type) ? 1 : 0;
+                                       // it is a normal array that is only used for grouping and indexing
+                               } elseif (is_array($value)) {
+                                       $localMatches += $this->arrayCompareComplex($subjectArray, $value, $type) ? 1 : 0;
+                                       // directly compare a value
+                               } else {
+                                       $localMatches += isset($subjectArray[$key]) && isset($value) && $subjectArray[$key] === $value ? 1 : 0;
+                               }
+
+                                       // if one or more matches are required ('OR'), return true after the first successful match
+                               if ($type == '%OR' && $localMatches > 0) return true;
+                                       // if all matches are required ('AND') and we have no result after the first run, return false
+                               if ($type == '%AND' && $localMatches == 0) return false;
+                       }
+               }
+
+                       // return the result for '%AND' (if nothing was checked, true is returned)
+               return $localEntries == $localMatches ? true : false;
+       }
+
+
+       /**
+        * Checks whether an object is an associative array.
+        *
+        * @param       mixed           $object: The object to be checked
+        * @return      boolean         Returns true, if the object is an associative array
+        */
+       function isAssociativeArray($object) {
+               return is_array($object) && count($object) && (array_keys($object) !== range(0, sizeof($object) - 1))
+                       ? true
+                       : false;
+       }
+
+
+       /**
+        * Makes a flat array from the $possibleRecords array.
+        * The key of the flat array is the value of the record,
+        * the value of the flat array is the label of the record.
+        *
+        * @param       array           $possibleRecords: The possibleRecords array (for select fields)
+        * @return      array           A flat array with key=uid, value=label
+        */
+       function getPossibleRecordsFlat($possibleRecords) {
+               $flat = array();
+               if (is_array($possibleRecords))
+                       foreach ($possibleRecords as $record) $flat[$record[1]] = $record[0];
+               return $flat;
+       }
+
+
+
+       /**
+        * Check, if a field should be skipped, that was defined to be handled as foreign_field or foreign_sortby of
+        * the parent record of the "inline"-type - if so, we have to skip this field - the rendering is done via "inline" as hidden field
+        *
+        * @param       string          $table: The table name
+        * @param       string          $field: The field name
+        * @param       array           $row: The record row from the database
+        * @param       array           $config: TCA configuration of the field
+        * @return      boolean         Determines whether the field should be skipped.
+        */
+       function skipField($table, $field, $row, $config) {
+               $skipThisField = false;
+
+               if ($this->getStructureDepth()) {
+                       $searchArray = array(
+                               '%OR' => array(
+                                       'config' => array(
+                                               0 => array(
+                                                       '%AND' => array(
+                                                               'foreign_table' => $table,
+                                                               '%OR' => array(
+                                                                       '%AND' => array(
+                                                                               'appearance' => array('useCombination' => 1),
+                                                                               'foreign_selector' => $field,
+                                                                       ),
+                                                                       'MM' => $config['MM']
+                                                               ),
+                                                       ),
+                                               ),
+                                               1 => array(
+                                                       'foreign_table' => $config['foreign_table'],
+                                                       'foreign_selector' => $config['foreign_field']
+                                               ),
+                                       ),
+                               ),
+                       );
+
+                               // get the parent record from structure stack
+                       $level = $this->getStructureLevel(-1);
+
+                               // if we have symmetric fields, check on which side we are and hide fields, that are set automatically
+                       if (t3lib_loadDBGroup::isOnSymmetricSide($level['uid'], $level['config'], $row)) {
+                               $searchArray['%OR']['config'][0]['%AND']['%OR']['symmetric_field'] = $field;
+                               $searchArray['%OR']['config'][0]['%AND']['%OR']['symmetric_sortby'] = $field;
+                               // hide fields, that are set automatically
+                       } else {
+                               $searchArray['%OR']['config'][0]['%AND']['%OR']['foreign_field'] = $field;
+                               $searchArray['%OR']['config'][0]['%AND']['%OR']['foreign_sortby'] = $field;
+                       }
+
+                       $skipThisField = $this->compareStructureConfiguration($searchArray, true);
+               }
+
+               return $skipThisField;
+       }
+ }
+?>
\ No newline at end of file
index 8a0eefa..6cf54d6 100755 (executable)
  *
  *
  *
- *  229: class t3lib_TCEmain
- *  348:     function start($data,$cmd,$altUserObject='')
- *  387:     function setMirror($mirror)
- *  412:     function setDefaultsFromUserTS($userTS)
- *  435:     function process_uploads($postFiles)
- *  473:     function process_uploads_traverseArray(&$outputArr,$inputArr,$keyToSet)
+ *  237: class t3lib_TCEmain
+ *  358:     function start($data,$cmd,$altUserObject='')
+ *  397:     function setMirror($mirror)
+ *  422:     function setDefaultsFromUserTS($userTS)
+ *  445:     function process_uploads($postFiles)
+ *  483:     function process_uploads_traverseArray(&$outputArr,$inputArr,$keyToSet)
  *
  *              SECTION: PROCESSING DATA
- *  509:     function process_datamap()
- *  815:     function placeholderShadowing($table,$id)
- *  851:     function fillInFieldArray($table,$id,$fieldArray,$incomingFieldArray,$realPid,$status,$tscPID)
+ *  519:     function process_datamap()
+ *  869:     function placeholderShadowing($table,$id)
+ *  905:     function fillInFieldArray($table,$id,$fieldArray,$incomingFieldArray,$realPid,$status,$tscPID)
  *
  *              SECTION: Evaluation of input values
- * 1072:     function checkValue($table,$field,$value,$id,$status,$realPid,$tscPID)
- * 1132:     function checkValue_SW($res,$value,$tcaFieldConf,$table,$id,$curValue,$status,$realPid,$recFID,$field,$uploadedFiles,$tscPID)
- * 1178:     function checkValue_input($res,$value,$tcaFieldConf,$PP,$field='')
- * 1216:     function checkValue_check($res,$value,$tcaFieldConf,$PP)
- * 1239:     function checkValue_radio($res,$value,$tcaFieldConf,$PP)
- * 1265:     function checkValue_group_select($res,$value,$tcaFieldConf,$PP,$uploadedFiles,$field)
- * 1366:     function checkValue_group_select_file($valueArray,$tcaFieldConf,$curValue,$uploadedFileArray,$status,$table,$id,$recFID)
- * 1536:     function checkValue_flex($res,$value,$tcaFieldConf,$PP,$uploadedFiles,$field)
- * 1609:     function checkValue_flexArray2Xml($array)
- * 1621:     function _DELETE_FLEX_FORMdata(&$valueArrayToRemoveFrom,$deleteCMDS)
- * 1643:     function _MOVE_FLEX_FORMdata(&$valueArrayToMoveIn, $moveCMDS, $direction)
+ * 1126:     function checkValue($table,$field,$value,$id,$status,$realPid,$tscPID)
+ * 1186:     function checkValue_SW($res,$value,$tcaFieldConf,$table,$id,$curValue,$status,$realPid,$recFID,$field,$uploadedFiles,$tscPID)
+ * 1235:     function checkValue_input($res,$value,$tcaFieldConf,$PP,$field='')
+ * 1273:     function checkValue_check($res,$value,$tcaFieldConf,$PP)
+ * 1296:     function checkValue_radio($res,$value,$tcaFieldConf,$PP)
+ * 1322:     function checkValue_group_select($res,$value,$tcaFieldConf,$PP,$uploadedFiles,$field)
+ * 1432:     function checkValue_group_select_file($valueArray,$tcaFieldConf,$curValue,$uploadedFileArray,$status,$table,$id,$recFID)
+ * 1601:     function checkValue_flex($res,$value,$tcaFieldConf,$PP,$uploadedFiles,$field)
+ * 1674:     function checkValue_flexArray2Xml($array, $addPrologue=FALSE)
+ * 1686:     function _DELETE_FLEX_FORMdata(&$valueArrayToRemoveFrom,$deleteCMDS)
+ * 1708:     function _MOVE_FLEX_FORMdata(&$valueArrayToMoveIn, $moveCMDS, $direction)
+ * 1748:     function checkValue_inline($res,$value,$tcaFieldConf,$PP,$field)
+ * 1791:     function checkValue_checkMax($tcaFieldConf, $valueArray)
  *
  *              SECTION: Helper functions for evaluation functions.
- * 1705:     function getUnique($table,$field,$value,$id,$newPid=0)
- * 1743:     function checkValue_input_Eval($value,$evalArray,$is_in)
- * 1839:     function checkValue_group_select_processDBdata($valueArray,$tcaFieldConf,$id,$status,$type)
- * 1872:     function checkValue_group_select_explodeSelectGroupValue($value)
- * 1896:     function checkValue_flex_procInData($dataPart,$dataPart_current,$uploadedFiles,$dataStructArray,$pParams,$callBackFunc='')
- * 1935:     function checkValue_flex_procInData_travDS(&$dataValues,$dataValues_current,$uploadedFiles,$DSelements,$pParams,$callBackFunc,$structurePath)
+ * 1843:     function getUnique($table,$field,$value,$id,$newPid=0)
+ * 1881:     function checkValue_input_Eval($value,$evalArray,$is_in)
+ * 1978:     function checkValue_group_select_processDBdata($valueArray,$tcaFieldConf,$id,$status,$type,$currentTable)
+ * 2022:     function checkValue_group_select_explodeSelectGroupValue($value)
+ * 2046:     function checkValue_flex_procInData($dataPart,$dataPart_current,$uploadedFiles,$dataStructArray,$pParams,$callBackFunc='')
+ * 2085:     function checkValue_flex_procInData_travDS(&$dataValues,$dataValues_current,$uploadedFiles,$DSelements,$pParams,$callBackFunc,$structurePath)
  *
  *              SECTION: PROCESSING COMMANDS
- * 2081:     function process_cmdmap()
+ * 2231:     function process_cmdmap()
  *
  *              SECTION: Cmd: Copying
- * 2221:     function copyRecord($table,$uid,$destPid,$first=0,$overrideValues=array(),$excludeFields='')
- * 2343:     function copyPages($uid,$destPid)
- * 2397:     function copySpecificPage($uid,$destPid,$copyTablesArray,$first=0)
- * 2431:     function copyRecord_raw($table,$uid,$pid,$overrideArray=array())
- * 2495:     function rawCopyPageContent($old_pid,$new_pid,$copyTablesArray)
- * 2519:     function insertNewCopyVersion($table,$fieldArray,$realPid)
- * 2570:     function copyRecord_procBasedOnFieldType($table,$uid,$field,$value,$row,$conf)
- * 2626:     function copyRecord_flexFormCallBack($pParams, $dsConf, $dataValue, $dataValue_ext1, $dataValue_ext2)
- * 2654:     function copyRecord_procFilesRefs($conf, $uid, $value)
+ * 2371:     function copyRecord($table,$uid,$destPid,$first=0,$overrideValues=array(),$excludeFields='')
+ * 2493:     function copyPages($uid,$destPid)
+ * 2547:     function copySpecificPage($uid,$destPid,$copyTablesArray,$first=0)
+ * 2581:     function copyRecord_raw($table,$uid,$pid,$overrideArray=array())
+ * 2645:     function rawCopyPageContent($old_pid,$new_pid,$copyTablesArray)
+ * 2669:     function insertNewCopyVersion($table,$fieldArray,$realPid)
+ * 2720:     function copyRecord_procBasedOnFieldType($table,$uid,$field,$value,$row,$conf)
+ * 2791:     function copyRecord_flexFormCallBack($pParams, $dsConf, $dataValue, $dataValue_ext1, $dataValue_ext2)
+ * 2819:     function copyRecord_procFilesRefs($conf, $uid, $value)
  *
  *              SECTION: Cmd: Moving, Localizing
- * 2723:     function moveRecord($table,$uid,$destPid)
- * 2910:     function localize($table,$uid,$language)
+ * 2888:     function moveRecord($table,$uid,$destPid)
+ * 3083:     function moveRecord_procFields($table,$uid,$destPid)
+ * 3103:     function moveRecord_procBasedOnFieldType($table,$uid,$destPid,$field,$value,$conf)
+ * 3136:     function localize($table,$uid,$language)
  *
  *              SECTION: Cmd: Deleting
- * 2994:     function deleteAction($table, $id)
- * 3041:     function deleteEl($table, $uid, $noRecordCheck=FALSE, $forceHardDelete=FALSE)
- * 3056:     function undeleteRecord($table,$uid)
- * 3073:     function deleteRecord($table,$uid, $noRecordCheck=FALSE, $forceHardDelete=FALSE,$undeleteRecord=FALSE)
- * 3159:     function deletePages($uid,$force=FALSE,$forceHardDelete=FALSE)
- * 3187:     function deleteSpecificPage($uid,$forceHardDelete=FALSE)
- * 3210:     function canDeletePage($uid)
- * 3237:     function cannotDeleteRecord($table,$id)
+ * 3220:     function deleteAction($table, $id)
+ * 3267:     function deleteEl($table, $uid, $noRecordCheck=FALSE, $forceHardDelete=FALSE)
+ * 3282:     function undeleteRecord($table,$uid)
+ * 3299:     function deleteRecord($table,$uid, $noRecordCheck=FALSE, $forceHardDelete=FALSE,$undeleteRecord=FALSE)
+ * 3396:     function deletePages($uid,$force=FALSE,$forceHardDelete=FALSE)
+ * 3424:     function deleteSpecificPage($uid,$forceHardDelete=FALSE)
+ * 3447:     function canDeletePage($uid)
+ * 3474:     function cannotDeleteRecord($table,$id)
+ * 3493:     function deleteRecord_procFields($table, $uid, $undeleteRecord = false)
+ * 3516:     function deleteRecord_procBasedOnFieldType($table, $uid, $field, $value, $conf, $undeleteRecord = false)
  *
  *              SECTION: Cmd: Versioning
- * 3275:     function versionizeRecord($table,$id,$label,$delete=FALSE,$versionizeTree=-1)
- * 3351:     function versionizePages($uid,$label,$versionizeTree)
- * 3414:     function version_swap($table,$id,$swapWith,$swapIntoWS=0)
- * 3585:     function version_clearWSID($table,$id)
- * 3619:     function version_setStage($table,$id,$stageId,$comment='')
+ * 3577:     function versionizeRecord($table,$id,$label,$delete=FALSE,$versionizeTree=-1)
+ * 3653:     function versionizePages($uid,$label,$versionizeTree)
+ * 3716:     function version_swap($table,$id,$swapWith,$swapIntoWS=0)
+ * 3887:     function version_clearWSID($table,$id)
+ * 3921:     function version_setStage($table,$id,$stageId,$comment='')
  *
  *              SECTION: Cmd: Helper functions
- * 3664:     function remapListedDBRecords()
- * 3741:     function remapListedDBRecords_flexFormCallBack($pParams, $dsConf, $dataValue, $dataValue_ext1, $dataValue_ext2)
- * 3767:     function remapListedDBRecords_procDBRefs($conf, $value, $MM_localUid)
+ * 3966:     function remapListedDBRecords()
+ * 4047:     function remapListedDBRecords_flexFormCallBack($pParams, $dsConf, $dataValue, $dataValue_ext1, $dataValue_ext2)
+ * 4074:     function remapListedDBRecords_procDBRefs($conf, $value, $MM_localUid, $table)
+ * 4120:     function remapListedDBRecords_procInline($conf, $value, $uid, $table)
  *
  *              SECTION: Access control / Checking functions
- * 3832:     function checkModifyAccessList($table)
- * 3844:     function isRecordInWebMount($table,$id)
- * 3858:     function isInWebMount($pid)
- * 3872:     function checkRecordUpdateAccess($table,$id)
- * 3896:     function checkRecordInsertAccess($insertTable,$pid,$action=1)
- * 3930:     function isTableAllowedForThisPage($page_uid, $checkTable)
- * 3963:     function doesRecordExist($table,$id,$perms)
- * 4024:     function doesRecordExist_pageLookUp($id, $perms)
- * 4050:     function doesBranchExist($inList,$pid,$perms,$recurse)
- * 4084:     function tableReadOnly($table)
- * 4096:     function tableAdminOnly($table)
- * 4110:     function destNotInsideSelf($dest,$id)
- * 4142:     function getExcludeListArray()
- * 4165:     function doesPageHaveUnallowedTables($page_uid,$doktype)
+ * 4163:     function checkModifyAccessList($table)
+ * 4175:     function isRecordInWebMount($table,$id)
+ * 4189:     function isInWebMount($pid)
+ * 4203:     function checkRecordUpdateAccess($table,$id)
+ * 4227:     function checkRecordInsertAccess($insertTable,$pid,$action=1)
+ * 4261:     function isTableAllowedForThisPage($page_uid, $checkTable)
+ * 4294:     function doesRecordExist($table,$id,$perms)
+ * 4355:     function doesRecordExist_pageLookUp($id, $perms)
+ * 4381:     function doesBranchExist($inList,$pid,$perms,$recurse)
+ * 4415:     function tableReadOnly($table)
+ * 4427:     function tableAdminOnly($table)
+ * 4441:     function destNotInsideSelf($dest,$id)
+ * 4473:     function getExcludeListArray()
+ * 4496:     function doesPageHaveUnallowedTables($page_uid,$doktype)
  *
  *              SECTION: Information lookup
- * 4214:     function pageInfo($id,$field)
- * 4234:     function recordInfo($table,$id,$fieldList)
- * 4255:     function getRecordProperties($table,$id,$noWSOL=FALSE)
- * 4271:     function getRecordPropertiesFromRow($table,$row)
+ * 4545:     function pageInfo($id,$field)
+ * 4565:     function recordInfo($table,$id,$fieldList)
+ * 4586:     function getRecordProperties($table,$id,$noWSOL=FALSE)
+ * 4602:     function getRecordPropertiesFromRow($table,$row)
  *
  *              SECTION: Storing data to Database Layer
- * 4314:     function updateDB($table,$id,$fieldArray)
- * 4366:     function insertDB($table,$id,$fieldArray,$newVersion=FALSE,$suggestedUid=0,$dontSetNewIdIndex=FALSE)
- * 4439:     function checkStoredRecord($table,$id,$fieldArray,$action)
- * 4476:     function setHistory($table,$id,$logId)
- * 4509:     function clearHistory($maxAgeSeconds=604800,$table)
- * 4523:     function updateRefIndex($table,$id)
+ * 4645:     function updateDB($table,$id,$fieldArray)
+ * 4697:     function insertDB($table,$id,$fieldArray,$newVersion=FALSE,$suggestedUid=0,$dontSetNewIdIndex=FALSE)
+ * 4770:     function checkStoredRecord($table,$id,$fieldArray,$action)
+ * 4807:     function setHistory($table,$id,$logId)
+ * 4840:     function clearHistory($maxAgeSeconds=604800,$table)
+ * 4854:     function updateRefIndex($table,$id)
  *
  *              SECTION: Misc functions
- * 4555:     function getSortNumber($table,$uid,$pid)
- * 4628:     function resorting($table,$pid,$sortRow, $return_SortNumber_After_This_Uid)
- * 4659:     function setTSconfigPermissions($fieldArray,$TSConfig_p)
- * 4676:     function newFieldArray($table)
- * 4708:     function addDefaultPermittedLanguageIfNotSet($table,&$incomingFieldArray)
- * 4732:     function overrideFieldArray($table,$data)
- * 4748:     function compareFieldArrayWithCurrentAndUnset($table,$id,$fieldArray)
- * 4794:     function assemblePermissions($string)
- * 4811:     function rmComma($input)
- * 4821:     function convNumEntityToByteValue($input)
- * 4843:     function destPathFromUploadFolder($folder)
- * 4853:     function deleteClause($table)
- * 4869:     function getTCEMAIN_TSconfig($tscPID)
- * 4884:     function getTableEntries($table,$TSconfig)
- * 4897:     function getPID($table,$uid)
- * 4910:     function dbAnalysisStoreExec()
- * 4926:     function removeRegisteredFiles()
- * 4938:     function removeCacheFiles()
- * 4952:     function int_pageTreeInfo($CPtable,$pid,$counter, $rootID)
- * 4973:     function compileAdminTables()
- * 4990:     function fixUniqueInPid($table,$uid)
- * 5026:     function fixCopyAfterDuplFields($table,$uid,$prevUid,$update, $newData=array())
- * 5051:     function extFileFields($table)
- * 5072:     function getUniqueFields($table)
- * 5097:     function isReferenceField($conf)
- * 5112:     function getCopyHeader($table,$pid,$field,$value,$count,$prevTitle='')
- * 5141:     function prependLabel($table)
- * 5158:     function resolvePid($table,$pid)
- * 5188:     function clearPrefixFromValue($table,$value)
- * 5203:     function extFileFunctions($table,$field,$filelist,$func)
- * 5233:     function noRecordsFromUnallowedTables($inList)
- * 5259:     function notifyStageChange($stat,$stageId,$table,$id,$comment)
- * 5354:     function notifyStageChange_getEmails($listOfUsers,$noTablePrefix=FALSE)
+ * 4886:     function getSortNumber($table,$uid,$pid)
+ * 4959:     function resorting($table,$pid,$sortRow, $return_SortNumber_After_This_Uid)
+ * 4990:     function setTSconfigPermissions($fieldArray,$TSConfig_p)
+ * 5007:     function newFieldArray($table)
+ * 5039:     function addDefaultPermittedLanguageIfNotSet($table,&$incomingFieldArray)
+ * 5063:     function overrideFieldArray($table,$data)
+ * 5079:     function compareFieldArrayWithCurrentAndUnset($table,$id,$fieldArray)
+ * 5125:     function assemblePermissions($string)
+ * 5142:     function rmComma($input)
+ * 5152:     function convNumEntityToByteValue($input)
+ * 5174:     function destPathFromUploadFolder($folder)
+ * 5184:     function deleteClause($table)
+ * 5200:     function getTCEMAIN_TSconfig($tscPID)
+ * 5215:     function getTableEntries($table,$TSconfig)
+ * 5228:     function getPID($table,$uid)
+ * 5241:     function dbAnalysisStoreExec()
+ * 5257:     function removeRegisteredFiles()
+ * 5269:     function removeCacheFiles()
+ * 5283:     function int_pageTreeInfo($CPtable,$pid,$counter, $rootID)
+ * 5304:     function compileAdminTables()
+ * 5321:     function fixUniqueInPid($table,$uid)
+ * 5357:     function fixCopyAfterDuplFields($table,$uid,$prevUid,$update, $newData=array())
+ * 5382:     function extFileFields($table)
+ * 5403:     function getUniqueFields($table)
+ * 5428:     function isReferenceField($conf)
+ * 5439:     function getInlineFieldType($conf)
+ * 5462:     function getCopyHeader($table,$pid,$field,$value,$count,$prevTitle='')
+ * 5491:     function prependLabel($table)
+ * 5508:     function resolvePid($table,$pid)
+ * 5538:     function clearPrefixFromValue($table,$value)
+ * 5553:     function extFileFunctions($table,$field,$filelist,$func)
+ * 5583:     function noRecordsFromUnallowedTables($inList)
+ * 5609:     function notifyStageChange($stat,$stageId,$table,$id,$comment)
+ * 5704:     function notifyStageChange_getEmails($listOfUsers,$noTablePrefix=FALSE)
  *
  *              SECTION: Clearing cache
- * 5400:     function clear_cache($table,$uid)
- * 5510:     function clear_cacheCmd($cacheCmd)
+ * 5750:     function clear_cache($table,$uid)
+ * 5860:     function clear_cacheCmd($cacheCmd)
  *
  *              SECTION: Logging
- * 5616:     function log($table,$recuid,$action,$recpid,$error,$details,$details_nr=-1,$data=array(),$event_pid=-1,$NEWid='')
- * 5633:     function newlog($message, $error=0)
- * 5643:     function printLogErrorMessages($redirect)
+ * 5966:     function log($table,$recuid,$action,$recpid,$error,$details,$details_nr=-1,$data=array(),$event_pid=-1,$NEWid='')
+ * 5983:     function newlog($message, $error=0)
+ * 5993:     function printLogErrorMessages($redirect)
  *
- * TOTAL FUNCTIONS: 115
+ * TOTAL FUNCTIONS: 123
  * (This index is automatically created/updated by the extension "extdeveval")
  *
  */
@@ -320,6 +328,8 @@ class t3lib_TCEmain {
        var $uploadedFileArray = array();                       // Uploaded files, set by process_uploads()
        var $registerDBList=array();                            // Used for tracking references that might need correction after operations
        var $copyMappingArray = Array();                        // Used by the copy action to track the ids of new pages so subpages are correctly inserted! THIS is internally cleared for each executed copy operation! DO NOT USE THIS FROM OUTSIDE! Read from copyMappingArray_merged instead which is accumulating this information.
+       var $remapStack = array();                                      // array used for remapping uids and values at the end of process_datamap
+       var $updateRefIndexStack = array();                     // array used for additional calls to $this->updateRefIndex
 
                // Various
        var $fileFunc;                                                          // For "singleTon" file-manipulation object
@@ -803,6 +813,50 @@ class t3lib_TCEmain        {
                                }
                        }
                }
+
+               // call_user_func_array
+
+                       // Process the stack of relations to remap/correct
+               if(is_array($this->remapStack)) {
+                       foreach($this->remapStack as $remapAction) {
+                                       // if no position index for the arguments was set, skip this remap action
+                               if (!is_array($remapAction['pos'])) continue;
+
+                                       // load values from the argument array in remapAction
+                               $field = $remapAction['field'];
+                               $id = $remapAction['args'][$remapAction['pos']['id']];
+                               $table = $remapAction['args'][$remapAction['pos']['table']];
+                               $valueArray = $remapAction['args'][$remapAction['pos']['valueArray']];
+                               $tcaFieldConf = $remapAction['args'][$remapAction['pos']['tcaFieldConf']];
+
+                                       // Replace NEW... IDs with real uids.
+                               if(strpos($id, 'NEW') !== false) {
+                                       $id = $this->substNEWwithIDs[$id];
+                                       $remapAction['args'][$remapAction['pos']['id']] = $id;
+                               }
+
+                                       // Replace relations to NEW...-IDs in values
+                               if(is_array($valueArray)) {
+                                       foreach($valueArray as $key => $value) {
+                                               if(strpos($value, 'NEW') !== false) {
+                                                       $valueArray[$key] = $this->substNEWwithIDs[$value];
+                                               }
+                                       }
+                                       $remapAction['args'][$remapAction['pos']['valueArray']] = $valueArray;
+                               }
+
+                                       // process the arguments with the defined function
+                               $remapAction['args'][$remapAction['pos']['valueArray']] = call_user_func_array(
+                                       array($this, $remapAction['func']),
+                                       $remapAction['args']
+                               );
+
+                                       // @TODO: Add option to disable count-field
+                               $newVal = $this->checkValue_checkMax($tcaFieldConf, $remapAction['args'][$remapAction['pos']['valueArray']]);
+                               $this->updateDB($table,$id,array($field => implode(',', $newVal)));
+                       }
+               }
+
                $this->dbAnalysisStoreExec();
                $this->removeRegisteredFiles();
        }
@@ -1154,6 +1208,9 @@ class t3lib_TCEmain       {
                        case 'select':
                                $res = $this->checkValue_group_select($res,$value,$tcaFieldConf,$PP,$uploadedFiles,$field);
                        break;
+                       case 'inline':
+                               $res = $this->checkValue_inline($res,$value,$tcaFieldConf,$PP,$field);
+                       break;
                        case 'flex':
                                if ($field)     {       // FlexForms are only allowed for real fields.
                                        $res = $this->checkValue_flex($res,$value,$tcaFieldConf,$PP,$uploadedFiles,$field);
@@ -1336,28 +1393,26 @@ class t3lib_TCEmain     {
                }
                        // For select types which has a foreign table attached:
                if ($tcaFieldConf['type']=='select' && $tcaFieldConf['foreign_table'])  {
-                       $valueArray = $this->checkValue_group_select_processDBdata($valueArray,$tcaFieldConf,$id,$status,'select', $table);
+                               // check, if there is a NEW... id in the value, that should be substituded later
+                       if (strpos($value, 'NEW') !== false) {
+                               $this->remapStack[] = array(
+                                       'func' => 'checkValue_group_select_processDBdata',
+                                       'args' => array($valueArray,$tcaFieldConf,$id,$status,'select',$table),
+                                       'pos' => array('valueArray' => 0, 'tcaFieldConf' => 1, 'id' => 2, 'table' => 5),
+                                       'field' => $field
+                               );
+                               $unsetResult = true;
+                       } else {
+                               $valueArray = $this->checkValue_group_select_processDBdata($valueArray,$tcaFieldConf,$id,$status,'select', $table);
+                       }
                }
 
-// BTW, checking for min and max items here does NOT make any sense when MM is used because the above function calls will just return an array with a single item (the count) if MM is used... Why didn't I perform the check before? Probably because we could not evaluate the validity of record uids etc... Hmm...
-
-                       // Checking the number of items, that it is correct.
-                       // If files, there MUST NOT be too many files in the list at this point, so check that prior to this code.
-               $valueArrayC = count($valueArray);
-               $minI = isset($tcaFieldConf['minitems']) ? intval($tcaFieldConf['minitems']):0;
-
-                       // NOTE to the comment: It's not really possible to check for too few items, because you must then determine first, if the field is actual used regarding the CType.
-               $maxI = isset($tcaFieldConf['maxitems']) ? intval($tcaFieldConf['maxitems']):1;
-               if ($valueArrayC > $maxI)       {$valueArrayC=$maxI;}   // Checking for not too many elements
-
-                       // Dumping array to list
-               $newVal=array();
-               foreach($valueArray as $nextVal)        {
-                       if ($valueArrayC==0)    {break;}
-                       $valueArrayC--;
-                       $newVal[]=$nextVal;
+               if (!$unsetResult) {
+                       $newVal=$this->checkValue_checkMax($tcaFieldConf, $valueArray);
+                       $res['value'] = implode(',',$newVal);
+               } else {
+                       unset($res['value']);
                }
-               $res['value'] = implode(',',$newVal);
 
                return $res;
        }
@@ -1531,8 +1586,7 @@ class t3lib_TCEmain       {
                                } else {
                                        $this->dbAnalysisStore[] = array($dbAnalysis, $tcaFieldConf['MM'], $id, 0);     // This will be traversed later to execute the actions
                                }
-                               $cc=count($dbAnalysis->itemArray);
-                               $valueArray = array($cc);
+                               $valueArray = $dbAnalysis->countItems();
                        }
                }
 
@@ -1637,7 +1691,7 @@ class t3lib_TCEmain       {
         * Deletes a flex form element
         *
         * @param       array           &$valueArrayToRemoveFrom: by reference
-        * @param       [type]          $deleteCMDS: ...         *
+        * @param       array           $deleteCMDS: ...         *
         * @return      void
         */
        function _DELETE_FLEX_FORMdata(&$valueArrayToRemoveFrom,$deleteCMDS)    {
@@ -1658,7 +1712,7 @@ class t3lib_TCEmain       {
         * TODO: Like _DELETE_FLEX_FORMdata, this is only a temporary solution!
         *
         * @param       array           &$valueArrayToMoveIn: by reference
-        * @param       [type]          $moveCMDS: ...   *
+        * @param       array           $moveCMDS: ...   *
         * @param       string          $direction: 'up' or 'down'
         * @return      void
         */
@@ -1691,6 +1745,79 @@ class t3lib_TCEmain      {
                }
        }
 
+       /**
+        * Evaluates 'inline' type values.
+        * (partly copied from the select_group function on this issue)
+        *
+        * @param       array           The result array. The processed value (if any!) is set in the 'value' key.
+        * @param       string          The value to set.
+        * @param       array           Field configuration from TCA
+        * @param       array           Additional parameters in a numeric array: $table,$id,$curValue,$status,$realPid,$recFID
+        * @param       string          Field name
+        * @return      array           Modified $res array
+        */
+       function checkValue_inline($res,$value,$tcaFieldConf,$PP,$field)        {
+               list($table,$id,$curValue,$status,$realPid,$recFID) = $PP;
+
+               if (!$tcaFieldConf['foreign_table'])    {
+                       return false;   // Fatal error, inline fields should always have a foreign_table defined
+               }
+
+                       // When values are sent they come as comma-separated values which are exploded by this function:
+               $valueArray = t3lib_div::trimExplode(',', $value);
+
+                       // Remove duplicates: (should not be needed)
+               $valueArray = array_unique($valueArray);
+
+                       // Example for received data:
+                       // $value = 45,NEW4555fdf59d154,12,123
+                       // We need to decide whether we use the stack or can save the relation directly.
+               if(strpos($value, 'NEW') !== false || !t3lib_div::testInt($id)) {
+                       $this->remapStack[] = array(
+                               'func' => 'checkValue_group_select_processDBdata',
+                               'args' => array($valueArray,$tcaFieldConf,$id,$status,'inline',$table),
+                               'pos' => array('valueArray' => 0, 'tcaFieldConf' => 1, 'id' => 2, 'table' => 5),
+                               'field' => $field
+                       );
+                       unset($res['value']);
+               } elseif($value || t3lib_div::testInt($id)) {
+                       $newValueArray = $this->checkValue_group_select_processDBdata($valueArray,$tcaFieldConf,$id,$status,'inline', $table);
+
+                               // Checking that the number of items is correct
+                       $newVal = $this->checkValue_checkMax($tcaFieldConf, $newValueArray);
+                       $res['value'] = implode(',',$newVal);
+               }
+
+               return $res;
+       }
+
+       /**
+        * Checks if a fields has more items than defined via TCA in maxitems.
+        * If there are more items than allowd, the item list is truncated to the defined number.
+        *
+        * @param       array           $tcaFieldConf: Field configuration from TCA
+        * @param       array           $valueArray: Current value array of items
+        * @return      array           The truncated value array of items
+        */
+       function checkValue_checkMax($tcaFieldConf, $valueArray) {
+               // BTW, checking for min and max items here does NOT make any sense when MM is used because the above function calls will just return an array with a single item (the count) if MM is used... Why didn't I perform the check before? Probably because we could not evaluate the validity of record uids etc... Hmm...
+
+               $valueArrayC = count($valueArray);
+
+                       // NOTE to the comment: It's not really possible to check for too few items, because you must then determine first, if the field is actual used regarding the CType.
+               $maxI = isset($tcaFieldConf['maxitems']) ? intval($tcaFieldConf['maxitems']):1;
+               if ($valueArrayC > $maxI)       {$valueArrayC=$maxI;}   // Checking for not too many elements
+
+                       // Dumping array to list
+               $newVal=array();
+               foreach($valueArray as $nextVal)        {
+                       if ($valueArrayC==0)    {break;}
+                       $valueArrayC--;
+                       $newVal[]=$nextVal;
+               }
+
+               return $newVal;
+       }
 
 
 
@@ -1797,11 +1924,11 @@ class t3lib_TCEmain     {
                                break;
                                case 'upper':
                                        $value = strtoupper($value);
-#                                      $value = strtr($value, 'áéúíâêûôîæøåäöü', 'ÁÉÚÍÂÊÛÔÎÆØÅÄÖÜ');   // WILL make trouble with other charsets than ISO-8859-1, so what do we do here? PHP-function which can handle this for other charsets? Currently the browsers JavaScript will fix it.
+#                                      $value = strtr($value, '', ''); // WILL make trouble with other charsets than ISO-8859-1, so what do we do here? PHP-function which can handle this for other charsets? Currently the browsers JavaScript will fix it.
                                break;
                                case 'lower':
                                        $value = strtolower($value);
-#                                      $value = strtr($value, 'ÁÉÚÍÂÊÛÔÎÆØÅÄÖÜ', 'áéúíâêûôîæøåäöü');   // WILL make trouble with other charsets than ISO-8859-1, so what do we do here? PHP-function which can handle this for other charsets? Currently the browsers JavaScript will fix it.
+#                                      $value = strtr($value, '', ''); // WILL make trouble with other charsets than ISO-8859-1, so what do we do here? PHP-function which can handle this for other charsets? Currently the browsers JavaScript will fix it.
                                break;
                                case 'required':
                                        if (!$value)    {$set=0;}
@@ -1855,7 +1982,7 @@ class t3lib_TCEmain       {
         * @param       array           TCA field config
         * @param       integer         Record id, used for look-up of MM relations (local_uid)
         * @param       string          Status string ('update' or 'new')
-        * @param       string          The type, either 'select' or 'group'
+        * @param       string          The type, either 'select', 'group' or 'inline'
         * @param       string          Table name, needs to be passed to t3lib_loadDBGroup
         * @return      array           Modified value array
         */
@@ -1873,8 +2000,19 @@ class t3lib_TCEmain      {
                        } else {
                                $this->dbAnalysisStore[] = array($dbAnalysis,$tcaFieldConf['MM'],$id,$prep);    // This will be traversed later to execute the actions
                        }
-                       $cc=count($dbAnalysis->itemArray);
-                       $valueArray = array($cc);
+                       $valueArray = $dbAnalysis->countItems();
+               } elseif ($type == 'inline') {
+                       if ($tcaFieldConf['foreign_field']) {
+                                       // update sorting
+                               $dbAnalysis->writeForeignField($tcaFieldConf, $id);
+                               $valueArray = $dbAnalysis->countItems();
+                       } else {
+                               $valueArray = $dbAnalysis->getValueArray($prep);
+                               if ($prep) {
+                                               // @TODO: Do we want to support relations to multiple tables in Comma Separated Lists?
+                                       $valueArray = $dbAnalysis->convertPosNeg($valueArray,$tcaFieldConf['foreign_table'],$tcaFieldConf['neg_foreign_table']);
+                               }
+                       }
                } else {
                        $valueArray = $dbAnalysis->getValueArray($prep);
                        if ($type=='select' && $prep)   {
@@ -1882,7 +2020,7 @@ class t3lib_TCEmain       {
                        }
                }
 
-                       // Here we should se if 1) the records exist anymore, 2) which are new and check if the BE_USER has read-access to the new ones.
+                       // Here we should see if 1) the records exist anymore, 2) which are new and check if the BE_USER has read-access to the new ones.
                return $valueArray;
        }
 
@@ -2595,10 +2733,10 @@ class t3lib_TCEmain     {
 
                        // Process references and files, currently that means only the files, prepending absolute paths (so the TCEmain engine will detect the file as new and one that should be made into a copy)
                $value = $this->copyRecord_procFilesRefs($conf, $uid, $value);
+               $inlineSubType = $this->getInlineFieldType($conf);
 
-
-                       // Register if there are references to take care of (no change to value):
-               if ($this->isReferenceField($conf))     {
+                       // Register if there are references to take care of or MM is used on an inline field (no change to value):
+               if ($this->isReferenceField($conf) || $inlineSubType == 'mm')   {
                        $allowedTables = $conf['type']=='group' ? $conf['allowed'] : $conf['foreign_table'].','.$conf['neg_foreign_table'];
                        $prependName = $conf['type']=='group' ? $conf['prepend_tname'] : $conf['neg_foreign_table'];
                        if ($conf['MM'])        {
@@ -2609,6 +2747,21 @@ class t3lib_TCEmain      {
                        if ($value)     {       // Setting the value in this array will notify the remapListedDBRecords() function that this field MAY need references to be corrected
                                $this->registerDBList[$table][$uid][$field] = $value;
                        }
+
+                       // if another inline subtype is used (foreign_field, mm with attributes or simply item list)
+               } elseif ($inlineSubType !== false) {
+                       $dbAnalysis = t3lib_div::makeInstance('t3lib_loadDBGroup');
+                       $dbAnalysis->start($value, $conf['foreign_table'], $conf['MM'], $uid, $table, $conf);
+
+                               // walk through the items, copy them and remember the new id
+                       foreach ($dbAnalysis->itemArray as $k => $v) {
+                               $newId = $this->copyRecord($v['table'], $v['id'], -$v['id']);
+                               $dbAnalysis->itemArray[$k]['id'] = $newId;
+                       }
+
+                               // store the new values, we will set up the uids for the subtype later on
+                       $value = implode(',',$dbAnalysis->getValueArray());
+                       $this->registerDBList[$table][$uid][$field] = $value;
                }
 
                        // For "flex" fieldtypes we need to traverse the structure for two reasons: If there are file references they have to be prepended with absolute paths and if there are database reference they MIGHT need to be remapped (still done in remapListedDBRecords())
@@ -2833,6 +2986,9 @@ class t3lib_TCEmain       {
                                                                        $sortNumber = $this->getSortNumber($table,$uid,$destPid);
                                                                        $updateFields[$sortRow] = $sortNumber;
                                                                }
+
+                                                                       // check for child records that have also to be moved
+                                                               $this->moveRecord_procFields($table,$uid,$destPid);
                                                                        // Create query for update:
                                                                $GLOBALS['TYPO3_DB']->exec_UPDATEquery($table, 'uid='.intval($uid), $updateFields);
 
@@ -2875,6 +3031,10 @@ class t3lib_TCEmain      {
                                                                                        // We now update the pid and sortnumber
                                                                                $updateFields['pid'] = $destPid;
                                                                                $updateFields[$sortRow] = $sortInfo['sortNumber'];
+
+                                                                                       // check for child records that have also to be moved
+                                                                               $this->moveRecord_procFields($table,$uid,$destPid);
+                                                                                       // Create query for update:
                                                                                $GLOBALS['TYPO3_DB']->exec_UPDATEquery($table, 'uid='.intval($uid), $updateFields);
 
                                                                                        // Call post processing hooks:
@@ -2923,6 +3083,60 @@ class t3lib_TCEmain      {
        }
 
        /**
+        * Walk through all fields of the moved record and look for children of e.g. the inline type.
+        * If child records are found, they are also move to the new $destPid.
+        *
+        * @param       string          $table: Record Table
+        * @param       string          $uid: Record UID
+        * @param       string          $destPid: Position to move to
+        * @return      [type]          ...
+        */
+       function moveRecord_procFields($table,$uid,$destPid) {
+               t3lib_div::loadTCA($table);
+               $conf = $GLOBALS['TCA'][$table]['columns'];
+               $row = t3lib_BEfunc::getRecordWSOL($table,$uid);
+               foreach ($row as $field => $value) {
+                       $this->moveRecord_procBasedOnFieldType($table,$uid,$destPid,$field,$value,$conf[$field]['config']);
+               }
+       }
+
+       /**
+        * Move child records depending on the field type of the parent record.
+        *
+        * @param       string          $table: Record Table
+        * @param       string          $uid: Record UID
+        * @param       string          $destPid: Position to move to
+        * @param       string          $field: Record field
+        * @param       string          $value: Record field value
+        * @param       array           $conf: TCA configuration on current field
+        * @return      [type]          ...
+        */
+       function moveRecord_procBasedOnFieldType($table,$uid,$destPid,$field,$value,$conf) {
+               $moveTable = '';
+               $moveIds = array();
+
+               if ($conf['type'] == 'inline')  {
+                       $foreign_table = $conf['foreign_table'];
+
+                       if ($foreign_table) {
+                               $inlineType = $this->getInlineFieldType($conf);
+                               if ($inlineType == 'list' || $inlineType == 'field') {
+                                       $moveTable = $foreign_table;
+                                       $dbAnalysis = t3lib_div::makeInstance('t3lib_loadDBGroup');
+                                       $dbAnalysis->start($value, $conf['foreign_table'], '', $uid, $table, $conf);
+                               }
+                       }
+               }
+
+                       // move the records
+               if (isset($dbAnalysis)) {
+                       foreach ($dbAnalysis->itemArray as $v) {
+                               $this->moveRecord($v['table'],$v['id'],$destPid);
+                       }
+               }
+       }
+
+       /**
         * Localizes a record to another system language
         *
         * @param       string          Table name
@@ -3144,6 +3358,8 @@ class t3lib_TCEmain       {
                                                $updateFields[$TCA[$table]['ctrl']['sortby']] = 1000000000;
                                        }
 
+                                               // before (un-)deleting this record, check for child records or references
+                                       $this->deleteRecord_procFields($table, $uid, $undeleteRecord);
                                        $GLOBALS['TYPO3_DB']->exec_UPDATEquery($table, 'uid='.intval($uid), $updateFields);
                                } else {
 
@@ -3205,6 +3421,15 @@ class t3lib_TCEmain      {
                                        // Update reference index:
                                $this->updateRefIndex($table,$uid);
 
+                                       // if there are entries in the updateRefIndexStack
+                               if (is_array($this->updateRefIndexStack[$table]) && is_array($this->updateRefIndexStack[$table][$uid])) {
+                                       while ($args = array_pop($this->updateRefIndexStack[$table][$uid])) {
+                                                       // $args[0]: table, $args[1]: uid
+                                               $this->updateRefIndex($args[0], $args[1]);
+                                       }
+                                       unset($this->updateRefIndexStack[$table][$uid]);
+                               }
+
                        } else $this->log($table,$uid,3,0,1,'Attempt to delete record without delete-permissions');
                }
        }
@@ -3328,9 +3553,74 @@ class t3lib_TCEmain      {
                }
        }
 
+       /**
+        * Beford a record is deleted, check if it has references such as inline type or MM references.
+        * If so, set these child records also to be deleted.
+        *
+        * @param       string          $table: Record Table
+        * @param       string          $uid: Record UID
+        * @param       boolean         $undeleteRecord: If a record should be undeleted (e.g. from history/undo)
+        * @return      void
+        * @see         deleteRecord()
+        */
+       function deleteRecord_procFields($table, $uid, $undeleteRecord = false) {
+               t3lib_div::loadTCA($table);
+               $conf = $GLOBALS['TCA'][$table]['columns'];
+               $row = t3lib_BEfunc::getRecord($table, $uid, '*', '', false);
+
+               foreach ($row as $field => $value) {
+                       $this->deleteRecord_procBasedOnFieldType($table, $uid, $field, $value, $conf[$field]['config'], $undeleteRecord);
+               }
+       }
+
+       /**
+        * Process fields of a record to be deleted and search for special handling, like
+        * inline type, MM records, etc.
+        *
+        * @param       string          $table: Record Table
+        * @param       string          $uid: Record UID
+        * @param       string          $field: Record field
+        * @param       string          $value: Record field value
+        * @param       array           $conf: TCA configuration on current field
+        * @param       boolean         $undeleteRecord: If a record should be undeleted (e.g. from history/undo)
+        * @return      void
+        * @see         deleteRecord()
+        */
+       function deleteRecord_procBasedOnFieldType($table, $uid, $field, $value, $conf, $undeleteRecord = false) {
+               if ($conf['type'] == 'inline')  {
+                       $foreign_table = $conf['foreign_table'];
+
+                       if ($foreign_table) {
+                               $inlineType = $this->getInlineFieldType($conf);
+                               if ($inlineType == 'list' || $inlineType == 'field') {
+                                       $dbAnalysis = t3lib_div::makeInstance('t3lib_loadDBGroup');
+                                       $dbAnalysis->start($value, $conf['foreign_table'], '', $uid, $table, $conf);
+                                       $dbAnalysis->undeleteRecord = true;
+
+                                               // walk through the items and remove them
+                                       foreach ($dbAnalysis->itemArray as $v) {
+                                               if (!$undeleteRecord)   {
+                                                       $this->deleteAction($v['table'], $v['id']);
+                                               } else {
+                                                       $this->undeleteRecord($v['table'], $v['id']);
+                                               }
+                                       }
+                               }
+                       }
 
+                       // no delete action but calls to updateRefIndex *AFTER* this record was deleted
+               } elseif ($this->isReferenceField($conf)) {
+                       $allowedTables = $conf['type']=='group' ? $conf['allowed'] : $conf['foreign_table'].','.$conf['neg_foreign_table'];
+                       $prependName = $conf['type']=='group' ? $conf['prepend_tname'] : $conf['neg_foreign_table'];
 
+                       $dbAnalysis = t3lib_div::makeInstance('t3lib_loadDBGroup');
+                       $dbAnalysis->start($value, $allowedTables, $conf['MM'], $uid, $table, $conf);
 
+                       foreach ($dbAnalysis->itemArray as $v) {
+                               $this->updateRefIndexStack[$table][$uid][] = array($v['table'], $v['id']);
+                       }
+               }
+       }
 
 
 
@@ -3799,6 +4089,9 @@ $this->log($table,$id,6,0,0,'Stage raised...',30,array('comment'=>$comment,'stag
                                                                        }
                                                                }
                                                        break;
+                                                       case 'inline':
+                                                               $this->remapListedDBRecords_procInline($conf, $value, $uid, $table);
+                                                       break;
                                                        default:
                                                                debug('Field type should not appear here: '. $conf['type']);
                                                        break;
@@ -3888,7 +4181,31 @@ $this->log($table,$id,6,0,0,'Stage raised...',30,array('comment'=>$comment,'stag
                }
        }
 
+       /**
+        * Performs remapping of old UID values to NEW uid values for a inline field.
+        *
+        * @param       array           $conf: TCA field config
+        * @param       string          $value: Field value
+        * @param       integer         $uid: The uid of the ORIGINAL record
+        * @param       string          $table: Table name
+        * @return      string          The value to be updated on the table field in the database
+        */
+       function remapListedDBRecords_procInline($conf, $value, $uid, $table) {
+               $theUidToUpdate = $this->copyMappingArray_merged[$table][$uid];
+
+               if ($conf['foreign_table']) {
+                       $inlineType = $this->getInlineFieldType($conf);
+
+                       if ($inlineType == 'field') {
+                               $dbAnalysis = t3lib_div::makeInstance('t3lib_loadDBGroup');
+                               $dbAnalysis->start($value, $conf['foreign_table'], $conf['MM'], 0, $table, $conf);
 
+                               $dbAnalysis->writeForeignField($conf, $uid, $theUidToUpdate);
+                       } elseif ($inlineType == 'mm') {
+                               $vArray = $this->remapListedDBRecords_procDBRefs($conf, $value, $theUidToUpdate, $table);
+                       }
+               }
+       }
 
 
 
@@ -5190,6 +5507,25 @@ $this->log($table,$id,6,0,0,'Stage raised...',30,array('comment'=>$comment,'stag
        }
 
        /**
+        * Returns the subtype as a string of an inline field.
+        * If it's not a inline field at all, it returns false.
+        *
+        * @param       array           config array for TCA/columns field
+        * @return      mixed           string: inline subtype (field|mm|list), boolean: false
+        */
+       function getInlineFieldType($conf) {
+               if ($conf['type'] == 'inline' && $conf['foreign_table']) {
+                       if ($conf['foreign_field'])
+                               return 'field';         // the reference to the parent is stored in a pointer field in the child record
+                       elseif ($conf['MM'])
+                               return 'mm';            // regular MM intermediate table is used to store data
+                       else
+                               return 'list';          // an item list (separated by comma) is stored (like select type is doing)
+               }
+               return false;
+       }
+
+       /**
         * Get modified header for a copied record
         *
         * @param       string          Table name
index b168244..b86e957 100755 (executable)
  *
  *
  *
- *   98: class t3lib_transferData
+ *   99: class t3lib_transferData
  *
  *              SECTION: Getting record content, ready for display in TCEforms
- *  137:     function fetchRecord($table,$idList,$operation)
- *  224:     function renderRecord($table, $id, $pid, $row)
- *  268:     function renderRecordRaw($table, $id, $pid, $row, $TSconfig='', $tscPID=0)
- *  326:     function renderRecord_SW($data,$fieldConfig,$TSconfig,$table,$row,$field)
- *  356:     function renderRecord_groupProc($data,$fieldConfig,$TSconfig,$table,$row,$field)
- *  407:     function renderRecord_selectProc($data,$fieldConfig,$TSconfig,$table,$row,$field)
- *  470:     function renderRecord_flexProc($data,$fieldConfig,$TSconfig,$table,$row,$field)
- *  499:     function renderRecord_typesProc($totalRecordContent,$types_fieldConfig,$tscPID,$table,$pid)
+ *  138:     function fetchRecord($table,$idList,$operation)
+ *  225:     function renderRecord($table, $id, $pid, $row)
+ *  269:     function renderRecordRaw($table, $id, $pid, $row, $TSconfig='', $tscPID=0)
+ *  327:     function renderRecord_SW($data,$fieldConfig,$TSconfig,$table,$row,$field)
+ *  359:     function renderRecord_groupProc($data,$fieldConfig,$TSconfig,$table,$row,$field)
+ *  410:     function renderRecord_selectProc($data,$fieldConfig,$TSconfig,$table,$row,$field)
+ *  473:     function renderRecord_flexProc($data,$fieldConfig,$TSconfig,$table,$row,$field)
+ *  504:     function renderRecord_typesProc($totalRecordContent,$types_fieldConfig,$tscPID,$table,$pid)
+ *  545:     function renderRecord_inlineProc($data,$fieldConfig,$TSconfig,$table,$row,$field)
  *
  *              SECTION: FlexForm processing functions
- *  555:     function renderRecord_flexProc_procInData($dataPart,$dataStructArray,$pParams)
- *  584:     function renderRecord_flexProc_procInData_travDS(&$dataValues,$DSelements,$pParams)
+ *  632:     function renderRecord_flexProc_procInData($dataPart,$dataStructArray,$pParams)
+ *  661:     function renderRecord_flexProc_procInData_travDS(&$dataValues,$DSelements,$pParams)
  *
  *              SECTION: Selector box processing functions
- *  661:     function selectAddSpecial($dataAcc, $elements, $specialKey)
- *  785:     function selectAddForeign($dataAcc, $elements, $fieldConfig, $field, $TSconfig, $row)
- *  838:     function getDataIdList($elements, $fieldConfig, $row)
- *  861:     function procesItemArray($selItems,$config,$fieldTSConfig,$table,$row,$field)
- *  876:     function addItems($items,$iArray)
- *  898:     function procItems($items,$itemsProcFuncTSconfig,$config,$table,$row,$field)
+ *  738:     function selectAddSpecial($dataAcc, $elements, $specialKey)
+ *  863:     function selectAddForeign($dataAcc, $elements, $fieldConfig, $field, $TSconfig, $row, $table)
+ *  917:     function getDataIdList($elements, $fieldConfig, $row, $table)
+ *  946:     function procesItemArray($selItems,$config,$fieldTSConfig,$table,$row,$field)
+ *  961:     function addItems($items,$iArray)
+ *  983:     function procItems($items,$itemsProcFuncTSconfig,$config,$table,$row,$field)
  *
  *              SECTION: Helper functions
- *  933:     function lockRecord($table, $id, $pid=0)
- *  950:     function regItem($table, $id, $field, $content)
- *  960:     function sL($in)
+ * 1018:     function lockRecord($table, $id, $pid=0)
+ * 1035:     function regItem($table, $id, $field, $content)
+ * 1045:     function sL($in)
  *
- * TOTAL FUNCTIONS: 19
+ * TOTAL FUNCTIONS: 20
  * (This index is automatically created/updated by the extension "extdeveval")
  *
  */
@@ -324,7 +325,6 @@ class t3lib_transferData {
         * @return      string          Modified $value
         */
        function renderRecord_SW($data,$fieldConfig,$TSconfig,$table,$row,$field)       {
-
                switch((string)$fieldConfig['config']['type'])  {
                        case 'group':
                                $data = $this->renderRecord_groupProc($data,$fieldConfig,$TSconfig,$table,$row,$field);
@@ -335,6 +335,9 @@ class t3lib_transferData {
                        case 'flex':
                                $data = $this->renderRecord_flexProc($data,$fieldConfig,$TSconfig,$table,$row,$field);
                        break;
+                       case 'inline':
+                               $data = $this->renderRecord_inlineProc($data,$fieldConfig,$TSconfig,$table,$row,$field);
+                       break;
                }
 
                return $data;
@@ -525,7 +528,31 @@ class t3lib_transferData {
                return $totalRecordContent;
        }
 
+       /**
+        * Processing of the data value in case the field type is "inline"
+        * In some parts nearly the same as type "select"
+        *
+        * @param       string          The field value
+        * @param       array           TCA field config
+        * @param       array           TCEform TSconfig for the record
+        * @param       string          Table name
+        * @param       array           The row
+        * @param       string          Field name
+        * @return      string          The processed input field value ($data)
+        * @access private
+        * @see renderRecord()
+        */
+       function renderRecord_inlineProc($data,$fieldConfig,$TSconfig,$table,$row,$field)       {
+               global $TCA;
 
+                       // Initialize:
+               $elements = t3lib_div::trimExplode(',',$data);  // Current data set.
+               $dataAcc=array();       // New data set, ready for interface (list of values, rawurlencoded)
+       
+               $dataAcc = $this->selectAddForeign($dataAcc, $elements, $fieldConfig, $field, $TSconfig, $row, $table);
+               
+               return implode(',',$dataAcc);
+       }
 
 
 
@@ -814,7 +841,7 @@ class t3lib_transferData {
                foreach($dataIds as $theId)     {
                        if (isset($recordList[$theId])) {
                                $lPrefix = $this->sL($fieldConfig['config'][($theId>0?'':'neg_').'foreign_table_prefix']);
-                               if ($fieldConfig['config']['MM'])       {
+                               if ($fieldConfig['config']['MM'] || $fieldConfig['config']['foreign_field'])    {
                                        $dataAcc[]=rawurlencode($theId).'|'.rawurlencode(t3lib_div::fixed_lgd_cs($lPrefix.strip_tags($recordList[$theId]),$GLOBALS['BE_USER']->uc['titleLen']));
                                } else {
                                        foreach($elements as $eKey => $value)   {
diff --git a/t3lib/jsfunc.inline.js b/t3lib/jsfunc.inline.js
new file mode 100644 (file)
index 0000000..2c8bcb4
--- /dev/null
@@ -0,0 +1,588 @@
+/*<![CDATA[*/
+
+/***************************************************************
+*  Inline-Relational-Record Editing
+*
+*
+*  Copyright notice
+*
+*  (c) 2006 Oliver Hader <oh@inpublica.de>
+*  All rights reserved
+*
+*  This script is part of the TYPO3 project. The TYPO3 project is
+*  free software; you can redistribute it and/or modify
+*  it under the terms of the GNU General Public License as published by
+*  the Free Software Foundation; either version 2 of the License, or
+*  (at your option) any later version.
+*
+*  The GNU General Public License can be found at
+*  http://www.gnu.org/copyleft/gpl.html.
+*
+*  This script is distributed in the hope that it will be useful,
+*  but WITHOUT ANY WARRANTY; without even the implied warranty of
+*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*  GNU General Public License for more details.
+*
+*  This copyright notice MUST APPEAR in all copies of the script!
+***************************************************************/
+
+var inline = {
+       prependFormFieldNames: 'data',
+       noTitleString: '[No title]',
+       data: [],
+       
+       addToDataArray: function(object) {
+               for (var i in object) {
+                       this.data[i] = $H(this.data[i]).merge(object[i]);
+               }
+       },
+
+       setPrependFormFieldNames: function(value) {
+               this.prependFormFieldNames = value;
+       },
+       
+       setNoTitleString: function(value) {
+               this.noTitleString = value;
+       },
+       
+       expandCollapseRecord: function(objectId, expandSingle) {
+                       // if only a single record should be visibly for that set of records
+                       // and the record clicked itself is no visible, collapse all others
+               if (expandSingle && !Element.visible(objectId+'_fields')) this.collapseAllRecords(objectId);
+               Element.toggle(objectId+'_fields');
+               return false;
+       },
+       
+       collapseAllRecords: function(objectId) {
+                       // get the form field, where all records are stored
+               var objectName = this.prependFormFieldNames+this.parseFormElementName('parts', objectId, 3, 2);
+               var formObj = document.getElementsByName(objectName);
+
+               if (formObj.length) {
+                               // the uid of the calling object (last part in objectId)
+                       var callingUid = this.parseFormElementName('none', objectId, 1);
+                       var objectPrefix = this.parseFormElementName('full', objectId, 0 , 1);
+
+                       var records = formObj[0].value.split(',');
+                       for (var i=0; i<records.length; i++) {
+                               if (records[i] != callingUid) Element.hide(objectPrefix+'['+records[i]+']_fields');
+                       }
+               }
+       },
+       
+       createNewRecord: function(objectId,prevRecordUid) {
+               if (this.isBelowMax(objectId)) this.makeAjaxCall('createNewRecord', objectId+(prevRecordUid ? '['+prevRecordUid+']' : ''));
+               else alert('There are no more relations possible at this moment!');
+               return false;
+       },
+
+       makeAjaxCall: function() {
+               if (arguments.length > 1) {
+                       var params = '';
+                       for (var i=0; i<arguments.length; i++) params += '&ajax['+i+']='+arguments[i];
+
+                       var url = 'alt_doc_ajax.php';
+                       var options = {
+                               method:         'post',
+                               parameters:     params,
+                               onSuccess:      inline.processAjaxResponse,
+                               onFailure:      inline.showAjaxFailure
+                       };
+                       
+                       new Ajax.Request(url, options);
+               }
+       },
+       
+       processAjaxResponse: function(xhr) {
+               var json = eval('('+xhr.responseText+')');
+               for (var i in json.scriptCall) eval(json.scriptCall[i]);
+       },
+
+       showAjaxFailure: function(xhr) {
+               alert('Error: '+xhr.status+"\n"+xhr.statusText);
+       },
+               
+       importNewRecord: function(objectId) {
+               var selector = $(objectId+'_selector');
+               if (selector.selectedIndex != -1) {
+                       var selectedValue = selector.options[selector.selectedIndex].value;
+                       this.makeAjaxCall('createNewRecord', objectId, selectedValue);
+               }
+               return false;
+       },
+       
+               // this function is applied to a newly inserted record by AJAX
+               // it removes the used select items, that should be unique
+       setUnique: function(objectId, recordUid, selectedValue) {
+               if (this.data.unique && this.data.unique[objectId]) {
+                       var unique = this.data.unique[objectId];
+
+                               // remove used items from each select-field of the child records
+                       if (!(unique.selector && unique.max == -1)) {
+                               var elName = this.parseFormElementName('full', objectId, 1)+'['+recordUid+']['+unique.field+']';
+                               var formName = this.prependFormFieldNames+this.parseFormElementName('parts', objectId, 3, 1);
+
+                               var fieldObj = document.getElementsByName(elName);
+                               var values = $H(unique.used).values();
+                               
+                               if (fieldObj.length) {
+                                               // remove all before used items from the new select-item
+                                       for (var i=0; i<values.length; i++) this.removeSelectOption(fieldObj[0], values[i]);
+                                               // set the selected item automatically to the first of the remaining items
+                                       selectedValue = fieldObj[0].options[0].value;
+                                       fieldObj[0].options[0].selected = true;
+                                       this.updateUnique(fieldObj[0], objectId, formName, recordUid);
+                                       this.handleChangedField(fieldObj[0], objectId+'['+recordUid+']');
+                                       if (typeof this.data.unique[objectId]['used'].length != 'undefined')
+                                               this.data.unique[objectId]['used'] = {};
+                                       this.data.unique[objectId]['used'][recordUid] = selectedValue;
+                               }
+                       }
+                       
+                               // remove used items from a selector-box
+                       if (unique.selector && selectedValue) {
+                               var selector = $(objectId+'_selector');
+                               this.removeSelectOption(selector, selectedValue);
+                               this.data.unique[objectId]['used'][recordUid] = selectedValue;
+                       }
+               }
+       },
+       
+       domAddNewRecord: function(method, insertObject, objectPrefix, htmlData) {
+               if (this.isBelowMax(objectPrefix)) {
+                       if (method == 'bottom')
+                               new Insertion.Bottom(insertObject, htmlData);
+                       else if (method == 'after')
+                               new Insertion.After(insertObject, htmlData);
+               }
+       },
+       
+       changeSorting: function(objectId, direction) {
+               var objectName = this.prependFormFieldNames+this.parseFormElementName('parts', objectId, 3, 2);
+               var objectPrefix = this.parseFormElementName('full', objectId, 0, 1);
+               var formObj = document.getElementsByName(objectName);
+               
+               if (formObj.length) {
+                               // the uid of the calling object (last part in objectId)
+                       var callingUid = this.parseFormElementName('none', objectId, 1);
+                       var records = formObj[0].value.split(',');
+                       var current = records.indexOf(callingUid);
+                       var changed = false;
+                       
+                               // move up
+                       if (direction > 0 && current > 0) {
+                               records[current] = records[current-1];
+                               records[current-1] = callingUid;
+                               changed = true;
+                               
+                               // move down
+                       } else if (direction < 0 && current < records.length-1) {
+                               records[current] = records[current+1];
+                               records[current+1] = callingUid;
+                               changed = true;
+                       }
+                       
+                       if (changed) {
+                               formObj[0].value = records.join(',');
+                               var cAdj = direction > 0 ? 1 : 0; // adjustment
+                               $(objectId+'_div').parentNode.insertBefore(
+                                       $(objectPrefix+'['+records[current-cAdj]+']_div'),
+                                       $(objectPrefix+'['+records[current+1-cAdj]+']_div')
+                               );
+                               this.redrawSortingButtons(objectPrefix, records);
+                       }
+               }
+               
+               return false;
+       },
+       
+       dragAndDropSorting: function(element) {
+               var objectId = element.getAttribute('id').replace(/_records$/, '');
+               var objectName = inline.prependFormFieldNames+inline.parseFormElementName('parts', objectId, 3);
+               var formObj = document.getElementsByName(objectName);
+
+               if (formObj.length) {
+                       var checked = new Array();
+                       var order = Sortable.sequence(element);
+                       var records = formObj[0].value.split(',');
+                       
+                               // check if ordered uid is really part of the records
+                               // virtually deleted items might still be there but ordering shouldn't saved at all on them
+                       for (var i=0; i<order.length; i++) {
+                               if (records.indexOf(order[i]) != -1) {
+                                       checked.push(order[i]);
+                               }
+                       }
+
+                       formObj[0].value = checked.join(',');
+
+                       if (inline.data.config && inline.data.config[objectId]) {
+                               var table = inline.data.config[objectId].table;
+                               inline.redrawSortingButtons(objectId+'['+table+']', checked);
+                       }
+               }
+       },
+       
+       createDragAndDropSorting: function(objectId) {
+               Sortable.create(
+                       objectId,
+                       {
+                               format: /^[^_\-](?:[A-Za-z0-9\[\]\-\_]*)\[(.*)\]_div$/,
+                               onUpdate: inline.dragAndDropSorting,
+                               tag: 'div',
+                               handle: 'sortableHandle',
+                               overlap: 'vertical',
+                               constraint: 'vertical'
+                       }
+               );
+       },
+       
+       destroyDragAndDropSorting: function(objectId) {
+               Sortable.destroy(objectId);
+       },
+       
+       redrawSortingButtons: function(objectPrefix, records) {
+               var i;
+               var headerObj;
+               var sortingObj = new Array();
+               
+                       // if no records were passed, fetch them from form field
+               if (typeof records == 'undefined') {
+                       records = new Array();
+                       var objectName = this.prependFormFieldNames+this.parseFormElementName('parts', objectPrefix, 3, 1);
+                       var formObj = document.getElementsByName(objectName);
+                       if (formObj.length) records = formObj[0].value.split(',');
+               }
+               
+               for (i=0; i<records.length; i++) {
+                       if (!records[i].length) continue;
+                       
+                       headerObj = $(objectPrefix+'['+records[i]+']_header');
+                       sortingObj[0] = headerObj.getElementsByClassName('sortingUp');
+                       sortingObj[1] = headerObj.getElementsByClassName('sortingDown');
+                       
+                       if (sortingObj[0].length)
+                               sortingObj[0][0].style.visibility = i == 0 ? 'hidden' : 'visible';
+                       if (sortingObj[1].length)
+                               sortingObj[1][0].style.visibility = i == records.length-1 ? 'hidden' : 'visible';
+               }
+       },
+       
+       memorizeAddRecord: function(objectPrefix, newUid, afterUid, selectedValue) {
+               if (this.isBelowMax(objectPrefix)) {
+                       var objectName = this.prependFormFieldNames+this.parseFormElementName('parts', objectPrefix, 3, 1);
+                       var formObj = document.getElementsByName(objectName);
+       
+                       if (formObj.length) {
+                               var records = new Array();
+                               if (formObj[0].value.length) records = formObj[0].value.split(',');
+                               
+                               if (afterUid) {
+                                       var newRecords = new Array();
+                                       for (var i=0; i<records.length; i++) {
+                                               if (records[i].length) newRecords.push(records[i]);
+                                               if (afterUid == records[i]) newRecords.push(newUid);
+                                       }
+                                       records = newRecords;
+                               } else {
+                                       records.push(newUid);
+                               }
+                               formObj[0].value = records.join(',');
+                       }
+       
+                       this.redrawSortingButtons(objectPrefix, records);
+                       
+                       if (this.data.unique && this.data.unique[objectPrefix]) {
+                               var unique = this.data.unique[objectPrefix];
+                               this.setUnique(objectPrefix, newUid, selectedValue);
+                       }
+               }
+               
+                       // if we reached the maximum off possible records after this action, hide the new buttons
+               if (!this.isBelowMax(objectPrefix))
+                       this.hideElementsWithClassName('inlineNewButton',  this.parseFormElementName('full', objectPrefix, 0 , 1));
+       },
+       
+       memorizeRemoveRecord: function(objectName, removeUid) {
+               var formObj = document.getElementsByName(objectName);
+               if (formObj.length) {
+                       var parts = new Array();
+                       if (formObj[0].value.length) {
+                               parts = formObj[0].value.split(',');
+                               parts = parts.without(removeUid);
+                               formObj[0].value = parts.join(',');
+                               return parts.length;
+                       }
+               }
+               return false;
+       },
+       
+       updateUnique: function(srcElement, objectPrefix, formName, recordUid) {
+               if (this.data.unique && this.data.unique[objectPrefix]) {
+                       var unique = this.data.unique[objectPrefix];
+                       var oldValue = unique.used[recordUid];
+
+                       if (unique.selector) {
+                               var selector = $(objectPrefix+'_selector');
+                               this.removeSelectOption(selector, srcElement.value);
+                               if (typeof oldValue != 'undefined') this.readdSelectOption(selector, oldValue, unique);
+                       }
+                       
+                       if (!(unique.selector && unique.max == -1)) {
+                               var formObj = document.getElementsByName(formName);
+                               if (unique && formObj.length) {
+                                       var records = formObj[0].value.split(',');
+                                       var recordObj;
+                                       for (var i=0; i<records.length; i++) {
+                                               recordObj = document.getElementsByName(this.prependFormFieldNames+'['+unique.table+']['+records[i]+']['+unique.field+']');
+                                               if (recordObj.length && recordObj[0] != srcElement) {
+                                                       this.removeSelectOption(recordObj[0], srcElement.value);
+                                                       if (typeof oldValue != 'undefined') this.readdSelectOption(recordObj[0], oldValue, unique);
+                                               }
+                                       }
+                                       this.data.unique[objectPrefix].used[recordUid] = srcElement.value;
+                               }
+                       }
+               }
+       },
+       
+       revertUnique: function(objectPrefix, elName, recordUid) {
+               var unique = this.data.unique[objectPrefix];
+               var fieldObj = document.getElementsByName(elName+'['+unique.field+']');
+
+               if (fieldObj.length) {
+                       delete(this.data.unique[objectPrefix].used[recordUid])
+                       
+                       if (unique.selector) {
+                               if (!isNaN(fieldObj[0].value)) {
+                                       var selector = $(objectPrefix+'_selector');
+                                       this.readdSelectOption(selector, fieldObj[0].value, unique);
+                               }
+                       }
+                       
+                       if (!(unique.selector && unique.max == -1)) {
+                               var formName = this.prependFormFieldNames+this.parseFormElementName('parts', objectPrefix, 3, 1);
+                               var formObj = document.getElementsByName(formName);
+                               if (formObj.length) {
+                                       var records = formObj[0].value.split(',');
+                                       var recordObj;
+                                               // walk through all inline records on that level and get the select field
+                                       for (var i=0; i<records.length; i++) {
+                                               recordObj = document.getElementsByName(this.prependFormFieldNames+'['+unique.table+']['+records[i]+']['+unique.field+']');
+                                               if (recordObj.length) this.readdSelectOption(recordObj[0], fieldObj[0].value, unique);
+                                       }
+                               }
+                       }
+               }
+       },
+       
+       enableDisableRecord: function(objectId) {
+               var elName = this.parseFormElementName('full', objectId, 2);
+               var imageObj = $(objectId+'_disabled');
+               var valueObj = document.getElementsByName(elName+'[hidden]');
+               var formObj = document.getElementsByName(elName+'[hidden]_0');
+               var imagePath = '';
+               
+               if (valueObj && formObj) {
+                       formObj[0].click();
+                       imagePath = this.parsePath(imageObj.src);
+                       imageObj.src = imagePath+(valueObj[0].value > 0 ? 'button_unhide.gif' : 'button_hide.gif');
+               }
+               
+               return false;
+       },
+       
+       deleteRecord: function(objectId) {
+               var objectPrefix = this.parseFormElementName('full', objectId, 0 , 1);
+               var elName = this.parseFormElementName('full', objectId, 2);
+               var shortName = this.parseFormElementName('parts', objectId, 2);
+               var recordUid = this.parseFormElementName('none', objectId, 1);
+               var beforeDeleteIsBelowMax = this.isBelowMax(objectPrefix);
+               
+                       // revert the unique settings if available
+               if (this.data.unique && this.data.unique[objectPrefix]) this.revertUnique(objectPrefix, elName, recordUid);
+
+                       // if the record is new and was never saved before, just remove it from DOM
+               if ($(objectId+'_div') && $(objectId+'_div').hasClassName('inlineIsNewRecord')) {
+                       new Effect.Fade(objectId+'_div', { afterFinish: function() { Element.remove(objectId+'_div'); } });
+                       // if the record already exists in storage, mark it to be deleted on clicking the save button
+               } else {
+                       document.getElementsByName('cmd'+shortName+'[delete]')[0].disabled = false;
+                       new Effect.Fade(objectId+'_div');
+               }
+
+               var recordCount = this.memorizeRemoveRecord(
+                       this.prependFormFieldNames+this.parseFormElementName('parts', objectId, 3, 2),
+                       recordUid
+               );
+
+               if (recordCount <= 1) {
+                       this.destroyDragAndDropSorting(this.parseFormElementName('full', objectId, 0 , 2)+'_records');
+               }
+               this.redrawSortingButtons(objectPrefix);
+               
+                       // if the NEW-button was hidden and now we can add again new children, show the button
+               if (!beforeDeleteIsBelowMax && this.isBelowMax(objectPrefix))
+                       this.showElementsWithClassName('inlineNewButton', this.parseFormElementName('full', objectPrefix, 0 , 1));
+
+               return false;
+       },
+       
+       parsePath: function(path) {
+               var backSlash = path.lastIndexOf('\\');
+               var normalSlash = path.lastIndexOf('/');
+               
+               if (backSlash > 0)
+                       path = path.substring(0,backSlash+1);
+               else if (normalSlash > 0)
+                       path = path.substring(0,normalSlash+1);
+               else
+                       path = '';
+                       
+               return path;
+       },
+       
+       parseFormElementName: function(wrap, objectId, rightCount, skipRight) {
+                       // remove left and right side "data[...|...]" -> '...|...'
+               objectId = objectId.substr(0, objectId.lastIndexOf(']')).substr(objectId.indexOf('[')+1);
+               
+               if (!wrap) wrap = 'full';
+               if (!skipRight) skipRight = 0;
+               
+               var elReturn;
+               var elParts = new Array();
+               var idParts = objectId.split('][');
+               for (var i=0; i<skipRight; i++) idParts.pop();
+
+               if (rightCount > 0) {
+                       for (var i=0; i<rightCount; i++) elParts.unshift(idParts.pop());
+               } else {
+                       for (var i=0; i<-rightCount; i++) idParts.shift();
+                       elParts = idParts;
+               }
+               
+               if (wrap == 'full') {
+                       elReturn = this.prependFormFieldNames+'['+elParts.join('][')+']';
+               } else if (wrap == 'parts') {
+                       elReturn = '['+elParts.join('][')+']';
+               } else if (wrap == 'none') {
+                       elReturn = elParts.length > 1 ? elParts : elParts.join('');
+               }
+
+               return elReturn;
+       },
+       
+       handleChangedField: function(formField, objectId) {
+               var formObj;
+               if (typeof formField == 'object') {
+                       formObj = formField;
+               } else {
+                       formObj = document.getElementsByName(formField);
+                       if (formObj.length) formObj = formObj[0];
+               }
+                       
+               if (formObj != undefined) {
+                       var value;
+                       if (formObj.nodeName == 'SELECT') value = formObj.options[formObj.selectedIndex].text;
+                       else value = formObj.value;
+                       $(objectId+'_label').innerHTML = value != undefined ? value : this.noTitleString;
+               }
+               return true;
+       },
+       
+       arrayAssocCount: function(object) {
+               var count = 0;
+               if (typeof object.length != 'undefined') {
+                       count = object.length;
+               } else {
+                       for (var i in object) count++;
+               }
+               return count;
+       },
+       
+       isBelowMax: function(objectPrefix) {
+               var isBelowMax = true;
+               var objectName = this.prependFormFieldNames+this.parseFormElementName('parts', objectPrefix, 3, 1);
+               var formObj = document.getElementsByName(objectName);
+
+               if (this.data.config && this.data.config[objectPrefix] && formObj.length) {
+                       var recordCount = formObj[0].value.split(',').length;
+                       if (recordCount >= this.data.config[objectPrefix].max) isBelowMax = false;
+               }
+               if (isBelowMax && this.data.unique && this.data.unique[objectPrefix]) {
+                       var unique = this.data.unique[objectPrefix];
+                       if (this.arrayAssocCount(unique.used) >= unique.max && unique.max >= 0) isBelowMax = false;
+               }
+               return isBelowMax;
+       },
+       
+       getOptionsHash: function(selectObj) {
+               var optionsHash = {};
+               for (var i=0; i<selectObj.options.length; i++) optionsHash[selectObj.options[i].value] = i;
+               return optionsHash;
+       },
+       
+       removeSelectOption: function(selectObj, value) {
+               var optionsHash = this.getOptionsHash(selectObj);
+               if (optionsHash[value] != undefined) selectObj.options[optionsHash[value]] = null;
+       },
+       
+       readdSelectOption: function(selectObj, value, unique) {
+               var index = null;
+               var optionsHash = this.getOptionsHash(selectObj);
+               var possibleValues = $H(unique.possible).keys();
+
+               for (var possibleValue in unique.possible) {
+                       if (possibleValue == value) break;
+                       if (optionsHash[possibleValue] != undefined) index = optionsHash[possibleValue];
+               }
+               
+               if (index == null) index = 0;
+               else if (index < selectObj.options.length) index++;
+                       // recreate the <option> tag
+               var readdOption = document.createElement('option');
+               readdOption.text = unique.possible[value];
+               readdOption.value = value;
+                       // add the <option> at the right position
+               selectObj.add(readdOption, document.all ? index : selectObj.options[index]);
+       },
+       
+       hideElementsWithClassName: function(className, parentElement) {
+               this.setVisibilityOfElementsWithClassName('hide', className, parentElement);
+       },
+       
+       showElementsWithClassName: function(className, parentElement) {
+               this.setVisibilityOfElementsWithClassName('show', className, parentElement);
+       },
+       
+       setVisibilityOfElementsWithClassName: function(action, className, parentElement) {
+               var domObjects = document.getElementsByClassName(className, parentElement);
+               for (var i=0; i<domObjects.length; i++) {
+                       if (action == 'hide')
+                               new Effect.Fade(domObjects[i]);
+                       else if (action = 'show')
+                               new Effect.Appear(domObjects[i]);
+               }
+       },
+       
+       fadeOutFadeIn: function(objectId) {
+               var optIn = { duration:0.5, transition:Effect.Transitions.linear, from:0.50, to:1.00 };
+               var optOut = { duration:0.5, transition:Effect.Transitions.linear, from:1.00, to:0.50 };
+               optOut.afterFinish = function() { new Effect.Opacity(objectId, optIn); };
+               new Effect.Opacity(objectId, optOut);
+       }
+}
+
+Object.extend(Array.prototype, {
+       diff: function(current) {
+               var diff = new Array();
+               if (this.length == current.length) {
+                       for (var i=0; i<this.length; i++) {
+                               if (this[i] !== current[i]) diff.push(i);
+                       }
+               }
+               return diff;
+       }
+});
+
+/*]]>*/
\ No newline at end of file
index 7b82410..eb58d05 100755 (executable)
@@ -253,6 +253,7 @@ class SC_alt_doc {
 
                        // GPvars specifically for processing:
                $this->data = t3lib_div::_GP('data');
+               $this->cmd = t3lib_div::_GP('cmd');
                $this->mirror = t3lib_div::_GP('mirror');
                $this->cacheCmd = t3lib_div::_GP('cacheCmd');
                $this->redirect = t3lib_div::_GP('redirect');
@@ -277,7 +278,7 @@ class SC_alt_doc {
                $tce->disableRTE = $this->disableRTE;
 
                        // Loading TCEmain with data:
-               $tce->start($this->data,array());
+               $tce->start($this->data,$this->cmd);
                if (is_array($this->mirror))    {       $tce->setMirror($this->mirror); }
 
                        // If pages are being edited, we set an instruction about updating the page tree after this operation.
@@ -297,27 +298,35 @@ class SC_alt_doc {
                                // Perform the saving operation with TCEmain:
                        $tce->process_uploads($_FILES);
                        $tce->process_datamap();
+                       $tce->process_cmdmap();
 
                                // If there was saved any new items, load them:
                        if (count($tce->substNEWwithIDs_table)) {
 
-                                       // Resetting editconf:
-                               $this->editconf = array();
-
-                                       // Traverse all new records and forge the content of ->editconf so we can continue to EDIT these records!
-                               foreach($tce->substNEWwithIDs_table as $nKey => $nTable)        {
-                                       $editId = $tce->substNEWwithIDs[$nKey];
-                                               // translate new id to the workspace version:
-                                       if ($versionRec = t3lib_BEfunc::getWorkspaceVersionOfRecord($GLOBALS['BE_USER']->workspace, $nTable, $editId,'uid'))    {
-                                               $editId = $versionRec['uid'];
-                                       }
-
-                                       $this->editconf[$nTable][$editId]='edit';
-                                       if ($nTable=='pages' && $this->retUrl!='dummy.php' && $this->returnNewPageId)   {
-                                               $this->retUrl.='&id='.$tce->substNEWwithIDs[$nKey];
+                               foreach($this->editconf as $tableName => $tableCmds) {
+                                       $keys = array_keys($tce->substNEWwithIDs_table, $tableName);
+                                       if(count($keys) > 0) {
+                                               foreach($keys as $key) {
+                                                       $editId = $tce->substNEWwithIDs[$key];
+                                                               // translate new id to the workspace version:
+                                                       if ($versionRec = t3lib_BEfunc::getWorkspaceVersionOfRecord($GLOBALS['BE_USER']->workspace, $nTable, $editId,'uid'))    {
+                                                               $editId = $versionRec['uid'];
+                                                       }
+                                                       $newEditConf[$tableName][$editId] = 'edit';
+       
+                                                               // Traverse all new records and forge the content of ->editconf so we can continue to EDIT these records!
+                                                       if ($tableName=='pages' && $this->retUrl!='dummy.php' && $this->returnNewPageId)        {
+                                                               $this->retUrl.='&id='.$tce->substNEWwithIDs[$key];
+                                                       }
+                                               }
+                                       } else {
+                                               $newEditConf[$tableName] = $tableCmds;
                                        }
                                }
 
+                                       // Resetting editconf:
+                               $this->editconf = $newEditConf;
+
                                        // Finally, set the editconf array in the "getvars" so they will be passed along in URLs as needed.
                                $this->R_URL_getvars['edit']=$this->editconf;
 
@@ -479,7 +488,7 @@ class SC_alt_doc {
 
                        // Begin edit:
                if (is_array($this->editconf))  {
-
+                       
                                // Initialize TCEforms (rendering the forms)
                        $this->tceforms = t3lib_div::makeInstance('t3lib_TCEforms');
                        $this->tceforms->initDefaultBEMode();
@@ -940,7 +949,7 @@ class SC_alt_doc {
 
                $formContent.='
                                <tr>
-                                       <td colspan="2"><div id="typo3-altdoc-header-info-options">'.$pagePath.$langSelector.'<div></td>
+                                       <td colspan="2"><div id="typo3-altdoc-header-info-options">'.$pagePath.$langSelector.'</div></td>
                                </tr>
                        </table>
 
diff --git a/typo3/alt_doc_ajax.php b/typo3/alt_doc_ajax.php
new file mode 100644 (file)
index 0000000..44bb611
--- /dev/null
@@ -0,0 +1,168 @@
+<?php
+/***************************************************************
+*  Copyright notice
+*
+*  (c) 2006 Oliver Hader <oh@inpublica.de>
+*  All rights reserved
+*
+*  This script is part of the TYPO3 project. The TYPO3 project is
+*  free software; you can redistribute it and/or modify
+*  it under the terms of the GNU General Public License as published by
+*  the Free Software Foundation; either version 2 of the License, or
+*  (at your option) any later version.
+*
+*  The GNU General Public License can be found at
+*  http://www.gnu.org/copyleft/gpl.html.
+*  A copy is found in the textfile GPL.txt and important notices to the license
+*  from the author is found in LICENSE.txt distributed with these scripts.
+*
+*
+*  This script is distributed in the hope that it will be useful,
+*  but WITHOUT ANY WARRANTY; without even the implied warranty of
+*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*  GNU General Public License for more details.
+*
+*  This copyright notice MUST APPEAR in all copies of the script!
+***************************************************************/
+/**
+ * Main form rendering script for AJAX calls only.
+ *
+ * @author     Oliver Hader <oh@inpublica.de>
+ */
+/**
+ * [CLASS/FUNCTION INDEX of SCRIPT]
+ *
+ *
+ *
+ *   65: class SC_alt_doc_ajax
+ *   75:     function init()
+ *  118:     function main()
+ *  145:     function printContent()
+ *
+ * TOTAL FUNCTIONS: 3
+ * (This index is automatically created/updated by the extension "extdeveval")
+ *
+ */
+
+
+
+require('init.php');
+require('template.php');
+$LANG->includeLLFile('EXT:lang/locallang_alt_doc.xml');
+require_once (PATH_t3lib.'class.t3lib_tceforms.php');
+       // @TODO: Do we really need this here?
+require_once (PATH_t3lib.'class.t3lib_clipboard.php');
+
+require_once (PATH_t3lib.'class.t3lib_tcemain.php');
+require_once (PATH_t3lib.'class.t3lib_loaddbgroup.php');
+require_once (PATH_t3lib.'class.t3lib_transferdata.php');
+
+
+t3lib_BEfunc::lockRecords();
+
+
+
+class SC_alt_doc_ajax {
+       var $content;                           // Content accumulation
+       var $retUrl;                            // Return URL script, processed. This contains the script (if any) that we should RETURN TO from the alt_doc.php script IF we press the close button. Thus this variable is normally passed along from the calling script so we can properly return if needed.
+       var $R_URL_parts;                       // Contains the parts of the REQUEST_URI (current url). By parts we mean the result of resolving REQUEST_URI (current url) by the parse_url() function. The result is an array where eg. "path" is the script path and "query" is the parameters...
+       var $R_URL_getvars;                     // Contains the current GET vars array; More specifically this array is the foundation for creating the R_URI internal var (which becomes the "url of this script" to which we submit the forms etc.)
+       var $R_URI;                                     // Set to the URL of this script including variables which is needed to re-display the form. See main()
+       var $tceforms;                          // Contains the instance of TCEforms class.
+       var $localizationMode;          // GP var, localization mode for TCEforms (eg. "text")
+       var $ajax = array();            // the AJAX paramerts from get/post
+
+       function init() {
+               global $BE_USER;
+
+                       // get AJAX parameters
+               $this->ajax = t3lib_div::_GP('ajax');
+
+                       // MENU-ITEMS:
+                       // If array, then it's a selector box menu
+                       // If empty string it's just a variable, that'll be saved.
+                       // Values NOT in this array will not be saved in the settings-array for the module.
+                       // @TODO: showPalettes etc. should be stored on client side and submitted via each ajax call
+               $this->MOD_MENU = array(
+                       'showPalettes' => '',
+                       'showDescriptions' => '',
+                       'disableRTE' => ''
+               );
+                       // Setting virtual document name
+               $this->MCONF['name']='xMOD_alt_doc.php';
+                       // CLEANSE SETTINGS
+               $this->MOD_SETTINGS = t3lib_BEfunc::getModuleData($this->MOD_MENU, t3lib_div::_GP('SET'), $this->MCONF['name']);
+
+               $this->tceforms = t3lib_div::makeInstance('t3lib_TCEforms');
+               $this->tceforms->initDefaultBEMode();
+               $this->tceforms->palettesCollapsed = !$this->MOD_SETTINGS['showPalettes'];
+               $this->tceforms->disableRTE = $this->MOD_SETTINGS['disableRTE'];
+               $this->tceforms->enableClickMenu = TRUE;
+               $this->tceforms->enableTabMenu = TRUE;
+
+                       // Clipboard is initialized:
+               $this->tceforms->clipObj = t3lib_div::makeInstance('t3lib_clipboard');          // Start clipboard
+               $this->tceforms->clipObj->initializeClipboard();        // Initialize - reads the clipboard content from the user session
+
+                       // Setting external variables:
+               if ($BE_USER->uc['edit_showFieldHelp']!='text' && $this->MOD_SETTINGS['showDescriptions'])      $this->tceforms->edit_showFieldHelp='text';
+       }
+
+       /**
+        * The main function for the AJAX call.
+        * Checks if the requested function call is valid and forwards the request to t3lib_TCEforms_inline.
+        * The out is written to $this->content
+        *
+        * @return      void
+        */
+       function main() {
+               header('Expires: Fri, 27 Nov 1981 09:43:00 GMT');
+               header('Last-Modified: '.gmdate("D, d M Y H:i:s").' GMT');
+               header('Cache-Control: no-cache, must-revalidate');
+               header('Pragma: no-cache');
+
+               $this->content = '';
+
+               if (is_array($this->ajax) && count($this->ajax)) {
+                               // the first argument is the method that should handle the AJAX call
+                       $method = array_shift($this->ajax);
+
+                               // Security check
+                       if(!in_array($method, array('createNewRecord'))) return false;
+
+                       $this->content = call_user_func_array(
+                               array(&$this->tceforms->inline, $method),
+                               $this->ajax
+                       );
+               }
+       }
+
+       /**
+        * Performs the output of $this->content.
+        *
+        * @return      void
+        */
+       function printContent() {
+               echo $this->content;
+       }
+}
+
+// Include extension?
+if (defined('TYPO3_MODE') && $TYPO3_CONF_VARS[TYPO3_MODE]['XCLASS']['typo3/alt_doc_ajax.php']) {
+       include_once($TYPO3_CONF_VARS[TYPO3_MODE]['XCLASS']['typo3/alt_doc_ajax.php']);
+}
+
+
+
+
+
+
+// Make instance:
+$SOBE = t3lib_div::makeInstance('SC_alt_doc_ajax');
+
+// Main:
+$SOBE->init();
+$SOBE->main();
+$SOBE->printContent();
+
+?>
\ No newline at end of file
diff --git a/typo3/gfx/move.gif b/typo3/gfx/move.gif
new file mode 100644 (file)
index 0000000..32deeea
Binary files /dev/null and b/typo3/gfx/move.gif differ
diff --git a/typo3/json.php b/typo3/json.php
new file mode 100644 (file)
index 0000000..0cddbdd
--- /dev/null
@@ -0,0 +1,806 @@
+<?php
+/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
+
+/**
+ * Converts to and from JSON format.
+ *
+ * JSON (JavaScript Object Notation) is a lightweight data-interchange
+ * format. It is easy for humans to read and write. It is easy for machines
+ * to parse and generate. It is based on a subset of the JavaScript
+ * Programming Language, Standard ECMA-262 3rd Edition - December 1999.
+ * This feature can also be found in  Python. JSON is a text format that is
+ * completely language independent but uses conventions that are familiar
+ * to programmers of the C-family of languages, including C, C++, C#, Java,
+ * JavaScript, Perl, TCL, and many others. These properties make JSON an
+ * ideal data-interchange language.
+ *
+ * This package provides a simple encoder and decoder for JSON notation. It
+ * is intended for use with client-side Javascript applications that make
+ * use of HTTPRequest to perform server communication functions - data can
+ * be encoded into JSON notation for use in a client-side javascript, or
+ * decoded from incoming Javascript requests. JSON format is native to
+ * Javascript, and can be directly eval()'ed with no further parsing
+ * overhead
+ *
+ * All strings should be in ASCII or UTF-8 format!
+ *
+ * LICENSE: Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met: Redistributions of source code must retain the
+ * above copyright notice, this list of conditions and the following
+ * disclaimer. Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
+ * NO EVENT SHALL CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+ * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
+ * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+ * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ *
+ * @category
+ * @package     Services_JSON
+ * @author      Michal Migurski <mike-json@teczno.com>
+ * @author      Matt Knapp <mdknapp[at]gmail[dot]com>
+ * @author      Brett Stimmerman <brettstimmerman[at]gmail[dot]com>
+ * @copyright   2005 Michal Migurski
+ * @version     CVS: $Id: JSON.php,v 1.31 2006/06/28 05:54:17 migurski Exp $
+ * @license     http://www.opensource.org/licenses/bsd-license.php
+ * @link        http://pear.php.net/pepr/pepr-proposal-show.php?id=198
+ */
+
+/**
+ * Marker constant for Services_JSON::decode(), used to flag stack state
+ */
+define('SERVICES_JSON_SLICE',   1);
+
+/**
+ * Marker constant for Services_JSON::decode(), used to flag stack state
+ */
+define('SERVICES_JSON_IN_STR',  2);
+
+/**
+ * Marker constant for Services_JSON::decode(), used to flag stack state
+ */
+define('SERVICES_JSON_IN_ARR',  3);
+
+/**
+ * Marker constant for Services_JSON::decode(), used to flag stack state
+ */
+define('SERVICES_JSON_IN_OBJ',  4);
+
+/**
+ * Marker constant for Services_JSON::decode(), used to flag stack state
+ */
+define('SERVICES_JSON_IN_CMT', 5);
+
+/**
+ * Behavior switch for Services_JSON::decode()
+ */
+define('SERVICES_JSON_LOOSE_TYPE', 16);
+
+/**
+ * Behavior switch for Services_JSON::decode()
+ */
+define('SERVICES_JSON_SUPPRESS_ERRORS', 32);
+
+/**
+ * Converts to and from JSON format.
+ *
+ * Brief example of use:
+ *
+ * <code>
+ * // create a new instance of Services_JSON
+ * $json = new Services_JSON();
+ *
+ * // convert a complexe value to JSON notation, and send it to the browser
+ * $value = array('foo', 'bar', array(1, 2, 'baz'), array(3, array(4)));
+ * $output = $json->encode($value);
+ *
+ * print($output);
+ * // prints: ["foo","bar",[1,2,"baz"],[3,[4]]]
+ *
+ * // accept incoming POST data, assumed to be in JSON notation
+ * $input = file_get_contents('php://input', 1000000);
+ * $value = $json->decode($input);
+ * </code>
+ */
+class Services_JSON
+{
+   /**
+    * constructs a new JSON instance
+    *
+    * @param    int     $use    object behavior flags; combine with boolean-OR
+    *
+    *                           possible values:
+    *                           - SERVICES_JSON_LOOSE_TYPE:  loose typing.
+    *                                   "{...}" syntax creates associative arrays
+    *                                   instead of objects in decode().
+    *                           - SERVICES_JSON_SUPPRESS_ERRORS:  error suppression.
+    *                                   Values which can't be encoded (e.g. resources)
+    *                                   appear as NULL instead of throwing errors.
+    *                                   By default, a deeply-nested resource will
+    *                                   bubble up with an error, so all return values
+    *                                   from encode() should be checked with isError()
+    */
+    function Services_JSON($use = 0)
+    {
+        $this->use = $use;
+    }
+
+   /**
+    * convert a string from one UTF-16 char to one UTF-8 char
+    *
+    * Normally should be handled by mb_convert_encoding, but
+    * provides a slower PHP-only method for installations
+    * that lack the multibye string extension.
+    *
+    * @param    string  $utf16  UTF-16 character
+    * @return   string  UTF-8 character
+    * @access   private
+    */
+    function utf162utf8($utf16)
+    {
+        // oh please oh please oh please oh please oh please
+        if(function_exists('mb_convert_encoding')) {
+            return mb_convert_encoding($utf16, 'UTF-8', 'UTF-16');
+        }
+
+        $bytes = (ord($utf16{0}) << 8) | ord($utf16{1});
+
+        switch(true) {
+            case ((0x7F & $bytes) == $bytes):
+                // this case should never be reached, because we are in ASCII range
+                // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+                return chr(0x7F & $bytes);
+
+            case (0x07FF & $bytes) == $bytes:
+                // return a 2-byte UTF-8 character
+                // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+                return chr(0xC0 | (($bytes >> 6) & 0x1F))
+                     . chr(0x80 | ($bytes & 0x3F));
+
+            case (0xFFFF & $bytes) == $bytes:
+                // return a 3-byte UTF-8 character
+                // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+                return chr(0xE0 | (($bytes >> 12) & 0x0F))
+                     . chr(0x80 | (($bytes >> 6) & 0x3F))
+                     . chr(0x80 | ($bytes & 0x3F));
+        }
+
+        // ignoring UTF-32 for now, sorry
+        return '';
+    }
+
+   /**
+    * convert a string from one UTF-8 char to one UTF-16 char
+    *
+    * Normally should be handled by mb_convert_encoding, but
+    * provides a slower PHP-only method for installations
+    * that lack the multibye string extension.
+    *
+    * @param    string  $utf8   UTF-8 character
+    * @return   string  UTF-16 character
+    * @access   private
+    */
+    function utf82utf16($utf8)
+    {
+        // oh please oh please oh please oh please oh please
+        if(function_exists('mb_convert_encoding')) {
+            return mb_convert_encoding($utf8, 'UTF-16', 'UTF-8');
+        }
+
+        switch(strlen($utf8)) {
+            case 1:
+                // this case should never be reached, because we are in ASCII range
+                // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+                return $utf8;
+
+            case 2:
+                // return a UTF-16 character from a 2-byte UTF-8 char
+                // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+                return chr(0x07 & (ord($utf8{0}) >> 2))
+                     . chr((0xC0 & (ord($utf8{0}) << 6))
+                         | (0x3F & ord($utf8{1})));
+
+            case 3:
+                // return a UTF-16 character from a 3-byte UTF-8 char
+                // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+                return chr((0xF0 & (ord($utf8{0}) << 4))
+                         | (0x0F & (ord($utf8{1}) >> 2)))
+                     . chr((0xC0 & (ord($utf8{1}) << 6))
+                         | (0x7F & ord($utf8{2})));
+        }
+
+        // ignoring UTF-32 for now, sorry
+        return '';
+    }
+
+   /**
+    * encodes an arbitrary variable into JSON format
+    *
+    * @param    mixed   $var    any number, boolean, string, array, or object to be encoded.
+    *                           see argument 1 to Services_JSON() above for array-parsing behavior.
+    *                           if var is a strng, note that encode() always expects it
+    *                           to be in ASCII or UTF-8 format!
+    *
+    * @return   mixed   JSON string representation of input var or an error if a problem occurs
+    * @access   public
+    */
+    function encode($var)
+    {
+        switch (gettype($var)) {
+            case 'boolean':
+                return $var ? 'true' : 'false';
+
+            case 'NULL':
+                return 'null';
+
+            case 'integer':
+                return (int) $var;
+
+            case 'double':
+            case 'float':
+                return (float) $var;
+
+            case 'string':
+                // STRINGS ARE EXPECTED TO BE IN ASCII OR UTF-8 FORMAT
+                $ascii = '';
+                $strlen_var = strlen($var);
+
+               /*
+                * Iterate over every character in the string,
+                * escaping with a slash or encoding to UTF-8 where necessary
+                */
+                for ($c = 0; $c < $strlen_var; ++$c) {
+
+                    $ord_var_c = ord($var{$c});
+
+                    switch (true) {
+                        case $ord_var_c == 0x08:
+                            $ascii .= '\b';
+                            break;
+                        case $ord_var_c == 0x09:
+                            $ascii .= '\t';
+                            break;
+                        case $ord_var_c == 0x0A:
+                            $ascii .= '\n';
+                            break;
+                        case $ord_var_c == 0x0C:
+                            $ascii .= '\f';
+                            break;
+                        case $ord_var_c == 0x0D:
+                            $ascii .= '\r';
+                            break;
+
+                        case $ord_var_c == 0x22:
+                        case $ord_var_c == 0x2F:
+                        case $ord_var_c == 0x5C:
+                            // double quote, slash, slosh
+                            $ascii .= '\\'.$var{$c};
+                            break;
+
+                        case (($ord_var_c >= 0x20) && ($ord_var_c <= 0x7F)):
+                            // characters U-00000000 - U-0000007F (same as ASCII)
+                            $ascii .= $var{$c};
+                            break;
+
+                        case (($ord_var_c & 0xE0) == 0xC0):
+                            // characters U-00000080 - U-000007FF, mask 110XXXXX
+                            // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+                            $char = pack('C*', $ord_var_c, ord($var{$c + 1}));
+                            $c += 1;
+                            $utf16 = $this->utf82utf16($char);
+                            $ascii .= sprintf('\u%04s', bin2hex($utf16));
+                            break;
+
+                        case (($ord_var_c & 0xF0) == 0xE0):
+                            // characters U-00000800 - U-0000FFFF, mask 1110XXXX
+                            // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+                            $char = pack('C*', $ord_var_c,
+                                         ord($var{$c + 1}),
+                                         ord($var{$c + 2}));
+                            $c += 2;
+                            $utf16 = $this->utf82utf16($char);
+                            $ascii .= sprintf('\u%04s', bin2hex($utf16));
+                            break;
+
+                        case (($ord_var_c & 0xF8) == 0xF0):
+                            // characters U-00010000 - U-001FFFFF, mask 11110XXX
+                            // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+                            $char = pack('C*', $ord_var_c,
+                                         ord($var{$c + 1}),
+                                         ord($var{$c + 2}),
+                                         ord($var{$c + 3}));
+                            $c += 3;
+                            $utf16 = $this->utf82utf16($char);
+                            $ascii .= sprintf('\u%04s', bin2hex($utf16));
+                            break;
+
+                        case (($ord_var_c & 0xFC) == 0xF8):
+                            // characters U-00200000 - U-03FFFFFF, mask 111110XX
+                            // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+                            $char = pack('C*', $ord_var_c,
+                                         ord($var{$c + 1}),
+                                         ord($var{$c + 2}),
+                                         ord($var{$c + 3}),
+                                         ord($var{$c + 4}));
+                            $c += 4;
+                            $utf16 = $this->utf82utf16($char);
+                            $ascii .= sprintf('\u%04s', bin2hex($utf16));
+                            break;
+
+                        case (($ord_var_c & 0xFE) == 0xFC):
+                            // characters U-04000000 - U-7FFFFFFF, mask 1111110X
+                            // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+                            $char = pack('C*', $ord_var_c,
+                                         ord($var{$c + 1}),
+                                         ord($var{$c + 2}),
+                                         ord($var{$c + 3}),
+                                         ord($var{$c + 4}),
+                                         ord($var{$c + 5}));
+                            $c += 5;
+                            $utf16 = $this->utf82utf16($char);
+                            $ascii .= sprintf('\u%04s', bin2hex($utf16));
+                            break;
+                    }
+                }
+
+                return '"'.$ascii.'"';
+
+            case 'array':
+               /*
+                * As per JSON spec if any array key is not an integer
+                * we must treat the the whole array as an object. We
+                * also try to catch a sparsely populated associative
+                * array with numeric keys here because some JS engines
+                * will create an array with empty indexes up to
+                * max_index which can cause memory issues and because
+                * the keys, which may be relevant, will be remapped
+                * otherwise.
+                *
+                * As per the ECMA and JSON specification an object may
+                * have any string as a property. Unfortunately due to
+                * a hole in the ECMA specification if the key is a
+                * ECMA reserved word or starts with a digit the
+                * parameter is only accessible using ECMAScript's
+                * bracket notation.
+                */
+
+                // treat as a JSON object
+                if (is_array($var) && count($var) && (array_keys($var) !== range(0, sizeof($var) - 1))) {
+                    $properties = array_map(array($this, 'name_value'),
+                                            array_keys($var),
+                                            array_values($var));
+
+                    foreach($properties as $property) {
+                        if(Services_JSON::isError($property)) {
+                            return $property;
+                        }
+                    }
+
+                    return '{' . join(',', $properties) . '}';
+                }
+
+                // treat it like a regular array
+                $elements = array_map(array($this, 'encode'), $var);
+
+                foreach($elements as $element) {
+                    if(Services_JSON::isError($element)) {
+                        return $element;
+                    }
+                }
+
+                return '[' . join(',', $elements) . ']';
+
+            case 'object':
+                $vars = get_object_vars($var);
+
+                $properties = array_map(array($this, 'name_value'),
+                                        array_keys($vars),
+                                        array_values($vars));
+
+                foreach($properties as $property) {
+                    if(Services_JSON::isError($property)) {
+                        return $property;
+                    }
+                }
+
+                return '{' . join(',', $properties) . '}';
+
+            default:
+                return ($this->use & SERVICES_JSON_SUPPRESS_ERRORS)
+                    ? 'null'
+                    : new Services_JSON_Error(gettype($var)." can not be encoded as JSON string");
+        }
+    }
+
+   /**
+    * array-walking function for use in generating JSON-formatted name-value pairs
+    *
+    * @param    string  $name   name of key to use
+    * @param    mixed   $value  reference to an array element to be encoded
+    *
+    * @return   string  JSON-formatted name-value pair, like '"name":value'
+    * @access   private
+    */
+    function name_value($name, $value)
+    {
+        $encoded_value = $this->encode($value);
+
+        if(Services_JSON::isError($encoded_value)) {
+            return $encoded_value;
+        }
+
+        return $this->encode(strval($name)) . ':' . $encoded_value;
+    }
+
+   /**
+    * reduce a string by removing leading and trailing comments and whitespace
+    *
+    * @param    $str    string      string value to strip of comments and whitespace
+    *
+    * @return   string  string value stripped of comments and whitespace
+    * @access   private
+    */
+    function reduce_string($str)
+    {
+        $str = preg_replace(array(
+
+                // eliminate single line comments in '// ...' form
+                '#^\s*//(.+)$#m',
+
+                // eliminate multi-line comments in '/* ... */' form, at start of string
+                '#^\s*/\*(.+)\*/#Us',
+
+                // eliminate multi-line comments in '/* ... */' form, at end of string
+                '#/\*(.+)\*/\s*$#Us'
+
+            ), '', $str);
+
+        // eliminate extraneous space
+        return trim($str);
+    }
+
+   /**
+    * decodes a JSON string into appropriate variable
+    *
+    * @param    string  $str    JSON-formatted string
+    *
+    * @return   mixed   number, boolean, string, array, or object
+    *                   corresponding to given JSON input string.
+    *                   See argument 1 to Services_JSON() above for object-output behavior.
+    *                   Note that decode() always returns strings
+    *                   in ASCII or UTF-8 format!
+    * @access   public
+    */
+    function decode($str)
+    {
+        $str = $this->reduce_string($str);
+
+        switch (strtolower($str)) {
+            case 'true':
+                return true;
+
+            case 'false':
+                return false;
+
+            case 'null':
+                return null;
+
+            default:
+                $m = array();
+
+                if (is_numeric($str)) {
+                    // Lookie-loo, it's a number
+
+                    // This would work on its own, but I'm trying to be
+                    // good about returning integers where appropriate:
+                    // return (float)$str;
+
+                    // Return float or int, as appropriate
+                    return ((float)$str == (integer)$str)
+                        ? (integer)$str
+                        : (float)$str;
+
+                } elseif (preg_match('/^("|\').*(\1)$/s', $str, $m) && $m[1] == $m[2]) {
+                    // STRINGS RETURNED IN UTF-8 FORMAT
+                    $delim = substr($str, 0, 1);
+                    $chrs = substr($str, 1, -1);
+                    $utf8 = '';
+                    $strlen_chrs = strlen($chrs);
+
+                    for ($c = 0; $c < $strlen_chrs; ++$c) {
+
+                        $substr_chrs_c_2 = substr($chrs, $c, 2);
+                        $ord_chrs_c = ord($chrs{$c});
+
+                        switch (true) {
+                            case $substr_chrs_c_2 == '\b':
+                                $utf8 .= chr(0x08);
+                                ++$c;
+                                break;
+                            case $substr_chrs_c_2 == '\t':
+                                $utf8 .= chr(0x09);
+                                ++$c;
+                                break;
+                            case $substr_chrs_c_2 == '\n':
+                                $utf8 .= chr(0x0A);
+                                ++$c;
+                                break;
+                            case $substr_chrs_c_2 == '\f':
+                                $utf8 .= chr(0x0C);
+                                ++$c;
+                                break;
+                            case $substr_chrs_c_2 == '\r':
+                                $utf8 .= chr(0x0D);
+                                ++$c;
+                                break;
+
+                            case $substr_chrs_c_2 == '\\"':
+                            case $substr_chrs_c_2 == '\\\'':
+                            case $substr_chrs_c_2 == '\\\\':
+                            case $substr_chrs_c_2 == '\\/':
+                                if (($delim == '"' && $substr_chrs_c_2 != '\\\'') ||
+                                   ($delim == "'" && $substr_chrs_c_2 != '\\"')) {
+                                    $utf8 .= $chrs{++$c};
+                                }
+                                break;
+
+                            case preg_match('/\\\u[0-9A-F]{4}/i', substr($chrs, $c, 6)):
+                                // single, escaped unicode character
+                                $utf16 = chr(hexdec(substr($chrs, ($c + 2), 2)))
+                                       . chr(hexdec(substr($chrs, ($c + 4), 2)));
+                                $utf8 .= $this->utf162utf8($utf16);
+                                $c += 5;
+                                break;
+
+                            case ($ord_chrs_c >= 0x20) && ($ord_chrs_c <= 0x7F):
+                                $utf8 .= $chrs{$c};
+                                break;
+
+                            case ($ord_chrs_c & 0xE0) == 0xC0:
+                                // characters U-00000080 - U-000007FF, mask 110XXXXX
+                                //see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+                                $utf8 .= substr($chrs, $c, 2);
+                                ++$c;
+                                break;
+
+                            case ($ord_chrs_c & 0xF0) == 0xE0:
+                                // characters U-00000800 - U-0000FFFF, mask 1110XXXX
+                                // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+                                $utf8 .= substr($chrs, $c, 3);
+                                $c += 2;
+                                break;
+
+                            case ($ord_chrs_c & 0xF8) == 0xF0:
+                                // characters U-00010000 - U-001FFFFF, mask 11110XXX
+                                // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+                                $utf8 .= substr($chrs, $c, 4);
+                                $c += 3;
+                                break;
+
+                            case ($ord_chrs_c & 0xFC) == 0xF8:
+                                // characters U-00200000 - U-03FFFFFF, mask 111110XX
+                                // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+                                $utf8 .= substr($chrs, $c, 5);
+                                $c += 4;
+                                break;
+
+                            case ($ord_chrs_c & 0xFE) == 0xFC:
+                                // characters U-04000000 - U-7FFFFFFF, mask 1111110X
+                                // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+                                $utf8 .= substr($chrs, $c, 6);
+                                $c += 5;
+                                break;
+
+                        }
+
+                    }
+
+                    return $utf8;
+
+                } elseif (preg_match('/^\[.*\]$/s', $str) || preg_match('/^\{.*\}$/s', $str)) {
+                    // array, or object notation
+
+                    if ($str{0} == '[') {
+                        $stk = array(SERVICES_JSON_IN_ARR);
+                        $arr = array();
+                    } else {
+                        if ($this->use & SERVICES_JSON_LOOSE_TYPE) {
+                            $stk = array(SERVICES_JSON_IN_OBJ);
+                            $obj = array();
+                        } else {
+                            $stk = array(SERVICES_JSON_IN_OBJ);
+                            $obj = new stdClass();
+                        }
+                    }
+
+                    array_push($stk, array('what'  => SERVICES_JSON_SLICE,
+                                           'where' => 0,
+                                           'delim' => false));
+
+                    $chrs = substr($str, 1, -1);
+                    $chrs = $this->reduce_string($chrs);
+
+                    if ($chrs == '') {
+                        if (reset($stk) == SERVICES_JSON_IN_ARR) {
+                            return $arr;
+
+                        } else {
+                            return $obj;
+
+                        }
+                    }
+
+                    //print("\nparsing {$chrs}\n");
+
+                    $strlen_chrs = strlen($chrs);
+
+                    for ($c = 0; $c <= $strlen_chrs; ++$c) {
+
+                        $top = end($stk);
+                        $substr_chrs_c_2 = substr($chrs, $c, 2);
+
+                        if (($c == $strlen_chrs) || (($chrs{$c} == ',') && ($top['what'] == SERVICES_JSON_SLICE))) {
+                            // found a comma that is not inside a string, array, etc.,
+                            // OR we've reached the end of the character list
+                            $slice = substr($chrs, $top['where'], ($c - $top['where']));
+                            array_push($stk, array('what' => SERVICES_JSON_SLICE, 'where' => ($c + 1), 'delim' => false));
+                            //print("Found split at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n");
+
+                            if (reset($stk) == SERVICES_JSON_IN_ARR) {
+                                // we are in an array, so just push an element onto the stack
+                                array_push($arr, $this->decode($slice));
+
+                            } elseif (reset($stk) == SERVICES_JSON_IN_OBJ) {
+                                // we are in an object, so figure
+                                // out the property name and set an
+                                // element in an associative array,
+                                // for now
+                                $parts = array();
+                                
+                                if (preg_match('/^\s*(["\'].*[^\\\]["\'])\s*:\s*(\S.*),?$/Uis', $slice, $parts)) {
+                                    // "name":value pair
+                                    $key = $this->decode($parts[1]);
+                                    $val = $this->decode($parts[2]);
+
+                                    if ($this->use & SERVICES_JSON_LOOSE_TYPE) {
+                                        $obj[$key] = $val;
+                                    } else {
+                                        $obj->$key = $val;
+                                    }
+                                } elseif (preg_match('/^\s*(\w+)\s*:\s*(\S.*),?$/Uis', $slice, $parts)) {
+                                    // name:value pair, where name is unquoted
+                                    $key = $parts[1];
+                                    $val = $this->decode($parts[2]);
+
+                                    if ($this->use & SERVICES_JSON_LOOSE_TYPE) {
+                                        $obj[$key] = $val;
+                                    } else {
+                                        $obj->$key = $val;
+                                    }
+                                }
+
+                            }
+
+                        } elseif ((($chrs{$c} == '"') || ($chrs{$c} == "'")) && ($top['what'] != SERVICES_JSON_IN_STR)) {
+                            // found a quote, and we are not inside a string
+                            array_push($stk, array('what' => SERVICES_JSON_IN_STR, 'where' => $c, 'delim' => $chrs{$c}));
+                            //print("Found start of string at {$c}\n");
+
+                        } elseif (($chrs{$c} == $top['delim']) &&
+                                 ($top['what'] == SERVICES_JSON_IN_STR) &&
+                                 ((strlen(substr($chrs, 0, $c)) - strlen(rtrim(substr($chrs, 0, $c), '\\'))) % 2 != 1)) {
+                            // found a quote, we're in a string, and it's not escaped
+                            // we know that it's not escaped becase there is _not_ an
+                            // odd number of backslashes at the end of the string so far
+                            array_pop($stk);
+                            //print("Found end of string at {$c}: ".substr($chrs, $top['where'], (1 + 1 + $c - $top['where']))."\n");
+
+                        } elseif (($chrs{$c} == '[') &&
+                                 in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) {
+                            // found a left-bracket, and we are in an array, object, or slice
+                            array_push($stk, array('what' => SERVICES_JSON_IN_ARR, 'where' => $c, 'delim' => false));
+                            //print("Found start of array at {$c}\n");
+
+                        } elseif (($chrs{$c} == ']') && ($top['what'] == SERVICES_JSON_IN_ARR)) {
+                            // found a right-bracket, and we're in an array
+                            array_pop($stk);
+                            //print("Found end of array at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n");
+
+                        } elseif (($chrs{$c} == '{') &&
+                                 in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) {
+                            // found a left-brace, and we are in an array, object, or slice
+                            array_push($stk, array('what' => SERVICES_JSON_IN_OBJ, 'where' => $c, 'delim' => false));
+                            //print("Found start of object at {$c}\n");
+
+                        } elseif (($chrs{$c} == '}') && ($top['what'] == SERVICES_JSON_IN_OBJ)) {
+                            // found a right-brace, and we're in an object
+                            array_pop($stk);
+                            //print("Found end of object at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n");
+
+                        } elseif (($substr_chrs_c_2 == '/*') &&
+                                 in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) {
+                            // found a comment start, and we are in an array, object, or slice
+                            array_push($stk, array('what' => SERVICES_JSON_IN_CMT, 'where' => $c, 'delim' => false));
+                            $c++;
+                            //print("Found start of comment at {$c}\n");
+
+                        } elseif (($substr_chrs_c_2 == '*/') && ($top['what'] == SERVICES_JSON_IN_CMT)) {
+                            // found a comment end, and we're in one now
+                            array_pop($stk);
+                            $c++;
+
+                            for ($i = $top['where']; $i <= $c; ++$i)
+                                $chrs = substr_replace($chrs, ' ', $i, 1);
+
+                            //print("Found end of comment at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n");
+
+                        }
+
+                    }
+
+                    if (reset($stk) == SERVICES_JSON_IN_ARR) {
+                        return $arr;
+
+                    } elseif (reset($stk) == SERVICES_JSON_IN_OBJ) {
+                        return $obj;
+
+                    }
+
+                }
+        }
+    }
+
+    /**
+     * @todo Ultimately, this should just call PEAR::isError()
+     */
+    function isError($data, $code = null)
+    {
+        if (class_exists('pear')) {
+            return PEAR::isError($data, $code);
+        } elseif (is_object($data) && (get_class($data) == 'services_json_error' ||
+                                 is_subclass_of($data, 'services_json_error'))) {
+            return true;
+        }
+
+        return false;
+    }
+}
+
+if (class_exists('PEAR_Error')) {
+
+    class Services_JSON_Error extends PEAR_Error
+    {
+        function Services_JSON_Error($message = 'unknown error', $code = null,
+                                     $mode = null, $options = null, $userinfo = null)
+        {
+            parent::PEAR_Error($message, $code, $mode, $options, $userinfo);
+        }
+    }
+
+} else {
+
+    /**
+     * @todo Ultimately, this class shall be descended from PEAR_Error
+     */
+    class Services_JSON_Error
+    {
+        function Services_JSON_Error($message = 'unknown error', $code = null,
+                                     $mode = null, $options = null, $userinfo = null)
+        {
+
+        }
+    }
+
+}
+    
+?>
index 14edec8..96fe7cb 100644 (file)
@@ -1156,9 +1156,8 @@ Element.Methods = {
 
   scrollTo: function(element) {
     element = $(element);
-    var x = element.x ? element.x : element.offsetLeft,
-        y = element.y ? element.y : element.offsetTop;
-    window.scrollTo(x, y);
+    var pos = Position.cumulativeOffset(element);
+    window.scrollTo(pos[0], pos[1]);
     return element;
   },
 
index 9737621..8aab7dd 100644 (file)
@@ -1,8 +1,9 @@
-// script.aculo.us builder.js v1.6.4, Wed Sep 06 11:30:58 CEST 2006
+// script.aculo.us builder.js v1.6.5, Wed Nov 08 14:17:49 CET 2006
 
-// Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
+// Copyright (c) 2005, 2006 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
 //
-// See scriptaculous.js for full license.
+// script.aculo.us is freely distributable under the terms of an MIT-style license.
+// For details, see the script.aculo.us web site: http://script.aculo.us/
 
 var Builder = {
   NODEMAP: {
@@ -77,10 +78,16 @@ var Builder = {
   _text: function(text) {
      return document.createTextNode(text);
   },
+
+  ATTR_MAP: {
+    'className': 'class',
+    'htmlFor': 'for'
+  },
+
   _attributes: function(attributes) {
     var attrs = [];
     for(attribute in attributes)
-      attrs.push((attribute=='className' ? 'class' : attribute) +
+      attrs.push((attribute in this.ATTR_MAP ? this.ATTR_MAP[attribute] : attribute) +
           '="' + attributes[attribute].toString().escapeHTML() + '"');
     return attrs.join(" ");
   },
@@ -100,6 +107,11 @@ var Builder = {
   _isStringOrNumber: function(param) {
     return(typeof param=='string' || typeof param=='number');
   },
+  build: function(html) {
+    var element = this.node('div');
+    $(element).update(html.strip());
+    return element.down();
+  },
   dump: function(scope) { 
     if(typeof scope != 'object' && typeof scope != 'function') scope = window; //global scope 
   
@@ -116,4 +128,4 @@ var Builder = {
       } 
     });
   }
-}
\ No newline at end of file
+}
index bb4186d..83230ea 100644 (file)
@@ -1,14 +1,15 @@
-// script.aculo.us controls.js v1.6.4, Wed Sep 06 11:30:58 CEST 2006
+// script.aculo.us controls.js v1.6.5, Wed Nov 08 14:17:49 CET 2006
 
-// Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
-//           (c) 2005 Ivan Krstic (http://blogs.law.harvard.edu/ivan)
-//           (c) 2005 Jon Tirsen (http://www.tirsen.com)
+// Copyright (c) 2005, 2006 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
+//           (c) 2005, 2006 Ivan Krstic (http://blogs.law.harvard.edu/ivan)
+//           (c) 2005, 2006 Jon Tirsen (http://www.tirsen.com)
 // Contributors:
 //  Richard Livsey
 //  Rahul Bhargava
 //  Rob Wills
 // 
-// See scriptaculous.js for full license.
+// script.aculo.us is freely distributable under the terms of an MIT-style license.
+// For details, see the script.aculo.us web site: http://script.aculo.us/
 
 // Autocompleter.Base handles all the autocompletion functionality 
 // that's independent of the data source for autocompletion. This
@@ -264,11 +265,11 @@ Autocompleter.Base.prototype = {
     if(!this.changed && this.hasFocus) {
       this.update.innerHTML = choices;
       Element.cleanWhitespace(this.update);
-      Element.cleanWhitespace(this.update.firstChild);
+      Element.cleanWhitespace(this.update.down());
 
-      if(this.update.firstChild && this.update.firstChild.childNodes) {
+      if(this.update.firstChild && this.update.down().childNodes) {
         this.entryCount = 
-          this.update.firstChild.childNodes.length;
+          this.update.down().childNodes.length;
         for (var i = 0; i < this.entryCount; i++) {
           var entry = this.getEntry(i);
           entry.autocompleteIndex = i;
index 979268d..3505b23 100644 (file)
@@ -1,11 +1,10 @@
-// script.aculo.us dragdrop.js v1.6.4, Wed Sep 06 11:30:58 CEST 2006
+// script.aculo.us dragdrop.js v1.6.5, Wed Nov 08 14:17:49 CET 2006
 
-// Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
-//           (c) 2005 Sammi Williams (http://www.oriontransfer.co.nz, sammi@oriontransfer.co.nz)
+// Copyright (c) 2005, 2006 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
+//           (c) 2005, 2006 Sammi Williams (http://www.oriontransfer.co.nz, sammi@oriontransfer.co.nz)
 // 
-// See scriptaculous.js for full license.
-
-/*--------------------------------------------------------------------------*/
+// script.aculo.us is freely distributable under the terms of an MIT-style license.
+// For details, see the script.aculo.us web site: http://script.aculo.us/
 
 if(typeof Effect == 'undefined')
   throw("dragdrop.js requires including script.aculo.us' effects.js library");
@@ -253,7 +252,7 @@ Draggable.prototype = {
       delay: 0
     };
     
-    if(arguments[1] && typeof arguments[1].endeffect == 'undefined')
+    if(!arguments[1] || typeof arguments[1].endeffect == 'undefined')
       Object.extend(defaults, {
         starteffect: function(element) {
           element._opacity = Element.getOpacity(element);
@@ -266,10 +265,9 @@ Draggable.prototype = {
 
     this.element = $(element);
     
-    if(options.handle && (typeof options.handle == 'string')) {
-      var h = Element.childrenWithClassName(this.element, options.handle, true);
-      if(h.length>0) this.handle = h[0];
-    }
+    if(options.handle && (typeof options.handle == 'string'))
+      this.handle = this.element.down('.'+options.handle, 0);
+    
     if(!this.handle) this.handle = $(options.handle);
     if(!this.handle) this.handle = this.element;
     
@@ -370,12 +368,8 @@ Draggable.prototype = {
         with(this._getWindowScroll(this.options.scroll)) { p = [ left, top, left+width, top+height ]; }
       } else {
         p = Position.page(this.options.scroll);
-        p[0] += this.options.scroll.scrollLeft;
-        p[1] += this.options.scroll.scrollTop;
-        
-        p[0] += (window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft || 0);
-        p[1] += (window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop || 0);
-        
+        p[0] += this.options.scroll.scrollLeft + Position.deltaX;
+        p[1] += this.options.scroll.scrollTop + Position.deltaY;
         p.push(p[0]+this.options.scroll.offsetWidth);
         p.push(p[1]+this.options.scroll.offsetHeight);
       }
@@ -443,7 +437,6 @@ Draggable.prototype = {
     var pos = Position.cumulativeOffset(this.element);
     if(this.options.ghosting) {
       var r   = Position.realOffset(this.element);
-      window.status = r.inspect();
       pos[0] += r[0] - Position.deltaX; pos[1] += r[1] - Position.deltaY;
     }
     
@@ -641,18 +634,7 @@ var Sortable = {
       delay:       options.delay,
       ghosting:    options.ghosting,
       constraint:  options.constraint,
-      handle:      options.handle,
-               onEnd: function (element, event) { 
-                       var h = Element.childrenWithClassName(element.element, 'dashboard-content')[0]
-                       if (h)  {
-                               if (Element.hasClassName(element.element.parentNode,'dashboard-dock'))  {
-                                       Element.update(h,'4566');
-                               } else {
-                                       Element.update(h,'123');
-                               }
-                       }
-               }
-};
+      handle:      options.handle };
 
     if(options.starteffect)
       options_for_draggable.starteffect = options.starteffect;
@@ -678,7 +660,6 @@ var Sortable = {
       tree:        options.tree,
       hoverclass:  options.hoverclass,
       onHover:     Sortable.onHover
-      //greedy:      !options.dropOnEmpty
     }
     
     var options_for_tree = {
@@ -703,7 +684,7 @@ var Sortable = {
     (this.findElements(element, options) || []).each( function(e) {
       // handles are per-draggable
       var handle = options.handle ? 
-        Element.childrenWithClassName(e, options.handle)[0] : e;    
+        $(e).down('.'+options.handle,0) : e;    
       options.draggables.push(
         new Draggable(e, Object.extend(options_for_draggable, { handle: handle })));
       Droppables.add(e, options_for_droppable);
@@ -801,7 +782,7 @@ var Sortable = {
   },
 
   unmark: function() {
-    if(Sortable._marker) Element.hide(Sortable._marker);
+    if(Sortable._marker) Sortable._marker.hide();
   },
 
   mark: function(dropon, position) {
@@ -810,23 +791,21 @@ var Sortable = {
     if(sortable && !sortable.ghosting) return; 
 
     if(!Sortable._marker) {
-      Sortable._marker = $('dropmarker') || document.createElement('DIV');
-      Element.hide(Sortable._marker);
-      Element.addClassName(Sortable._marker, 'dropmarker');
-      Sortable._marker.style.position = 'absolute';
+      Sortable._marker = 
+        ($('dropmarker') || Element.extend(document.createElement('DIV'))).
+          hide().addClassName('dropmarker').setStyle({position:'absolute'});
       document.getElementsByTagName("body").item(0).appendChild(Sortable._marker);
     }    
     var offsets = Position.cumulativeOffset(dropon);
-    Sortable._marker.style.left = offsets[0] + 'px';
-    Sortable._marker.style.top = offsets[1] + 'px';
+    Sortable._marker.setStyle({left: offsets[0]+'px', top: offsets[1] + 'px'});
     
     if(position=='after')
       if(sortable.overlap == 'horizontal') 
-        Sortable._marker.style.left = (offsets[0]+dropon.clientWidth) + 'px';
+        Sortable._marker.setStyle({left: (offsets[0]+dropon.clientWidth) + 'px'});
       else
-        Sortable._marker.style.top = (offsets[1]+dropon.clientHeight) + 'px';
+        Sortable._marker.setStyle({top: (offsets[1]+dropon.clientHeight) + 'px'});
     
-    Element.show(Sortable._marker);
+    Sortable._marker.show();
   },
   
   _tree: function(element, options, parent) {
@@ -841,9 +820,9 @@ var Sortable = {
         id: encodeURIComponent(match ? match[1] : null),
         element: element,
         parent: parent,
-        children: new Array,
+        children: [],
         position: parent.children.length,
-        container: Sortable._findChildrenElement(children[i], options.treeTag.toUpperCase())
+        container: $(children[i]).down(options.treeTag)
       }
       
       /* Get the element containing the children and recurse over it */
@@ -856,17 +835,6 @@ var Sortable = {
     return parent; 
   },
 
-  /* Finds the first element of the given tag type within a parent element.
-    Used for finding the first LI[ST] within a L[IST]I[TEM].*/
-  _findChildrenElement: function (element, containerTag) {
-    if (element && element.hasChildNodes)
-      for (var i = 0; i < element.childNodes.length; ++i)
-        if (element.childNodes[i].tagName == containerTag)
-          return element.childNodes[i];
-  
-    return null;
-  },
-
   tree: function(element) {
     element = $(element);
     var sortableOptions = this.options(element);
@@ -881,12 +849,12 @@ var Sortable = {
     var root = {
       id: null,
       parent: null,
-      children: new Array,
+      children: [],
       container: element,
       position: 0
     }
     
-    return Sortable._tree (element, options, root);
+    return Sortable._tree(element, options, root);
   },
 
   /* Construct a [i] index for a particular node */
@@ -946,12 +914,10 @@ var Sortable = {
   }
 }
 
-/* Returns true if child is contained within element */
+// Returns true if child is contained within element
 Element.isParent = function(child, element) {
   if (!child.parentNode || child == element) return false;
-
   if (child.parentNode == element) return true;
-
   return Element.isParent(child.parentNode, element);
 }
 
@@ -974,8 +940,5 @@ Element.findChildren = function(element, only, recursive, tagName) {
 }
 
 Element.offsetSize = function (element, type) {
-  if (type == 'vertical' || type == 'height')
-    return element.offsetHeight;
-  else
-    return element.offsetWidth;
-}
\ No newline at end of file
+  return element['offset' + ((type=='vertical' || type=='height') ? 'Height' : 'Width')];
+}
index 8aa6d13..a464df5 100644 (file)
@@ -1,12 +1,13 @@
-// script.aculo.us effects.js v1.6.4, Wed Sep 06 11:30:58 CEST 2006
+// script.aculo.us effects.js v1.6.5, Wed Nov 08 14:17:49 CET 2006
 
-// Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
+// Copyright (c) 2005, 2006 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
 // Contributors:
 //  Justin Palmer (http://encytemedia.com/)
 //  Mark Pilgrim (http://diveintomark.org/)
 //  Martin Bialasinki
 // 
-// See scriptaculous.js for full license.  
+// script.aculo.us is freely distributable under the terms of an MIT-style license.
+// For details, see the script.aculo.us web site: http://script.aculo.us/ 
 
 // converts rgb() and #xxx to #xxxxxx format,  
 // returns self (or first argument) if not convertable  
@@ -43,15 +44,17 @@ Element.collectTextNodesIgnoreClass = function(element, className) {
 
 Element.setContentZoom = function(element, percent) {
   element = $(element);  
-  Element.setStyle(element, {fontSize: (percent/100) + 'em'});   
+  element.setStyle({fontSize: (percent/100) + 'em'});   
   if(navigator.appVersion.indexOf('AppleWebKit')>0) window.scrollBy(0,0);
+  return element;
 }
 
-Element.getOpacity = function(element){  
+Element.getOpacity = function(element){
+  element = $(element);
   var opacity;
-  if (opacity = Element.getStyle(element, 'opacity'))  
+  if (opacity = element.getStyle('opacity'))  
     return parseFloat(opacity);  
-  if (opacity = (Element.getStyle(element, 'filter') || '').match(/alpha\(opacity=(.*)\)/))  
+  if (opacity = (element.getStyle('filter') || '').match(/alpha\(opacity=(.*)\)/))  
     if(opacity[1]) return parseFloat(opacity[1]) / 100;  
   return 1.0;  
 }
@@ -59,34 +62,26 @@ Element.getOpacity = function(element){
 Element.setOpacity = function(element, value){  
   element= $(element);  
   if (value == 1){
-    Element.setStyle(element, { opacity: 
+    element.setStyle({ opacity: 
       (/Gecko/.test(navigator.userAgent) && !/Konqueror|Safari|KHTML/.test(navigator.userAgent)) ? 
       0.999999 : 1.0 });
     if(/MSIE/.test(navigator.userAgent) && !window.opera)  
-      Element.setStyle(element, {filter: Element.getStyle(element,'filter').replace(/alpha\([^\)]*\)/gi,'')});  
+      element.setStyle({filter: Element.getStyle(element,'filter').replace(/alpha\([^\)]*\)/gi,'')});  
   } else {  
     if(value < 0.00001) value = 0;  
-    Element.setStyle(element, {opacity: value});
+    element.setStyle({opacity: value});
     if(/MSIE/.test(navigator.userAgent) && !window.opera)  
-     Element.setStyle(element, 
-       { filter: Element.getStyle(element,'filter').replace(/alpha\([^\)]*\)/gi,'') +
-                 'alpha(opacity='+value*100+')' });  
+      element.setStyle(
+        { filter: element.getStyle('filter').replace(/alpha\([^\)]*\)/gi,'') +
+            'alpha(opacity='+value*100+')' });  
   }
+  return element;
 }  
  
 Element.getInlineOpacity = function(element){  
   return $(element).style.opacity || '';
 }  
 
-Element.childrenWithClassName = function(element, className, findFirst) {
-  var classNameRegExp = new RegExp("(^|\\s)" + className + "(\\s|$)");
-  var results = $A($(element).getElementsByTagName('*'))[findFirst ? 'detect' : 'select']( function(c) { 
-    return (c.className && c.className.match(classNameRegExp));
-  });
-  if(!results) results = [];
-  return results;
-}
-
 Element.forceRerendering = function(element) {
   try {
     element = $(element);
@@ -116,6 +111,7 @@ var Effect = {
       
     var tagifyStyle = 'position:relative';
     if(/MSIE/.test(navigator.userAgent) && !window.opera) tagifyStyle += ';zoom:1';
+    
     element = $(element);
     $A(element.childNodes).each( function(child) {
       if(child.nodeType==3) {
@@ -168,32 +164,35 @@ var Effect2 = Effect; // deprecated
 
 /* ------------- transitions ------------- */
 
-Effect.Transitions = {}
-
-Effect.Transitions.linear = Prototype.K;
-
-Effect.Transitions.sinoidal = function(pos) {
-  return (-Math.cos(pos*Math.PI)/2) + 0.5;
-}
-Effect.Transitions.reverse  = function(pos) {
-  return 1-pos;
-}
-Effect.Transitions.flicker = function(pos) {
-  return ((-Math.cos(pos*Math.PI)/4) + 0.75) + Math.random()/4;
-}
-Effect.Transitions.wobble = function(pos) {
-  return (-Math.cos(pos*Math.PI*(9*pos))/2) + 0.5;
-}
-Effect.Transitions.pulse = function(pos) {
-  return (Math.floor(pos*10) % 2 == 0 ? 
-    (pos*10-Math.floor(pos*10)) : 1-(pos*10-Math.floor(pos*10)));
-}
-Effect.Transitions.none = function(pos) {
-  return 0;
-}
-Effect.Transitions.full = function(pos) {
-  return 1;
-}
+Effect.Transitions = {
+  linear: Prototype.K,
+  sinoidal: function(pos) {
+    return (-Math.cos(pos*Math.PI)/2) + 0.5;
+  },
+  reverse: function(pos) {
+    return 1-pos;
+  },
+  flicker: function(pos) {
+    return ((-Math.cos(pos*Math.PI)/4) + 0.75) + Math.random()/4;
+  },
+  wobble: function(pos) {
+    return (-Math.cos(pos*Math.PI*(9*pos))/2) + 0.5;
+  },
+  pulse: function(pos, pulses) { 
+    pulses = pulses || 5; 
+    return (
+      Math.round((pos % (1/pulses)) * pulses) == 0 ? 
+            ((pos * pulses * 2) - Math.floor(pos * pulses * 2)) : 
+        1 - ((pos * pulses * 2) - Math.floor(pos * pulses * 2))
+      );
+  },
+  none: function(pos) {
+    return 0;
+  },
+  full: function(pos) {
+    return 1;
+  }
+};
 
 /* ------------- core effects ------------- */
 
@@ -220,6 +219,9 @@ Object.extend(Object.extend(Effect.ScopedQueue.prototype, Enumerable), {
             e.finishOn += effect.finishOn;
           });
         break;
+      case 'with-last':
+        timestamp = this.effects.pluck('startOn').max() || timestamp;
+        break;
       case 'end':
         // start effect after last queued effect has finished
         timestamp = this.effects.pluck('finishOn').max() || timestamp;
@@ -356,6 +358,17 @@ Object.extend(Object.extend(Effect.Parallel.prototype, Effect.Base.prototype), {
   }
 });
 
<