2 /***************************************************************
5 * (c) 1999-2011 Kasper Skårhøj (kasperYYYY@typo3.com)
8 * This script is part of the TYPO3 project. The TYPO3 project is
9 * free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version.
14 * The GNU General Public License can be found at
15 * http://www.gnu.org/copyleft/gpl.html.
16 * A copy is found in the textfile GPL.txt and important notices to the license
17 * from the author is found in LICENSE.txt distributed with these scripts.
20 * This script is distributed in the hope that it will be useful,
21 * but WITHOUT ANY WARRANTY; without even the implied warranty of
22 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23 * GNU General Public License for more details.
25 * This copyright notice MUST APPEAR in all copies of the script!
26 ***************************************************************/
28 * Contains class for loading database groups
30 * Revised for TYPO3 3.6 September/2003 by Kasper Skårhøj
32 * @author Kasper Skårhøj <kasperYYYY@typo3.com>
37 * Load database groups (relations)
38 * Used to process the relations created by the TCA element types "group" and "select" for database records. Manages MM-relations as well.
40 * @author Kasper Skårhøj <kasperYYYY@typo3.com>
44 class t3lib_loadDBGroup
{
46 var $fromTC = 1; // Means that only uid and the label-field is returned
47 var $registerNonTableValues = 0; // If set, values that are not ids in tables are normally discarded. By this options they will be preserved.
50 var $tableArray = Array(); // Contains the table names as keys. The values are the id-values for each table. Should ONLY contain proper table names.
51 var $itemArray = Array(); // Contains items in an numeric array (table/id for each). Tablenames here might be "_NO_TABLE"
52 var $nonTableArray = array(); // Array for NON-table elements
53 var $additionalWhere = array();
54 var $checkIfDeleted = 1; // deleted-column is added to additionalWhere... if this is set...
55 var $dbPaths = Array();
56 var $firstTable = ''; // Will contain the first table name in the $tablelist (for positive ids)
57 var $secondTable = ''; // Will contain the second table name in the $tablelist (for negative ids)
59 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"
60 var $MM_oppositeField = ''; // field name at the "local" side of the MM relation
61 var $MM_oppositeTable = ''; // only set if MM_is_foreign is set
62 var $MM_oppositeFieldConf = ''; // only set if MM_is_foreign is set
63 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)
64 var $currentTable; // current table => Only needed for reverse relations
65 var $undeleteRecord; // if a record should be undeleted (so do not use the $useDeleteClause on t3lib_BEfunc)
68 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
69 var $MM_insert_fields = array(); // array of fields and value pairs used for insert in MM table
70 var $MM_table_where = ''; // extra MM table where
75 protected $updateReferenceIndex = TRUE;
78 * Initialization of the class.
80 * @param string List of group/select items
81 * @param string Comma list of tables, first table takes priority if no table is set for an entry in the list.
82 * @param string Name of a MM table.
83 * @param integer Local UID for MM lookup
84 * @param string current table name
85 * @param integer TCA configuration for current field
88 function start($itemlist, $tablelist, $MMtable = '', $MMuid = 0, $currentTable = '', $conf = array()) {
89 // SECTION: MM reverse relations
90 $this->MM_is_foreign
= ($conf['MM_opposite_field'] ?
1 : 0);
91 $this->MM_oppositeField
= $conf['MM_opposite_field'];
92 $this->MM_table_where
= $conf['MM_table_where'];
93 $this->MM_hasUidField
= $conf['MM_hasUidField'];
94 $this->MM_match_fields
= is_array($conf['MM_match_fields']) ?
$conf['MM_match_fields'] : array();
95 $this->MM_insert_fields
= is_array($conf['MM_insert_fields']) ?
$conf['MM_insert_fields'] : $this->MM_match_fields
;
97 $this->currentTable
= $currentTable;
98 if ($this->MM_is_foreign
) {
99 $tmp = ($conf['type'] === 'group' ?
$conf['allowed'] : $conf['foreign_table']);
100 // 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']
101 $tmp = t3lib_div
::trimExplode(',', $tmp);
102 $this->MM_oppositeTable
= $tmp[0];
105 // only add the current table name if there is more than one allowed field
106 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.
107 $this->MM_oppositeFieldConf
= $GLOBALS['TCA'][$this->MM_oppositeTable
]['columns'][$this->MM_oppositeField
]['config'];
109 if ($this->MM_oppositeFieldConf
['allowed']) {
110 $oppositeFieldConf_allowed = explode(',', $this->MM_oppositeFieldConf
['allowed']);
111 if (count($oppositeFieldConf_allowed) > 1 ||
$this->MM_oppositeFieldConf
['allowed'] === '*') {
112 $this->MM_isMultiTableRelationship
= $oppositeFieldConf_allowed[0];
117 // SECTION: normal MM relations
119 // If the table list is "*" then all tables are used in the list:
120 if (!strcmp(trim($tablelist), '*')) {
121 $tablelist = implode(',', array_keys($GLOBALS['TCA']));
124 // The tables are traversed and internal arrays are initialized:
125 $tempTableArray = t3lib_div
::trimExplode(',', $tablelist, 1);
126 foreach ($tempTableArray as $key => $val) {
128 $this->tableArray
[$tName] = Array();
129 if ($this->checkIfDeleted
&& $GLOBALS['TCA'][$tName]['ctrl']['delete']) {
130 $fieldN = $tName . '.' . $GLOBALS['TCA'][$tName]['ctrl']['delete'];
131 $this->additionalWhere
[$tName] .= ' AND ' . $fieldN . '=0';
135 if (is_array($this->tableArray
)) {
136 reset($this->tableArray
);
141 // Set first and second tables:
142 $this->firstTable
= key($this->tableArray
); // Is the first table
143 next($this->tableArray
);
144 $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...
146 // Now, populate the internal itemArray and tableArray arrays:
147 if ($MMtable) { // If MM, then call this function to do that:
149 $this->readMM($MMtable, $MMuid);
150 } else { // Revert to readList() for new records in order to load possible default values from $itemlist
151 $this->readList($itemlist);
153 } elseif ($MMuid && $conf['foreign_field']) {
154 // If not MM but foreign_field, the read the records by the foreign_field
155 $this->readForeignField($MMuid, $conf);
157 // If not MM, then explode the itemlist by "," and traverse the list:
158 $this->readList($itemlist);
159 // do automatic default_sortby, if any
160 if ($conf['foreign_default_sortby']) {
161 $this->sortList($conf['foreign_default_sortby']);
167 * Sets whether the reference index shall be updated.
169 * @param boolean $updateReferenceIndex Whether the reference index shall be updated
172 public function setUpdateReferenceIndex($updateReferenceIndex) {
173 $this->updateReferenceIndex
= (bool) $updateReferenceIndex;
177 * Explodes the item list and stores the parts in the internal arrays itemArray and tableArray from MM records.
179 * @param string Item list
182 function readList($itemlist) {
183 if ((string) trim($itemlist) != '') {
184 $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...
185 foreach ($tempItemArray as $key => $val) {
186 $isSet = 0; // Will be set to "1" if the entry was a real table/id:
188 // Extract table name and id. This is un the formular [tablename]_[id] where table name MIGHT contain "_", hence the reversion of the string!
190 $parts = explode('_', $val, 2);
191 $theID = strrev($parts[0]);
193 // Check that the id IS an integer:
194 if (t3lib_utility_Math
::canBeInterpretedAsInteger($theID)) {
195 // 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
196 $theTable = trim($parts[1]) ?
strrev(trim($parts[1])) : ($this->secondTable
&& $theID < 0 ?
$this->secondTable
: $this->firstTable
);
197 // If the ID is not blank and the table name is among the names in the inputted tableList, then proceed:
198 if ((string) $theID != '' && $theID && $theTable && isset($this->tableArray
[$theTable])) {
199 // Get ID as the right value:
200 $theID = $this->secondTable ?
abs(intval($theID)) : intval($theID);
201 // Register ID/table name in internal arrays:
202 $this->itemArray
[$key]['id'] = $theID;
203 $this->itemArray
[$key]['table'] = $theTable;
204 $this->tableArray
[$theTable][] = $theID;
210 // 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:
211 if (!$isSet && $this->registerNonTableValues
) {
212 $this->itemArray
[$key]['id'] = $tempItemArray[$key];
213 $this->itemArray
[$key]['table'] = '_NO_TABLE';
214 $this->nonTableArray
[] = $tempItemArray[$key];
221 * Does a sorting on $this->itemArray depending on a default sortby field.
222 * This is only used for automatic sorting of comma separated lists.
223 * This function is only relevant for data that is stored in comma separated lists!
225 * @param string $sortby: The default_sortby field/command (e.g. 'price DESC')
228 function sortList($sortby) {
229 // sort directly without fetching addional data
230 if ($sortby == 'uid') {
231 usort($this->itemArray
, create_function('$a,$b', 'return $a["id"] < $b["id"] ? -1 : 1;'));
232 // only useful if working on the same table
233 } elseif (count($this->tableArray
) == 1) {
234 reset($this->tableArray
);
235 $table = key($this->tableArray
);
236 $uidList = implode(',', current($this->tableArray
));
239 $this->itemArray
= array();
240 $this->tableArray
= array();
242 $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery('uid', $table, 'uid IN (' . $uidList . ')', '', $sortby);
243 while ($row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res)) {
244 $this->itemArray
[] = array('id' => $row['uid'], 'table' => $table);
245 $this->tableArray
[$table][] = $row['uid'];
247 $GLOBALS['TYPO3_DB']->sql_free_result($res);
253 * Reads the record tablename/id into the internal arrays itemArray and tableArray from MM records.
254 * You can call this function after start if you supply no list to start()
256 * @param string MM Tablename
257 * @param integer Local UID
260 function readMM($tableName, $uid) {
262 $additionalWhere = '';
264 if ($this->MM_is_foreign
) { // in case of a reverse relation
265 $uidLocal_field = 'uid_foreign';
266 $uidForeign_field = 'uid_local';
267 $sorting_field = 'sorting_foreign';
269 if ($this->MM_isMultiTableRelationship
) {
270 $additionalWhere .= ' AND ( tablenames=' . $GLOBALS['TYPO3_DB']->fullQuoteStr($this->currentTable
, $tableName);
271 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.
272 $additionalWhere .= ' OR tablenames=\'\'';
274 $additionalWhere .= ' ) ';
276 $theTable = $this->MM_oppositeTable
;
278 $uidLocal_field = 'uid_local';
279 $uidForeign_field = 'uid_foreign';
280 $sorting_field = 'sorting';
284 if ($this->MM_table_where
) {
285 $additionalWhere .= LF
. str_replace('###THIS_UID###', intval($uid), $this->MM_table_where
);
287 foreach ($this->MM_match_fields
as $field => $value) {
288 $additionalWhere .= ' AND ' . $field . '=' . $GLOBALS['TYPO3_DB']->fullQuoteStr($value, $tableName);
291 // Select all MM relations:
292 $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery('*', $tableName, $uidLocal_field . '=' . intval($uid) . $additionalWhere, '', $sorting_field);
293 while ($row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res)) {
294 if (!$this->MM_is_foreign
) { // default
295 $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...
297 if (($row[$uidForeign_field] ||
$theTable == 'pages') && $theTable && isset($this->tableArray
[$theTable])) {
299 $this->itemArray
[$key]['id'] = $row[$uidForeign_field];
300 $this->itemArray
[$key]['table'] = $theTable;
301 $this->tableArray
[$theTable][] = $row[$uidForeign_field];
302 } elseif ($this->registerNonTableValues
) {
303 $this->itemArray
[$key]['id'] = $row[$uidForeign_field];
304 $this->itemArray
[$key]['table'] = '_NO_TABLE';
305 $this->nonTableArray
[] = $row[$uidForeign_field];
309 $GLOBALS['TYPO3_DB']->sql_free_result($res);
313 * Writes the internal itemArray to MM table:
315 * @param string MM table name
316 * @param integer Local UID
317 * @param boolean If set, then table names will always be written.
320 function writeMM($MM_tableName, $uid, $prependTableName = 0) {
322 if ($this->MM_is_foreign
) { // in case of a reverse relation
323 $uidLocal_field = 'uid_foreign';
324 $uidForeign_field = 'uid_local';
325 $sorting_field = 'sorting_foreign';
327 $uidLocal_field = 'uid_local';
328 $uidForeign_field = 'uid_foreign';
329 $sorting_field = 'sorting';
332 // If there are tables...
333 $tableC = count($this->tableArray
);
335 $prep = ($tableC > 1 ||
$prependTableName ||
$this->MM_isMultiTableRelationship
) ?
1 : 0; // boolean: does the field "tablename" need to be filled?
338 $additionalWhere_tablenames = '';
339 if ($this->MM_is_foreign
&& $prep) {
340 $additionalWhere_tablenames = ' AND tablenames=' . $GLOBALS['TYPO3_DB']->fullQuoteStr($this->currentTable
, $MM_tableName);
343 $additionalWhere = '';
344 // add WHERE clause if configured
345 if ($this->MM_table_where
) {
346 $additionalWhere .= LF
. str_replace('###THIS_UID###', intval($uid), $this->MM_table_where
);
348 // Select, update or delete only those relations that match the configured fields
349 foreach ($this->MM_match_fields
as $field => $value) {
350 $additionalWhere .= ' AND ' . $field . '=' . $GLOBALS['TYPO3_DB']->fullQuoteStr($value, $MM_tableName);
353 $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery(
354 $uidForeign_field . ($prep ?
', tablenames' : '') . ($this->MM_hasUidField ?
', uid' : ''),
356 $uidLocal_field . '=' . $uid . $additionalWhere_tablenames . $additionalWhere,
362 $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
363 while ($row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res)) {
364 if (!$this->MM_is_foreign
&& $prep) {
365 $oldMMs[] = array($row['tablenames'], $row[$uidForeign_field]);
367 $oldMMs[] = $row[$uidForeign_field];
369 $oldMMs_inclUid[] = array($row['tablenames'], $row[$uidForeign_field], $row['uid']);
372 // For each item, insert it:
373 foreach ($this->itemArray
as $val) {
376 if ($prep ||
$val['table'] == '_NO_TABLE') {
377 if ($this->MM_is_foreign
) { // insert current table if needed
378 $tablename = $this->currentTable
;
380 $tablename = $val['table'];
386 if (!$this->MM_is_foreign
&& $prep) {
387 $item = array($val['table'], $val['id']);
392 if (in_array($item, $oldMMs)) {
393 $oldMMs_index = array_search($item, $oldMMs);
395 $whereClause = $uidLocal_field . '=' . $uid . ' AND ' . $uidForeign_field . '=' . $val['id'] .
396 ($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.
398 $whereClause .= ' AND tablenames=' . $GLOBALS['TYPO3_DB']->fullQuoteStr($tablename, $MM_tableName);
400 $GLOBALS['TYPO3_DB']->exec_UPDATEquery($MM_tableName, $whereClause . $additionalWhere, array($sorting_field => $c));
402 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.
403 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.
406 $insertFields = $this->MM_insert_fields
;
407 $insertFields[$uidLocal_field] = $uid;
408 $insertFields[$uidForeign_field] = $val['id'];
409 $insertFields[$sorting_field] = $c;
411 $insertFields['tablenames'] = $tablename;
414 $GLOBALS['TYPO3_DB']->exec_INSERTquery($MM_tableName, $insertFields);
416 if ($this->MM_is_foreign
) {
417 $this->updateRefIndex($val['table'], $val['id']);
422 // Delete all not-used relations:
423 if (is_array($oldMMs) && count($oldMMs) > 0) {
424 $removeClauses = array();
425 $updateRefIndex_records = array();
426 foreach ($oldMMs as $oldMM_key => $mmItem) {
427 if ($this->MM_hasUidField
) { // If UID field is present, of course we need only use that for deleting...:
428 $removeClauses[] = 'uid=' . intval($oldMMs_inclUid[$oldMM_key][2]);
429 $elDelete = $oldMMs_inclUid[$oldMM_key];
431 if (is_array($mmItem)) {
432 $removeClauses[] = 'tablenames=' . $GLOBALS['TYPO3_DB']->fullQuoteStr($mmItem[0], $MM_tableName) . ' AND ' . $uidForeign_field . '=' . $mmItem[1];
434 $removeClauses[] = $uidForeign_field . '=' . $mmItem;
437 if ($this->MM_is_foreign
) {
438 if (is_array($mmItem)) {
439 $updateRefIndex_records[] = array($mmItem[0], $mmItem[1]);
441 $updateRefIndex_records[] = array($this->firstTable
, $mmItem);
445 $deleteAddWhere = ' AND (' . implode(' OR ', $removeClauses) . ')';
446 $GLOBALS['TYPO3_DB']->exec_DELETEquery($MM_tableName, $uidLocal_field . '=' . intval($uid) . $deleteAddWhere . $additionalWhere_tablenames . $additionalWhere);
449 foreach ($updateRefIndex_records as $pair) {
450 $this->updateRefIndex($pair[0], $pair[1]);
454 // 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 ...
455 $this->updateRefIndex($this->currentTable
, $uid);
460 * Remaps MM table elements from one local uid to another
461 * Does NOT update the reference index for you, must be called subsequently to do that!
463 * @param string MM table name
464 * @param integer Local, current UID
465 * @param integer Local, new UID
466 * @param boolean If set, then table names will always be written.
469 function remapMM($MM_tableName, $uid, $newUid, $prependTableName = 0) {
471 if ($this->MM_is_foreign
) { // in case of a reverse relation
472 $uidLocal_field = 'uid_foreign';
474 $uidLocal_field = 'uid_local';
477 // If there are tables...
478 $tableC = count($this->tableArray
);
480 $prep = ($tableC > 1 ||
$prependTableName ||
$this->MM_isMultiTableRelationship
) ?
1 : 0; // boolean: does the field "tablename" need to be filled?
483 $additionalWhere_tablenames = '';
484 if ($this->MM_is_foreign
&& $prep) {
485 $additionalWhere_tablenames = ' AND tablenames=' . $GLOBALS['TYPO3_DB']->fullQuoteStr($this->currentTable
, $MM_tableName);
488 $additionalWhere = '';
489 // add WHERE clause if configured
490 if ($this->MM_table_where
) {
491 $additionalWhere .= LF
. str_replace('###THIS_UID###', intval($uid), $this->MM_table_where
);
493 // Select, update or delete only those relations that match the configured fields
494 foreach ($this->MM_match_fields
as $field => $value) {
495 $additionalWhere .= ' AND ' . $field . '=' . $GLOBALS['TYPO3_DB']->fullQuoteStr($value, $MM_tableName);
498 $GLOBALS['TYPO3_DB']->exec_UPDATEquery($MM_tableName, $uidLocal_field . '=' . intval($uid) . $additionalWhere_tablenames . $additionalWhere, array($uidLocal_field => $newUid));
503 * Reads items from a foreign_table, that has a foreign_field (uid of the parent record) and
504 * stores the parts in the internal array itemArray and tableArray.
506 * @param integer $uid: The uid of the parent record (this value is also on the foreign_table in the foreign_field)
507 * @param array $conf: TCA configuration for current field
510 function readForeignField($uid, $conf) {
514 $foreign_table = $conf['foreign_table'];
515 $foreign_table_field = $conf['foreign_table_field'];
516 $useDeleteClause = $this->undeleteRecord ?
FALSE : TRUE;
517 $foreign_match_fields = is_array($conf['foreign_match_fields']) ?
$conf['foreign_match_fields'] : array();
519 // search for $uid in foreign_field, and if we have symmetric relations, do this also on symmetric_field
520 if ($conf['symmetric_field']) {
521 $whereClause = '(' . $conf['foreign_field'] . '=' . $uid . ' OR ' . $conf['symmetric_field'] . '=' . $uid . ')';
523 $whereClause = $conf['foreign_field'] . '=' . $uid;
525 // use the deleteClause (e.g. "deleted=0") on this table
526 if ($useDeleteClause) {
527 $whereClause .= t3lib_BEfunc
::deleteClause($foreign_table);
529 // if it's requested to look for the parent uid AND the parent table,
530 // add an additional SQL-WHERE clause
531 if ($foreign_table_field && $this->currentTable
) {
532 $whereClause .= ' AND ' . $foreign_table_field . '=' . $GLOBALS['TYPO3_DB']->fullQuoteStr($this->currentTable
, $foreign_table);
535 // Add additional where clause if foreign_match_fields are defined
536 foreach ($foreign_match_fields as $field => $value) {
537 $whereClause .= ' AND ' . $field . '=' . $GLOBALS['TYPO3_DB']->fullQuoteStr($value, $foreign_table);
540 // Select children in the same workspace:
541 if (t3lib_BEfunc
::isTableWorkspaceEnabled($this->currentTable
) && t3lib_BEfunc
::isTableWorkspaceEnabled($foreign_table)) {
542 $currentRecord = t3lib_BEfunc
::getRecord($this->currentTable
, $uid, 't3ver_wsid', '', $useDeleteClause);
543 $whereClause .= t3lib_BEfunc
::getWorkspaceWhereClause($foreign_table, $currentRecord['t3ver_wsid']);
546 // get the correct sorting field
547 if ($conf['foreign_sortby']) { // specific manual sortby for data handled by this field
548 if ($conf['symmetric_sortby'] && $conf['symmetric_field']) {
549 // sorting depends on, from which side of the relation we're looking at it
552 WHEN ' . $conf['foreign_field'] . '=' . $uid . '
553 THEN ' . $conf['foreign_sortby'] . '
554 ELSE ' . $conf['symmetric_sortby'] . '
557 // regular single-side behaviour
558 $sortby = $conf['foreign_sortby'];
560 } elseif ($conf['foreign_default_sortby']) { // specific default sortby for data handled by this field
561 $sortby = $conf['foreign_default_sortby'];
562 } elseif ($GLOBALS['TCA'][$foreign_table]['ctrl']['sortby']) { // manual sortby for all table records
563 $sortby = $GLOBALS['TCA'][$foreign_table]['ctrl']['sortby'];
564 } elseif ($GLOBALS['TCA'][$foreign_table]['ctrl']['default_sortby']) { // default sortby for all table records
565 $sortby = $GLOBALS['TCA'][$foreign_table]['ctrl']['default_sortby'];
568 // strip a possible "ORDER BY" in front of the $sortby value
569 $sortby = $GLOBALS['TYPO3_DB']->stripOrderBy($sortby);
570 // get the rows from storage
571 $rows = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows('uid', $foreign_table, $whereClause, '', $sortby);
574 foreach ($rows as $row) {
575 $this->itemArray
[$key]['id'] = $row['uid'];
576 $this->itemArray
[$key]['table'] = $foreign_table;
577 $this->tableArray
[$foreign_table][] = $row['uid'];
584 * Write the sorting values to a foreign_table, that has a foreign_field (uid of the parent record)
586 * @param array $conf: TCA configuration for current field
587 * @param integer $parentUid: The uid of the parent record
588 * @param boolean $updateToUid: Whether to update the foreign field with the $parentUid (on Copy)
589 * @param boolean $skipSorting: Do not update the sorting columns, this could happen for imported values
592 function writeForeignField($conf, $parentUid, $updateToUid = 0, $skipSorting = FALSE) {
594 $foreign_table = $conf['foreign_table'];
595 $foreign_field = $conf['foreign_field'];
596 $symmetric_field = $conf['symmetric_field'];
597 $foreign_table_field = $conf['foreign_table_field'];
598 $foreign_match_fields = is_array($conf['foreign_match_fields']) ?
$conf['foreign_match_fields'] : array();
600 // if there are table items and we have a proper $parentUid
601 if (t3lib_utility_Math
::canBeInterpretedAsInteger($parentUid) && count($this->tableArray
)) {
602 // if updateToUid is not a positive integer, set it to '0', so it will be ignored
603 if (!(t3lib_utility_Math
::canBeInterpretedAsInteger($updateToUid) && $updateToUid > 0)) {
607 $considerWorkspaces = ($GLOBALS['BE_USER']->workspace
!== 0 && t3lib_BEfunc
::isTableWorkspaceEnabled($foreign_table));
609 $fields = 'uid,' . $foreign_field;
610 // Consider the symmetric field if defined:
611 if ($symmetric_field) {
612 $fields .= ',' . $symmetric_field;
614 // Consider workspaces if defined and currently used:
615 if ($considerWorkspaces) {
616 $fields .= ',' . 't3ver_state,t3ver_oid';
620 foreach ($this->itemArray
as $val) {
622 $table = $val['table'];
624 // fetch the current (not overwritten) relation record if we should handle symmetric relations
625 if ($symmetric_field ||
$considerWorkspaces) {
626 $row = t3lib_BEfunc
::getRecord($table, $uid, $fields, '', FALSE);
628 if ($symmetric_field) {
629 $isOnSymmetricSide = t3lib_loadDBGroup
::isOnSymmetricSide($parentUid, $conf, $row);
632 $updateValues = $foreign_match_fields;
633 $workspaceValues = array();
635 // no update to the uid is requested, so this is the normal behaviour
636 // just update the fields and care about sorting
638 // Always add the pointer to the parent uid
639 if ($isOnSymmetricSide) {
640 $updateValues[$symmetric_field] = $parentUid;
642 $updateValues[$foreign_field] = $parentUid;
645 // if it is configured in TCA also to store the parent table in the child record, just do it
646 if ($foreign_table_field && $this->currentTable
) {
647 $updateValues[$foreign_table_field] = $this->currentTable
;
650 // update sorting columns if not to be skipped
652 // get the correct sorting field
653 if ($conf['foreign_sortby']) { // specific manual sortby for data handled by this field
654 $sortby = $conf['foreign_sortby'];
655 } elseif ($GLOBALS['TCA'][$foreign_table]['ctrl']['sortby']) { // manual sortby for all table records
656 $sortby = $GLOBALS['TCA'][$foreign_table]['ctrl']['sortby'];
658 // Apply sorting on the symmetric side (it depends on who created the relation, so what uid is in the symmetric_field):
659 if ($isOnSymmetricSide && isset($conf['symmetric_sortby']) && $conf['symmetric_sortby']) {
660 $sortby = $conf['symmetric_sortby'];
661 // Strip a possible "ORDER BY" in front of the $sortby value:
663 $sortby = $GLOBALS['TYPO3_DB']->stripOrderBy($sortby);
667 $updateValues[$sortby] = $workspaceValues[$sortby] = ++
$c;
671 // update to a foreign_field/symmetric_field pointer is requested, normally used on record copies
672 // only update the fields, if the old uid is found somewhere - for select fields, TCEmain is doing this already!
674 if ($isOnSymmetricSide) {
675 $updateValues[$symmetric_field] = $updateToUid;
677 $updateValues[$foreign_field] = $updateToUid;
681 // Update accordant fields in the database:
682 if (count($updateValues)) {
683 $GLOBALS['TYPO3_DB']->exec_UPDATEquery($table, 'uid=' . intval($uid), $updateValues);
684 $this->updateRefIndex($table, $uid);
686 // Update accordant fields in the database for workspaces overlays/placeholders:
687 if (count($workspaceValues) && $considerWorkspaces) {
688 if (isset($row['t3ver_oid']) && $row['t3ver_oid'] && $row['t3ver_state'] == -1) {
689 $GLOBALS['TYPO3_DB']->exec_UPDATEquery($table, 'uid=' . intval($row['t3ver_oid']), $workspaceValues);
697 * After initialization you can extract an array of the elements from the object. Use this function for that.
699 * @param boolean If set, then table names will ALWAYS be prepended (unless its a _NO_TABLE value)
700 * @return array A numeric array.
702 function getValueArray($prependTableName = '') {
704 $valueArray = Array();
705 $tableC = count($this->tableArray
);
707 // If there are tables in the table array:
709 // If there are more than ONE table in the table array, then always prepend table names:
710 $prep = ($tableC > 1 ||
$prependTableName) ?
1 : 0;
712 // Traverse the array of items:
713 foreach ($this->itemArray
as $val) {
714 $valueArray[] = (($prep && $val['table'] != '_NO_TABLE') ?
$val['table'] . '_' : '') .
723 * Converts id numbers from negative to positive.
725 * @param array Array of [table]_[id] pairs.
726 * @param string Foreign table (the one used for positive numbers)
727 * @param string NEGative foreign table
728 * @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.
730 function convertPosNeg($valueArray, $fTable, $nfTable) {
731 if (is_array($valueArray) && $fTable) {
732 foreach ($valueArray as $key => $val) {
734 $parts = explode('_', $val, 2);
735 $theID = strrev($parts[0]);
736 $theTable = strrev($parts[1]);
738 if (t3lib_utility_Math
::canBeInterpretedAsInteger($theID) && (!$theTable ||
!strcmp($theTable, $fTable) ||
!strcmp($theTable, $nfTable))) {
739 $valueArray[$key] = $theTable && strcmp($theTable, $fTable) ?
$theID * -1 : $theID;
747 * 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.
748 * If $this->fromTC is set you can save a little memory since only uid,pid and a few other fields are selected.
752 function getFromDB() {
753 // Traverses the tables listed:
754 foreach ($this->tableArray
as $key => $val) {
755 if (is_array($val)) {
756 $itemList = implode(',', $val);
761 if ($GLOBALS['TCA'][$key]['ctrl']['label']) {
762 $from .= ',' . $GLOBALS['TCA'][$key]['ctrl']['label']; // Titel
764 if ($GLOBALS['TCA'][$key]['ctrl']['label_alt']) {
765 $from .= ',' . $GLOBALS['TCA'][$key]['ctrl']['label_alt']; // Alternative Title-Fields
767 if ($GLOBALS['TCA'][$key]['ctrl']['thumbnail']) {
768 $from .= ',' . $GLOBALS['TCA'][$key]['ctrl']['thumbnail']; // Thumbnail
771 $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery($from, $key, 'uid IN (' . $itemList . ')' . $this->additionalWhere
[$key]);
772 while ($row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res)) {
773 $this->results
[$key][$row['uid']] = $row;
778 return $this->results
;
782 * Prepare items from itemArray to be transferred to the TCEforms interface (as a comma list)
785 * @see t3lib_transferdata::renderRecord()
787 function readyForInterface() {
788 if (!is_array($this->itemArray
)) {
793 $perms_clause = $GLOBALS['BE_USER']->getPagePermsClause(1); // For use when getting the paths....
794 $titleLen = intval($GLOBALS['BE_USER']->uc
['titleLen']);
796 foreach ($this->itemArray
as $key => $val) {
797 $theRow = $this->results
[$val['table']][$val['id']];
798 if ($theRow && is_array($GLOBALS['TCA'][$val['table']])) {
799 $label = t3lib_div
::fixed_lgd_cs(strip_tags(t3lib_BEfunc
::getRecordTitle($val['table'], $theRow)), $titleLen);
800 $label = ($label) ?
$label : '[...]';
801 $output[] = str_replace(',', '', $val['table'] . '_' . $val['id'] . '|' . rawurlencode($label));
804 return implode(',', $output);
808 * Counts the items in $this->itemArray and puts this value in an array by default.
810 * @param boolean Whether to put the count value in an array
811 * @return mixed The plain count as integer or the same inside an array
813 function countItems($returnAsArray = TRUE) {
814 $count = count($this->itemArray
);
815 if ($returnAsArray) {
816 $count = array($count);
822 * Update Reference Index (sys_refindex) for a record
823 * Should be called any almost any update to a record which could affect references inside the record.
824 * (copied from TCEmain)
826 * @param string Table name
827 * @param integer Record UID
828 * @return array Information concerning modifications delivered by t3lib_refindex::updateRefIndexTable()
830 function updateRefIndex($table, $id) {
831 if ($this->updateReferenceIndex
=== TRUE) {
832 /** @var $refIndexObj t3lib_refindex */
833 $refIndexObj = t3lib_div
::makeInstance('t3lib_refindex');
834 return $refIndexObj->updateRefIndexTable($table, $id);
839 * Checks, if we're looking from the "other" side, the symmetric side, to a symmetric relation.
841 * @param string $parentUid: The uid of the parent record
842 * @param array $parentConf: The TCA configuration of the parent field embedding the child records
843 * @param array $childRec: The record row of the child record
844 * @return boolean Returns TRUE if looking from the symmetric ("other") side to the relation.
846 function isOnSymmetricSide($parentUid, $parentConf, $childRec) {
847 return t3lib_utility_Math
::canBeInterpretedAsInteger($childRec['uid']) && $parentConf['symmetric_field'] && $parentUid == $childRec[$parentConf['symmetric_field']]