‪TYPO3CMS  9.5
RelationHandler.php
Go to the documentation of this file.
1 <?php
3 
4 /*
5  * This file is part of the TYPO3 CMS project.
6  *
7  * It is free software; you can redistribute it and/or modify it under
8  * the terms of the GNU General Public License, either version 2
9  * of the License, or any later version.
10  *
11  * For the full copyright and license information, please read the
12  * LICENSE.txt file that was distributed with this source code.
13  *
14  * The TYPO3 project - inspiring people to share!
15  */
16 
25 
32 {
38  protected ‪$fetchAllFields = false;
39 
45  public ‪$registerNonTableValues = false;
46 
53  public ‪$tableArray = [];
54 
60  public ‪$itemArray = [];
61 
67  public ‪$nonTableArray = [];
68 
72  public ‪$additionalWhere = [];
73 
79  public ‪$checkIfDeleted = true;
80 
84  public ‪$dbPaths = [];
85 
91  public ‪$firstTable = '';
92 
98  public ‪$secondTable = '';
99 
106  public ‪$MM_is_foreign = false;
107 
113  public ‪$MM_oppositeField = '';
114 
120  public ‪$MM_oppositeTable = '';
121 
127  public ‪$MM_oppositeFieldConf = '';
128 
135 
141  public ‪$currentTable;
142 
149  public ‪$undeleteRecord;
150 
157  public ‪$MM_match_fields = [];
158 
164  public ‪$MM_hasUidField;
165 
171  public ‪$MM_insert_fields = [];
172 
178  public ‪$MM_table_where = '';
179 
185  protected ‪$MM_oppositeUsage;
186 
190  protected ‪$updateReferenceIndex = true;
191 
195  protected ‪$useLiveParentIds = true;
196 
200  protected ‪$useLiveReferenceIds = true;
201 
205  protected ‪$workspaceId;
206 
210  protected ‪$purged = false;
211 
217  public ‪$results = [];
218 
224  public function ‪getWorkspaceId()
225  {
226  if (!isset($this->workspaceId)) {
227  $this->workspaceId = (int)‪$GLOBALS['BE_USER']->workspace;
228  }
229  return ‪$this->workspaceId;
230  }
231 
237  public function ‪setWorkspaceId(‪$workspaceId)
238  {
239  $this->workspaceId = (int)‪$workspaceId;
240  }
241 
247  public function ‪isPurged()
248  {
249  return ‪$this->purged;
250  }
251 
262  public function ‪start($itemlist, $tablelist, $MMtable = '', $MMuid = 0, ‪$currentTable = '', $conf = [])
263  {
264  $conf = (array)$conf;
265  // SECTION: MM reverse relations
266  $this->MM_is_foreign = (bool)($conf['MM_opposite_field'] ?? false);
267  $this->MM_oppositeField = $conf['MM_opposite_field'] ?? null;
268  $this->MM_table_where = $conf['MM_table_where'] ?? null;
269  $this->MM_hasUidField = $conf['MM_hasUidField'] ?? null;
270  $this->MM_match_fields = (isset($conf['MM_match_fields']) && is_array($conf['MM_match_fields'])) ? $conf['MM_match_fields'] : [];
271  $this->MM_insert_fields = (isset($conf['MM_insert_fields']) && is_array($conf['MM_insert_fields'])) ? $conf['MM_insert_fields'] : $this->MM_match_fields;
272  $this->currentTable = ‪$currentTable;
273  if (!empty($conf['MM_oppositeUsage']) && is_array($conf['MM_oppositeUsage'])) {
274  $this->MM_oppositeUsage = $conf['MM_oppositeUsage'];
275  }
276  if ($this->MM_is_foreign) {
277  $tmp = $conf['type'] === 'group' ? $conf['allowed'] : $conf['foreign_table'];
278  // Normally, $conf['allowed'] can contain a list of tables,
279  // but as we are looking at a MM relation from the foreign side,
280  // it only makes sense to allow one one table in $conf['allowed']
281  $tmp = GeneralUtility::trimExplode(',', $tmp);
282  $this->MM_oppositeTable = $tmp[0];
283  unset($tmp);
284  // Only add the current table name if there is more than one allowed field
285  // We must be sure this has been done at least once before accessing the "columns" part of TCA for a table.
286  $this->MM_oppositeFieldConf = ‪$GLOBALS['TCA'][‪$this->MM_oppositeTable]['columns'][‪$this->MM_oppositeField]['config'];
287  if ($this->MM_oppositeFieldConf['allowed']) {
288  $oppositeFieldConf_allowed = explode(',', $this->MM_oppositeFieldConf['allowed']);
289  if (count($oppositeFieldConf_allowed) > 1 || $this->MM_oppositeFieldConf['allowed'] === '*') {
290  $this->MM_isMultiTableRelationship = $oppositeFieldConf_allowed[0];
291  }
292  }
293  }
294  // SECTION: normal MM relations
295  // If the table list is "*" then all tables are used in the list:
296  if (trim($tablelist) === '*') {
297  $tablelist = implode(',', array_keys(‪$GLOBALS['TCA']));
298  }
299  // The tables are traversed and internal arrays are initialized:
300  $tempTableArray = GeneralUtility::trimExplode(',', $tablelist, true);
301  foreach ($tempTableArray as $val) {
302  $tName = trim($val);
303  $this->tableArray[$tName] = [];
304  $deleteField = ‪$GLOBALS['TCA'][$tName]['ctrl']['delete'] ?? false;
305  if ($this->checkIfDeleted && $deleteField) {
306  $fieldN = $tName . '.' . $deleteField;
307  $this->additionalWhere[$tName] .= ' AND ' . $fieldN . '=0';
308  }
309  }
310  if (is_array($this->tableArray)) {
311  reset($this->tableArray);
312  } else {
313  // No tables
314  return;
315  }
316  // Set first and second tables:
317  // Is the first table
318  $this->firstTable = key($this->tableArray);
319  next($this->tableArray);
320  // If the second table is set and the ID number is less than zero (later)
321  // then the record is regarded to come from the second table...
322  $this->secondTable = key($this->tableArray);
323  // Now, populate the internal itemArray and tableArray arrays:
324  // If MM, then call this function to do that:
325  if ($MMtable) {
326  if ($MMuid) {
327  $this->‪readMM($MMtable, $MMuid);
328  $this->‪purgeItemArray();
329  } else {
330  // Revert to readList() for new records in order to load possible default values from $itemlist
331  $this->‪readList($itemlist, $conf);
332  $this->‪purgeItemArray();
333  }
334  } elseif ($MMuid && isset($conf['foreign_field']) && (bool)$conf['foreign_field']) {
335  // If not MM but foreign_field, the read the records by the foreign_field
336  $this->‪readForeignField($MMuid, $conf);
337  } else {
338  // If not MM, then explode the itemlist by "," and traverse the list:
339  $this->‪readList($itemlist, $conf);
340  // Do automatic default_sortby, if any
341  if (isset($conf['foreign_default_sortby']) && $conf['foreign_default_sortby']) {
342  $this->‪sortList($conf['foreign_default_sortby']);
343  }
344  }
345  }
346 
352  public function ‪setFetchAllFields($allFields)
353  {
354  $this->fetchAllFields = (bool)$allFields;
355  }
356 
363  {
364  $this->updateReferenceIndex = (bool)‪$updateReferenceIndex;
365  }
366 
371  {
372  $this->useLiveParentIds = (bool)‪$useLiveParentIds;
373  }
374 
379  {
380  $this->useLiveReferenceIds = (bool)‪$useLiveReferenceIds;
381  }
382 
389  public function ‪readList($itemlist, array $configuration)
390  {
391  if ((string)trim($itemlist) != '') {
392  $tempItemArray = GeneralUtility::trimExplode(',', $itemlist);
393  // Changed to trimExplode 31/3 04; HMENU special type "list" didn't work
394  // if there were spaces in the list... I suppose this is better overall...
395  foreach ($tempItemArray as $key => $val) {
396  // Will be set to "1" if the entry was a real table/id:
397  $isSet = 0;
398  // Extract table name and id. This is un the formular [tablename]_[id]
399  // where table name MIGHT contain "_", hence the reversion of the string!
400  $val = strrev($val);
401  $parts = explode('_', $val, 2);
402  $theID = strrev($parts[0]);
403  // Check that the id IS an integer:
405  // Get the table name: If a part of the exploded string, use that.
406  // Otherwise if the id number is LESS than zero, use the second table, otherwise the first table
407  $theTable = trim($parts[1] ?? '')
408  ? strrev(trim($parts[1] ?? ''))
409  : ($this->secondTable && $theID < 0 ? $this->secondTable : $this->firstTable);
410  // If the ID is not blank and the table name is among the names in the inputted tableList
411  if (
412  (string)$theID != ''
413  // allow the default language '0' for the special languages configuration
414  && ($theID || ($configuration['special'] ?? null) === 'languages')
415  && $theTable && isset($this->tableArray[$theTable])
416  ) {
417  // Get ID as the right value:
418  $theID = $this->secondTable ? abs((int)$theID) : (int)$theID;
419  // Register ID/table name in internal arrays:
420  $this->itemArray[$key]['id'] = $theID;
421  $this->itemArray[$key]['table'] = $theTable;
422  $this->tableArray[$theTable][] = $theID;
423  // Set update-flag:
424  $isSet = 1;
425  }
426  }
427  // If it turns out that the value from the list was NOT a valid reference to a table-record,
428  // then we might still set it as a NO_TABLE value:
429  if (!$isSet && $this->registerNonTableValues) {
430  $this->itemArray[$key]['id'] = $tempItemArray[$key];
431  $this->itemArray[$key]['table'] = '_NO_TABLE';
432  $this->nonTableArray[] = $tempItemArray[$key];
433  }
434  }
435 
436  // Skip if not dealing with IRRE in a CSV list on a workspace
437  if ($configuration['type'] !== 'inline' || empty($configuration['foreign_table']) || !empty($configuration['foreign_field'])
438  || !empty($configuration['MM']) || count($this->tableArray) !== 1 || empty($this->tableArray[$configuration['foreign_table']])
439  || $this->‪getWorkspaceId() === 0 || !‪BackendUtility::isTableWorkspaceEnabled($configuration['foreign_table'])) {
440  return;
441  }
442 
443  // Fetch live record data
444  if ($this->useLiveReferenceIds) {
445  foreach ($this->itemArray as &$item) {
446  $item['id'] = $this->‪getLiveDefaultId($item['table'], $item['id']);
447  }
448  } else {
449  // Directly overlay workspace data
450  $this->itemArray = [];
451  $foreignTable = $configuration['foreign_table'];
452  $ids = $this->‪getResolver($foreignTable, $this->tableArray[$foreignTable])->‪get();
453  foreach ($ids as $id) {
454  $this->itemArray[] = [
455  'id' => $id,
456  'table' => $foreignTable,
457  ];
458  }
459  }
460  }
461  }
462 
470  public function ‪sortList($sortby)
471  {
472  // Sort directly without fetching additional data
473  if ($sortby === 'uid') {
474  usort(
475  $this->itemArray,
476  function ($a, $b) {
477  return $a['id'] < $b['id'] ? -1 : 1;
478  }
479  );
480  } elseif (count($this->tableArray) === 1) {
481  reset($this->tableArray);
482  $table = key($this->tableArray);
483  $connection = $this->‪getConnectionForTableName($table);
484  $maxBindParameters = ‪PlatformInformation::getMaxBindParameters($connection->getDatabasePlatform());
485 
486  foreach (array_chunk(current($this->tableArray), $maxBindParameters - 10, true) as $chunk) {
487  if (empty($chunk)) {
488  continue;
489  }
490  $this->itemArray = [];
491  $this->tableArray = [];
492  $queryBuilder = $connection->createQueryBuilder();
493  $queryBuilder->getRestrictions()->removeAll();
494  $queryBuilder->select('uid')
495  ->from($table)
496  ->where(
497  $queryBuilder->expr()->in(
498  'uid',
499  $queryBuilder->createNamedParameter($chunk, Connection::PARAM_INT_ARRAY)
500  )
501  );
502  foreach (‪QueryHelper::parseOrderBy((string)$sortby) as $orderPair) {
503  list($fieldName, $order) = $orderPair;
504  $queryBuilder->addOrderBy($fieldName, $order);
505  }
506  $statement = $queryBuilder->execute();
507  while ($row = $statement->fetch()) {
508  $this->itemArray[] = ['id' => $row['uid'], 'table' => $table];
509  $this->tableArray[$table][] = $row['uid'];
510  }
511  }
512  }
513  }
514 
522  public function ‪readMM($tableName, $uid)
523  {
524  $key = 0;
525  $theTable = null;
526  $queryBuilder = $this->‪getConnectionForTableName($tableName)
528  $queryBuilder->‪getRestrictions()->‪removeAll();
529  $queryBuilder->select('*')->from($tableName);
530  // In case of a reverse relation
531  if ($this->MM_is_foreign) {
532  $uidLocal_field = 'uid_foreign';
533  $uidForeign_field = 'uid_local';
534  $sorting_field = 'sorting_foreign';
535  if ($this->MM_isMultiTableRelationship) {
536  // Be backwards compatible! When allowing more than one table after
537  // having previously allowed only one table, this case applies.
538  if ($this->currentTable == $this->MM_isMultiTableRelationship) {
539  $expression = $queryBuilder->expr()->orX(
540  $queryBuilder->expr()->eq(
541  'tablenames',
542  $queryBuilder->createNamedParameter($this->currentTable, \PDO::PARAM_STR)
543  ),
544  $queryBuilder->expr()->eq(
545  'tablenames',
546  $queryBuilder->createNamedParameter('', \PDO::PARAM_STR)
547  )
548  );
549  } else {
550  $expression = $queryBuilder->expr()->eq(
551  'tablenames',
552  $queryBuilder->createNamedParameter($this->currentTable, \PDO::PARAM_STR)
553  );
554  }
555  $queryBuilder->andWhere($expression);
556  }
557  $theTable = ‪$this->MM_oppositeTable;
558  } else {
559  // Default
560  $uidLocal_field = 'uid_local';
561  $uidForeign_field = 'uid_foreign';
562  $sorting_field = 'sorting';
563  }
564  if ($this->MM_table_where) {
565  $queryBuilder->andWhere(
566  ‪QueryHelper::stripLogicalOperatorPrefix(str_replace('###THIS_UID###', (int)$uid, $this->MM_table_where))
567  );
568  }
569  foreach ($this->MM_match_fields as $field => $value) {
570  $queryBuilder->andWhere(
571  $queryBuilder->expr()->eq($field, $queryBuilder->createNamedParameter($value, \PDO::PARAM_STR))
572  );
573  }
574  $queryBuilder->andWhere(
575  $queryBuilder->expr()->eq(
576  $uidLocal_field,
577  $queryBuilder->createNamedParameter((int)$uid, \PDO::PARAM_INT)
578  )
579  );
580  $queryBuilder->orderBy($sorting_field);
581  $statement = $queryBuilder->execute();
582  while ($row = $statement->fetch()) {
583  // Default
584  if (!$this->MM_is_foreign) {
585  // If tablesnames columns exists and contain a name, then this value is the table, else it's the firstTable...
586  $theTable = $row['tablenames'] ?: ‪$this->firstTable;
587  }
588  if (($row[$uidForeign_field] || $theTable === 'pages') && $theTable && isset($this->tableArray[$theTable])) {
589  $this->itemArray[$key]['id'] = $row[$uidForeign_field];
590  $this->itemArray[$key]['table'] = $theTable;
591  $this->tableArray[$theTable][] = $row[$uidForeign_field];
592  } elseif ($this->registerNonTableValues) {
593  $this->itemArray[$key]['id'] = $row[$uidForeign_field];
594  $this->itemArray[$key]['table'] = '_NO_TABLE';
595  $this->nonTableArray[] = $row[$uidForeign_field];
596  }
597  $key++;
598  }
599  }
600 
608  public function ‪writeMM($MM_tableName, $uid, $prependTableName = false)
609  {
610  $connection = $this->‪getConnectionForTableName($MM_tableName);
611  $expressionBuilder = $connection->createQueryBuilder()->expr();
612 
613  // In case of a reverse relation
614  if ($this->MM_is_foreign) {
615  $uidLocal_field = 'uid_foreign';
616  $uidForeign_field = 'uid_local';
617  $sorting_field = 'sorting_foreign';
618  } else {
619  // default
620  $uidLocal_field = 'uid_local';
621  $uidForeign_field = 'uid_foreign';
622  $sorting_field = 'sorting';
623  }
624  // If there are tables...
625  $tableC = count($this->tableArray);
626  if ($tableC) {
627  // Boolean: does the field "tablename" need to be filled?
628  $prep = $tableC > 1 || $prependTableName || ‪$this->MM_isMultiTableRelationship;
629  $c = 0;
630  $additionalWhere_tablenames = '';
631  if ($this->MM_is_foreign && $prep) {
632  $additionalWhere_tablenames = $expressionBuilder->eq(
633  'tablenames',
634  $expressionBuilder->literal($this->currentTable)
635  );
636  }
637  ‪$additionalWhere = $expressionBuilder->andX();
638  // Add WHERE clause if configured
639  if ($this->MM_table_where) {
640  ‪$additionalWhere->add(
642  str_replace('###THIS_UID###', (int)$uid, $this->MM_table_where)
643  )
644  );
645  }
646  // Select, update or delete only those relations that match the configured fields
647  foreach ($this->MM_match_fields as $field => $value) {
648  ‪$additionalWhere->add($expressionBuilder->eq($field, $expressionBuilder->literal($value)));
649  }
650 
651  $queryBuilder = $connection->createQueryBuilder();
652  $queryBuilder->getRestrictions()->removeAll();
653  $queryBuilder->select($uidForeign_field)
654  ->from($MM_tableName)
655  ->where($queryBuilder->expr()->eq(
656  $uidLocal_field,
657  $queryBuilder->createNamedParameter($uid, \PDO::PARAM_INT)
658  ))
659  ->orderBy($sorting_field);
660 
661  if ($prep) {
662  $queryBuilder->addSelect('tablenames');
663  }
664  if ($this->MM_hasUidField) {
665  $queryBuilder->addSelect('uid');
666  }
667  if ($additionalWhere_tablenames) {
668  $queryBuilder->andWhere($additionalWhere_tablenames);
669  }
670  if (‪$additionalWhere->count()) {
671  $queryBuilder->andWhere(‪$additionalWhere);
672  }
673 
674  $result = $queryBuilder->execute();
675  $oldMMs = [];
676  // This array is similar to $oldMMs but also holds the uid of the MM-records, if any (configured by MM_hasUidField).
677  // If the UID is present it will be used to update sorting and delete MM-records.
678  // This is necessary if the "multiple" feature is used for the MM relations.
679  // $oldMMs is still needed for the in_array() search used to look if an item from $this->itemArray is in $oldMMs
680  $oldMMs_inclUid = [];
681  while ($row = $result->fetch()) {
682  if (!$this->MM_is_foreign && $prep) {
683  $oldMMs[] = [$row['tablenames'], $row[$uidForeign_field]];
684  } else {
685  $oldMMs[] = $row[$uidForeign_field];
686  }
687  $oldMMs_inclUid[] = [$row['tablenames'], $row[$uidForeign_field], $row['uid']];
688  }
689  // For each item, insert it:
690  foreach ($this->itemArray as $val) {
691  $c++;
692  if ($prep || $val['table'] === '_NO_TABLE') {
693  // Insert current table if needed
694  if ($this->MM_is_foreign) {
695  $tablename = ‪$this->currentTable;
696  } else {
697  $tablename = $val['table'];
698  }
699  } else {
700  $tablename = '';
701  }
702  if (!$this->MM_is_foreign && $prep) {
703  $item = [$val['table'], $val['id']];
704  } else {
705  $item = $val['id'];
706  }
707  if (in_array($item, $oldMMs)) {
708  $oldMMs_index = array_search($item, $oldMMs);
709  // In principle, selecting on the UID is all we need to do
710  // if a uid field is available since that is unique!
711  // But as long as it "doesn't hurt" we just add it to the where clause. It should all match up.
712  $queryBuilder = $connection->createQueryBuilder();
713  $queryBuilder->update($MM_tableName)
714  ->set($sorting_field, $c)
715  ->where(
716  $expressionBuilder->eq(
717  $uidLocal_field,
718  $queryBuilder->createNamedParameter($uid, \PDO::PARAM_INT)
719  ),
720  $expressionBuilder->eq(
721  $uidForeign_field,
722  $queryBuilder->createNamedParameter($val['id'], \PDO::PARAM_INT)
723  )
724  );
725 
726  if (‪$additionalWhere->count()) {
727  $queryBuilder->andWhere(‪$additionalWhere);
728  }
729  if ($this->MM_hasUidField) {
730  $queryBuilder->andWhere(
731  $expressionBuilder->eq(
732  'uid',
733  $queryBuilder->createNamedParameter($oldMMs_inclUid[$oldMMs_index][2], \PDO::PARAM_INT)
734  )
735  );
736  }
737  if ($tablename) {
738  $queryBuilder->andWhere(
739  $expressionBuilder->eq(
740  'tablenames',
741  $queryBuilder->createNamedParameter($tablename, \PDO::PARAM_STR)
742  )
743  );
744  }
745 
746  $queryBuilder->execute();
747  // Remove the item from the $oldMMs array so after this
748  // foreach loop only the ones that need to be deleted are in there.
749  unset($oldMMs[$oldMMs_index]);
750  // Remove the item from the $oldMMs_inclUid array so after this
751  // foreach loop only the ones that need to be deleted are in there.
752  unset($oldMMs_inclUid[$oldMMs_index]);
753  } else {
754  $insertFields = ‪$this->MM_insert_fields;
755  $insertFields[$uidLocal_field] = $uid;
756  $insertFields[$uidForeign_field] = $val['id'];
757  $insertFields[$sorting_field] = $c;
758  if ($tablename) {
759  $insertFields['tablenames'] = $tablename;
760  $insertFields = $this->‪completeOppositeUsageValues($tablename, $insertFields);
761  }
762  $connection->insert($MM_tableName, $insertFields);
763  if ($this->MM_is_foreign) {
764  $this->‪updateRefIndex($val['table'], $val['id']);
765  }
766  }
767  }
768  // Delete all not-used relations:
769  if (is_array($oldMMs) && !empty($oldMMs)) {
770  $queryBuilder = $connection->createQueryBuilder();
771  $removeClauses = $queryBuilder->expr()->orX();
772  $updateRefIndex_records = [];
773  foreach ($oldMMs as $oldMM_key => $mmItem) {
774  // If UID field is present, of course we need only use that for deleting.
775  if ($this->MM_hasUidField) {
776  $removeClauses->add($queryBuilder->expr()->eq(
777  'uid',
778  $queryBuilder->createNamedParameter($oldMMs_inclUid[$oldMM_key][2], \PDO::PARAM_INT)
779  ));
780  } else {
781  if (is_array($mmItem)) {
782  $removeClauses->add(
783  $queryBuilder->expr()->andX(
784  $queryBuilder->expr()->eq(
785  'tablenames',
786  $queryBuilder->createNamedParameter($mmItem[0], \PDO::PARAM_STR)
787  ),
788  $queryBuilder->expr()->eq(
789  $uidForeign_field,
790  $queryBuilder->createNamedParameter($mmItem[1], \PDO::PARAM_INT)
791  )
792  )
793  );
794  } else {
795  $removeClauses->add(
796  $queryBuilder->expr()->eq(
797  $uidForeign_field,
798  $queryBuilder->createNamedParameter($mmItem, \PDO::PARAM_INT)
799  )
800  );
801  }
802  }
803  if ($this->MM_is_foreign) {
804  if (is_array($mmItem)) {
805  $updateRefIndex_records[] = [$mmItem[0], $mmItem[1]];
806  } else {
807  $updateRefIndex_records[] = [‪$this->firstTable, $mmItem];
808  }
809  }
810  }
811 
812  $queryBuilder->delete($MM_tableName)
813  ->where(
814  $queryBuilder->expr()->eq(
815  $uidLocal_field,
816  $queryBuilder->createNamedParameter($uid, \PDO::PARAM_INT)
817  ),
818  $removeClauses
819  );
820 
821  if ($additionalWhere_tablenames) {
822  $queryBuilder->andWhere($additionalWhere_tablenames);
823  }
824  if (‪$additionalWhere->count()) {
825  $queryBuilder->andWhere(‪$additionalWhere);
826  }
827 
828  $queryBuilder->execute();
829 
830  // Update ref index:
831  foreach ($updateRefIndex_records as $pair) {
832  $this->‪updateRefIndex($pair[0], $pair[1]);
833  }
834  }
835  // Update ref index; In DataHandler it is not certain that this will happen because
836  // if only the MM field is changed the record itself is not updated and so the ref-index is not either.
837  // This could also have been fixed in updateDB in DataHandler, however I decided to do it here ...
838  $this->‪updateRefIndex($this->currentTable, $uid);
839  }
840  }
841 
851  public function ‪remapMM($MM_tableName, $uid, $newUid, $prependTableName = false)
852  {
853  // In case of a reverse relation
854  if ($this->MM_is_foreign) {
855  $uidLocal_field = 'uid_foreign';
856  } else {
857  // default
858  $uidLocal_field = 'uid_local';
859  }
860  // If there are tables...
861  $tableC = count($this->tableArray);
862  if ($tableC) {
863  $queryBuilder = $this->‪getConnectionForTableName($MM_tableName)
865  $queryBuilder->‪update($MM_tableName)
866  ->‪set($uidLocal_field, (int)$newUid)
867  ->‪where($queryBuilder->expr()->eq(
868  $uidLocal_field,
869  $queryBuilder->createNamedParameter($uid, \PDO::PARAM_INT)
870  ));
871  // Boolean: does the field "tablename" need to be filled?
872  $prep = $tableC > 1 || $prependTableName || ‪$this->MM_isMultiTableRelationship;
873  if ($this->MM_is_foreign && $prep) {
874  $queryBuilder->andWhere(
875  $queryBuilder->expr()->eq(
876  'tablenames',
877  $queryBuilder->createNamedParameter($this->currentTable, \PDO::PARAM_STR)
878  )
879  );
880  }
881  // Add WHERE clause if configured
882  if ($this->MM_table_where) {
883  $queryBuilder->andWhere(
884  ‪QueryHelper::stripLogicalOperatorPrefix(str_replace('###THIS_UID###', (int)$uid, $this->MM_table_where))
885  );
886  }
887  // Select, update or delete only those relations that match the configured fields
888  foreach ($this->MM_match_fields as $field => $value) {
889  $queryBuilder->andWhere(
890  $queryBuilder->expr()->eq($field, $queryBuilder->createNamedParameter($value, \PDO::PARAM_STR))
891  );
892  }
893  $queryBuilder->execute();
894  }
895  }
896 
904  public function ‪readForeignField($uid, $conf)
905  {
906  if ($this->useLiveParentIds) {
907  $uid = $this->‪getLiveDefaultId($this->currentTable, $uid);
908  }
909 
910  $key = 0;
911  $uid = (int)$uid;
912  // skip further processing if $uid does not
913  // point to a valid parent record
914  if ($uid === 0) {
915  return;
916  }
917 
918  $foreign_table = $conf['foreign_table'];
919  $foreign_table_field = $conf['foreign_table_field'];
920  $useDeleteClause = !‪$this->undeleteRecord;
921  $foreign_match_fields = is_array($conf['foreign_match_fields']) ? $conf['foreign_match_fields'] : [];
922  $queryBuilder = $this->‪getConnectionForTableName($foreign_table)
924  $queryBuilder->‪getRestrictions()
925  ->‪removeAll();
926  // Use the deleteClause (e.g. "deleted=0") on this table
927  if ($useDeleteClause) {
928  $queryBuilder->getRestrictions()->‪add(GeneralUtility::makeInstance(DeletedRestriction::class));
929  }
930 
931  $queryBuilder->select('uid')
932  ->from($foreign_table);
933 
934  // Search for $uid in foreign_field, and if we have symmetric relations, do this also on symmetric_field
935  if ($conf['symmetric_field']) {
936  $queryBuilder->where(
937  $queryBuilder->expr()->orX(
938  $queryBuilder->expr()->eq(
939  $conf['foreign_field'],
940  $queryBuilder->createNamedParameter($uid, \PDO::PARAM_INT)
941  ),
942  $queryBuilder->expr()->eq(
943  $conf['symmetric_field'],
944  $queryBuilder->createNamedParameter($uid, \PDO::PARAM_INT)
945  )
946  )
947  );
948  } else {
949  $queryBuilder->where($queryBuilder->expr()->eq(
950  $conf['foreign_field'],
951  $queryBuilder->createNamedParameter($uid, \PDO::PARAM_INT)
952  ));
953  }
954  // If it's requested to look for the parent uid AND the parent table,
955  // add an additional SQL-WHERE clause
956  if ($foreign_table_field && $this->currentTable) {
957  $queryBuilder->andWhere(
958  $queryBuilder->expr()->eq(
959  $foreign_table_field,
960  $queryBuilder->createNamedParameter($this->currentTable, \PDO::PARAM_STR)
961  )
962  );
963  }
964  // Add additional where clause if foreign_match_fields are defined
965  foreach ($foreign_match_fields as $field => $value) {
966  $queryBuilder->andWhere(
967  $queryBuilder->expr()->eq($field, $queryBuilder->createNamedParameter($value, \PDO::PARAM_STR))
968  );
969  }
970  // Select children from the live(!) workspace only
971  if (‪BackendUtility::isTableWorkspaceEnabled($foreign_table)) {
972  $queryBuilder->andWhere(
973  $queryBuilder->expr()->in(
974  $foreign_table . '.t3ver_wsid',
975  $queryBuilder->createNamedParameter([0, (int)$this->getWorkspaceId()], Connection::PARAM_INT_ARRAY)
976  ),
977  $queryBuilder->expr()->neq(
978  $foreign_table . '.pid',
979  $queryBuilder->createNamedParameter(-1, \PDO::PARAM_INT)
980  )
981  );
982  }
983  // Get the correct sorting field
984  // Specific manual sortby for data handled by this field
985  $sortby = '';
986  if ($conf['foreign_sortby']) {
987  if ($conf['symmetric_sortby'] && $conf['symmetric_field']) {
988  // Sorting depends on, from which side of the relation we're looking at it
989  // This requires bypassing automatic quoting and setting of the default sort direction
990  // @TODO: Doctrine: generalize to standard SQL to guarantee database independency
991  $queryBuilder->‪add(
992  'orderBy',
993  'CASE
994  WHEN ' . $queryBuilder->expr()->eq($conf['foreign_field'], $uid) . '
995  THEN ' . $queryBuilder->quoteIdentifier($conf['foreign_sortby']) . '
996  ELSE ' . $queryBuilder->quoteIdentifier($conf['symmetric_sortby']) . '
997  END'
998  );
999  } else {
1000  // Regular single-side behaviour
1001  $sortby = $conf['foreign_sortby'];
1002  }
1003  } elseif ($conf['foreign_default_sortby']) {
1004  // Specific default sortby for data handled by this field
1005  $sortby = $conf['foreign_default_sortby'];
1006  } elseif (‪$GLOBALS['TCA'][$foreign_table]['ctrl']['sortby']) {
1007  // Manual sortby for all table records
1008  $sortby = ‪$GLOBALS['TCA'][$foreign_table]['ctrl']['sortby'];
1009  } elseif (‪$GLOBALS['TCA'][$foreign_table]['ctrl']['default_sortby']) {
1010  // Default sortby for all table records
1011  $sortby = ‪$GLOBALS['TCA'][$foreign_table]['ctrl']['default_sortby'];
1012  }
1013 
1014  if (!empty($sortby)) {
1015  foreach (‪QueryHelper::parseOrderBy($sortby) as $orderPair) {
1016  list($fieldName, $sorting) = $orderPair;
1017  $queryBuilder->addOrderBy($fieldName, $sorting);
1018  }
1019  }
1021  // Get the rows from storage
1022  $rows = [];
1023  $result = $queryBuilder->execute();
1024  while ($row = $result->fetch()) {
1025  $rows[$row['uid']] = $row;
1026  }
1027  if (!empty($rows)) {
1028  // Retrieve the parsed and prepared ORDER BY configuration for the resolver
1029  $sortby = $queryBuilder->getQueryPart('orderBy');
1030  $ids = $this->‪getResolver($foreign_table, array_keys($rows), $sortby)->‪get();
1031  foreach ($ids as $id) {
1032  $this->itemArray[$key]['id'] = $id;
1033  $this->itemArray[$key]['table'] = $foreign_table;
1034  $this->tableArray[$foreign_table][] = $id;
1035  $key++;
1036  }
1037  }
1038  }
1039 
1048  public function ‪writeForeignField($conf, $parentUid, $updateToUid = 0, $skipSorting = false)
1049  {
1050  if ($this->useLiveParentIds) {
1051  $parentUid = $this->‪getLiveDefaultId($this->currentTable, $parentUid);
1052  if (!empty($updateToUid)) {
1053  $updateToUid = $this->‪getLiveDefaultId($this->currentTable, $updateToUid);
1054  }
1055  }
1056 
1057  $c = 0;
1058  $foreign_table = $conf['foreign_table'];
1059  $foreign_field = $conf['foreign_field'];
1060  $symmetric_field = $conf['symmetric_field'];
1061  $foreign_table_field = $conf['foreign_table_field'];
1062  $foreign_match_fields = is_array($conf['foreign_match_fields']) ? $conf['foreign_match_fields'] : [];
1063  // If there are table items and we have a proper $parentUid
1064  if (‪MathUtility::canBeInterpretedAsInteger($parentUid) && !empty($this->tableArray)) {
1065  // If updateToUid is not a positive integer, set it to '0', so it will be ignored
1066  if (!(‪MathUtility::canBeInterpretedAsInteger($updateToUid) && $updateToUid > 0)) {
1067  $updateToUid = 0;
1068  }
1069  $considerWorkspaces = ‪BackendUtility::isTableWorkspaceEnabled($foreign_table);
1070  ‪$fields = 'uid,pid,' . $foreign_field;
1071  // Consider the symmetric field if defined:
1072  if ($symmetric_field) {
1073  ‪$fields .= ',' . $symmetric_field;
1074  }
1075  // Consider workspaces if defined and currently used:
1076  if ($considerWorkspaces) {
1077  ‪$fields .= ',t3ver_wsid,t3ver_state,t3ver_oid';
1078  }
1079  // Update all items
1080  foreach ($this->itemArray as $val) {
1081  $uid = $val['id'];
1082  $table = $val['table'];
1083  $row = [];
1084  // Fetch the current (not overwritten) relation record if we should handle symmetric relations
1085  if ($symmetric_field || $considerWorkspaces) {
1086  $row = ‪BackendUtility::getRecord($table, $uid, ‪$fields, '', true);
1087  if (empty($row)) {
1088  continue;
1089  }
1090  }
1091  $isOnSymmetricSide = false;
1092  if ($symmetric_field) {
1093  $isOnSymmetricSide = ‪self::isOnSymmetricSide($parentUid, $conf, $row);
1094  }
1095  $updateValues = $foreign_match_fields;
1096  // No update to the uid is requested, so this is the normal behaviour
1097  // just update the fields and care about sorting
1098  if (!$updateToUid) {
1099  // Always add the pointer to the parent uid
1100  if ($isOnSymmetricSide) {
1101  $updateValues[$symmetric_field] = $parentUid;
1102  } else {
1103  $updateValues[$foreign_field] = $parentUid;
1104  }
1105  // If it is configured in TCA also to store the parent table in the child record, just do it
1106  if ($foreign_table_field && $this->currentTable) {
1107  $updateValues[$foreign_table_field] = ‪$this->currentTable;
1108  }
1109  // Update sorting columns if not to be skipped
1110  if (!$skipSorting) {
1111  // Get the correct sorting field
1112  // Specific manual sortby for data handled by this field
1113  $sortby = '';
1114  if ($conf['foreign_sortby']) {
1115  $sortby = $conf['foreign_sortby'];
1116  } elseif (‪$GLOBALS['TCA'][$foreign_table]['ctrl']['sortby']) {
1117  // manual sortby for all table records
1118  $sortby = ‪$GLOBALS['TCA'][$foreign_table]['ctrl']['sortby'];
1119  }
1120  // Apply sorting on the symmetric side
1121  // (it depends on who created the relation, so what uid is in the symmetric_field):
1122  if ($isOnSymmetricSide && isset($conf['symmetric_sortby']) && $conf['symmetric_sortby']) {
1123  $sortby = $conf['symmetric_sortby'];
1124  } else {
1125  $tempSortBy = [];
1126  foreach (‪QueryHelper::parseOrderBy($sortby) as $orderPair) {
1127  list($fieldName, $order) = $orderPair;
1128  if ($order !== null) {
1129  $tempSortBy[] = implode(' ', $orderPair);
1130  } else {
1131  $tempSortBy[] = $fieldName;
1132  }
1133  }
1134  $sortby = implode(',', $tempSortBy);
1135  }
1136  if ($sortby) {
1137  $updateValues[$sortby] = ++$c;
1138  }
1139  }
1140  } else {
1141  if ($isOnSymmetricSide) {
1142  $updateValues[$symmetric_field] = $updateToUid;
1143  } else {
1144  $updateValues[$foreign_field] = $updateToUid;
1145  }
1146  }
1147  // Update accordant fields in the database:
1148  if (!empty($updateValues)) {
1149  // Update tstamp if any foreign field value has changed
1150  if (!empty(‪$GLOBALS['TCA'][$table]['ctrl']['tstamp'])) {
1151  $updateValues[‪$GLOBALS['TCA'][$table]['ctrl']['tstamp']] = ‪$GLOBALS['EXEC_TIME'];
1152  }
1153  $this->‪getConnectionForTableName($table)
1154  ->‪update(
1155  $table,
1156  $updateValues,
1157  ['uid' => (int)$uid]
1158  );
1159  $this->‪updateRefIndex($table, $uid);
1160  }
1161  // Update accordant fields in the database for workspaces overlays/placeholders:
1162  if ($considerWorkspaces) {
1163  // It's the specific versioned record -> update placeholder (if any)
1164  if (!empty($row['t3ver_oid']) && ‪VersionState::cast($row['t3ver_state'])->equals(‪VersionState::NEW_PLACEHOLDER_VERSION)) {
1165  $this->‪getConnectionForTableName($table)
1166  ->‪update(
1167  $table,
1168  $updateValues,
1169  ['uid' => (int)$row['t3ver_oid']]
1170  );
1171  }
1172  }
1173  }
1174  }
1175  }
1176 
1183  public function ‪getValueArray($prependTableName = false)
1184  {
1185  // INIT:
1186  $valueArray = [];
1187  $tableC = count($this->tableArray);
1188  // If there are tables in the table array:
1189  if ($tableC) {
1190  // If there are more than ONE table in the table array, then always prepend table names:
1191  $prep = $tableC > 1 || $prependTableName;
1192  // Traverse the array of items:
1193  foreach ($this->itemArray as $val) {
1194  $valueArray[] = ($prep && $val['table'] !== '_NO_TABLE' ? $val['table'] . '_' : '') . $val['id'];
1195  }
1196  }
1197  // Return the array
1198  return $valueArray;
1199  }
1200 
1209  public function ‪getFromDB()
1210  {
1211  // Traverses the tables listed:
1212  foreach ($this->tableArray as $table => $ids) {
1213  if (is_array($ids) && !empty($ids)) {
1214  $connection = $this->‪getConnectionForTableName($table);
1215  $maxBindParameters = ‪PlatformInformation::getMaxBindParameters($connection->getDatabasePlatform());
1216 
1217  foreach (array_chunk($ids, $maxBindParameters - 10, true) as $chunk) {
1218  if ($this->fetchAllFields) {
1219  ‪$fields = '*';
1220  } else {
1221  ‪$fields = 'uid,pid';
1222  if (‪$GLOBALS['TCA'][$table]['ctrl']['label']) {
1223  // Titel
1224  ‪$fields .= ',' . ‪$GLOBALS['TCA'][$table]['ctrl']['label'];
1225  }
1226  if (‪$GLOBALS['TCA'][$table]['ctrl']['label_alt']) {
1227  // Alternative Title-Fields
1228  ‪$fields .= ',' . ‪$GLOBALS['TCA'][$table]['ctrl']['label_alt'];
1229  }
1230  if (‪$GLOBALS['TCA'][$table]['ctrl']['thumbnail']) {
1231  // Thumbnail
1232  ‪$fields .= ',' . ‪$GLOBALS['TCA'][$table]['ctrl']['thumbnail'];
1233  }
1234  }
1235  $queryBuilder = $connection->createQueryBuilder();
1236  $queryBuilder->getRestrictions()->removeAll();
1237  $queryBuilder->select(...GeneralUtility::trimExplode(',', ‪$fields, true))
1238  ->from($table)
1239  ->where($queryBuilder->expr()->in(
1240  'uid',
1241  $queryBuilder->createNamedParameter($chunk, Connection::PARAM_INT_ARRAY)
1242  ));
1243  if ($this->additionalWhere[$table]) {
1244  $queryBuilder->andWhere(
1245  ‪QueryHelper::stripLogicalOperatorPrefix($this->additionalWhere[$table])
1246  );
1247  }
1248  $statement = $queryBuilder->execute();
1249  while ($row = $statement->fetch()) {
1250  $this->results[$table][$row['uid']] = $row;
1251  }
1252  }
1253  }
1254  }
1255  return ‪$this->results;
1256  }
1257 
1271  public function ‪getResolvedItemArray(): array
1272  {
1273  ‪$itemArray = [];
1274  foreach ($this->itemArray as $item) {
1275  if (isset($this->results[$item['table']][$item['id']])) {
1276  ‪$itemArray[] = [
1277  'table' => $item['table'],
1278  'uid' => $item['id'],
1279  ];
1280  }
1281  }
1282  return ‪$itemArray;
1283  }
1284 
1291  public function ‪countItems($returnAsArray = true)
1292  {
1293  $count = count($this->itemArray);
1294  if ($returnAsArray) {
1295  $count = [$count];
1296  }
1297  return $count;
1298  }
1299 
1309  public function ‪updateRefIndex($table, $id)
1310  {
1311  $statisticsArray = [];
1312  if ($this->updateReferenceIndex) {
1314  $refIndexObj = GeneralUtility::makeInstance(\‪TYPO3\CMS\Core\Database\ReferenceIndex::class);
1316  $refIndexObj->setWorkspaceId($this->‪getWorkspaceId());
1317  }
1318  $refIndexObj->enableRuntimeCache();
1319  $statisticsArray = $refIndexObj->updateRefIndexTable($table, $id);
1320  }
1321  return $statisticsArray;
1322  }
1323 
1333  public function ‪convertItemArray()
1334  {
1335  $hasBeenConverted = false;
1336 
1337  // conversion is only required in a workspace context
1338  // (the case that version ids are submitted in a live context are rare)
1339  if ($this->‪getWorkspaceId() === 0) {
1340  return $hasBeenConverted;
1341  }
1342 
1343  foreach ($this->tableArray as $tableName => $ids) {
1344  if (empty($ids) || !‪BackendUtility::isTableWorkspaceEnabled($tableName)) {
1345  continue;
1346  }
1347 
1348  // convert live ids to version ids if available
1349  $convertedIds = $this->‪getResolver($tableName, $ids)
1351  ->‪setKeepMovePlaceholder(false)
1352  ->‪processVersionOverlays($ids);
1353  foreach ($this->itemArray as $index => $item) {
1354  if ($item['table'] !== $tableName) {
1355  continue;
1356  }
1357  $currentItemId = $item['id'];
1358  if (
1359  !isset($convertedIds[$currentItemId])
1360  || $currentItemId === $convertedIds[$currentItemId]
1361  ) {
1362  continue;
1363  }
1364  // adjust local item to use resolved version id
1365  $this->itemArray[$index]['id'] = $convertedIds[$currentItemId];
1366  $hasBeenConverted = true;
1367  }
1368  // update per-table reference for ids
1369  if ($hasBeenConverted) {
1370  $this->tableArray[$tableName] = array_values($convertedIds);
1371  }
1372  }
1373 
1374  return $hasBeenConverted;
1375  }
1376 
1381  public function ‪purgeItemArray(‪$workspaceId = null)
1382  {
1383  if (‪$workspaceId === null) {
1385  } else {
1387  }
1388 
1389  // Ensure, only live relations are in the items Array
1390  if (‪$workspaceId === 0) {
1391  $purgeCallback = 'purgeVersionedIds';
1392  } else {
1393  // Otherwise, ensure that live relations are purged if version exists
1394  $purgeCallback = 'purgeLiveVersionedIds';
1395  }
1396 
1397  $itemArrayHasBeenPurged = $this->‪purgeItemArrayHandler($purgeCallback);
1398  $this->purged = ($this->purged || $itemArrayHasBeenPurged);
1399  return $itemArrayHasBeenPurged;
1400  }
1401 
1407  public function ‪processDeletePlaceholder()
1408  {
1409  if (!$this->useLiveReferenceIds || $this->‪getWorkspaceId() === 0) {
1410  return false;
1411  }
1412 
1413  return $this->‪purgeItemArrayHandler('purgeDeletePlaceholder');
1414  }
1415 
1422  protected function ‪purgeItemArrayHandler($purgeCallback)
1423  {
1424  $itemArrayHasBeenPurged = false;
1425 
1426  foreach ($this->tableArray as $itemTableName => $itemIds) {
1427  if (empty($itemIds) || !‪BackendUtility::isTableWorkspaceEnabled($itemTableName)) {
1428  continue;
1429  }
1430 
1431  $purgedItemIds = call_user_func([$this, $purgeCallback], $itemTableName, $itemIds);
1432  $removedItemIds = array_diff($itemIds, $purgedItemIds);
1433  foreach ($removedItemIds as $removedItemId) {
1434  $this->‪removeFromItemArray($itemTableName, $removedItemId);
1435  }
1436  $this->tableArray[$itemTableName] = $purgedItemIds;
1437  if (!empty($removedItemIds)) {
1438  $itemArrayHasBeenPurged = true;
1439  }
1440  }
1441 
1442  return $itemArrayHasBeenPurged;
1443  }
1444 
1452  protected function ‪purgeVersionedIds($tableName, array $ids)
1453  {
1454  $ids = array_combine($ids, $ids);
1455  $connection = $this->‪getConnectionForTableName($tableName);
1456  $maxBindParameters = ‪PlatformInformation::getMaxBindParameters($connection->getDatabasePlatform());
1457 
1458  foreach (array_chunk($ids, $maxBindParameters - 10, true) as $chunk) {
1459  $queryBuilder = $connection->createQueryBuilder();
1460  $queryBuilder->getRestrictions()->removeAll();
1461  $result = $queryBuilder->select('uid', 't3ver_oid', 't3ver_state')
1462  ->from($tableName)
1463  ->where(
1464  $queryBuilder->expr()->eq(
1465  'pid',
1466  $queryBuilder->createNamedParameter(-1, \PDO::PARAM_INT)
1467  ),
1468  $queryBuilder->expr()->in(
1469  't3ver_oid',
1470  $queryBuilder->createNamedParameter($chunk, Connection::PARAM_INT_ARRAY)
1471  ),
1472  $queryBuilder->expr()->neq(
1473  't3ver_wsid',
1474  $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT)
1475  )
1476  )
1477  ->orderBy('t3ver_state', 'DESC')
1478  ->execute();
1479 
1480  while ($version = $result->fetch()) {
1481  $versionId = $version['uid'];
1482  if (isset($ids[$versionId])) {
1483  unset($ids[$versionId]);
1484  }
1485  }
1486  }
1487 
1488  return array_values($ids);
1489  }
1490 
1498  protected function ‪purgeLiveVersionedIds($tableName, array $ids)
1499  {
1500  $ids = array_combine($ids, $ids);
1501  $connection = $this->‪getConnectionForTableName($tableName);
1502  $maxBindParameters = ‪PlatformInformation::getMaxBindParameters($connection->getDatabasePlatform());
1503 
1504  foreach (array_chunk($ids, $maxBindParameters - 10, true) as $chunk) {
1505  $queryBuilder = $connection->createQueryBuilder();
1506  $queryBuilder->getRestrictions()->removeAll();
1507  $result = $queryBuilder->select('uid', 't3ver_oid', 't3ver_state')
1508  ->from($tableName)
1509  ->where(
1510  $queryBuilder->expr()->eq(
1511  'pid',
1512  $queryBuilder->createNamedParameter(-1, \PDO::PARAM_INT)
1513  ),
1514  $queryBuilder->expr()->in(
1515  't3ver_oid',
1516  $queryBuilder->createNamedParameter($chunk, Connection::PARAM_INT_ARRAY)
1517  ),
1518  $queryBuilder->expr()->neq(
1519  't3ver_wsid',
1520  $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT)
1521  )
1522  )
1523  ->orderBy('t3ver_state', 'DESC')
1524  ->execute();
1525 
1526  while ($version = $result->fetch()) {
1527  $versionId = $version['uid'];
1528  $liveId = $version['t3ver_oid'];
1529  if (isset($ids[$liveId]) && isset($ids[$versionId])) {
1530  unset($ids[$liveId]);
1531  }
1532  }
1533  }
1534 
1535  return array_values($ids);
1536  }
1537 
1545  protected function ‪purgeDeletePlaceholder($tableName, array $ids)
1546  {
1547  $ids = array_combine($ids, $ids);
1548  $connection = $this->‪getConnectionForTableName($tableName);
1549  $maxBindParameters = ‪PlatformInformation::getMaxBindParameters($connection->getDatabasePlatform());
1550 
1551  foreach (array_chunk($ids, $maxBindParameters - 10, true) as $chunk) {
1552  $queryBuilder = $connection->createQueryBuilder();
1553  $queryBuilder->getRestrictions()->removeAll();
1554  $result = $queryBuilder->select('uid', 't3ver_oid', 't3ver_state')
1555  ->from($tableName)
1556  ->where(
1557  $queryBuilder->expr()->eq(
1558  'pid',
1559  $queryBuilder->createNamedParameter(-1, \PDO::PARAM_INT)
1560  ),
1561  $queryBuilder->expr()->in(
1562  't3ver_oid',
1563  $queryBuilder->createNamedParameter($chunk, Connection::PARAM_INT_ARRAY)
1564  ),
1565  $queryBuilder->expr()->eq(
1566  't3ver_wsid',
1567  $queryBuilder->createNamedParameter(
1568  $this->getWorkspaceId(),
1569  \PDO::PARAM_INT
1570  )
1571  ),
1572  $queryBuilder->expr()->eq(
1573  't3ver_state',
1574  $queryBuilder->createNamedParameter(
1576  \PDO::PARAM_INT
1577  )
1578  )
1579  )
1580  ->execute();
1581 
1582  while ($version = $result->fetch()) {
1583  $liveId = $version['t3ver_oid'];
1584  if (isset($ids[$liveId])) {
1585  unset($ids[$liveId]);
1586  }
1587  }
1588  }
1589 
1590  return array_values($ids);
1591  }
1592 
1593  protected function ‪removeFromItemArray($tableName, $id)
1594  {
1595  foreach ($this->itemArray as $index => $item) {
1596  if ($item['table'] === $tableName && (string)$item['id'] === (string)$id) {
1597  unset($this->itemArray[$index]);
1598  return true;
1599  }
1600  }
1601  return false;
1602  }
1603 
1612  public static function ‪isOnSymmetricSide($parentUid, $parentConf, $childRec)
1613  {
1614  return ‪MathUtility::canBeInterpretedAsInteger($childRec['uid'])
1615  && $parentConf['symmetric_field']
1616  && $parentUid == $childRec[$parentConf['symmetric_field']];
1617  }
1618 
1627  protected function ‪completeOppositeUsageValues($tableName, array $referenceValues)
1628  {
1629  if (empty($this->MM_oppositeUsage[$tableName]) || count($this->MM_oppositeUsage[$tableName]) > 1) {
1630  return $referenceValues;
1631  }
1632 
1633  $fieldName = $this->MM_oppositeUsage[$tableName][0];
1634  if (empty(‪$GLOBALS['TCA'][$tableName]['columns'][$fieldName]['config'])) {
1635  return $referenceValues;
1636  }
1637 
1638  $configuration = ‪$GLOBALS['TCA'][$tableName]['columns'][$fieldName]['config'];
1639  if (!empty($configuration['MM_insert_fields'])) {
1640  $referenceValues = array_merge($configuration['MM_insert_fields'], $referenceValues);
1641  } elseif (!empty($configuration['MM_match_fields'])) {
1642  $referenceValues = array_merge($configuration['MM_match_fields'], $referenceValues);
1643  }
1644 
1645  return $referenceValues;
1646  }
1647 
1656  protected function ‪getLiveDefaultId($tableName, $id)
1657  {
1658  $liveDefaultId = ‪BackendUtility::getLiveVersionIdOfRecord($tableName, $id);
1659  if ($liveDefaultId === null) {
1660  $liveDefaultId = $id;
1661  }
1662  return (int)$liveDefaultId;
1663  }
1664 
1671  protected function ‪getResolver($tableName, array $ids, array $sortingStatement = null)
1672  {
1674  $resolver = GeneralUtility::makeInstance(
1675  PlainDataResolver::class,
1676  $tableName,
1677  $ids,
1678  $sortingStatement
1679  );
1680  $resolver->setWorkspaceId($this->‪getWorkspaceId());
1681  $resolver->setKeepDeletePlaceholder(true);
1682  $resolver->setKeepLiveIds($this->useLiveReferenceIds);
1683  return $resolver;
1684  }
1685 
1690  protected function ‪getConnectionForTableName(string $tableName)
1691  {
1692  return GeneralUtility::makeInstance(ConnectionPool::class)
1693  ->getConnectionForTable($tableName);
1694  }
1695 }
‪TYPO3\CMS\Core\Database\RelationHandler\$additionalWhere
‪array $additionalWhere
Definition: RelationHandler.php:66
‪TYPO3\CMS\Core\Database\Query\QueryHelper\parseOrderBy
‪static array array[] parseOrderBy(string $input)
Definition: QueryHelper.php:42
‪TYPO3\CMS\Core\Database\RelationHandler\purgeItemArrayHandler
‪bool purgeItemArrayHandler($purgeCallback)
Definition: RelationHandler.php:1394
‪TYPO3\CMS\Core\Database\RelationHandler\purgeItemArray
‪bool purgeItemArray($workspaceId=null)
Definition: RelationHandler.php:1353
‪TYPO3\CMS\Core\DataHandling\PlainDataResolver\processVersionOverlays
‪int[] processVersionOverlays(array $ids)
Definition: PlainDataResolver.php:157
‪TYPO3\CMS\Core\Database\RelationHandler\$MM_oppositeField
‪string $MM_oppositeField
Definition: RelationHandler.php:101
‪TYPO3\CMS\Core\Database\RelationHandler\purgeLiveVersionedIds
‪array purgeLiveVersionedIds($tableName, array $ids)
Definition: RelationHandler.php:1470
‪TYPO3\CMS\Core\DataHandling\PlainDataResolver
Definition: PlainDataResolver.php:32
‪TYPO3\CMS\Core\Database\RelationHandler\$useLiveParentIds
‪bool $useLiveParentIds
Definition: RelationHandler.php:171
‪TYPO3\CMS\Core\Database\RelationHandler\readList
‪readList($itemlist, array $configuration)
Definition: RelationHandler.php:361
‪TYPO3\CMS\Core\Utility\MathUtility\canBeInterpretedAsInteger
‪static bool canBeInterpretedAsInteger($var)
Definition: MathUtility.php:73
‪TYPO3\CMS\Core\Database\RelationHandler\$MM_hasUidField
‪bool $MM_hasUidField
Definition: RelationHandler.php:145
‪TYPO3\CMS\Core\Database\RelationHandler\sortList
‪sortList($sortby)
Definition: RelationHandler.php:442
‪TYPO3\CMS\Core\Database\RelationHandler\$MM_is_foreign
‪bool $MM_is_foreign
Definition: RelationHandler.php:95
‪TYPO3\CMS\Core\Database\RelationHandler\$MM_isMultiTableRelationship
‪string $MM_isMultiTableRelationship
Definition: RelationHandler.php:119
‪TYPO3\CMS\Core\Database\RelationHandler\purgeDeletePlaceholder
‪array purgeDeletePlaceholder($tableName, array $ids)
Definition: RelationHandler.php:1517
‪TYPO3\CMS\Core\Database\RelationHandler\$MM_table_where
‪string $MM_table_where
Definition: RelationHandler.php:157
‪TYPO3\CMS\Core\Database\RelationHandler
Definition: RelationHandler.php:32
‪TYPO3\CMS\Core\Database\RelationHandler\$secondTable
‪string $secondTable
Definition: RelationHandler.php:88
‪TYPO3\CMS\Core\Database\RelationHandler\$workspaceId
‪int $workspaceId
Definition: RelationHandler.php:179
‪TYPO3
‪TYPO3\CMS\Core\Database\RelationHandler\getWorkspaceId
‪int getWorkspaceId()
Definition: RelationHandler.php:196
‪TYPO3\CMS\Core\DataHandling\PlainDataResolver\setKeepMovePlaceholder
‪PlainDataResolver setKeepMovePlaceholder($keepMovePlaceholder)
Definition: PlainDataResolver.php:117
‪TYPO3\CMS\Core\DataHandling\PlainDataResolver\get
‪int[] get()
Definition: PlainDataResolver.php:126
‪TYPO3\CMS\Core\Database\Query\Restriction\QueryRestrictionContainerInterface\removeAll
‪QueryRestrictionContainerInterface removeAll()
‪TYPO3\CMS\Core\Database\RelationHandler\$currentTable
‪string $currentTable
Definition: RelationHandler.php:125
‪TYPO3\CMS\Core\Database\RelationHandler\getConnectionForTableName
‪Connection getConnectionForTableName(string $tableName)
Definition: RelationHandler.php:1662
‪TYPO3\CMS\Core\Database\RelationHandler\$MM_oppositeFieldConf
‪string $MM_oppositeFieldConf
Definition: RelationHandler.php:113
‪TYPO3\CMS\Core\Database\RelationHandler\$MM_insert_fields
‪array $MM_insert_fields
Definition: RelationHandler.php:151
‪TYPO3\CMS\Core\Database\Query\QueryBuilder\getRestrictions
‪QueryRestrictionContainerInterface getRestrictions()
Definition: QueryBuilder.php:89
‪TYPO3\CMS\Core\Database\RelationHandler\$tableArray
‪array $tableArray
Definition: RelationHandler.php:50
‪TYPO3\CMS\Core\Database\Query\QueryBuilder\update
‪QueryBuilder update(string $update, string $alias=null)
Definition: QueryBuilder.php:472
‪TYPO3\CMS\Core\Versioning\VersionState\DELETE_PLACEHOLDER
‪const DELETE_PLACEHOLDER
Definition: VersionState.php:54
‪TYPO3\CMS\Core\Database\RelationHandler\getFromDB
‪array getFromDB()
Definition: RelationHandler.php:1181
‪TYPO3\CMS\Core\Database\RelationHandler\$itemArray
‪array $itemArray
Definition: RelationHandler.php:56
‪TYPO3\CMS\Core\Database\RelationHandler\setUseLiveReferenceIds
‪setUseLiveReferenceIds($useLiveReferenceIds)
Definition: RelationHandler.php:350
‪$fields
‪$fields
Definition: pages.php:4
‪TYPO3\CMS\Core\Database\RelationHandler\isPurged
‪bool isPurged()
Definition: RelationHandler.php:219
‪TYPO3\CMS\Core\Database\Connection\update
‪int update($tableName, array $data, array $identifier, array $types=[])
Definition: Connection.php:287
‪TYPO3\CMS\Core\Database\RelationHandler\writeMM
‪writeMM($MM_tableName, $uid, $prependTableName=false)
Definition: RelationHandler.php:580
‪TYPO3\CMS\Core\Database\RelationHandler\convertItemArray
‪bool convertItemArray()
Definition: RelationHandler.php:1305
‪TYPO3\CMS\Backend\Utility\BackendUtility\isTableWorkspaceEnabled
‪static bool isTableWorkspaceEnabled($table)
Definition: BackendUtility.php:4493
‪TYPO3\CMS\Core\Type\Enumeration\cast
‪static static cast($value)
Definition: Enumeration.php:182
‪TYPO3\CMS\Core\Database\RelationHandler\remapMM
‪remapMM($MM_tableName, $uid, $newUid, $prependTableName=false)
Definition: RelationHandler.php:823
‪TYPO3\CMS\Core\Database\RelationHandler\$MM_oppositeUsage
‪array $MM_oppositeUsage
Definition: RelationHandler.php:163
‪TYPO3\CMS\Core\Database\Platform\PlatformInformation\getMaxBindParameters
‪static int getMaxBindParameters(AbstractPlatform $platform)
Definition: PlatformInformation.php:71
‪TYPO3\CMS\Core\Database\Query\QueryHelper
Definition: QueryHelper.php:30
‪TYPO3\CMS\Core\Database\RelationHandler\$results
‪array $results
Definition: RelationHandler.php:189
‪TYPO3\CMS\Core\Database\RelationHandler\purgeVersionedIds
‪array purgeVersionedIds($tableName, array $ids)
Definition: RelationHandler.php:1424
‪TYPO3\CMS\Core\Database\RelationHandler\completeOppositeUsageValues
‪array completeOppositeUsageValues($tableName, array $referenceValues)
Definition: RelationHandler.php:1599
‪TYPO3\CMS\Core\DataHandling\PlainDataResolver\setKeepDeletePlaceholder
‪PlainDataResolver setKeepDeletePlaceholder($keepDeletePlaceholder)
Definition: PlainDataResolver.php:105
‪TYPO3\CMS\Core\Database\RelationHandler\getValueArray
‪array getValueArray($prependTableName=false)
Definition: RelationHandler.php:1155
‪TYPO3\CMS\Core\Database\RelationHandler\getResolvedItemArray
‪array getResolvedItemArray()
Definition: RelationHandler.php:1243
‪TYPO3\CMS\Core\Database\RelationHandler\$dbPaths
‪array $dbPaths
Definition: RelationHandler.php:76
‪TYPO3\CMS\Core\Database\RelationHandler\$useLiveReferenceIds
‪bool $useLiveReferenceIds
Definition: RelationHandler.php:175
‪TYPO3\CMS\Core\Database\RelationHandler\start
‪start($itemlist, $tablelist, $MMtable='', $MMuid=0, $currentTable='', $conf=[])
Definition: RelationHandler.php:234
‪TYPO3\CMS\Core\Database\RelationHandler\getLiveDefaultId
‪int getLiveDefaultId($tableName, $id)
Definition: RelationHandler.php:1628
‪TYPO3\CMS\Core\Database\RelationHandler\writeForeignField
‪writeForeignField($conf, $parentUid, $updateToUid=0, $skipSorting=false)
Definition: RelationHandler.php:1020
‪TYPO3\CMS\Core\Database\RelationHandler\updateRefIndex
‪array updateRefIndex($table, $id)
Definition: RelationHandler.php:1281
‪TYPO3\CMS\Core\Database\RelationHandler\readMM
‪readMM($tableName, $uid)
Definition: RelationHandler.php:494
‪TYPO3\CMS\Core\Database\RelationHandler\countItems
‪mixed countItems($returnAsArray=true)
Definition: RelationHandler.php:1263
‪TYPO3\CMS\Core\Database\RelationHandler\readForeignField
‪readForeignField($uid, $conf)
Definition: RelationHandler.php:876
‪TYPO3\CMS\Backend\Utility\BackendUtility
Definition: BackendUtility.php:72
‪TYPO3\CMS\Core\Database\RelationHandler\$registerNonTableValues
‪bool $registerNonTableValues
Definition: RelationHandler.php:43
‪TYPO3\CMS\Backend\Utility\BackendUtility\getRecord
‪static array null getRecord($table, $uid, $fields=' *', $where='', $useDeleteClause=true)
Definition: BackendUtility.php:130
‪TYPO3\CMS\Core\Versioning\VersionState
Definition: VersionState.php:23
‪TYPO3\CMS\Core\Database\RelationHandler\$MM_oppositeTable
‪string $MM_oppositeTable
Definition: RelationHandler.php:107
‪TYPO3\CMS\Core\Database\Connection
Definition: Connection.php:31
‪TYPO3\CMS\Core\Versioning\VersionState\NEW_PLACEHOLDER_VERSION
‪const NEW_PLACEHOLDER_VERSION
Definition: VersionState.php:32
‪TYPO3\CMS\Backend\Utility\BackendUtility\getLiveVersionIdOfRecord
‪static int getLiveVersionIdOfRecord($table, $uid)
Definition: BackendUtility.php:4229
‪TYPO3\CMS\Core\Database\RelationHandler\setFetchAllFields
‪setFetchAllFields($allFields)
Definition: RelationHandler.php:324
‪TYPO3\CMS\Core\Database\Query\QueryHelper\stripLogicalOperatorPrefix
‪static string stripLogicalOperatorPrefix(string $constraint)
Definition: QueryHelper.php:163
‪$GLOBALS
‪$GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['adminpanel']['modules']
Definition: ext_localconf.php:5
‪TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction
Definition: DeletedRestriction.php:26
‪TYPO3\CMS\Core\Database\RelationHandler\setUseLiveParentIds
‪setUseLiveParentIds($useLiveParentIds)
Definition: RelationHandler.php:342
‪TYPO3\CMS\Core\Database\Platform\PlatformInformation
Definition: PlatformInformation.php:31
‪TYPO3\CMS\Core\Database\RelationHandler\$firstTable
‪string $firstTable
Definition: RelationHandler.php:82
‪TYPO3\CMS\Core\Database\RelationHandler\isOnSymmetricSide
‪static bool isOnSymmetricSide($parentUid, $parentConf, $childRec)
Definition: RelationHandler.php:1584
‪TYPO3\CMS\Core\Database\Query\QueryBuilder\set
‪QueryBuilder set(string $key, $value, bool $createNamedParameter=true)
Definition: QueryBuilder.php:613
‪TYPO3\CMS\Core\Database\RelationHandler\$nonTableArray
‪array $nonTableArray
Definition: RelationHandler.php:62
‪TYPO3\CMS\Core\Utility\MathUtility
Definition: MathUtility.php:21
‪TYPO3\CMS\Core\Database\Connection\createQueryBuilder
‪TYPO3 CMS Core Database Query QueryBuilder createQueryBuilder()
Definition: Connection.php:110
‪TYPO3\CMS\Core\Database\RelationHandler\getResolver
‪PlainDataResolver getResolver($tableName, array $ids, array $sortingStatement=null)
Definition: RelationHandler.php:1643
‪TYPO3\CMS\Core\Database\RelationHandler\$fetchAllFields
‪bool $fetchAllFields
Definition: RelationHandler.php:37
‪TYPO3\CMS\Core\Database\RelationHandler\setWorkspaceId
‪setWorkspaceId($workspaceId)
Definition: RelationHandler.php:209
‪TYPO3\CMS\Core\Database\Query\Restriction\QueryRestrictionContainerInterface\add
‪QueryRestrictionContainerInterface add(QueryRestrictionInterface $restriction)
‪TYPO3\CMS\Core\Utility\GeneralUtility
Definition: GeneralUtility.php:45
‪TYPO3\CMS\Core\Database\RelationHandler\$MM_match_fields
‪array $MM_match_fields
Definition: RelationHandler.php:139
‪TYPO3\CMS\Core\Database\RelationHandler\$checkIfDeleted
‪bool $checkIfDeleted
Definition: RelationHandler.php:72
‪TYPO3\CMS\Core\Database\RelationHandler\setUpdateReferenceIndex
‪setUpdateReferenceIndex($updateReferenceIndex)
Definition: RelationHandler.php:334
‪TYPO3\CMS\Core\Database\Query\QueryBuilder\where
‪QueryBuilder where(... $predicates)
Definition: QueryBuilder.php:630
‪TYPO3\CMS\Core\Database\RelationHandler\removeFromItemArray
‪removeFromItemArray($tableName, $id)
Definition: RelationHandler.php:1565
‪TYPO3\CMS\Core\Database\RelationHandler\$purged
‪bool $purged
Definition: RelationHandler.php:183
‪TYPO3\CMS\Core\Database\RelationHandler\processDeletePlaceholder
‪bool processDeletePlaceholder()
Definition: RelationHandler.php:1379
‪TYPO3\CMS\Core\Database\RelationHandler\$undeleteRecord
‪bool $undeleteRecord
Definition: RelationHandler.php:132
‪TYPO3\CMS\Core\Database\RelationHandler\$updateReferenceIndex
‪bool $updateReferenceIndex
Definition: RelationHandler.php:167
‪TYPO3\CMS\Core\Database
Definition: Connection.php:3