002ad9ad9b8903e0348c7c6863fb1156a464db33
[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 text file 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 use TYPO3\CMS\Backend\Utility\BackendUtility;
31 use TYPO3\CMS\Core\Utility\GeneralUtility;
32 use TYPO3\CMS\Core\Utility\MathUtility;
33 use TYPO3\CMS\Core\Versioning\VersionState;
34
35 /**
36 * Load database groups (relations)
37 * Used to process the relations created by the TCA element types "group" and "select" for database records.
38 * Manages MM-relations as well.
39 *
40 * @author Kasper Skårhøj <kasperYYYY@typo3.com>
41 */
42 class RelationHandler {
43
44 /**
45 * $fetchAllFields if false getFromDB() fetches only uid, pid, thumbnail and label fields (as defined in TCA)
46 *
47 * @var boolean
48 */
49 protected $fetchAllFields = FALSE;
50
51 /**
52 * If set, values that are not ids in tables are normally discarded. By this options they will be preserved.
53 * @todo Define visibility
54 */
55 public $registerNonTableValues = 0;
56
57 /**
58 * Contains the table names as keys. The values are the id-values for each table.
59 * Should ONLY contain proper table names.
60 *
61 * @var array
62 * @todo Define visibility
63 */
64 public $tableArray = array();
65
66 /**
67 * Contains items in an numeric array (table/id for each). Tablenames here might be "_NO_TABLE"
68 *
69 * @var array
70 * @todo Define visibility
71 */
72 public $itemArray = array();
73
74 /**
75 * Array for NON-table elements
76 *
77 * @var array
78 * @todo Define visibility
79 */
80 public $nonTableArray = array();
81
82 /**
83 * @var array
84 * @todo Define visibility
85 */
86 public $additionalWhere = array();
87
88 /**
89 * Deleted-column is added to additionalWhere... if this is set...
90 *
91 * @var boolean
92 * @todo Define visibility
93 */
94 public $checkIfDeleted = TRUE;
95
96 /**
97 * @var array
98 * @todo Define visibility
99 */
100 public $dbPaths = array();
101
102 /**
103 * Will contain the first table name in the $tablelist (for positive ids)
104 *
105 * @var string
106 * @todo Define visibility
107 */
108 public $firstTable = '';
109
110 /**
111 * Will contain the second table name in the $tablelist (for negative ids)
112 *
113 * @var string
114 * @todo Define visibility
115 */
116 public $secondTable = '';
117
118 /**
119 * If TRUE, uid_local and uid_foreign are switched, and the current table
120 * is inserted as tablename - this means you display a foreign relation "from the opposite side"
121 *
122 * @var boolean
123 * @todo Define visibility
124 */
125 public $MM_is_foreign = FALSE;
126
127 /**
128 * Field name at the "local" side of the MM relation
129 *
130 * @var string
131 * @todo Define visibility
132 */
133 public $MM_oppositeField = '';
134
135 /**
136 * Only set if MM_is_foreign is set
137 *
138 * @var string
139 * @todo Define visibility
140 */
141 public $MM_oppositeTable = '';
142
143 /**
144 * Only set if MM_is_foreign is set
145 *
146 * @var string
147 * @todo Define visibility
148 */
149 public $MM_oppositeFieldConf = '';
150
151 /**
152 * Is empty by default; if MM_is_foreign is set and there is more than one table
153 * allowed (on the "local" side), then it contains the first table (as a fallback)
154 * @todo Define visibility
155 * @var string
156 */
157 public $MM_isMultiTableRelationship = '';
158
159 /**
160 * Current table => Only needed for reverse relations
161 *
162 * @var string
163 * @todo Define visibility
164 */
165 public $currentTable;
166
167 /**
168 * If a record should be undeleted
169 * (so do not use the $useDeleteClause on \TYPO3\CMS\Backend\Utility\BackendUtility)
170 *
171 * @var boolean
172 * @todo Define visibility
173 */
174 public $undeleteRecord;
175
176 /**
177 * Array of fields value pairs that should match while SELECT
178 * and will be written into MM table if $MM_insert_fields is not set
179 *
180 * @var array
181 * @todo Define visibility
182 */
183 public $MM_match_fields = array();
184
185 /**
186 * This is set to TRUE if the MM table has a UID field.
187 *
188 * @var boolean
189 * @todo Define visibility
190 */
191 public $MM_hasUidField;
192
193 /**
194 * Array of fields and value pairs used for insert in MM table
195 *
196 * @var array
197 * @todo Define visibility
198 */
199 public $MM_insert_fields = array();
200
201 /**
202 * Extra MM table where
203 *
204 * @var string
205 * @todo Define visibility
206 */
207 public $MM_table_where = '';
208
209 /**
210 * Usage of a MM field on the opposite relation.
211 *
212 * @var array
213 */
214 protected $MM_oppositeUsage;
215
216 /**
217 * @var boolean
218 */
219 protected $updateReferenceIndex = TRUE;
220
221 /**
222 * @var bool
223 */
224 protected $useLiveParentIds = TRUE;
225
226 /**
227 * @var bool
228 */
229 protected $useLiveReferenceIds = TRUE;
230
231 /**
232 * This array will be filled by getFromDB().
233 *
234 * @var array
235 */
236 public $results = array();
237
238 /**
239 * Initialization of the class.
240 *
241 * @param string $itemlist List of group/select items
242 * @param string $tablelist Comma list of tables, first table takes priority if no table is set for an entry in the list.
243 * @param string $MMtable Name of a MM table.
244 * @param integer $MMuid Local UID for MM lookup
245 * @param string $currentTable Current table name
246 * @param array $conf TCA configuration for current field
247 * @return void
248 * @todo Define visibility
249 */
250 public function start($itemlist, $tablelist, $MMtable = '', $MMuid = 0, $currentTable = '', $conf = array()) {
251 $conf = (array)$conf;
252 // SECTION: MM reverse relations
253 $this->MM_is_foreign = (boolean)$conf['MM_opposite_field'];
254 $this->MM_oppositeField = $conf['MM_opposite_field'];
255 $this->MM_table_where = $conf['MM_table_where'];
256 $this->MM_hasUidField = $conf['MM_hasUidField'];
257 $this->MM_match_fields = is_array($conf['MM_match_fields']) ? $conf['MM_match_fields'] : array();
258 $this->MM_insert_fields = is_array($conf['MM_insert_fields']) ? $conf['MM_insert_fields'] : $this->MM_match_fields;
259 $this->currentTable = $currentTable;
260 if (!empty($conf['MM_oppositeUsage']) && is_array($conf['MM_oppositeUsage'])) {
261 $this->MM_oppositeUsage = $conf['MM_oppositeUsage'];
262 }
263 if ($this->MM_is_foreign) {
264 $tmp = $conf['type'] === 'group' ? $conf['allowed'] : $conf['foreign_table'];
265 // Normally, $conf['allowed'] can contain a list of tables,
266 // but as we are looking at a MM relation from the foreign side,
267 // it only makes sense to allow one one table in $conf['allowed']
268 $tmp = GeneralUtility::trimExplode(',', $tmp);
269 $this->MM_oppositeTable = $tmp[0];
270 unset($tmp);
271 // Only add the current table name if there is more than one allowed field
272 // We must be sure this has been done at least once before accessing the "columns" part of TCA for a table.
273 $this->MM_oppositeFieldConf = $GLOBALS['TCA'][$this->MM_oppositeTable]['columns'][$this->MM_oppositeField]['config'];
274 if ($this->MM_oppositeFieldConf['allowed']) {
275 $oppositeFieldConf_allowed = explode(',', $this->MM_oppositeFieldConf['allowed']);
276 if (count($oppositeFieldConf_allowed) > 1 || $this->MM_oppositeFieldConf['allowed'] === '*') {
277 $this->MM_isMultiTableRelationship = $oppositeFieldConf_allowed[0];
278 }
279 }
280 }
281 // SECTION: normal MM relations
282 // If the table list is "*" then all tables are used in the list:
283 if (trim($tablelist) === '*') {
284 $tablelist = implode(',', array_keys($GLOBALS['TCA']));
285 }
286 // The tables are traversed and internal arrays are initialized:
287 $tempTableArray = GeneralUtility::trimExplode(',', $tablelist, TRUE);
288 foreach ($tempTableArray as $val) {
289 $tName = trim($val);
290 $this->tableArray[$tName] = array();
291 if ($this->checkIfDeleted && $GLOBALS['TCA'][$tName]['ctrl']['delete']) {
292 $fieldN = $tName . '.' . $GLOBALS['TCA'][$tName]['ctrl']['delete'];
293 $this->additionalWhere[$tName] .= ' AND ' . $fieldN . '=0';
294 }
295 }
296 if (is_array($this->tableArray)) {
297 reset($this->tableArray);
298 } else {
299 // No tables
300 return;
301 }
302 // Set first and second tables:
303 // Is the first table
304 $this->firstTable = key($this->tableArray);
305 next($this->tableArray);
306 // If the second table is set and the ID number is less than zero (later)
307 // then the record is regarded to come from the second table...
308 $this->secondTable = key($this->tableArray);
309 // Now, populate the internal itemArray and tableArray arrays:
310 // If MM, then call this function to do that:
311 if ($MMtable) {
312 if ($MMuid) {
313 $this->readMM($MMtable, $MMuid);
314 } else {
315 // Revert to readList() for new records in order to load possible default values from $itemlist
316 $this->readList($itemlist, $conf);
317 }
318 } elseif ($MMuid && $conf['foreign_field']) {
319 // If not MM but foreign_field, the read the records by the foreign_field
320 $this->readForeignField($MMuid, $conf);
321 } else {
322 // If not MM, then explode the itemlist by "," and traverse the list:
323 $this->readList($itemlist, $conf);
324 // Do automatic default_sortby, if any
325 if ($conf['foreign_default_sortby']) {
326 $this->sortList($conf['foreign_default_sortby']);
327 }
328 }
329 }
330
331 /**
332 * Magic setter method.
333 * Used for compatibility with changed attribute visibility
334 *
335 * @param string $name name of the attribute
336 * @param mixed $value value to set the attribute to
337 * @deprecated since 6.1, only required as compatibility layer for renamed attribute $fromTC
338 */
339 public function __set($name, $value) {
340 if($name === 'fromTC') {
341 GeneralUtility::deprecationLog(
342 '$fromTC is protected since TYPO3 6.1. Use setFetchAllFields() instead!'
343 );
344 $this->setFetchAllFields(!$value);
345 }
346 }
347
348 /**
349 * Sets $fetchAllFields
350 *
351 * @param boolean $allFields enables fetching of all fields in getFromDB()
352 */
353 public function setFetchAllFields($allFields) {
354 $this->fetchAllFields = (boolean)$allFields;
355 }
356
357 /**
358 * Sets whether the reference index shall be updated.
359 *
360 * @param boolean $updateReferenceIndex Whether the reference index shall be updated
361 * @return void
362 */
363 public function setUpdateReferenceIndex($updateReferenceIndex) {
364 $this->updateReferenceIndex = (boolean)$updateReferenceIndex;
365 }
366
367 /**
368 * @param bool $useLiveParentIds
369 */
370 public function setUseLiveParentIds($useLiveParentIds) {
371 $this->useLiveParentIds = (bool)$useLiveParentIds;
372 }
373
374 /**
375 * @param bool $useLiveReferences
376 */
377 public function setUseLiveReferenceIds($useLiveReferenceIds) {
378 $this->useLiveReferenceIds = (bool)$useLiveReferenceIds;
379 }
380
381 /**
382 * Explodes the item list and stores the parts in the internal arrays itemArray and tableArray from MM records.
383 *
384 * @param string $itemlist Item list
385 * @param array $configuration Parent field configuration
386 * @return void
387 * @todo Define visibility
388 */
389 public function readList($itemlist, array $configuration) {
390 if ((string) trim($itemlist) != '') {
391 $tempItemArray = GeneralUtility::trimExplode(',', $itemlist);
392 // Changed to trimExplode 31/3 04; HMENU special type "list" didn't work
393 // if there were spaces in the list... I suppose this is better overall...
394 foreach ($tempItemArray as $key => $val) {
395 // Will be set to "1" if the entry was a real table/id:
396 $isSet = 0;
397 // Extract table name and id. This is un the formular [tablename]_[id]
398 // where table name MIGHT contain "_", hence the reversion of the string!
399 $val = strrev($val);
400 $parts = explode('_', $val, 2);
401 $theID = strrev($parts[0]);
402 // Check that the id IS an integer:
403 if (MathUtility::canBeInterpretedAsInteger($theID)) {
404 // Get the table name: If a part of the exploded string, use that.
405 // Otherwise if the id number is LESS than zero, use the second table, otherwise the first table
406 $theTable = trim($parts[1])
407 ? strrev(trim($parts[1]))
408 : ($this->secondTable && $theID < 0 ? $this->secondTable : $this->firstTable);
409 // If the ID is not blank and the table name is among the names in the inputted tableList
410 if (((string) $theID != '' && $theID) && $theTable && isset($this->tableArray[$theTable])) {
411 // Get ID as the right value:
412 $theID = $this->secondTable ? abs((int)$theID) : (int)$theID;
413 // Register ID/table name in internal arrays:
414 $this->itemArray[$key]['id'] = $theID;
415 $this->itemArray[$key]['table'] = $theTable;
416 $this->tableArray[$theTable][] = $theID;
417 // Set update-flag:
418 $isSet = 1;
419 }
420 }
421 // If it turns out that the value from the list was NOT a valid reference to a table-record,
422 // then we might still set it as a NO_TABLE value:
423 if (!$isSet && $this->registerNonTableValues) {
424 $this->itemArray[$key]['id'] = $tempItemArray[$key];
425 $this->itemArray[$key]['table'] = '_NO_TABLE';
426 $this->nonTableArray[] = $tempItemArray[$key];
427 }
428 }
429
430 // Skip if not dealing with IRRE in a CSV list on a workspace
431 if ($configuration['type'] !== 'inline' || empty($configuration['foreign_table']) || !empty($configuration['foreign_field'])
432 || !empty($configuration['MM']) || count($this->tableArray) !== 1 || empty($this->tableArray[$configuration['foreign_table']])
433 || (int)$GLOBALS['BE_USER']->workspace === 0 || !BackendUtility::isTableWorkspaceEnabled($configuration['foreign_table'])) {
434 return;
435 }
436
437 // Fetch live record data
438 if ($this->useLiveReferenceIds) {
439 foreach ($this->itemArray as &$item) {
440 $item['id'] = $this->getLiveDefaultId($item['table'], $item['id']);
441 }
442 // Directly overlay workspace data
443 } else {
444 $rows = array();
445 $foreignTable = $configuration['foreign_table'];
446 foreach ($this->tableArray[$foreignTable] as $itemId) {
447 $rows[$itemId] = array('uid' => $itemId);
448 }
449 $this->itemArray = array();
450 foreach ($this->getRecordVersionsIds($foreignTable, $rows) as $row) {
451 $this->itemArray[] = array(
452 'id' => $row['uid'],
453 'table' => $foreignTable,
454 );
455 }
456 }
457 }
458 }
459
460 /**
461 * Does a sorting on $this->itemArray depending on a default sortby field.
462 * This is only used for automatic sorting of comma separated lists.
463 * This function is only relevant for data that is stored in comma separated lists!
464 *
465 * @param string $sortby The default_sortby field/command (e.g. 'price DESC')
466 * @return void
467 * @todo Define visibility
468 */
469 public function sortList($sortby) {
470 // Sort directly without fetching addional data
471 if ($sortby == 'uid') {
472 usort(
473 $this->itemArray,
474 function ($a, $b) {
475 return $a['id'] < $b['id'] ? -1 : 1;
476 }
477 );
478 } elseif (count($this->tableArray) == 1) {
479 reset($this->tableArray);
480 $table = key($this->tableArray);
481 $uidList = implode(',', current($this->tableArray));
482 if ($uidList) {
483 $this->itemArray = array();
484 $this->tableArray = array();
485 $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery('uid', $table, 'uid IN (' . $uidList . ')', '', $sortby);
486 while ($row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res)) {
487 $this->itemArray[] = array('id' => $row['uid'], 'table' => $table);
488 $this->tableArray[$table][] = $row['uid'];
489 }
490 $GLOBALS['TYPO3_DB']->sql_free_result($res);
491 }
492 }
493 }
494
495 /**
496 * Reads the record tablename/id into the internal arrays itemArray and tableArray from MM records.
497 * You can call this function after start if you supply no list to start()
498 *
499 * @param string $tableName MM Tablename
500 * @param integer $uid Local UID
501 * @return void
502 * @todo Define visibility
503 */
504 public function readMM($tableName, $uid) {
505 $key = 0;
506 $additionalWhere = '';
507 $theTable = NULL;
508 // In case of a reverse relation
509 if ($this->MM_is_foreign) {
510 $uidLocal_field = 'uid_foreign';
511 $uidForeign_field = 'uid_local';
512 $sorting_field = 'sorting_foreign';
513 if ($this->MM_isMultiTableRelationship) {
514 $additionalWhere .= ' AND ( tablenames=' . $GLOBALS['TYPO3_DB']->fullQuoteStr($this->currentTable, $tableName);
515 // Be backwards compatible! When allowing more than one table after
516 // having previously allowed only one table, this case applies.
517 if ($this->currentTable == $this->MM_isMultiTableRelationship) {
518 $additionalWhere .= ' OR tablenames=\'\'';
519 }
520 $additionalWhere .= ' ) ';
521 }
522 $theTable = $this->MM_oppositeTable;
523 } else {
524 // Default
525 $uidLocal_field = 'uid_local';
526 $uidForeign_field = 'uid_foreign';
527 $sorting_field = 'sorting';
528 }
529 if ($this->MM_table_where) {
530 $additionalWhere .= LF . str_replace('###THIS_UID###', (int)$uid, $this->MM_table_where);
531 }
532 foreach ($this->MM_match_fields as $field => $value) {
533 $additionalWhere .= ' AND ' . $field . '=' . $GLOBALS['TYPO3_DB']->fullQuoteStr($value, $tableName);
534 }
535 // Select all MM relations:
536 $where = $uidLocal_field . '=' . (int)$uid . $additionalWhere;
537 $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery('*', $tableName, $where, '', $sorting_field);
538 while ($row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res)) {
539 // Default
540 if (!$this->MM_is_foreign) {
541 // If tablesnames columns exists and contain a name, then this value is the table, else it's the firstTable...
542 $theTable = $row['tablenames'] ?: $this->firstTable;
543 }
544 if (($row[$uidForeign_field] || $theTable == 'pages') && $theTable && isset($this->tableArray[$theTable])) {
545 $this->itemArray[$key]['id'] = $row[$uidForeign_field];
546 $this->itemArray[$key]['table'] = $theTable;
547 $this->tableArray[$theTable][] = $row[$uidForeign_field];
548 } elseif ($this->registerNonTableValues) {
549 $this->itemArray[$key]['id'] = $row[$uidForeign_field];
550 $this->itemArray[$key]['table'] = '_NO_TABLE';
551 $this->nonTableArray[] = $row[$uidForeign_field];
552 }
553 $key++;
554 }
555 $GLOBALS['TYPO3_DB']->sql_free_result($res);
556 }
557
558 /**
559 * Writes the internal itemArray to MM table:
560 *
561 * @param string $MM_tableName MM table name
562 * @param integer $uid Local UID
563 * @param boolean $prependTableName If set, then table names will always be written.
564 * @return void
565 * @todo Define visibility
566 */
567 public function writeMM($MM_tableName, $uid, $prependTableName = FALSE) {
568 // In case of a reverse relation
569 if ($this->MM_is_foreign) {
570 $uidLocal_field = 'uid_foreign';
571 $uidForeign_field = 'uid_local';
572 $sorting_field = 'sorting_foreign';
573 } else {
574 // default
575 $uidLocal_field = 'uid_local';
576 $uidForeign_field = 'uid_foreign';
577 $sorting_field = 'sorting';
578 }
579 // If there are tables...
580 $tableC = count($this->tableArray);
581 if ($tableC) {
582 // Boolean: does the field "tablename" need to be filled?
583 $prep = $tableC > 1 || $prependTableName || $this->MM_isMultiTableRelationship ? 1 : 0;
584 $c = 0;
585 $additionalWhere_tablenames = '';
586 if ($this->MM_is_foreign && $prep) {
587 $additionalWhere_tablenames = ' AND tablenames=' . $GLOBALS['TYPO3_DB']->fullQuoteStr($this->currentTable, $MM_tableName);
588 }
589 $additionalWhere = '';
590 // Add WHERE clause if configured
591 if ($this->MM_table_where) {
592 $additionalWhere .= LF . str_replace('###THIS_UID###', (int)$uid, $this->MM_table_where);
593 }
594 // Select, update or delete only those relations that match the configured fields
595 foreach ($this->MM_match_fields as $field => $value) {
596 $additionalWhere .= ' AND ' . $field . '=' . $GLOBALS['TYPO3_DB']->fullQuoteStr($value, $MM_tableName);
597 }
598 $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery(
599 $uidForeign_field . ($prep ? ', tablenames' : '') . ($this->MM_hasUidField ? ', uid' : ''),
600 $MM_tableName,
601 $uidLocal_field . '=' . $uid . $additionalWhere_tablenames . $additionalWhere,
602 '',
603 $sorting_field
604 );
605 $oldMMs = array();
606 // This array is similar to $oldMMs but also holds the uid of the MM-records, if any (configured by MM_hasUidField).
607 // If the UID is present it will be used to update sorting and delete MM-records.
608 // This is necessary if the "multiple" feature is used for the MM relations.
609 // $oldMMs is still needed for the in_array() search used to look if an item from $this->itemArray is in $oldMMs
610 $oldMMs_inclUid = array();
611 while ($row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res)) {
612 if (!$this->MM_is_foreign && $prep) {
613 $oldMMs[] = array($row['tablenames'], $row[$uidForeign_field]);
614 } else {
615 $oldMMs[] = $row[$uidForeign_field];
616 }
617 $oldMMs_inclUid[] = array($row['tablenames'], $row[$uidForeign_field], $row['uid']);
618 }
619 $GLOBALS['TYPO3_DB']->sql_free_result($res);
620 // For each item, insert it:
621 foreach ($this->itemArray as $val) {
622 $c++;
623 if ($prep || $val['table'] == '_NO_TABLE') {
624 // Insert current table if needed
625 if ($this->MM_is_foreign) {
626 $tablename = $this->currentTable;
627 } else {
628 $tablename = $val['table'];
629 }
630 } else {
631 $tablename = '';
632 }
633 if (!$this->MM_is_foreign && $prep) {
634 $item = array($val['table'], $val['id']);
635 } else {
636 $item = $val['id'];
637 }
638 if (in_array($item, $oldMMs)) {
639 $oldMMs_index = array_search($item, $oldMMs);
640 // In principle, selecting on the UID is all we need to do
641 // if a uid field is available since that is unique!
642 // But as long as it "doesn't hurt" we just add it to the where clause. It should all match up.
643 $whereClause = $uidLocal_field . '=' . $uid . ' AND ' . $uidForeign_field . '=' . $val['id']
644 . ($this->MM_hasUidField ? ' AND uid=' . (int)$oldMMs_inclUid[$oldMMs_index][2] : '');
645 if ($tablename) {
646 $whereClause .= ' AND tablenames=' . $GLOBALS['TYPO3_DB']->fullQuoteStr($tablename, $MM_tableName);
647 }
648 $GLOBALS['TYPO3_DB']->exec_UPDATEquery($MM_tableName, $whereClause . $additionalWhere, array($sorting_field => $c));
649 // Remove the item from the $oldMMs array so after this
650 // foreach loop only the ones that need to be deleted are in there.
651 unset($oldMMs[$oldMMs_index]);
652 // Remove the item from the $oldMMs_inclUid array so after this
653 // foreach loop only the ones that need to be deleted are in there.
654 unset($oldMMs_inclUid[$oldMMs_index]);
655 } else {
656 $insertFields = $this->MM_insert_fields;
657 $insertFields[$uidLocal_field] = $uid;
658 $insertFields[$uidForeign_field] = $val['id'];
659 $insertFields[$sorting_field] = $c;
660 if ($tablename) {
661 $insertFields['tablenames'] = $tablename;
662 $insertFields = $this->completeOppositeUsageValues($tablename, $insertFields);
663 }
664 $GLOBALS['TYPO3_DB']->exec_INSERTquery($MM_tableName, $insertFields);
665 if ($this->MM_is_foreign) {
666 $this->updateRefIndex($val['table'], $val['id']);
667 }
668 }
669 }
670 // Delete all not-used relations:
671 if (is_array($oldMMs) && count($oldMMs) > 0) {
672 $removeClauses = array();
673 $updateRefIndex_records = array();
674 foreach ($oldMMs as $oldMM_key => $mmItem) {
675 // If UID field is present, of course we need only use that for deleting.
676 if ($this->MM_hasUidField) {
677 $removeClauses[] = 'uid=' . (int)$oldMMs_inclUid[$oldMM_key][2];
678 } else {
679 if (is_array($mmItem)) {
680 $removeClauses[] = 'tablenames=' . $GLOBALS['TYPO3_DB']->fullQuoteStr($mmItem[0], $MM_tableName)
681 . ' AND ' . $uidForeign_field . '=' . $mmItem[1];
682 } else {
683 $removeClauses[] = $uidForeign_field . '=' . $mmItem;
684 }
685 }
686 if ($this->MM_is_foreign) {
687 if (is_array($mmItem)) {
688 $updateRefIndex_records[] = array($mmItem[0], $mmItem[1]);
689 } else {
690 $updateRefIndex_records[] = array($this->firstTable, $mmItem);
691 }
692 }
693 }
694 $deleteAddWhere = ' AND (' . implode(' OR ', $removeClauses) . ')';
695 $where = $uidLocal_field . '=' . (int)$uid . $deleteAddWhere . $additionalWhere_tablenames . $additionalWhere;
696 $GLOBALS['TYPO3_DB']->exec_DELETEquery($MM_tableName, $where);
697 // Update ref index:
698 foreach ($updateRefIndex_records as $pair) {
699 $this->updateRefIndex($pair[0], $pair[1]);
700 }
701 }
702 // Update ref index; In tcemain it is not certain that this will happen because
703 // if only the MM field is changed the record itself is not updated and so the ref-index is not either.
704 // This could also have been fixed in updateDB in tcemain, however I decided to do it here ...
705 $this->updateRefIndex($this->currentTable, $uid);
706 }
707 }
708
709 /**
710 * Remaps MM table elements from one local uid to another
711 * Does NOT update the reference index for you, must be called subsequently to do that!
712 *
713 * @param string $MM_tableName MM table name
714 * @param integer $uid Local, current UID
715 * @param integer $newUid Local, new UID
716 * @param boolean $prependTableName If set, then table names will always be written.
717 * @return void
718 * @todo Define visibility
719 */
720 public function remapMM($MM_tableName, $uid, $newUid, $prependTableName = FALSE) {
721 // In case of a reverse relation
722 if ($this->MM_is_foreign) {
723 $uidLocal_field = 'uid_foreign';
724 } else {
725 // default
726 $uidLocal_field = 'uid_local';
727 }
728 // If there are tables...
729 $tableC = count($this->tableArray);
730 if ($tableC) {
731 // Boolean: does the field "tablename" need to be filled?
732 $prep = $tableC > 1 || $prependTableName || $this->MM_isMultiTableRelationship;
733 $additionalWhere_tablenames = '';
734 if ($this->MM_is_foreign && $prep) {
735 $additionalWhere_tablenames = ' AND tablenames=' . $GLOBALS['TYPO3_DB']->fullQuoteStr($this->currentTable, $MM_tableName);
736 }
737 $additionalWhere = '';
738 // Add WHERE clause if configured
739 if ($this->MM_table_where) {
740 $additionalWhere .= LF . str_replace('###THIS_UID###', (int)$uid, $this->MM_table_where);
741 }
742 // Select, update or delete only those relations that match the configured fields
743 foreach ($this->MM_match_fields as $field => $value) {
744 $additionalWhere .= ' AND ' . $field . '=' . $GLOBALS['TYPO3_DB']->fullQuoteStr($value, $MM_tableName);
745 }
746 $where = $uidLocal_field . '=' . (int)$uid . $additionalWhere_tablenames . $additionalWhere;
747 $GLOBALS['TYPO3_DB']->exec_UPDATEquery($MM_tableName, $where, array($uidLocal_field => $newUid));
748 }
749 }
750
751 /**
752 * Reads items from a foreign_table, that has a foreign_field (uid of the parent record) and
753 * stores the parts in the internal array itemArray and tableArray.
754 *
755 * @param integer $uid The uid of the parent record (this value is also on the foreign_table in the foreign_field)
756 * @param array $conf TCA configuration for current field
757 * @return void
758 * @todo Define visibility
759 */
760 public function readForeignField($uid, $conf) {
761 if ($this->useLiveParentIds) {
762 $uid = $this->getLiveDefaultId($this->currentTable, $uid);
763 }
764
765 $key = 0;
766 $uid = (int)$uid;
767 $foreign_table = $conf['foreign_table'];
768 $foreign_table_field = $conf['foreign_table_field'];
769 $useDeleteClause = !$this->undeleteRecord;
770 $foreign_match_fields = is_array($conf['foreign_match_fields']) ? $conf['foreign_match_fields'] : array();
771 // Search for $uid in foreign_field, and if we have symmetric relations, do this also on symmetric_field
772 if ($conf['symmetric_field']) {
773 $whereClause = '(' . $conf['foreign_field'] . '=' . $uid . ' OR ' . $conf['symmetric_field'] . '=' . $uid . ')';
774 } else {
775 $whereClause = $conf['foreign_field'] . '=' . $uid;
776 }
777 // Use the deleteClause (e.g. "deleted=0") on this table
778 if ($useDeleteClause) {
779 $whereClause .= BackendUtility::deleteClause($foreign_table);
780 }
781 // If it's requested to look for the parent uid AND the parent table,
782 // add an additional SQL-WHERE clause
783 if ($foreign_table_field && $this->currentTable) {
784 $whereClause .= ' AND ' . $foreign_table_field . '=' . $GLOBALS['TYPO3_DB']->fullQuoteStr($this->currentTable, $foreign_table);
785 }
786 // Add additional where clause if foreign_match_fields are defined
787 foreach ($foreign_match_fields as $field => $value) {
788 $whereClause .= ' AND ' . $field . '=' . $GLOBALS['TYPO3_DB']->fullQuoteStr($value, $foreign_table);
789 }
790 // Select children from the live(!) workspace only
791 if (BackendUtility::isTableWorkspaceEnabled($foreign_table)) {
792 $workspaceList = '0,' . (int)$GLOBALS['BE_USER']->workspace;
793 $whereClause .= ' AND ' . $foreign_table . '.t3ver_wsid IN (' . $workspaceList . ') AND ' . $foreign_table . '.pid<>-1';
794 }
795 // Get the correct sorting field
796 // Specific manual sortby for data handled by this field
797 $sortby = '';
798 if ($conf['foreign_sortby']) {
799 if ($conf['symmetric_sortby'] && $conf['symmetric_field']) {
800 // Sorting depends on, from which side of the relation we're looking at it
801 $sortby = '
802 CASE
803 WHEN ' . $conf['foreign_field'] . '=' . $uid . '
804 THEN ' . $conf['foreign_sortby'] . '
805 ELSE ' . $conf['symmetric_sortby'] . '
806 END';
807 } else {
808 // Regular single-side behaviour
809 $sortby = $conf['foreign_sortby'];
810 }
811 } elseif ($conf['foreign_default_sortby']) {
812 // Specific default sortby for data handled by this field
813 $sortby = $conf['foreign_default_sortby'];
814 } elseif ($GLOBALS['TCA'][$foreign_table]['ctrl']['sortby']) {
815 // Manual sortby for all table records
816 $sortby = $GLOBALS['TCA'][$foreign_table]['ctrl']['sortby'];
817 } elseif ($GLOBALS['TCA'][$foreign_table]['ctrl']['default_sortby']) {
818 // Default sortby for all table records
819 $sortby = $GLOBALS['TCA'][$foreign_table]['ctrl']['default_sortby'];
820 }
821 // Strip a possible "ORDER BY" in front of the $sortby value
822 $sortby = $GLOBALS['TYPO3_DB']->stripOrderBy($sortby);
823 // Get the rows from storage
824 $rows = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows('uid', $foreign_table, $whereClause, '', $sortby, '', 'uid');
825 if (count($rows)) {
826 if (BackendUtility::isTableWorkspaceEnabled($foreign_table) && !$this->useLiveReferenceIds) {
827 $rows = $this->getRecordVersionsIds($foreign_table, $rows);
828 }
829 foreach ($rows as $row) {
830 $this->itemArray[$key]['id'] = $row['uid'];
831 $this->itemArray[$key]['table'] = $foreign_table;
832 $this->tableArray[$foreign_table][] = $row['uid'];
833 $key++;
834 }
835 }
836 }
837
838 /**
839 * Write the sorting values to a foreign_table, that has a foreign_field (uid of the parent record)
840 *
841 * @param array $conf TCA configuration for current field
842 * @param integer $parentUid The uid of the parent record
843 * @param integer $updateToUid If this is larger than zero it will be used as foreign UID instead of the given $parentUid (on Copy)
844 * @param boolean $skipSorting Do not update the sorting columns, this could happen for imported values
845 * @return void
846 * @todo Define visibility
847 */
848 public function writeForeignField($conf, $parentUid, $updateToUid = 0, $skipSorting = FALSE) {
849 if ($this->useLiveParentIds) {
850 $parentUid = $this->getLiveDefaultId($this->currentTable, $parentUid);
851 if (!empty($updateToUid)) {
852 $updateToUid = $this->getLiveDefaultId($this->currentTable, $updateToUid);
853 }
854 }
855
856 $c = 0;
857 $foreign_table = $conf['foreign_table'];
858 $foreign_field = $conf['foreign_field'];
859 $symmetric_field = $conf['symmetric_field'];
860 $foreign_table_field = $conf['foreign_table_field'];
861 $foreign_match_fields = is_array($conf['foreign_match_fields']) ? $conf['foreign_match_fields'] : array();
862 // If there are table items and we have a proper $parentUid
863 if (MathUtility::canBeInterpretedAsInteger($parentUid) && count($this->tableArray)) {
864 // If updateToUid is not a positive integer, set it to '0', so it will be ignored
865 if (!(MathUtility::canBeInterpretedAsInteger($updateToUid) && $updateToUid > 0)) {
866 $updateToUid = 0;
867 }
868 $considerWorkspaces = ($GLOBALS['BE_USER']->workspace !== 0 && BackendUtility::isTableWorkspaceEnabled($foreign_table));
869 $fields = 'uid,pid,' . $foreign_field;
870 // Consider the symmetric field if defined:
871 if ($symmetric_field) {
872 $fields .= ',' . $symmetric_field;
873 }
874 // Consider workspaces if defined and currently used:
875 if ($considerWorkspaces) {
876 $fields .= ',t3ver_wsid,t3ver_state,t3ver_oid';
877 }
878 // Update all items
879 foreach ($this->itemArray as $val) {
880 $uid = $val['id'];
881 $table = $val['table'];
882 $row = array();
883 // Fetch the current (not overwritten) relation record if we should handle symmetric relations
884 if ($symmetric_field || $considerWorkspaces) {
885 $row = BackendUtility::getRecord($table, $uid, $fields, '', FALSE);
886 }
887 $isOnSymmetricSide = FALSE;
888 if ($symmetric_field) {
889 $isOnSymmetricSide = self::isOnSymmetricSide($parentUid, $conf, $row);
890 }
891 $updateValues = $foreign_match_fields;
892 // No update to the uid is requested, so this is the normal behaviour
893 // just update the fields and care about sorting
894 if (!$updateToUid) {
895 // Always add the pointer to the parent uid
896 if ($isOnSymmetricSide) {
897 $updateValues[$symmetric_field] = $parentUid;
898 } else {
899 $updateValues[$foreign_field] = $parentUid;
900 }
901 // If it is configured in TCA also to store the parent table in the child record, just do it
902 if ($foreign_table_field && $this->currentTable) {
903 $updateValues[$foreign_table_field] = $this->currentTable;
904 }
905 // Update sorting columns if not to be skipped
906 if (!$skipSorting) {
907 // Get the correct sorting field
908 // Specific manual sortby for data handled by this field
909 $sortby = '';
910 if ($conf['foreign_sortby']) {
911 $sortby = $conf['foreign_sortby'];
912 } elseif ($GLOBALS['TCA'][$foreign_table]['ctrl']['sortby']) {
913 // manual sortby for all table records
914 $sortby = $GLOBALS['TCA'][$foreign_table]['ctrl']['sortby'];
915 }
916 // Apply sorting on the symmetric side
917 // (it depends on who created the relation, so what uid is in the symmetric_field):
918 if ($isOnSymmetricSide && isset($conf['symmetric_sortby']) && $conf['symmetric_sortby']) {
919 $sortby = $conf['symmetric_sortby'];
920 } else {
921 $sortby = $GLOBALS['TYPO3_DB']->stripOrderBy($sortby);
922 }
923 if ($sortby) {
924 $updateValues[$sortby] = ++$c;
925 }
926 }
927 } else {
928 if ($isOnSymmetricSide) {
929 $updateValues[$symmetric_field] = $updateToUid;
930 } else {
931 $updateValues[$foreign_field] = $updateToUid;
932 }
933 }
934 // Update accordant fields in the database:
935 if (count($updateValues)) {
936 $GLOBALS['TYPO3_DB']->exec_UPDATEquery($table, 'uid=' . (int)$uid, $updateValues);
937 $this->updateRefIndex($table, $uid);
938 }
939 // Update accordant fields in the database for workspaces overlays/placeholders:
940 if ($considerWorkspaces) {
941 // It's the specific versioned record -> update placeholder (if any)
942 if (!empty($row['t3ver_oid']) && VersionState::cast($row['t3ver_state'])->equals(VersionState::NEW_PLACEHOLDER_VERSION)) {
943 $GLOBALS['TYPO3_DB']->exec_UPDATEquery($table, 'uid=' . (int)$row['t3ver_oid'], $updateValues);
944 }
945 }
946 }
947 }
948 }
949
950 /**
951 * After initialization you can extract an array of the elements from the object. Use this function for that.
952 *
953 * @param boolean $prependTableName If set, then table names will ALWAYS be prepended (unless its a _NO_TABLE value)
954 * @return array A numeric array.
955 * @todo Define visibility
956 */
957 public function getValueArray($prependTableName = FALSE) {
958 // INIT:
959 $valueArray = array();
960 $tableC = count($this->tableArray);
961 // If there are tables in the table array:
962 if ($tableC) {
963 // If there are more than ONE table in the table array, then always prepend table names:
964 $prep = $tableC > 1 || $prependTableName;
965 // Traverse the array of items:
966 foreach ($this->itemArray as $val) {
967 $valueArray[] = ($prep && $val['table'] != '_NO_TABLE' ? $val['table'] . '_' : '') . $val['id'];
968 }
969 }
970 // Return the array
971 return $valueArray;
972 }
973
974 /**
975 * Converts id numbers from negative to positive.
976 *
977 * @param array $valueArray Array of [table]_[id] pairs.
978 * @param string $fTable Foreign table (the one used for positive numbers)
979 * @param string $nfTable Negative foreign table
980 * @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.
981 * @todo Define visibility
982 */
983 public function convertPosNeg($valueArray, $fTable, $nfTable) {
984 if (is_array($valueArray) && $fTable) {
985 foreach ($valueArray as $key => $val) {
986 $val = strrev($val);
987 $parts = explode('_', $val, 2);
988 $theID = strrev($parts[0]);
989 $theTable = strrev($parts[1]);
990 if (MathUtility::canBeInterpretedAsInteger($theID)
991 && (!$theTable || $theTable === (string)$fTable || $theTable === (string)$nfTable)
992 ) {
993 $valueArray[$key] = $theTable && $theTable !== (string)$fTable ? $theID * -1 : $theID;
994 }
995 }
996 }
997 return $valueArray;
998 }
999
1000 /**
1001 * Reads all records from internal tableArray into the internal ->results array
1002 * where keys are table names and for each table, records are stored with uids as their keys.
1003 * If $this->fetchAllFields is false you can save a little memory
1004 * since only uid,pid and a few other fields are selected.
1005 *
1006 * @return array
1007 * @todo Define visibility
1008 */
1009 public function getFromDB() {
1010 // Traverses the tables listed:
1011 foreach ($this->tableArray as $key => $val) {
1012 if (is_array($val)) {
1013 $itemList = implode(',', $val);
1014 if ($itemList) {
1015 if ($this->fetchAllFields) {
1016 $from = '*';
1017 } else {
1018 $from = 'uid,pid';
1019 if ($GLOBALS['TCA'][$key]['ctrl']['label']) {
1020 // Titel
1021 $from .= ',' . $GLOBALS['TCA'][$key]['ctrl']['label'];
1022 }
1023 if ($GLOBALS['TCA'][$key]['ctrl']['label_alt']) {
1024 // Alternative Title-Fields
1025 $from .= ',' . $GLOBALS['TCA'][$key]['ctrl']['label_alt'];
1026 }
1027 if ($GLOBALS['TCA'][$key]['ctrl']['thumbnail']) {
1028 // Thumbnail
1029 $from .= ',' . $GLOBALS['TCA'][$key]['ctrl']['thumbnail'];
1030 }
1031 }
1032 $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery($from, $key, 'uid IN (' . $itemList . ')' . $this->additionalWhere[$key]);
1033 while ($row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res)) {
1034 $this->results[$key][$row['uid']] = $row;
1035 }
1036 $GLOBALS['TYPO3_DB']->sql_free_result($res);
1037 }
1038 }
1039 }
1040 return $this->results;
1041 }
1042
1043 /**
1044 * Prepare items from itemArray to be transferred to the TCEforms interface (as a comma list)
1045 *
1046 * @return string
1047 * @todo Define visibility
1048 */
1049 public function readyForInterface() {
1050 if (!is_array($this->itemArray)) {
1051 return FALSE;
1052 }
1053 $output = array();
1054 $titleLen = (int)$GLOBALS['BE_USER']->uc['titleLen'];
1055 foreach ($this->itemArray as $val) {
1056 $theRow = $this->results[$val['table']][$val['id']];
1057 if ($theRow && is_array($GLOBALS['TCA'][$val['table']])) {
1058 $label = GeneralUtility::fixed_lgd_cs(strip_tags(
1059 BackendUtility::getRecordTitle($val['table'], $theRow)), $titleLen);
1060 $label = $label ? $label : '[...]';
1061 $output[] = str_replace(',', '', $val['table'] . '_' . $val['id'] . '|' . rawurlencode($label));
1062 }
1063 }
1064 return implode(',', $output);
1065 }
1066
1067 /**
1068 * Counts the items in $this->itemArray and puts this value in an array by default.
1069 *
1070 * @param boolean $returnAsArray Whether to put the count value in an array
1071 * @return mixed The plain count as integer or the same inside an array
1072 * @todo Define visibility
1073 */
1074 public function countItems($returnAsArray = TRUE) {
1075 $count = count($this->itemArray);
1076 if ($returnAsArray) {
1077 $count = array($count);
1078 }
1079 return $count;
1080 }
1081
1082 /**
1083 * Update Reference Index (sys_refindex) for a record
1084 * Should be called any almost any update to a record which could affect references inside the record.
1085 * (copied from TCEmain)
1086 *
1087 * @param string $table Table name
1088 * @param integer $id Record UID
1089 * @return array Information concerning modifications delivered by \TYPO3\CMS\Core\Database\ReferenceIndex::updateRefIndexTable()
1090 * @todo Define visibility
1091 */
1092 public function updateRefIndex($table, $id) {
1093 $statisticsArray = array();
1094 if ($this->updateReferenceIndex) {
1095 /** @var $refIndexObj \TYPO3\CMS\Core\Database\ReferenceIndex */
1096 $refIndexObj = GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\Database\\ReferenceIndex');
1097 if (BackendUtility::isTableWorkspaceEnabled($table)) {
1098 $refIndexObj->setWorkspaceId($GLOBALS['BE_USER']->workspace);
1099 }
1100 $statisticsArray = $refIndexObj->updateRefIndexTable($table, $id);
1101 }
1102 return $statisticsArray;
1103 }
1104
1105 /**
1106 * Checks, if we're looking from the "other" side, the symmetric side, to a symmetric relation.
1107 *
1108 * @param string $parentUid The uid of the parent record
1109 * @param array $parentConf The TCA configuration of the parent field embedding the child records
1110 * @param array $childRec The record row of the child record
1111 * @return boolean Returns TRUE if looking from the symmetric ("other") side to the relation.
1112 * @todo Define visibility
1113 */
1114 public function isOnSymmetricSide($parentUid, $parentConf, $childRec) {
1115 return MathUtility::canBeInterpretedAsInteger($childRec['uid'])
1116 && $parentConf['symmetric_field']
1117 && $parentUid == $childRec[$parentConf['symmetric_field']];
1118 }
1119
1120 /**
1121 * Completes MM values to be written by values from the opposite relation.
1122 * This method used MM insert field or MM match fields if defined.
1123 *
1124 * @param string $tableName Name of the opposite table
1125 * @param array $referenceValues Values to be written
1126 * @return array Values to be written, possibly modified
1127 */
1128 protected function completeOppositeUsageValues($tableName, array $referenceValues) {
1129 if (empty($this->MM_oppositeUsage[$tableName]) || count($this->MM_oppositeUsage[$tableName]) > 1) {
1130 return $referenceValues;
1131 }
1132
1133 $fieldName = $this->MM_oppositeUsage[$tableName][0];
1134 if (empty($GLOBALS['TCA'][$tableName]['columns'][$fieldName]['config'])) {
1135 return $referenceValues;
1136 }
1137
1138 $configuration = $GLOBALS['TCA'][$tableName]['columns'][$fieldName]['config'];
1139 if (!empty($configuration['MM_insert_fields'])) {
1140 $referenceValues = array_merge($configuration['MM_insert_fields'], $referenceValues);
1141 } elseif (!empty($configuration['MM_match_fields'])) {
1142 $referenceValues = array_merge($configuration['MM_match_fields'], $referenceValues);
1143 }
1144
1145 return $referenceValues;
1146 }
1147
1148 /**
1149 * @param string $tableName
1150 * @param array $records
1151 * @return array
1152 */
1153 protected function getRecordVersionsIds($tableName, array $records) {
1154 $workspaceId = (int)$GLOBALS['BE_USER']->workspace;
1155 $liveIds = array_map('intval', $this->extractValues($records, 'uid'));
1156 $liveIdList = implode(',', $liveIds);
1157
1158 if (BackendUtility::isTableMovePlaceholderAware($tableName)) {
1159 $versions = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows(
1160 'uid,t3ver_move_id',
1161 $tableName,
1162 't3ver_state=3 AND t3ver_wsid=' . $workspaceId . ' AND t3ver_move_id IN (' . $liveIdList . ')'
1163 );
1164
1165 if (!empty($versions)) {
1166 foreach ($versions as $version) {
1167 $liveReferenceId = $version['t3ver_move_id'];
1168 $movePlaceholderId = $version['uid'];
1169 if (isset($records[$liveReferenceId]) && $records[$movePlaceholderId]) {
1170 $records[$movePlaceholderId] = $records[$liveReferenceId];
1171 unset($records[$liveReferenceId]);
1172 }
1173 }
1174 $liveIds = array_map('intval', $this->extractValues($records, 'uid'));
1175 $records = array_combine($liveIds, array_values($records));
1176 $liveIdList = implode(',', $liveIds);
1177 }
1178 }
1179
1180 $versions = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows(
1181 'uid,t3ver_oid,t3ver_state',
1182 $tableName,
1183 'pid=-1 AND t3ver_oid IN (' . $liveIdList . ') AND t3ver_wsid=' . $workspaceId,
1184 '',
1185 't3ver_state DESC'
1186 );
1187
1188 if (!empty($versions)) {
1189 foreach ($versions as $version) {
1190 $liveId = $version['t3ver_oid'];
1191 if (isset($records[$liveId])) {
1192 $records[$liveId] = $version;
1193 }
1194 }
1195 }
1196
1197 return $records;
1198 }
1199
1200 /**
1201 * @param array $array
1202 * @param string $fieldName
1203 * @return array
1204 */
1205 protected function extractValues(array $array, $fieldName) {
1206 $values = array();
1207 foreach ($array as $item) {
1208 $values[] = $item[$fieldName];
1209 }
1210 return $values;
1211 }
1212
1213 /**
1214 * Gets the record uid of the live default record. If already
1215 * pointing to the live record, the submitted record uid is returned.
1216 *
1217 * @param string $tableName
1218 * @param int $id
1219 * @return int
1220 */
1221 protected function getLiveDefaultId($tableName, $id) {
1222 $liveDefaultId = BackendUtility::getLiveVersionIdOfRecord($tableName, $id);
1223 if ($liveDefaultId === NULL) {
1224 $liveDefaultId = $id;
1225 }
1226 return $liveDefaultId;
1227 }
1228
1229 }