TYPO3 API  SVNRelease
class.t3lib_loaddbgroup.php
Go to the documentation of this file.
00001 <?php
00002 /***************************************************************
00003  *  Copyright notice
00004  *
00005  *  (c) 1999-2011 Kasper Skårhøj (kasperYYYY@typo3.com)
00006  *  All rights reserved
00007  *
00008  *  This script is part of the TYPO3 project. The TYPO3 project is
00009  *  free software; you can redistribute it and/or modify
00010  *  it under the terms of the GNU General Public License as published by
00011  *  the Free Software Foundation; either version 2 of the License, or
00012  *  (at your option) any later version.
00013  *
00014  *  The GNU General Public License can be found at
00015  *  http://www.gnu.org/copyleft/gpl.html.
00016  *  A copy is found in the textfile GPL.txt and important notices to the license
00017  *  from the author is found in LICENSE.txt distributed with these scripts.
00018  *
00019  *
00020  *  This script is distributed in the hope that it will be useful,
00021  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
00022  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00023  *  GNU General Public License for more details.
00024  *
00025  *  This copyright notice MUST APPEAR in all copies of the script!
00026  ***************************************************************/
00027 /**
00028  * Contains class for loading database groups
00029  *
00030  * $Id: class.t3lib_loaddbgroup.php 10121 2011-01-18 20:15:30Z ohader $
00031  * Revised for TYPO3 3.6 September/2003 by Kasper Skårhøj
00032  *
00033  * @author  Kasper Skårhøj <kasperYYYY@typo3.com>
00034  */
00035 /**
00036  * [CLASS/FUNCTION INDEX of SCRIPT]
00037  *
00038  *
00039  *
00040  *   76: class t3lib_loadDBGroup
00041  *  111:     function start($itemlist, $tablelist, $MMtable='', $MMuid=0, $currentTable='', $conf=array())
00042  *  179:     function readList($itemlist)
00043  *  225:     function readMM($tableName,$uid)
00044  *  276:     function writeMM($tableName,$uid,$prependTableName=0)
00045  *  352:     function readForeignField($uid, $conf)
00046  *  435:     function writeForeignField($conf, $parentUid, $updateToUid=0)
00047  *  510:     function getValueArray($prependTableName='')
00048  *  538:     function convertPosNeg($valueArray,$fTable,$nfTable)
00049  *  560:     function getFromDB()
00050  *  595:     function readyForInterface()
00051  *  621:     function countItems($returnAsArray = true)
00052  *  636:     function updateRefIndex($table,$id)
00053  *
00054  * TOTAL FUNCTIONS: 12
00055  * (This index is automatically created/updated by the extension "extdeveval")
00056  *
00057  */
00058 
00059 
00060 /**
00061  * Load database groups (relations)
00062  * Used to process the relations created by the TCA element types "group" and "select" for database records. Manages MM-relations as well.
00063  *
00064  * @author  Kasper Skårhøj <kasperYYYY@typo3.com>
00065  * @package TYPO3
00066  * @subpackage t3lib
00067  */
00068 class t3lib_loadDBGroup {
00069         // External, static:
00070     var $fromTC = 1; // Means that only uid and the label-field is returned
00071     var $registerNonTableValues = 0; // If set, values that are not ids in tables are normally discarded. By this options they will be preserved.
00072 
00073         // Internal, dynamic:
00074     var $tableArray = Array(); // Contains the table names as keys. The values are the id-values for each table. Should ONLY contain proper table names.
00075     var $itemArray = Array(); // Contains items in an numeric array (table/id for each). Tablenames here might be "_NO_TABLE"
00076     var $nonTableArray = array(); // Array for NON-table elements
00077     var $additionalWhere = array();
00078     var $checkIfDeleted = 1; // deleted-column is added to additionalWhere... if this is set...
00079     var $dbPaths = Array();
00080     var $firstTable = ''; // Will contain the first table name in the $tablelist (for positive ids)
00081     var $secondTable = ''; // Will contain the second table name in the $tablelist (for negative ids)
00082         // private
00083     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"
00084     var $MM_oppositeField = ''; // field name at the "local" side of the MM relation
00085     var $MM_oppositeTable = ''; // only set if MM_is_foreign is set
00086     var $MM_oppositeFieldConf = ''; // only set if MM_is_foreign is set
00087     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)
00088     var $currentTable; // current table => Only needed for reverse relations
00089     var $undeleteRecord; // if a record should be undeleted (so do not use the $useDeleteClause on t3lib_BEfunc)
00090 
00091 
00092     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
00093     var $MM_insert_fields = array(); // array of fields and value pairs used for insert in MM table
00094     var $MM_table_where = ''; // extra MM table where
00095 
00096     /**
00097      * @var boolean
00098      */
00099     protected $updateReferenceIndex = TRUE;
00100 
00101     /**
00102      * Initialization of the class.
00103      *
00104      * @param   string      List of group/select items
00105      * @param   string      Comma list of tables, first table takes priority if no table is set for an entry in the list.
00106      * @param   string      Name of a MM table.
00107      * @param   integer     Local UID for MM lookup
00108      * @param   string      current table name
00109      * @param   integer     TCA configuration for current field
00110      * @return  void
00111      */
00112     function start($itemlist, $tablelist, $MMtable = '', $MMuid = 0, $currentTable = '', $conf = array()) {
00113             // SECTION: MM reverse relations
00114         $this->MM_is_foreign = ($conf['MM_opposite_field'] ? 1 : 0);
00115         $this->MM_oppositeField = $conf['MM_opposite_field'];
00116         $this->MM_table_where = $conf['MM_table_where'];
00117         $this->MM_hasUidField = $conf['MM_hasUidField'];
00118         $this->MM_match_fields = is_array($conf['MM_match_fields']) ? $conf['MM_match_fields'] : array();
00119         $this->MM_insert_fields = is_array($conf['MM_insert_fields']) ? $conf['MM_insert_fields'] : $this->MM_match_fields;
00120 
00121         $this->currentTable = $currentTable;
00122         if ($this->MM_is_foreign) {
00123             $tmp = ($conf['type'] === 'group' ? $conf['allowed'] : $conf['foreign_table']);
00124                 // 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']
00125             $tmp = t3lib_div::trimExplode(',', $tmp);
00126             $this->MM_oppositeTable = $tmp[0];
00127             unset($tmp);
00128 
00129                 // only add the current table name if there is more than one allowed field
00130             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.
00131             $this->MM_oppositeFieldConf = $GLOBALS['TCA'][$this->MM_oppositeTable]['columns'][$this->MM_oppositeField]['config'];
00132 
00133             if ($this->MM_oppositeFieldConf['allowed']) {
00134                 $oppositeFieldConf_allowed = explode(',', $this->MM_oppositeFieldConf['allowed']);
00135                 if (count($oppositeFieldConf_allowed) > 1) {
00136                     $this->MM_isMultiTableRelationship = $oppositeFieldConf_allowed[0];
00137                 }
00138             }
00139         }
00140 
00141             // SECTION: normal MM relations
00142 
00143             // If the table list is "*" then all tables are used in the list:
00144         if (!strcmp(trim($tablelist), '*')) {
00145             $tablelist = implode(',', array_keys($GLOBALS['TCA']));
00146         }
00147 
00148             // The tables are traversed and internal arrays are initialized:
00149         $tempTableArray = t3lib_div::trimExplode(',', $tablelist, 1);
00150         foreach ($tempTableArray as $key => $val) {
00151             $tName = trim($val);
00152             $this->tableArray[$tName] = Array();
00153             if ($this->checkIfDeleted && $GLOBALS['TCA'][$tName]['ctrl']['delete']) {
00154                 $fieldN = $tName . '.' . $GLOBALS['TCA'][$tName]['ctrl']['delete'];
00155                 $this->additionalWhere[$tName] .= ' AND ' . $fieldN . '=0';
00156             }
00157         }
00158 
00159         if (is_array($this->tableArray)) {
00160             reset($this->tableArray);
00161         } else {
00162             return 'No tables!';
00163         }
00164 
00165             // Set first and second tables:
00166         $this->firstTable = key($this->tableArray); // Is the first table
00167         next($this->tableArray);
00168         $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...
00169 
00170             // Now, populate the internal itemArray and tableArray arrays:
00171         if ($MMtable) { // If MM, then call this function to do that:
00172             if ($MMuid) {
00173                 $this->readMM($MMtable, $MMuid);
00174             } else { // Revert to readList() for new records in order to load possible default values from $itemlist
00175                 $this->readList($itemlist);
00176             }
00177         } elseif ($MMuid && $conf['foreign_field']) {
00178                 // If not MM but foreign_field, the read the records by the foreign_field
00179             $this->readForeignField($MMuid, $conf);
00180         } else {
00181                 // If not MM, then explode the itemlist by "," and traverse the list:
00182             $this->readList($itemlist);
00183                 // do automatic default_sortby, if any
00184             if ($conf['foreign_default_sortby']) {
00185                 $this->sortList($conf['foreign_default_sortby']);
00186             }
00187         }
00188     }
00189 
00190     /**
00191      * Sets whether the reference index shall be updated.
00192      *
00193      * @param boolean $updateReferenceIndex Whether the reference index shall be updated
00194      * @return void
00195      */
00196     public function setUpdateReferenceIndex($updateReferenceIndex) {
00197         $this->updateReferenceIndex = (bool) $updateReferenceIndex;
00198     }
00199 
00200     /**
00201      * Explodes the item list and stores the parts in the internal arrays itemArray and tableArray from MM records.
00202      *
00203      * @param   string      Item list
00204      * @return  void
00205      */
00206     function readList($itemlist) {
00207         if ((string) trim($itemlist) != '') {
00208             $tempItemArray = t3lib_div::trimExplode(',', $itemlist); // Changed to trimExplode 31/3 04; HMENU special type "list" didn't work if there were spaces in the list... I suppose this is better overall...
00209             foreach ($tempItemArray as $key => $val) {
00210                 $isSet = 0; // Will be set to "1" if the entry was a real table/id:
00211 
00212                     // Extract table name and id. This is un the formular [tablename]_[id] where table name MIGHT contain "_", hence the reversion of the string!
00213                 $val = strrev($val);
00214                 $parts = explode('_', $val, 2);
00215                 $theID = strrev($parts[0]);
00216 
00217                     // Check that the id IS an integer:
00218                 if (t3lib_div::testInt($theID)) {
00219                         // Get the table name: If a part of the exploded string, use that. Otherwise if the id number is LESS than zero, use the second table, otherwise the first table
00220                     $theTable = trim($parts[1]) ? strrev(trim($parts[1])) : ($this->secondTable && $theID < 0 ? $this->secondTable : $this->firstTable);
00221                         // If the ID is not blank and the table name is among the names in the inputted tableList, then proceed:
00222                     if ((string) $theID != '' && $theID && $theTable && isset($this->tableArray[$theTable])) {
00223                             // Get ID as the right value:
00224                         $theID = $this->secondTable ? abs(intval($theID)) : intval($theID);
00225                             // Register ID/table name in internal arrays:
00226                         $this->itemArray[$key]['id'] = $theID;
00227                         $this->itemArray[$key]['table'] = $theTable;
00228                         $this->tableArray[$theTable][] = $theID;
00229                             // Set update-flag:
00230                         $isSet = 1;
00231                     }
00232                 }
00233 
00234                     // If it turns out that the value from the list was NOT a valid reference to a table-record, then we might still set it as a NO_TABLE value:
00235                 if (!$isSet && $this->registerNonTableValues) {
00236                     $this->itemArray[$key]['id'] = $tempItemArray[$key];
00237                     $this->itemArray[$key]['table'] = '_NO_TABLE';
00238                     $this->nonTableArray[] = $tempItemArray[$key];
00239                 }
00240             }
00241         }
00242     }
00243 
00244     /**
00245      * Does a sorting on $this->itemArray depending on a default sortby field.
00246      * This is only used for automatic sorting of comma separated lists.
00247      * This function is only relevant for data that is stored in comma separated lists!
00248      *
00249      * @param   string      $sortby: The default_sortby field/command (e.g. 'price DESC')
00250      * @return  void
00251      */
00252     function sortList($sortby) {
00253             // sort directly without fetching addional data
00254         if ($sortby == 'uid') {
00255             usort($this->itemArray, create_function('$a,$b', 'return $a["id"] < $b["id"] ? -1 : 1;'));
00256                 // only useful if working on the same table
00257         } elseif (count($this->tableArray) == 1) {
00258             reset($this->tableArray);
00259             $table = key($this->tableArray);
00260             $uidList = implode(',', current($this->tableArray));
00261 
00262             if ($uidList) {
00263                 $this->itemArray = array();
00264                 $this->tableArray = array();
00265 
00266                 $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery('uid', $table, 'uid IN (' . $uidList . ')', '', $sortby);
00267                 while ($row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res)) {
00268                     $this->itemArray[] = array('id' => $row['uid'], 'table' => $table);
00269                     $this->tableArray[$table][] = $row['uid'];
00270                 }
00271                 $GLOBALS['TYPO3_DB']->sql_free_result($res);
00272             }
00273         }
00274     }
00275 
00276     /**
00277      * Reads the record tablename/id into the internal arrays itemArray and tableArray from MM records.
00278      * You can call this function after start if you supply no list to start()
00279      *
00280      * @param   string      MM Tablename
00281      * @param   integer     Local UID
00282      * @return  void
00283      */
00284     function readMM($tableName, $uid) {
00285         $key = 0;
00286         $additionalWhere = '';
00287 
00288         if ($this->MM_is_foreign) { // in case of a reverse relation
00289             $uidLocal_field = 'uid_foreign';
00290             $uidForeign_field = 'uid_local';
00291             $sorting_field = 'sorting_foreign';
00292 
00293             if ($this->MM_isMultiTableRelationship) {
00294                 $additionalWhere .= ' AND ( tablenames=' . $GLOBALS['TYPO3_DB']->fullQuoteStr($this->currentTable, $tableName);
00295                 if ($this->currentTable == $this->MM_isMultiTableRelationship) { // be backwards compatible! When allowing more than one table after having previously allowed only one table, this case applies.
00296                     $additionalWhere .= ' OR tablenames=\'\'';
00297                 }
00298                 $additionalWhere .= ' ) ';
00299             }
00300             $theTable = $this->MM_oppositeTable;
00301         } else { // default
00302             $uidLocal_field = 'uid_local';
00303             $uidForeign_field = 'uid_foreign';
00304             $sorting_field = 'sorting';
00305         }
00306 
00307 
00308         if ($this->MM_table_where) {
00309             $additionalWhere .= LF . str_replace('###THIS_UID###', intval($uid), $this->MM_table_where);
00310         }
00311         foreach ($this->MM_match_fields as $field => $value) {
00312             $additionalWhere .= ' AND ' . $field . '=' . $GLOBALS['TYPO3_DB']->fullQuoteStr($value, $tableName);
00313         }
00314 
00315             // Select all MM relations:
00316         $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery('*', $tableName, $uidLocal_field . '=' . intval($uid) . $additionalWhere, '', $sorting_field);
00317         while ($row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res)) {
00318             if (!$this->MM_is_foreign) { // default
00319                 $theTable = $row['tablenames'] ? $row['tablenames'] : $this->firstTable; // If tablesnames columns exists and contain a name, then this value is the table, else it's the firstTable...
00320             }
00321             if (($row[$uidForeign_field] || $theTable == 'pages') && $theTable && isset($this->tableArray[$theTable])) {
00322 
00323                 $this->itemArray[$key]['id'] = $row[$uidForeign_field];
00324                 $this->itemArray[$key]['table'] = $theTable;
00325                 $this->tableArray[$theTable][] = $row[$uidForeign_field];
00326             } elseif ($this->registerNonTableValues) {
00327                 $this->itemArray[$key]['id'] = $row[$uidForeign_field];
00328                 $this->itemArray[$key]['table'] = '_NO_TABLE';
00329                 $this->nonTableArray[] = $row[$uidForeign_field];
00330             }
00331             $key++;
00332         }
00333         $GLOBALS['TYPO3_DB']->sql_free_result($res);
00334     }
00335 
00336     /**
00337      * Writes the internal itemArray to MM table:
00338      *
00339      * @param   string      MM table name
00340      * @param   integer     Local UID
00341      * @param   boolean     If set, then table names will always be written.
00342      * @return  void
00343      */
00344     function writeMM($MM_tableName, $uid, $prependTableName = 0) {
00345 
00346         if ($this->MM_is_foreign) { // in case of a reverse relation
00347             $uidLocal_field = 'uid_foreign';
00348             $uidForeign_field = 'uid_local';
00349             $sorting_field = 'sorting_foreign';
00350         } else { // default
00351             $uidLocal_field = 'uid_local';
00352             $uidForeign_field = 'uid_foreign';
00353             $sorting_field = 'sorting';
00354         }
00355 
00356             // If there are tables...
00357         $tableC = count($this->tableArray);
00358         if ($tableC) {
00359             $prep = ($tableC > 1 || $prependTableName || $this->MM_isMultiTableRelationship) ? 1 : 0; // boolean: does the field "tablename" need to be filled?
00360             $c = 0;
00361 
00362             $additionalWhere_tablenames = '';
00363             if ($this->MM_is_foreign && $prep) {
00364                 $additionalWhere_tablenames = ' AND tablenames=' . $GLOBALS['TYPO3_DB']->fullQuoteStr($this->currentTable, $MM_tableName);
00365             }
00366 
00367             $additionalWhere = '';
00368                 // add WHERE clause if configured
00369             if ($this->MM_table_where) {
00370                 $additionalWhere .= LF . str_replace('###THIS_UID###', intval($uid), $this->MM_table_where);
00371             }
00372                 // Select, update or delete only those relations that match the configured fields
00373             foreach ($this->MM_match_fields as $field => $value) {
00374                 $additionalWhere .= ' AND ' . $field . '=' . $GLOBALS['TYPO3_DB']->fullQuoteStr($value, $MM_tableName);
00375             }
00376 
00377             $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery(
00378                 $uidForeign_field . ($prep ? ', tablenames' : '') . ($this->MM_hasUidField ? ', uid' : ''),
00379                 $MM_tableName,
00380                 $uidLocal_field . '=' . $uid . $additionalWhere_tablenames . $additionalWhere,
00381                 '',
00382                 $sorting_field
00383             );
00384 
00385             $oldMMs = array();
00386             $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
00387             while ($row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res)) {
00388                 if (!$this->MM_is_foreign && $prep) {
00389                     $oldMMs[] = array($row['tablenames'], $row[$uidForeign_field]);
00390                 } else {
00391                     $oldMMs[] = $row[$uidForeign_field];
00392                 }
00393                 $oldMMs_inclUid[] = array($row['tablenames'], $row[$uidForeign_field], $row['uid']);
00394             }
00395 
00396                 // For each item, insert it:
00397             foreach ($this->itemArray as $val) {
00398                 $c++;
00399 
00400                 if ($prep || $val['table'] == '_NO_TABLE') {
00401                     if ($this->MM_is_foreign) { // insert current table if needed
00402                         $tablename = $this->currentTable;
00403                     } else {
00404                         $tablename = $val['table'];
00405                     }
00406                 } else {
00407                     $tablename = '';
00408                 }
00409 
00410                 if (!$this->MM_is_foreign && $prep) {
00411                     $item = array($val['table'], $val['id']);
00412                 } else {
00413                     $item = $val['id'];
00414                 }
00415 
00416                 if (in_array($item, $oldMMs)) {
00417                     $oldMMs_index = array_search($item, $oldMMs);
00418 
00419                     $whereClause = $uidLocal_field . '=' . $uid . ' AND ' . $uidForeign_field . '=' . $val['id'] .
00420                                    ($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.
00421                     if ($tablename) {
00422                         $whereClause .= ' AND tablenames=' . $GLOBALS['TYPO3_DB']->fullQuoteStr($tablename, $MM_tableName);
00423                     }
00424                     $GLOBALS['TYPO3_DB']->exec_UPDATEquery($MM_tableName, $whereClause . $additionalWhere, array($sorting_field => $c));
00425 
00426                     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.
00427                     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.
00428                 } else {
00429 
00430                     $insertFields = $this->MM_insert_fields;
00431                     $insertFields[$uidLocal_field] = $uid;
00432                     $insertFields[$uidForeign_field] = $val['id'];
00433                     $insertFields[$sorting_field] = $c;
00434                     if ($tablename) {
00435                         $insertFields['tablenames'] = $tablename;
00436                     }
00437 
00438                     $GLOBALS['TYPO3_DB']->exec_INSERTquery($MM_tableName, $insertFields);
00439 
00440                     if ($this->MM_is_foreign) {
00441                         $this->updateRefIndex($val['table'], $val['id']);
00442                     }
00443                 }
00444             }
00445 
00446                 // Delete all not-used relations:
00447             if (is_array($oldMMs) && count($oldMMs) > 0) {
00448                 $removeClauses = array();
00449                 $updateRefIndex_records = array();
00450                 foreach ($oldMMs as $oldMM_key => $mmItem) {
00451                     if ($this->MM_hasUidField) { // If UID field is present, of course we need only use that for deleting...:
00452                         $removeClauses[] = 'uid=' . intval($oldMMs_inclUid[$oldMM_key][2]);
00453                         $elDelete = $oldMMs_inclUid[$oldMM_key];
00454                     } else {
00455                         if (is_array($mmItem)) {
00456                             $removeClauses[] = 'tablenames=' . $GLOBALS['TYPO3_DB']->fullQuoteStr($mmItem[0], $MM_tableName) . ' AND ' . $uidForeign_field . '=' . $mmItem[1];
00457                         } else {
00458                             $removeClauses[] = $uidForeign_field . '=' . $mmItem;
00459                         }
00460                     }
00461                     if ($this->MM_is_foreign) {
00462                         if (is_array($mmItem)) {
00463                             $updateRefIndex_records[] = array($mmItem[0], $mmItem[1]);
00464                         } else {
00465                             $updateRefIndex_records[] = array($this->firstTable, $mmItem);
00466                         }
00467                     }
00468                 }
00469                 $deleteAddWhere = ' AND (' . implode(' OR ', $removeClauses) . ')';
00470                 $GLOBALS['TYPO3_DB']->exec_DELETEquery($MM_tableName, $uidLocal_field . '=' . intval($uid) . $deleteAddWhere . $additionalWhere_tablenames . $additionalWhere);
00471 
00472                     // Update ref index:
00473                 foreach ($updateRefIndex_records as $pair) {
00474                     $this->updateRefIndex($pair[0], $pair[1]);
00475                 }
00476             }
00477 
00478                 // 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 ...
00479             $this->updateRefIndex($this->currentTable, $uid);
00480         }
00481     }
00482 
00483     /**
00484      * Remaps MM table elements from one local uid to another
00485      * Does NOT update the reference index for you, must be called subsequently to do that!
00486      *
00487      * @param   string      MM table name
00488      * @param   integer     Local, current UID
00489      * @param   integer     Local, new UID
00490      * @param   boolean     If set, then table names will always be written.
00491      * @return  void
00492      */
00493     function remapMM($MM_tableName, $uid, $newUid, $prependTableName = 0) {
00494 
00495         if ($this->MM_is_foreign) { // in case of a reverse relation
00496             $uidLocal_field = 'uid_foreign';
00497         } else { // default
00498             $uidLocal_field = 'uid_local';
00499         }
00500 
00501             // If there are tables...
00502         $tableC = count($this->tableArray);
00503         if ($tableC) {
00504             $prep = ($tableC > 1 || $prependTableName || $this->MM_isMultiTableRelationship) ? 1 : 0; // boolean: does the field "tablename" need to be filled?
00505             $c = 0;
00506 
00507             $additionalWhere_tablenames = '';
00508             if ($this->MM_is_foreign && $prep) {
00509                 $additionalWhere_tablenames = ' AND tablenames=' . $GLOBALS['TYPO3_DB']->fullQuoteStr($this->currentTable, $MM_tableName);
00510             }
00511 
00512             $additionalWhere = '';
00513                 // add WHERE clause if configured
00514             if ($this->MM_table_where) {
00515                 $additionalWhere .= LF . str_replace('###THIS_UID###', intval($uid), $this->MM_table_where);
00516             }
00517                 // Select, update or delete only those relations that match the configured fields
00518             foreach ($this->MM_match_fields as $field => $value) {
00519                 $additionalWhere .= ' AND ' . $field . '=' . $GLOBALS['TYPO3_DB']->fullQuoteStr($value, $MM_tableName);
00520             }
00521 
00522             $GLOBALS['TYPO3_DB']->exec_UPDATEquery($MM_tableName, $uidLocal_field . '=' . intval($uid) . $additionalWhere_tablenames . $additionalWhere, array($uidLocal_field => $newUid));
00523         }
00524     }
00525 
00526     /**
00527      * Reads items from a foreign_table, that has a foreign_field (uid of the parent record) and
00528      * stores the parts in the internal array itemArray and tableArray.
00529      *
00530      * @param   integer     $uid: The uid of the parent record (this value is also on the foreign_table in the foreign_field)
00531      * @param   array       $conf: TCA configuration for current field
00532      * @return  void
00533      */
00534     function readForeignField($uid, $conf) {
00535         $key = 0;
00536         $uid = intval($uid);
00537         $whereClause = '';
00538         $foreign_table = $conf['foreign_table'];
00539         $foreign_table_field = $conf['foreign_table_field'];
00540         $useDeleteClause = $this->undeleteRecord ? FALSE : TRUE;
00541 
00542             // search for $uid in foreign_field, and if we have symmetric relations, do this also on symmetric_field
00543         if ($conf['symmetric_field']) {
00544             $whereClause = '(' . $conf['foreign_field'] . '=' . $uid . ' OR ' . $conf['symmetric_field'] . '=' . $uid . ')';
00545         } else {
00546             $whereClause = $conf['foreign_field'] . '=' . $uid;
00547         }
00548             // use the deleteClause (e.g. "deleted=0") on this table
00549         if ($useDeleteClause) {
00550             $whereClause .= t3lib_BEfunc::deleteClause($foreign_table);
00551         }
00552             // if it's requested to look for the parent uid AND the parent table,
00553             // add an additional SQL-WHERE clause
00554         if ($foreign_table_field && $this->currentTable) {
00555             $whereClause .= ' AND ' . $foreign_table_field . '=' . $GLOBALS['TYPO3_DB']->fullQuoteStr($this->currentTable, $foreign_table);
00556         }
00557 
00558             // Select children in the same workspace:
00559         if (t3lib_BEfunc::isTableWorkspaceEnabled($this->currentTable) && t3lib_BEfunc::isTableWorkspaceEnabled($foreign_table)) {
00560             $currentRecord = t3lib_BEfunc::getRecord($this->currentTable, $uid, 't3ver_wsid', '', $useDeleteClause);
00561             $whereClause .= t3lib_BEfunc::getWorkspaceWhereClause($foreign_table, $currentRecord['t3ver_wsid']);
00562         }
00563 
00564             // get the correct sorting field
00565         if ($conf['foreign_sortby']) { // specific manual sortby for data handled by this field
00566             if ($conf['symmetric_sortby'] && $conf['symmetric_field']) {
00567                     // sorting depends on, from which side of the relation we're looking at it
00568                 $sortby = '
00569                     CASE
00570                         WHEN ' . $conf['foreign_field'] . '=' . $uid . '
00571                         THEN ' . $conf['foreign_sortby'] . '
00572                         ELSE ' . $conf['symmetric_sortby'] . '
00573                     END';
00574             } else {
00575                     // regular single-side behaviour
00576                 $sortby = $conf['foreign_sortby'];
00577             }
00578         } elseif ($conf['foreign_default_sortby']) { // specific default sortby for data handled by this field
00579             $sortby = $conf['foreign_default_sortby'];
00580         } elseif ($GLOBALS['TCA'][$foreign_table]['ctrl']['sortby']) { // manual sortby for all table records
00581             $sortby = $GLOBALS['TCA'][$foreign_table]['ctrl']['sortby'];
00582         } elseif ($GLOBALS['TCA'][$foreign_table]['ctrl']['default_sortby']) { // default sortby for all table records
00583             $sortby = $GLOBALS['TCA'][$foreign_table]['ctrl']['default_sortby'];
00584         }
00585 
00586             // strip a possible "ORDER BY" in front of the $sortby value
00587         $sortby = $GLOBALS['TYPO3_DB']->stripOrderBy($sortby);
00588             // get the rows from storage
00589         $rows = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows('uid', $foreign_table, $whereClause, '', $sortby);
00590 
00591         if (count($rows)) {
00592             foreach ($rows as $row) {
00593                 $this->itemArray[$key]['id'] = $row['uid'];
00594                 $this->itemArray[$key]['table'] = $foreign_table;
00595                 $this->tableArray[$foreign_table][] = $row['uid'];
00596                 $key++;
00597             }
00598         }
00599     }
00600 
00601     /**
00602      * Write the sorting values to a foreign_table, that has a foreign_field (uid of the parent record)
00603      *
00604      * @param   array       $conf: TCA configuration for current field
00605      * @param   integer     $parentUid: The uid of the parent record
00606      * @param   boolean     $updateToUid: Whether to update the foreign field with the $parentUid (on Copy)
00607      * @param    boolean        $skipSorting: Do not update the sorting columns, this could happen for imported values
00608      * @return  void
00609      */
00610     function writeForeignField($conf, $parentUid, $updateToUid = 0, $skipSorting = FALSE) {
00611         $c = 0;
00612         $foreign_table = $conf['foreign_table'];
00613         $foreign_field = $conf['foreign_field'];
00614         $symmetric_field = $conf['symmetric_field'];
00615         $foreign_table_field = $conf['foreign_table_field'];
00616 
00617             // if there are table items and we have a proper $parentUid
00618         if (t3lib_div::testInt($parentUid) && count($this->tableArray)) {
00619                 // if updateToUid is not a positive integer, set it to '0', so it will be ignored
00620             if (!(t3lib_div::testInt($updateToUid) && $updateToUid > 0)) {
00621                 $updateToUid = 0;
00622             }
00623 
00624             $considerWorkspaces = ($GLOBALS['BE_USER']->workspace !== 0 && t3lib_BEfunc::isTableWorkspaceEnabled($foreign_table));
00625 
00626             $fields = 'uid,' . $foreign_field;
00627                 // Consider the symmetric field if defined:
00628             if ($symmetric_field) {
00629                 $fields .= ',' . $symmetric_field;
00630             }
00631                 // Consider workspaces if defined and currently used:
00632             if ($considerWorkspaces) {
00633                 $fields .= ',' . 't3ver_state,t3ver_oid';
00634             }
00635 
00636                 // update all items
00637             foreach ($this->itemArray as $val) {
00638                 $uid = $val['id'];
00639                 $table = $val['table'];
00640 
00641                     // fetch the current (not overwritten) relation record if we should handle symmetric relations
00642                 if ($symmetric_field || $considerWorkspaces) {
00643                     $row = t3lib_BEfunc::getRecord($table, $uid, $fields, '', FALSE);
00644                 }
00645                 if ($symmetric_field) {
00646                     $isOnSymmetricSide = t3lib_loadDBGroup::isOnSymmetricSide($parentUid, $conf, $row);
00647                 }
00648 
00649                 $updateValues = array();
00650                 $workspaceValues = array();
00651 
00652                     // no update to the uid is requested, so this is the normal behaviour
00653                     // just update the fields and care about sorting
00654                 if (!$updateToUid) {
00655                         // Always add the pointer to the parent uid
00656                     if ($isOnSymmetricSide) {
00657                         $updateValues[$symmetric_field] = $parentUid;
00658                     } else {
00659                         $updateValues[$foreign_field] = $parentUid;
00660                     }
00661 
00662                         // if it is configured in TCA also to store the parent table in the child record, just do it
00663                     if ($foreign_table_field && $this->currentTable) {
00664                         $updateValues[$foreign_table_field] = $this->currentTable;
00665                     }
00666 
00667                         // update sorting columns if not to be skipped
00668                     if (!$skipSorting) {
00669                             // get the correct sorting field
00670                         if ($conf['foreign_sortby']) { // specific manual sortby for data handled by this field
00671                             $sortby = $conf['foreign_sortby'];
00672                         } elseif ($GLOBALS['TCA'][$foreign_table]['ctrl']['sortby']) { // manual sortby for all table records
00673                             $sortby = $GLOBALS['TCA'][$foreign_table]['ctrl']['sortby'];
00674                         }
00675                             // Apply sorting on the symmetric side (it depends on who created the relation, so what uid is in the symmetric_field):
00676                         if ($isOnSymmetricSide && isset($conf['symmetric_sortby']) && $conf['symmetric_sortby']) {
00677                             $sortby = $conf['symmetric_sortby'];
00678                             // Strip a possible "ORDER BY" in front of the $sortby value:
00679                         } else {
00680                             $sortby = $GLOBALS['TYPO3_DB']->stripOrderBy($sortby);
00681                         }
00682 
00683                         if ($sortby) {
00684                             $updateValues[$sortby] = $workspaceValues[$sortby] = ++$c;
00685                         }
00686                     }
00687 
00688                         // update to a foreign_field/symmetric_field pointer is requested, normally used on record copies
00689                         // only update the fields, if the old uid is found somewhere - for select fields, TCEmain is doing this already!
00690                 } else {
00691                     if ($isOnSymmetricSide) {
00692                         $updateValues[$symmetric_field] = $updateToUid;
00693                     } else {
00694                         $updateValues[$foreign_field] = $updateToUid;
00695                     }
00696                 }
00697 
00698                     // Update accordant fields in the database:
00699                 if (count($updateValues)) {
00700                     $GLOBALS['TYPO3_DB']->exec_UPDATEquery($table, 'uid=' . intval($uid), $updateValues);
00701                     $this->updateRefIndex($table, $uid);
00702                 }
00703                     // Update accordant fields in the database for workspaces overlays/placeholders:
00704                 if (count($workspaceValues) && $considerWorkspaces) {
00705                     if (isset($row['t3ver_oid']) && $row['t3ver_oid'] && $row['t3ver_state'] == -1) {
00706                         $GLOBALS['TYPO3_DB']->exec_UPDATEquery($table, 'uid=' . intval($row['t3ver_oid']), $workspaceValues);
00707                     }
00708                 }
00709             }
00710         }
00711     }
00712 
00713     /**
00714      * After initialization you can extract an array of the elements from the object. Use this function for that.
00715      *
00716      * @param   boolean     If set, then table names will ALWAYS be prepended (unless its a _NO_TABLE value)
00717      * @return  array       A numeric array.
00718      */
00719     function getValueArray($prependTableName = '') {
00720             // INIT:
00721         $valueArray = Array();
00722         $tableC = count($this->tableArray);
00723 
00724             // If there are tables in the table array:
00725         if ($tableC) {
00726                 // If there are more than ONE table in the table array, then always prepend table names:
00727             $prep = ($tableC > 1 || $prependTableName) ? 1 : 0;
00728 
00729                 // Traverse the array of items:
00730             foreach ($this->itemArray as $val) {
00731                 $valueArray[] = (($prep && $val['table'] != '_NO_TABLE') ? $val['table'] . '_' : '') .
00732                                 $val['id'];
00733             }
00734         }
00735             // Return the array
00736         return $valueArray;
00737     }
00738 
00739     /**
00740      * Converts id numbers from negative to positive.
00741      *
00742      * @param   array       Array of [table]_[id] pairs.
00743      * @param   string      Foreign table (the one used for positive numbers)
00744      * @param   string      NEGative foreign table
00745      * @return  array       The array with ID integer values, converted to positive for those where the table name was set but did NOT match the positive foreign table.
00746      */
00747     function convertPosNeg($valueArray, $fTable, $nfTable) {
00748         if (is_array($valueArray) && $fTable) {
00749             foreach ($valueArray as $key => $val) {
00750                 $val = strrev($val);
00751                 $parts = explode('_', $val, 2);
00752                 $theID = strrev($parts[0]);
00753                 $theTable = strrev($parts[1]);
00754 
00755                 if (t3lib_div::testInt($theID) && (!$theTable || !strcmp($theTable, $fTable) || !strcmp($theTable, $nfTable))) {
00756                     $valueArray[$key] = $theTable && strcmp($theTable, $fTable) ? $theID * -1 : $theID;
00757                 }
00758             }
00759         }
00760         return $valueArray;
00761     }
00762 
00763     /**
00764      * Reads all records from internal tableArray into the internal ->results array where keys are table names and for each table, records are stored with uids as their keys.
00765      * If $this->fromTC is set you can save a little memory since only uid,pid and a few other fields are selected.
00766      *
00767      * @return  void
00768      */
00769     function getFromDB() {
00770             // Traverses the tables listed:
00771         foreach ($this->tableArray as $key => $val) {
00772             if (is_array($val)) {
00773                 $itemList = implode(',', $val);
00774                 if ($itemList) {
00775                     $from = '*';
00776                     if ($this->fromTC) {
00777                         $from = 'uid,pid';
00778                         if ($GLOBALS['TCA'][$key]['ctrl']['label']) {
00779                             $from .= ',' . $GLOBALS['TCA'][$key]['ctrl']['label']; // Titel
00780                         }
00781                         if ($GLOBALS['TCA'][$key]['ctrl']['label_alt']) {
00782                             $from .= ',' . $GLOBALS['TCA'][$key]['ctrl']['label_alt']; // Alternative Title-Fields
00783                         }
00784                         if ($GLOBALS['TCA'][$key]['ctrl']['thumbnail']) {
00785                             $from .= ',' . $GLOBALS['TCA'][$key]['ctrl']['thumbnail']; // Thumbnail
00786                         }
00787                     }
00788                     $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery($from, $key, 'uid IN (' . $itemList . ')' . $this->additionalWhere[$key]);
00789                     while ($row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res)) {
00790                         $this->results[$key][$row['uid']] = $row;
00791                     }
00792                 }
00793             }
00794         }
00795         return $this->results;
00796     }
00797 
00798     /**
00799      * Prepare items from itemArray to be transferred to the TCEforms interface (as a comma list)
00800      *
00801      * @return  string
00802      * @see t3lib_transferdata::renderRecord()
00803      */
00804     function readyForInterface() {
00805         global $TCA;
00806 
00807         if (!is_array($this->itemArray)) {
00808             return FALSE;
00809         }
00810 
00811         $output = array();
00812         $perms_clause = $GLOBALS['BE_USER']->getPagePermsClause(1); // For use when getting the paths....
00813         $titleLen = intval($GLOBALS['BE_USER']->uc['titleLen']);
00814 
00815         foreach ($this->itemArray as $key => $val) {
00816             $theRow = $this->results[$val['table']][$val['id']];
00817             if ($theRow && is_array($TCA[$val['table']])) {
00818                 $label = t3lib_div::fixed_lgd_cs(strip_tags(t3lib_BEfunc::getRecordTitle($val['table'], $theRow)), $titleLen);
00819                 $label = ($label) ? $label : '[...]';
00820                 $output[] = str_replace(',', '', $val['table'] . '_' . $val['id'] . '|' . rawurlencode($label));
00821             }
00822         }
00823         return implode(',', $output);
00824     }
00825 
00826     /**
00827      * Counts the items in $this->itemArray and puts this value in an array by default.
00828      *
00829      * @param   boolean     Whether to put the count value in an array
00830      * @return  mixed       The plain count as integer or the same inside an array
00831      */
00832     function countItems($returnAsArray = TRUE) {
00833         $count = count($this->itemArray);
00834         if ($returnAsArray) {
00835             $count = array($count);
00836         }
00837         return $count;
00838     }
00839 
00840     /**
00841      * Update Reference Index (sys_refindex) for a record
00842      * Should be called any almost any update to a record which could affect references inside the record.
00843      * (copied from TCEmain)
00844      *
00845      * @param   string      Table name
00846      * @param   integer     Record UID
00847      * @return  array Information concerning modifications delivered by t3lib_refindex::updateRefIndexTable()
00848      */
00849     function updateRefIndex($table, $id) {
00850         if ($this->updateReferenceIndex === TRUE) {
00851             /** @var $refIndexObj t3lib_refindex */
00852             $refIndexObj = t3lib_div::makeInstance('t3lib_refindex');
00853             return $refIndexObj->updateRefIndexTable($table, $id);
00854         }
00855     }
00856 
00857     /**
00858      * Checks, if we're looking from the "other" side, the symmetric side, to a symmetric relation.
00859      *
00860      * @param   string      $parentUid: The uid of the parent record
00861      * @param   array       $parentConf: The TCA configuration of the parent field embedding the child records
00862      * @param   array       $childRec: The record row of the child record
00863      * @return  boolean     Returns true if looking from the symmetric ("other") side to the relation.
00864      */
00865     function isOnSymmetricSide($parentUid, $parentConf, $childRec) {
00866         return t3lib_div::testInt($childRec['uid']) && $parentConf['symmetric_field'] && $parentUid == $childRec[$parentConf['symmetric_field']]
00867                 ? TRUE
00868                 : FALSE;
00869     }
00870 }
00871 
00872 
00873 if (defined('TYPO3_MODE') && isset($GLOBALS['TYPO3_CONF_VARS'][TYPO3_MODE]['XCLASS']['t3lib/class.t3lib_loaddbgroup.php'])) {
00874     include_once($GLOBALS['TYPO3_CONF_VARS'][TYPO3_MODE]['XCLASS']['t3lib/class.t3lib_loaddbgroup.php']);
00875 }
00876 
00877 ?>