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