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