Index: /Users/kasper/Sites/typo3/TYPO3core/t3lib/class.t3lib_loaddbgroup.php =================================================================== --- /Users/kasper/Sites/typo3/TYPO3core/t3lib/class.t3lib_loaddbgroup.php (revision 2992) +++ /Users/kasper/Sites/typo3/TYPO3core/t3lib/class.t3lib_loaddbgroup.php (working copy) @@ -62,6 +62,7 @@ +require_once (PATH_t3lib.'class.t3lib_refindex.php'); @@ -118,6 +119,7 @@ $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; @@ -130,6 +132,7 @@ 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']) { @@ -327,7 +330,7 @@ * @param boolean If set, then table names will always be written. * @return void */ - function writeMM($tableName,$uid,$prependTableName=0) { + function writeMM($MM_tableName,$uid,$prependTableName=0) { if ($this->MM_is_foreign) { // in case of a reverse relation $uidLocal_field = 'uid_foreign'; @@ -357,12 +360,19 @@ } // 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, $tableName); + $additionalWhere.= ' AND '.$field.'='.$GLOBALS['TYPO3_DB']->fullQuoteStr($value, $MM_tableName); } - $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery($uidForeign_field.($prep?', tablenames':''), $tableName, $uidLocal_field.'='.$uid.$additionalWhere_tablenames.$additionalWhere); + $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]); @@ -369,6 +379,7 @@ } else { $oldMMs[] = $row[$uidForeign_field]; } + $oldMMs_inclUid[] = array($row['tablenames'], $row[$uidForeign_field], $row['uid']); } // For each item, insert it: @@ -392,13 +403,17 @@ } if (in_array($item, $oldMMs)) { - unset($oldMMs[array_search($item, $oldMMs)]); // remove the item from the $oldMMs array so after this foreach loop only the ones that need to be deleted are in there. - - $whereClause = $uidLocal_field.'='.$uid.' AND '.$uidForeign_field.'='.$val['id']; + $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($tableName, $whereClause.$additionalWhere, array($sorting_field => $c)); + $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; @@ -409,7 +424,11 @@ $insertFields['tablenames'] = $tablename; } - $GLOBALS['TYPO3_DB']->exec_INSERTquery($tableName, $insertFields); + $GLOBALS['TYPO3_DB']->exec_INSERTquery($MM_tableName, $insertFields); + + if ($this->MM_is_foreign) { + $this->updateRefIndex($val['table'], $val['id']); + } } } @@ -416,16 +435,80 @@ // Delete all not-used relations: if(is_array($oldMMs) && count($oldMMs) > 0) { $removeClauses = array(); - foreach($oldMMs as $mmItem) { - if(is_array($mmItem)) { - $removeClauses[] = 'tablenames="'.$mmItem[0].'" AND '.$uidForeign_field.'='.$mmItem[1]; + $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 { - $removeClauses[] = $uidForeign_field.'='.$mmItem; + 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($tableName, $uidLocal_field.'='.intval($uid).$deleteAddWhere.$additionalWhere_tablenames.$additionalWhere); + $GLOBALS['TYPO3_DB']->exec_DELETEquery($MM_tableName, $uidLocal_field.'='.intval($uid).$deleteAddWhere.$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); + } + } + + /** + * Remaps MM table elements from one local uid to another + * Does NOT update the reference index for you, must be called subsequently to do that! + * + * @param string MM table name + * @param integer Local, current UID + * @param integer Local, new UID + * @param boolean If set, then table names will always be written. + * @return void + */ + function remapMM($MM_tableName,$uid,$newUid,$prependTableName=0) { + + if ($this->MM_is_foreign) { // in case of a reverse relation + $uidLocal_field = 'uid_foreign'; + } else { // default + $uidLocal_field = 'uid_local'; + } + + // 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); + } + + $GLOBALS['TYPO3_DB']->exec_UPDATEquery($MM_tableName, $uidLocal_field.'='.intval($uid).$additionalWhere_tablenames.$additionalWhere, array($uidLocal_field => $newUid)); } } Index: /Users/kasper/Sites/typo3/TYPO3core/t3lib/class.t3lib_tcemain.php =================================================================== --- /Users/kasper/Sites/typo3/TYPO3core/t3lib/class.t3lib_tcemain.php (revision 2996) +++ /Users/kasper/Sites/typo3/TYPO3core/t3lib/class.t3lib_tcemain.php (working copy) @@ -2059,7 +2059,7 @@ if ($status=='update') { $dbAnalysis->writeMM($tcaFieldConf['MM'],$id,$prep); } else { - $this->dbAnalysisStore[] = array($dbAnalysis,$tcaFieldConf['MM'],$id,$prep); // This will be traversed later to execute the actions + $this->dbAnalysisStore[] = array($dbAnalysis,$tcaFieldConf['MM'],$id,$prep,$currentTable); // This will be traversed later to execute the actions } $valueArray = $dbAnalysis->countItems(); } elseif ($type == 'inline') { @@ -2227,7 +2227,8 @@ $dsConf['TCEforms']['config'], $dataValues[$key][$vKey], $dataValues_current[$key][$vKey], - $uploadedFiles[$key][$vKey] + $uploadedFiles[$key][$vKey], + $structurePath.$key.'/'.$vKey.'/' ); } } else { // Default @@ -4314,6 +4315,9 @@ $curVersion['t3ver_swapmode'] = $swapVersion['t3ver_swapmode']; } + // Registering and swapping MM relations in current and swap records: + $this->version_remapMMForVersionSwap($table,$id,$swapWith); + // Execute swapping: $sqlErrors = array(); $GLOBALS['TYPO3_DB']->exec_UPDATEquery($table,'uid='.intval($id),$swapVersion); @@ -4504,9 +4508,147 @@ $swapVersion[$field] = $tempValue; } } + + /** + * Swaps MM-relations for current/swap record, see version_swap() + * + * @param string Table for the two input records + * @param integer Current record (about to go offline) + * @param integer Swap record (about to go online) + * @return void + * @see version_swap() + */ + function version_remapMMForVersionSwap($table,$id,$swapWith) { + global $TCA; + + // Actually, selecting the records fully is only need if flexforms are found inside... This could be optimized ... + $currentRec = t3lib_BEfunc::getRecord($table,$id); + $swapRec = t3lib_BEfunc::getRecord($table,$swapWith); + + $this->version_remapMMForVersionSwap_reg = array(); + + foreach($TCA[$table]['columns'] as $field => $fConf) { + $conf = $fConf['config']; + + if ($this->isReferenceField($conf)) { + $allowedTables = $conf['type']=='group' ? $conf['allowed'] : $conf['foreign_table'].','.$conf['neg_foreign_table']; + $prependName = $conf['type']=='group' ? $conf['prepend_tname'] : $conf['neg_foreign_table']; + if ($conf['MM']) { + + $dbAnalysis = t3lib_div::makeInstance('t3lib_loadDBGroup'); + /* @var $dbAnalysis t3lib_loadDBGroup */ + $dbAnalysis->start('', $allowedTables, $conf['MM'], $id, $table, $conf); + if (count($dbAnalysis->getValueArray($prependName))) { + $this->version_remapMMForVersionSwap_reg[$id][$field] = array($dbAnalysis, $conf['MM'], $prependName); + } + + $dbAnalysis = t3lib_div::makeInstance('t3lib_loadDBGroup'); + /* @var $dbAnalysis t3lib_loadDBGroup */ + $dbAnalysis->start('', $allowedTables, $conf['MM'], $swapWith, $table, $conf); + if (count($dbAnalysis->getValueArray($prependName))) { + $this->version_remapMMForVersionSwap_reg[$swapWith][$field] = array($dbAnalysis, $conf['MM'], $prependName); + } + } + } elseif($conf['type']=='flex') { + + // Current record + $dataStructArray = t3lib_BEfunc::getFlexFormDS($conf, $currentRec, $table); + $currentValueArray = t3lib_div::xml2array($currentRec[$field]); + + if (is_array($currentValueArray)) { + $this->checkValue_flex_procInData( + $currentValueArray['data'], + array(), // Not used. + array(), // Not used. + $dataStructArray, + array($table,$id,$field), // Parameters. + 'version_remapMMForVersionSwap_flexFormCallBack' + ); + } + + // Swap record + $dataStructArray = t3lib_BEfunc::getFlexFormDS($conf, $swapRec, $table); + $currentValueArray = t3lib_div::xml2array($swapRec[$field]); + + if (is_array($currentValueArray)) { + $this->checkValue_flex_procInData( + $currentValueArray['data'], + array(), // Not used. + array(), // Not used. + $dataStructArray, + array($table,$swapWith,$field), // Parameters. + 'version_remapMMForVersionSwap_flexFormCallBack' + ); + } + } + } + + // Execute: + $this->version_remapMMForVersionSwap_execSwap($table,$id,$swapWith); + } + + /** + * Callback function for traversing the FlexForm structure in relation to ... + * + * @param array Array of parameters in num-indexes: table, uid, field + * @param array TCA field configuration (from Data Structure XML) + * @param string The value of the flexForm field + * @param string Not used. + * @param string Not used. + * @param string Path in flexforms + * @return array Result array with key "value" containing the value of the processing. + * @see version_remapMMForVersionSwap(), checkValue_flex_procInData_travDS() + */ + function version_remapMMForVersionSwap_flexFormCallBack($pParams, $dsConf, $dataValue, $dataValue_ext1, $dataValue_ext2, $path) { + + // Extract parameters: + list($table, $uid, $field) = $pParams; + + if ($this->isReferenceField($dsConf)) { + $allowedTables = $dsConf['type']=='group' ? $dsConf['allowed'] : $dsConf['foreign_table'].','.$dsConf['neg_foreign_table']; + $prependName = $dsConf['type']=='group' ? $dsConf['prepend_tname'] : $dsConf['neg_foreign_table']; + if ($dsConf['MM']) { + $dbAnalysis = t3lib_div::makeInstance('t3lib_loadDBGroup'); + /* @var $dbAnalysis t3lib_loadDBGroup */ + $dbAnalysis->start('', $allowedTables, $dsConf['MM'], $uid, $table, $dsConf); + $this->version_remapMMForVersionSwap_reg[$uid][$field.'/'.$path] = array($dbAnalysis, $dsConf['MM'], $prependName); + } + } + } + /** + * Performing the remapping operations found necessary in version_remapMMForVersionSwap() + * It must be done in three steps with an intermediate "fake" uid. The UID can be something else than -$id (fx. 9999999+$id if you dare... :-)- as long as it is unique. + * + * @param string Table for the two input records + * @param integer Current record (about to go offline) + * @param integer Swap record (about to go online) + * @return void + * @see version_remapMMForVersionSwap() + */ + function version_remapMMForVersionSwap_execSwap($table,$id,$swapWith) { + + if (is_array($this->version_remapMMForVersionSwap_reg[$id])) { + foreach($this->version_remapMMForVersionSwap_reg[$id] as $field => $str) { + $str[0]->remapMM($str[1],$id,-$id,$str[2]); + } + } + if (is_array($this->version_remapMMForVersionSwap_reg[$swapWith])) { + foreach($this->version_remapMMForVersionSwap_reg[$swapWith] as $field => $str) { + $str[0]->remapMM($str[1],$swapWith,$id,$str[2]); + } + } + if (is_array($this->version_remapMMForVersionSwap_reg[$id])) { + foreach($this->version_remapMMForVersionSwap_reg[$id] as $field => $str) { + $str[0]->remapMM($str[1],-$id,$swapWith,$str[2]); + } + } + } + + + @@ -4659,7 +4801,7 @@ // If a change has been done, set the new value(s) if ($set) { if ($conf['MM']) { - $dbAnalysis->writeMM($conf['MM'], $theUidToUpdate, $prependName); + $dbAnalysis->writeMM($conf['MM'], $MM_localUid, $prependName); } else { $vArray = $dbAnalysis->getValueArray($prependName); if ($conf['type']=='select') { @@ -5947,7 +6089,7 @@ function dbAnalysisStoreExec() { reset($this->dbAnalysisStore); while(list($k,$v)=each($this->dbAnalysisStore)) { - $id = $this->substNEWwithIDs[$v[2]]; + $id = t3lib_BEfunc::wsMapId($v[4],$this->substNEWwithIDs[$v[2]]); if ($id) { $v[2] = $id; $v[0]->writeMM($v[1],$v[2],$v[3]);