[BUGFIX] Autoloader Cache is not updated
[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 /**
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.
39 *
40 * @author Kasper Skårhøj <kasperYYYY@typo3.com>
41 * @package TYPO3
42 * @subpackage t3lib
43 */
44 class t3lib_loadDBGroup {
45 // External, static:
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.
48
49 // Internal, dynamic:
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)
58 // private
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)
66
67
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
71
72 /**
73 * @var boolean
74 */
75 protected $updateReferenceIndex = TRUE;
76
77 /**
78 * Initialization of the class.
79 *
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
86 * @return void
87 */
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;
96
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];
103 unset($tmp);
104
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'];
108
109 if ($this->MM_oppositeFieldConf['allowed']) {
110 $oppositeFieldConf_allowed = explode(',', $this->MM_oppositeFieldConf['allowed']);
111 if (count($oppositeFieldConf_allowed) > 1) {
112 $this->MM_isMultiTableRelationship = $oppositeFieldConf_allowed[0];
113 }
114 }
115 }
116
117 // SECTION: normal MM relations
118
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']));
122 }
123
124 // The tables are traversed and internal arrays are initialized:
125 $tempTableArray = t3lib_div::trimExplode(',', $tablelist, 1);
126 foreach ($tempTableArray as $key => $val) {
127 $tName = trim($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';
132 }
133 }
134
135 if (is_array($this->tableArray)) {
136 reset($this->tableArray);
137 } else {
138 return 'No tables!';
139 }
140
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...
145
146 // Now, populate the internal itemArray and tableArray arrays:
147 if ($MMtable) { // If MM, then call this function to do that:
148 if ($MMuid) {
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);
152 }
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);
156 } else {
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']);
162 }
163 }
164 }
165
166 /**
167 * Sets whether the reference index shall be updated.
168 *
169 * @param boolean $updateReferenceIndex Whether the reference index shall be updated
170 * @return void
171 */
172 public function setUpdateReferenceIndex($updateReferenceIndex) {
173 $this->updateReferenceIndex = (bool) $updateReferenceIndex;
174 }
175
176 /**
177 * Explodes the item list and stores the parts in the internal arrays itemArray and tableArray from MM records.
178 *
179 * @param string Item list
180 * @return void
181 */
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:
187
188 // Extract table name and id. This is un the formular [tablename]_[id] where table name MIGHT contain "_", hence the reversion of the string!
189 $val = strrev($val);
190 $parts = explode('_', $val, 2);
191 $theID = strrev($parts[0]);
192
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;
205 // Set update-flag:
206 $isSet = 1;
207 }
208 }
209
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];
215 }
216 }
217 }
218 }
219
220 /**
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!
224 *
225 * @param string $sortby: The default_sortby field/command (e.g. 'price DESC')
226 * @return void
227 */
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));
237
238 if ($uidList) {
239 $this->itemArray = array();
240 $this->tableArray = array();
241
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'];
246 }
247 $GLOBALS['TYPO3_DB']->sql_free_result($res);
248 }
249 }
250 }
251
252 /**
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()
255 *
256 * @param string MM Tablename
257 * @param integer Local UID
258 * @return void
259 */
260 function readMM($tableName, $uid) {
261 $key = 0;
262 $additionalWhere = '';
263
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';
268
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=\'\'';
273 }
274 $additionalWhere .= ' ) ';
275 }
276 $theTable = $this->MM_oppositeTable;
277 } else { // default
278 $uidLocal_field = 'uid_local';
279 $uidForeign_field = 'uid_foreign';
280 $sorting_field = 'sorting';
281 }
282
283
284 if ($this->MM_table_where) {
285 $additionalWhere .= LF . str_replace('###THIS_UID###', intval($uid), $this->MM_table_where);
286 }
287 foreach ($this->MM_match_fields as $field => $value) {
288 $additionalWhere .= ' AND ' . $field . '=' . $GLOBALS['TYPO3_DB']->fullQuoteStr($value, $tableName);
289 }
290
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...
296 }
297 if (($row[$uidForeign_field] || $theTable == 'pages') && $theTable && isset($this->tableArray[$theTable])) {
298
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];
306 }
307 $key++;
308 }
309 $GLOBALS['TYPO3_DB']->sql_free_result($res);
310 }
311
312 /**
313 * Writes the internal itemArray to MM table:
314 *
315 * @param string MM table name
316 * @param integer Local UID
317 * @param boolean If set, then table names will always be written.
318 * @return void
319 */
320 function writeMM($MM_tableName, $uid, $prependTableName = 0) {
321
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';
326 } else { // default
327 $uidLocal_field = 'uid_local';
328 $uidForeign_field = 'uid_foreign';
329 $sorting_field = 'sorting';
330 }
331
332 // If there are tables...
333 $tableC = count($this->tableArray);
334 if ($tableC) {
335 $prep = ($tableC > 1 || $prependTableName || $this->MM_isMultiTableRelationship) ? 1 : 0; // boolean: does the field "tablename" need to be filled?
336 $c = 0;
337
338 $additionalWhere_tablenames = '';
339 if ($this->MM_is_foreign && $prep) {
340 $additionalWhere_tablenames = ' AND tablenames=' . $GLOBALS['TYPO3_DB']->fullQuoteStr($this->currentTable, $MM_tableName);
341 }
342
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);
347 }
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);
351 }
352
353 $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery(
354 $uidForeign_field . ($prep ? ', tablenames' : '') . ($this->MM_hasUidField ? ', uid' : ''),
355 $MM_tableName,
356 $uidLocal_field . '=' . $uid . $additionalWhere_tablenames . $additionalWhere,
357 '',
358 $sorting_field
359 );
360
361 $oldMMs = array();
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]);
366 } else {
367 $oldMMs[] = $row[$uidForeign_field];
368 }
369 $oldMMs_inclUid[] = array($row['tablenames'], $row[$uidForeign_field], $row['uid']);
370 }
371
372 // For each item, insert it:
373 foreach ($this->itemArray as $val) {
374 $c++;
375
376 if ($prep || $val['table'] == '_NO_TABLE') {
377 if ($this->MM_is_foreign) { // insert current table if needed
378 $tablename = $this->currentTable;
379 } else {
380 $tablename = $val['table'];
381 }
382 } else {
383 $tablename = '';
384 }
385
386 if (!$this->MM_is_foreign && $prep) {
387 $item = array($val['table'], $val['id']);
388 } else {
389 $item = $val['id'];
390 }
391
392 if (in_array($item, $oldMMs)) {
393 $oldMMs_index = array_search($item, $oldMMs);
394
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.
397 if ($tablename) {
398 $whereClause .= ' AND tablenames=' . $GLOBALS['TYPO3_DB']->fullQuoteStr($tablename, $MM_tableName);
399 }
400 $GLOBALS['TYPO3_DB']->exec_UPDATEquery($MM_tableName, $whereClause . $additionalWhere, array($sorting_field => $c));
401
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.
404 } else {
405
406 $insertFields = $this->MM_insert_fields;
407 $insertFields[$uidLocal_field] = $uid;
408 $insertFields[$uidForeign_field] = $val['id'];
409 $insertFields[$sorting_field] = $c;
410 if ($tablename) {
411 $insertFields['tablenames'] = $tablename;
412 }
413
414 $GLOBALS['TYPO3_DB']->exec_INSERTquery($MM_tableName, $insertFields);
415
416 if ($this->MM_is_foreign) {
417 $this->updateRefIndex($val['table'], $val['id']);
418 }
419 }
420 }
421
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];
430 } else {
431 if (is_array($mmItem)) {
432 $removeClauses[] = 'tablenames=' . $GLOBALS['TYPO3_DB']->fullQuoteStr($mmItem[0], $MM_tableName) . ' AND ' . $uidForeign_field . '=' . $mmItem[1];
433 } else {
434 $removeClauses[] = $uidForeign_field . '=' . $mmItem;
435 }
436 }
437 if ($this->MM_is_foreign) {
438 if (is_array($mmItem)) {
439 $updateRefIndex_records[] = array($mmItem[0], $mmItem[1]);
440 } else {
441 $updateRefIndex_records[] = array($this->firstTable, $mmItem);
442 }
443 }
444 }
445 $deleteAddWhere = ' AND (' . implode(' OR ', $removeClauses) . ')';
446 $GLOBALS['TYPO3_DB']->exec_DELETEquery($MM_tableName, $uidLocal_field . '=' . intval($uid) . $deleteAddWhere . $additionalWhere_tablenames . $additionalWhere);
447
448 // Update ref index:
449 foreach ($updateRefIndex_records as $pair) {
450 $this->updateRefIndex($pair[0], $pair[1]);
451 }
452 }
453
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);
456 }
457 }
458
459 /**
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!
462 *
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.
467 * @return void
468 */
469 function remapMM($MM_tableName, $uid, $newUid, $prependTableName = 0) {
470
471 if ($this->MM_is_foreign) { // in case of a reverse relation
472 $uidLocal_field = 'uid_foreign';
473 } else { // default
474 $uidLocal_field = 'uid_local';
475 }
476
477 // If there are tables...
478 $tableC = count($this->tableArray);
479 if ($tableC) {
480 $prep = ($tableC > 1 || $prependTableName || $this->MM_isMultiTableRelationship) ? 1 : 0; // boolean: does the field "tablename" need to be filled?
481 $c = 0;
482
483 $additionalWhere_tablenames = '';
484 if ($this->MM_is_foreign && $prep) {
485 $additionalWhere_tablenames = ' AND tablenames=' . $GLOBALS['TYPO3_DB']->fullQuoteStr($this->currentTable, $MM_tableName);
486 }
487
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);
492 }
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);
496 }
497
498 $GLOBALS['TYPO3_DB']->exec_UPDATEquery($MM_tableName, $uidLocal_field . '=' . intval($uid) . $additionalWhere_tablenames . $additionalWhere, array($uidLocal_field => $newUid));
499 }
500 }
501
502 /**
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.
505 *
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
508 * @return void
509 */
510 function readForeignField($uid, $conf) {
511 $key = 0;
512 $uid = intval($uid);
513 $whereClause = '';
514 $foreign_table = $conf['foreign_table'];
515 $foreign_table_field = $conf['foreign_table_field'];
516 $useDeleteClause = $this->undeleteRecord ? FALSE : TRUE;
517
518 // search for $uid in foreign_field, and if we have symmetric relations, do this also on symmetric_field
519 if ($conf['symmetric_field']) {
520 $whereClause = '(' . $conf['foreign_field'] . '=' . $uid . ' OR ' . $conf['symmetric_field'] . '=' . $uid . ')';
521 } else {
522 $whereClause = $conf['foreign_field'] . '=' . $uid;
523 }
524 // use the deleteClause (e.g. "deleted=0") on this table
525 if ($useDeleteClause) {
526 $whereClause .= t3lib_BEfunc::deleteClause($foreign_table);
527 }
528 // if it's requested to look for the parent uid AND the parent table,
529 // add an additional SQL-WHERE clause
530 if ($foreign_table_field && $this->currentTable) {
531 $whereClause .= ' AND ' . $foreign_table_field . '=' . $GLOBALS['TYPO3_DB']->fullQuoteStr($this->currentTable, $foreign_table);
532 }
533
534 // Select children in the same workspace:
535 if (t3lib_BEfunc::isTableWorkspaceEnabled($this->currentTable) && t3lib_BEfunc::isTableWorkspaceEnabled($foreign_table)) {
536 $currentRecord = t3lib_BEfunc::getRecord($this->currentTable, $uid, 't3ver_wsid', '', $useDeleteClause);
537 $whereClause .= t3lib_BEfunc::getWorkspaceWhereClause($foreign_table, $currentRecord['t3ver_wsid']);
538 }
539
540 // get the correct sorting field
541 if ($conf['foreign_sortby']) { // specific manual sortby for data handled by this field
542 if ($conf['symmetric_sortby'] && $conf['symmetric_field']) {
543 // sorting depends on, from which side of the relation we're looking at it
544 $sortby = '
545 CASE
546 WHEN ' . $conf['foreign_field'] . '=' . $uid . '
547 THEN ' . $conf['foreign_sortby'] . '
548 ELSE ' . $conf['symmetric_sortby'] . '
549 END';
550 } else {
551 // regular single-side behaviour
552 $sortby = $conf['foreign_sortby'];
553 }
554 } elseif ($conf['foreign_default_sortby']) { // specific default sortby for data handled by this field
555 $sortby = $conf['foreign_default_sortby'];
556 } elseif ($GLOBALS['TCA'][$foreign_table]['ctrl']['sortby']) { // manual sortby for all table records
557 $sortby = $GLOBALS['TCA'][$foreign_table]['ctrl']['sortby'];
558 } elseif ($GLOBALS['TCA'][$foreign_table]['ctrl']['default_sortby']) { // default sortby for all table records
559 $sortby = $GLOBALS['TCA'][$foreign_table]['ctrl']['default_sortby'];
560 }
561
562 // strip a possible "ORDER BY" in front of the $sortby value
563 $sortby = $GLOBALS['TYPO3_DB']->stripOrderBy($sortby);
564 // get the rows from storage
565 $rows = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows('uid', $foreign_table, $whereClause, '', $sortby);
566
567 if (count($rows)) {
568 foreach ($rows as $row) {
569 $this->itemArray[$key]['id'] = $row['uid'];
570 $this->itemArray[$key]['table'] = $foreign_table;
571 $this->tableArray[$foreign_table][] = $row['uid'];
572 $key++;
573 }
574 }
575 }
576
577 /**
578 * Write the sorting values to a foreign_table, that has a foreign_field (uid of the parent record)
579 *
580 * @param array $conf: TCA configuration for current field
581 * @param integer $parentUid: The uid of the parent record
582 * @param boolean $updateToUid: Whether to update the foreign field with the $parentUid (on Copy)
583 * @param boolean $skipSorting: Do not update the sorting columns, this could happen for imported values
584 * @return void
585 */
586 function writeForeignField($conf, $parentUid, $updateToUid = 0, $skipSorting = FALSE) {
587 $c = 0;
588 $foreign_table = $conf['foreign_table'];
589 $foreign_field = $conf['foreign_field'];
590 $symmetric_field = $conf['symmetric_field'];
591 $foreign_table_field = $conf['foreign_table_field'];
592
593 // if there are table items and we have a proper $parentUid
594 if (t3lib_utility_Math::canBeInterpretedAsInteger($parentUid) && count($this->tableArray)) {
595 // if updateToUid is not a positive integer, set it to '0', so it will be ignored
596 if (!(t3lib_utility_Math::canBeInterpretedAsInteger($updateToUid) && $updateToUid > 0)) {
597 $updateToUid = 0;
598 }
599
600 $considerWorkspaces = ($GLOBALS['BE_USER']->workspace !== 0 && t3lib_BEfunc::isTableWorkspaceEnabled($foreign_table));
601
602 $fields = 'uid,' . $foreign_field;
603 // Consider the symmetric field if defined:
604 if ($symmetric_field) {
605 $fields .= ',' . $symmetric_field;
606 }
607 // Consider workspaces if defined and currently used:
608 if ($considerWorkspaces) {
609 $fields .= ',' . 't3ver_state,t3ver_oid';
610 }
611
612 // update all items
613 foreach ($this->itemArray as $val) {
614 $uid = $val['id'];
615 $table = $val['table'];
616
617 // fetch the current (not overwritten) relation record if we should handle symmetric relations
618 if ($symmetric_field || $considerWorkspaces) {
619 $row = t3lib_BEfunc::getRecord($table, $uid, $fields, '', FALSE);
620 }
621 if ($symmetric_field) {
622 $isOnSymmetricSide = t3lib_loadDBGroup::isOnSymmetricSide($parentUid, $conf, $row);
623 }
624
625 $updateValues = array();
626 $workspaceValues = array();
627
628 // no update to the uid is requested, so this is the normal behaviour
629 // just update the fields and care about sorting
630 if (!$updateToUid) {
631 // Always add the pointer to the parent uid
632 if ($isOnSymmetricSide) {
633 $updateValues[$symmetric_field] = $parentUid;
634 } else {
635 $updateValues[$foreign_field] = $parentUid;
636 }
637
638 // if it is configured in TCA also to store the parent table in the child record, just do it
639 if ($foreign_table_field && $this->currentTable) {
640 $updateValues[$foreign_table_field] = $this->currentTable;
641 }
642
643 // update sorting columns if not to be skipped
644 if (!$skipSorting) {
645 // get the correct sorting field
646 if ($conf['foreign_sortby']) { // specific manual sortby for data handled by this field
647 $sortby = $conf['foreign_sortby'];
648 } elseif ($GLOBALS['TCA'][$foreign_table]['ctrl']['sortby']) { // manual sortby for all table records
649 $sortby = $GLOBALS['TCA'][$foreign_table]['ctrl']['sortby'];
650 }
651 // Apply sorting on the symmetric side (it depends on who created the relation, so what uid is in the symmetric_field):
652 if ($isOnSymmetricSide && isset($conf['symmetric_sortby']) && $conf['symmetric_sortby']) {
653 $sortby = $conf['symmetric_sortby'];
654 // Strip a possible "ORDER BY" in front of the $sortby value:
655 } else {
656 $sortby = $GLOBALS['TYPO3_DB']->stripOrderBy($sortby);
657 }
658
659 if ($sortby) {
660 $updateValues[$sortby] = $workspaceValues[$sortby] = ++$c;
661 }
662 }
663
664 // update to a foreign_field/symmetric_field pointer is requested, normally used on record copies
665 // only update the fields, if the old uid is found somewhere - for select fields, TCEmain is doing this already!
666 } else {
667 if ($isOnSymmetricSide) {
668 $updateValues[$symmetric_field] = $updateToUid;
669 } else {
670 $updateValues[$foreign_field] = $updateToUid;
671 }
672 }
673
674 // Update accordant fields in the database:
675 if (count($updateValues)) {
676 $GLOBALS['TYPO3_DB']->exec_UPDATEquery($table, 'uid=' . intval($uid), $updateValues);
677 $this->updateRefIndex($table, $uid);
678 }
679 // Update accordant fields in the database for workspaces overlays/placeholders:
680 if (count($workspaceValues) && $considerWorkspaces) {
681 if (isset($row['t3ver_oid']) && $row['t3ver_oid'] && $row['t3ver_state'] == -1) {
682 $GLOBALS['TYPO3_DB']->exec_UPDATEquery($table, 'uid=' . intval($row['t3ver_oid']), $workspaceValues);
683 }
684 }
685 }
686 }
687 }
688
689 /**
690 * After initialization you can extract an array of the elements from the object. Use this function for that.
691 *
692 * @param boolean If set, then table names will ALWAYS be prepended (unless its a _NO_TABLE value)
693 * @return array A numeric array.
694 */
695 function getValueArray($prependTableName = '') {
696 // INIT:
697 $valueArray = Array();
698 $tableC = count($this->tableArray);
699
700 // If there are tables in the table array:
701 if ($tableC) {
702 // If there are more than ONE table in the table array, then always prepend table names:
703 $prep = ($tableC > 1 || $prependTableName) ? 1 : 0;
704
705 // Traverse the array of items:
706 foreach ($this->itemArray as $val) {
707 $valueArray[] = (($prep && $val['table'] != '_NO_TABLE') ? $val['table'] . '_' : '') .
708 $val['id'];
709 }
710 }
711 // Return the array
712 return $valueArray;
713 }
714
715 /**
716 * Converts id numbers from negative to positive.
717 *
718 * @param array Array of [table]_[id] pairs.
719 * @param string Foreign table (the one used for positive numbers)
720 * @param string NEGative foreign table
721 * @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.
722 */
723 function convertPosNeg($valueArray, $fTable, $nfTable) {
724 if (is_array($valueArray) && $fTable) {
725 foreach ($valueArray as $key => $val) {
726 $val = strrev($val);
727 $parts = explode('_', $val, 2);
728 $theID = strrev($parts[0]);
729 $theTable = strrev($parts[1]);
730
731 if (t3lib_utility_Math::canBeInterpretedAsInteger($theID) && (!$theTable || !strcmp($theTable, $fTable) || !strcmp($theTable, $nfTable))) {
732 $valueArray[$key] = $theTable && strcmp($theTable, $fTable) ? $theID * -1 : $theID;
733 }
734 }
735 }
736 return $valueArray;
737 }
738
739 /**
740 * 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.
741 * If $this->fromTC is set you can save a little memory since only uid,pid and a few other fields are selected.
742 *
743 * @return void
744 */
745 function getFromDB() {
746 // Traverses the tables listed:
747 foreach ($this->tableArray as $key => $val) {
748 if (is_array($val)) {
749 $itemList = implode(',', $val);
750 if ($itemList) {
751 $from = '*';
752 if ($this->fromTC) {
753 $from = 'uid,pid';
754 if ($GLOBALS['TCA'][$key]['ctrl']['label']) {
755 $from .= ',' . $GLOBALS['TCA'][$key]['ctrl']['label']; // Titel
756 }
757 if ($GLOBALS['TCA'][$key]['ctrl']['label_alt']) {
758 $from .= ',' . $GLOBALS['TCA'][$key]['ctrl']['label_alt']; // Alternative Title-Fields
759 }
760 if ($GLOBALS['TCA'][$key]['ctrl']['thumbnail']) {
761 $from .= ',' . $GLOBALS['TCA'][$key]['ctrl']['thumbnail']; // Thumbnail
762 }
763 }
764 $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery($from, $key, 'uid IN (' . $itemList . ')' . $this->additionalWhere[$key]);
765 while ($row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res)) {
766 $this->results[$key][$row['uid']] = $row;
767 }
768 }
769 }
770 }
771 return $this->results;
772 }
773
774 /**
775 * Prepare items from itemArray to be transferred to the TCEforms interface (as a comma list)
776 *
777 * @return string
778 * @see t3lib_transferdata::renderRecord()
779 */
780 function readyForInterface() {
781 if (!is_array($this->itemArray)) {
782 return FALSE;
783 }
784
785 $output = array();
786 $perms_clause = $GLOBALS['BE_USER']->getPagePermsClause(1); // For use when getting the paths....
787 $titleLen = intval($GLOBALS['BE_USER']->uc['titleLen']);
788
789 foreach ($this->itemArray as $key => $val) {
790 $theRow = $this->results[$val['table']][$val['id']];
791 if ($theRow && is_array($GLOBALS['TCA'][$val['table']])) {
792 $label = t3lib_div::fixed_lgd_cs(strip_tags(t3lib_BEfunc::getRecordTitle($val['table'], $theRow)), $titleLen);
793 $label = ($label) ? $label : '[...]';
794 $output[] = str_replace(',', '', $val['table'] . '_' . $val['id'] . '|' . rawurlencode($label));
795 }
796 }
797 return implode(',', $output);
798 }
799
800 /**
801 * Counts the items in $this->itemArray and puts this value in an array by default.
802 *
803 * @param boolean Whether to put the count value in an array
804 * @return mixed The plain count as integer or the same inside an array
805 */
806 function countItems($returnAsArray = TRUE) {
807 $count = count($this->itemArray);
808 if ($returnAsArray) {
809 $count = array($count);
810 }
811 return $count;
812 }
813
814 /**
815 * Update Reference Index (sys_refindex) for a record
816 * Should be called any almost any update to a record which could affect references inside the record.
817 * (copied from TCEmain)
818 *
819 * @param string Table name
820 * @param integer Record UID
821 * @return array Information concerning modifications delivered by t3lib_refindex::updateRefIndexTable()
822 */
823 function updateRefIndex($table, $id) {
824 if ($this->updateReferenceIndex === TRUE) {
825 /** @var $refIndexObj t3lib_refindex */
826 $refIndexObj = t3lib_div::makeInstance('t3lib_refindex');
827 return $refIndexObj->updateRefIndexTable($table, $id);
828 }
829 }
830
831 /**
832 * Checks, if we're looking from the "other" side, the symmetric side, to a symmetric relation.
833 *
834 * @param string $parentUid: The uid of the parent record
835 * @param array $parentConf: The TCA configuration of the parent field embedding the child records
836 * @param array $childRec: The record row of the child record
837 * @return boolean Returns TRUE if looking from the symmetric ("other") side to the relation.
838 */
839 function isOnSymmetricSide($parentUid, $parentConf, $childRec) {
840 return t3lib_utility_Math::canBeInterpretedAsInteger($childRec['uid']) && $parentConf['symmetric_field'] && $parentUid == $childRec[$parentConf['symmetric_field']]
841 ? TRUE
842 : FALSE;
843 }
844 }
845
846
847 if (defined('TYPO3_MODE') && isset($GLOBALS['TYPO3_CONF_VARS'][TYPO3_MODE]['XCLASS']['t3lib/class.t3lib_loaddbgroup.php'])) {
848 include_once($GLOBALS['TYPO3_CONF_VARS'][TYPO3_MODE]['XCLASS']['t3lib/class.t3lib_loaddbgroup.php']);
849 }
850
851 ?>