Bugfixes to MM-relations, among other in workspaces (Bugs 3531 / 3907 solved)
authorKasper Skårhøj <kasper@typo3.org>
Thu, 31 Jan 2008 21:27:45 +0000 (21:27 +0000)
committerKasper Skårhøj <kasper@typo3.org>
Thu, 31 Jan 2008 21:27:45 +0000 (21:27 +0000)
git-svn-id: https://svn.typo3.org/TYPO3v4/Core/trunk@3002 709f56b5-9817-0410-a4d7-c38de5d9e867

ChangeLog
t3lib/class.t3lib_loaddbgroup.php
t3lib/class.t3lib_tcemain.php

index 97e1301..9a0e658 100755 (executable)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,18 @@
+2008-01-31  Kasper Sk\8crh¿j <kasper2008@typo3.com>
+       
+       * Bugfixes to MM-relations, among other in workspaces (Bugs 3531 / 3907 solved)
+
+       class.t3lib_tcemain.php
+       - Most importantly - and depending on changes in class.t3lib_loaddbgroup.php - is that MM-relations are now swapped when publishing in a workspace. This bug has been known as #3531 and #3907 for a while.
+       - Fixed old bug that MM-relations wouldn't get remapped to new elements that were are part of a copy operation ($theUidToUpdate => $MM_localUid)
+       - Fixed that MM-relations added to a new unsaved record in a workspace would get attached to the live version of the record (change in dbAnalysisStoreExec())
+       
+       class.t3lib_loaddbgroup.php
+       - Fixed several bugs related to MM-relations: 
+       - The feature "multiple" didn't work after bidir-mm was implemented. This is now fixed but requires that the MM table gets a real UID field. This can be configured with the TCA options "MM_hasUidField" (See TYPO3 Core API)
+       - With bidir-MM relations the reference index on the "native" side is now updated when "opposite side" relations are edited. (The only known problem for this is when workspaces publishes such a record, then the ref. index is not updated for "opposite side" fields. It's considered low-importance for now. If you depend on the reference index, update it nightly with cronjobs.)
+
+
 2008-01-31  Kasper Sk\8crh¿j <kasper2008@typo3.com>
 
        * - Cleaner was updated to sort output - makes better diff-views now.
index cfe73c6..54e8cba 100755 (executable)
@@ -62,6 +62,7 @@
 
 
 
+require_once (PATH_t3lib.'class.t3lib_refindex.php');
 
 
 
@@ -118,6 +119,7 @@ class t3lib_loadDBGroup     {
                $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 @@ class t3lib_loadDBGroup     {
                        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 @@ class t3lib_loadDBGroup     {
         * @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,18 +360,26 @@ class t3lib_loadDBGroup   {
                        }
                                // 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]);
                                } else {
                                        $oldMMs[] = $row[$uidForeign_field];
                                }
+                               $oldMMs_inclUid[] = array($row['tablenames'], $row[$uidForeign_field], $row['uid']);
                        }
 
                                // For each item, insert it:
@@ -392,13 +403,17 @@ class t3lib_loadDBGroup   {
                                }
 
                                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,23 +424,91 @@ class t3lib_loadDBGroup   {
                                                $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']);
+                                       }
                                }
                        }
 
                                // 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 784c724..0000b26 100755 (executable)
@@ -2059,7 +2059,7 @@ class t3lib_TCEmain       {
                        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 @@ class t3lib_TCEmain       {
                                                                                                $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 @@ class t3lib_TCEmain       {
                                                                                                $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,6 +4508,144 @@ $this->log($table,$id,6,0,0,'Stage raised...',30,array('comment'=>$comment,'stag
                        $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 @@ $this->log($table,$id,6,0,0,'Stage raised...',30,array('comment'=>$comment,'stag
                        // 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 @@ $this->log($table,$id,6,0,0,'Stage raised...',30,array('comment'=>$comment,'stag
        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]);