[BUGFIX] Use t3lib_cache_frontend_VariableFrontend as fallback frontend
[Packages/TYPO3.CMS.git] / t3lib / class.t3lib_loaddbgroup.php
1 <?php
2 /***************************************************************
3 * Copyright notice
4 *
5 * (c) 1999-2011 Kasper Skårhøj (kasperYYYY@typo3.com)
6 * All rights reserved
7 *
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.
13 *
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.
18 *
19 *
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.
24 *
25 * This copyright notice MUST APPEAR in all copies of the script!
26 ***************************************************************/
27 /**
28 * Contains class for loading database groups
29 *
30 * Revised for TYPO3 3.6 September/2003 by Kasper Skårhøj
31 *
32 * @author Kasper Skårhøj <kasperYYYY@typo3.com>
33 */
34 /**
35 * [CLASS/FUNCTION INDEX of SCRIPT]
36 *
37 *
38 *
39 * 76: class t3lib_loadDBGroup
40 * 111: function start($itemlist, $tablelist, $MMtable='', $MMuid=0, $currentTable='', $conf=array())
41 * 179: function readList($itemlist)
42 * 225: function readMM($tableName,$uid)
43 * 276: function writeMM($tableName,$uid,$prependTableName=0)
44 * 352: function readForeignField($uid, $conf)
45 * 435: function writeForeignField($conf, $parentUid, $updateToUid=0)
46 * 510: function getValueArray($prependTableName='')
47 * 538: function convertPosNeg($valueArray,$fTable,$nfTable)
48 * 560: function getFromDB()
49 * 595: function readyForInterface()
50 * 621: function countItems($returnAsArray = TRUE)
51 * 636: function updateRefIndex($table,$id)
52 *
53 * TOTAL FUNCTIONS: 12
54 * (This index is automatically created/updated by the extension "extdeveval")
55 *
56 */
57
58
59 /**
60 * Load database groups (relations)
61 * Used to process the relations created by the TCA element types "group" and "select" for database records. Manages MM-relations as well.
62 *
63 * @author Kasper Skårhøj <kasperYYYY@typo3.com>
64 * @package TYPO3
65 * @subpackage t3lib
66 */
67 class t3lib_loadDBGroup {
68 // External, static:
69 var $fromTC = 1; // Means that only uid and the label-field is returned
70 var $registerNonTableValues = 0; // If set, values that are not ids in tables are normally discarded. By this options they will be preserved.
71
72 // Internal, dynamic:
73 var $tableArray = Array(); // Contains the table names as keys. The values are the id-values for each table. Should ONLY contain proper table names.
74 var $itemArray = Array(); // Contains items in an numeric array (table/id for each). Tablenames here might be "_NO_TABLE"
75 var $nonTableArray = array(); // Array for NON-table elements
76 var $additionalWhere = array();
77 var $checkIfDeleted = 1; // deleted-column is added to additionalWhere... if this is set...
78 var $dbPaths = Array();
79 var $firstTable = ''; // Will contain the first table name in the $tablelist (for positive ids)
80 var $secondTable = ''; // Will contain the second table name in the $tablelist (for negative ids)
81 // private
82 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"
83 var $MM_oppositeField = ''; // field name at the "local" side of the MM relation
84 var $MM_oppositeTable = ''; // only set if MM_is_foreign is set
85 var $MM_oppositeFieldConf = ''; // only set if MM_is_foreign is set
86 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)
87 var $currentTable; // current table => Only needed for reverse relations
88 var $undeleteRecord; // if a record should be undeleted (so do not use the $useDeleteClause on t3lib_BEfunc)
89
90
91 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
92 var $MM_insert_fields = array(); // array of fields and value pairs used for insert in MM table
93 var $MM_table_where = ''; // extra MM table where
94
95 /**
96 * @var boolean
97 */
98 protected $updateReferenceIndex = TRUE;
99
100 /**
101 * Initialization of the class.
102 *
103 * @param string List of group/select items
104 * @param string Comma list of tables, first table takes priority if no table is set for an entry in the list.
105 * @param string Name of a MM table.
106 * @param integer Local UID for MM lookup
107 * @param string current table name
108 * @param integer TCA configuration for current field
109 * @return void
110 */
111 function start($itemlist, $tablelist, $MMtable = '', $MMuid = 0, $currentTable = '', $conf = array()) {
112 // SECTION: MM reverse relations
113 $this->MM_is_foreign = ($conf['MM_opposite_field'] ? 1 : 0);
114 $this->MM_oppositeField = $conf['MM_opposite_field'];
115 $this->MM_table_where = $conf['MM_table_where'];
116 $this->MM_hasUidField = $conf['MM_hasUidField'];
117 $this->MM_match_fields = is_array($conf['MM_match_fields']) ? $conf['MM_match_fields'] : array();
118 $this->MM_insert_fields = is_array($conf['MM_insert_fields']) ? $conf['MM_insert_fields'] : $this->MM_match_fields;
119
120 $this->currentTable = $currentTable;
121 if ($this->MM_is_foreign) {
122 $tmp = ($conf['type'] === 'group' ? $conf['allowed'] : $conf['foreign_table']);
123 // 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']
124 $tmp = t3lib_div::trimExplode(',', $tmp);
125 $this->MM_oppositeTable = $tmp[0];
126 unset($tmp);
127
128 // only add the current table name if there is more than one allowed field
129 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.
130 $this->MM_oppositeFieldConf = $GLOBALS['TCA'][$this->MM_oppositeTable]['columns'][$this->MM_oppositeField]['config'];
131
132 if ($this->MM_oppositeFieldConf['allowed']) {
133 $oppositeFieldConf_allowed = explode(',', $this->MM_oppositeFieldConf['allowed']);
134 if (count($oppositeFieldConf_allowed) > 1) {
135 $this->MM_isMultiTableRelationship = $oppositeFieldConf_allowed[0];
136 }
137 }
138 }
139
140 // SECTION: normal MM relations
141
142 // If the table list is "*" then all tables are used in the list:
143 if (!strcmp(trim($tablelist), '*')) {
144 $tablelist = implode(',', array_keys($GLOBALS['TCA']));
145 }
146
147 // The tables are traversed and internal arrays are initialized:
148 $tempTableArray = t3lib_div::trimExplode(',', $tablelist, 1);
149 foreach ($tempTableArray as $key => $val) {
150 $tName = trim($val);
151 $this->tableArray[$tName] = Array();
152 if ($this->checkIfDeleted && $GLOBALS['TCA'][$tName]['ctrl']['delete']) {
153 $fieldN = $tName . '.' . $GLOBALS['TCA'][$tName]['ctrl']['delete'];
154 $this->additionalWhere[$tName] .= ' AND ' . $fieldN . '=0';
155 }
156 }
157
158 if (is_array($this->tableArray)) {
159 reset($this->tableArray);
160 } else {
161 return 'No tables!';
162 }
163
164 // Set first and second tables:
165 $this->firstTable = key($this->tableArray); // Is the first table
166 next($this->tableArray);
167 $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...
168
169 // Now, populate the internal itemArray and tableArray arrays:
170 if ($MMtable) { // If MM, then call this function to do that:
171 if ($MMuid) {
172 $this->readMM($MMtable, $MMuid);
173 } else { // Revert to readList() for new records in order to load possible default values from $itemlist
174 $this->readList($itemlist);
175 }
176 } elseif ($MMuid && $conf['foreign_field']) {
177 // If not MM but foreign_field, the read the records by the foreign_field
178 $this->readForeignField($MMuid, $conf);
179 } else {
180 // If not MM, then explode the itemlist by "," and traverse the list:
181 $this->readList($itemlist);
182 // do automatic default_sortby, if any
183 if ($conf['foreign_default_sortby']) {
184 $this->sortList($conf['foreign_default_sortby']);
185 }
186 }
187 }
188
189 /**
190 * Sets whether the reference index shall be updated.
191 *
192 * @param boolean $updateReferenceIndex Whether the reference index shall be updated
193 * @return void
194 */
195 public function setUpdateReferenceIndex($updateReferenceIndex) {
196 $this->updateReferenceIndex = (bool) $updateReferenceIndex;
197 }
198
199 /**
200 * Explodes the item list and stores the parts in the internal arrays itemArray and tableArray from MM records.
201 *
202 * @param string Item list
203 * @return void
204 */
205 function readList($itemlist) {
206 if ((string) trim($itemlist) != '') {
207 $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...
208 foreach ($tempItemArray as $key => $val) {
209 $isSet = 0; // Will be set to "1" if the entry was a real table/id:
210
211 // Extract table name and id. This is un the formular [tablename]_[id] where table name MIGHT contain "_", hence the reversion of the string!
212 $val = strrev($val);
213 $parts = explode('_', $val, 2);
214 $theID = strrev($parts[0]);
215
216 // Check that the id IS an integer:
217 if (t3lib_div::testInt($theID)) {
218 // 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
219 $theTable = trim($parts[1]) ? strrev(trim($parts[1])) : ($this->secondTable && $theID < 0 ? $this->secondTable : $this->firstTable);
220 // If the ID is not blank and the table name is among the names in the inputted tableList, then proceed:
221 if ((string) $theID != '' && $theID && $theTable && isset($this->tableArray[$theTable])) {
222 // Get ID as the right value:
223 $theID = $this->secondTable ? abs(intval($theID)) : intval($theID);
224 // Register ID/table name in internal arrays:
225 $this->itemArray[$key]['id'] = $theID;
226 $this->itemArray[$key]['table'] = $theTable;
227 $this->tableArray[$theTable][] = $theID;
228 // Set update-flag:
229 $isSet = 1;
230 }
231 }
232
233 // 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:
234 if (!$isSet && $this->registerNonTableValues) {
235 $this->itemArray[$key]['id'] = $tempItemArray[$key];
236 $this->itemArray[$key]['table'] = '_NO_TABLE';
237 $this->nonTableArray[] = $tempItemArray[$key];
238 }
239 }
240 }
241 }
242
243 /**
244 * Does a sorting on $this->itemArray depending on a default sortby field.
245 * This is only used for automatic sorting of comma separated lists.
246 * This function is only relevant for data that is stored in comma separated lists!
247 *
248 * @param string $sortby: The default_sortby field/command (e.g. 'price DESC')
249 * @return void
250 */
251 function sortList($sortby) {
252 // sort directly without fetching addional data
253 if ($sortby == 'uid') {
254 usort($this->itemArray, create_function('$a,$b', 'return $a["id"] < $b["id"] ? -1 : 1;'));
255 // only useful if working on the same table
256 } elseif (count($this->tableArray) == 1) {
257 reset($this->tableArray);
258 $table = key($this->tableArray);
259 $uidList = implode(',', current($this->tableArray));
260
261 if ($uidList) {
262 $this->itemArray = array();
263 $this->tableArray = array();
264
265 $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery('uid', $table, 'uid IN (' . $uidList . ')', '', $sortby);
266 while ($row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res)) {
267 $this->itemArray[] = array('id' => $row['uid'], 'table' => $table);
268 $this->tableArray[$table][] = $row['uid'];
269 }
270 $GLOBALS['TYPO3_DB']->sql_free_result($res);
271 }
272 }
273 }
274
275 /**
276 * Reads the record tablename/id into the internal arrays itemArray and tableArray from MM records.
277 * You can call this function after start if you supply no list to start()
278 *
279 * @param string MM Tablename
280 * @param integer Local UID
281 * @return void
282 */
283 function readMM($tableName, $uid) {
284 $key = 0;
285 $additionalWhere = '';
286
287 if ($this->MM_is_foreign) { // in case of a reverse relation
288 $uidLocal_field = 'uid_foreign';
289 $uidForeign_field = 'uid_local';
290 $sorting_field = 'sorting_foreign';
291
292 if ($this->MM_isMultiTableRelationship) {
293 $additionalWhere .= ' AND ( tablenames=' . $GLOBALS['TYPO3_DB']->fullQuoteStr($this->currentTable, $tableName);
294 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.
295 $additionalWhere .= ' OR tablenames=\'\'';
296 }
297 $additionalWhere .= ' ) ';
298 }
299 $theTable = $this->MM_oppositeTable;
300 } else { // default
301 $uidLocal_field = 'uid_local';
302 $uidForeign_field = 'uid_foreign';
303 $sorting_field = 'sorting';
304 }
305
306
307 if ($this->MM_table_where) {
308 $additionalWhere .= LF . str_replace('###THIS_UID###', intval($uid), $this->MM_table_where);
309 }
310 foreach ($this->MM_match_fields as $field => $value) {
311 $additionalWhere .= ' AND ' . $field . '=' . $GLOBALS['TYPO3_DB']->fullQuoteStr($value, $tableName);
312 }
313
314 // Select all MM relations:
315 $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery('*', $tableName, $uidLocal_field . '=' . intval($uid) . $additionalWhere, '', $sorting_field);
316 while ($row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res)) {
317 if (!$this->MM_is_foreign) { // default
318 $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...
319 }
320 if (($row[$uidForeign_field] || $theTable == 'pages') && $theTable && isset($this->tableArray[$theTable])) {
321
322 $this->itemArray[$key]['id'] = $row[$uidForeign_field];
323 $this->itemArray[$key]['table'] = $theTable;
324 $this->tableArray[$theTable][] = $row[$uidForeign_field];
325 } elseif ($this->registerNonTableValues) {
326 $this->itemArray[$key]['id'] = $row[$uidForeign_field];
327 $this->itemArray[$key]['table'] = '_NO_TABLE';
328 $this->nonTableArray[] = $row[$uidForeign_field];
329 }
330 $key++;
331 }
332 $GLOBALS['TYPO3_DB']->sql_free_result($res);
333 }
334
335 /**
336 * Writes the internal itemArray to MM table:
337 *
338 * @param string MM table name
339 * @param integer Local UID
340 * @param boolean If set, then table names will always be written.
341 * @return void
342 */
343 function writeMM($MM_tableName, $uid, $prependTableName = 0) {
344
345 if ($this->MM_is_foreign) { // in case of a reverse relation
346 $uidLocal_field = 'uid_foreign';
347 $uidForeign_field = 'uid_local';
348 $sorting_field = 'sorting_foreign';
349 } else { // default
350 $uidLocal_field = 'uid_local';
351 $uidForeign_field = 'uid_foreign';
352 $sorting_field = 'sorting';
353 }
354
355 // If there are tables...
356 $tableC = count($this->tableArray);
357 if ($tableC) {
358 $prep = ($tableC > 1 || $prependTableName || $this->MM_isMultiTableRelationship) ? 1 : 0; // boolean: does the field "tablename" need to be filled?
359 $c = 0;
360
361 $additionalWhere_tablenames = '';
362 if ($this->MM_is_foreign && $prep) {
363 $additionalWhere_tablenames = ' AND tablenames=' . $GLOBALS['TYPO3_DB']->fullQuoteStr($this->currentTable, $MM_tableName);
364 }
365
366 $additionalWhere = '';
367 // add WHERE clause if configured
368 if ($this->MM_table_where) {
369 $additionalWhere .= LF . str_replace('###THIS_UID###', intval($uid), $this->MM_table_where);
370 }
371 // Select, update or delete only those relations that match the configured fields
372 foreach ($this->MM_match_fields as $field => $value) {
373 $additionalWhere .= ' AND ' . $field . '=' . $GLOBALS['TYPO3_DB']->fullQuoteStr($value, $MM_tableName);
374 }
375
376 $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery(
377 $uidForeign_field . ($prep ? ', tablenames' : '') . ($this->MM_hasUidField ? ', uid' : ''),
378 $MM_tableName,
379 $uidLocal_field . '=' . $uid . $additionalWhere_tablenames . $additionalWhere,
380 '',
381 $sorting_field
382 );
383
384 $oldMMs = array();
385 $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
386 while ($row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res)) {
387 if (!$this->MM_is_foreign && $prep) {
388 $oldMMs[] = array($row['tablenames'], $row[$uidForeign_field]);
389 } else {
390 $oldMMs[] = $row[$uidForeign_field];
391 }
392 $oldMMs_inclUid[] = array($row['tablenames'], $row[$uidForeign_field], $row['uid']);
393 }
394
395 // For each item, insert it:
396 foreach ($this->itemArray as $val) {
397 $c++;
398
399 if ($prep || $val['table'] == '_NO_TABLE') {
400 if ($this->MM_is_foreign) { // insert current table if needed
401 $tablename = $this->currentTable;
402 } else {
403 $tablename = $val['table'];
404 }
405 } else {
406 $tablename = '';
407 }
408
409 if (!$this->MM_is_foreign && $prep) {
410 $item = array($val['table'], $val['id']);
411 } else {
412 $item = $val['id'];
413 }
414
415 if (in_array($item, $oldMMs)) {
416 $oldMMs_index = array_search($item, $oldMMs);
417
418 $whereClause = $uidLocal_field . '=' . $uid . ' AND ' . $uidForeign_field . '=' . $val['id'] .
419 ($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.
420 if ($tablename) {
421 $whereClause .= ' AND tablenames=' . $GLOBALS['TYPO3_DB']->fullQuoteStr($tablename, $MM_tableName);
422 }
423 $GLOBALS['TYPO3_DB']->exec_UPDATEquery($MM_tableName, $whereClause . $additionalWhere, array($sorting_field => $c));
424
425 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.
426 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.
427 } else {
428
429 $insertFields = $this->MM_insert_fields;
430 $insertFields[$uidLocal_field] = $uid;
431 $insertFields[$uidForeign_field] = $val['id'];
432 $insertFields[$sorting_field] = $c;
433 if ($tablename) {
434 $insertFields['tablenames'] = $tablename;
435 }
436
437 $GLOBALS['TYPO3_DB']->exec_INSERTquery($MM_tableName, $insertFields);
438
439 if ($this->MM_is_foreign) {
440 $this->updateRefIndex($val['table'], $val['id']);
441 }
442 }
443 }
444
445 // Delete all not-used relations:
446 if (is_array($oldMMs) && count($oldMMs) > 0) {
447 $removeClauses = array();
448 $updateRefIndex_records = array();
449 foreach ($oldMMs as $oldMM_key => $mmItem) {
450 if ($this->MM_hasUidField) { // If UID field is present, of course we need only use that for deleting...:
451 $removeClauses[] = 'uid=' . intval($oldMMs_inclUid[$oldMM_key][2]);
452 $elDelete = $oldMMs_inclUid[$oldMM_key];
453 } else {
454 if (is_array($mmItem)) {
455 $removeClauses[] = 'tablenames=' . $GLOBALS['TYPO3_DB']->fullQuoteStr($mmItem[0], $MM_tableName) . ' AND ' . $uidForeign_field . '=' . $mmItem[1];
456 } else {
457 $removeClauses[] = $uidForeign_field . '=' . $mmItem;
458 }
459 }
460 if ($this->MM_is_foreign) {
461 if (is_array($mmItem)) {
462 $updateRefIndex_records[] = array($mmItem[0], $mmItem[1]);
463 } else {
464 $updateRefIndex_records[] = array($this->firstTable, $mmItem);
465 }
466 }
467 }
468 $deleteAddWhere = ' AND (' . implode(' OR ', $removeClauses) . ')';
469 $GLOBALS['TYPO3_DB']->exec_DELETEquery($MM_tableName, $uidLocal_field . '=' . intval($uid) . $deleteAddWhere . $additionalWhere_tablenames . $additionalWhere);
470
471 // Update ref index:
472 foreach ($updateRefIndex_records as $pair) {
473 $this->updateRefIndex($pair[0], $pair[1]);
474 }
475 }
476
477 // 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 ...
478 $this->updateRefIndex($this->currentTable, $uid);
479 }
480 }
481
482 /**
483 * Remaps MM table elements from one local uid to another
484 * Does NOT update the reference index for you, must be called subsequently to do that!
485 *
486 * @param string MM table name
487 * @param integer Local, current UID
488 * @param integer Local, new UID
489 * @param boolean If set, then table names will always be written.
490 * @return void
491 */
492 function remapMM($MM_tableName, $uid, $newUid, $prependTableName = 0) {
493
494 if ($this->MM_is_foreign) { // in case of a reverse relation
495 $uidLocal_field = 'uid_foreign';
496 } else { // default
497 $uidLocal_field = 'uid_local';
498 }
499
500 // If there are tables...
501 $tableC = count($this->tableArray);
502 if ($tableC) {
503 $prep = ($tableC > 1 || $prependTableName || $this->MM_isMultiTableRelationship) ? 1 : 0; // boolean: does the field "tablename" need to be filled?
504 $c = 0;
505
506 $additionalWhere_tablenames = '';
507 if ($this->MM_is_foreign && $prep) {
508 $additionalWhere_tablenames = ' AND tablenames=' . $GLOBALS['TYPO3_DB']->fullQuoteStr($this->currentTable, $MM_tableName);
509 }
510
511 $additionalWhere = '';
512 // add WHERE clause if configured
513 if ($this->MM_table_where) {
514 $additionalWhere .= LF . str_replace('###THIS_UID###', intval($uid), $this->MM_table_where);
515 }
516 // Select, update or delete only those relations that match the configured fields
517 foreach ($this->MM_match_fields as $field => $value) {
518 $additionalWhere .= ' AND ' . $field . '=' . $GLOBALS['TYPO3_DB']->fullQuoteStr($value, $MM_tableName);
519 }
520
521 $GLOBALS['TYPO3_DB']->exec_UPDATEquery($MM_tableName, $uidLocal_field . '=' . intval($uid) . $additionalWhere_tablenames . $additionalWhere, array($uidLocal_field => $newUid));
522 }
523 }
524
525 /**
526 * Reads items from a foreign_table, that has a foreign_field (uid of the parent record) and
527 * stores the parts in the internal array itemArray and tableArray.
528 *
529 * @param integer $uid: The uid of the parent record (this value is also on the foreign_table in the foreign_field)
530 * @param array $conf: TCA configuration for current field
531 * @return void
532 */
533 function readForeignField($uid, $conf) {
534 $key = 0;
535 $uid = intval($uid);
536 $whereClause = '';
537 $foreign_table = $conf['foreign_table'];
538 $foreign_table_field = $conf['foreign_table_field'];
539 $useDeleteClause = $this->undeleteRecord ? FALSE : TRUE;
540
541 // search for $uid in foreign_field, and if we have symmetric relations, do this also on symmetric_field
542 if ($conf['symmetric_field']) {
543 $whereClause = '(' . $conf['foreign_field'] . '=' . $uid . ' OR ' . $conf['symmetric_field'] . '=' . $uid . ')';
544 } else {
545 $whereClause = $conf['foreign_field'] . '=' . $uid;
546 }
547 // use the deleteClause (e.g. "deleted=0") on this table
548 if ($useDeleteClause) {
549 $whereClause .= t3lib_BEfunc::deleteClause($foreign_table);
550 }
551 // if it's requested to look for the parent uid AND the parent table,
552 // add an additional SQL-WHERE clause
553 if ($foreign_table_field && $this->currentTable) {
554 $whereClause .= ' AND ' . $foreign_table_field . '=' . $GLOBALS['TYPO3_DB']->fullQuoteStr($this->currentTable, $foreign_table);
555 }
556
557 // Select children in the same workspace:
558 if (t3lib_BEfunc::isTableWorkspaceEnabled($this->currentTable) && t3lib_BEfunc::isTableWorkspaceEnabled($foreign_table)) {
559 $currentRecord = t3lib_BEfunc::getRecord($this->currentTable, $uid, 't3ver_wsid', '', $useDeleteClause);
560 $whereClause .= t3lib_BEfunc::getWorkspaceWhereClause($foreign_table, $currentRecord['t3ver_wsid']);
561 }
562
563 // get the correct sorting field
564 if ($conf['foreign_sortby']) { // specific manual sortby for data handled by this field
565 if ($conf['symmetric_sortby'] && $conf['symmetric_field']) {
566 // sorting depends on, from which side of the relation we're looking at it
567 $sortby = '
568 CASE
569 WHEN ' . $conf['foreign_field'] . '=' . $uid . '
570 THEN ' . $conf['foreign_sortby'] . '
571 ELSE ' . $conf['symmetric_sortby'] . '
572 END';
573 } else {
574 // regular single-side behaviour
575 $sortby = $conf['foreign_sortby'];
576 }
577 } elseif ($conf['foreign_default_sortby']) { // specific default sortby for data handled by this field
578 $sortby = $conf['foreign_default_sortby'];
579 } elseif ($GLOBALS['TCA'][$foreign_table]['ctrl']['sortby']) { // manual sortby for all table records
580 $sortby = $GLOBALS['TCA'][$foreign_table]['ctrl']['sortby'];
581 } elseif ($GLOBALS['TCA'][$foreign_table]['ctrl']['default_sortby']) { // default sortby for all table records
582 $sortby = $GLOBALS['TCA'][$foreign_table]['ctrl']['default_sortby'];
583 }
584
585 // strip a possible "ORDER BY" in front of the $sortby value
586 $sortby = $GLOBALS['TYPO3_DB']->stripOrderBy($sortby);
587 // get the rows from storage
588 $rows = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows('uid', $foreign_table, $whereClause, '', $sortby);
589
590 if (count($rows)) {
591 foreach ($rows as $row) {
592 $this->itemArray[$key]['id'] = $row['uid'];
593 $this->itemArray[$key]['table'] = $foreign_table;
594 $this->tableArray[$foreign_table][] = $row['uid'];
595 $key++;
596 }
597 }
598 }
599
600 /**
601 * Write the sorting values to a foreign_table, that has a foreign_field (uid of the parent record)
602 *
603 * @param array $conf: TCA configuration for current field
604 * @param integer $parentUid: The uid of the parent record
605 * @param boolean $updateToUid: Whether to update the foreign field with the $parentUid (on Copy)
606 * @param boolean $skipSorting: Do not update the sorting columns, this could happen for imported values
607 * @return void
608 */
609 function writeForeignField($conf, $parentUid, $updateToUid = 0, $skipSorting = FALSE) {
610 $c = 0;
611 $foreign_table = $conf['foreign_table'];
612 $foreign_field = $conf['foreign_field'];
613 $symmetric_field = $conf['symmetric_field'];
614 $foreign_table_field = $conf['foreign_table_field'];
615
616 // if there are table items and we have a proper $parentUid
617 if (t3lib_div::testInt($parentUid) && count($this->tableArray)) {
618 // if updateToUid is not a positive integer, set it to '0', so it will be ignored
619 if (!(t3lib_div::testInt($updateToUid) && $updateToUid > 0)) {
620 $updateToUid = 0;
621 }
622
623 $considerWorkspaces = ($GLOBALS['BE_USER']->workspace !== 0 && t3lib_BEfunc::isTableWorkspaceEnabled($foreign_table));
624
625 $fields = 'uid,' . $foreign_field;
626 // Consider the symmetric field if defined:
627 if ($symmetric_field) {
628 $fields .= ',' . $symmetric_field;
629 }
630 // Consider workspaces if defined and currently used:
631 if ($considerWorkspaces) {
632 $fields .= ',' . 't3ver_state,t3ver_oid';
633 }
634
635 // update all items
636 foreach ($this->itemArray as $val) {
637 $uid = $val['id'];
638 $table = $val['table'];
639
640 // fetch the current (not overwritten) relation record if we should handle symmetric relations
641 if ($symmetric_field || $considerWorkspaces) {
642 $row = t3lib_BEfunc::getRecord($table, $uid, $fields, '', FALSE);
643 }
644 if ($symmetric_field) {
645 $isOnSymmetricSide = t3lib_loadDBGroup::isOnSymmetricSide($parentUid, $conf, $row);
646 }
647
648 $updateValues = array();
649 $workspaceValues = array();
650
651 // no update to the uid is requested, so this is the normal behaviour
652 // just update the fields and care about sorting
653 if (!$updateToUid) {
654 // Always add the pointer to the parent uid
655 if ($isOnSymmetricSide) {
656 $updateValues[$symmetric_field] = $parentUid;
657 } else {
658 $updateValues[$foreign_field] = $parentUid;
659 }
660
661 // if it is configured in TCA also to store the parent table in the child record, just do it
662 if ($foreign_table_field && $this->currentTable) {
663 $updateValues[$foreign_table_field] = $this->currentTable;
664 }
665
666 // update sorting columns if not to be skipped
667 if (!$skipSorting) {
668 // get the correct sorting field
669 if ($conf['foreign_sortby']) { // specific manual sortby for data handled by this field
670 $sortby = $conf['foreign_sortby'];
671 } elseif ($GLOBALS['TCA'][$foreign_table]['ctrl']['sortby']) { // manual sortby for all table records
672 $sortby = $GLOBALS['TCA'][$foreign_table]['ctrl']['sortby'];
673 }
674 // Apply sorting on the symmetric side (it depends on who created the relation, so what uid is in the symmetric_field):
675 if ($isOnSymmetricSide && isset($conf['symmetric_sortby']) && $conf['symmetric_sortby']) {
676 $sortby = $conf['symmetric_sortby'];
677 // Strip a possible "ORDER BY" in front of the $sortby value:
678 } else {
679 $sortby = $GLOBALS['TYPO3_DB']->stripOrderBy($sortby);
680 }
681
682 if ($sortby) {
683 $updateValues[$sortby] = $workspaceValues[$sortby] = ++$c;
684 }
685 }
686
687 // update to a foreign_field/symmetric_field pointer is requested, normally used on record copies
688 // only update the fields, if the old uid is found somewhere - for select fields, TCEmain is doing this already!
689 } else {
690 if ($isOnSymmetricSide) {
691 $updateValues[$symmetric_field] = $updateToUid;
692 } else {
693 $updateValues[$foreign_field] = $updateToUid;
694 }
695 }
696
697 // Update accordant fields in the database:
698 if (count($updateValues)) {
699 $GLOBALS['TYPO3_DB']->exec_UPDATEquery($table, 'uid=' . intval($uid), $updateValues);
700 $this->updateRefIndex($table, $uid);
701 }
702 // Update accordant fields in the database for workspaces overlays/placeholders:
703 if (count($workspaceValues) && $considerWorkspaces) {
704 if (isset($row['t3ver_oid']) && $row['t3ver_oid'] && $row['t3ver_state'] == -1) {
705 $GLOBALS['TYPO3_DB']->exec_UPDATEquery($table, 'uid=' . intval($row['t3ver_oid']), $workspaceValues);
706 }
707 }
708 }
709 }
710 }
711
712 /**
713 * After initialization you can extract an array of the elements from the object. Use this function for that.
714 *
715 * @param boolean If set, then table names will ALWAYS be prepended (unless its a _NO_TABLE value)
716 * @return array A numeric array.
717 */
718 function getValueArray($prependTableName = '') {
719 // INIT:
720 $valueArray = Array();
721 $tableC = count($this->tableArray);
722
723 // If there are tables in the table array:
724 if ($tableC) {
725 // If there are more than ONE table in the table array, then always prepend table names:
726 $prep = ($tableC > 1 || $prependTableName) ? 1 : 0;
727
728 // Traverse the array of items:
729 foreach ($this->itemArray as $val) {
730 $valueArray[] = (($prep && $val['table'] != '_NO_TABLE') ? $val['table'] . '_' : '') .
731 $val['id'];
732 }
733 }
734 // Return the array
735 return $valueArray;
736 }
737
738 /**
739 * Converts id numbers from negative to positive.
740 *
741 * @param array Array of [table]_[id] pairs.
742 * @param string Foreign table (the one used for positive numbers)
743 * @param string NEGative foreign table
744 * @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.
745 */
746 function convertPosNeg($valueArray, $fTable, $nfTable) {
747 if (is_array($valueArray) && $fTable) {
748 foreach ($valueArray as $key => $val) {
749 $val = strrev($val);
750 $parts = explode('_', $val, 2);
751 $theID = strrev($parts[0]);
752 $theTable = strrev($parts[1]);
753
754 if (t3lib_div::testInt($theID) && (!$theTable || !strcmp($theTable, $fTable) || !strcmp($theTable, $nfTable))) {
755 $valueArray[$key] = $theTable && strcmp($theTable, $fTable) ? $theID * -1 : $theID;
756 }
757 }
758 }
759 return $valueArray;
760 }
761
762 /**
763 * 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.
764 * If $this->fromTC is set you can save a little memory since only uid,pid and a few other fields are selected.
765 *
766 * @return void
767 */
768 function getFromDB() {
769 // Traverses the tables listed:
770 foreach ($this->tableArray as $key => $val) {
771 if (is_array($val)) {
772 $itemList = implode(',', $val);
773 if ($itemList) {
774 $from = '*';
775 if ($this->fromTC) {
776 $from = 'uid,pid';
777 if ($GLOBALS['TCA'][$key]['ctrl']['label']) {
778 $from .= ',' . $GLOBALS['TCA'][$key]['ctrl']['label']; // Titel
779 }
780 if ($GLOBALS['TCA'][$key]['ctrl']['label_alt']) {
781 $from .= ',' . $GLOBALS['TCA'][$key]['ctrl']['label_alt']; // Alternative Title-Fields
782 }
783 if ($GLOBALS['TCA'][$key]['ctrl']['thumbnail']) {
784 $from .= ',' . $GLOBALS['TCA'][$key]['ctrl']['thumbnail']; // Thumbnail
785 }
786 }
787 $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery($from, $key, 'uid IN (' . $itemList . ')' . $this->additionalWhere[$key]);
788 while ($row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res)) {
789 $this->results[$key][$row['uid']] = $row;
790 }
791 }
792 }
793 }
794 return $this->results;
795 }
796
797 /**
798 * Prepare items from itemArray to be transferred to the TCEforms interface (as a comma list)
799 *
800 * @return string
801 * @see t3lib_transferdata::renderRecord()
802 */
803 function readyForInterface() {
804 if (!is_array($this->itemArray)) {
805 return FALSE;
806 }
807
808 $output = array();
809 $perms_clause = $GLOBALS['BE_USER']->getPagePermsClause(1); // For use when getting the paths....
810 $titleLen = intval($GLOBALS['BE_USER']->uc['titleLen']);
811
812 foreach ($this->itemArray as $key => $val) {
813 $theRow = $this->results[$val['table']][$val['id']];
814 if ($theRow && is_array($GLOBALS['TCA'][$val['table']])) {
815 $label = t3lib_div::fixed_lgd_cs(strip_tags(t3lib_BEfunc::getRecordTitle($val['table'], $theRow)), $titleLen);
816 $label = ($label) ? $label : '[...]';
817 $output[] = str_replace(',', '', $val['table'] . '_' . $val['id'] . '|' . rawurlencode($label));
818 }
819 }
820 return implode(',', $output);
821 }
822
823 /**
824 * Counts the items in $this->itemArray and puts this value in an array by default.
825 *
826 * @param boolean Whether to put the count value in an array
827 * @return mixed The plain count as integer or the same inside an array
828 */
829 function countItems($returnAsArray = TRUE) {
830 $count = count($this->itemArray);
831 if ($returnAsArray) {
832 $count = array($count);
833 }
834 return $count;
835 }
836
837 /**
838 * Update Reference Index (sys_refindex) for a record
839 * Should be called any almost any update to a record which could affect references inside the record.
840 * (copied from TCEmain)
841 *
842 * @param string Table name
843 * @param integer Record UID
844 * @return array Information concerning modifications delivered by t3lib_refindex::updateRefIndexTable()
845 */
846 function updateRefIndex($table, $id) {
847 if ($this->updateReferenceIndex === TRUE) {
848 /** @var $refIndexObj t3lib_refindex */
849 $refIndexObj = t3lib_div::makeInstance('t3lib_refindex');
850 return $refIndexObj->updateRefIndexTable($table, $id);
851 }
852 }
853
854 /**
855 * Checks, if we're looking from the "other" side, the symmetric side, to a symmetric relation.
856 *
857 * @param string $parentUid: The uid of the parent record
858 * @param array $parentConf: The TCA configuration of the parent field embedding the child records
859 * @param array $childRec: The record row of the child record
860 * @return boolean Returns TRUE if looking from the symmetric ("other") side to the relation.
861 */
862 function isOnSymmetricSide($parentUid, $parentConf, $childRec) {
863 return t3lib_div::testInt($childRec['uid']) && $parentConf['symmetric_field'] && $parentUid == $childRec[$parentConf['symmetric_field']]
864 ? TRUE
865 : FALSE;
866 }
867 }
868
869
870 if (defined('TYPO3_MODE') && isset($GLOBALS['TYPO3_CONF_VARS'][TYPO3_MODE]['XCLASS']['t3lib/class.t3lib_loaddbgroup.php'])) {
871 include_once($GLOBALS['TYPO3_CONF_VARS'][TYPO3_MODE]['XCLASS']['t3lib/class.t3lib_loaddbgroup.php']);
872 }
873
874 ?>