[TASK] Encapsulate bootstrap base code
[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
306 if ($this->MM_table_where) {
307 $additionalWhere .= LF . str_replace('###THIS_UID###', intval($uid), $this->MM_table_where);
308 }
309 foreach ($this->MM_match_fields as $field => $value) {
310 $additionalWhere .= ' AND ' . $field . '=' . $GLOBALS['TYPO3_DB']->fullQuoteStr($value, $tableName);
311 }
312
313 // Select all MM relations:
314 $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery('*', $tableName, $uidLocal_field . '=' . intval($uid) . $additionalWhere, '', $sorting_field);
315 while ($row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res)) {
316 // Default
317 if (!$this->MM_is_foreign) {
318 // If tablesnames columns exists and contain a name, then this value is the table, else it's the firstTable...
319 $theTable = $row['tablenames'] ? $row['tablenames'] : $this->firstTable;
320 }
321 if (($row[$uidForeign_field] || $theTable == 'pages') && $theTable && isset($this->tableArray[$theTable])) {
322
323 $this->itemArray[$key]['id'] = $row[$uidForeign_field];
324 $this->itemArray[$key]['table'] = $theTable;
325 $this->tableArray[$theTable][] = $row[$uidForeign_field];
326 } elseif ($this->registerNonTableValues) {
327 $this->itemArray[$key]['id'] = $row[$uidForeign_field];
328 $this->itemArray[$key]['table'] = '_NO_TABLE';
329 $this->nonTableArray[] = $row[$uidForeign_field];
330 }
331 $key++;
332 }
333 $GLOBALS['TYPO3_DB']->sql_free_result($res);
334 }
335
336 /**
337 * Writes the internal itemArray to MM table:
338 *
339 * @param string $MM_tableName MM table name
340 * @param integer $uid Local UID
341 * @param boolean $prependTableName If set, then table names will always be written.
342 * @return void
343 */
344 function writeMM($MM_tableName, $uid, $prependTableName = 0) {
345
346 // In case of a reverse relation
347 if ($this->MM_is_foreign) {
348 $uidLocal_field = 'uid_foreign';
349 $uidForeign_field = 'uid_local';
350 $sorting_field = 'sorting_foreign';
351 } else { // default
352 $uidLocal_field = 'uid_local';
353 $uidForeign_field = 'uid_foreign';
354 $sorting_field = 'sorting';
355 }
356
357 // If there are tables...
358 $tableC = count($this->tableArray);
359 if ($tableC) {
360 // Boolean: does the field "tablename" need to be filled?
361 $prep = ($tableC > 1 || $prependTableName || $this->MM_isMultiTableRelationship) ? 1 : 0;
362 $c = 0;
363
364 $additionalWhere_tablenames = '';
365 if ($this->MM_is_foreign && $prep) {
366 $additionalWhere_tablenames = ' AND tablenames=' . $GLOBALS['TYPO3_DB']->fullQuoteStr($this->currentTable, $MM_tableName);
367 }
368
369 $additionalWhere = '';
370 // Add WHERE clause if configured
371 if ($this->MM_table_where) {
372 $additionalWhere .= LF . str_replace('###THIS_UID###', intval($uid), $this->MM_table_where);
373 }
374 // Select, update or delete only those relations that match the configured fields
375 foreach ($this->MM_match_fields as $field => $value) {
376 $additionalWhere .= ' AND ' . $field . '=' . $GLOBALS['TYPO3_DB']->fullQuoteStr($value, $MM_tableName);
377 }
378
379 $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery(
380 $uidForeign_field . ($prep ? ', tablenames' : '') . ($this->MM_hasUidField ? ', uid' : ''),
381 $MM_tableName,
382 $uidLocal_field . '=' . $uid . $additionalWhere_tablenames . $additionalWhere,
383 '',
384 $sorting_field
385 );
386
387 $oldMMs = array();
388 // This array is similar to $oldMMs but also holds the uid of the MM-records, if any (configured by MM_hasUidField).
389 // If the UID is present it will be used to update sorting and delete MM-records.
390 // This is necessary if the "multiple" feature is used for the MM relations.
391 // $oldMMs is still needed for the in_array() search used to look if an item from $this->itemArray is in $oldMMs
392 $oldMMs_inclUid = array();
393 while ($row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res)) {
394 if (!$this->MM_is_foreign && $prep) {
395 $oldMMs[] = array($row['tablenames'], $row[$uidForeign_field]);
396 } else {
397 $oldMMs[] = $row[$uidForeign_field];
398 }
399 $oldMMs_inclUid[] = array($row['tablenames'], $row[$uidForeign_field], $row['uid']);
400 }
401
402 // For each item, insert it:
403 foreach ($this->itemArray as $val) {
404 $c++;
405
406 if ($prep || $val['table'] == '_NO_TABLE') {
407 // Insert current table if needed
408 if ($this->MM_is_foreign) {
409 $tablename = $this->currentTable;
410 } else {
411 $tablename = $val['table'];
412 }
413 } else {
414 $tablename = '';
415 }
416
417 if (!$this->MM_is_foreign && $prep) {
418 $item = array($val['table'], $val['id']);
419 } else {
420 $item = $val['id'];
421 }
422
423 if (in_array($item, $oldMMs)) {
424 $oldMMs_index = array_search($item, $oldMMs);
425 // 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.
426 $whereClause = $uidLocal_field . '=' . $uid . ' AND ' . $uidForeign_field . '=' . $val['id'] .
427 ($this->MM_hasUidField ? ' AND uid=' . intval($oldMMs_inclUid[$oldMMs_index][2]) : '');
428 if ($tablename) {
429 $whereClause .= ' AND tablenames=' . $GLOBALS['TYPO3_DB']->fullQuoteStr($tablename, $MM_tableName);
430 }
431 $GLOBALS['TYPO3_DB']->exec_UPDATEquery($MM_tableName, $whereClause . $additionalWhere, array($sorting_field => $c));
432
433 // Remove the item from the $oldMMs array so after this foreach loop only the ones that need to be deleted are in there.
434 unset($oldMMs[$oldMMs_index]);
435 // Remove the item from the $oldMMs array so after this foreach loop only the ones that need to be deleted are in there.
436 unset($oldMMs_inclUid[$oldMMs_index]);
437 } else {
438
439 $insertFields = $this->MM_insert_fields;
440 $insertFields[$uidLocal_field] = $uid;
441 $insertFields[$uidForeign_field] = $val['id'];
442 $insertFields[$sorting_field] = $c;
443 if ($tablename) {
444 $insertFields['tablenames'] = $tablename;
445 }
446
447 $GLOBALS['TYPO3_DB']->exec_INSERTquery($MM_tableName, $insertFields);
448
449 if ($this->MM_is_foreign) {
450 $this->updateRefIndex($val['table'], $val['id']);
451 }
452 }
453 }
454
455 // Delete all not-used relations:
456 if (is_array($oldMMs) && count($oldMMs) > 0) {
457 $removeClauses = array();
458 $updateRefIndex_records = array();
459 foreach ($oldMMs as $oldMM_key => $mmItem) {
460 // If UID field is present, of course we need only use that for deleting.
461 if ($this->MM_hasUidField) {
462 $removeClauses[] = 'uid=' . intval($oldMMs_inclUid[$oldMM_key][2]);
463 $elDelete = $oldMMs_inclUid[$oldMM_key];
464 } else {
465 if (is_array($mmItem)) {
466 $removeClauses[] = 'tablenames=' . $GLOBALS['TYPO3_DB']->fullQuoteStr($mmItem[0], $MM_tableName) . ' AND ' . $uidForeign_field . '=' . $mmItem[1];
467 } else {
468 $removeClauses[] = $uidForeign_field . '=' . $mmItem;
469 }
470 }
471 if ($this->MM_is_foreign) {
472 if (is_array($mmItem)) {
473 $updateRefIndex_records[] = array($mmItem[0], $mmItem[1]);
474 } else {
475 $updateRefIndex_records[] = array($this->firstTable, $mmItem);
476 }
477 }
478 }
479 $deleteAddWhere = ' AND (' . implode(' OR ', $removeClauses) . ')';
480 $GLOBALS['TYPO3_DB']->exec_DELETEquery($MM_tableName, $uidLocal_field . '=' . intval($uid) . $deleteAddWhere . $additionalWhere_tablenames . $additionalWhere);
481
482 // Update ref index:
483 foreach ($updateRefIndex_records as $pair) {
484 $this->updateRefIndex($pair[0], $pair[1]);
485 }
486 }
487
488 // 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.
489 // This could also have been fixed in updateDB in tcemain, however I decided to do it here ...
490 $this->updateRefIndex($this->currentTable, $uid);
491 }
492 }
493
494 /**
495 * Remaps MM table elements from one local uid to another
496 * Does NOT update the reference index for you, must be called subsequently to do that!
497 *
498 * @param string $MM_tableName MM table name
499 * @param integer $uid Local, current UID
500 * @param integer $newUid Local, new UID
501 * @param boolean $prependTableName If set, then table names will always be written.
502 * @return void
503 */
504 function remapMM($MM_tableName, $uid, $newUid, $prependTableName = 0) {
505 // In case of a reverse relation
506 if ($this->MM_is_foreign) {
507 $uidLocal_field = 'uid_foreign';
508 } else { // default
509 $uidLocal_field = 'uid_local';
510 }
511
512 // If there are tables...
513 $tableC = count($this->tableArray);
514 if ($tableC) {
515 // Boolean: does the field "tablename" need to be filled?
516 $prep = ($tableC > 1 || $prependTableName || $this->MM_isMultiTableRelationship) ? 1 : 0;
517 $c = 0;
518
519 $additionalWhere_tablenames = '';
520 if ($this->MM_is_foreign && $prep) {
521 $additionalWhere_tablenames = ' AND tablenames=' . $GLOBALS['TYPO3_DB']->fullQuoteStr($this->currentTable, $MM_tableName);
522 }
523
524 $additionalWhere = '';
525 // Add WHERE clause if configured
526 if ($this->MM_table_where) {
527 $additionalWhere .= LF . str_replace('###THIS_UID###', intval($uid), $this->MM_table_where);
528 }
529 // Select, update or delete only those relations that match the configured fields
530 foreach ($this->MM_match_fields as $field => $value) {
531 $additionalWhere .= ' AND ' . $field . '=' . $GLOBALS['TYPO3_DB']->fullQuoteStr($value, $MM_tableName);
532 }
533
534 $GLOBALS['TYPO3_DB']->exec_UPDATEquery($MM_tableName, $uidLocal_field . '=' . intval($uid) . $additionalWhere_tablenames . $additionalWhere, array($uidLocal_field => $newUid));
535 }
536 }
537
538 /**
539 * Reads items from a foreign_table, that has a foreign_field (uid of the parent record) and
540 * stores the parts in the internal array itemArray and tableArray.
541 *
542 * @param integer $uid The uid of the parent record (this value is also on the foreign_table in the foreign_field)
543 * @param array $conf TCA configuration for current field
544 * @return void
545 */
546 function readForeignField($uid, $conf) {
547 $key = 0;
548 $uid = intval($uid);
549 $whereClause = '';
550 $foreign_table = $conf['foreign_table'];
551 $foreign_table_field = $conf['foreign_table_field'];
552 $useDeleteClause = $this->undeleteRecord ? FALSE : TRUE;
553 $foreign_match_fields = is_array($conf['foreign_match_fields']) ? $conf['foreign_match_fields'] : array();
554
555 // Search for $uid in foreign_field, and if we have symmetric relations, do this also on symmetric_field
556 if ($conf['symmetric_field']) {
557 $whereClause = '(' . $conf['foreign_field'] . '=' . $uid . ' OR ' . $conf['symmetric_field'] . '=' . $uid . ')';
558 } else {
559 $whereClause = $conf['foreign_field'] . '=' . $uid;
560 }
561 // Use the deleteClause (e.g. "deleted=0") on this table
562 if ($useDeleteClause) {
563 $whereClause .= t3lib_BEfunc::deleteClause($foreign_table);
564 }
565 // If it's requested to look for the parent uid AND the parent table,
566 // add an additional SQL-WHERE clause
567 if ($foreign_table_field && $this->currentTable) {
568 $whereClause .= ' AND ' . $foreign_table_field . '=' . $GLOBALS['TYPO3_DB']->fullQuoteStr($this->currentTable, $foreign_table);
569 }
570
571 // Add additional where clause if foreign_match_fields are defined
572 foreach ($foreign_match_fields as $field => $value) {
573 $whereClause .= ' AND ' . $field . '=' . $GLOBALS['TYPO3_DB']->fullQuoteStr($value, $foreign_table);
574 }
575
576 // Select children in the same workspace:
577 if (t3lib_BEfunc::isTableWorkspaceEnabled($this->currentTable) && t3lib_BEfunc::isTableWorkspaceEnabled($foreign_table)) {
578 $currentRecord = t3lib_BEfunc::getRecord($this->currentTable, $uid, 't3ver_wsid', '', $useDeleteClause);
579 $whereClause .= t3lib_BEfunc::getWorkspaceWhereClause($foreign_table, $currentRecord['t3ver_wsid']);
580 }
581
582 // Get the correct sorting field
583 // Specific manual sortby for data handled by this field
584 if ($conf['foreign_sortby']) {
585 if ($conf['symmetric_sortby'] && $conf['symmetric_field']) {
586 // Sorting depends on, from which side of the relation we're looking at it
587 $sortby = '
588 CASE
589 WHEN ' . $conf['foreign_field'] . '=' . $uid . '
590 THEN ' . $conf['foreign_sortby'] . '
591 ELSE ' . $conf['symmetric_sortby'] . '
592 END';
593 } else {
594 // Regular single-side behaviour
595 $sortby = $conf['foreign_sortby'];
596 }
597 } elseif ($conf['foreign_default_sortby']) { // Specific default sortby for data handled by this field
598 $sortby = $conf['foreign_default_sortby'];
599 } elseif ($GLOBALS['TCA'][$foreign_table]['ctrl']['sortby']) { // Manual sortby for all table records
600 $sortby = $GLOBALS['TCA'][$foreign_table]['ctrl']['sortby'];
601 } elseif ($GLOBALS['TCA'][$foreign_table]['ctrl']['default_sortby']) { // Default sortby for all table records
602 $sortby = $GLOBALS['TCA'][$foreign_table]['ctrl']['default_sortby'];
603 }
604
605 // Strip a possible "ORDER BY" in front of the $sortby value
606 $sortby = $GLOBALS['TYPO3_DB']->stripOrderBy($sortby);
607 // Get the rows from storage
608 $rows = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows('uid', $foreign_table, $whereClause, '', $sortby);
609
610 if (count($rows)) {
611 foreach ($rows as $row) {
612 $this->itemArray[$key]['id'] = $row['uid'];
613 $this->itemArray[$key]['table'] = $foreign_table;
614 $this->tableArray[$foreign_table][] = $row['uid'];
615 $key++;
616 }
617 }
618 }
619
620 /**
621 * Write the sorting values to a foreign_table, that has a foreign_field (uid of the parent record)
622 *
623 * @param array $conf TCA configuration for current field
624 * @param integer $parentUid The uid of the parent record
625 * @param boolean $updateToUid Whether to update the foreign field with the $parentUid (on Copy)
626 * @param boolean $skipSorting Do not update the sorting columns, this could happen for imported values
627 * @return void
628 */
629 function writeForeignField($conf, $parentUid, $updateToUid = 0, $skipSorting = FALSE) {
630 $c = 0;
631 $foreign_table = $conf['foreign_table'];
632 $foreign_field = $conf['foreign_field'];
633 $symmetric_field = $conf['symmetric_field'];
634 $foreign_table_field = $conf['foreign_table_field'];
635 $foreign_match_fields = is_array($conf['foreign_match_fields']) ? $conf['foreign_match_fields'] : array();
636
637 // If there are table items and we have a proper $parentUid
638 if (t3lib_utility_Math::canBeInterpretedAsInteger($parentUid) && count($this->tableArray)) {
639 // If updateToUid is not a positive integer, set it to '0', so it will be ignored
640 if (!(t3lib_utility_Math::canBeInterpretedAsInteger($updateToUid) && $updateToUid > 0)) {
641 $updateToUid = 0;
642 }
643
644 $considerWorkspaces = ($GLOBALS['BE_USER']->workspace !== 0 && t3lib_BEfunc::isTableWorkspaceEnabled($foreign_table));
645
646 $fields = 'uid,' . $foreign_field;
647 // Consider the symmetric field if defined:
648 if ($symmetric_field) {
649 $fields .= ',' . $symmetric_field;
650 }
651 // Consider workspaces if defined and currently used:
652 if ($considerWorkspaces) {
653 $fields .= ',' . 't3ver_state,t3ver_oid';
654 }
655
656 // Update all items
657 foreach ($this->itemArray as $val) {
658 $uid = $val['id'];
659 $table = $val['table'];
660
661 // Fetch the current (not overwritten) relation record if we should handle symmetric relations
662 if ($symmetric_field || $considerWorkspaces) {
663 $row = t3lib_BEfunc::getRecord($table, $uid, $fields, '', FALSE);
664 }
665 if ($symmetric_field) {
666 $isOnSymmetricSide = self::isOnSymmetricSide($parentUid, $conf, $row);
667 }
668
669 $updateValues = $foreign_match_fields;
670 $workspaceValues = array();
671
672 // No update to the uid is requested, so this is the normal behaviour
673 // just update the fields and care about sorting
674 if (!$updateToUid) {
675 // Always add the pointer to the parent uid
676 if ($isOnSymmetricSide) {
677 $updateValues[$symmetric_field] = $parentUid;
678 } else {
679 $updateValues[$foreign_field] = $parentUid;
680 }
681
682 // If it is configured in TCA also to store the parent table in the child record, just do it
683 if ($foreign_table_field && $this->currentTable) {
684 $updateValues[$foreign_table_field] = $this->currentTable;
685 }
686
687 // Update sorting columns if not to be skipped
688 if (!$skipSorting) {
689 // Get the correct sorting field
690 // Specific manual sortby for data handled by this field
691 if ($conf['foreign_sortby']) {
692 $sortby = $conf['foreign_sortby'];
693 } elseif ($GLOBALS['TCA'][$foreign_table]['ctrl']['sortby']) { // manual sortby for all table records
694 $sortby = $GLOBALS['TCA'][$foreign_table]['ctrl']['sortby'];
695 }
696 // Apply sorting on the symmetric side (it depends on who created the relation, so what uid is in the symmetric_field):
697 if ($isOnSymmetricSide && isset($conf['symmetric_sortby']) && $conf['symmetric_sortby']) {
698 $sortby = $conf['symmetric_sortby'];
699 // Strip a possible "ORDER BY" in front of the $sortby value:
700 } else {
701 $sortby = $GLOBALS['TYPO3_DB']->stripOrderBy($sortby);
702 }
703
704 if ($sortby) {
705 $updateValues[$sortby] = $workspaceValues[$sortby] = ++$c;
706 }
707 }
708
709 // Update to a foreign_field/symmetric_field pointer is requested, normally used on record copies
710 // only update the fields, if the old uid is found somewhere - for select fields, TCEmain is doing this already!
711 } else {
712 if ($isOnSymmetricSide) {
713 $updateValues[$symmetric_field] = $updateToUid;
714 } else {
715 $updateValues[$foreign_field] = $updateToUid;
716 }
717 }
718
719 // Update accordant fields in the database:
720 if (count($updateValues)) {
721 $GLOBALS['TYPO3_DB']->exec_UPDATEquery($table, 'uid=' . intval($uid), $updateValues);
722 $this->updateRefIndex($table, $uid);
723 }
724 // Update accordant fields in the database for workspaces overlays/placeholders:
725 if (count($workspaceValues) && $considerWorkspaces) {
726 if (isset($row['t3ver_oid']) && $row['t3ver_oid'] && $row['t3ver_state'] == -1) {
727 $GLOBALS['TYPO3_DB']->exec_UPDATEquery($table, 'uid=' . intval($row['t3ver_oid']), $workspaceValues);
728 }
729 }
730 }
731 }
732 }
733
734 /**
735 * After initialization you can extract an array of the elements from the object. Use this function for that.
736 *
737 * @param boolean $prependTableName If set, then table names will ALWAYS be prepended (unless its a _NO_TABLE value)
738 * @return array A numeric array.
739 */
740 function getValueArray($prependTableName = '') {
741 // INIT:
742 $valueArray = Array();
743 $tableC = count($this->tableArray);
744
745 // If there are tables in the table array:
746 if ($tableC) {
747 // If there are more than ONE table in the table array, then always prepend table names:
748 $prep = ($tableC > 1 || $prependTableName) ? 1 : 0;
749
750 // Traverse the array of items:
751 foreach ($this->itemArray as $val) {
752 $valueArray[] = (($prep && $val['table'] != '_NO_TABLE') ? $val['table'] . '_' : '') .
753 $val['id'];
754 }
755 }
756 // Return the array
757 return $valueArray;
758 }
759
760 /**
761 * Converts id numbers from negative to positive.
762 *
763 * @param array $valueArray Array of [table]_[id] pairs.
764 * @param string $fTable Foreign table (the one used for positive numbers)
765 * @param string $nfTable Negative foreign table
766 * @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.
767 */
768 function convertPosNeg($valueArray, $fTable, $nfTable) {
769 if (is_array($valueArray) && $fTable) {
770 foreach ($valueArray as $key => $val) {
771 $val = strrev($val);
772 $parts = explode('_', $val, 2);
773 $theID = strrev($parts[0]);
774 $theTable = strrev($parts[1]);
775
776 if (t3lib_utility_Math::canBeInterpretedAsInteger($theID) && (!$theTable || !strcmp($theTable, $fTable) || !strcmp($theTable, $nfTable))) {
777 $valueArray[$key] = $theTable && strcmp($theTable, $fTable) ? $theID * -1 : $theID;
778 }
779 }
780 }
781 return $valueArray;
782 }
783
784 /**
785 * 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.
786 * If $this->fromTC is set you can save a little memory since only uid,pid and a few other fields are selected.
787 *
788 * @return void
789 */
790 function getFromDB() {
791 // Traverses the tables listed:
792 foreach ($this->tableArray as $key => $val) {
793 if (is_array($val)) {
794 $itemList = implode(',', $val);
795 if ($itemList) {
796 $from = '*';
797 if ($this->fromTC) {
798 $from = 'uid,pid';
799 if ($GLOBALS['TCA'][$key]['ctrl']['label']) {
800 // Titel
801 $from .= ',' . $GLOBALS['TCA'][$key]['ctrl']['label'];
802 }
803 if ($GLOBALS['TCA'][$key]['ctrl']['label_alt']) {
804 // Alternative Title-Fields
805 $from .= ',' . $GLOBALS['TCA'][$key]['ctrl']['label_alt'];
806 }
807 if ($GLOBALS['TCA'][$key]['ctrl']['thumbnail']) {
808 // Thumbnail
809 $from .= ',' . $GLOBALS['TCA'][$key]['ctrl']['thumbnail'];
810 }
811 }
812 $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery($from, $key, 'uid IN (' . $itemList . ')' . $this->additionalWhere[$key]);
813 while ($row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res)) {
814 $this->results[$key][$row['uid']] = $row;
815 }
816 }
817 }
818 }
819 return $this->results;
820 }
821
822 /**
823 * Prepare items from itemArray to be transferred to the TCEforms interface (as a comma list)
824 *
825 * @return string
826 * @see t3lib_transferdata::renderRecord()
827 */
828 function readyForInterface() {
829 if (!is_array($this->itemArray)) {
830 return FALSE;
831 }
832
833 $output = array();
834 // For use when getting the paths
835 $perms_clause = $GLOBALS['BE_USER']->getPagePermsClause(1);
836 $titleLen = intval($GLOBALS['BE_USER']->uc['titleLen']);
837
838 foreach ($this->itemArray as $key => $val) {
839 $theRow = $this->results[$val['table']][$val['id']];
840 if ($theRow && is_array($GLOBALS['TCA'][$val['table']])) {
841 $label = t3lib_div::fixed_lgd_cs(strip_tags(t3lib_BEfunc::getRecordTitle($val['table'], $theRow)), $titleLen);
842 $label = ($label) ? $label : '[...]';
843 $output[] = str_replace(',', '', $val['table'] . '_' . $val['id'] . '|' . rawurlencode($label));
844 }
845 }
846 return implode(',', $output);
847 }
848
849 /**
850 * Counts the items in $this->itemArray and puts this value in an array by default.
851 *
852 * @param boolean $returnAsArray Whether to put the count value in an array
853 * @return mixed The plain count as integer or the same inside an array
854 */
855 function countItems($returnAsArray = TRUE) {
856 $count = count($this->itemArray);
857 if ($returnAsArray) {
858 $count = array($count);
859 }
860 return $count;
861 }
862
863 /**
864 * Update Reference Index (sys_refindex) for a record
865 * Should be called any almost any update to a record which could affect references inside the record.
866 * (copied from TCEmain)
867 *
868 * @param string $table Table name
869 * @param integer $id Record UID
870 * @return array Information concerning modifications delivered by t3lib_refindex::updateRefIndexTable()
871 */
872 function updateRefIndex($table, $id) {
873 if ($this->updateReferenceIndex === TRUE) {
874 /** @var $refIndexObj t3lib_refindex */
875 $refIndexObj = t3lib_div::makeInstance('t3lib_refindex');
876 return $refIndexObj->updateRefIndexTable($table, $id);
877 }
878 }
879
880 /**
881 * Checks, if we're looking from the "other" side, the symmetric side, to a symmetric relation.
882 *
883 * @param string $parentUid The uid of the parent record
884 * @param array $parentConf The TCA configuration of the parent field embedding the child records
885 * @param array $childRec The record row of the child record
886 * @return boolean Returns TRUE if looking from the symmetric ("other") side to the relation.
887 */
888 function isOnSymmetricSide($parentUid, $parentConf, $childRec) {
889 return t3lib_utility_Math::canBeInterpretedAsInteger($childRec['uid']) && $parentConf['symmetric_field'] && $parentUid == $childRec[$parentConf['symmetric_field']]
890 ? TRUE
891 : FALSE;
892 }
893 }
894
895 ?>