TYPO3 CMS  TYPO3_8-7
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 
128 
135 
142 
150 
157  public $MM_match_fields = [];
158 
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'];
267  $this->MM_oppositeField = $conf['MM_opposite_field'];
268  $this->MM_table_where = $conf['MM_table_where'];
269  $this->MM_hasUidField = $conf['MM_hasUidField'];
270  $this->MM_match_fields = is_array($conf['MM_match_fields']) ? $conf['MM_match_fields'] : [];
271  $this->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  if ($this->checkIfDeleted && $GLOBALS['TCA'][$tName]['ctrl']['delete']) {
305  $fieldN = $tName . '.' . $GLOBALS['TCA'][$tName]['ctrl']['delete'];
306  $this->additionalWhere[$tName] .= ' AND ' . $fieldN . '=0';
307  }
308  }
309  if (is_array($this->tableArray)) {
310  reset($this->tableArray);
311  } else {
312  // No tables
313  return;
314  }
315  // Set first and second tables:
316  // Is the first table
317  $this->firstTable = key($this->tableArray);
318  next($this->tableArray);
319  // If the second table is set and the ID number is less than zero (later)
320  // then the record is regarded to come from the second table...
321  $this->secondTable = key($this->tableArray);
322  // Now, populate the internal itemArray and tableArray arrays:
323  // If MM, then call this function to do that:
324  if ($MMtable) {
325  if ($MMuid) {
326  $this->readMM($MMtable, $MMuid);
327  $this->purgeItemArray();
328  } else {
329  // Revert to readList() for new records in order to load possible default values from $itemlist
330  $this->readList($itemlist, $conf);
331  $this->purgeItemArray();
332  }
333  } elseif ($MMuid && $conf['foreign_field']) {
334  // If not MM but foreign_field, the read the records by the foreign_field
335  $this->readForeignField($MMuid, $conf);
336  } else {
337  // If not MM, then explode the itemlist by "," and traverse the list:
338  $this->readList($itemlist, $conf);
339  // Do automatic default_sortby, if any
340  if ($conf['foreign_default_sortby']) {
341  $this->sortList($conf['foreign_default_sortby']);
342  }
343  }
344  }
345 
351  public function setFetchAllFields($allFields)
352  {
353  $this->fetchAllFields = (bool)$allFields;
354  }
355 
362  {
363  $this->updateReferenceIndex = (bool)$updateReferenceIndex;
364  }
365 
370  {
371  $this->useLiveParentIds = (bool)$useLiveParentIds;
372  }
373 
378  {
379  $this->useLiveReferenceIds = (bool)$useLiveReferenceIds;
380  }
381 
388  public function readList($itemlist, array $configuration)
389  {
390  if ((string)trim($itemlist) != '') {
391  $tempItemArray = GeneralUtility::trimExplode(',', $itemlist);
392  // Changed to trimExplode 31/3 04; HMENU special type "list" didn't work
393  // if there were spaces in the list... I suppose this is better overall...
394  foreach ($tempItemArray as $key => $val) {
395  // Will be set to "1" if the entry was a real table/id:
396  $isSet = 0;
397  // Extract table name and id. This is un the formular [tablename]_[id]
398  // where table name MIGHT contain "_", hence the reversion of the string!
399  $val = strrev($val);
400  $parts = explode('_', $val, 2);
401  $theID = strrev($parts[0]);
402  // Check that the id IS an integer:
404  // Get the table name: If a part of the exploded string, use that.
405  // Otherwise if the id number is LESS than zero, use the second table, otherwise the first table
406  $theTable = trim($parts[1])
407  ? strrev(trim($parts[1]))
408  : ($this->secondTable && $theID < 0 ? $this->secondTable : $this->firstTable);
409  // If the ID is not blank and the table name is among the names in the inputted tableList
410  if (
411  (string)$theID != ''
412  // allow the default language '0' for the special languages configuration
413  && ($theID || ($configuration['special'] ?? null) === 'languages')
414  && $theTable && isset($this->tableArray[$theTable])
415  ) {
416  // Get ID as the right value:
417  $theID = $this->secondTable ? abs((int)$theID) : (int)$theID;
418  // Register ID/table name in internal arrays:
419  $this->itemArray[$key]['id'] = $theID;
420  $this->itemArray[$key]['table'] = $theTable;
421  $this->tableArray[$theTable][] = $theID;
422  // Set update-flag:
423  $isSet = 1;
424  }
425  }
426  // If it turns out that the value from the list was NOT a valid reference to a table-record,
427  // then we might still set it as a NO_TABLE value:
428  if (!$isSet && $this->registerNonTableValues) {
429  $this->itemArray[$key]['id'] = $tempItemArray[$key];
430  $this->itemArray[$key]['table'] = '_NO_TABLE';
431  $this->nonTableArray[] = $tempItemArray[$key];
432  }
433  }
434 
435  // Skip if not dealing with IRRE in a CSV list on a workspace
436  if ($configuration['type'] !== 'inline' || empty($configuration['foreign_table']) || !empty($configuration['foreign_field'])
437  || !empty($configuration['MM']) || count($this->tableArray) !== 1 || empty($this->tableArray[$configuration['foreign_table']])
438  || $this->getWorkspaceId() === 0 || !BackendUtility::isTableWorkspaceEnabled($configuration['foreign_table'])) {
439  return;
440  }
441 
442  // Fetch live record data
443  if ($this->useLiveReferenceIds) {
444  foreach ($this->itemArray as &$item) {
445  $item['id'] = $this->getLiveDefaultId($item['table'], $item['id']);
446  }
447  } else {
448  // Directly overlay workspace data
449  $this->itemArray = [];
450  $foreignTable = $configuration['foreign_table'];
451  $ids = $this->getResolver($foreignTable, $this->tableArray[$foreignTable])->get();
452  foreach ($ids as $id) {
453  $this->itemArray[] = [
454  'id' => $id,
455  'table' => $foreignTable,
456  ];
457  }
458  }
459  }
460  }
461 
469  public function sortList($sortby)
470  {
471  // Sort directly without fetching additional data
472  if ($sortby === 'uid') {
473  usort(
474  $this->itemArray,
475  function ($a, $b) {
476  return $a['id'] < $b['id'] ? -1 : 1;
477  }
478  );
479  } elseif (count($this->tableArray) === 1) {
480  reset($this->tableArray);
481  $table = key($this->tableArray);
482  $connection = $this->getConnectionForTableName($table);
483  $maxBindParameters = PlatformInformation::getMaxBindParameters($connection->getDatabasePlatform());
484 
485  foreach (array_chunk(current($this->tableArray), $maxBindParameters - 10, true) as $chunk) {
486  if (empty($chunk)) {
487  continue;
488  }
489  $this->itemArray = [];
490  $this->tableArray = [];
491  $queryBuilder = $connection->createQueryBuilder();
492  $queryBuilder->getRestrictions()->removeAll();
493  $queryBuilder->select('uid')
494  ->from($table)
495  ->where(
496  $queryBuilder->expr()->in(
497  'uid',
498  $queryBuilder->createNamedParameter($chunk, Connection::PARAM_INT_ARRAY)
499  )
500  );
501  foreach (QueryHelper::parseOrderBy((string)$sortby) as $orderPair) {
502  list($fieldName, $order) = $orderPair;
503  $queryBuilder->addOrderBy($fieldName, $order);
504  }
505  $statement = $queryBuilder->execute();
506  while ($row = $statement->fetch()) {
507  $this->itemArray[] = ['id' => $row['uid'], 'table' => $table];
508  $this->tableArray[$table][] = $row['uid'];
509  }
510  }
511  }
512  }
513 
521  public function readMM($tableName, $uid)
522  {
523  $key = 0;
524  $theTable = null;
525  $queryBuilder = $this->getConnectionForTableName($tableName)
526  ->createQueryBuilder();
527  $queryBuilder->getRestrictions()->removeAll();
528  $queryBuilder->select('*')->from($tableName);
529  // In case of a reverse relation
530  if ($this->MM_is_foreign) {
531  $uidLocal_field = 'uid_foreign';
532  $uidForeign_field = 'uid_local';
533  $sorting_field = 'sorting_foreign';
534  if ($this->MM_isMultiTableRelationship) {
535  // Be backwards compatible! When allowing more than one table after
536  // having previously allowed only one table, this case applies.
537  if ($this->currentTable == $this->MM_isMultiTableRelationship) {
538  $expression = $queryBuilder->expr()->orX(
539  $queryBuilder->expr()->eq(
540  'tablenames',
541  $queryBuilder->createNamedParameter($this->currentTable, \PDO::PARAM_STR)
542  ),
543  $queryBuilder->expr()->eq(
544  'tablenames',
545  $queryBuilder->createNamedParameter('', \PDO::PARAM_STR)
546  )
547  );
548  } else {
549  $expression = $queryBuilder->expr()->eq(
550  'tablenames',
551  $queryBuilder->createNamedParameter($this->currentTable, \PDO::PARAM_STR)
552  );
553  }
554  $queryBuilder->andWhere($expression);
555  }
556  $theTable = $this->MM_oppositeTable;
557  } else {
558  // Default
559  $uidLocal_field = 'uid_local';
560  $uidForeign_field = 'uid_foreign';
561  $sorting_field = 'sorting';
562  }
563  if ($this->MM_table_where) {
564  $queryBuilder->andWhere(
565  QueryHelper::stripLogicalOperatorPrefix(str_replace('###THIS_UID###', (int)$uid, $this->MM_table_where))
566  );
567  }
568  foreach ($this->MM_match_fields as $field => $value) {
569  $queryBuilder->andWhere(
570  $queryBuilder->expr()->eq($field, $queryBuilder->createNamedParameter($value, \PDO::PARAM_STR))
571  );
572  }
573  $queryBuilder->andWhere(
574  $queryBuilder->expr()->eq(
575  $uidLocal_field,
576  $queryBuilder->createNamedParameter((int)$uid, \PDO::PARAM_INT)
577  )
578  );
579  $queryBuilder->orderBy($sorting_field);
580  $statement = $queryBuilder->execute();
581  while ($row = $statement->fetch()) {
582  // Default
583  if (!$this->MM_is_foreign) {
584  // If tablesnames columns exists and contain a name, then this value is the table, else it's the firstTable...
585  $theTable = $row['tablenames'] ?: $this->firstTable;
586  }
587  if (($row[$uidForeign_field] || $theTable === 'pages') && $theTable && isset($this->tableArray[$theTable])) {
588  $this->itemArray[$key]['id'] = $row[$uidForeign_field];
589  $this->itemArray[$key]['table'] = $theTable;
590  $this->tableArray[$theTable][] = $row[$uidForeign_field];
591  } elseif ($this->registerNonTableValues) {
592  $this->itemArray[$key]['id'] = $row[$uidForeign_field];
593  $this->itemArray[$key]['table'] = '_NO_TABLE';
594  $this->nonTableArray[] = $row[$uidForeign_field];
595  }
596  $key++;
597  }
598  }
599 
607  public function writeMM($MM_tableName, $uid, $prependTableName = false)
608  {
609  $connection = $this->getConnectionForTableName($MM_tableName);
610  $expressionBuilder = $connection->createQueryBuilder()->expr();
611 
612  // In case of a reverse relation
613  if ($this->MM_is_foreign) {
614  $uidLocal_field = 'uid_foreign';
615  $uidForeign_field = 'uid_local';
616  $sorting_field = 'sorting_foreign';
617  } else {
618  // default
619  $uidLocal_field = 'uid_local';
620  $uidForeign_field = 'uid_foreign';
621  $sorting_field = 'sorting';
622  }
623  // If there are tables...
624  $tableC = count($this->tableArray);
625  if ($tableC) {
626  // Boolean: does the field "tablename" need to be filled?
627  $prep = $tableC > 1 || $prependTableName || $this->MM_isMultiTableRelationship ? 1 : 0;
628  $c = 0;
629  $additionalWhere_tablenames = '';
630  if ($this->MM_is_foreign && $prep) {
631  $additionalWhere_tablenames = $expressionBuilder->eq(
632  'tablenames',
633  $expressionBuilder->literal($this->currentTable)
634  );
635  }
636  $additionalWhere = $expressionBuilder->andX();
637  // Add WHERE clause if configured
638  if ($this->MM_table_where) {
639  $additionalWhere->add(
641  str_replace('###THIS_UID###', (int)$uid, $this->MM_table_where)
642  )
643  );
644  }
645  // Select, update or delete only those relations that match the configured fields
646  foreach ($this->MM_match_fields as $field => $value) {
647  $additionalWhere->add($expressionBuilder->eq($field, $expressionBuilder->literal($value)));
648  }
649 
650  $queryBuilder = $connection->createQueryBuilder();
651  $queryBuilder->getRestrictions()->removeAll();
652  $queryBuilder->select($uidForeign_field)
653  ->from($MM_tableName)
654  ->where($queryBuilder->expr()->eq(
655  $uidLocal_field,
656  $queryBuilder->createNamedParameter($uid, \PDO::PARAM_INT)
657  ))
658  ->orderBy($sorting_field);
659 
660  if ($prep) {
661  $queryBuilder->addSelect('tablenames');
662  }
663  if ($this->MM_hasUidField) {
664  $queryBuilder->addSelect('uid');
665  }
666  if ($additionalWhere_tablenames) {
667  $queryBuilder->andWhere($additionalWhere_tablenames);
668  }
669  if ($additionalWhere->count()) {
670  $queryBuilder->andWhere($additionalWhere);
671  }
672 
673  $result = $queryBuilder->execute();
674  $oldMMs = [];
675  // This array is similar to $oldMMs but also holds the uid of the MM-records, if any (configured by MM_hasUidField).
676  // If the UID is present it will be used to update sorting and delete MM-records.
677  // This is necessary if the "multiple" feature is used for the MM relations.
678  // $oldMMs is still needed for the in_array() search used to look if an item from $this->itemArray is in $oldMMs
679  $oldMMs_inclUid = [];
680  while ($row = $result->fetch()) {
681  if (!$this->MM_is_foreign && $prep) {
682  $oldMMs[] = [$row['tablenames'], $row[$uidForeign_field]];
683  } else {
684  $oldMMs[] = $row[$uidForeign_field];
685  }
686  $oldMMs_inclUid[] = [$row['tablenames'], $row[$uidForeign_field], $row['uid']];
687  }
688  // For each item, insert it:
689  foreach ($this->itemArray as $val) {
690  $c++;
691  if ($prep || $val['table'] === '_NO_TABLE') {
692  // Insert current table if needed
693  if ($this->MM_is_foreign) {
694  $tablename = $this->currentTable;
695  } else {
696  $tablename = $val['table'];
697  }
698  } else {
699  $tablename = '';
700  }
701  if (!$this->MM_is_foreign && $prep) {
702  $item = [$val['table'], $val['id']];
703  } else {
704  $item = $val['id'];
705  }
706  if (in_array($item, $oldMMs)) {
707  $oldMMs_index = array_search($item, $oldMMs);
708  // In principle, selecting on the UID is all we need to do
709  // if a uid field is available since that is unique!
710  // But as long as it "doesn't hurt" we just add it to the where clause. It should all match up.
711  $queryBuilder = $connection->createQueryBuilder();
712  $queryBuilder->update($MM_tableName)
713  ->set($sorting_field, $c)
714  ->where(
715  $expressionBuilder->eq(
716  $uidLocal_field,
717  $queryBuilder->createNamedParameter($uid, \PDO::PARAM_INT)
718  ),
719  $expressionBuilder->eq(
720  $uidForeign_field,
721  $queryBuilder->createNamedParameter($val['id'], \PDO::PARAM_INT)
722  )
723  );
724 
725  if ($additionalWhere->count()) {
726  $queryBuilder->andWhere($additionalWhere);
727  }
728  if ($this->MM_hasUidField) {
729  $queryBuilder->andWhere(
730  $expressionBuilder->eq(
731  'uid',
732  $queryBuilder->createNamedParameter($oldMMs_inclUid[$oldMMs_index][2], \PDO::PARAM_INT)
733  )
734  );
735  }
736  if ($tablename) {
737  $queryBuilder->andWhere(
738  $expressionBuilder->eq(
739  'tablenames',
740  $queryBuilder->createNamedParameter($tablename, \PDO::PARAM_STR)
741  )
742  );
743  }
744 
745  $queryBuilder->execute();
746  // Remove the item from the $oldMMs array so after this
747  // foreach loop only the ones that need to be deleted are in there.
748  unset($oldMMs[$oldMMs_index]);
749  // Remove the item from the $oldMMs_inclUid array so after this
750  // foreach loop only the ones that need to be deleted are in there.
751  unset($oldMMs_inclUid[$oldMMs_index]);
752  } else {
753  $insertFields = $this->MM_insert_fields;
754  $insertFields[$uidLocal_field] = $uid;
755  $insertFields[$uidForeign_field] = $val['id'];
756  $insertFields[$sorting_field] = $c;
757  if ($tablename) {
758  $insertFields['tablenames'] = $tablename;
759  $insertFields = $this->completeOppositeUsageValues($tablename, $insertFields);
760  }
761  $connection->insert($MM_tableName, $insertFields);
762  if ($this->MM_is_foreign) {
763  $this->updateRefIndex($val['table'], $val['id']);
764  }
765  }
766  }
767  // Delete all not-used relations:
768  if (is_array($oldMMs) && !empty($oldMMs)) {
769  $queryBuilder = $connection->createQueryBuilder();
770  $removeClauses = $queryBuilder->expr()->orX();
771  $updateRefIndex_records = [];
772  foreach ($oldMMs as $oldMM_key => $mmItem) {
773  // If UID field is present, of course we need only use that for deleting.
774  if ($this->MM_hasUidField) {
775  $removeClauses->add($queryBuilder->expr()->eq(
776  'uid',
777  $queryBuilder->createNamedParameter($oldMMs_inclUid[$oldMM_key][2], \PDO::PARAM_INT)
778  ));
779  } else {
780  if (is_array($mmItem)) {
781  $removeClauses->add(
782  $queryBuilder->expr()->andX(
783  $queryBuilder->expr()->eq(
784  'tablenames',
785  $queryBuilder->createNamedParameter($mmItem[0], \PDO::PARAM_STR)
786  ),
787  $queryBuilder->expr()->eq(
788  $uidForeign_field,
789  $queryBuilder->createNamedParameter($mmItem[1], \PDO::PARAM_INT)
790  )
791  )
792  );
793  } else {
794  $removeClauses->add(
795  $queryBuilder->expr()->eq(
796  $uidForeign_field,
797  $queryBuilder->createNamedParameter($mmItem, \PDO::PARAM_INT)
798  )
799  );
800  }
801  }
802  if ($this->MM_is_foreign) {
803  if (is_array($mmItem)) {
804  $updateRefIndex_records[] = [$mmItem[0], $mmItem[1]];
805  } else {
806  $updateRefIndex_records[] = [$this->firstTable, $mmItem];
807  }
808  }
809  }
810 
811  $queryBuilder->delete($MM_tableName)
812  ->where(
813  $queryBuilder->expr()->eq(
814  $uidLocal_field,
815  $queryBuilder->createNamedParameter($uid, \PDO::PARAM_INT)
816  ),
817  $removeClauses
818  );
819 
820  if ($additionalWhere_tablenames) {
821  $queryBuilder->andWhere($additionalWhere_tablenames);
822  }
823  if ($additionalWhere->count()) {
824  $queryBuilder->andWhere($additionalWhere);
825  }
826 
827  $queryBuilder->execute();
828 
829  // Update ref index:
830  foreach ($updateRefIndex_records as $pair) {
831  $this->updateRefIndex($pair[0], $pair[1]);
832  }
833  }
834  // Update ref index; In DataHandler it is not certain that this will happen because
835  // if only the MM field is changed the record itself is not updated and so the ref-index is not either.
836  // This could also have been fixed in updateDB in DataHandler, however I decided to do it here ...
837  $this->updateRefIndex($this->currentTable, $uid);
838  }
839  }
840 
850  public function remapMM($MM_tableName, $uid, $newUid, $prependTableName = false)
851  {
852  // In case of a reverse relation
853  if ($this->MM_is_foreign) {
854  $uidLocal_field = 'uid_foreign';
855  } else {
856  // default
857  $uidLocal_field = 'uid_local';
858  }
859  // If there are tables...
860  $tableC = count($this->tableArray);
861  if ($tableC) {
862  $queryBuilder = $this->getConnectionForTableName($MM_tableName)
863  ->createQueryBuilder();
864  $queryBuilder->update($MM_tableName)
865  ->set($uidLocal_field, (int)$newUid)
866  ->where($queryBuilder->expr()->eq(
867  $uidLocal_field,
868  $queryBuilder->createNamedParameter($uid, \PDO::PARAM_INT)
869  ));
870  // Boolean: does the field "tablename" need to be filled?
871  $prep = $tableC > 1 || $prependTableName || $this->MM_isMultiTableRelationship;
872  if ($this->MM_is_foreign && $prep) {
873  $queryBuilder->andWhere(
874  $queryBuilder->expr()->eq(
875  'tablenames',
876  $queryBuilder->createNamedParameter($this->currentTable, \PDO::PARAM_STR)
877  )
878  );
879  }
880  // Add WHERE clause if configured
881  if ($this->MM_table_where) {
882  $queryBuilder->andWhere(
883  QueryHelper::stripLogicalOperatorPrefix(str_replace('###THIS_UID###', (int)$uid, $this->MM_table_where))
884  );
885  }
886  // Select, update or delete only those relations that match the configured fields
887  foreach ($this->MM_match_fields as $field => $value) {
888  $queryBuilder->andWhere(
889  $queryBuilder->expr()->eq($field, $queryBuilder->createNamedParameter($value, \PDO::PARAM_STR))
890  );
891  }
892  $queryBuilder->execute();
893  }
894  }
895 
903  public function readForeignField($uid, $conf)
904  {
905  if ($this->useLiveParentIds) {
906  $uid = $this->getLiveDefaultId($this->currentTable, $uid);
907  }
908 
909  $key = 0;
910  $uid = (int)$uid;
911  // skip further processing if $uid does not
912  // point to a valid parent record
913  if ($uid === 0) {
914  return;
915  }
916 
917  $foreign_table = $conf['foreign_table'];
918  $foreign_table_field = $conf['foreign_table_field'];
919  $useDeleteClause = !$this->undeleteRecord;
920  $foreign_match_fields = is_array($conf['foreign_match_fields']) ? $conf['foreign_match_fields'] : [];
921  $queryBuilder = $this->getConnectionForTableName($foreign_table)
922  ->createQueryBuilder();
923  $queryBuilder->getRestrictions()
924  ->removeAll();
925  // Use the deleteClause (e.g. "deleted=0") on this table
926  if ($useDeleteClause) {
927  $queryBuilder->getRestrictions()->add(GeneralUtility::makeInstance(DeletedRestriction::class));
928  }
929 
930  $queryBuilder->select('uid')
931  ->from($foreign_table);
932 
933  // Search for $uid in foreign_field, and if we have symmetric relations, do this also on symmetric_field
934  if ($conf['symmetric_field']) {
935  $queryBuilder->where(
936  $queryBuilder->expr()->orX(
937  $queryBuilder->expr()->eq(
938  $conf['foreign_field'],
939  $queryBuilder->createNamedParameter($uid, \PDO::PARAM_INT)
940  ),
941  $queryBuilder->expr()->eq(
942  $conf['symmetric_field'],
943  $queryBuilder->createNamedParameter($uid, \PDO::PARAM_INT)
944  )
945  )
946  );
947  } else {
948  $queryBuilder->where($queryBuilder->expr()->eq(
949  $conf['foreign_field'],
950  $queryBuilder->createNamedParameter($uid, \PDO::PARAM_INT)
951  ));
952  }
953  // If it's requested to look for the parent uid AND the parent table,
954  // add an additional SQL-WHERE clause
955  if ($foreign_table_field && $this->currentTable) {
956  $queryBuilder->andWhere(
957  $queryBuilder->expr()->eq(
958  $foreign_table_field,
959  $queryBuilder->createNamedParameter($this->currentTable, \PDO::PARAM_STR)
960  )
961  );
962  }
963  // Add additional where clause if foreign_match_fields are defined
964  foreach ($foreign_match_fields as $field => $value) {
965  $queryBuilder->andWhere(
966  $queryBuilder->expr()->eq($field, $queryBuilder->createNamedParameter($value, \PDO::PARAM_STR))
967  );
968  }
969  // Select children from the live(!) workspace only
970  if (BackendUtility::isTableWorkspaceEnabled($foreign_table)) {
971  $queryBuilder->andWhere(
972  $queryBuilder->expr()->in(
973  $foreign_table . '.t3ver_wsid',
974  $queryBuilder->createNamedParameter([0, (int)$this->getWorkspaceId()], Connection::PARAM_INT_ARRAY)
975  ),
976  $queryBuilder->expr()->neq(
977  $foreign_table . '.pid',
978  $queryBuilder->createNamedParameter(-1, \PDO::PARAM_INT)
979  )
980  );
981  }
982  // Get the correct sorting field
983  // Specific manual sortby for data handled by this field
984  $sortby = '';
985  if ($conf['foreign_sortby']) {
986  if ($conf['symmetric_sortby'] && $conf['symmetric_field']) {
987  // Sorting depends on, from which side of the relation we're looking at it
988  // This requires bypassing automatic quoting and setting of the default sort direction
989  // @TODO: Doctrine: generalize to standard SQL to guarantee database independency
990  $queryBuilder->add(
991  'orderBy',
992  'CASE
993  WHEN ' . $queryBuilder->expr()->eq($conf['foreign_field'], $uid) . '
994  THEN ' . $queryBuilder->quoteIdentifier($conf['foreign_sortby']) . '
995  ELSE ' . $queryBuilder->quoteIdentifier($conf['symmetric_sortby']) . '
996  END'
997  );
998  } else {
999  // Regular single-side behaviour
1000  $sortby = $conf['foreign_sortby'];
1001  }
1002  } elseif ($conf['foreign_default_sortby']) {
1003  // Specific default sortby for data handled by this field
1004  $sortby = $conf['foreign_default_sortby'];
1005  } elseif ($GLOBALS['TCA'][$foreign_table]['ctrl']['sortby']) {
1006  // Manual sortby for all table records
1007  $sortby = $GLOBALS['TCA'][$foreign_table]['ctrl']['sortby'];
1008  } elseif ($GLOBALS['TCA'][$foreign_table]['ctrl']['default_sortby']) {
1009  // Default sortby for all table records
1010  $sortby = $GLOBALS['TCA'][$foreign_table]['ctrl']['default_sortby'];
1011  }
1012 
1013  if (!empty($sortby)) {
1014  foreach (QueryHelper::parseOrderBy($sortby) as $orderPair) {
1015  list($fieldName, $sorting) = $orderPair;
1016  $queryBuilder->addOrderBy($fieldName, $sorting);
1017  }
1018  }
1019 
1020  // Get the rows from storage
1021  $rows = [];
1022  $result = $queryBuilder->execute();
1023  while ($row = $result->fetch()) {
1024  $rows[$row['uid']] = $row;
1025  }
1026  if (!empty($rows)) {
1027  // Retrieve the parsed and prepared ORDER BY configuration for the resolver
1028  $sortby = $queryBuilder->getQueryPart('orderBy');
1029  $ids = $this->getResolver($foreign_table, array_keys($rows), $sortby)->get();
1030  foreach ($ids as $id) {
1031  $this->itemArray[$key]['id'] = $id;
1032  $this->itemArray[$key]['table'] = $foreign_table;
1033  $this->tableArray[$foreign_table][] = $id;
1034  $key++;
1035  }
1036  }
1037  }
1038 
1047  public function writeForeignField($conf, $parentUid, $updateToUid = 0, $skipSorting = false)
1048  {
1049  if ($this->useLiveParentIds) {
1050  $parentUid = $this->getLiveDefaultId($this->currentTable, $parentUid);
1051  if (!empty($updateToUid)) {
1052  $updateToUid = $this->getLiveDefaultId($this->currentTable, $updateToUid);
1053  }
1054  }
1055 
1056  $c = 0;
1057  $foreign_table = $conf['foreign_table'];
1058  $foreign_field = $conf['foreign_field'];
1059  $symmetric_field = $conf['symmetric_field'];
1060  $foreign_table_field = $conf['foreign_table_field'];
1061  $foreign_match_fields = is_array($conf['foreign_match_fields']) ? $conf['foreign_match_fields'] : [];
1062  // If there are table items and we have a proper $parentUid
1063  if (MathUtility::canBeInterpretedAsInteger($parentUid) && !empty($this->tableArray)) {
1064  // If updateToUid is not a positive integer, set it to '0', so it will be ignored
1065  if (!(MathUtility::canBeInterpretedAsInteger($updateToUid) && $updateToUid > 0)) {
1066  $updateToUid = 0;
1067  }
1068  $considerWorkspaces = BackendUtility::isTableWorkspaceEnabled($foreign_table);
1069  $fields = 'uid,pid,' . $foreign_field;
1070  // Consider the symmetric field if defined:
1071  if ($symmetric_field) {
1072  $fields .= ',' . $symmetric_field;
1073  }
1074  // Consider workspaces if defined and currently used:
1075  if ($considerWorkspaces) {
1076  $fields .= ',t3ver_wsid,t3ver_state,t3ver_oid';
1077  }
1078  // Update all items
1079  foreach ($this->itemArray as $val) {
1080  $uid = $val['id'];
1081  $table = $val['table'];
1082  $row = [];
1083  // Fetch the current (not overwritten) relation record if we should handle symmetric relations
1084  if ($symmetric_field || $considerWorkspaces) {
1085  $row = BackendUtility::getRecord($table, $uid, $fields, '', true);
1086  if (empty($row)) {
1087  continue;
1088  }
1089  }
1090  $isOnSymmetricSide = false;
1091  if ($symmetric_field) {
1092  $isOnSymmetricSide = self::isOnSymmetricSide($parentUid, $conf, $row);
1093  }
1094  $updateValues = $foreign_match_fields;
1095  // No update to the uid is requested, so this is the normal behaviour
1096  // just update the fields and care about sorting
1097  if (!$updateToUid) {
1098  // Always add the pointer to the parent uid
1099  if ($isOnSymmetricSide) {
1100  $updateValues[$symmetric_field] = $parentUid;
1101  } else {
1102  $updateValues[$foreign_field] = $parentUid;
1103  }
1104  // If it is configured in TCA also to store the parent table in the child record, just do it
1105  if ($foreign_table_field && $this->currentTable) {
1106  $updateValues[$foreign_table_field] = $this->currentTable;
1107  }
1108  // Update sorting columns if not to be skipped
1109  if (!$skipSorting) {
1110  // Get the correct sorting field
1111  // Specific manual sortby for data handled by this field
1112  $sortby = '';
1113  if ($conf['foreign_sortby']) {
1114  $sortby = $conf['foreign_sortby'];
1115  } elseif ($GLOBALS['TCA'][$foreign_table]['ctrl']['sortby']) {
1116  // manual sortby for all table records
1117  $sortby = $GLOBALS['TCA'][$foreign_table]['ctrl']['sortby'];
1118  }
1119  // Apply sorting on the symmetric side
1120  // (it depends on who created the relation, so what uid is in the symmetric_field):
1121  if ($isOnSymmetricSide && isset($conf['symmetric_sortby']) && $conf['symmetric_sortby']) {
1122  $sortby = $conf['symmetric_sortby'];
1123  } else {
1124  $tempSortBy = [];
1125  foreach (QueryHelper::parseOrderBy($sortby) as $orderPair) {
1126  list($fieldName, $order) = $orderPair;
1127  if ($order !== null) {
1128  $tempSortBy[] = implode(' ', $orderPair);
1129  } else {
1130  $tempSortBy[] = $fieldName;
1131  }
1132  }
1133  $sortby = implode(',', $tempSortBy);
1134  }
1135  if ($sortby) {
1136  $updateValues[$sortby] = ++$c;
1137  }
1138  }
1139  } else {
1140  if ($isOnSymmetricSide) {
1141  $updateValues[$symmetric_field] = $updateToUid;
1142  } else {
1143  $updateValues[$foreign_field] = $updateToUid;
1144  }
1145  }
1146  // Update accordant fields in the database:
1147  if (!empty($updateValues)) {
1148  // Update tstamp if any foreign field value has changed
1149  if (!empty($GLOBALS['TCA'][$table]['ctrl']['tstamp'])) {
1150  $updateValues[$GLOBALS['TCA'][$table]['ctrl']['tstamp']] = $GLOBALS['EXEC_TIME'];
1151  }
1152  $this->getConnectionForTableName($table)
1153  ->update(
1154  $table,
1155  $updateValues,
1156  ['uid' => (int)$uid]
1157  );
1158  $this->updateRefIndex($table, $uid);
1159  }
1160  // Update accordant fields in the database for workspaces overlays/placeholders:
1161  if ($considerWorkspaces) {
1162  // It's the specific versioned record -> update placeholder (if any)
1163  if (!empty($row['t3ver_oid']) && VersionState::cast($row['t3ver_state'])->equals(VersionState::NEW_PLACEHOLDER_VERSION)) {
1164  $this->getConnectionForTableName($table)
1165  ->update(
1166  $table,
1167  $updateValues,
1168  ['uid' => (int)$row['t3ver_oid']]
1169  );
1170  }
1171  }
1172  }
1173  }
1174  }
1175 
1182  public function getValueArray($prependTableName = false)
1183  {
1184  // INIT:
1185  $valueArray = [];
1186  $tableC = count($this->tableArray);
1187  // If there are tables in the table array:
1188  if ($tableC) {
1189  // If there are more than ONE table in the table array, then always prepend table names:
1190  $prep = $tableC > 1 || $prependTableName;
1191  // Traverse the array of items:
1192  foreach ($this->itemArray as $val) {
1193  $valueArray[] = ($prep && $val['table'] !== '_NO_TABLE' ? $val['table'] . '_' : '') . $val['id'];
1194  }
1195  }
1196  // Return the array
1197  return $valueArray;
1198  }
1199 
1208  public function getFromDB()
1209  {
1210  // Traverses the tables listed:
1211  foreach ($this->tableArray as $table => $ids) {
1212  if (is_array($ids) && !empty($ids)) {
1213  $connection = $this->getConnectionForTableName($table);
1214  $maxBindParameters = PlatformInformation::getMaxBindParameters($connection->getDatabasePlatform());
1215 
1216  foreach (array_chunk($ids, $maxBindParameters - 10, true) as $chunk) {
1217  if ($this->fetchAllFields) {
1218  $fields = '*';
1219  } else {
1220  $fields = 'uid,pid';
1221  if ($GLOBALS['TCA'][$table]['ctrl']['label']) {
1222  // Titel
1223  $fields .= ',' . $GLOBALS['TCA'][$table]['ctrl']['label'];
1224  }
1225  if ($GLOBALS['TCA'][$table]['ctrl']['label_alt']) {
1226  // Alternative Title-Fields
1227  $fields .= ',' . $GLOBALS['TCA'][$table]['ctrl']['label_alt'];
1228  }
1229  if ($GLOBALS['TCA'][$table]['ctrl']['thumbnail']) {
1230  // Thumbnail
1231  $fields .= ',' . $GLOBALS['TCA'][$table]['ctrl']['thumbnail'];
1232  }
1233  }
1234  $queryBuilder = $connection->createQueryBuilder();
1235  $queryBuilder->getRestrictions()->removeAll();
1236  $queryBuilder->select(...(GeneralUtility::trimExplode(',', $fields, true)))
1237  ->from($table)
1238  ->where($queryBuilder->expr()->in(
1239  'uid',
1240  $queryBuilder->createNamedParameter($chunk, Connection::PARAM_INT_ARRAY)
1241  ));
1242  if ($this->additionalWhere[$table]) {
1243  $queryBuilder->andWhere(
1244  QueryHelper::stripLogicalOperatorPrefix($this->additionalWhere[$table])
1245  );
1246  }
1247  $statement = $queryBuilder->execute();
1248  while ($row = $statement->fetch()) {
1249  $this->results[$table][$row['uid']] = $row;
1250  }
1251  }
1252  }
1253  }
1254  return $this->results;
1255  }
1256 
1263  public function readyForInterface()
1264  {
1266  if (!is_array($this->itemArray)) {
1267  return false;
1268  }
1269  $output = [];
1270  $titleLen = (int)$GLOBALS['BE_USER']->uc['titleLen'];
1271  foreach ($this->itemArray as $val) {
1272  $theRow = $this->results[$val['table']][$val['id']];
1273  if ($theRow && is_array($GLOBALS['TCA'][$val['table']])) {
1274  $label = GeneralUtility::fixed_lgd_cs(strip_tags(
1275  BackendUtility::getRecordTitle($val['table'], $theRow)
1276  ), $titleLen);
1277  $label = $label ? $label : '[...]';
1278  $output[] = str_replace(',', '', $val['table'] . '_' . $val['id'] . '|' . rawurlencode($label));
1279  }
1280  }
1281  return implode(',', $output);
1282  }
1283 
1297  public function getResolvedItemArray(): array
1298  {
1299  $itemArray = [];
1300  foreach ($this->itemArray as $item) {
1301  if (isset($this->results[$item['table']][$item['id']])) {
1302  $itemArray[] = [
1303  'table' => $item['table'],
1304  'uid' => $item['id'],
1305  ];
1306  }
1307  }
1308  return $itemArray;
1309  }
1310 
1317  public function countItems($returnAsArray = true)
1318  {
1319  $count = count($this->itemArray);
1320  if ($returnAsArray) {
1321  $count = [$count];
1322  }
1323  return $count;
1324  }
1325 
1335  public function updateRefIndex($table, $id)
1336  {
1337  $statisticsArray = [];
1338  if ($this->updateReferenceIndex) {
1340  $refIndexObj = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Database\ReferenceIndex::class);
1342  $refIndexObj->setWorkspaceId($this->getWorkspaceId());
1343  }
1344  $statisticsArray = $refIndexObj->updateRefIndexTable($table, $id);
1345  }
1346  return $statisticsArray;
1347  }
1348 
1358  public function convertItemArray()
1359  {
1360  $hasBeenConverted = false;
1361 
1362  // conversion is only required in a workspace context
1363  // (the case that version ids are submitted in a live context are rare)
1364  if ($this->getWorkspaceId() === 0) {
1365  return $hasBeenConverted;
1366  }
1367 
1368  foreach ($this->tableArray as $tableName => $ids) {
1369  if (empty($ids) || !BackendUtility::isTableWorkspaceEnabled($tableName)) {
1370  continue;
1371  }
1372 
1373  // convert live ids to version ids if available
1374  $convertedIds = $this->getResolver($tableName, $ids)
1375  ->setKeepDeletePlaceholder(false)
1376  ->setKeepMovePlaceholder(false)
1377  ->processVersionOverlays($ids);
1378  foreach ($this->itemArray as $index => $item) {
1379  if ($item['table'] !== $tableName) {
1380  continue;
1381  }
1382  $currentItemId = $item['id'];
1383  if (
1384  !isset($convertedIds[$currentItemId])
1385  || $currentItemId === $convertedIds[$currentItemId]
1386  ) {
1387  continue;
1388  }
1389  // adjust local item to use resolved version id
1390  $this->itemArray[$index]['id'] = $convertedIds[$currentItemId];
1391  $hasBeenConverted = true;
1392  }
1393  // update per-table reference for ids
1394  if ($hasBeenConverted) {
1395  $this->tableArray[$tableName] = array_values($convertedIds);
1396  }
1397  }
1398 
1399  return $hasBeenConverted;
1400  }
1401 
1406  public function purgeItemArray($workspaceId = null)
1407  {
1408  if ($workspaceId === null) {
1409  $workspaceId = $this->getWorkspaceId();
1410  } else {
1411  $workspaceId = (int)$workspaceId;
1412  }
1413 
1414  // Ensure, only live relations are in the items Array
1415  if ($workspaceId === 0) {
1416  $purgeCallback = 'purgeVersionedIds';
1417  } else {
1418  // Otherwise, ensure that live relations are purged if version exists
1419  $purgeCallback = 'purgeLiveVersionedIds';
1420  }
1421 
1422  $itemArrayHasBeenPurged = $this->purgeItemArrayHandler($purgeCallback);
1423  $this->purged = ($this->purged || $itemArrayHasBeenPurged);
1424  return $itemArrayHasBeenPurged;
1425  }
1426 
1432  public function processDeletePlaceholder()
1433  {
1434  if (!$this->useLiveReferenceIds || $this->getWorkspaceId() === 0) {
1435  return false;
1436  }
1437 
1438  return $this->purgeItemArrayHandler('purgeDeletePlaceholder');
1439  }
1440 
1447  protected function purgeItemArrayHandler($purgeCallback)
1448  {
1449  $itemArrayHasBeenPurged = false;
1450 
1451  foreach ($this->tableArray as $itemTableName => $itemIds) {
1452  if (empty($itemIds) || !BackendUtility::isTableWorkspaceEnabled($itemTableName)) {
1453  continue;
1454  }
1455 
1456  $purgedItemIds = call_user_func([$this, $purgeCallback], $itemTableName, $itemIds);
1457  $removedItemIds = array_diff($itemIds, $purgedItemIds);
1458  foreach ($removedItemIds as $removedItemId) {
1459  $this->removeFromItemArray($itemTableName, $removedItemId);
1460  }
1461  $this->tableArray[$itemTableName] = $purgedItemIds;
1462  if (!empty($removedItemIds)) {
1463  $itemArrayHasBeenPurged = true;
1464  }
1465  }
1466 
1467  return $itemArrayHasBeenPurged;
1468  }
1469 
1477  protected function purgeVersionedIds($tableName, array $ids)
1478  {
1479  $ids = array_combine($ids, $ids);
1480  $connection = $this->getConnectionForTableName($tableName);
1481  $maxBindParameters = PlatformInformation::getMaxBindParameters($connection->getDatabasePlatform());
1482 
1483  foreach (array_chunk($ids, $maxBindParameters - 10, true) as $chunk) {
1484  $queryBuilder = $connection->createQueryBuilder();
1485  $queryBuilder->getRestrictions()->removeAll();
1486  $result = $queryBuilder->select('uid', 't3ver_oid', 't3ver_state')
1487  ->from($tableName)
1488  ->where(
1489  $queryBuilder->expr()->eq(
1490  'pid',
1491  $queryBuilder->createNamedParameter(-1, \PDO::PARAM_INT)
1492  ),
1493  $queryBuilder->expr()->in(
1494  't3ver_oid',
1495  $queryBuilder->createNamedParameter($chunk, Connection::PARAM_INT_ARRAY)
1496  ),
1497  $queryBuilder->expr()->neq(
1498  't3ver_wsid',
1499  $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT)
1500  )
1501  )
1502  ->orderBy('t3ver_state', 'DESC')
1503  ->execute();
1504 
1505  while ($version = $result->fetch()) {
1506  $versionId = $version['uid'];
1507  if (isset($ids[$versionId])) {
1508  unset($ids[$versionId]);
1509  }
1510  }
1511  }
1512 
1513  return array_values($ids);
1514  }
1515 
1523  protected function purgeLiveVersionedIds($tableName, array $ids)
1524  {
1525  $ids = array_combine($ids, $ids);
1526  $connection = $this->getConnectionForTableName($tableName);
1527  $maxBindParameters = PlatformInformation::getMaxBindParameters($connection->getDatabasePlatform());
1528 
1529  foreach (array_chunk($ids, $maxBindParameters - 10, true) as $chunk) {
1530  $queryBuilder = $connection->createQueryBuilder();
1531  $queryBuilder->getRestrictions()->removeAll();
1532  $result = $queryBuilder->select('uid', 't3ver_oid', 't3ver_state')
1533  ->from($tableName)
1534  ->where(
1535  $queryBuilder->expr()->eq(
1536  'pid',
1537  $queryBuilder->createNamedParameter(-1, \PDO::PARAM_INT)
1538  ),
1539  $queryBuilder->expr()->in(
1540  't3ver_oid',
1541  $queryBuilder->createNamedParameter($chunk, Connection::PARAM_INT_ARRAY)
1542  ),
1543  $queryBuilder->expr()->neq(
1544  't3ver_wsid',
1545  $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT)
1546  )
1547  )
1548  ->orderBy('t3ver_state', 'DESC')
1549  ->execute();
1550 
1551  while ($version = $result->fetch()) {
1552  $versionId = $version['uid'];
1553  $liveId = $version['t3ver_oid'];
1554  if (isset($ids[$liveId]) && isset($ids[$versionId])) {
1555  unset($ids[$liveId]);
1556  }
1557  }
1558  }
1559 
1560  return array_values($ids);
1561  }
1562 
1570  protected function purgeDeletePlaceholder($tableName, array $ids)
1571  {
1572  $ids = array_combine($ids, $ids);
1573  $connection = $this->getConnectionForTableName($tableName);
1574  $maxBindParameters = PlatformInformation::getMaxBindParameters($connection->getDatabasePlatform());
1575 
1576  foreach (array_chunk($ids, $maxBindParameters - 10, true) as $chunk) {
1577  $queryBuilder = $connection->createQueryBuilder();
1578  $queryBuilder->getRestrictions()->removeAll();
1579  $result = $queryBuilder->select('uid', 't3ver_oid', 't3ver_state')
1580  ->from($tableName)
1581  ->where(
1582  $queryBuilder->expr()->eq(
1583  'pid',
1584  $queryBuilder->createNamedParameter(-1, \PDO::PARAM_INT)
1585  ),
1586  $queryBuilder->expr()->in(
1587  't3ver_oid',
1588  $queryBuilder->createNamedParameter($chunk, Connection::PARAM_INT_ARRAY)
1589  ),
1590  $queryBuilder->expr()->eq(
1591  't3ver_wsid',
1592  $queryBuilder->createNamedParameter(
1593  $this->getWorkspaceId(),
1594  \PDO::PARAM_INT
1595  )
1596  ),
1597  $queryBuilder->expr()->eq(
1598  't3ver_state',
1599  $queryBuilder->createNamedParameter(
1601  \PDO::PARAM_INT
1602  )
1603  )
1604  )
1605  ->execute();
1606 
1607  while ($version = $result->fetch()) {
1608  $liveId = $version['t3ver_oid'];
1609  if (isset($ids[$liveId])) {
1610  unset($ids[$liveId]);
1611  }
1612  }
1613  }
1614 
1615  return array_values($ids);
1616  }
1617 
1618  protected function removeFromItemArray($tableName, $id)
1619  {
1620  foreach ($this->itemArray as $index => $item) {
1621  if ($item['table'] === $tableName && (string)$item['id'] === (string)$id) {
1622  unset($this->itemArray[$index]);
1623  return true;
1624  }
1625  }
1626  return false;
1627  }
1628 
1637  public static function isOnSymmetricSide($parentUid, $parentConf, $childRec)
1638  {
1639  return MathUtility::canBeInterpretedAsInteger($childRec['uid'])
1640  && $parentConf['symmetric_field']
1641  && $parentUid == $childRec[$parentConf['symmetric_field']];
1642  }
1643 
1652  protected function completeOppositeUsageValues($tableName, array $referenceValues)
1653  {
1654  if (empty($this->MM_oppositeUsage[$tableName]) || count($this->MM_oppositeUsage[$tableName]) > 1) {
1655  return $referenceValues;
1656  }
1657 
1658  $fieldName = $this->MM_oppositeUsage[$tableName][0];
1659  if (empty($GLOBALS['TCA'][$tableName]['columns'][$fieldName]['config'])) {
1660  return $referenceValues;
1661  }
1662 
1663  $configuration = $GLOBALS['TCA'][$tableName]['columns'][$fieldName]['config'];
1664  if (!empty($configuration['MM_insert_fields'])) {
1665  $referenceValues = array_merge($configuration['MM_insert_fields'], $referenceValues);
1666  } elseif (!empty($configuration['MM_match_fields'])) {
1667  $referenceValues = array_merge($configuration['MM_match_fields'], $referenceValues);
1668  }
1669 
1670  return $referenceValues;
1671  }
1672 
1681  protected function getLiveDefaultId($tableName, $id)
1682  {
1683  $liveDefaultId = BackendUtility::getLiveVersionIdOfRecord($tableName, $id);
1684  if ($liveDefaultId === null) {
1685  $liveDefaultId = $id;
1686  }
1687  return (int)$liveDefaultId;
1688  }
1689 
1696  protected function getResolver($tableName, array $ids, array $sortingStatement = null)
1697  {
1699  $resolver = GeneralUtility::makeInstance(
1700  PlainDataResolver::class,
1701  $tableName,
1702  $ids,
1703  $sortingStatement
1704  );
1705  $resolver->setWorkspaceId($this->getWorkspaceId());
1706  $resolver->setKeepDeletePlaceholder(true);
1707  $resolver->setKeepLiveIds($this->useLiveReferenceIds);
1708  return $resolver;
1709  }
1710 
1715  protected function getConnectionForTableName(string $tableName)
1716  {
1717  return GeneralUtility::makeInstance(ConnectionPool::class)
1718  ->getConnectionForTable($tableName);
1719  }
1720 }
completeOppositeUsageValues($tableName, array $referenceValues)
writeMM($MM_tableName, $uid, $prependTableName=false)
setUpdateReferenceIndex($updateReferenceIndex)
static trimExplode($delim, $string, $removeEmptyValues=false, $limit=0)
static makeInstance($className,... $constructorArguments)
$fields
Definition: pages.php:4
static isOnSymmetricSide($parentUid, $parentConf, $childRec)
writeForeignField($conf, $parentUid, $updateToUid=0, $skipSorting=false)
static getMaxBindParameters(AbstractPlatform $platform)
readList($itemlist, array $configuration)
purgeLiveVersionedIds($tableName, array $ids)
static getRecordTitle($table, $row, $prep=false, $forceResult=true)
purgeDeletePlaceholder($tableName, array $ids)
static stripLogicalOperatorPrefix(string $constraint)
static fixed_lgd_cs($string, $chars, $appendString='...')
static getRecord($table, $uid, $fields=' *', $where='', $useDeleteClause=true)
if(TYPO3_MODE==='BE') $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tsfebeuserauth.php']['frontendEditingController']['default']
start($itemlist, $tablelist, $MMtable='', $MMuid=0, $currentTable='', $conf=[])
remapMM($MM_tableName, $uid, $newUid, $prependTableName=false)