双向MM表对应同一个表的同一个字段.

* @package TYPO3
* @subpackage t3lib
*/
class ux_t3lib_loadDBGroup extends t3lib_loadDBGroup{
// External, static:
var $fromTC = 1; // Means that only uid and the label-field is returned
var $registerNonTableValues=0; // If set, values that are not ids in tables are normally discarded. By this options they will be preserved.
// Internal, dynamic:
var $tableArray=Array(); // Contains the table names as keys. The values are the id-values for each table. Should ONLY contain proper table names.
var $itemArray=Array(); // Contains items in an numeric array (table/id for each). Tablenames here might be "_NO_TABLE"
var $nonTableArray=array(); // Array for NON-table elements
var $additionalWhere=array();
var $checkIfDeleted = 1; // deleted-column is added to additionalWhere... if this is set...
var $dbPaths=Array();
var $firstTable = ''; // Will contain the first table name in the $tablelist (for positive ids)
var $secondTable = ''; // Will contain the second table name in the $tablelist (for negative ids)
// private
var $MM_is_foreign = 0; // boolean - if 1, uid_local and uid_foreign are switched, and the current table is inserted as tablename - this means you display a foreign relation "from the opposite side"
var $MM_oppositeField = ''; // field name at the "local" side of the MM relation
var $MM_oppositeTable = ''; // only set if MM_is_foreign is set
var $MM_oppositeFieldConf = ''; // only set if MM_is_foreign is set
var $MM_isMultiTableRelationship = 0; // is empty by default; if MM_is_foreign is set and there is more than one table allowed (on the "local" side), then it contains the first table (as a fallback)
var $currentTable; // current table => Only needed for reverse relations
var $undeleteRecord; // if a record should be undeleted (so do not use the $useDeleteClause on t3lib_BEfunc)
var $MM_ownField = false; //add by bobo
var $MM_match_fields = array(); // array of fields value pairs that should match while SELECT and will be written into MM table if $MM_insert_fields is not set
var $MM_insert_fields = array(); // array of fields and value pairs used for insert in MM table
var $MM_table_where = ''; // extra MM table where
/**
* Initialization of the class.
*
* @param string List of group/select items
* @param string Comma list of tables, first table takes priority if no table is set for an entry in the list.
* @param string Name of a MM table.
* @param integer Local UID for MM lookup
* @param string current table name
* @param integer TCA configuration for current field
* @return void
*/
function start($itemlist, $tablelist, $MMtable='', $MMuid=0, $currentTable='', $conf=array()) {
// SECTION: MM reverse relations
$this->MM_is_foreign = ($conf['MM_opposite_field']?1:0);
$this->MM_oppositeField = $conf['MM_opposite_field'];
$this->MM_table_where = $conf['MM_table_where'];
$this->MM_hasUidField = $conf['MM_hasUidField'];
$this->MM_match_fields = is_array($conf['MM_match_fields']) ? $conf['MM_match_fields'] : array();
$this->MM_insert_fields = is_array($conf['MM_insert_fields']) ? $conf['MM_insert_fields'] : $this->MM_match_fields;
$this->MM_ownField = $conf['MM_own_field']; //add by bobo
$this->currentTable = $currentTable;
if ($this->MM_is_foreign) {
$tmp = ($conf['type']==='group'?$conf['allowed']:$conf['foreign_table']);
// normally, $conf['allowed'] can contain a list of tables, but as we are looking at a MM relation from the foreign side, it only makes sense to allow one one table in $conf['allowed']
$tmp = t3lib_div::trimExplode(',', $tmp);
$this->MM_oppositeTable = $tmp[0];
unset($tmp);
// only add the current table name if there is more than one allowed field
t3lib_div::loadTCA($this->MM_oppositeTable); // We must be sure this has been done at least once before accessing the "columns" part of TCA for a table.
$this->MM_oppositeFieldConf = $GLOBALS['TCA'][$this->MM_oppositeTable]['columns'][$this->MM_oppositeField]['config'];
if ($this->MM_oppositeFieldConf['allowed']) {
$oppositeFieldConf_allowed = explode(',', $this->MM_oppositeFieldConf['allowed']);
if (count($oppositeFieldConf_allowed) > 1) {
$this->MM_isMultiTableRelationship = $oppositeFieldConf_allowed[0];
}
}
}
// SECTION: normal MM relations
// If the table list is "*" then all tables are used in the list:
if (!strcmp(trim($tablelist),'*')) {
$tablelist = implode(',',array_keys($GLOBALS['TCA']));
}
// The tables are traversed and internal arrays are initialized:
$tempTableArray = t3lib_div::trimExplode(',',$tablelist,1);
foreach($tempTableArray as $key => $val) {
$tName = trim($val);
$this->tableArray[$tName] = Array();
if ($this->checkIfDeleted && $GLOBALS['TCA'][$tName]['ctrl']['delete']) {
$fieldN = $tName.'.'.$GLOBALS['TCA'][$tName]['ctrl']['delete'];
$this->additionalWhere[$tName].=' AND '.$fieldN.'=0';
}
}
if (is_array($this->tableArray)) {
reset($this->tableArray);
} else {return 'No tables!';}
// Set first and second tables:
$this->firstTable = key($this->tableArray); // Is the first table
next($this->tableArray);
$this->secondTable = key($this->tableArray); // If the second table is set and the ID number is less than zero (later) then the record is regarded to come from the second table...
// Now, populate the internal itemArray and tableArray arrays:
if ($MMtable) { // If MM, then call this function to do that:
if ($MMuid) {
$this->readMM($MMtable, $MMuid);
} else { // Revert to readList() for new records in order to load possible default values from $itemlist
$this->readList($itemlist);
}
} elseif ($MMuid && $conf['foreign_field']) {
// If not MM but foreign_field, the read the records by the foreign_field
$this->readForeignField($MMuid, $conf);
} else {
// If not MM, then explode the itemlist by "," and traverse the list:
$this->readList($itemlist);
// do automatic default_sortby, if any
if ($conf['foreign_default_sortby']) {
$this->sortList($conf['foreign_default_sortby']);
}
}
}
/**
* Writes the internal itemArray to MM table:
*
* @param string MM table name
* @param integer Local UID
* @param boolean If set, then table names will always be written.
* @return void
*/
function writeMM($MM_tableName,$uid,$prependTableName=0) {
if ($this->MM_is_foreign) { // in case of a reverse relation
$uidLocal_field = 'uid_foreign';
$uidForeign_field = 'uid_local';
$sorting_field = 'sorting_foreign';
} else { // default
$uidLocal_field = 'uid_local';
$uidForeign_field = 'uid_foreign';
$sorting_field = 'sorting';
}
// If there are tables...
$tableC = count($this->tableArray);
if ($tableC) {
$prep = ($tableC>1||$prependTableName||$this->MM_isMultiTableRelationship) ? 1 : 0; // boolean: does the field "tablename" need to be filled?
$c=0;
$additionalWhere_tablenames = '';
if ($this->MM_is_foreign && $prep) {
$additionalWhere_tablenames = ' AND tablenames="'.$this->currentTable.'"';
}
$additionalWhere = '';
// add WHERE clause if configured
if ($this->MM_table_where) {
$additionalWhere.= "\n".str_replace('###THIS_UID###', intval($uid), $this->MM_table_where);
}
// Select, update or delete only those relations that match the configured fields
foreach ($this->MM_match_fields as $field => $value) {
$additionalWhere.= ' AND '.$field.'='.$GLOBALS['TYPO3_DB']->fullQuoteStr($value, $MM_tableName);
}
$res = $GLOBALS['TYPO3_DB']->exec_SELECTquery(
$uidForeign_field.($prep?', tablenames':'').($this->MM_hasUidField?', uid':''),
$MM_tableName,
$uidLocal_field.'='.$uid.$additionalWhere_tablenames.$additionalWhere,
'',
$sorting_field
);
$oldMMs = array();
$oldMMs_inclUid = array(); // This array is similar to $oldMMs but also holds the uid of the MM-records, if any (configured by MM_hasUidField). If the UID is present it will be used to update sorting and delete MM-records. This is necessary if the "multiple" feature is used for the MM relations. $oldMMs is still needed for the in_array() search used to look if an item from $this->itemArray is in $oldMMs
while($row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res)) {
if (!$this->MM_is_foreign && $prep) {
$oldMMs[] = array($row['tablenames'], $row[$uidForeign_field]);
} else {
$oldMMs[] = $row[$uidForeign_field];
}
$oldMMs_inclUid[] = array($row['tablenames'], $row[$uidForeign_field], $row['uid']);
}
// For each item, insert it:
foreach($this->itemArray as $val) {
$c++;
if ($prep || $val['table']=='_NO_TABLE') {
if ($this->MM_is_foreign) { // insert current table if needed
$tablename = $this->currentTable;
} else {
$tablename = $val['table'];
}
} else {
$tablename = '';
}
if(!$this->MM_is_foreign && $prep) {
$item = array($val['table'], $val['id']);
} else {
$item = $val['id'];
}
if (in_array($item, $oldMMs)) {
$oldMMs_index = array_search($item, $oldMMs);
$whereClause = $uidLocal_field.'='.$uid.' AND '.$uidForeign_field.'='.$val['id'].
($this->MM_hasUidField ? ' AND uid='.intval($oldMMs_inclUid[$oldMMs_index][2]) : ''); // In principle, selecting on the UID is all we need to do if a uid field is available since that is unique! But as long as it "doesn't hurt" we just add it to the where clause. It should all match up.
if ($tablename) {
$whereClause .= ' AND tablenames="'.$tablename.'"';
}
$GLOBALS['TYPO3_DB']->exec_UPDATEquery($MM_tableName, $whereClause.$additionalWhere, array($sorting_field => $c));
unset($oldMMs[$oldMMs_index]); // remove the item from the $oldMMs array so after this foreach loop only the ones that need to be deleted are in there.
unset($oldMMs_inclUid[$oldMMs_index]); // remove the item from the $oldMMs array so after this foreach loop only the ones that need to be deleted are in there.
} else {
$insertFields = $this->MM_insert_fields;
$insertFields[$uidLocal_field] = $uid;
$insertFields[$uidForeign_field] = $val['id'];
$insertFields[$sorting_field] = $c;
if($tablename) {
$insertFields['tablenames'] = $tablename;
}
$GLOBALS['TYPO3_DB']->exec_INSERTquery($MM_tableName, $insertFields);
//add by bobo
if ($this->MM_ownField) {
$c++;
$insertFields[$uidLocal_field] = $val['id'];
$insertFields[$uidForeign_field] = $uid;
$insertFields[$sorting_field] = $c;
$GLOBALS['TYPO3_DB']->exec_INSERTquery($MM_tableName, $insertFields);
}
if ($this->MM_is_foreign) {
$this->updateRefIndex($val['table'], $val['id']);
}
}
}
// Delete all not-used relations:
if(is_array($oldMMs) && count($oldMMs) > 0) {
$removeClauses = array();
$updateRefIndex_records = array();
foreach($oldMMs as $oldMM_key => $mmItem) {
if ($this->MM_hasUidField) { // If UID field is present, of course we need only use that for deleting...:
$removeClauses[] = 'uid='.intval($oldMMs_inclUid[$oldMM_key][2]);
$elDelete = $oldMMs_inclUid[$oldMM_key];
} else {
if(is_array($mmItem)) {
$removeClauses[] = 'tablenames="'.$mmItem[0].'" AND '.$uidForeign_field.'='.$mmItem[1];
} else {
$removeClauses[] = $uidForeign_field.'='.$mmItem;
}
}
if ($this->MM_is_foreign) {
if(is_array($mmItem)) {
$updateRefIndex_records[] = array($mmItem[0],$mmItem[1]);
} else {
$updateRefIndex_records[] = array($this->firstTable,$mmItem);
}
}
}
$deleteAddWhere = ' AND ('.implode(' OR ', $removeClauses).')';
$GLOBALS['TYPO3_DB']->exec_DELETEquery($MM_tableName, $uidLocal_field.'='.intval($uid).$deleteAddWhere.$additionalWhere_tablenames.$additionalWhere);
//add by bobo
if ($this->MM_ownField) {
$removeClauses = str_replace($uidForeign_field,$uidLocal_field,$removeClauses);
$deleteAddWhere = '('.implode(' OR ', $removeClauses).')';
$GLOBALS['TYPO3_DB']->exec_DELETEquery($MM_tableName, $deleteAddWhere.' and '.$uidForeign_field.'='.intval($uid).$additionalWhere_tablenames.$additionalWhere);
}
// Update ref index:
foreach($updateRefIndex_records as $pair) {
$this->updateRefIndex($pair[0],$pair[1]);
}
}
// Update ref index; In tcemain it is not certain that this will happen because if only the MM field is changed the record itself is not updated and so the ref-index is not either. This could also have been fixed in updateDB in tcemain, however I decided to do it here ...
$this->updateRefIndex($this->currentTable,$uid);
}
}
}
?>