Total Complexity | 254 |
Total Lines | 1661 |
Duplicated Lines | 6.56 % |
Changes | 0 |
Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.
Common duplication problems, and corresponding solutions are:
Complex classes like RelationHandler often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use RelationHandler, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
30 | class RelationHandler |
||
31 | { |
||
32 | /** |
||
33 | * $fetchAllFields if false getFromDB() fetches only uid, pid, thumbnail and label fields (as defined in TCA) |
||
34 | * |
||
35 | * @var bool |
||
36 | */ |
||
37 | protected $fetchAllFields = false; |
||
38 | |||
39 | /** |
||
40 | * If set, values that are not ids in tables are normally discarded. By this options they will be preserved. |
||
41 | * |
||
42 | * @var bool |
||
43 | */ |
||
44 | public $registerNonTableValues = false; |
||
45 | |||
46 | /** |
||
47 | * Contains the table names as keys. The values are the id-values for each table. |
||
48 | * Should ONLY contain proper table names. |
||
49 | * |
||
50 | * @var array |
||
51 | */ |
||
52 | public $tableArray = []; |
||
53 | |||
54 | /** |
||
55 | * Contains items in an numeric array (table/id for each). Tablenames here might be "_NO_TABLE" |
||
56 | * |
||
57 | * @var array |
||
58 | */ |
||
59 | public $itemArray = []; |
||
60 | |||
61 | /** |
||
62 | * Array for NON-table elements |
||
63 | * |
||
64 | * @var array |
||
65 | */ |
||
66 | public $nonTableArray = []; |
||
67 | |||
68 | /** |
||
69 | * @var array |
||
70 | */ |
||
71 | public $additionalWhere = []; |
||
72 | |||
73 | /** |
||
74 | * Deleted-column is added to additionalWhere... if this is set... |
||
75 | * |
||
76 | * @var bool |
||
77 | */ |
||
78 | public $checkIfDeleted = true; |
||
79 | |||
80 | /** |
||
81 | * @var array |
||
82 | */ |
||
83 | public $dbPaths = []; |
||
84 | |||
85 | /** |
||
86 | * Will contain the first table name in the $tablelist (for positive ids) |
||
87 | * |
||
88 | * @var string |
||
89 | */ |
||
90 | public $firstTable = ''; |
||
91 | |||
92 | /** |
||
93 | * Will contain the second table name in the $tablelist (for negative ids) |
||
94 | * |
||
95 | * @var string |
||
96 | */ |
||
97 | public $secondTable = ''; |
||
98 | |||
99 | /** |
||
100 | * If TRUE, uid_local and uid_foreign are switched, and the current table |
||
101 | * is inserted as tablename - this means you display a foreign relation "from the opposite side" |
||
102 | * |
||
103 | * @var bool |
||
104 | */ |
||
105 | public $MM_is_foreign = false; |
||
106 | |||
107 | /** |
||
108 | * Field name at the "local" side of the MM relation |
||
109 | * |
||
110 | * @var string |
||
111 | */ |
||
112 | public $MM_oppositeField = ''; |
||
113 | |||
114 | /** |
||
115 | * Only set if MM_is_foreign is set |
||
116 | * |
||
117 | * @var string |
||
118 | */ |
||
119 | public $MM_oppositeTable = ''; |
||
120 | |||
121 | /** |
||
122 | * Only set if MM_is_foreign is set |
||
123 | * |
||
124 | * @var string |
||
125 | */ |
||
126 | public $MM_oppositeFieldConf = ''; |
||
127 | |||
128 | /** |
||
129 | * Is empty by default; if MM_is_foreign is set and there is more than one table |
||
130 | * allowed (on the "local" side), then it contains the first table (as a fallback) |
||
131 | * @var string |
||
132 | */ |
||
133 | public $MM_isMultiTableRelationship = ''; |
||
134 | |||
135 | /** |
||
136 | * Current table => Only needed for reverse relations |
||
137 | * |
||
138 | * @var string |
||
139 | */ |
||
140 | public $currentTable; |
||
141 | |||
142 | /** |
||
143 | * If a record should be undeleted |
||
144 | * (so do not use the $useDeleteClause on \TYPO3\CMS\Backend\Utility\BackendUtility) |
||
145 | * |
||
146 | * @var bool |
||
147 | */ |
||
148 | public $undeleteRecord; |
||
149 | |||
150 | /** |
||
151 | * Array of fields value pairs that should match while SELECT |
||
152 | * and will be written into MM table if $MM_insert_fields is not set |
||
153 | * |
||
154 | * @var array |
||
155 | */ |
||
156 | public $MM_match_fields = []; |
||
157 | |||
158 | /** |
||
159 | * This is set to TRUE if the MM table has a UID field. |
||
160 | * |
||
161 | * @var bool |
||
162 | */ |
||
163 | public $MM_hasUidField; |
||
164 | |||
165 | /** |
||
166 | * Array of fields and value pairs used for insert in MM table |
||
167 | * |
||
168 | * @var array |
||
169 | */ |
||
170 | public $MM_insert_fields = []; |
||
171 | |||
172 | /** |
||
173 | * Extra MM table where |
||
174 | * |
||
175 | * @var string |
||
176 | */ |
||
177 | public $MM_table_where = ''; |
||
178 | |||
179 | /** |
||
180 | * Usage of a MM field on the opposite relation. |
||
181 | * |
||
182 | * @var array |
||
183 | */ |
||
184 | protected $MM_oppositeUsage; |
||
185 | |||
186 | /** |
||
187 | * @var bool |
||
188 | */ |
||
189 | protected $updateReferenceIndex = true; |
||
190 | |||
191 | /** |
||
192 | * @var bool |
||
193 | */ |
||
194 | protected $useLiveParentIds = true; |
||
195 | |||
196 | /** |
||
197 | * @var bool |
||
198 | */ |
||
199 | protected $useLiveReferenceIds = true; |
||
200 | |||
201 | /** |
||
202 | * @var int |
||
203 | */ |
||
204 | protected $workspaceId; |
||
205 | |||
206 | /** |
||
207 | * @var bool |
||
208 | */ |
||
209 | protected $purged = false; |
||
210 | |||
211 | /** |
||
212 | * This array will be filled by getFromDB(). |
||
213 | * |
||
214 | * @var array |
||
215 | */ |
||
216 | public $results = []; |
||
217 | |||
218 | /** |
||
219 | * Gets the current workspace id. |
||
220 | * |
||
221 | * @return int |
||
222 | */ |
||
223 | public function getWorkspaceId() |
||
224 | { |
||
225 | if (!isset($this->workspaceId)) { |
||
226 | $this->workspaceId = (int)$GLOBALS['BE_USER']->workspace; |
||
227 | } |
||
228 | return $this->workspaceId; |
||
229 | } |
||
230 | |||
231 | /** |
||
232 | * Sets the current workspace id. |
||
233 | * |
||
234 | * @param int $workspaceId |
||
235 | */ |
||
236 | public function setWorkspaceId($workspaceId) |
||
237 | { |
||
238 | $this->workspaceId = (int)$workspaceId; |
||
239 | } |
||
240 | |||
241 | /** |
||
242 | * Whether item array has been purged in this instance. |
||
243 | * |
||
244 | * @return bool |
||
245 | */ |
||
246 | public function isPurged() |
||
247 | { |
||
248 | return $this->purged; |
||
249 | } |
||
250 | |||
251 | /** |
||
252 | * Initialization of the class. |
||
253 | * |
||
254 | * @param string $itemlist List of group/select items |
||
255 | * @param string $tablelist Comma list of tables, first table takes priority if no table is set for an entry in the list. |
||
256 | * @param string $MMtable Name of a MM table. |
||
257 | * @param int $MMuid Local UID for MM lookup |
||
258 | * @param string $currentTable Current table name |
||
259 | * @param array $conf TCA configuration for current field |
||
260 | */ |
||
261 | public function start($itemlist, $tablelist, $MMtable = '', $MMuid = 0, $currentTable = '', $conf = []) |
||
262 | { |
||
263 | $conf = (array)$conf; |
||
264 | // SECTION: MM reverse relations |
||
265 | $this->MM_is_foreign = (bool)$conf['MM_opposite_field']; |
||
266 | $this->MM_oppositeField = $conf['MM_opposite_field']; |
||
267 | $this->MM_table_where = $conf['MM_table_where']; |
||
268 | $this->MM_hasUidField = $conf['MM_hasUidField']; |
||
269 | $this->MM_match_fields = is_array($conf['MM_match_fields']) ? $conf['MM_match_fields'] : []; |
||
270 | $this->MM_insert_fields = is_array($conf['MM_insert_fields']) ? $conf['MM_insert_fields'] : $this->MM_match_fields; |
||
271 | $this->currentTable = $currentTable; |
||
272 | View Code Duplication | if (!empty($conf['MM_oppositeUsage']) && is_array($conf['MM_oppositeUsage'])) { |
|
273 | $this->MM_oppositeUsage = $conf['MM_oppositeUsage']; |
||
274 | } |
||
275 | if ($this->MM_is_foreign) { |
||
276 | $tmp = $conf['type'] === 'group' ? $conf['allowed'] : $conf['foreign_table']; |
||
277 | // Normally, $conf['allowed'] can contain a list of tables, |
||
278 | // but as we are looking at a MM relation from the foreign side, |
||
279 | // it only makes sense to allow one one table in $conf['allowed'] |
||
280 | $tmp = GeneralUtility::trimExplode(',', $tmp); |
||
281 | $this->MM_oppositeTable = $tmp[0]; |
||
282 | unset($tmp); |
||
283 | // Only add the current table name if there is more than one allowed field |
||
284 | // We must be sure this has been done at least once before accessing the "columns" part of TCA for a table. |
||
285 | $this->MM_oppositeFieldConf = $GLOBALS['TCA'][$this->MM_oppositeTable]['columns'][$this->MM_oppositeField]['config']; |
||
286 | if ($this->MM_oppositeFieldConf['allowed']) { |
||
287 | $oppositeFieldConf_allowed = explode(',', $this->MM_oppositeFieldConf['allowed']); |
||
288 | if (count($oppositeFieldConf_allowed) > 1 || $this->MM_oppositeFieldConf['allowed'] === '*') { |
||
289 | $this->MM_isMultiTableRelationship = $oppositeFieldConf_allowed[0]; |
||
290 | } |
||
291 | } |
||
292 | } |
||
293 | // SECTION: normal MM relations |
||
294 | // If the table list is "*" then all tables are used in the list: |
||
295 | if (trim($tablelist) === '*') { |
||
296 | $tablelist = implode(',', array_keys($GLOBALS['TCA'])); |
||
297 | } |
||
298 | // The tables are traversed and internal arrays are initialized: |
||
299 | $tempTableArray = GeneralUtility::trimExplode(',', $tablelist, true); |
||
300 | foreach ($tempTableArray as $val) { |
||
301 | $tName = trim($val); |
||
302 | $this->tableArray[$tName] = []; |
||
303 | if ($this->checkIfDeleted && $GLOBALS['TCA'][$tName]['ctrl']['delete']) { |
||
304 | $fieldN = $tName . '.' . $GLOBALS['TCA'][$tName]['ctrl']['delete']; |
||
305 | $this->additionalWhere[$tName] .= ' AND ' . $fieldN . '=0'; |
||
306 | } |
||
307 | } |
||
308 | if (is_array($this->tableArray)) { |
||
309 | reset($this->tableArray); |
||
310 | } else { |
||
311 | // No tables |
||
312 | return; |
||
313 | } |
||
314 | // Set first and second tables: |
||
315 | // Is the first table |
||
316 | $this->firstTable = key($this->tableArray); |
||
317 | next($this->tableArray); |
||
318 | // If the second table is set and the ID number is less than zero (later) |
||
319 | // then the record is regarded to come from the second table... |
||
320 | $this->secondTable = key($this->tableArray); |
||
321 | // Now, populate the internal itemArray and tableArray arrays: |
||
322 | // If MM, then call this function to do that: |
||
323 | if ($MMtable) { |
||
324 | if ($MMuid) { |
||
325 | $this->readMM($MMtable, $MMuid); |
||
326 | $this->purgeItemArray(); |
||
327 | } else { |
||
328 | // Revert to readList() for new records in order to load possible default values from $itemlist |
||
329 | $this->readList($itemlist, $conf); |
||
330 | $this->purgeItemArray(); |
||
331 | } |
||
332 | } elseif ($MMuid && $conf['foreign_field']) { |
||
333 | // If not MM but foreign_field, the read the records by the foreign_field |
||
334 | $this->readForeignField($MMuid, $conf); |
||
335 | } else { |
||
336 | // If not MM, then explode the itemlist by "," and traverse the list: |
||
337 | $this->readList($itemlist, $conf); |
||
338 | // Do automatic default_sortby, if any |
||
339 | if ($conf['foreign_default_sortby']) { |
||
340 | $this->sortList($conf['foreign_default_sortby']); |
||
341 | } |
||
342 | } |
||
343 | } |
||
344 | |||
345 | /** |
||
346 | * Sets $fetchAllFields |
||
347 | * |
||
348 | * @param bool $allFields enables fetching of all fields in getFromDB() |
||
349 | */ |
||
350 | public function setFetchAllFields($allFields) |
||
351 | { |
||
352 | $this->fetchAllFields = (bool)$allFields; |
||
353 | } |
||
354 | |||
355 | /** |
||
356 | * Sets whether the reference index shall be updated. |
||
357 | * |
||
358 | * @param bool $updateReferenceIndex Whether the reference index shall be updated |
||
359 | */ |
||
360 | public function setUpdateReferenceIndex($updateReferenceIndex) |
||
361 | { |
||
362 | $this->updateReferenceIndex = (bool)$updateReferenceIndex; |
||
363 | } |
||
364 | |||
365 | /** |
||
366 | * @param bool $useLiveParentIds |
||
367 | */ |
||
368 | public function setUseLiveParentIds($useLiveParentIds) |
||
369 | { |
||
370 | $this->useLiveParentIds = (bool)$useLiveParentIds; |
||
371 | } |
||
372 | |||
373 | /** |
||
374 | * @param bool $useLiveReferenceIds |
||
375 | */ |
||
376 | public function setUseLiveReferenceIds($useLiveReferenceIds) |
||
377 | { |
||
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 | */ |
||
387 | public function readList($itemlist, array $configuration) |
||
388 | { |
||
389 | if ((string)trim($itemlist) != '') { |
||
390 | $tempItemArray = GeneralUtility::trimExplode(',', $itemlist); |
||
391 | // Changed to trimExplode 31/3 04; HMENU special type "list" didn't work |
||
392 | // if there were spaces in the list... I suppose this is better overall... |
||
393 | foreach ($tempItemArray as $key => $val) { |
||
394 | // Will be set to "1" if the entry was a real table/id: |
||
395 | $isSet = 0; |
||
396 | // Extract table name and id. This is un the formular [tablename]_[id] |
||
397 | // where table name MIGHT contain "_", hence the reversion of the string! |
||
398 | $val = strrev($val); |
||
399 | $parts = explode('_', $val, 2); |
||
400 | $theID = strrev($parts[0]); |
||
401 | // Check that the id IS an integer: |
||
402 | if (MathUtility::canBeInterpretedAsInteger($theID)) { |
||
403 | // Get the table name: If a part of the exploded string, use that. |
||
404 | // Otherwise if the id number is LESS than zero, use the second table, otherwise the first table |
||
405 | $theTable = trim($parts[1]) |
||
406 | ? strrev(trim($parts[1])) |
||
407 | : ($this->secondTable && $theID < 0 ? $this->secondTable : $this->firstTable); |
||
408 | // If the ID is not blank and the table name is among the names in the inputted tableList |
||
409 | if ( |
||
410 | (string)$theID != '' |
||
411 | // allow the default language '0' for the special languages configuration |
||
412 | && ($theID || ($configuration['special'] ?? null) === 'languages') |
||
413 | && $theTable && isset($this->tableArray[$theTable]) |
||
414 | ) { |
||
415 | // Get ID as the right value: |
||
416 | $theID = $this->secondTable ? abs((int)$theID) : (int)$theID; |
||
417 | // Register ID/table name in internal arrays: |
||
418 | $this->itemArray[$key]['id'] = $theID; |
||
419 | $this->itemArray[$key]['table'] = $theTable; |
||
420 | $this->tableArray[$theTable][] = $theID; |
||
421 | // Set update-flag: |
||
422 | $isSet = 1; |
||
423 | } |
||
424 | } |
||
425 | // If it turns out that the value from the list was NOT a valid reference to a table-record, |
||
426 | // then we might still set it as a NO_TABLE value: |
||
427 | View Code Duplication | if (!$isSet && $this->registerNonTableValues) { |
|
428 | $this->itemArray[$key]['id'] = $tempItemArray[$key]; |
||
429 | $this->itemArray[$key]['table'] = '_NO_TABLE'; |
||
430 | $this->nonTableArray[] = $tempItemArray[$key]; |
||
431 | } |
||
432 | } |
||
433 | |||
434 | // Skip if not dealing with IRRE in a CSV list on a workspace |
||
435 | if ($configuration['type'] !== 'inline' || empty($configuration['foreign_table']) || !empty($configuration['foreign_field']) |
||
436 | || !empty($configuration['MM']) || count($this->tableArray) !== 1 || empty($this->tableArray[$configuration['foreign_table']]) |
||
437 | || $this->getWorkspaceId() === 0 || !BackendUtility::isTableWorkspaceEnabled($configuration['foreign_table'])) { |
||
438 | return; |
||
439 | } |
||
440 | |||
441 | // Fetch live record data |
||
442 | if ($this->useLiveReferenceIds) { |
||
443 | foreach ($this->itemArray as &$item) { |
||
444 | $item['id'] = $this->getLiveDefaultId($item['table'], $item['id']); |
||
445 | } |
||
446 | } else { |
||
447 | // Directly overlay workspace data |
||
448 | $this->itemArray = []; |
||
449 | $foreignTable = $configuration['foreign_table']; |
||
450 | $ids = $this->getResolver($foreignTable, $this->tableArray[$foreignTable])->get(); |
||
451 | foreach ($ids as $id) { |
||
452 | $this->itemArray[] = [ |
||
453 | 'id' => $id, |
||
454 | 'table' => $foreignTable, |
||
455 | ]; |
||
456 | } |
||
457 | } |
||
458 | } |
||
459 | } |
||
460 | |||
461 | /** |
||
462 | * Does a sorting on $this->itemArray depending on a default sortby field. |
||
463 | * This is only used for automatic sorting of comma separated lists. |
||
464 | * This function is only relevant for data that is stored in comma separated lists! |
||
465 | * |
||
466 | * @param string $sortby The default_sortby field/command (e.g. 'price DESC') |
||
467 | */ |
||
468 | public function sortList($sortby) |
||
469 | { |
||
470 | // Sort directly without fetching additional 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 = []; |
||
484 | $this->tableArray = []; |
||
485 | $queryBuilder = $this->getConnectionForTableName($table) |
||
486 | ->createQueryBuilder(); |
||
487 | $queryBuilder->getRestrictions()->removeAll(); |
||
488 | $queryBuilder->select('uid') |
||
489 | ->from($table) |
||
490 | ->where( |
||
491 | $queryBuilder->expr()->in( |
||
492 | 'uid', |
||
493 | $queryBuilder->createNamedParameter( |
||
494 | GeneralUtility::intExplode(',', $uidList), |
||
495 | Connection::PARAM_INT_ARRAY |
||
496 | ) |
||
497 | ) |
||
498 | ); |
||
499 | foreach (QueryHelper::parseOrderBy((string)$sortby) as $orderPair) { |
||
500 | list($fieldName, $order) = $orderPair; |
||
501 | $queryBuilder->addOrderBy($fieldName, $order); |
||
502 | } |
||
503 | $statement = $queryBuilder->execute(); |
||
504 | while ($row = $statement->fetch()) { |
||
505 | $this->itemArray[] = ['id' => $row['uid'], 'table' => $table]; |
||
506 | $this->tableArray[$table][] = $row['uid']; |
||
507 | } |
||
508 | } |
||
509 | } |
||
510 | } |
||
511 | |||
512 | /** |
||
513 | * Reads the record tablename/id into the internal arrays itemArray and tableArray from MM records. |
||
514 | * You can call this function after start if you supply no list to start() |
||
515 | * |
||
516 | * @param string $tableName MM Tablename |
||
517 | * @param int $uid Local UID |
||
518 | */ |
||
519 | public function readMM($tableName, $uid) |
||
520 | { |
||
521 | $key = 0; |
||
522 | $theTable = null; |
||
523 | $queryBuilder = $this->getConnectionForTableName($tableName) |
||
524 | ->createQueryBuilder(); |
||
525 | $queryBuilder->getRestrictions()->removeAll(); |
||
526 | $queryBuilder->select('*')->from($tableName); |
||
527 | // In case of a reverse relation |
||
528 | if ($this->MM_is_foreign) { |
||
529 | $uidLocal_field = 'uid_foreign'; |
||
530 | $uidForeign_field = 'uid_local'; |
||
531 | $sorting_field = 'sorting_foreign'; |
||
532 | if ($this->MM_isMultiTableRelationship) { |
||
533 | // Be backwards compatible! When allowing more than one table after |
||
534 | // having previously allowed only one table, this case applies. |
||
535 | if ($this->currentTable == $this->MM_isMultiTableRelationship) { |
||
536 | $expression = $queryBuilder->expr()->orX( |
||
537 | $queryBuilder->expr()->eq( |
||
538 | 'tablenames', |
||
539 | $queryBuilder->createNamedParameter($this->currentTable, \PDO::PARAM_STR) |
||
540 | ), |
||
541 | $queryBuilder->expr()->eq( |
||
542 | 'tablenames', |
||
543 | $queryBuilder->createNamedParameter('', \PDO::PARAM_STR) |
||
544 | ) |
||
545 | ); |
||
546 | } else { |
||
547 | $expression = $queryBuilder->expr()->eq( |
||
548 | 'tablenames', |
||
549 | $queryBuilder->createNamedParameter($this->currentTable, \PDO::PARAM_STR) |
||
550 | ); |
||
551 | } |
||
552 | $queryBuilder->andWhere($expression); |
||
553 | } |
||
554 | $theTable = $this->MM_oppositeTable; |
||
555 | } else { |
||
556 | // Default |
||
557 | $uidLocal_field = 'uid_local'; |
||
558 | $uidForeign_field = 'uid_foreign'; |
||
559 | $sorting_field = 'sorting'; |
||
560 | } |
||
561 | if ($this->MM_table_where) { |
||
562 | $queryBuilder->andWhere( |
||
563 | QueryHelper::stripLogicalOperatorPrefix(str_replace('###THIS_UID###', (int)$uid, $this->MM_table_where)) |
||
564 | ); |
||
565 | } |
||
566 | View Code Duplication | foreach ($this->MM_match_fields as $field => $value) { |
|
567 | $queryBuilder->andWhere( |
||
568 | $queryBuilder->expr()->eq($field, $queryBuilder->createNamedParameter($value, \PDO::PARAM_STR)) |
||
569 | ); |
||
570 | } |
||
571 | $queryBuilder->andWhere( |
||
572 | $queryBuilder->expr()->eq( |
||
573 | $uidLocal_field, |
||
574 | $queryBuilder->createNamedParameter((int)$uid, \PDO::PARAM_INT) |
||
575 | ) |
||
576 | ); |
||
577 | $queryBuilder->orderBy($sorting_field); |
||
578 | $statement = $queryBuilder->execute(); |
||
579 | while ($row = $statement->fetch()) { |
||
580 | // Default |
||
581 | if (!$this->MM_is_foreign) { |
||
582 | // If tablesnames columns exists and contain a name, then this value is the table, else it's the firstTable... |
||
583 | $theTable = $row['tablenames'] ?: $this->firstTable; |
||
584 | } |
||
585 | if (($row[$uidForeign_field] || $theTable === 'pages') && $theTable && isset($this->tableArray[$theTable])) { |
||
586 | $this->itemArray[$key]['id'] = $row[$uidForeign_field]; |
||
587 | $this->itemArray[$key]['table'] = $theTable; |
||
588 | $this->tableArray[$theTable][] = $row[$uidForeign_field]; |
||
589 | View Code Duplication | } elseif ($this->registerNonTableValues) { |
|
590 | $this->itemArray[$key]['id'] = $row[$uidForeign_field]; |
||
591 | $this->itemArray[$key]['table'] = '_NO_TABLE'; |
||
592 | $this->nonTableArray[] = $row[$uidForeign_field]; |
||
593 | } |
||
594 | $key++; |
||
595 | } |
||
596 | } |
||
597 | |||
598 | /** |
||
599 | * Writes the internal itemArray to MM table: |
||
600 | * |
||
601 | * @param string $MM_tableName MM table name |
||
602 | * @param int $uid Local UID |
||
603 | * @param bool $prependTableName If set, then table names will always be written. |
||
604 | */ |
||
605 | public function writeMM($MM_tableName, $uid, $prependTableName = false) |
||
606 | { |
||
607 | $connection = $this->getConnectionForTableName($MM_tableName); |
||
608 | $expressionBuilder = $connection->createQueryBuilder()->expr(); |
||
609 | |||
610 | // In case of a reverse relation |
||
611 | if ($this->MM_is_foreign) { |
||
612 | $uidLocal_field = 'uid_foreign'; |
||
613 | $uidForeign_field = 'uid_local'; |
||
614 | $sorting_field = 'sorting_foreign'; |
||
615 | } else { |
||
616 | // default |
||
617 | $uidLocal_field = 'uid_local'; |
||
618 | $uidForeign_field = 'uid_foreign'; |
||
619 | $sorting_field = 'sorting'; |
||
620 | } |
||
621 | // If there are tables... |
||
622 | $tableC = count($this->tableArray); |
||
623 | if ($tableC) { |
||
624 | // Boolean: does the field "tablename" need to be filled? |
||
625 | $prep = $tableC > 1 || $prependTableName || $this->MM_isMultiTableRelationship; |
||
626 | $c = 0; |
||
627 | $additionalWhere_tablenames = ''; |
||
628 | if ($this->MM_is_foreign && $prep) { |
||
629 | $additionalWhere_tablenames = $expressionBuilder->eq( |
||
630 | 'tablenames', |
||
631 | $expressionBuilder->literal($this->currentTable) |
||
632 | ); |
||
633 | } |
||
634 | $additionalWhere = $expressionBuilder->andX(); |
||
635 | // Add WHERE clause if configured |
||
636 | if ($this->MM_table_where) { |
||
637 | $additionalWhere->add( |
||
638 | QueryHelper::stripLogicalOperatorPrefix( |
||
639 | str_replace('###THIS_UID###', (int)$uid, $this->MM_table_where) |
||
640 | ) |
||
641 | ); |
||
642 | } |
||
643 | // Select, update or delete only those relations that match the configured fields |
||
644 | foreach ($this->MM_match_fields as $field => $value) { |
||
645 | $additionalWhere->add($expressionBuilder->eq($field, $expressionBuilder->literal($value))); |
||
646 | } |
||
647 | |||
648 | $queryBuilder = $connection->createQueryBuilder(); |
||
649 | $queryBuilder->getRestrictions()->removeAll(); |
||
650 | $queryBuilder->select($uidForeign_field) |
||
651 | ->from($MM_tableName) |
||
652 | ->where($queryBuilder->expr()->eq( |
||
653 | $uidLocal_field, |
||
654 | $queryBuilder->createNamedParameter($uid, \PDO::PARAM_INT) |
||
655 | )) |
||
656 | ->orderBy($sorting_field); |
||
657 | |||
658 | if ($prep) { |
||
659 | $queryBuilder->addSelect('tablenames'); |
||
660 | } |
||
661 | if ($this->MM_hasUidField) { |
||
662 | $queryBuilder->addSelect('uid'); |
||
663 | } |
||
664 | if ($additionalWhere_tablenames) { |
||
665 | $queryBuilder->andWhere($additionalWhere_tablenames); |
||
666 | } |
||
667 | if ($additionalWhere->count()) { |
||
668 | $queryBuilder->andWhere($additionalWhere); |
||
669 | } |
||
670 | |||
671 | $result = $queryBuilder->execute(); |
||
672 | $oldMMs = []; |
||
673 | // This array is similar to $oldMMs but also holds the uid of the MM-records, if any (configured by MM_hasUidField). |
||
674 | // If the UID is present it will be used to update sorting and delete MM-records. |
||
675 | // This is necessary if the "multiple" feature is used for the MM relations. |
||
676 | // $oldMMs is still needed for the in_array() search used to look if an item from $this->itemArray is in $oldMMs |
||
677 | $oldMMs_inclUid = []; |
||
678 | while ($row = $result->fetch()) { |
||
679 | if (!$this->MM_is_foreign && $prep) { |
||
680 | $oldMMs[] = [$row['tablenames'], $row[$uidForeign_field]]; |
||
681 | } else { |
||
682 | $oldMMs[] = $row[$uidForeign_field]; |
||
683 | } |
||
684 | $oldMMs_inclUid[] = [$row['tablenames'], $row[$uidForeign_field], $row['uid']]; |
||
685 | } |
||
686 | // For each item, insert it: |
||
687 | foreach ($this->itemArray as $val) { |
||
688 | $c++; |
||
689 | if ($prep || $val['table'] === '_NO_TABLE') { |
||
690 | // Insert current table if needed |
||
691 | if ($this->MM_is_foreign) { |
||
692 | $tablename = $this->currentTable; |
||
693 | } else { |
||
694 | $tablename = $val['table']; |
||
695 | } |
||
696 | } else { |
||
697 | $tablename = ''; |
||
698 | } |
||
699 | if (!$this->MM_is_foreign && $prep) { |
||
700 | $item = [$val['table'], $val['id']]; |
||
701 | } else { |
||
702 | $item = $val['id']; |
||
703 | } |
||
704 | if (in_array($item, $oldMMs)) { |
||
705 | $oldMMs_index = array_search($item, $oldMMs); |
||
706 | // In principle, selecting on the UID is all we need to do |
||
707 | // if a uid field is available since that is unique! |
||
708 | // But as long as it "doesn't hurt" we just add it to the where clause. It should all match up. |
||
709 | $queryBuilder = $connection->createQueryBuilder(); |
||
710 | $queryBuilder->update($MM_tableName) |
||
711 | ->set($sorting_field, $c) |
||
712 | ->where( |
||
713 | $expressionBuilder->eq( |
||
714 | $uidLocal_field, |
||
715 | $queryBuilder->createNamedParameter($uid, \PDO::PARAM_INT) |
||
716 | ), |
||
717 | $expressionBuilder->eq( |
||
718 | $uidForeign_field, |
||
719 | $queryBuilder->createNamedParameter($val['id'], \PDO::PARAM_INT) |
||
720 | ) |
||
721 | ); |
||
722 | |||
723 | if ($additionalWhere->count()) { |
||
724 | $queryBuilder->andWhere($additionalWhere); |
||
725 | } |
||
726 | if ($this->MM_hasUidField) { |
||
727 | $queryBuilder->andWhere( |
||
728 | $expressionBuilder->eq( |
||
729 | 'uid', |
||
730 | $queryBuilder->createNamedParameter($oldMMs_inclUid[$oldMMs_index][2], \PDO::PARAM_INT) |
||
731 | ) |
||
732 | ); |
||
733 | } |
||
734 | if ($tablename) { |
||
735 | $queryBuilder->andWhere( |
||
736 | $expressionBuilder->eq( |
||
737 | 'tablenames', |
||
738 | $queryBuilder->createNamedParameter($tablename, \PDO::PARAM_STR) |
||
739 | ) |
||
740 | ); |
||
741 | } |
||
742 | |||
743 | $queryBuilder->execute(); |
||
744 | // Remove the item from the $oldMMs array so after this |
||
745 | // foreach loop only the ones that need to be deleted are in there. |
||
746 | unset($oldMMs[$oldMMs_index]); |
||
747 | // Remove the item from the $oldMMs_inclUid array so after this |
||
748 | // foreach loop only the ones that need to be deleted are in there. |
||
749 | unset($oldMMs_inclUid[$oldMMs_index]); |
||
750 | } else { |
||
751 | $insertFields = $this->MM_insert_fields; |
||
752 | $insertFields[$uidLocal_field] = $uid; |
||
753 | $insertFields[$uidForeign_field] = $val['id']; |
||
754 | $insertFields[$sorting_field] = $c; |
||
755 | if ($tablename) { |
||
756 | $insertFields['tablenames'] = $tablename; |
||
757 | $insertFields = $this->completeOppositeUsageValues($tablename, $insertFields); |
||
758 | } |
||
759 | $connection->insert($MM_tableName, $insertFields); |
||
760 | if ($this->MM_is_foreign) { |
||
761 | $this->updateRefIndex($val['table'], $val['id']); |
||
762 | } |
||
763 | } |
||
764 | } |
||
765 | // Delete all not-used relations: |
||
766 | if (is_array($oldMMs) && !empty($oldMMs)) { |
||
767 | $queryBuilder = $connection->createQueryBuilder(); |
||
768 | $removeClauses = $queryBuilder->expr()->orX(); |
||
769 | $updateRefIndex_records = []; |
||
770 | foreach ($oldMMs as $oldMM_key => $mmItem) { |
||
771 | // If UID field is present, of course we need only use that for deleting. |
||
772 | if ($this->MM_hasUidField) { |
||
773 | $removeClauses->add($queryBuilder->expr()->eq( |
||
774 | 'uid', |
||
775 | $queryBuilder->createNamedParameter($oldMMs_inclUid[$oldMM_key][2], \PDO::PARAM_INT) |
||
776 | )); |
||
777 | View Code Duplication | } else { |
|
778 | if (is_array($mmItem)) { |
||
779 | $removeClauses->add( |
||
780 | $queryBuilder->expr()->andX( |
||
781 | $queryBuilder->expr()->eq( |
||
782 | 'tablenames', |
||
783 | $queryBuilder->createNamedParameter($mmItem[0], \PDO::PARAM_STR) |
||
784 | ), |
||
785 | $queryBuilder->expr()->eq( |
||
786 | $uidForeign_field, |
||
787 | $queryBuilder->createNamedParameter($mmItem[1], \PDO::PARAM_INT) |
||
788 | ) |
||
789 | ) |
||
790 | ); |
||
791 | } else { |
||
792 | $removeClauses->add( |
||
793 | $queryBuilder->expr()->eq( |
||
794 | $uidForeign_field, |
||
795 | $queryBuilder->createNamedParameter($mmItem, \PDO::PARAM_INT) |
||
796 | ) |
||
797 | ); |
||
798 | } |
||
799 | } |
||
800 | if ($this->MM_is_foreign) { |
||
801 | if (is_array($mmItem)) { |
||
802 | $updateRefIndex_records[] = [$mmItem[0], $mmItem[1]]; |
||
803 | } else { |
||
804 | $updateRefIndex_records[] = [$this->firstTable, $mmItem]; |
||
805 | } |
||
806 | } |
||
807 | } |
||
808 | |||
809 | $queryBuilder->delete($MM_tableName) |
||
810 | ->where( |
||
811 | $queryBuilder->expr()->eq( |
||
812 | $uidLocal_field, |
||
813 | $queryBuilder->createNamedParameter($uid, \PDO::PARAM_INT) |
||
814 | ), |
||
815 | $removeClauses |
||
816 | ); |
||
817 | |||
818 | if ($additionalWhere_tablenames) { |
||
819 | $queryBuilder->andWhere($additionalWhere_tablenames); |
||
820 | } |
||
821 | if ($additionalWhere->count()) { |
||
822 | $queryBuilder->andWhere($additionalWhere); |
||
823 | } |
||
824 | |||
825 | $queryBuilder->execute(); |
||
826 | |||
827 | // Update ref index: |
||
828 | foreach ($updateRefIndex_records as $pair) { |
||
829 | $this->updateRefIndex($pair[0], $pair[1]); |
||
830 | } |
||
831 | } |
||
832 | // Update ref index; In DataHandler it is not certain that this will happen because |
||
833 | // if only the MM field is changed the record itself is not updated and so the ref-index is not either. |
||
834 | // This could also have been fixed in updateDB in DataHandler, however I decided to do it here ... |
||
835 | $this->updateRefIndex($this->currentTable, $uid); |
||
836 | } |
||
837 | } |
||
838 | |||
839 | /** |
||
840 | * Remaps MM table elements from one local uid to another |
||
841 | * Does NOT update the reference index for you, must be called subsequently to do that! |
||
842 | * |
||
843 | * @param string $MM_tableName MM table name |
||
844 | * @param int $uid Local, current UID |
||
845 | * @param int $newUid Local, new UID |
||
846 | * @param bool $prependTableName If set, then table names will always be written. |
||
847 | */ |
||
848 | public function remapMM($MM_tableName, $uid, $newUid, $prependTableName = false) |
||
849 | { |
||
850 | // In case of a reverse relation |
||
851 | if ($this->MM_is_foreign) { |
||
852 | $uidLocal_field = 'uid_foreign'; |
||
853 | } else { |
||
854 | // default |
||
855 | $uidLocal_field = 'uid_local'; |
||
856 | } |
||
857 | // If there are tables... |
||
858 | $tableC = count($this->tableArray); |
||
859 | if ($tableC) { |
||
860 | $queryBuilder = $this->getConnectionForTableName($MM_tableName) |
||
861 | ->createQueryBuilder(); |
||
862 | $queryBuilder->update($MM_tableName) |
||
863 | ->set($uidLocal_field, (int)$newUid) |
||
864 | ->where($queryBuilder->expr()->eq( |
||
865 | $uidLocal_field, |
||
866 | $queryBuilder->createNamedParameter($uid, \PDO::PARAM_INT) |
||
867 | )); |
||
868 | // Boolean: does the field "tablename" need to be filled? |
||
869 | $prep = $tableC > 1 || $prependTableName || $this->MM_isMultiTableRelationship; |
||
870 | if ($this->MM_is_foreign && $prep) { |
||
871 | $queryBuilder->andWhere( |
||
872 | $queryBuilder->expr()->eq( |
||
873 | 'tablenames', |
||
874 | $queryBuilder->createNamedParameter($this->currentTable, \PDO::PARAM_STR) |
||
875 | ) |
||
876 | ); |
||
877 | } |
||
878 | // Add WHERE clause if configured |
||
879 | if ($this->MM_table_where) { |
||
880 | $queryBuilder->andWhere( |
||
881 | QueryHelper::stripLogicalOperatorPrefix(str_replace('###THIS_UID###', (int)$uid, $this->MM_table_where)) |
||
882 | ); |
||
883 | } |
||
884 | // Select, update or delete only those relations that match the configured fields |
||
885 | View Code Duplication | foreach ($this->MM_match_fields as $field => $value) { |
|
886 | $queryBuilder->andWhere( |
||
887 | $queryBuilder->expr()->eq($field, $queryBuilder->createNamedParameter($value, \PDO::PARAM_STR)) |
||
888 | ); |
||
889 | } |
||
890 | $queryBuilder->execute(); |
||
891 | } |
||
892 | } |
||
893 | |||
894 | /** |
||
895 | * Reads items from a foreign_table, that has a foreign_field (uid of the parent record) and |
||
896 | * stores the parts in the internal array itemArray and tableArray. |
||
897 | * |
||
898 | * @param int $uid The uid of the parent record (this value is also on the foreign_table in the foreign_field) |
||
899 | * @param array $conf TCA configuration for current field |
||
900 | */ |
||
901 | public function readForeignField($uid, $conf) |
||
902 | { |
||
903 | if ($this->useLiveParentIds) { |
||
904 | $uid = $this->getLiveDefaultId($this->currentTable, $uid); |
||
905 | } |
||
906 | |||
907 | $key = 0; |
||
908 | $uid = (int)$uid; |
||
909 | // skip further processing if $uid does not |
||
910 | // point to a valid parent record |
||
911 | if ($uid === 0) { |
||
912 | return; |
||
913 | } |
||
914 | |||
915 | $foreign_table = $conf['foreign_table']; |
||
916 | $foreign_table_field = $conf['foreign_table_field']; |
||
917 | $useDeleteClause = !$this->undeleteRecord; |
||
918 | $foreign_match_fields = is_array($conf['foreign_match_fields']) ? $conf['foreign_match_fields'] : []; |
||
919 | $queryBuilder = $this->getConnectionForTableName($foreign_table) |
||
920 | ->createQueryBuilder(); |
||
921 | $queryBuilder->getRestrictions() |
||
922 | ->removeAll(); |
||
923 | // Use the deleteClause (e.g. "deleted=0") on this table |
||
924 | if ($useDeleteClause) { |
||
925 | $queryBuilder->getRestrictions()->add(GeneralUtility::makeInstance(DeletedRestriction::class)); |
||
926 | } |
||
927 | |||
928 | $queryBuilder->select('uid') |
||
929 | ->from($foreign_table); |
||
930 | |||
931 | // Search for $uid in foreign_field, and if we have symmetric relations, do this also on symmetric_field |
||
932 | View Code Duplication | if ($conf['symmetric_field']) { |
|
933 | $queryBuilder->where( |
||
934 | $queryBuilder->expr()->orX( |
||
935 | $queryBuilder->expr()->eq( |
||
936 | $conf['foreign_field'], |
||
937 | $queryBuilder->createNamedParameter($uid, \PDO::PARAM_INT) |
||
938 | ), |
||
939 | $queryBuilder->expr()->eq( |
||
940 | $conf['symmetric_field'], |
||
941 | $queryBuilder->createNamedParameter($uid, \PDO::PARAM_INT) |
||
942 | ) |
||
943 | ) |
||
944 | ); |
||
945 | } else { |
||
946 | $queryBuilder->where($queryBuilder->expr()->eq( |
||
947 | $conf['foreign_field'], |
||
948 | $queryBuilder->createNamedParameter($uid, \PDO::PARAM_INT) |
||
949 | )); |
||
950 | } |
||
951 | // If it's requested to look for the parent uid AND the parent table, |
||
952 | // add an additional SQL-WHERE clause |
||
953 | if ($foreign_table_field && $this->currentTable) { |
||
954 | $queryBuilder->andWhere( |
||
955 | $queryBuilder->expr()->eq( |
||
956 | $foreign_table_field, |
||
957 | $queryBuilder->createNamedParameter($this->currentTable, \PDO::PARAM_STR) |
||
958 | ) |
||
959 | ); |
||
960 | } |
||
961 | // Add additional where clause if foreign_match_fields are defined |
||
962 | foreach ($foreign_match_fields as $field => $value) { |
||
963 | $queryBuilder->andWhere( |
||
964 | $queryBuilder->expr()->eq($field, $queryBuilder->createNamedParameter($value, \PDO::PARAM_STR)) |
||
965 | ); |
||
966 | } |
||
967 | // Select children from the live(!) workspace only |
||
968 | if (BackendUtility::isTableWorkspaceEnabled($foreign_table)) { |
||
969 | $queryBuilder->andWhere( |
||
970 | $queryBuilder->expr()->in( |
||
971 | $foreign_table . '.t3ver_wsid', |
||
972 | $queryBuilder->createNamedParameter([0, (int)$this->getWorkspaceId()], Connection::PARAM_INT_ARRAY) |
||
973 | ), |
||
974 | $queryBuilder->expr()->neq( |
||
975 | $foreign_table . '.pid', |
||
976 | $queryBuilder->createNamedParameter(-1, \PDO::PARAM_INT) |
||
977 | ) |
||
978 | ); |
||
979 | } |
||
980 | // Get the correct sorting field |
||
981 | // Specific manual sortby for data handled by this field |
||
982 | $sortby = ''; |
||
983 | if ($conf['foreign_sortby']) { |
||
984 | if ($conf['symmetric_sortby'] && $conf['symmetric_field']) { |
||
985 | // Sorting depends on, from which side of the relation we're looking at it |
||
986 | // This requires bypassing automatic quoting and setting of the default sort direction |
||
987 | // @TODO: Doctrine: generalize to standard SQL to guarantee database independency |
||
988 | $queryBuilder->add( |
||
989 | 'orderBy', |
||
990 | 'CASE |
||
991 | WHEN ' . $queryBuilder->expr()->eq($conf['foreign_field'], $uid) . ' |
||
992 | THEN ' . $queryBuilder->quoteIdentifier($conf['foreign_sortby']) . ' |
||
993 | ELSE ' . $queryBuilder->quoteIdentifier($conf['symmetric_sortby']) . ' |
||
994 | END' |
||
995 | ); |
||
996 | } else { |
||
997 | // Regular single-side behaviour |
||
998 | $sortby = $conf['foreign_sortby']; |
||
999 | } |
||
1000 | } elseif ($conf['foreign_default_sortby']) { |
||
1001 | // Specific default sortby for data handled by this field |
||
1002 | $sortby = $conf['foreign_default_sortby']; |
||
1003 | View Code Duplication | } elseif ($GLOBALS['TCA'][$foreign_table]['ctrl']['sortby']) { |
|
1004 | // Manual sortby for all table records |
||
1005 | $sortby = $GLOBALS['TCA'][$foreign_table]['ctrl']['sortby']; |
||
1006 | } elseif ($GLOBALS['TCA'][$foreign_table]['ctrl']['default_sortby']) { |
||
1007 | // Default sortby for all table records |
||
1008 | $sortby = $GLOBALS['TCA'][$foreign_table]['ctrl']['default_sortby']; |
||
1009 | } |
||
1010 | |||
1011 | if (!empty($sortby)) { |
||
1012 | foreach (QueryHelper::parseOrderBy($sortby) as $orderPair) { |
||
1013 | list($fieldName, $sorting) = $orderPair; |
||
1014 | $queryBuilder->addOrderBy($fieldName, $sorting); |
||
1015 | } |
||
1016 | } |
||
1017 | |||
1018 | // Get the rows from storage |
||
1019 | $rows = []; |
||
1020 | $result = $queryBuilder->execute(); |
||
1021 | while ($row = $result->fetch()) { |
||
1022 | $rows[$row['uid']] = $row; |
||
1023 | } |
||
1024 | if (!empty($rows)) { |
||
1025 | // Retrieve the parsed and prepared ORDER BY configuration for the resolver |
||
1026 | $sortby = $queryBuilder->getQueryPart('orderBy'); |
||
1027 | $ids = $this->getResolver($foreign_table, array_keys($rows), $sortby)->get(); |
||
1028 | foreach ($ids as $id) { |
||
1029 | $this->itemArray[$key]['id'] = $id; |
||
1030 | $this->itemArray[$key]['table'] = $foreign_table; |
||
1031 | $this->tableArray[$foreign_table][] = $id; |
||
1032 | $key++; |
||
1033 | } |
||
1034 | } |
||
1035 | } |
||
1036 | |||
1037 | /** |
||
1038 | * Write the sorting values to a foreign_table, that has a foreign_field (uid of the parent record) |
||
1039 | * |
||
1040 | * @param array $conf TCA configuration for current field |
||
1041 | * @param int $parentUid The uid of the parent record |
||
1042 | * @param int $updateToUid If this is larger than zero it will be used as foreign UID instead of the given $parentUid (on Copy) |
||
1043 | * @param bool $skipSorting Do not update the sorting columns, this could happen for imported values |
||
1044 | */ |
||
1045 | public function writeForeignField($conf, $parentUid, $updateToUid = 0, $skipSorting = false) |
||
1046 | { |
||
1047 | if ($this->useLiveParentIds) { |
||
1048 | $parentUid = $this->getLiveDefaultId($this->currentTable, $parentUid); |
||
1049 | if (!empty($updateToUid)) { |
||
1050 | $updateToUid = $this->getLiveDefaultId($this->currentTable, $updateToUid); |
||
1051 | } |
||
1052 | } |
||
1053 | |||
1054 | $c = 0; |
||
1055 | $foreign_table = $conf['foreign_table']; |
||
1056 | $foreign_field = $conf['foreign_field']; |
||
1057 | $symmetric_field = $conf['symmetric_field']; |
||
1058 | $foreign_table_field = $conf['foreign_table_field']; |
||
1059 | $foreign_match_fields = is_array($conf['foreign_match_fields']) ? $conf['foreign_match_fields'] : []; |
||
1060 | // If there are table items and we have a proper $parentUid |
||
1061 | if (MathUtility::canBeInterpretedAsInteger($parentUid) && !empty($this->tableArray)) { |
||
1062 | // If updateToUid is not a positive integer, set it to '0', so it will be ignored |
||
1063 | if (!(MathUtility::canBeInterpretedAsInteger($updateToUid) && $updateToUid > 0)) { |
||
1064 | $updateToUid = 0; |
||
1065 | } |
||
1066 | $considerWorkspaces = BackendUtility::isTableWorkspaceEnabled($foreign_table); |
||
1067 | $fields = 'uid,pid,' . $foreign_field; |
||
1068 | // Consider the symmetric field if defined: |
||
1069 | if ($symmetric_field) { |
||
1070 | $fields .= ',' . $symmetric_field; |
||
1071 | } |
||
1072 | // Consider workspaces if defined and currently used: |
||
1073 | if ($considerWorkspaces) { |
||
1074 | $fields .= ',t3ver_wsid,t3ver_state,t3ver_oid'; |
||
1075 | } |
||
1076 | // Update all items |
||
1077 | foreach ($this->itemArray as $val) { |
||
1078 | $uid = $val['id']; |
||
1079 | $table = $val['table']; |
||
1080 | $row = []; |
||
1081 | // Fetch the current (not overwritten) relation record if we should handle symmetric relations |
||
1082 | if ($symmetric_field || $considerWorkspaces) { |
||
1083 | $row = BackendUtility::getRecord($table, $uid, $fields, '', true); |
||
1084 | if (empty($row)) { |
||
1085 | continue; |
||
1086 | } |
||
1087 | } |
||
1088 | $isOnSymmetricSide = false; |
||
1089 | if ($symmetric_field) { |
||
1090 | $isOnSymmetricSide = self::isOnSymmetricSide($parentUid, $conf, $row); |
||
1091 | } |
||
1092 | $updateValues = $foreign_match_fields; |
||
1093 | // No update to the uid is requested, so this is the normal behaviour |
||
1094 | // just update the fields and care about sorting |
||
1095 | if (!$updateToUid) { |
||
1096 | // Always add the pointer to the parent uid |
||
1097 | if ($isOnSymmetricSide) { |
||
1098 | $updateValues[$symmetric_field] = $parentUid; |
||
1099 | } else { |
||
1100 | $updateValues[$foreign_field] = $parentUid; |
||
1101 | } |
||
1102 | // If it is configured in TCA also to store the parent table in the child record, just do it |
||
1103 | if ($foreign_table_field && $this->currentTable) { |
||
1104 | $updateValues[$foreign_table_field] = $this->currentTable; |
||
1105 | } |
||
1106 | // Update sorting columns if not to be skipped |
||
1107 | if (!$skipSorting) { |
||
1108 | // Get the correct sorting field |
||
1109 | // Specific manual sortby for data handled by this field |
||
1110 | $sortby = ''; |
||
1111 | if ($conf['foreign_sortby']) { |
||
1112 | $sortby = $conf['foreign_sortby']; |
||
1113 | View Code Duplication | } elseif ($GLOBALS['TCA'][$foreign_table]['ctrl']['sortby']) { |
|
1114 | // manual sortby for all table records |
||
1115 | $sortby = $GLOBALS['TCA'][$foreign_table]['ctrl']['sortby']; |
||
1116 | } |
||
1117 | // Apply sorting on the symmetric side |
||
1118 | // (it depends on who created the relation, so what uid is in the symmetric_field): |
||
1119 | if ($isOnSymmetricSide && isset($conf['symmetric_sortby']) && $conf['symmetric_sortby']) { |
||
1120 | $sortby = $conf['symmetric_sortby']; |
||
1121 | } else { |
||
1122 | $tempSortBy = []; |
||
1123 | foreach (QueryHelper::parseOrderBy($sortby) as $orderPair) { |
||
1124 | list($fieldName, $order) = $orderPair; |
||
1125 | if ($order !== null) { |
||
1126 | $tempSortBy[] = implode(' ', $orderPair); |
||
1127 | } else { |
||
1128 | $tempSortBy[] = $fieldName; |
||
1129 | } |
||
1130 | } |
||
1131 | $sortby = implode(',', $tempSortBy); |
||
1132 | } |
||
1133 | if ($sortby) { |
||
1134 | $updateValues[$sortby] = ++$c; |
||
1135 | } |
||
1136 | } |
||
1137 | } else { |
||
1138 | if ($isOnSymmetricSide) { |
||
1139 | $updateValues[$symmetric_field] = $updateToUid; |
||
1140 | } else { |
||
1141 | $updateValues[$foreign_field] = $updateToUid; |
||
1142 | } |
||
1143 | } |
||
1144 | // Update accordant fields in the database: |
||
1145 | if (!empty($updateValues)) { |
||
1146 | // Update tstamp if any foreign field value has changed |
||
1147 | if (!empty($GLOBALS['TCA'][$table]['ctrl']['tstamp'])) { |
||
1148 | $updateValues[$GLOBALS['TCA'][$table]['ctrl']['tstamp']] = $GLOBALS['EXEC_TIME']; |
||
1149 | } |
||
1150 | $this->getConnectionForTableName($table) |
||
1151 | ->update( |
||
1152 | $table, |
||
1153 | $updateValues, |
||
1154 | ['uid' => (int)$uid] |
||
1155 | ); |
||
1156 | $this->updateRefIndex($table, $uid); |
||
1157 | } |
||
1158 | // Update accordant fields in the database for workspaces overlays/placeholders: |
||
1159 | if ($considerWorkspaces) { |
||
1160 | // It's the specific versioned record -> update placeholder (if any) |
||
1161 | if (!empty($row['t3ver_oid']) && VersionState::cast($row['t3ver_state'])->equals(VersionState::NEW_PLACEHOLDER_VERSION)) { |
||
1162 | $this->getConnectionForTableName($table) |
||
1163 | ->update( |
||
1164 | $table, |
||
1165 | $updateValues, |
||
1166 | ['uid' => (int)$row['t3ver_oid']] |
||
1167 | ); |
||
1168 | } |
||
1169 | } |
||
1170 | } |
||
1171 | } |
||
1172 | } |
||
1173 | |||
1174 | /** |
||
1175 | * After initialization you can extract an array of the elements from the object. Use this function for that. |
||
1176 | * |
||
1177 | * @param bool $prependTableName If set, then table names will ALWAYS be prepended (unless its a _NO_TABLE value) |
||
1178 | * @return array A numeric array. |
||
1179 | */ |
||
1180 | public function getValueArray($prependTableName = false) |
||
1181 | { |
||
1182 | // INIT: |
||
1183 | $valueArray = []; |
||
1184 | $tableC = count($this->tableArray); |
||
1185 | // If there are tables in the table array: |
||
1186 | if ($tableC) { |
||
1187 | // If there are more than ONE table in the table array, then always prepend table names: |
||
1188 | $prep = $tableC > 1 || $prependTableName; |
||
1189 | // Traverse the array of items: |
||
1190 | foreach ($this->itemArray as $val) { |
||
1191 | $valueArray[] = ($prep && $val['table'] !== '_NO_TABLE' ? $val['table'] . '_' : '') . $val['id']; |
||
1192 | } |
||
1193 | } |
||
1194 | // Return the array |
||
1195 | return $valueArray; |
||
1196 | } |
||
1197 | |||
1198 | /** |
||
1199 | * Reads all records from internal tableArray into the internal ->results array |
||
1200 | * where keys are table names and for each table, records are stored with uids as their keys. |
||
1201 | * If $this->fetchAllFields is false you can save a little memory |
||
1202 | * since only uid,pid and a few other fields are selected. |
||
1203 | * |
||
1204 | * @return array |
||
1205 | */ |
||
1206 | public function getFromDB() |
||
1207 | { |
||
1208 | // Traverses the tables listed: |
||
1209 | foreach ($this->tableArray as $table => $val) { |
||
1210 | if (is_array($val)) { |
||
1211 | $itemList = implode(',', $val); |
||
1212 | if ($itemList) { |
||
1213 | if ($this->fetchAllFields) { |
||
1214 | $fields = '*'; |
||
1215 | } else { |
||
1216 | $fields = 'uid,pid'; |
||
1217 | View Code Duplication | if ($GLOBALS['TCA'][$table]['ctrl']['label']) { |
|
1218 | // Titel |
||
1219 | $fields .= ',' . $GLOBALS['TCA'][$table]['ctrl']['label']; |
||
1220 | } |
||
1221 | View Code Duplication | if ($GLOBALS['TCA'][$table]['ctrl']['label_alt']) { |
|
1222 | // Alternative Title-Fields |
||
1223 | $fields .= ',' . $GLOBALS['TCA'][$table]['ctrl']['label_alt']; |
||
1224 | } |
||
1225 | View Code Duplication | if ($GLOBALS['TCA'][$table]['ctrl']['thumbnail']) { |
|
1226 | // Thumbnail |
||
1227 | $fields .= ',' . $GLOBALS['TCA'][$table]['ctrl']['thumbnail']; |
||
1228 | } |
||
1229 | } |
||
1230 | $queryBuilder = $this->getConnectionForTableName($table) |
||
1231 | ->createQueryBuilder(); |
||
1232 | $queryBuilder->getRestrictions()->removeAll(); |
||
1233 | $queryBuilder->select(...(GeneralUtility::trimExplode(',', $fields, true))) |
||
1234 | ->from($table) |
||
1235 | ->where($queryBuilder->expr()->in( |
||
1236 | 'uid', |
||
1237 | $queryBuilder->createNamedParameter( |
||
1238 | GeneralUtility::intExplode(',', $itemList), |
||
1239 | Connection::PARAM_INT_ARRAY |
||
1240 | ) |
||
1241 | )); |
||
1242 | if ($this->additionalWhere[$table]) { |
||
1243 | $queryBuilder->andWhere(QueryHelper::stripLogicalOperatorPrefix($this->additionalWhere[$table])); |
||
1244 | } |
||
1245 | $statement = $queryBuilder->execute(); |
||
1246 | while ($row = $statement->fetch()) { |
||
1247 | $this->results[$table][$row['uid']] = $row; |
||
1248 | } |
||
1249 | } |
||
1250 | } |
||
1251 | } |
||
1252 | return $this->results; |
||
1253 | } |
||
1254 | |||
1255 | /** |
||
1256 | * This method is typically called after getFromDB(). |
||
1257 | * $this->results holds a list of resolved and valid relations, |
||
1258 | * $this->itemArray hold a list of "selected" relations from the incoming selection array. |
||
1259 | * The difference is that "itemArray" may hold a single table/uid combination multiple times, |
||
1260 | * for instance in a type=group relation having multiple=true, while "results" hold each |
||
1261 | * resolved relation only once. |
||
1262 | * The methods creates a sanitized "itemArray" from resolved "results" list, normalized |
||
1263 | * the return array to always contain both table name and uid, and keep incoming |
||
1264 | * "itemArray" sort order and keeps "multiple" selections. |
||
1265 | * |
||
1266 | * @return array |
||
1267 | */ |
||
1268 | public function getResolvedItemArray(): array |
||
1269 | { |
||
1270 | $itemArray = []; |
||
1271 | foreach ($this->itemArray as $item) { |
||
1272 | if (isset($this->results[$item['table']][$item['id']])) { |
||
1273 | $itemArray[] = [ |
||
1274 | 'table' => $item['table'], |
||
1275 | 'uid' => $item['id'], |
||
1276 | ]; |
||
1277 | } |
||
1278 | } |
||
1279 | return $itemArray; |
||
1280 | } |
||
1281 | |||
1282 | /** |
||
1283 | * Counts the items in $this->itemArray and puts this value in an array by default. |
||
1284 | * |
||
1285 | * @param bool $returnAsArray Whether to put the count value in an array |
||
1286 | * @return mixed The plain count as integer or the same inside an array |
||
1287 | */ |
||
1288 | public function countItems($returnAsArray = true) |
||
1289 | { |
||
1290 | $count = count($this->itemArray); |
||
1291 | if ($returnAsArray) { |
||
1292 | $count = [$count]; |
||
1293 | } |
||
1294 | return $count; |
||
1295 | } |
||
1296 | |||
1297 | /** |
||
1298 | * Update Reference Index (sys_refindex) for a record |
||
1299 | * Should be called any almost any update to a record which could affect references inside the record. |
||
1300 | * (copied from DataHandler) |
||
1301 | * |
||
1302 | * @param string $table Table name |
||
1303 | * @param int $id Record UID |
||
1304 | * @return array Information concerning modifications delivered by \TYPO3\CMS\Core\Database\ReferenceIndex::updateRefIndexTable() |
||
1305 | */ |
||
1306 | public function updateRefIndex($table, $id) |
||
1319 | } |
||
1320 | |||
1321 | /** |
||
1322 | * Converts elements in the local item array to use version ids instead of |
||
1323 | * live ids, if possible. The most common use case is, to call that prior |
||
1324 | * to processing with MM relations in a workspace context. For tha special |
||
1325 | * case, ids on both side of the MM relation must use version ids if |
||
1326 | * available. |
||
1327 | * |
||
1328 | * @return bool Whether items have been converted |
||
1329 | */ |
||
1330 | public function convertItemArray() |
||
1331 | { |
||
1332 | $hasBeenConverted = false; |
||
1333 | |||
1334 | // conversion is only required in a workspace context |
||
1335 | // (the case that version ids are submitted in a live context are rare) |
||
1336 | if ($this->getWorkspaceId() === 0) { |
||
1337 | return $hasBeenConverted; |
||
1338 | } |
||
1339 | |||
1340 | foreach ($this->tableArray as $tableName => $ids) { |
||
1341 | if (empty($ids) || !BackendUtility::isTableWorkspaceEnabled($tableName)) { |
||
1342 | continue; |
||
1343 | } |
||
1344 | |||
1345 | // convert live ids to version ids if available |
||
1346 | $convertedIds = $this->getResolver($tableName, $ids) |
||
1347 | ->setKeepDeletePlaceholder(false) |
||
1348 | ->setKeepMovePlaceholder(false) |
||
1349 | ->processVersionOverlays($ids); |
||
1350 | foreach ($this->itemArray as $index => $item) { |
||
1351 | if ($item['table'] !== $tableName) { |
||
1352 | continue; |
||
1353 | } |
||
1354 | $currentItemId = $item['id']; |
||
1355 | if ( |
||
1356 | !isset($convertedIds[$currentItemId]) |
||
1357 | || $currentItemId === $convertedIds[$currentItemId] |
||
1358 | ) { |
||
1359 | continue; |
||
1360 | } |
||
1361 | // adjust local item to use resolved version id |
||
1362 | $this->itemArray[$index]['id'] = $convertedIds[$currentItemId]; |
||
1363 | $hasBeenConverted = true; |
||
1364 | } |
||
1365 | // update per-table reference for ids |
||
1366 | if ($hasBeenConverted) { |
||
1367 | $this->tableArray[$tableName] = array_values($convertedIds); |
||
1368 | } |
||
1369 | } |
||
1370 | |||
1371 | return $hasBeenConverted; |
||
1372 | } |
||
1373 | |||
1374 | /** |
||
1375 | * @param int|null $workspaceId |
||
1376 | * @return bool Whether items have been purged |
||
1377 | */ |
||
1378 | public function purgeItemArray($workspaceId = null) |
||
1379 | { |
||
1380 | if ($workspaceId === null) { |
||
1381 | $workspaceId = $this->getWorkspaceId(); |
||
1382 | } else { |
||
1383 | $workspaceId = (int)$workspaceId; |
||
1384 | } |
||
1385 | |||
1386 | // Ensure, only live relations are in the items Array |
||
1387 | if ($workspaceId === 0) { |
||
1388 | $purgeCallback = 'purgeVersionedIds'; |
||
1389 | } else { |
||
1390 | // Otherwise, ensure that live relations are purged if version exists |
||
1391 | $purgeCallback = 'purgeLiveVersionedIds'; |
||
1392 | } |
||
1393 | |||
1394 | $itemArrayHasBeenPurged = $this->purgeItemArrayHandler($purgeCallback); |
||
1395 | $this->purged = ($this->purged || $itemArrayHasBeenPurged); |
||
1396 | return $itemArrayHasBeenPurged; |
||
1397 | } |
||
1398 | |||
1399 | /** |
||
1400 | * Removes items having a delete placeholder from $this->itemArray |
||
1401 | * |
||
1402 | * @return bool Whether items have been purged |
||
1403 | */ |
||
1404 | public function processDeletePlaceholder() |
||
1405 | { |
||
1406 | if (!$this->useLiveReferenceIds || $this->getWorkspaceId() === 0) { |
||
1407 | return false; |
||
1408 | } |
||
1409 | |||
1410 | return $this->purgeItemArrayHandler('purgeDeletePlaceholder'); |
||
1411 | } |
||
1412 | |||
1413 | /** |
||
1414 | * Handles a purge callback on $this->itemArray |
||
1415 | * |
||
1416 | * @param callable $purgeCallback |
||
1417 | * @return bool Whether items have been purged |
||
1418 | */ |
||
1419 | protected function purgeItemArrayHandler($purgeCallback) |
||
1420 | { |
||
1421 | $itemArrayHasBeenPurged = false; |
||
1422 | |||
1423 | foreach ($this->tableArray as $itemTableName => $itemIds) { |
||
1424 | if (empty($itemIds) || !BackendUtility::isTableWorkspaceEnabled($itemTableName)) { |
||
1425 | continue; |
||
1426 | } |
||
1427 | |||
1428 | $purgedItemIds = call_user_func([$this, $purgeCallback], $itemTableName, $itemIds); |
||
1429 | $removedItemIds = array_diff($itemIds, $purgedItemIds); |
||
1430 | foreach ($removedItemIds as $removedItemId) { |
||
1431 | $this->removeFromItemArray($itemTableName, $removedItemId); |
||
1432 | } |
||
1433 | $this->tableArray[$itemTableName] = $purgedItemIds; |
||
1434 | if (!empty($removedItemIds)) { |
||
1435 | $itemArrayHasBeenPurged = true; |
||
1436 | } |
||
1437 | } |
||
1438 | |||
1439 | return $itemArrayHasBeenPurged; |
||
1440 | } |
||
1441 | |||
1442 | /** |
||
1443 | * Purges ids that are versioned. |
||
1444 | * |
||
1445 | * @param string $tableName |
||
1446 | * @param array $ids |
||
1447 | * @return array |
||
1448 | */ |
||
1449 | protected function purgeVersionedIds($tableName, array $ids) |
||
1450 | { |
||
1451 | $ids = array_combine($ids, $ids); |
||
1452 | |||
1453 | $queryBuilder = $this->getConnectionForTableName($tableName) |
||
1454 | ->createQueryBuilder(); |
||
1455 | $queryBuilder->getRestrictions()->removeAll(); |
||
1456 | $versions = $queryBuilder->select('uid', 't3ver_oid', 't3ver_state') |
||
1457 | ->from($tableName) |
||
1458 | ->where( |
||
1459 | $queryBuilder->expr()->eq( |
||
1460 | 'pid', |
||
1461 | $queryBuilder->createNamedParameter(-1, \PDO::PARAM_INT) |
||
1462 | ), |
||
1463 | $queryBuilder->expr()->in( |
||
1464 | 't3ver_oid', |
||
1465 | $queryBuilder->createNamedParameter($ids, Connection::PARAM_INT_ARRAY) |
||
1466 | ), |
||
1467 | $queryBuilder->expr()->neq( |
||
1468 | 't3ver_wsid', |
||
1469 | $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT) |
||
1470 | ) |
||
1471 | ) |
||
1472 | ->orderBy('t3ver_state', 'DESC') |
||
1473 | ->execute() |
||
1474 | ->fetchAll(); |
||
1475 | |||
1476 | View Code Duplication | if (!empty($versions)) { |
|
1477 | foreach ($versions as $version) { |
||
1478 | $versionId = $version['uid']; |
||
1479 | if (isset($ids[$versionId])) { |
||
1480 | unset($ids[$versionId]); |
||
1481 | } |
||
1482 | } |
||
1483 | } |
||
1484 | |||
1485 | return array_values($ids); |
||
1486 | } |
||
1487 | |||
1488 | /** |
||
1489 | * Purges ids that are live but have an accordant version. |
||
1490 | * |
||
1491 | * @param string $tableName |
||
1492 | * @param array $ids |
||
1493 | * @return array |
||
1494 | */ |
||
1495 | protected function purgeLiveVersionedIds($tableName, array $ids) |
||
1496 | { |
||
1497 | $ids = array_combine($ids, $ids); |
||
1498 | |||
1499 | $queryBuilder = $this->getConnectionForTableName($tableName) |
||
1500 | ->createQueryBuilder(); |
||
1501 | $queryBuilder->getRestrictions()->removeAll(); |
||
1502 | $versions = $queryBuilder->select('uid', 't3ver_oid', 't3ver_state') |
||
1503 | ->from($tableName) |
||
1504 | ->where( |
||
1505 | $queryBuilder->expr()->eq( |
||
1506 | 'pid', |
||
1507 | $queryBuilder->createNamedParameter(-1, \PDO::PARAM_INT) |
||
1508 | ), |
||
1509 | $queryBuilder->expr()->in( |
||
1510 | 't3ver_oid', |
||
1511 | $queryBuilder->createNamedParameter($ids, Connection::PARAM_INT_ARRAY) |
||
1512 | ), |
||
1513 | $queryBuilder->expr()->neq( |
||
1514 | 't3ver_wsid', |
||
1515 | $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT) |
||
1516 | ) |
||
1517 | ) |
||
1518 | ->orderBy('t3ver_state', 'DESC') |
||
1519 | ->execute() |
||
1520 | ->fetchAll(); |
||
1521 | |||
1522 | if (!empty($versions)) { |
||
1523 | foreach ($versions as $version) { |
||
1524 | $versionId = $version['uid']; |
||
1525 | $liveId = $version['t3ver_oid']; |
||
1526 | if (isset($ids[$liveId]) && isset($ids[$versionId])) { |
||
1527 | unset($ids[$liveId]); |
||
1528 | } |
||
1529 | } |
||
1530 | } |
||
1531 | |||
1532 | return array_values($ids); |
||
1533 | } |
||
1534 | |||
1535 | /** |
||
1536 | * Purges ids that have a delete placeholder |
||
1537 | * |
||
1538 | * @param string $tableName |
||
1539 | * @param array $ids |
||
1540 | * @return array |
||
1541 | */ |
||
1542 | protected function purgeDeletePlaceholder($tableName, array $ids) |
||
1543 | { |
||
1544 | $ids = array_combine($ids, $ids); |
||
1545 | |||
1546 | $queryBuilder = $this->getConnectionForTableName($tableName) |
||
1547 | ->createQueryBuilder(); |
||
1548 | $queryBuilder->getRestrictions()->removeAll(); |
||
1549 | $versions = $queryBuilder->select('uid', 't3ver_oid', 't3ver_state') |
||
1550 | ->from($tableName) |
||
1551 | ->where( |
||
1552 | $queryBuilder->expr()->eq( |
||
1553 | 'pid', |
||
1554 | $queryBuilder->createNamedParameter(-1, \PDO::PARAM_INT) |
||
1555 | ), |
||
1556 | $queryBuilder->expr()->in( |
||
1557 | 't3ver_oid', |
||
1558 | $queryBuilder->createNamedParameter($ids, Connection::PARAM_INT_ARRAY) |
||
1559 | ), |
||
1560 | $queryBuilder->expr()->neq( |
||
1561 | 't3ver_wsid', |
||
1562 | $queryBuilder->createNamedParameter( |
||
1563 | $this->getWorkspaceId(), |
||
1564 | \PDO::PARAM_INT |
||
1565 | ) |
||
1566 | ), |
||
1567 | $queryBuilder->expr()->eq( |
||
1568 | 't3ver_state', |
||
1569 | $queryBuilder->createNamedParameter( |
||
1570 | (string)VersionState::cast(VersionState::DELETE_PLACEHOLDER), |
||
1571 | \PDO::PARAM_INT |
||
1572 | ) |
||
1573 | ) |
||
1574 | ) |
||
1575 | ->execute() |
||
1576 | ->fetchAll(); |
||
1577 | |||
1578 | View Code Duplication | if (!empty($versions)) { |
|
1579 | foreach ($versions as $version) { |
||
1580 | $liveId = $version['t3ver_oid']; |
||
1581 | if (isset($ids[$liveId])) { |
||
1582 | unset($ids[$liveId]); |
||
1583 | } |
||
1584 | } |
||
1585 | } |
||
1586 | |||
1587 | return array_values($ids); |
||
1588 | } |
||
1589 | |||
1590 | protected function removeFromItemArray($tableName, $id) |
||
1591 | { |
||
1592 | foreach ($this->itemArray as $index => $item) { |
||
1593 | if ($item['table'] === $tableName && (string)$item['id'] === (string)$id) { |
||
1594 | unset($this->itemArray[$index]); |
||
1595 | return true; |
||
1596 | } |
||
1597 | } |
||
1598 | return false; |
||
1599 | } |
||
1600 | |||
1601 | /** |
||
1602 | * Checks, if we're looking from the "other" side, the symmetric side, to a symmetric relation. |
||
1603 | * |
||
1604 | * @param string $parentUid The uid of the parent record |
||
1605 | * @param array $parentConf The TCA configuration of the parent field embedding the child records |
||
1606 | * @param array $childRec The record row of the child record |
||
1607 | * @return bool Returns TRUE if looking from the symmetric ("other") side to the relation. |
||
1608 | */ |
||
1609 | public static function isOnSymmetricSide($parentUid, $parentConf, $childRec) |
||
1610 | { |
||
1611 | return MathUtility::canBeInterpretedAsInteger($childRec['uid']) |
||
1612 | && $parentConf['symmetric_field'] |
||
1613 | && $parentUid == $childRec[$parentConf['symmetric_field']]; |
||
1614 | } |
||
1615 | |||
1616 | /** |
||
1617 | * Completes MM values to be written by values from the opposite relation. |
||
1618 | * This method used MM insert field or MM match fields if defined. |
||
1619 | * |
||
1620 | * @param string $tableName Name of the opposite table |
||
1621 | * @param array $referenceValues Values to be written |
||
1622 | * @return array Values to be written, possibly modified |
||
1623 | */ |
||
1624 | protected function completeOppositeUsageValues($tableName, array $referenceValues) |
||
1625 | { |
||
1626 | if (empty($this->MM_oppositeUsage[$tableName]) || count($this->MM_oppositeUsage[$tableName]) > 1) { |
||
1627 | return $referenceValues; |
||
1628 | } |
||
1629 | |||
1630 | $fieldName = $this->MM_oppositeUsage[$tableName][0]; |
||
1631 | if (empty($GLOBALS['TCA'][$tableName]['columns'][$fieldName]['config'])) { |
||
1632 | return $referenceValues; |
||
1633 | } |
||
1634 | |||
1635 | $configuration = $GLOBALS['TCA'][$tableName]['columns'][$fieldName]['config']; |
||
1636 | if (!empty($configuration['MM_insert_fields'])) { |
||
1637 | $referenceValues = array_merge($configuration['MM_insert_fields'], $referenceValues); |
||
1638 | } elseif (!empty($configuration['MM_match_fields'])) { |
||
1639 | $referenceValues = array_merge($configuration['MM_match_fields'], $referenceValues); |
||
1640 | } |
||
1641 | |||
1642 | return $referenceValues; |
||
1643 | } |
||
1644 | |||
1645 | /** |
||
1646 | * Gets the record uid of the live default record. If already |
||
1647 | * pointing to the live record, the submitted record uid is returned. |
||
1648 | * |
||
1649 | * @param string $tableName |
||
1650 | * @param int $id |
||
1651 | * @return int |
||
1652 | */ |
||
1653 | View Code Duplication | protected function getLiveDefaultId($tableName, $id) |
|
1660 | } |
||
1661 | |||
1662 | /** |
||
1663 | * @param string $tableName |
||
1664 | * @param int[] $ids |
||
1665 | * @param array $sortingStatement |
||
1666 | * @return PlainDataResolver |
||
1667 | */ |
||
1668 | protected function getResolver($tableName, array $ids, array $sortingStatement = null) |
||
1681 | } |
||
1682 | |||
1683 | /** |
||
1684 | * @param string $tableName |
||
1685 | * @return Connection |
||
1686 | */ |
||
1687 | protected function getConnectionForTableName(string $tableName) |
||
1688 | { |
||
1689 | return GeneralUtility::makeInstance(ConnectionPool::class) |
||
1690 | ->getConnectionForTable($tableName); |
||
1691 | } |
||
1692 | } |
||
1693 |