[BUGFIX] Faulty localization if IRRE is used on pages
[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 $foreign_table = $conf['foreign_table'];
594 $foreign_table_field = $conf['foreign_table_field'];
595 $useDeleteClause = $this->undeleteRecord ? FALSE : TRUE;
596 $foreign_match_fields = is_array($conf['foreign_match_fields']) ? $conf['foreign_match_fields'] : array();
597 // Search for $uid in foreign_field, and if we have symmetric relations, do this also on symmetric_field
598 if ($conf['symmetric_field']) {
599 $whereClause = '(' . $conf['foreign_field'] . '=' . $uid . ' OR ' . $conf['symmetric_field'] . '=' . $uid . ')';
600 } else {
601 $whereClause = $conf['foreign_field'] . '=' . $uid;
602 }
603 // Use the deleteClause (e.g. "deleted=0") on this table
604 if ($useDeleteClause) {
605 $whereClause .= \TYPO3\CMS\Backend\Utility\BackendUtility::deleteClause($foreign_table);
606 }
607 // If it's requested to look for the parent uid AND the parent table,
608 // add an additional SQL-WHERE clause
609 if ($foreign_table_field && $this->currentTable) {
610 $whereClause .= ' AND ' . $foreign_table_field . '=' . $GLOBALS['TYPO3_DB']->fullQuoteStr($this->currentTable, $foreign_table);
611 }
612 // Add additional where clause if foreign_match_fields are defined
613 foreach ($foreign_match_fields as $field => $value) {
614 $whereClause .= ' AND ' . $field . '=' . $GLOBALS['TYPO3_DB']->fullQuoteStr($value, $foreign_table);
615 }
616 // Select children in the same workspace:
617 if (\TYPO3\CMS\Backend\Utility\BackendUtility::isTableWorkspaceEnabled($this->currentTable) && \TYPO3\CMS\Backend\Utility\BackendUtility::isTableWorkspaceEnabled($foreign_table)) {
618 $currentRecord = \TYPO3\CMS\Backend\Utility\BackendUtility::getRecord($this->currentTable, $uid, 't3ver_wsid', '', $useDeleteClause);
619 $whereClause .= \TYPO3\CMS\Backend\Utility\BackendUtility::getWorkspaceWhereClause($foreign_table, $currentRecord['t3ver_wsid']);
620 }
621 // Get the correct sorting field
622 // Specific manual sortby for data handled by this field
623 if ($conf['foreign_sortby']) {
624 if ($conf['symmetric_sortby'] && $conf['symmetric_field']) {
625 // Sorting depends on, from which side of the relation we're looking at it
626 $sortby = '
627 CASE
628 WHEN ' . $conf['foreign_field'] . '=' . $uid . '
629 THEN ' . $conf['foreign_sortby'] . '
630 ELSE ' . $conf['symmetric_sortby'] . '
631 END';
632 } else {
633 // Regular single-side behaviour
634 $sortby = $conf['foreign_sortby'];
635 }
636 } elseif ($conf['foreign_default_sortby']) {
637 // Specific default sortby for data handled by this field
638 $sortby = $conf['foreign_default_sortby'];
639 } elseif ($GLOBALS['TCA'][$foreign_table]['ctrl']['sortby']) {
640 // Manual sortby for all table records
641 $sortby = $GLOBALS['TCA'][$foreign_table]['ctrl']['sortby'];
642 } elseif ($GLOBALS['TCA'][$foreign_table]['ctrl']['default_sortby']) {
643 // Default sortby for all table records
644 $sortby = $GLOBALS['TCA'][$foreign_table]['ctrl']['default_sortby'];
645 }
646 // Strip a possible "ORDER BY" in front of the $sortby value
647 $sortby = $GLOBALS['TYPO3_DB']->stripOrderBy($sortby);
648 // Get the rows from storage
649 $rows = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows('uid', $foreign_table, $whereClause, '', $sortby);
650 if (count($rows)) {
651 foreach ($rows as $row) {
652 $this->itemArray[$key]['id'] = $row['uid'];
653 $this->itemArray[$key]['table'] = $foreign_table;
654 $this->tableArray[$foreign_table][] = $row['uid'];
655 $key++;
656 }
657 }
658 }
659
660 /**
661 * Write the sorting values to a foreign_table, that has a foreign_field (uid of the parent record)
662 *
663 * @param array $conf TCA configuration for current field
664 * @param integer $parentUid The uid of the parent record
665 * @param boolean $updateToUid Whether to update the foreign field with the $parentUid (on Copy)
666 * @param boolean $skipSorting Do not update the sorting columns, this could happen for imported values
667 * @return void
668 * @todo Define visibility
669 */
670 public function writeForeignField($conf, $parentUid, $updateToUid = 0, $skipSorting = FALSE) {
671 $c = 0;
672 $foreign_table = $conf['foreign_table'];
673 $foreign_field = $conf['foreign_field'];
674 $symmetric_field = $conf['symmetric_field'];
675 $foreign_table_field = $conf['foreign_table_field'];
676 $foreign_match_fields = is_array($conf['foreign_match_fields']) ? $conf['foreign_match_fields'] : array();
677 // If there are table items and we have a proper $parentUid
678 if (\TYPO3\CMS\Core\Utility\MathUtility::canBeInterpretedAsInteger($parentUid) && count($this->tableArray)) {
679 // If updateToUid is not a positive integer, set it to '0', so it will be ignored
680 if (!(\TYPO3\CMS\Core\Utility\MathUtility::canBeInterpretedAsInteger($updateToUid) && $updateToUid > 0)) {
681 $updateToUid = 0;
682 }
683 $considerWorkspaces = $GLOBALS['BE_USER']->workspace !== 0 && \TYPO3\CMS\Backend\Utility\BackendUtility::isTableWorkspaceEnabled($foreign_table);
684 $fields = 'uid,' . $foreign_field;
685 // Consider the symmetric field if defined:
686 if ($symmetric_field) {
687 $fields .= ',' . $symmetric_field;
688 }
689 // Consider workspaces if defined and currently used:
690 if ($considerWorkspaces) {
691 $fields .= ',' . 't3ver_state,t3ver_oid';
692 }
693 // Update all items
694 foreach ($this->itemArray as $val) {
695 $uid = $val['id'];
696 $table = $val['table'];
697 // Fetch the current (not overwritten) relation record if we should handle symmetric relations
698 if ($symmetric_field || $considerWorkspaces) {
699 $row = \TYPO3\CMS\Backend\Utility\BackendUtility::getRecord($table, $uid, $fields, '', FALSE);
700 }
701 if ($symmetric_field) {
702 $isOnSymmetricSide = self::isOnSymmetricSide($parentUid, $conf, $row);
703 }
704 $updateValues = $foreign_match_fields;
705 $workspaceValues = array();
706 // No update to the uid is requested, so this is the normal behaviour
707 // just update the fields and care about sorting
708 if (!$updateToUid) {
709 // Always add the pointer to the parent uid
710 if ($isOnSymmetricSide) {
711 $updateValues[$symmetric_field] = $parentUid;
712 } else {
713 $updateValues[$foreign_field] = $parentUid;
714 }
715 // If it is configured in TCA also to store the parent table in the child record, just do it
716 if ($foreign_table_field && $this->currentTable) {
717 $updateValues[$foreign_table_field] = $this->currentTable;
718 }
719 // Update sorting columns if not to be skipped
720 if (!$skipSorting) {
721 // Get the correct sorting field
722 // Specific manual sortby for data handled by this field
723 if ($conf['foreign_sortby']) {
724 $sortby = $conf['foreign_sortby'];
725 } elseif ($GLOBALS['TCA'][$foreign_table]['ctrl']['sortby']) {
726 // manual sortby for all table records
727 $sortby = $GLOBALS['TCA'][$foreign_table]['ctrl']['sortby'];
728 }
729 // Apply sorting on the symmetric side (it depends on who created the relation, so what uid is in the symmetric_field):
730 if ($isOnSymmetricSide && isset($conf['symmetric_sortby']) && $conf['symmetric_sortby']) {
731 $sortby = $conf['symmetric_sortby'];
732 } else {
733 $sortby = $GLOBALS['TYPO3_DB']->stripOrderBy($sortby);
734 }
735 if ($sortby) {
736 $updateValues[$sortby] = ($workspaceValues[$sortby] = ++$c);
737 }
738 }
739 } else {
740 if ($isOnSymmetricSide) {
741 $updateValues[$symmetric_field] = $updateToUid;
742 } else {
743 $updateValues[$foreign_field] = $updateToUid;
744 }
745 }
746 // Update accordant fields in the database:
747 if (count($updateValues)) {
748 $GLOBALS['TYPO3_DB']->exec_UPDATEquery($table, 'uid=' . intval($uid), $updateValues);
749 $this->updateRefIndex($table, $uid);
750 }
751 // Update accordant fields in the database for workspaces overlays/placeholders:
752 if (count($workspaceValues) && $considerWorkspaces) {
753 if (isset($row['t3ver_oid']) && $row['t3ver_oid'] && $row['t3ver_state'] == -1) {
754 $GLOBALS['TYPO3_DB']->exec_UPDATEquery($table, 'uid=' . intval($row['t3ver_oid']), $workspaceValues);
755 }
756 }
757 }
758 }
759 }
760
761 /**
762 * After initialization you can extract an array of the elements from the object. Use this function for that.
763 *
764 * @param boolean $prependTableName If set, then table names will ALWAYS be prepended (unless its a _NO_TABLE value)
765 * @return array A numeric array.
766 * @todo Define visibility
767 */
768 public function getValueArray($prependTableName = '') {
769 // INIT:
770 $valueArray = array();
771 $tableC = count($this->tableArray);
772 // If there are tables in the table array:
773 if ($tableC) {
774 // If there are more than ONE table in the table array, then always prepend table names:
775 $prep = $tableC > 1 || $prependTableName ? 1 : 0;
776 // Traverse the array of items:
777 foreach ($this->itemArray as $val) {
778 $valueArray[] = ($prep && $val['table'] != '_NO_TABLE' ? $val['table'] . '_' : '') . $val['id'];
779 }
780 }
781 // Return the array
782 return $valueArray;
783 }
784
785 /**
786 * Converts id numbers from negative to positive.
787 *
788 * @param array $valueArray Array of [table]_[id] pairs.
789 * @param string $fTable Foreign table (the one used for positive numbers)
790 * @param string $nfTable Negative foreign table
791 * @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.
792 * @todo Define visibility
793 */
794 public function convertPosNeg($valueArray, $fTable, $nfTable) {
795 if (is_array($valueArray) && $fTable) {
796 foreach ($valueArray as $key => $val) {
797 $val = strrev($val);
798 $parts = explode('_', $val, 2);
799 $theID = strrev($parts[0]);
800 $theTable = strrev($parts[1]);
801 if (\TYPO3\CMS\Core\Utility\MathUtility::canBeInterpretedAsInteger($theID) && (!$theTable || !strcmp($theTable, $fTable) || !strcmp($theTable, $nfTable))) {
802 $valueArray[$key] = $theTable && strcmp($theTable, $fTable) ? $theID * -1 : $theID;
803 }
804 }
805 }
806 return $valueArray;
807 }
808
809 /**
810 * 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.
811 * If $this->fromTC is set you can save a little memory since only uid,pid and a few other fields are selected.
812 *
813 * @return void
814 * @todo Define visibility
815 */
816 public function getFromDB() {
817 // Traverses the tables listed:
818 foreach ($this->tableArray as $key => $val) {
819 if (is_array($val)) {
820 $itemList = implode(',', $val);
821 if ($itemList) {
822 $from = '*';
823 if ($this->fromTC) {
824 $from = 'uid,pid';
825 if ($GLOBALS['TCA'][$key]['ctrl']['label']) {
826 // Titel
827 $from .= ',' . $GLOBALS['TCA'][$key]['ctrl']['label'];
828 }
829 if ($GLOBALS['TCA'][$key]['ctrl']['label_alt']) {
830 // Alternative Title-Fields
831 $from .= ',' . $GLOBALS['TCA'][$key]['ctrl']['label_alt'];
832 }
833 if ($GLOBALS['TCA'][$key]['ctrl']['thumbnail']) {
834 // Thumbnail
835 $from .= ',' . $GLOBALS['TCA'][$key]['ctrl']['thumbnail'];
836 }
837 }
838 $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery($from, $key, 'uid IN (' . $itemList . ')' . $this->additionalWhere[$key]);
839 while ($row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res)) {
840 $this->results[$key][$row['uid']] = $row;
841 }
842 $GLOBALS['TYPO3_DB']->sql_free_result($res);
843 }
844 }
845 }
846 return $this->results;
847 }
848
849 /**
850 * Prepare items from itemArray to be transferred to the TCEforms interface (as a comma list)
851 *
852 * @return string
853 * @see t3lib_transferdata::renderRecord()
854 * @todo Define visibility
855 */
856 public function readyForInterface() {
857 if (!is_array($this->itemArray)) {
858 return FALSE;
859 }
860 $output = array();
861 // For use when getting the paths
862 $perms_clause = $GLOBALS['BE_USER']->getPagePermsClause(1);
863 $titleLen = intval($GLOBALS['BE_USER']->uc['titleLen']);
864 foreach ($this->itemArray as $key => $val) {
865 $theRow = $this->results[$val['table']][$val['id']];
866 if ($theRow && is_array($GLOBALS['TCA'][$val['table']])) {
867 $label = \TYPO3\CMS\Core\Utility\GeneralUtility::fixed_lgd_cs(strip_tags(\TYPO3\CMS\Backend\Utility\BackendUtility::getRecordTitle($val['table'], $theRow)), $titleLen);
868 $label = $label ? $label : '[...]';
869 $output[] = str_replace(',', '', $val['table'] . '_' . $val['id'] . '|' . rawurlencode($label));
870 }
871 }
872 return implode(',', $output);
873 }
874
875 /**
876 * Counts the items in $this->itemArray and puts this value in an array by default.
877 *
878 * @param boolean $returnAsArray Whether to put the count value in an array
879 * @return mixed The plain count as integer or the same inside an array
880 * @todo Define visibility
881 */
882 public function countItems($returnAsArray = TRUE) {
883 $count = count($this->itemArray);
884 if ($returnAsArray) {
885 $count = array($count);
886 }
887 return $count;
888 }
889
890 /**
891 * Update Reference Index (sys_refindex) for a record
892 * Should be called any almost any update to a record which could affect references inside the record.
893 * (copied from TCEmain)
894 *
895 * @param string $table Table name
896 * @param integer $id Record UID
897 * @return array Information concerning modifications delivered by t3lib_refindex::updateRefIndexTable()
898 * @todo Define visibility
899 */
900 public function updateRefIndex($table, $id) {
901 if ($this->updateReferenceIndex === TRUE) {
902 /** @var $refIndexObj \TYPO3\CMS\Core\Database\ReferenceIndex */
903 $refIndexObj = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\Database\\ReferenceIndex');
904 return $refIndexObj->updateRefIndexTable($table, $id);
905 }
906 }
907
908 /**
909 * Checks, if we're looking from the "other" side, the symmetric side, to a symmetric relation.
910 *
911 * @param string $parentUid The uid of the parent record
912 * @param array $parentConf The TCA configuration of the parent field embedding the child records
913 * @param array $childRec The record row of the child record
914 * @return boolean Returns TRUE if looking from the symmetric ("other") side to the relation.
915 * @todo Define visibility
916 */
917 public function isOnSymmetricSide($parentUid, $parentConf, $childRec) {
918 return \TYPO3\CMS\Core\Utility\MathUtility::canBeInterpretedAsInteger($childRec['uid']) && $parentConf['symmetric_field'] && $parentUid == $childRec[$parentConf['symmetric_field']] ? TRUE : FALSE;
919 }
920
921 }
922
923
924 ?>