Index: t3lib/class.t3lib_tceforms.php
===================================================================
--- t3lib/class.t3lib_tceforms.php	(Revision 3598)
+++ t3lib/class.t3lib_tceforms.php	(Arbeitskopie)
@@ -356,7 +356,7 @@
 			'check' => array('cols', 'showIfRTE'),
 			'select' => array('size', 'autoSizeMax', 'maxitems', 'minitems'),
 			'group' => array('size', 'autoSizeMax', 'max_size', 'show_thumbs', 'maxitems', 'minitems', 'disable_controls'),
-			'inline' => array('appearance', 'behaviour', 'foreign_label', 'foreign_selector', 'foreign_unique', 'maxitems', 'minitems', 'size', 'autoSizeMax', 'symmetric_label'),
+			'inline' => array('appearance', 'behaviour', 'foreign_label', 'foreign_selector', 'foreign_unique', 'maxitems', 'minitems', 'size', 'autoSizeMax', 'symmetric_label', 'symmetric_selector'),
 		);
 
 			// Create instance of t3lib_TCEforms_inline only if this a non-IRRE-AJAX call:
Index: t3lib/class.t3lib_tceforms_inline.php
===================================================================
--- t3lib/class.t3lib_tceforms_inline.php	(Revision 3598)
+++ t3lib/class.t3lib_tceforms_inline.php	(Arbeitskopie)
@@ -114,6 +114,7 @@
 	var $prependCmdFieldNames;				// reference to $this->fObj->prependCmdFieldNames
 
 	protected $hookObjects = array();			// array containing instances of hook classes called once for IRRE objects
+	protected $inCombinationMode = false;		// indicates whether already a combination rendering is active (no nested combination views allowed!)
 
 
 	/**
@@ -249,7 +250,7 @@
 			$selConfig = $this->getPossibleRecordsSelectorConfig($config, $config['foreign_unique']);
 				// Get the used unique ids:
 			$uniqueIds = $this->getUniqueIds($relatedRecords['records'], $config, $selConfig['type']=='groupdb');
-			$possibleRecords = $this->getPossibleRecords($table,$field,$row,$config,'foreign_unique');
+			$possibleRecords = $this->getPossibleRecords($table, $field, $row, $config);
 			$uniqueMax = $config['appearance']['useCombination'] || $possibleRecords === false ? -1	: count($possibleRecords);
 			$this->inlineData['unique'][$nameObject.'['.$foreign_table.']'] = array(
 				'max' => $uniqueMax,
@@ -259,6 +260,16 @@
 				'elTable' => $selConfig['table'], // element/record table (one step down in hierarchy)
 				'field' => $config['foreign_unique'],
 				'selector' => $selConfig['selector'],
+				'selector' => array(
+					'foreign' => array(
+						'type' => '',
+						'field' => '',
+					),
+					'symmetric' => array(
+						'type' => '',
+						'field' => '',
+					),
+				),
 				'possible' => $this->getPossibleRecordsFlat($possibleRecords),
 			);
 		}
@@ -267,7 +278,7 @@
 		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);
+				$possibleRecords = $this->getPossibleRecords($table, $field, $row, $config);
 				$uniqueIds = array();
 			}
 			$selectorBox = $this->renderPossibleRecordsSelector($possibleRecords,$config,$uniqueIds);
@@ -356,7 +367,7 @@
 	function renderForeignRecord($parentUid, $rec, $config = array()) {
 		$foreign_table = $config['foreign_table'];
 		$foreign_field = $config['foreign_field'];
-		$foreign_selector = $config['foreign_selector'];
+		$selectorField = $this->getSelectorField($rec, $config, $parentUid);
 
 			// Register default localization content:
 		$parent = $this->getStructureLevel(-1);
@@ -372,8 +383,8 @@
 			// Set this variable if the record is virtual and only show with header and not editable fields:
 		$isVirtualRecord = (isset($rec['__virtual']) && $rec['__virtual']);
 			// If there is a selector field, normalize it:
-		if ($foreign_selector) {
-			$rec[$foreign_selector] = $this->normalizeUid($rec[$foreign_selector]);
+		if ($selectorField) {
+			$rec[$selectorField] = $this->normalizeUid($rec[$selectorField]);
 		}
 
 		if (!$this->checkAccess(($isNewRecord ? 'new' : 'edit'), $foreign_table, $rec['uid'])) {
@@ -474,8 +485,7 @@
 
 			// Pre-Processing:
 		$isOnSymmetricSide = t3lib_loadDBGroup::isOnSymmetricSide($parentUid, $config, $rec);
-		$hasForeignLabel = !$isOnSymmetricSide && $config['foreign_label'] ? true : false;
-		$hasSymmetricLabel = $isOnSymmetricSide && $config['symmetric_label'] ? true : false;
+		$specificLabelField = $this->getLabelField($rec, $config, $parentUid);
 			// Get the record title/label for a record:
 			// render using a self-defined user function
 		if ($GLOBALS['TCA'][$foreign_table]['ctrl']['label_userFunc']) {
@@ -493,16 +503,15 @@
 			t3lib_div::callUserFunction($GLOBALS['TCA'][$foreign_table]['ctrl']['label_userFunc'], $params, $null);
 			$recTitle = $params['title'];
 			// render the special alternative title
-		} elseif ($hasForeignLabel || $hasSymmetricLabel) {
-			$titleCol = $hasForeignLabel ? $config['foreign_label'] : $config['symmetric_label'];
-			$foreignConfig = $this->getPossibleRecordsSelectorConfig($config, $titleCol);
+		} elseif ($specificLabelField) {
+			$foreignConfig = $this->getPossibleRecordsSelectorConfig($config, $specificLabelField);
 				// Render title for everything else than group/db:
 			if ($foreignConfig['type'] != 'groupdb') {
-				$recTitle = t3lib_BEfunc::getProcessedValueExtra($foreign_table, $titleCol, $rec[$titleCol], 0, 0, false);
+				$recTitle = t3lib_BEfunc::getProcessedValueExtra($foreign_table, $specificLabelField, $rec[$specificLabelField], 0, 0, false);
 				// Render title for group/db:
 			} else {
 					// $recTitle could be something like: "tx_table_123|...",
-				$valueParts = t3lib_div::trimExplode('|', $rec[$titleCol]);
+				$valueParts = t3lib_div::trimExplode('|', $rec[$specificLabelField]);
 				$itemParts = t3lib_div::revExplode('_', $valueParts[0], 2);
 				$recTemp = t3lib_befunc::getRecordWSOL($itemParts[0], $itemParts[1]);
 				$recTitle = t3lib_BEfunc::getRecordTitle($itemParts[0], $recTemp, true);
@@ -707,18 +716,20 @@
 	 */
 	function renderCombinationTable(&$rec, $appendFormFieldNames, $config = array()) {
 		$foreign_table = $config['foreign_table'];
-		$foreign_selector = $config['foreign_selector'];
+		$selectorField = $this->getSelectorField($rec, $config);
 
-		if ($foreign_selector && $config['appearance']['useCombination']) {
-			$comboConfig = $GLOBALS['TCA'][$foreign_table]['columns'][$foreign_selector]['config'];
+		if (!$this->inCombinationMode && $selectorField && $config['appearance']['useCombination']) {
+			$this->inCombinationMode = true;
+
+			$comboConfig = $GLOBALS['TCA'][$foreign_table]['columns'][$selectorField]['config'];
 			$comboRecord = array();
 
 				// If record does already exist, load it:
-			if ($rec[$foreign_selector] && t3lib_div::testInt($rec[$foreign_selector])) {
+			if ($rec[$selectorField] && t3lib_div::testInt($rec[$selectorField])) {
 				$comboRecord = $this->getRecord(
 					$this->inlineFirstPid,
 					$comboConfig['foreign_table'],
-					$rec[$foreign_selector]
+					$rec[$selectorField]
 				);
 				$isNewRecord = false;
 				// It is a new record, create a new record virtually:
@@ -741,10 +752,12 @@
 			}
 
 				// 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.']';
+			if ($isNewRecord || $this->getBehaviourSetting($config, 'uniqueness')) {
+				$parentFormFieldName = $this->prependFormFieldNames.$appendFormFieldNames.'['.$selectorField.']';
 				$out .= '<input type="hidden" name="'.$parentFormFieldName.'" value="'.$comboRecord['uid'].'" />';
 			}
+
+			$this->inCombinationMode = false;
 		}
 
 		return $out;
@@ -1402,6 +1415,10 @@
 		foreach($itemArray as $uid) {
 				// Get the records for this uid using t3lib_transferdata:
 			if ($record = $this->getRecord($pid, $table, $uid)) {
+					// If this record was already processed in the current structure, show it virtually:
+				if ($this->isPartOfStructure($table, $record['uid'])) {
+					$record['__virtual'] = true;
+				}
 				$records[$uid] = $record;
 			}
 		}
@@ -1484,6 +1501,7 @@
 			// Field configuration from TCA:
 		$foreign_table = $conf['foreign_table'];
 		$foreign_check = $conf[$checkForConfField];
+		echo $foreign_check;
 
 		$foreignConfig = $this->getPossibleRecordsSelectorConfig($conf, $foreign_check);
 		$PA = $foreignConfig['PA'];
@@ -1783,7 +1801,27 @@
 		}
 	}
 
+	/**
+	 * Determines whether a set of table-uid or table-uid-field is part of the current structure stack.
+	 * This can be used to figure out if a record was already processed and thus can determine endless recursions.
+	 *
+	 * @param	string		$table: name of the table to search for
+	 * @param	mixed		$uid: uid of the record to search for (can be integer or string, like 'NEW...')
+	 * @param	string		$field: (optional) name of the field to search for
+	 * @return	boolean		Whether the set of table-uid or table-uid-field is present in the current structure stack
+	 */
+	protected function isPartOfStructure($table, $uid, $field='') {
+		$structureDepth = $this->getStructureDepth();
+		for ($i = 0; $i < $structureDepth; $i++) {
+			$level = $this->getStructureLevel($i);
+			if ($level['table']==$table && $level['uid']==$uid && (!$field || $level['field']==$field)) {
+				return true;
+			}
+		}
+		return false;
+	}
 
+
 	/*******************************************************
 	 *
 	 * Helper functions
@@ -1821,6 +1859,14 @@
 		} elseif (!isset($config['appearance']['levelLinksPosition']) || !in_array($config['appearance']['levelLinksPosition'], array('top', 'bottom', 'both', 'none'))) {
 			$config['appearance']['levelLinksPosition'] = 'top';
 		}
+			// Init behaviour if not set:
+		if (!isset($config['behaviour']) || !is_array($config['behaviour'])) {
+			$config['behaviour'] = array();
+		}
+			// @deprecated: behaviour.uniqueness should be used instead of foreign_unique since TYPO3 4.2
+		if (isset($config['foreign_unique']) && isset($config['foreign_selector']) && $config['foreign_unique'] && $config['foreign_unique']==$config['foreign_selector'] && !isset($config['behaviour']['uniqueness'])) {
+			$config['behaviour']['uniqueness'] = true;
+		}
 
 		return true;
 	}
@@ -2197,7 +2243,10 @@
 								'%OR' => array(
 									'%AND' => array(
 										'appearance' => array('useCombination' => true),
-										'foreign_selector' => $field,
+										'%OR' => array(
+											'foreign_selector' => $field,
+											'symmetric_selector' => $field,
+										),
 									),
 									'MM' => $config['MM']
 								),
@@ -2361,6 +2410,98 @@
 		$result.= '>'.$text.'</a>';
 		return $result;
 	}
+
+	/**
+	 * Returns the correct selector field (foreign_selector or symmetric_selector) which depends
+	 * on the disposal of the relationship in general (asymmetric or symmetric). If symmetric
+	 * relations are used, it depends furthermore on the side the parent record is currently accessed.
+	 * Wrapper for getFieldBySymmetricSide().
+	 *
+	 * @param	array		$childRecord: Child record (in asymmetric/symmetric context normally from intermediate table)
+	 * @param	array		$parentConfig: The field configuration of the parent
+	 * @param	mixed		$parentUid: The uid of the parent (can also be NEW... if it's a new record)
+	 * @return	mixed		Returns the selector field (string) or false (boolean) if this couldn't be determined
+	 * @see		getFieldBySymmetricSide()
+	 */
+	protected function getSelectorField(array $childRecord, array $parentConfig, $parentUid=0) {
+		return $this->getFieldBySymmetricSide('selector', $childRecord, $parentConfig, $parentUid);
+	}
+
+	/**
+	 * Returns the correct label field (foreign_label or symmetric_label) which depends
+	 * on the disposal of the relationship in general (asymmetric or symmetric). If symmetric
+	 * relations are used, it depends furthermore on the side the parent record is currently accessed.
+	 * Wrapper for getFieldBySymmetricSide().
+	 *
+	 * @param	array		$childRecord: Child record (in asymmetric/symmetric context normally from intermediate table)
+	 * @param	array		$parentConfig: The field configuration of the parent
+	 * @param	mixed		$parentUid: The uid of the parent (can also be NEW... if it's a new record)
+	 * @return	mixed		Returns the selector field (string) or false (boolean) if this couldn't be determined
+	 * @see		getFieldBySymmetricSide()
+	 */
+	protected function getLabelField(array $childRecord, array $parentConfig, $parentUid=0) {
+		return $this->getFieldBySymmetricSide('label', $childRecord, $parentConfig, $parentUid);
+	}
+
+	/**
+	 * Returns the correct unique field (foreign_unique or symmetric_unique) which depends
+	 * on the disposal of the relationship in general (asymmetric or symmetric). If symmetric
+	 * relations are used, it depends furthermore on the side the parent record is currently accessed.
+	 * Wrapper for getFieldBySymmetricSide().
+	 *
+	 * @param	array		$childRecord: Child record (in asymmetric/symmetric context normally from intermediate table)
+	 * @param	array		$parentConfig: The field configuration of the parent
+	 * @param	mixed		$parentUid: The uid of the parent (can also be NEW... if it's a new record)
+	 * @return	mixed		Returns the selector field (string) or false (boolean) if this couldn't be determined
+	 * @see		getFieldBySymmetricSide()
+	 */
+	protected function getUniqueField(array $childRecord, array $parentConfig, $parentUid=0) {
+		$uniqueField = false;
+		if (isset($parentConfig['behaviour']['uniqueness']) && $parentConfig['behaviour']['uniqueness']) {
+			$uniqueField = $this->getFieldBySymmetricSide('selector', $childRecord, $parentConfig, $parentUid);
+		}
+		return $uniqueField;
+	}
+
+	/**
+	 * Returns the a correct field (foreign_<configKeySuffix> or symmetric_<configKeySuffix>) which depends
+	 * on the disposal of the relationship in general (asymmetric or symmetric). If symmetric
+	 * relations are used, it depends furthermore on the side the parent record is currently accessed.
+	 *
+	 * @param	string		$configKeySuffix: The configuration key suffix to be used (e.g. 'field' for 'foreign_field' or 'symmetric_field')
+	 * @param	array		$childRecord: Child record (in asymmetric/symmetric context normally from intermediate table)
+	 * @param	array		$parentConfig: The field configuration of the parent
+	 * @param	mixed		$parentUid: The uid of the parent (can also be NEW... if it's a new record)
+	 * @return	mixed		Returns the selector field (string) or false (boolean) if this couldn't be determined
+	 */
+	protected function getFieldBySymmetricSide($configKeySuffix, array $childRecord, array $parentConfig, $parentUid=0) {
+		$field = false;
+		$configKeyForeign = 'foreign_'.$configKeySuffix;
+		$configKeySymmetric = 'symmetric_'.$configKeySuffix;
+
+			// Fetch the uid of the parent from current stack:
+		if (!$parentUid) {
+			$parent = $this->getStructureLevel(-1);
+			$parentUid = $parent['uid'];
+		}
+
+		$isOnSymmetricSide = t3lib_loadDBGroup::isOnSymmetricSide($parentUid, $parentConfig, $childRecord);
+			// Asymmetric disposal or symmetric disposal accessing from the non-symmetric side:
+		if (!$isOnSymmetricSide && isset($parentConfig[$configKeyForeign]) && $parentConfig[$configKeyForeign]) {
+			$field = $parentConfig[$configKeyForeign];
+			// Symmetric disposal and accessing from the symmetric side:
+		} elseif ($isOnSymmetricSide && isset($parentConfig[$configKeySymmetric]) && $parentConfig[$configKeySymmetric]) {
+			$field = $parentConfig[$configKeySymmetric];
+		}
+
+		return $field;
+	}
+
+	protected function getBehaviourSetting($config, $key) {
+		if (isset($config['behaviour'][$key])) {
+			return $config['behaviour'][$key];
+		}
+	}
 }
 
 
