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