* Features & bugfixes for Inline Relational Record Editing (IRRE)
authorOliver Hader <oliver.hader@typo3.org>
Sun, 4 Feb 2007 16:15:38 +0000 (16:15 +0000)
committerOliver Hader <oliver.hader@typo3.org>
Sun, 4 Feb 2007 16:15:38 +0000 (16:15 +0000)
- Feature: hide new record link by appearance property
- Feature #4838: enabled element browser to be used as selector
- Bugfix #4839: use "size" parameter of parent table for record selector
- Bugfix: changed handling of required fields on nested child records

git-svn-id: https://svn.typo3.org/TYPO3v4/Core/trunk@1971 709f56b5-9817-0410-a4d7-c38de5d9e867

ChangeLog
t3lib/class.t3lib_tceforms.php
t3lib/class.t3lib_tceforms_inline.php
t3lib/jsfunc.inline.js
typo3/class.browse_links.php

index 1da2add..c4c93a1 100755 (executable)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,11 @@
+2007-02-04  Oliver Hader  <oh@inpublica.de>
+
+       * Features & bugfixes for Inline Relational Record Editing (IRRE)
+               - Feature: hide new record link by appearance property
+               - Feature #4838: enabled element browser to be used as selector
+               - Bugfix #4839: use "size" parameter of parent table for record selector
+               - Bugfix: changed handling of required fields on nested child records
+
 2007-02-04  Ingmar Schlecht  <ingmar@typo3.org>
 
        * Instant collapsing for pagetree: The pagetree now doesn't wait for the AJAX response any more when collapsing
index 1d9a960..ae6fe0f 100755 (executable)
@@ -1995,7 +1995,7 @@ class t3lib_TCEforms      {
                                        'thumbnails' => $thumbsnail,
                                        'readOnly' => $disabled
                                );
-                               $item.= $this->dbFileIcons($PA['itemFormElName'],'db',implode(',',$tempFT),$itemArray,'',$params,$PA['onFocus']);
+                               $item.= $this->dbFileIcons($PA['itemFormElName'],'db',implode(',',$tempFT),$itemArray,'',$params,$PA['onFocus'],$table,$field,$row['uid']);
 
                        break;
                }
@@ -3040,9 +3040,12 @@ class t3lib_TCEforms     {
         * @param       string          Alternative selector box.
         * @param       array           An array of additional parameters, eg: "size", "info", "headers" (array with "selector" and "items"), "noBrowser", "thumbnails"
         * @param       string          On focus attribute string
+        * @param       string          $table: (optional) Table name processing for
+        * @param       string          $field: (optional) Field of table name processing for
+        * @param       string          $uid:   (optional) uid of table record processing for
         * @return      string          The form fields for the selection.
         */
-       function dbFileIcons($fName,$mode,$allowed,$itemArray,$selector='',$params=array(),$onFocus='') {
+       function dbFileIcons($fName,$mode,$allowed,$itemArray,$selector='',$params=array(),$onFocus='',$table='',$field='',$uid='')     {
 
 
                $disabled = '';
@@ -3105,7 +3108,16 @@ class t3lib_TCEforms     {
                );
                if (!$params['readOnly']) {
                        if (!$params['noBrowser'])      {
-                               $aOnClick='setFormValueOpenBrowser(\''.$mode.'\',\''.($fName.'|||'.$allowed.'|').'\'); return false;';
+                                       // 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) {
+                                               $objectPrefix = $this->inline->inlineNames['object'].'['.$table.']';
+                                               $aOnClickInline = $objectPrefix.'|inline.checkUniqueElement|inline.setUniqueElement';
+                                               $rOnClickInline = 'inline.revertUnique(\''.$objectPrefix.'\',null,\''.$uid.'\');';
+                                       }
+                               }
+                               $aOnClick='setFormValueOpenBrowser(\''.$mode.'\',\''.($fName.'|||'.$allowed.'|'.$aOnClickInline).'\'); return false;';
                                $icons['R'][]='<a href="#" onclick="'.htmlspecialchars($aOnClick).'">'.
                                                '<img'.t3lib_iconWorks::skinImg($this->backPath,'gfx/insert3.gif','width="14" height="14"').' border="0" '.t3lib_BEfunc::titleAltAttrib($this->getLL('l_browse_'.($mode=='file'?'file':'db'))).' />'.
                                                '</a>';
@@ -3151,7 +3163,8 @@ class t3lib_TCEforms      {
                                                '<img'.t3lib_iconWorks::skinImg($this->backPath,'gfx/insert5.png','width="14" height="14"').' border="0" '.t3lib_BEfunc::titleAltAttrib(sprintf($this->getLL('l_clipInsert_'.($mode=='file'?'file':'db')),count($clipElements))).' />'.
                                                '</a>';
                        }
-                       $icons['L'][]='<a href="#" onclick="setFormValueManipulate(\''.$fName.'\',\'Remove\'); return false;">'.
+                       $rOnClick = $rOnClickInline.'setFormValueManipulate(\''.$fName.'\',\'Remove\'); return false';
+                       $icons['L'][]='<a href="#" onclick="'.htmlspecialchars($rOnClick).'">'.
                                        '<img'.t3lib_iconWorks::skinImg($this->backPath,'gfx/group_clear.gif','width="14" height="14"').' border="0" '.t3lib_BEfunc::titleAltAttrib($this->getLL('l_remove_selected')).' />'.
                                        '</a>';
                }
index 0c7da54..eb66e7e 100755 (executable)
@@ -139,6 +139,7 @@ class t3lib_TCEforms_inline {
                        // Init:
                $config = $PA['fieldConf']['config'];
                $foreign_table = $config['foreign_table'];
+               t3lib_div::loadTCA($foreign_table);
 
                $minitems = t3lib_div::intInRange($config['minitems'],0);
                $maxitems = t3lib_div::intInRange($config['maxitems'],0);
@@ -192,15 +193,20 @@ class t3lib_TCEforms_inline {
 
                        // 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);
+                               // If unique *and* selector, the should both be the same - get config:
+                       $selConfig = $this->getPossibleRecordsSelectorConfig($config, $config['foreign_unique']);
+                               // Get the used unique ids:
+                       $uniqueIds = $this->getUniqueIds($recordList, $config, $selConfig['type']=='groupdb');
                        $possibleRecords = $this->getPossibleRecords($table,$field,$row,$config,'foreign_unique');
-                       $uniqueMax = $config['appearance']['useCombination'] ? -1 : count($possibleRecords);
+                       $uniqueMax = $config['appearance']['useCombination'] || $possibleRecords === false ? -1 : count($possibleRecords);
                        $this->inlineData['unique'][$nameObject.'['.$foreign_table.']'] = array(
                                'max' => $uniqueMax,
                                'used' => $uniqueIds,
+                               'type' => $selConfig['type'],
                                'table' => $config['foreign_table'],
+                               'elTable' => $selConfig['table'], // element/record table (one step down in hierarchy)
                                'field' => $config['foreign_unique'],
-                               'selector' => $config['foreign_selector'] ? true : false,
+                               'selector' => $selConfig['PA'] && $selConfig['type'] ? $selConfig['type'] : false,
                                'possible' => $this->getPossibleRecordsFlat($possibleRecords),
                        );
                }
@@ -224,7 +230,7 @@ class t3lib_TCEforms_inline {
                        $config['inline']['inlineNewButtonStyle'] = 'display: none;';
                }
                        // add the "Create new record" link before all child records
-               if ($config['appearance']['newRecordLinkPosition'] != 'bottom') {
+               if (in_array($config['appearance']['newRecordLinkPosition'], array('both', 'top'))) {
                        $item .= $this->getNewRecordLink($nameObject.'['.$foreign_table.']', $config);
                }
 
@@ -239,7 +245,7 @@ class t3lib_TCEforms_inline {
                $item .= '</div>';
 
                        // add the "Create new record" link after all child records
-               if ($config['appearance']['newRecordLinkPosition'] != 'top') {
+               if (in_array($config['appearance']['newRecordLinkPosition'], array('both', 'bottom'))) {
                        $item .= $this->getNewRecordLink($nameObject.'['.$foreign_table.']', $config);
                }
 
@@ -283,9 +289,13 @@ class t3lib_TCEforms_inline {
                $foreign_field = $config['foreign_field'];
                $foreign_selector = $config['foreign_selector'];
 
-                       // record comes from storage (e.g. database)
+                       // Send a mapping information to the browser via JSON:
+                       // e.g. data[<curTable>][<curId>][<curField>] => data[<pid>][<parentTable>][<parentId>][<parentField>][<curTable>][<curId>][<curField>]
+               $this->inlineData['map'][$this->inlineNames['form']] = $this->inlineNames['object'];
+
+                       // Set this variable if we handle a brand new unsaved record:
                $isNewRecord = t3lib_div::testInt($rec['uid']) ? false : true;
-                       // if there is a selector field, normalize it
+                       // If there is a selector field, normalize it:
                if ($foreign_selector) {
                        $rec[$foreign_selector] = $this->normalizeUid($rec[$foreign_selector]);
                }
@@ -294,7 +304,7 @@ class t3lib_TCEforms_inline {
 
                if(!$hasAccess) return false;
 
-                       // get the current prependObjectId
+                       // Get the current naming scheme for DOM name/id attributes:
                $nameObject = $this->inlineNames['object'];
                $appendFormFieldNames = '['.$foreign_table.']['.$rec['uid'].']';
                $formFieldNames = $nameObject.$appendFormFieldNames;
@@ -320,6 +330,7 @@ class t3lib_TCEforms_inline {
                                // set additional field for processing for saving
                        $fields .= '<input type="hidden" name="'.$this->prependCmdFieldNames.$appendFormFieldNames.'[delete]" value="1" disabled="disabled" />';
                }
+
                        // if this record should be shown collapsed
                if (!$isExpanded) $appearanceStyleFields = ' style="display: none;"';
 
@@ -368,7 +379,18 @@ class t3lib_TCEforms_inline {
                        // render the special alternative title
                } elseif ($hasForeignLabel || $hasSymmetricLabel) {
                        $titleCol = $hasForeignLabel ? $config['foreign_label'] : $config['symmetric_label'];
-                       $recTitle = t3lib_BEfunc::getProcessedValueExtra($foreign_table, $titleCol, $rec[$titleCol], 0, 0, false);
+                       $foreignConfig = $this->getPossibleRecordsSelectorConfig($config, $titleCol);
+                               // Render title for everything else than group/db:
+                       if ($foreignConfig['type'] != 'groupdb') {
+                               $recTitle = t3lib_BEfunc::getProcessedValueExtra($foreign_table, $titleCol, $rec[$titleCol], 0, 0, false);
+                               // Render title for group/db:
+                       } else {
+                                       // $recTitle could be something like: "tx_table_123|...",
+                               $valueParts = t3lib_div::trimExplode('|', $rec[$titleCol]);
+                               $itemParts = t3lib_div::revExplode('_', $valueParts[0], 2);
+                               $recTemp = t3lib_befunc::getRecordWSOL($itemParts[0], $itemParts[1]);
+                               $recTitle = t3lib_BEfunc::getRecordTitle($itemParts[0], $recTemp, true);
+                       }
                        $recTitle = t3lib_BEfunc::getRecordTitlePrep($recTitle);
                        if (!strcmp(trim($recTitle),'')) {
                                $recTitle = t3lib_BEfunc::getNoRecordTitle(true);
@@ -510,7 +532,7 @@ class t3lib_TCEforms_inline {
                                ($isPagesTable && ($localCalcPerms&4)) || (!$isPagesTable && ($calcPerms&16))
                                )       {
                                $onClick = "inline.deleteRecord('".$nameObjectFtId."');";
-                               $cells[]='<a href="#" onclick="'.htmlspecialchars('if (confirm('.$GLOBALS['LANG']->JScharCode($GLOBALS['LANG']->getLL('deleteWarning').t3lib_BEfunc::referenceCount($foreign_table,$rec['uid'],' (There are %s reference(s) to this record!)')).')) {   '.$onClick.' } return false;').'">'.
+                               $cells[]='<a href="#" onclick="'.htmlspecialchars('if (confirm('.$GLOBALS['LANG']->JScharCode($GLOBALS['LANG']->getLL('deleteWarning')).')) {   '.$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>';
                        }
@@ -529,6 +551,7 @@ class t3lib_TCEforms_inline {
                                                                                        <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).
@@ -584,6 +607,7 @@ class t3lib_TCEforms_inline {
                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).
@@ -597,6 +621,33 @@ class t3lib_TCEforms_inline {
                $foreign_table = $conf['foreign_table'];
                $foreign_selector = $conf['foreign_selector'];
 
+               $selConfig = $this->getPossibleRecordsSelectorConfig($conf, $foreign_selector);
+               $config = $selConfig['PA']['fieldConf']['config'];
+
+               if ($selConfig['type'] == 'select') {
+                       $item = $this->renderPossibleRecordsSelectorTypeSelect($selItems, $conf, $selConfig['PA'], $uniqueIds);
+               } elseif ($selConfig['type'] == 'groupdb') {
+                       $item = $this->renderPossibleRecordsSelectorTypeGroupDB($conf, $selConfig['PA']);
+               }
+
+               return $item;
+       }
+
+
+       /**
+        * 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           $PA: An array with additional configuration options
+        * @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 renderPossibleRecordsSelectorTypeSelect($selItems, $conf, &$PA, $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
@@ -621,14 +672,14 @@ class t3lib_TCEforms_inline {
 
                                // 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 = '
+                       $size = intval($conf['size']);
+                       $size = $conf['autoSizeMax'] ? t3lib_div::intInRange(count($itemArray)+1,t3lib_div::intInRange($size,1),$conf['autoSizeMax']) : $size;
+                       $onChange = "return inline.importNewRecord('".$this->inlineNames['object']."[".$conf['foreign_table']."]')";
+                       $item = '
                                <select id="'.$this->inlineNames['object'].'['.$conf['foreign_table'].']_selector"'.
                                                        $this->fObj->insertDefStyle('select').
                                                        ($size ? ' size="'.$size.'"' : '').
-                                                       ' onchange="'.htmlspecialchars($sOnChange).'"'.
+                                                       ' onchange="'.htmlspecialchars($onChange).'"'.
                                                        $PA['onFocus'].
                                                        $selector_itemListStyle.
                                                        ($conf['foreign_unique'] ? ' isunique="isunique"' : '').'>
@@ -641,15 +692,41 @@ class t3lib_TCEforms_inline {
                                // 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.
+                       $item .=
+                               '<a href="#" onclick="'.htmlspecialchars($onChange).'" align="abstop">'.
+                                       '<img'.t3lib_iconWorks::skinImg($this->backPath,'gfx/edit2.gif','width="11" height="12"').' align="absmiddle" '.t3lib_BEfunc::titleAltAttrib($createNewRelationText).' /> '.$createNewRelationText.
                                '</a>';
                                // wrap the selector and add a spacer to the bottom
-                       $itemsToSelect = '<div style="margin-bottom: 20px;">'.$itemsToSelect.'</div>';
+                       $item = '<div style="margin-bottom: 20px;">'.$item.'</div>';
                }
 
-               return $itemsToSelect;
+               return $item;
+       }
+
+
+       /**
+        * Generate a link that opens an element browser in a new window.
+        * For group/db there is no way o use a "selector" like a <select>|</select>-box.
+        *
+        * @param       array           $conf: TCA configuration of the parent(!) field
+        * @param       array           $PA: An array with additional configuration options
+        * @return      string          A HTML link that opens an element browser in a new window
+        */
+       function renderPossibleRecordsSelectorTypeGroupDB($conf, &$PA) {
+               $foreign_table = $conf['foreign_table'];
+
+               $config = $PA['fieldConf']['config'];
+               $allowed = $config['allowed'];
+               $objectPrefix = $this->inlineNames['object'].'['.$foreign_table.']';
+
+               $createNewRelationText = $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.php:cm.createNewRelation',1);
+               $onClick = "setFormValueOpenBrowser('db','".('|||'.$allowed.'|'.$objectPrefix.'|inline.checkUniqueElement||inline.importElement')."'); return false;";
+               $item =
+                       '<a href="#" onclick="'.htmlspecialchars($onClick).'">'.
+                               '<img'.t3lib_iconWorks::skinImg($this->backPath,'gfx/insert3.gif','width="14" height="14"').' align="absmiddle" '.t3lib_BEfunc::titleAltAttrib($createNewRelationText).' /> '.$createNewRelationText.
+                       '</a>';
+
+               return $item;
        }
 
 
@@ -717,7 +794,7 @@ class t3lib_TCEforms_inline {
                $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']);
@@ -731,7 +808,10 @@ class t3lib_TCEforms_inline {
                        // 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;
+                       $selConfig = $this->getPossibleRecordsSelectorConfig($config, $config['foreign_selector']);
+                               // For a selector of type group/db, prepend the tablename (<tablename>_<uid>):
+                       $record[$config['foreign_selector']] = $selConfig['type'] != 'groupdb' ? '' : $selConfig['table'].'_';
+                       $record[$config['foreign_selector']] .= $foreignUid;
                }
 
                        // the HTML-object-id's prefix of the dynamically created record
@@ -880,7 +960,7 @@ class t3lib_TCEforms_inline {
         * @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
+        * @return      mixed           Array of possible record items; false if type is "group/db", then everything could be "possible"
         */
        function getPossibleRecords($table,$field,$row,$conf,$checkForConfField='foreign_selector') {
                        // ctrl configuration from TCA:
@@ -889,35 +969,37 @@ class t3lib_TCEforms_inline {
                $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);
+               $foreignConfig = $this->getPossibleRecordsSelectorConfig($conf, $foreign_check);
+               $PA = $foreignConfig['PA'];
                $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 = $tcaTableCtrl['languageField'] && !strcmp($tcaTableCtrl['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])))   {
+               
+               if ($foreignConfig['type'] == 'select') {
+                               // 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 = $tcaTableCtrl['languageField'] && !strcmp($tcaTableCtrl['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]);
+                                       }
                                }
                        }
+               } else {
+                       $selItems = false;
                }
 
                return $selItems;
@@ -928,13 +1010,27 @@ class t3lib_TCEforms_inline {
         *
         * @param       array           $records: All inline records on this level
         * @param       array           $conf: The TCA field configuration of the inline field to be rendered
+        * @param       boolean         $splitValue: for usage with group/db, values come like "tx_table_123|Title%20abc", but we need "tx_table" and "123"
         * @return      array           The uids, that have been used already and should be used unique
         */
-       function getUniqueIds($records, $conf=array()) {
+       function getUniqueIds($records, $conf=array(), $splitValue=false) {
                $uniqueIds = array();
 
-               if ($conf['foreign_unique'] && count($records))
-                       foreach ($records as $rec) $uniqueIds[$rec['uid']] = $rec[$conf['foreign_unique']];
+               if ($conf['foreign_unique'] && count($records)) {
+                       foreach ($records as $rec) {
+                               $value = $rec[$conf['foreign_unique']];
+                                       // Split the value and extract the table and uid:
+                               if ($splitValue) {
+                                       $valueParts = t3lib_div::trimExplode('|', $value);
+                                       $itemParts = explode('_', $valueParts[0]);
+                                       $value = array(
+                                               'uid' => array_pop($itemParts),
+                                               'table' => implode('_', $itemParts)
+                                       );
+                               }
+                               $uniqueIds[$rec['uid']] = $value;
+                       }
+               }
 
                return $uniqueIds;
        }
@@ -1163,14 +1259,20 @@ class t3lib_TCEforms_inline {
        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]))
+                       // 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']))
+               }
+                       // Init appearance if not set:
+               if (!is_array($config['appearance'])) {
                        $config['appearance'] = array();
-               if (!in_array($config['appearance']['newRecordLinkPosition'], array('top', 'bottom', 'both')))
+               }
+                       // Set the position/appearance of the "Create new record" link:
+               if ($config['foreign_selector'] && !$config['appearance']['useCombination']) {
+                       $config['appearance']['newRecordLinkPosition'] = 'none';
+               } elseif (!in_array($config['appearance']['newRecordLinkPosition'], array('top', 'bottom', 'both', 'none'))) {
                        $config['appearance']['newRecordLinkPosition'] = 'top';
+               }
 
                return true;
        }
@@ -1437,17 +1539,70 @@ class t3lib_TCEforms_inline {
         * 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
+        * @return      mixed           A flat array with key=uid, value=label; if $possibleRecords isn't an array, false is returned.
         */
        function getPossibleRecordsFlat($possibleRecords) {
-               $flat = array();
-               if (is_array($possibleRecords))
+               $flat = false;
+               if (is_array($possibleRecords)) {
+                       $flat = array();
                        foreach ($possibleRecords as $record) $flat[$record[1]] = $record[0];
+               }
                return $flat;
        }
 
 
        /**
+        * Determine the configuration and the type of a record selector.
+        *
+        * @param       array           $conf: TCA configuration of the parent(!) field
+        * @return      array           Associative array with the keys 'PA' and 'type', both are false if the selector was not valid.
+        */
+       function getPossibleRecordsSelectorConfig($conf, $field = '') {
+               $foreign_table = $conf['foreign_table'];
+               $foreign_selector = $conf['foreign_selector'];
+
+               $PA = false;
+               $type = false;
+               $table = false;
+               
+               if ($field) {
+                       $PA = array();
+                       $PA['fieldConf'] = $GLOBALS['TCA'][$foreign_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
+                       $PA['fieldTSConfig'] = $this->fObj->setTSconfig($foreign_table,array(),$field);
+                       $config = $PA['fieldConf']['config'];
+                               // Determine type of Selector:
+                       $type = $this->getPossibleRecordsSelectorType($config);
+                               // Return table on this level:
+                       $table = $type == 'select' ? $config['foreign_table'] : $config['allowed'];
+               }
+               
+               return array(
+                       'PA' => $PA,
+                       'type' => $type,
+                       'table' => $table
+               );
+       }
+       
+
+       /**
+        * Determine the type of a record selector, e.g. select or group/db.
+        *
+        * @param       array           $config: TCE configuration of the selector
+        * @return      mixed           The type of the selector, 'select' or 'groupdb' - false not valid
+        */
+       function getPossibleRecordsSelectorType($config) {
+               $type = false;
+               if ($config['type'] == 'select') {
+                       $type = 'select';
+               } elseif ($config['type'] == 'group' && $config['internal_type'] == 'db') {
+                       $type = 'groupdb';
+               }
+               return $type;
+       }
+       
+       
+       /**
         * 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
         *
@@ -1489,11 +1644,11 @@ class t3lib_TCEforms_inline {
                                // 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 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
+                               // 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;
index a5061a4..ef9bad4 100755 (executable)
@@ -33,25 +33,15 @@ 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;
-       },
-       
+       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) {
                var currentUid = this.parseFormElementName('none', objectId, 1);
                var objectPrefix = this.parseFormElementName('full', objectId, 0, 1);
-               
+
                var currentState = '';
                var collapse = new Array();
                var expand = new Array();
@@ -63,7 +53,7 @@ var inline = {
 
                Element.toggle(objectId+'_fields');
                currentState = Element.visible(objectId+'_fields') ? 1 : 0
-               
+
                if (this.isNewRecord(objectId))
                        this.updateExpandedCollapsedStateLocally(objectId, currentState);
                else if (currentState)
@@ -72,7 +62,7 @@ var inline = {
                        collapse.push(currentUid);
 
                this.setExpandedCollapsedState(objectId, expand.join(','), collapse.join(','));
-               
+
                return false;
        },
 
@@ -96,16 +86,16 @@ var inline = {
                                }
                        }
                }
-               
+
                return collapse;
        },
-       
+
        updateExpandedCollapsedStateLocally: function(objectId, value) {
                var ucName = 'uc'+this.parseFormElementName('parts', objectId, 3, 2);
                var ucFormObj = document.getElementsByName(ucName);
                if (ucFormObj.length) ucFormObj[0].value = value;
        },
-       
+
        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!');
@@ -113,10 +103,9 @@ var inline = {
        },
 
        setExpandedCollapsedState: function(objectId, expand, collapse) {
-               // alert(objectId+': '+expand+', '+collapse);
                this.makeAjaxCall('setExpandedCollapsedState', objectId, expand, collapse);
        },
-       
+
        makeAjaxCall: function() {
                if (arguments.length > 1) {
                        var params = '';
@@ -129,11 +118,11 @@ var inline = {
                                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]);
@@ -142,53 +131,109 @@ var inline = {
        showAjaxFailure: function(xhr) {
                alert('Error: '+xhr.status+"\n"+xhr.statusText);
        },
-               
+
+               // foreign_selector: used by selector box (type='select')
        importNewRecord: function(objectId) {
                var selector = $(objectId+'_selector');
                if (selector.selectedIndex != -1) {
                        var selectedValue = selector.options[selector.selectedIndex].value;
+                       if (!this.data.unique || !this.data.unique[objectId]) {
+                               selector.options[selector.selectedIndex].selected = false;
+                       }
                        this.makeAjaxCall('createNewRecord', objectId, selectedValue);
                }
                return false;
        },
-       
+
+               // foreign_selector: used by element browser (type='group/db')
+       importElement: function(objectId, table, uid, type) {
+               window.setTimeout(
+                       function() {
+                               inline.makeAjaxCall('createNewRecord', objectId, uid);
+                       },
+                       10
+               );
+       },
+
+               // Check uniqueness for element browser:
+       checkUniqueElement: function(objectId, table, uid, type) {
+               if (this.checkUniqueUsed(objectId, uid, table)) {
+                       return {passed: false,message: 'There is already a relation to the selected element!'};
+               } else {
+                       return {passed: true};
+               }
+       },
+
+               // Checks if a record was used and should be unique:
+       checkUniqueUsed: function(objectId, uid, table) {
+               if (this.data.unique && this.data.unique[objectId]) {
+                       var unique = this.data.unique[objectId];
+                       var values = $H(unique.used).values();
+
+                               // for select: only the uid is stored
+                       if (unique['type'] == 'select') {
+                               if (values.indexOf(uid) != -1) return true;
+                               
+                               // for group/db: table and uid is stored in a assoc array
+                       } else if (unique.type == 'groupdb') {
+                               for (var i=values.length-1; i>=0; i--) {
+                                               // if the pair table:uid is already used:
+                                       if (values[i].table==table && values[i].uid==uid) return true;
+                               }
+                       }
+               }
+               return false;
+       },
+
+       setUniqueElement: function(objectId, table, uid, type, elName) {
+               var recordUid = this.parseFormElementName('none', elName, 1, 1);
+               // alert(objectId+'/'+table+'/'+uid+'/'+recordUid);
+               this.setUnique(objectId, recordUid, uid);
+       },
+
                // 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);
+                       if (unique.type == 'select') {
+                                       // 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;
+                                       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;
+                                       }
                                }
+                       } else if (unique.type == 'groupdb') {
+                                       // add the new record to the used items:
+                               this.data.unique[objectId].used[recordUid] = {'table':unique.elTable, 'uid':selectedValue};
                        }
-                       
+
                                // remove used items from a selector-box
-                       if (unique.selector && selectedValue) {
+                       if (unique.selector == 'select' && 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')
@@ -197,32 +242,32 @@ var inline = {
                                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
@@ -233,10 +278,10 @@ var inline = {
                                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);
@@ -263,7 +308,7 @@ var inline = {
                        }
                }
        },
-       
+
        createDragAndDropSorting: function(objectId) {
                Sortable.create(
                        objectId,
@@ -277,16 +322,16 @@ var inline = {
                        }
                );
        },
-       
+
        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();
@@ -294,30 +339,30 @@ var inline = {
                        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++) {
@@ -330,22 +375,23 @@ var inline = {
                                }
                                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))
+               if (!this.isBelowMax(objectPrefix)) {
                        this.hideElementsWithClassName('inlineNewButton',  this.parseFormElementName('full', objectPrefix, 0 , 1));
+               }
 
                if (TBE_EDITOR) TBE_EDITOR.fieldChanged_fName(objectName, formObj);
        },
-       
+
        memorizeRemoveRecord: function(objectName, removeUid) {
                var formObj = document.getElementsByName(objectName);
                if (formObj.length) {
@@ -360,18 +406,18 @@ var inline = {
                }
                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) {
+                       if (unique.selector == 'select') {
                                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) {
@@ -389,60 +435,66 @@ var inline = {
                        }
                }
        },
-       
+
        revertUnique: function(objectPrefix, elName, recordUid) {
                var unique = this.data.unique[objectPrefix];
-               var fieldObj = document.getElementsByName(elName+'['+unique.field+']');
+               var fieldObj = elName ? document.getElementsByName(elName+'['+unique.field+']') : null;
 
-               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.type == 'select') {
+                       if (fieldObj && fieldObj.length) {
+                               delete(this.data.unique[objectPrefix].used[recordUid])
+                               
+                               if (unique.selector == 'select') {
+                                       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);
+
+                               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);
+                                               }
                                        }
                                }
                        }
+               } else if (unique.type == 'groupdb') {
+                       // alert(objectPrefix+'/'+recordUid);
+                       delete(this.data.unique[objectPrefix].used[recordUid])
                }
        },
-       
+
        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 i, j, inlineRecords, records, childObjectId, childTable;
                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);
 
@@ -455,8 +507,20 @@ var inline = {
                        new Effect.Fade(objectId+'_div');
                }
 
-                       // remove from TBE_EDITOR (required fields, required range, etc.):
+                       // Remove from TBE_EDITOR (required fields, required range, etc.):
                if (TBE_EDITOR && TBE_EDITOR.removeElement) {
+                       inlineRecords = document.getElementsByClassName('inlineRecord', objectId+'_div');
+                               // Remove nested child records from TBE_EDITOR required/range checks:
+                       for (i=inlineRecords.length-1; i>=0; i--) {
+                               if (inlineRecords[i].value.length) {
+                                       records = inlineRecords[i].value.split(',');
+                                       childObjectId = this.data.map[inlineRecords[i].name];
+                                       childTable = this.data.config[childObjectId].table;
+                                       for (j=records.length-1; j>=0; j--) {
+                                               TBE_EDITOR.removeElement(this.prependFormFieldNames+'['+childTable+']['+records[j]+']');
+                                       }
+                               }
+                       }
                        TBE_EDITOR.removeElement(this.prependFormFieldNames+shortName);
                }
 
@@ -469,35 +533,35 @@ var inline = {
                        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('][');
@@ -509,7 +573,7 @@ var inline = {
                        for (var i=0; i<-rightCount; i++) idParts.shift();
                        elParts = idParts;
                }
-               
+
                if (wrap == 'full') {
                        elReturn = this.prependFormFieldNames+'['+elParts.join('][')+']';
                } else if (wrap == 'parts') {
@@ -520,7 +584,7 @@ var inline = {
 
                return elReturn;
        },
-       
+
        handleChangedField: function(formField, objectId) {
                var formObj;
                if (typeof formField == 'object') {
@@ -529,7 +593,7 @@ var inline = {
                        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;
@@ -538,7 +602,7 @@ var inline = {
                }
                return true;
        },
-       
+
        arrayAssocCount: function(object) {
                var count = 0;
                if (typeof object.length != 'undefined') {
@@ -548,7 +612,7 @@ var inline = {
                }
                return count;
        },
-       
+
        isBelowMax: function(objectPrefix) {
                var isBelowMax = true;
                var objectName = this.prependFormFieldNames+this.parseFormElementName('parts', objectPrefix, 3, 1);
@@ -564,18 +628,18 @@ var inline = {
                }
                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);
@@ -585,7 +649,7 @@ var inline = {
                        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
@@ -595,15 +659,15 @@ var inline = {
                        // 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++) {
@@ -613,14 +677,14 @@ var inline = {
                                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);
        },
-       
+
        isNewRecord: function(objectId) {
                return $(objectId+'_div') && $(objectId+'_div').hasClassName('inlineIsNewRecord')
                        ? true
index 96b81d3..10de7d0 100755 (executable)
@@ -706,10 +706,14 @@ class browse_links {
         * Example value: "data[pages][39][bodytext]|||tt_content|" or "data[tt_content][NEW3fba56fde763d][image]|||gif,jpg,jpeg,tif,bmp,pcx,tga,png,pdf,ai|"
         *
         * Values:
-        * 0: form field name reference
+        * 0: form field name reference, eg. "data[tt_content][123][image]"
         * 1: old/unused?
         * 2: old/unused?
         * 3: allowed types. Eg. "tt_content" or "gif,jpg,jpeg,tif,bmp,pcx,tga,png,pdf,ai"
+        * 4: IRRE uniqueness: target level object-id to perform actions/checks on, eg. "data[79][tt_address][1][<field>][<foreign_table>]"
+        * 5: IRRE uniqueness: name of function in opener window that checks if element is already used, eg. "inline.checkUniqueElement"
+        * 6: IRRE uniqueness: name of function in opener window that performs some additional(!) action, eg. "inline.setUniqueElement"
+        * 7: IRRE uniqueness: name of function in opener window that performs action instead of using addElement/insertElement, eg. "inline.importElement"
         *
         * $pArr = explode('|',$this->bparams);
         * $formFieldName = $pArr[0];
@@ -946,9 +950,70 @@ class browse_links {
                ';
 
 
-                       // This is JavaScript especially for the TBE Element Browser!
+               /**
+                * Splits parts of $this->bparams
+                * @see $bparams
+                */
                $pArr = explode('|',$this->bparams);
+
+                       // This is JavaScript especially for the TBE Element Browser!
                $formFieldName = 'data['.$pArr[0].']['.$pArr[1].']['.$pArr[2].']';
+               
+                       // insertElement - Call check function (e.g. for uniqueness handling):
+               if ($pArr[4] && $pArr[5]) {
+                       $JScodeCheck = '
+                                       // Call a check function in the opener window (e.g. for uniqueness handling):
+                               if (parent.window.opener) {
+                                       var res = parent.window.opener.'.$pArr[5].'("'.addslashes($pArr[4]).'",table,uid,type);
+                                       if (!res.passed) {
+                                               if (res.message) alert(res.message);
+                                               performAction = false;
+                                       }
+                               } else {
+                                       alert("Error - reference to main window is not set properly!");
+                                       parent.close();
+                               }
+                       ';
+               }
+                       // insertElement - Call helper function:
+               if ($pArr[4] && $pArr[6]) {
+                       $JScodeHelper = '
+                                               // Call helper function to manage data in the opener window:
+                                       if (parent.window.opener) {
+                                               parent.window.opener.'.$pArr[6].'("'.addslashes($pArr[4]).'",table,uid,type,"'.addslashes($pArr[0]).'");
+                                       } else {
+                                               alert("Error - reference to main window is not set properly!");
+                                               parent.close();
+                                       }
+                       ';
+               }
+                       // insertElement - perform action commands:
+               if ($pArr[4] && $pArr[7]) {
+                               // Call user defined action function:
+                       $JScodeAction = '
+                                       if (parent.window.opener) {
+                                               parent.window.opener.'.$pArr[7].'("'.addslashes($pArr[4]).'",table,uid,type);
+                                               focusOpenerAndClose(close);
+                                       } else {
+                                               alert("Error - reference to main window is not set properly!");
+                                               parent.close();
+                                       }
+                       ';
+               } else if ($pArr[0] && !$pArr[1] && !$pArr[2]) {
+                       $JScodeAction = '
+                                       addElement(filename,table+"_"+uid,fp,close);
+                       ';
+               } else {
+                       $JScodeAction = '
+                                       if (setReferences()) {
+                                               parent.window.opener.group_change("add","'.$pArr[0].'","'.$pArr[1].'","'.$pArr[2].'",elRef,targetDoc);
+                                       } else {
+                                               alert("Error - reference to main window is not set properly!");
+                                       }
+                                       focusOpenerAndClose(close);
+                       ';
+               }
+               
                $JScode.='
                        var elRef="";
                        var targetDoc="";
@@ -974,33 +1039,29 @@ class browse_links {
                                }
                        }
                        function insertElement(table, uid, type, filename,fp,filetype,imagefile,action, close)  {       //
-                               if (1=='.($pArr[0]&&!$pArr[1]&&!$pArr[2] ? 1 : 0).')    {
-                                       addElement(filename,table+"_"+uid,fp,close);
-                               } else {
-                                       if (setReferences())    {
-                                               parent.window.opener.group_change("add","'.$pArr[0].'","'.$pArr[1].'","'.$pArr[2].'",elRef,targetDoc);
-                                       } else {
-                                               alert("Error - reference to main window is not set properly!");
-                                       }
-                                       if (close)      {
-                                               parent.window.opener.focus();
-                                               parent.close();
-                                       }
+                               var performAction = true;
+                               '.$JScodeCheck.'
+                                       // Call performing function and finish this action:
+                               if (performAction) {
+                                               '.$JScodeHelper.$JScodeAction.'
                                }
                                return false;
                        }
                        function addElement(elName,elValue,altElValue,close)    {       //
                                if (parent.window.opener && parent.window.opener.setFormValueFromBrowseWin)     {
                                        parent.window.opener.setFormValueFromBrowseWin("'.$pArr[0].'",altElValue?altElValue:elValue,elName);
-                                       if (close)      {
-                                               parent.window.opener.focus();
-                                               parent.close();
-                                       }
+                                       focusOpenerAndClose(close);
                                } else {
                                        alert("Error - reference to main window is not set properly!");
                                        parent.close();
                                }
                        }
+                       function focusOpenerAndClose(close)     {       //
+                               if (close)      {
+                                       parent.window.opener.focus();
+                                       parent.close();
+                               }
+                       }
                ';
 
                        // Finally, add the accumulated JavaScript to the template object:
@@ -1378,7 +1439,7 @@ class browse_links {
        function main_db()      {
 
                        // Starting content:
-               $content=$this->doc->startPage('TBE file selector');
+               $content=$this->doc->startPage('TBE record selector');
 
                        // Init variable:
                $pArr = explode('|',$this->bparams);