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