‪TYPO3CMS  11.5
DataMapProcessor.php
Go to the documentation of this file.
1 <?php
2 
3 /*
4  * This file is part of the TYPO3 CMS project.
5  *
6  * It is free software; you can redistribute it and/or modify it under
7  * the terms of the GNU General Public License, either version 2
8  * of the License, or any later version.
9  *
10  * For the full copyright and license information, please read the
11  * LICENSE.txt file that was distributed with this source code.
12  *
13  * The TYPO3 project - inspiring people to share!
14  */
15 
17 
18 use TYPO3\CMS\Backend\Utility\BackendUtility;
36 
57 {
61  protected ‪$allDataMap = [];
62 
66  protected $modifiedDataMap = [];
67 
71  protected $sanitizationMap = [];
72 
76  protected $backendUser;
77 
81  protected $referenceIndexUpdater;
82 
86  protected $allItems = [];
87 
91  protected $nextItems = [];
92 
101  public static function instance(
102  array $dataMap,
103  ‪BackendUserAuthentication $backendUser,
104  ‪ReferenceIndexUpdater $referenceIndexUpdater = null
105  ) {
106  return GeneralUtility::makeInstance(
107  static::class,
108  $dataMap,
109  $backendUser,
110  $referenceIndexUpdater
111  );
112  }
113 
119  public function __construct(
120  array $dataMap,
121  ‪BackendUserAuthentication $backendUser,
122  ‪ReferenceIndexUpdater $referenceIndexUpdater = null
123  ) {
124  $this->allDataMap = $dataMap;
125  $this->‪modifiedDataMap = $dataMap;
126  $this->‪backendUser = $backendUser;
127  if ($referenceIndexUpdater === null) {
128  $referenceIndexUpdater = GeneralUtility::makeInstance(ReferenceIndexUpdater::class);
129  }
130  $this->‪referenceIndexUpdater = $referenceIndexUpdater;
131  }
132 
139  public function ‪process()
140  {
141  $iterations = 0;
142 
143  while (!empty($this->‪modifiedDataMap)) {
144  $this->nextItems = [];
145  foreach ($this->‪modifiedDataMap as $tableName => $idValues) {
146  $this->‪collectItems($tableName, $idValues);
147  }
148 
149  $this->‪modifiedDataMap = [];
150  if (empty($this->nextItems)) {
151  break;
152  }
153 
154  if ($iterations++ === 0) {
155  $this->‪sanitize($this->allItems);
156  }
157  $this->‪enrich($this->nextItems);
158  }
159 
160  $this->allDataMap = $this->‪purgeDataMap($this->allDataMap);
161  return ‪$this->allDataMap;
162  }
163 
170  protected function ‪purgeDataMap(array $dataMap): array
171  {
172  foreach ($dataMap as $tableName => $idValues) {
173  foreach ($idValues as $id => $values) {
174  if (empty($values)) {
175  unset($dataMap[$tableName][$id]);
176  }
177  }
178  if (empty($dataMap[$tableName])) {
179  unset($dataMap[$tableName]);
180  }
181  }
182  return $dataMap;
183  }
184 
191  protected function ‪collectItems(string $tableName, array $idValues)
192  {
193  if (!$this->‪isApplicable($tableName)) {
194  return;
195  }
196 
197  $fieldNames = [
198  'uid' => 'uid',
199  'l10n_state' => 'l10n_state',
200  'language' => ‪$GLOBALS['TCA'][$tableName]['ctrl']['languageField'],
201  'parent' => ‪$GLOBALS['TCA'][$tableName]['ctrl']['transOrigPointerField'],
202  ];
203  if (!empty(‪$GLOBALS['TCA'][$tableName]['ctrl']['translationSource'])) {
204  $fieldNames['source'] = ‪$GLOBALS['TCA'][$tableName]['ctrl']['translationSource'];
205  }
206 
207  $translationValues = $this->‪fetchTranslationValues(
208  $tableName,
209  $fieldNames,
210  $this->‪filterNewItemIds(
211  $tableName,
212  $this->‪filterNumericIds(array_keys($idValues))
213  )
214  );
215 
216  $dependencies = $this->‪fetchDependencies(
217  $tableName,
218  $this->‪filterNewItemIds($tableName, array_keys($idValues))
219  );
220 
221  foreach ($idValues as $id => $values) {
222  $item = $this->‪findItem($tableName, $id);
223  // build item if it has not been created in a previous iteration
224  if ($item === null) {
225  $recordValues = $translationValues[$id] ?? [];
226  $item = ‪DataMapItem::build(
227  $tableName,
228  $id,
229  $values,
230  $recordValues,
231  $fieldNames
232  );
233 
234  // elements using "all language" cannot be localized
235  if ($item->getLanguage() === -1) {
236  unset($item);
237  continue;
238  }
239  // must be any kind of localization and in connected mode
240  if ($item->getLanguage() > 0 && empty($item->getParent())) {
241  unset($item);
242  continue;
243  }
244  // add dependencies
245  if (!empty($dependencies[$id])) {
246  $item->setDependencies($dependencies[$id]);
247  }
248  }
249  // add item to $this->allItems and $this->nextItems
250  $this->‪addNextItem($item);
251  }
252  }
253 
260  protected function ‪sanitize(array $items)
261  {
262  foreach (['directChild', 'grandChild'] as $type) {
263  foreach ($this->‪filterItemsByType($type, $items) as $item) {
264  $this->‪sanitizeTranslationItem($item);
265  }
266  }
267  }
268 
274  protected function ‪enrich(array $items)
275  {
276  foreach (['directChild', 'grandChild'] as $type) {
277  foreach ($this->‪filterItemsByType($type, $items) as $item) {
278  foreach ($item->getApplicableScopes() as $scope) {
279  $fromId = $item->getIdForScope($scope);
280  $fieldNames = $this->‪getFieldNamesForItemScope($item, $scope, !$item->isNew());
281  $this->‪synchronizeTranslationItem($item, $fieldNames, $fromId);
282  }
283  $this->‪populateTranslationItem($item);
284  $this->‪finishTranslationItem($item);
285  }
286  }
287  foreach ($this->‪filterItemsByType('parent', $items) as $item) {
288  $this->‪populateTranslationItem($item);
289  }
290  }
291 
299  protected function ‪sanitizeTranslationItem(DataMapItem $item)
300  {
301  $fieldNames = [];
302  foreach ($item->getApplicableScopes() as $scope) {
303  $fieldNames = array_merge(
304  $fieldNames,
305  $this->‪getFieldNamesForItemScope($item, $scope, false)
306  );
307  }
308 
309  $fieldNameMap = array_combine($fieldNames, $fieldNames) ?: [];
310  // separate fields, that are submitted in data-map, but not defined as custom
311  $this->sanitizationMap[$item->getTableName()][$item->getId()] = array_intersect_key(
312  $this->allDataMap[$item->getTableName()][$item->getId()],
313  $fieldNameMap
314  );
315  // remove fields, that are submitted in data-map, but not defined as custom
316  $this->allDataMap[$item->getTableName()][$item->getId()] = array_diff_key(
317  $this->allDataMap[$item->getTableName()][$item->getId()],
318  $fieldNameMap
319  );
320  }
321 
329  protected function ‪synchronizeTranslationItem(DataMapItem $item, array $fieldNames, $fromId)
330  {
331  if (empty($fieldNames)) {
332  return;
333  }
334 
335  $fieldNameList = 'uid,' . implode(',', $fieldNames);
336 
337  $fromRecord = ['uid' => $fromId];
339  $fromRecord = BackendUtility::getRecordWSOL(
340  $item->getTableName(),
341  (int)$fromId,
342  $fieldNameList
343  );
344  }
345 
346  $forRecord = [];
347  if (!$item->isNew()) {
348  $forRecord = BackendUtility::getRecordWSOL(
349  $item->getTableName(),
350  $item->getId(),
351  $fieldNameList
352  );
353  }
354 
355  if (is_array($fromRecord) && is_array($forRecord)) {
356  foreach ($fieldNames as $fieldName) {
358  $item,
359  $fieldName,
360  $fromRecord,
361  $forRecord
362  );
363  }
364  }
365  }
366 
373  protected function ‪populateTranslationItem(‪DataMapItem $item)
374  {
376  foreach ($item->‪findDependencies($scope) as $dependentItem) {
377  // use suggested item, if it was submitted in data-map
378  $suggestedDependentItem = $this->‪findItem(
379  $dependentItem->getTableName(),
380  $dependentItem->getId()
381  );
382  if ($suggestedDependentItem !== null) {
383  $dependentItem = $suggestedDependentItem;
384  }
385  foreach ([$scope, ‪DataMapItem::SCOPE_EXCLUDE] as $dependentScope) {
386  $fieldNames = $this->‪getFieldNamesForItemScope(
387  $dependentItem,
388  $dependentScope,
389  false
390  );
392  $dependentItem,
393  $fieldNames,
394  $item->‪getId()
395  );
396  }
397  }
398  }
399  }
400 
406  protected function ‪finishTranslationItem(‪DataMapItem $item)
407  {
408  if (
409  $item->‪isParentType()
411  ) {
412  return;
413  }
414 
415  $this->allDataMap[$item->‪getTableName()][$item->‪getId()]['l10n_state'] = $item->‪getState()->‪export();
416  }
417 
426  protected function ‪synchronizeFieldValues(DataMapItem $item, string $fieldName, array $fromRecord, array $forRecord)
427  {
428  // skip if this field has been processed already, assumed that proper sanitation happened
429  if ($this->‪isSetInDataMap($item->getTableName(), $item->getId(), $fieldName)) {
430  return;
431  }
432 
433  $fromId = $fromRecord['uid'];
434  // retrieve value from in-memory data-map
435  if ($this->‪isSetInDataMap($item->getTableName(), $fromId, $fieldName)) {
436  $fromValue = $this->allDataMap[$item->getTableName()][$fromId][$fieldName];
437  } elseif (array_key_exists($fieldName, $fromRecord)) {
438  // retrieve value from record
439  $fromValue = $fromRecord[$fieldName];
440  } else {
441  // otherwise abort synchronization
442  return;
443  }
444 
445  // plain values
446  if (!$this->‪isRelationField($item->getTableName(), $fieldName)) {
447  $this->‪modifyDataMap(
448  $item->getTableName(),
449  $item->getId(),
450  [$fieldName => $fromValue]
451  );
452  } elseif (!$this->‪isInlineRelationField($item->getTableName(), $fieldName)) {
453  // direct relational values
454  $this->‪synchronizeDirectRelations($item, $fieldName, $fromRecord);
455  } else {
456  // inline relational values
457  $this->‪synchronizeInlineRelations($item, $fieldName, $fromRecord, $forRecord);
458  }
459  }
460 
468  protected function ‪synchronizeDirectRelations(DataMapItem $item, string $fieldName, array $fromRecord)
469  {
470  $configuration = ‪$GLOBALS['TCA'][$item->getTableName()]['columns'][$fieldName];
471  $fromId = $fromRecord['uid'];
472  if ($this->‪isSetInDataMap($item->getTableName(), $fromId, $fieldName)) {
473  $fromValue = $this->allDataMap[$item->getTableName()][$fromId][$fieldName];
474  } else {
475  $fromValue = $fromRecord[$fieldName];
476  }
477 
478  // non-MM relations are stored as comma separated values, just use them
479  // if values are available in data-map already, just use them as well
480  if (
481  empty($configuration['config']['MM'])
482  || $this->‪isSetInDataMap($item->getTableName(), $fromId, $fieldName)
483  ) {
484  $this->‪modifyDataMap(
485  $item->getTableName(),
486  $item->getId(),
487  [$fieldName => $fromValue]
488  );
489  return;
490  }
491  // fetch MM relations from storage
492  $type = $configuration['config']['type'];
493  $manyToManyTable = $configuration['config']['MM'];
494  if ($type === 'group' && !empty(trim($configuration['config']['allowed'] ?? ''))) {
495  $tableNames = trim($configuration['config']['allowed']);
496  } elseif ($configuration['config']['type'] === 'select' || $configuration['config']['type'] === 'category') {
497  $tableNames = $configuration['config']['foreign_table'] ?? '';
498  } else {
499  return;
500  }
501 
502  $relationHandler = $this->‪createRelationHandler();
503  $relationHandler->start(
504  '',
505  $tableNames,
506  $manyToManyTable,
507  $fromId,
508  $item->getTableName(),
509  $configuration['config']
510  );
511 
512  // provide list of relations, optionally prepended with table name
513  // e.g. "13,19,23" or "tt_content_27,tx_extension_items_28"
514  $this->‪modifyDataMap(
515  $item->getTableName(),
516  $item->getId(),
517  [$fieldName => implode(',', $relationHandler->getValueArray())]
518  );
519  }
520 
533  protected function ‪synchronizeInlineRelations(DataMapItem $item, string $fieldName, array $fromRecord, array $forRecord)
534  {
535  $configuration = ‪$GLOBALS['TCA'][$item->getTableName()]['columns'][$fieldName];
536  $isLocalizationModeExclude = ($configuration['l10n_mode'] ?? null) === 'exclude';
537  $foreignTableName = $configuration['config']['foreign_table'];
538 
539  $fieldNames = [
540  'language' => ‪$GLOBALS['TCA'][$foreignTableName]['ctrl']['languageField'] ?? null,
541  'parent' => ‪$GLOBALS['TCA'][$foreignTableName]['ctrl']['transOrigPointerField'] ?? null,
542  'source' => ‪$GLOBALS['TCA'][$foreignTableName]['ctrl']['translationSource'] ?? null,
543  ];
544  $isTranslatable = (!empty($fieldNames['language']) && !empty($fieldNames['parent']));
545  $isLocalized = !empty($item->getLanguage());
546 
547  $suggestedAncestorIds = $this->‪resolveSuggestedInlineRelations(
548  $item,
549  $fieldName,
550  $fromRecord
551  );
552  $persistedIds = $this->‪resolvePersistedInlineRelations(
553  $item,
554  $fieldName,
555  $forRecord
556  );
557 
558  // The dependent ID map points from language parent/source record to
559  // localization, thus keys: parents/sources & values: localizations
560  $dependentIdMap = $this->‪fetchDependentIdMap($foreignTableName, $suggestedAncestorIds, (int)$item->getLanguage());
561  // filter incomplete structures - this is a drawback of DataHandler's remap stack, since
562  // just created IRRE translations still belong to the language parent - filter them out
563  $suggestedAncestorIds = array_diff($suggestedAncestorIds, array_values($dependentIdMap));
564  // compile element differences to be resolved
565  // remove elements that are persisted at the language translation, but not required anymore
566  $removeIds = array_diff($persistedIds, array_values($dependentIdMap));
567  // remove elements that are persisted at the language parent/source, but not required anymore
568  $removeAncestorIds = array_diff(array_keys($dependentIdMap), $suggestedAncestorIds);
569  // missing elements that are persisted at the language parent/source, but not translated yet
570  $missingAncestorIds = array_diff($suggestedAncestorIds, array_keys($dependentIdMap));
571  // persisted elements that should be copied or localized
572  $createAncestorIds = $this->‪filterNumericIds($missingAncestorIds);
573  // non-persisted elements that should be duplicated in data-map directly
574  $populateAncestorIds = array_diff($missingAncestorIds, $createAncestorIds);
575  // this desired state map defines the final result of child elements in their parent translation
576  $desiredIdMap = array_combine($suggestedAncestorIds, $suggestedAncestorIds) ?: [];
577  // update existing translations in the desired state map
578  foreach ($dependentIdMap as $ancestorId => $translationId) {
579  if (isset($desiredIdMap[$ancestorId])) {
580  $desiredIdMap[$ancestorId] = $translationId;
581  }
582  }
583  // no children to be synchronized, but element order could have been changed
584  if (empty($removeAncestorIds) && empty($missingAncestorIds)) {
585  $this->‪modifyDataMap(
586  $item->getTableName(),
587  $item->getId(),
588  [$fieldName => implode(',', array_values($desiredIdMap))]
589  );
590  return;
591  }
592  // In case only missing elements shall be created, re-use previously sanitized
593  // values IF the relation parent item is new and the count of missing relations
594  // equals the count of previously sanitized relations.
595  // This is caused during copy processes, when the child relations
596  // already have been cloned in DataHandler::copyRecord_procBasedOnFieldType()
597  // without the possibility to resolve the initial connections at this point.
598  // Otherwise child relations would superfluously be duplicated again here.
599  // @todo Invalid manually injected child relations cannot be determined here
600  $sanitizedValue = $this->sanitizationMap[$item->getTableName()][$item->getId()][$fieldName] ?? null;
601  if (
602  !empty($missingAncestorIds) && $item->isNew() && $sanitizedValue !== null
603  && count(‪GeneralUtility::trimExplode(',', $sanitizedValue, true)) === count($missingAncestorIds)
604  ) {
605  $this->‪modifyDataMap(
606  $item->getTableName(),
607  $item->getId(),
608  [$fieldName => $sanitizedValue]
609  );
610  return;
611  }
612 
613  $localCommandMap = [];
614  foreach ($removeIds as $removeId) {
615  $localCommandMap[$foreignTableName][$removeId]['delete'] = true;
616  }
617  foreach ($removeAncestorIds as $removeAncestorId) {
618  $removeId = $dependentIdMap[$removeAncestorId];
619  $localCommandMap[$foreignTableName][$removeId]['delete'] = true;
620  }
621  foreach ($createAncestorIds as $createAncestorId) {
622  // if child table is not aware of localization, just copy
623  if ($isLocalizationModeExclude || !$isTranslatable) {
624  $localCommandMap[$foreignTableName][$createAncestorId]['copy'] = [
625  'target' => -$createAncestorId,
626  'ignoreLocalization' => true,
627  ];
628  } else {
629  // otherwise, trigger the localization process
630  $localCommandMap[$foreignTableName][$createAncestorId]['localize'] = $item->getLanguage();
631  }
632  }
633  // execute copy, localize and delete actions on persisted child records
634  if (!empty($localCommandMap)) {
635  $localDataHandler = GeneralUtility::makeInstance(DataHandler::class, $this->‪referenceIndexUpdater);
636  $localDataHandler->start([], $localCommandMap, $this->‪backendUser);
637  $localDataHandler->process_cmdmap();
638  // update copied or localized ids
639  foreach ($createAncestorIds as $createAncestorId) {
640  if (empty($localDataHandler->copyMappingArray_merged[$foreignTableName][$createAncestorId])) {
641  $additionalInformation = '';
642  if (!empty($localDataHandler->errorLog)) {
643  $additionalInformation = ', reason "'
644  . implode(', ', $localDataHandler->errorLog) . '"';
645  }
646  throw new \RuntimeException(
647  'Child record was not processed' . $additionalInformation,
648  1486233164
649  );
650  }
651  $newLocalizationId = $localDataHandler->copyMappingArray_merged[$foreignTableName][$createAncestorId];
652  $newLocalizationId = $localDataHandler->getAutoVersionId($foreignTableName, $newLocalizationId) ?? $newLocalizationId;
653  $desiredIdMap[$createAncestorId] = $newLocalizationId;
654  // apply localization references to l10n_mode=exclude children
655  // (without keeping their reference to their origin, synchronization is not possible)
656  if ($isLocalizationModeExclude && $isTranslatable && $isLocalized) {
657  $adjustCopiedValues = $this->‪applyLocalizationReferences(
658  $foreignTableName,
659  $createAncestorId,
660  (int)$item->getLanguage(),
661  $fieldNames,
662  []
663  );
664  $this->‪modifyDataMap(
665  $foreignTableName,
666  $newLocalizationId,
667  $adjustCopiedValues
668  );
669  }
670  }
671  }
672  // populate new child records in data-map
673  if (!empty($populateAncestorIds)) {
674  foreach ($populateAncestorIds as $populateAncestorId) {
675  $newLocalizationId = ‪StringUtility::getUniqueId('NEW');
676  $desiredIdMap[$populateAncestorId] = $newLocalizationId;
677  $duplicatedValues = $this->allDataMap[$foreignTableName][$populateAncestorId] ?? [];
678  // applies localization references to given raw data-map item
679  if ($isTranslatable && $isLocalized) {
680  $duplicatedValues = $this->‪applyLocalizationReferences(
681  $foreignTableName,
682  $populateAncestorId,
683  (int)$item->getLanguage(),
684  $fieldNames,
685  $duplicatedValues
686  );
687  }
688  // prefixes language title if applicable for the accordant field name in raw data-map item
689  if ($isTranslatable && $isLocalized && !$isLocalizationModeExclude) {
690  $duplicatedValues = $this->‪prefixLanguageTitle(
691  $foreignTableName,
692  $populateAncestorId,
693  (int)$item->getLanguage(),
694  $duplicatedValues
695  );
696  }
697  $this->‪modifyDataMap(
698  $foreignTableName,
699  $newLocalizationId,
700  $duplicatedValues
701  );
702  }
703  }
704  // update inline parent field references - required to update pointer fields
705  $this->‪modifyDataMap(
706  $item->getTableName(),
707  $item->getId(),
708  [$fieldName => implode(',', array_values($desiredIdMap))]
709  );
710  }
711 
722  protected function ‪resolveSuggestedInlineRelations(DataMapItem $item, string $fieldName, array $fromRecord): array
723  {
724  $suggestedAncestorIds = [];
725  $fromId = $fromRecord['uid'];
726  $configuration = ‪$GLOBALS['TCA'][$item->getTableName()]['columns'][$fieldName];
727  $foreignTableName = $configuration['config']['foreign_table'];
728  $manyToManyTable = ($configuration['config']['MM'] ?? '');
729 
730  // determine suggested elements of either translation parent or source record
731  // from data-map, in case the accordant language parent/source record was modified
732  if ($this->‪isSetInDataMap($item->getTableName(), $fromId, $fieldName)) {
733  $suggestedAncestorIds = ‪GeneralUtility::trimExplode(
734  ',',
735  $this->allDataMap[$item->getTableName()][$fromId][$fieldName],
736  true
737  );
738  } elseif (‪MathUtility::canBeInterpretedAsInteger($fromId)) {
739  // determine suggested elements of either translation parent or source record from storage
740  $relationHandler = $this->‪createRelationHandler();
741  $relationHandler->start(
742  $fromRecord[$fieldName],
743  $foreignTableName,
744  $manyToManyTable,
745  $fromId,
746  $item->getTableName(),
747  $configuration['config']
748  );
749  $suggestedAncestorIds = $this->‪mapRelationItemId($relationHandler->itemArray);
750  }
751 
752  return array_filter($suggestedAncestorIds);
753  }
754 
763  private function ‪resolvePersistedInlineRelations(DataMapItem $item, string $fieldName, array $forRecord): array
764  {
765  $persistedIds = [];
766  $configuration = ‪$GLOBALS['TCA'][$item->getTableName()]['columns'][$fieldName];
767  $foreignTableName = $configuration['config']['foreign_table'];
768  $manyToManyTable = ($configuration['config']['MM'] ?? '');
769 
770  // determine persisted elements for the current data-map item
771  if (!$item->isNew()) {
772  $relationHandler = $this->‪createRelationHandler();
773  $relationHandler->start(
774  $forRecord[$fieldName] ?? '',
775  $foreignTableName,
776  $manyToManyTable,
777  $item->getId(),
778  $item->getTableName(),
779  $configuration['config']
780  );
781  $persistedIds = $this->‪mapRelationItemId($relationHandler->itemArray);
782  }
783 
784  return array_filter($persistedIds);
785  }
786 
797  protected function ‪isSetInDataMap(string $tableName, $id, string $fieldName)
798  {
799  return
800  // directly look-up field name
801  isset($this->allDataMap[$tableName][$id][$fieldName])
802  // check existence of field name as key for null values
803  || isset($this->allDataMap[$tableName][$id])
804  && is_array($this->allDataMap[$tableName][$id])
805  && array_key_exists($fieldName, $this->allDataMap[$tableName][$id]);
806  }
807 
818  protected function ‪modifyDataMap(string $tableName, $id, array $values)
819  {
820  // avoid superfluous iterations by data-map changes with values
821  // that actually have not been changed and were available already
822  $sameValues = array_intersect_assoc(
823  $this->allDataMap[$tableName][$id] ?? [],
824  $values
825  );
826  if (!empty($sameValues)) {
827  $fieldNames = implode(', ', array_keys($sameValues));
828  throw new \RuntimeException(
829  sprintf(
830  'Issued data-map change for table %s with same values '
831  . 'for these fields names %s',
832  $tableName,
833  $fieldNames
834  ),
835  1488634845
836  );
837  }
838 
839  $this->‪modifiedDataMap[$tableName][$id] = array_merge(
840  $this->‪modifiedDataMap[$tableName][$id] ?? [],
841  $values
842  );
843  $this->allDataMap[$tableName][$id] = array_merge(
844  $this->allDataMap[$tableName][$id] ?? [],
845  $values
846  );
847  }
848 
852  protected function ‪addNextItem(‪DataMapItem $item)
853  {
854  $identifier = $item->‪getTableName() . ':' . $item->‪getId();
855  if (!isset($this->allItems[$identifier])) {
856  $this->allItems[$identifier] = $item;
857  }
858  $this->nextItems[$identifier] = $item;
859  }
860 
870  protected function ‪fetchTranslationValues(string $tableName, array $fieldNames, array $ids)
871  {
872  if (empty($ids)) {
873  return [];
874  }
875 
876  $connection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable($tableName);
877  $queryBuilder = $connection->createQueryBuilder();
878  $queryBuilder->getRestrictions()->removeAll()
879  // NOT using WorkspaceRestriction here since it's wrong in this case. See ws OR restriction below.
880  ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
881 
882  $expressions = [];
883  if (BackendUtility::isTableWorkspaceEnabled($tableName)) {
884  $expressions[] = $queryBuilder->expr()->eq('t3ver_wsid', 0);
885  }
886  if ($this->‪backendUser->workspace > 0 && BackendUtility::isTableWorkspaceEnabled($tableName)) {
887  // If this is a workspace record (t3ver_wsid = be-user-workspace), then fetch this one
888  // if it is NOT a deleted placeholder (t3ver_state=2), but ok with casual overlay (t3ver_state=0),
889  // new ws-record (t3ver_state=1), or moved record (t3ver_state=4).
890  // It *might* be possible to simplify this since it may be the case that ws-deleted records are
891  // impossible to be incoming here at all? But this query is a safe thing, so we go with it for now.
892  $expressions[] = $queryBuilder->expr()->and(
893  $queryBuilder->expr()->eq('t3ver_wsid', $queryBuilder->createNamedParameter($this->backendUser->workspace, ‪Connection::PARAM_INT)),
894  $queryBuilder->expr()->in(
895  't3ver_state',
896  $queryBuilder->createNamedParameter(
898  Connection::PARAM_INT_ARRAY
899  )
900  ),
901  );
902  }
903 
904  $translationValues = [];
905  $maxBindParameters = ‪PlatformInformation::getMaxBindParameters($connection->getDatabasePlatform());
906  // We are using the max bind parameter value as way to retrieve the data in chunks. However, we are not
907  // using up the placeholders by providing the id list directly, we keep this calculation to avoid hitting
908  // max query size limitation in most cases. If that is hit, it can be increased by adjusting the dbms setting.
909  foreach (array_chunk($ids, $maxBindParameters, true) as $chunk) {
910  $result = $queryBuilder
911  ->select(...array_values($fieldNames))
912  ->from($tableName)
913  ->where(
914  $queryBuilder->expr()->in(
915  'uid',
916  $queryBuilder->quoteArrayBasedValueListToIntegerList($chunk)
917  ),
918  $queryBuilder->expr()->or(...$expressions)
919  )
920  ->executeQuery();
921  while ($record = $result->fetchAssociative()) {
922  $translationValues[$record['uid']] = $record;
923  }
924  }
925  return $translationValues;
926  }
927 
945  protected function ‪fetchDependencies(string $tableName, array $ids)
946  {
947  if (empty($ids) || !BackendUtility::isTableLocalizable($tableName)) {
948  return [];
949  }
950 
951  $fieldNames = [
952  'uid' => 'uid',
953  'l10n_state' => 'l10n_state',
954  'language' => ‪$GLOBALS['TCA'][$tableName]['ctrl']['languageField'],
955  'parent' => ‪$GLOBALS['TCA'][$tableName]['ctrl']['transOrigPointerField'],
956  ];
957  if (!empty(‪$GLOBALS['TCA'][$tableName]['ctrl']['translationSource'])) {
958  $fieldNames['source'] = ‪$GLOBALS['TCA'][$tableName]['ctrl']['translationSource'];
959  }
960  $fieldNamesMap = array_combine($fieldNames, $fieldNames);
961 
962  $persistedIds = $this->‪filterNumericIds($ids);
963  $createdIds = array_diff($ids, $persistedIds);
964  $dependentElements = $this->‪fetchDependentElements($tableName, $persistedIds, $fieldNames);
965 
966  foreach ($createdIds as $createdId) {
967  $data = $this->allDataMap[$tableName][$createdId] ?? null;
968  if ($data === null) {
969  continue;
970  }
971  $dependentElements[] = array_merge(
972  ['uid' => $createdId],
973  array_intersect_key($data, $fieldNamesMap)
974  );
975  }
976 
977  $dependencyMap = [];
978  foreach ($dependentElements as $dependentElement) {
979  $dependentItem = ‪DataMapItem::build(
980  $tableName,
981  $dependentElement['uid'],
982  [],
983  $dependentElement,
984  $fieldNames
985  );
986 
987  if ($dependentItem->isDirectChildType()) {
988  $dependencyMap[$dependentItem->getParent()][‪State::STATE_PARENT][] = $dependentItem;
989  }
990  if ($dependentItem->isGrandChildType()) {
991  $dependencyMap[$dependentItem->getParent()][‪State::STATE_PARENT][] = $dependentItem;
992  $dependencyMap[$dependentItem->getSource()][‪State::STATE_SOURCE][] = $dependentItem;
993  }
994  }
995  return $dependencyMap;
996  }
997 
1022  protected function ‪fetchDependentIdMap(string $tableName, array $ids, int $desiredLanguage)
1023  {
1024  $ancestorIdMap = [];
1025  if (empty($ids)) {
1026  return [];
1027  }
1028 
1029  $ids = $this->‪filterNumericIds($ids);
1030  $isTranslatable = BackendUtility::isTableLocalizable($tableName);
1031  $originFieldName = (‪$GLOBALS['TCA'][$tableName]['ctrl']['origUid'] ?? null);
1032 
1033  if (!$isTranslatable && $originFieldName === null) {
1034  // @todo Possibly throw an error, since pointing to original entity is not possible (via origin/parent)
1035  return [];
1036  }
1037 
1038  if ($isTranslatable) {
1039  $fieldNames = [
1040  'uid' => 'uid',
1041  'l10n_state' => 'l10n_state',
1042  'language' => ‪$GLOBALS['TCA'][$tableName]['ctrl']['languageField'],
1043  'parent' => ‪$GLOBALS['TCA'][$tableName]['ctrl']['transOrigPointerField'],
1044  ];
1045  if (!empty(‪$GLOBALS['TCA'][$tableName]['ctrl']['translationSource'])) {
1046  $fieldNames['source'] = ‪$GLOBALS['TCA'][$tableName]['ctrl']['translationSource'];
1047  }
1048  } else {
1049  $fieldNames = [
1050  'uid' => 'uid',
1051  'origin' => $originFieldName,
1052  ];
1053  }
1054 
1055  $fetchIds = $ids;
1056  if ($isTranslatable) {
1057  // expand search criteria via parent and source elements
1058  $translationValues = $this->‪fetchTranslationValues($tableName, $fieldNames, $ids);
1059  $ancestorIdMap = $this->‪buildElementAncestorIdMap($fieldNames, $translationValues);
1060  $fetchIds = array_unique(array_merge($ids, array_keys($ancestorIdMap)));
1061  }
1062 
1063  $dependentElements = $this->‪fetchDependentElements($tableName, $fetchIds, $fieldNames);
1064 
1065  $dependentIdMap = [];
1066  foreach ($dependentElements as $dependentElement) {
1067  $dependentId = $dependentElement['uid'];
1068  // implicit: use origin pointer if table cannot be translated
1069  if (!$isTranslatable) {
1070  $ancestorId = (int)$dependentElement[$fieldNames['origin']];
1071  // only consider element if it reflects the desired language
1072  } elseif ((int)$dependentElement[$fieldNames['language']] === $desiredLanguage) {
1073  $ancestorId = $this->‪resolveAncestorId($fieldNames, $dependentElement);
1074  } else {
1075  // otherwise skip the element completely
1076  continue;
1077  }
1078  // only keep ancestors that were initially requested before expanding
1079  if (in_array($ancestorId, $ids, true)) {
1080  $dependentIdMap[$ancestorId] = $dependentId;
1081  } elseif (!empty($ancestorIdMap[$ancestorId])) {
1082  // resolve from previously expanded search criteria
1083  $possibleChainedIds = array_intersect(
1084  $ids,
1085  $ancestorIdMap[$ancestorId]
1086  );
1087  if (!empty($possibleChainedIds)) {
1088  // use the first found id from `$possibleChainedIds`
1089  $ancestorId = reset($possibleChainedIds);
1090  $dependentIdMap[$ancestorId] = $dependentId;
1091  }
1092  }
1093  }
1094  return $dependentIdMap;
1095  }
1096 
1108  protected function ‪fetchDependentElements(string $tableName, array $ids, array $fieldNames)
1109  {
1110  if (empty($ids)) {
1111  return [];
1112  }
1113 
1114  $connection = GeneralUtility::makeInstance(ConnectionPool::class)
1115  ->getConnectionForTable($tableName);
1116  $ids = $this->‪filterNumericIds($ids);
1117  $maxBindParameters = ‪PlatformInformation::getMaxBindParameters($connection->getDatabasePlatform());
1118 
1119  $dependentElements = [];
1120  foreach (array_chunk($ids, $maxBindParameters, true) as $idsChunked) {
1121  $queryBuilder = $connection->createQueryBuilder();
1122  $queryBuilder->getRestrictions()
1123  ->removeAll()
1124  ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
1125  ->add(GeneralUtility::makeInstance(WorkspaceRestriction::class, $this->‪backendUser->workspace));
1126 
1127  $zeroParameter = $queryBuilder->createNamedParameter(0, ‪Connection::PARAM_INT);
1128  $idsParameter = $queryBuilder->quoteArrayBasedValueListToIntegerList($idsChunked);
1129 
1130  // fetch by language dependency
1131  if (!empty($fieldNames['language']) && !empty($fieldNames['parent'])) {
1132  $ancestorPredicates = [
1133  $queryBuilder->expr()->in(
1134  $fieldNames['parent'],
1135  $idsParameter
1136  ),
1137  ];
1138  if (!empty($fieldNames['source'])) {
1139  $ancestorPredicates[] = $queryBuilder->expr()->in(
1140  $fieldNames['source'],
1141  $idsParameter
1142  );
1143  }
1144  $predicates = [
1145  // must be any kind of localization
1146  $queryBuilder->expr()->gt(
1147  $fieldNames['language'],
1148  $zeroParameter
1149  ),
1150  // must be in connected mode
1151  $queryBuilder->expr()->gt(
1152  $fieldNames['parent'],
1153  $zeroParameter
1154  ),
1155  // any parent or source pointers
1156  $queryBuilder->expr()->or(...$ancestorPredicates),
1157  ];
1158  } elseif (!empty($fieldNames['origin'])) {
1159  // fetch by origin dependency ("copied from")
1160  $predicates = [
1161  $queryBuilder->expr()->in(
1162  $fieldNames['origin'],
1163  $idsParameter
1164  ),
1165  ];
1166  } else {
1167  // otherwise: stop execution
1168  throw new \InvalidArgumentException(
1169  'Invalid combination of query field names given',
1170  1487192370
1171  );
1172  }
1173 
1174  $statement = $queryBuilder
1175  ->select(...array_values($fieldNames))
1176  ->from($tableName)
1177  ->andWhere(...$predicates)
1178  ->executeQuery();
1179 
1180  while ($record = $statement->fetchAssociative()) {
1181  $dependentElements[] = $record;
1182  }
1183  }
1184  return $dependentElements;
1185  }
1186 
1194  protected function ‪filterItemsByType(string $type, array $items)
1195  {
1196  return array_filter(
1197  $items,
1198  static function (DataMapItem $item) use ($type) {
1199  return $item->getType() === $type;
1200  }
1201  );
1202  }
1210  protected function ‪filterNumericIds(array $ids)
1211  {
1212  $ids = array_filter(
1213  $ids,
1214  static function ($id) {
1216  }
1217  );
1218  return array_map('intval', $ids);
1219  }
1220 
1228  protected function ‪filterNewItemIds(string $tableName, array $ids)
1229  {
1230  return array_filter(
1231  $ids,
1232  function ($id) use ($tableName) {
1233  return $this->‪findItem($tableName, $id) === null;
1234  }
1235  );
1236  }
1244  protected function ‪mapRelationItemId(array $relationItems)
1245  {
1246  return array_map(
1247  static function (array $relationItem) {
1248  return (int)$relationItem['id'];
1249  },
1250  $relationItems
1251  );
1252  }
1253 
1259  protected function ‪resolveAncestorId(array $fieldNames, array $element)
1260  {
1261  $sourceName = $fieldNames['source'] ?? null;
1262  if ($sourceName !== null && !empty($element[$sourceName])) {
1263  // implicit: use source pointer if given (not empty)
1264  return (int)$element[$sourceName];
1265  }
1266  $parentName = $fieldNames['parent'] ?? null;
1267  if ($parentName !== null && !empty($element[$parentName])) {
1268  // implicit: use parent pointer if given (not empty)
1269  return (int)$element[$parentName];
1270  }
1271  return null;
1272  }
1273 
1284  protected function ‪buildElementAncestorIdMap(array $fieldNames, array $elements)
1285  {
1286  $ancestorIdMap = [];
1287  foreach ($elements as $element) {
1288  $ancestorId = $this->‪resolveAncestorId($fieldNames, $element);
1289  if ($ancestorId !== null) {
1290  $ancestorIdMap[$ancestorId][] = (int)$element['uid'];
1291  }
1292  }
1293  return $ancestorIdMap;
1294  }
1295 
1303  protected function ‪findItem(string $tableName, $id)
1304  {
1305  return $this->allItems[$tableName . ':' . $id] ?? null;
1306  }
1307 
1318  protected function ‪applyLocalizationReferences(string $tableName, $fromId, int $language, array $fieldNames, array $data): array
1319  {
1320  // just return if localization cannot be applied
1321  if (empty($language)) {
1322  return $data;
1323  }
1324 
1325  // apply `languageField`, e.g. `sys_language_uid`
1326  $data[$fieldNames['language']] = $language;
1327  // apply `transOrigPointerField`, e.g. `l10n_parent`
1328  if (empty($data[$fieldNames['parent']])) {
1329  // @todo Only $id used in TCA type 'select' is resolved in DataHandler's remapStack
1330  $data[$fieldNames['parent']] = $fromId;
1331  }
1332  // apply `translationSource`, e.g. `l10n_source`
1333  if (!empty($fieldNames['source'])) {
1334  // @todo Not sure, whether $id is resolved in DataHandler's remapStack
1335  $data[$fieldNames['source']] = $fromId;
1336  }
1337  // unset field names that are expected to be handled in this processor
1338  foreach ($this->‪getFieldNamesToBeHandled($tableName) as $fieldName) {
1339  unset($data[$fieldName]);
1340  }
1341 
1342  return $data;
1343  }
1344 
1354  protected function ‪prefixLanguageTitle(string $tableName, $fromId, int $language, array $data): array
1355  {
1356  $prefixFieldNames = array_intersect(
1357  array_keys($data),
1358  $this->‪getPrefixLanguageTitleFieldNames($tableName)
1359  );
1360  if (empty($prefixFieldNames)) {
1361  return $data;
1362  }
1363 
1364  [$pageId] = BackendUtility::getTSCpid($tableName, (int)$fromId, $data['pid'] ?? null);
1365  $tsConfig = BackendUtility::getPagesTSconfig($pageId)['TCEMAIN.'] ?? [];
1366  if (($translateToMessage = (string)($tsConfig['translateToMessage'] ?? '')) === '') {
1367  // Return in case translateToMessage had been unset
1368  return $data;
1369  }
1370 
1371  $tableRelatedConfig = $tsConfig['default.'] ?? [];
1373  $tableRelatedConfig,
1374  $tsConfig['table.'][$tableName . '.'] ?? []
1375  );
1376  if ($tableRelatedConfig['disablePrependAtCopy'] ?? false) {
1377  // Return in case "disablePrependAtCopy" is set for this table
1378  return $data;
1379  }
1380 
1381  try {
1382  $site = GeneralUtility::makeInstance(SiteFinder::class)->getSiteByPageId($pageId);
1383  $siteLanguage = $site->getLanguageById($language);
1384  $languageTitle = $siteLanguage->getTitle();
1385  } catch (SiteNotFoundException | \InvalidArgumentException $e) {
1386  $languageTitle = '';
1387  }
1388 
1389  $languageService = $this->‪getLanguageService();
1390  if ($languageService !== null) {
1391  $translateToMessage = $languageService->sL($translateToMessage);
1392  }
1393  $translateToMessage = sprintf($translateToMessage, $languageTitle);
1394 
1395  if ($translateToMessage === '') {
1396  // Return for edge cases when the translateToMessage got empty, e.g. because the referenced LLL
1397  // label is empty or only contained a placeholder which is replaced by an empty language title.
1398  return $data;
1399  }
1400 
1401  foreach ($prefixFieldNames as $prefixFieldName) {
1402  // @todo The hook in DataHandler is not applied here
1403  $data[$prefixFieldName] = '[' . $translateToMessage . '] ' . $data[$prefixFieldName];
1404  }
1405 
1406  return $data;
1407  }
1408 
1417  protected function ‪getFieldNamesForItemScope(
1418  DataMapItem $item,
1419  string $scope,
1420  bool $modified
1421  ) {
1422  if (
1423  $scope === ‪DataMapItem::SCOPE_PARENT
1424  || $scope === ‪DataMapItem::SCOPE_SOURCE
1425  ) {
1426  if (!‪State::isApplicable($item->getTableName())) {
1427  return [];
1428  }
1429  return $item->getState()->filterFieldNames($scope, $modified);
1430  }
1431  if ($scope === ‪DataMapItem::SCOPE_EXCLUDE) {
1433  $item->getTableName()
1434  );
1435  }
1436  return [];
1437  }
1445  protected function ‪getLocalizationModeExcludeFieldNames(string $tableName)
1446  {
1447  $localizationExcludeFieldNames = [];
1448  if (empty(‪$GLOBALS['TCA'][$tableName]['columns'])) {
1449  return $localizationExcludeFieldNames;
1450  }
1451 
1452  foreach (‪$GLOBALS['TCA'][$tableName]['columns'] as $fieldName => $configuration) {
1453  if (($configuration['l10n_mode'] ?? null) === 'exclude'
1454  && ($configuration['config']['type'] ?? null) !== 'none'
1455  ) {
1456  $localizationExcludeFieldNames[] = $fieldName;
1457  }
1458  }
1459 
1460  return $localizationExcludeFieldNames;
1461  }
1462 
1470  protected function ‪getFieldNamesToBeHandled(string $tableName)
1471  {
1472  return array_merge(
1473  ‪State::getFieldNames($tableName),
1474  $this->‪getLocalizationModeExcludeFieldNames($tableName)
1475  );
1476  }
1484  protected function ‪getPrefixLanguageTitleFieldNames(string $tableName)
1485  {
1486  $prefixLanguageTitleFieldNames = [];
1487  if (empty(‪$GLOBALS['TCA'][$tableName]['columns'])) {
1488  return $prefixLanguageTitleFieldNames;
1489  }
1490 
1491  foreach (‪$GLOBALS['TCA'][$tableName]['columns'] as $fieldName => $configuration) {
1492  $type = $configuration['config']['type'] ?? null;
1493  if (
1494  ($configuration['l10n_mode'] ?? null) === 'prefixLangTitle'
1495  && ($type === 'input' || $type === 'text')
1496  ) {
1497  $prefixLanguageTitleFieldNames[] = $fieldName;
1498  }
1499  }
1500 
1501  return $prefixLanguageTitleFieldNames;
1502  }
1503 
1511  protected function ‪isRelationField(string $tableName, string $fieldName): bool
1512  {
1513  if (empty(‪$GLOBALS['TCA'][$tableName]['columns'][$fieldName]['config']['type'])) {
1514  return false;
1515  }
1516 
1517  $configuration = ‪$GLOBALS['TCA'][$tableName]['columns'][$fieldName]['config'];
1518 
1519  return ($configuration['type'] === 'group' && !empty($configuration['allowed']))
1520  || (
1521  ($configuration['type'] === 'select' || $configuration['type'] === 'category')
1522  && !empty($configuration['foreign_table'])
1523  && !empty(‪$GLOBALS['TCA'][$configuration['foreign_table']])
1524  )
1525  || $this->‪isInlineRelationField($tableName, $fieldName)
1526  ;
1527  }
1528 
1536  protected function ‪isInlineRelationField(string $tableName, string $fieldName): bool
1537  {
1538  if (empty(‪$GLOBALS['TCA'][$tableName]['columns'][$fieldName]['config']['type'])) {
1539  return false;
1540  }
1541 
1542  $configuration = ‪$GLOBALS['TCA'][$tableName]['columns'][$fieldName]['config'];
1543 
1544  return
1545  $configuration['type'] === 'inline'
1546  && !empty($configuration['foreign_table'])
1547  && !empty(‪$GLOBALS['TCA'][$configuration['foreign_table']])
1548  ;
1549  }
1550 
1558  protected function ‪isApplicable(string $tableName): bool
1559  {
1560  return
1561  ‪State::isApplicable($tableName)
1562  || BackendUtility::isTableLocalizable($tableName)
1563  && count($this->‪getLocalizationModeExcludeFieldNames($tableName)) > 0
1564  ;
1565  }
1566 
1570  protected function ‪createRelationHandler()
1571  {
1572  $relationHandler = GeneralUtility::makeInstance(RelationHandler::class);
1573  $relationHandler->setWorkspaceId($this->‪backendUser->workspace);
1574  return $relationHandler;
1575  }
1576 
1580  protected function ‪getLanguageService()
1581  {
1582  return ‪$GLOBALS['LANG'] ?? null;
1583  }
1584 }
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapItem\getType
‪string getType()
Definition: DataMapItem.php:229
‪TYPO3\CMS\Core\DataHandling\DataHandler
Definition: DataHandler.php:86
‪TYPO3\CMS\Core\Utility\GeneralUtility\trimExplode
‪static list< string > trimExplode($delim, $string, $removeEmptyValues=false, $limit=0)
Definition: GeneralUtility.php:999
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor\applyLocalizationReferences
‪array applyLocalizationReferences(string $tableName, $fromId, int $language, array $fieldNames, array $data)
Definition: DataMapProcessor.php:1311
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor\createRelationHandler
‪RelationHandler createRelationHandler()
Definition: DataMapProcessor.php:1563
‪TYPO3\CMS\Core\Database\Connection\PARAM_INT
‪const PARAM_INT
Definition: Connection.php:49
‪TYPO3\CMS\Core\Utility\MathUtility\canBeInterpretedAsInteger
‪static bool canBeInterpretedAsInteger($var)
Definition: MathUtility.php:74
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapItem\getState
‪State null getState()
Definition: DataMapItem.php:276
‪TYPO3\CMS\Core\Versioning\VersionState\NEW_PLACEHOLDER
‪const NEW_PLACEHOLDER
Definition: VersionState.php:53
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor\resolveSuggestedInlineRelations
‪int[] string[] resolveSuggestedInlineRelations(DataMapItem $item, string $fieldName, array $fromRecord)
Definition: DataMapProcessor.php:715
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor\purgeDataMap
‪array purgeDataMap(array $dataMap)
Definition: DataMapProcessor.php:163
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor\sanitize
‪sanitize(array $items)
Definition: DataMapProcessor.php:253
‪TYPO3\CMS\Core\Database\RelationHandler
Definition: RelationHandler.php:37
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapItem\findDependencies
‪DataMapItem[] findDependencies(string $scope)
Definition: DataMapItem.php:370
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapItem\build
‪static DataMapItem build(string $tableName, $id, array $suggestedValues, array $persistedValues, array $configurationFieldNames)
Definition: DataMapItem.php:94
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor\getFieldNamesToBeHandled
‪string[] getFieldNamesToBeHandled(string $tableName)
Definition: DataMapProcessor.php:1463
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor\filterNewItemIds
‪array filterNewItemIds(string $tableName, array $ids)
Definition: DataMapProcessor.php:1221
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor\resolvePersistedInlineRelations
‪int[] resolvePersistedInlineRelations(DataMapItem $item, string $fieldName, array $forRecord)
Definition: DataMapProcessor.php:756
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor\backendUser
‪$this backendUser
Definition: DataMapProcessor.php:119
‪TYPO3\CMS\Core\DataHandling\Localization\State\filterFieldNames
‪string[] filterFieldNames(string $desiredState, bool $modified=false)
Definition: State.php:282
‪TYPO3\CMS\Core\DataHandling\Localization\State\STATE_PARENT
‪const STATE_PARENT
Definition: State.php:26
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor\referenceIndexUpdater
‪if($referenceIndexUpdater===null) $this referenceIndexUpdater
Definition: DataMapProcessor.php:123
‪TYPO3\CMS\Core\Exception\SiteNotFoundException
Definition: SiteNotFoundException.php:25
‪TYPO3\CMS\Core\Site\SiteFinder
Definition: SiteFinder.php:31
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor\getLanguageService
‪LanguageService null getLanguageService()
Definition: DataMapProcessor.php:1573
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor\fetchDependentElements
‪array fetchDependentElements(string $tableName, array $ids, array $fieldNames)
Definition: DataMapProcessor.php:1101
‪TYPO3\CMS\Core\Utility\ArrayUtility\mergeRecursiveWithOverrule
‪static mergeRecursiveWithOverrule(array &$original, array $overrule, $addKeys=true, $includeEmptyValues=true, $enableUnsetFeature=true)
Definition: ArrayUtility.php:654
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor\getFieldNamesForItemScope
‪string[] getFieldNamesForItemScope(DataMapItem $item, string $scope, bool $modified)
Definition: DataMapProcessor.php:1410
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor\fetchDependentIdMap
‪array fetchDependentIdMap(string $tableName, array $ids, int $desiredLanguage)
Definition: DataMapProcessor.php:1015
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor\getPrefixLanguageTitleFieldNames
‪array getPrefixLanguageTitleFieldNames(string $tableName)
Definition: DataMapProcessor.php:1477
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapItem\getTableName
‪string getTableName()
Definition: DataMapItem.php:148
‪TYPO3\CMS\Core\Versioning\VersionState\MOVE_POINTER
‪const MOVE_POINTER
Definition: VersionState.php:78
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor\buildElementAncestorIdMap
‪array buildElementAncestorIdMap(array $fieldNames, array $elements)
Definition: DataMapProcessor.php:1277
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapItem\SCOPE_EXCLUDE
‪const SCOPE_EXCLUDE
Definition: DataMapItem.php:33
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor\fetchDependencies
‪DataMapItem[][] fetchDependencies(string $tableName, array $ids)
Definition: DataMapProcessor.php:938
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor\modifyDataMap
‪modifyDataMap(string $tableName, $id, array $values)
Definition: DataMapProcessor.php:811
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapItem\isParentType
‪bool isParentType()
Definition: DataMapItem.php:252
‪TYPO3\CMS\Core\DataHandling\Localization\State\isApplicable
‪static bool isApplicable(string $tableName)
Definition: State.php:68
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor
Definition: DataMapProcessor.php:57
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor\populateTranslationItem
‪populateTranslationItem(DataMapItem $item)
Definition: DataMapProcessor.php:366
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor\collectItems
‪collectItems(string $tableName, array $idValues)
Definition: DataMapProcessor.php:184
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor\enrich
‪enrich(array $items)
Definition: DataMapProcessor.php:267
‪TYPO3\CMS\Core\DataHandling\Localization\State\getFieldNames
‪static array getFieldNames(string $tableName)
Definition: State.php:82
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapItem\getApplicableScopes
‪string[] getApplicableScopes()
Definition: DataMapItem.php:378
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor\fetchTranslationValues
‪array fetchTranslationValues(string $tableName, array $fieldNames, array $ids)
Definition: DataMapProcessor.php:863
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor\prefixLanguageTitle
‪array prefixLanguageTitle(string $tableName, $fromId, int $language, array $data)
Definition: DataMapProcessor.php:1347
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor\mapRelationItemId
‪string[] mapRelationItemId(array $relationItems)
Definition: DataMapProcessor.php:1237
‪TYPO3\CMS\Core\Database\Platform\PlatformInformation\getMaxBindParameters
‪static int getMaxBindParameters(AbstractPlatform $platform)
Definition: PlatformInformation.php:125
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor\process
‪array process()
Definition: DataMapProcessor.php:132
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapItem\isNew
‪bool isNew()
Definition: DataMapItem.php:221
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor\modifiedDataMap
‪array< string, $modifiedDataMap=array();protected array< string, $sanitizationMap=array();protected BackendUserAuthentication $backendUser;protected ReferenceIndexUpdater $referenceIndexUpdater;protected DataMapItem[] $allItems=array();protected DataMapItem[] $nextItems=array();public static DataMapProcessor function instance(array $dataMap, BackendUserAuthentication $backendUser, ReferenceIndexUpdater $referenceIndexUpdater=null) { return GeneralUtility::makeInstance(static::class, $dataMap, $backendUser, $referenceIndexUpdater);} public function __construct(array $dataMap, BackendUserAuthentication $backendUser, ReferenceIndexUpdater $referenceIndexUpdater=null) { $this->allDataMap=$dataMap;$this-> modifiedDataMap
Definition: DataMapProcessor.php:118
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor\isSetInDataMap
‪bool isSetInDataMap(string $tableName, $id, string $fieldName)
Definition: DataMapProcessor.php:790
‪TYPO3\CMS\Core\Authentication\BackendUserAuthentication
Definition: BackendUserAuthentication.php:62
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor\filterItemsByType
‪DataMapItem[] filterItemsByType(string $type, array $items)
Definition: DataMapProcessor.php:1187
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor\addNextItem
‪addNextItem(DataMapItem $item)
Definition: DataMapProcessor.php:845
‪TYPO3\CMS\Core\DataHandling\Localization
Definition: DataMapItem.php:16
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor\isApplicable
‪bool isApplicable(string $tableName)
Definition: DataMapProcessor.php:1551
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor\filterNumericIds
‪int[] filterNumericIds(array $ids)
Definition: DataMapProcessor.php:1203
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor\synchronizeInlineRelations
‪synchronizeInlineRelations(DataMapItem $item, string $fieldName, array $fromRecord, array $forRecord)
Definition: DataMapProcessor.php:526
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor\isInlineRelationField
‪bool isInlineRelationField(string $tableName, string $fieldName)
Definition: DataMapProcessor.php:1529
‪TYPO3\CMS\Core\Versioning\VersionState
Definition: VersionState.php:24
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapItem
Definition: DataMapItem.php:26
‪TYPO3\CMS\Core\DataHandling\Localization\State\STATE_SOURCE
‪const STATE_SOURCE
Definition: State.php:27
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor\synchronizeDirectRelations
‪synchronizeDirectRelations(DataMapItem $item, string $fieldName, array $fromRecord)
Definition: DataMapProcessor.php:461
‪TYPO3\CMS\Core\Database\Connection
Definition: Connection.php:38
‪TYPO3\CMS\Core\Utility\StringUtility\getUniqueId
‪static string getUniqueId($prefix='')
Definition: StringUtility.php:128
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapItem\getLanguage
‪string int getLanguage()
Definition: DataMapItem.php:287
‪TYPO3\CMS\Core\Versioning\VersionState\DEFAULT_STATE
‪const DEFAULT_STATE
Definition: VersionState.php:44
‪TYPO3\CMS\Core\Utility\ArrayUtility
Definition: ArrayUtility.php:24
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor\resolveAncestorId
‪int null resolveAncestorId(array $fieldNames, array $element)
Definition: DataMapProcessor.php:1252
‪$GLOBALS
‪$GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['adminpanel']['modules']
Definition: ext_localconf.php:25
‪TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction
Definition: DeletedRestriction.php:28
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapItem\SCOPE_SOURCE
‪const SCOPE_SOURCE
Definition: DataMapItem.php:32
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor\isRelationField
‪bool isRelationField(string $tableName, string $fieldName)
Definition: DataMapProcessor.php:1504
‪TYPO3\CMS\Core\Database\Platform\PlatformInformation
Definition: PlatformInformation.php:33
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor\$allDataMap
‪array $allDataMap
Definition: DataMapProcessor.php:60
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor\findItem
‪DataMapItem null findItem(string $tableName, $id)
Definition: DataMapProcessor.php:1296
‪TYPO3\CMS\Core\Utility\MathUtility
Definition: MathUtility.php:22
‪TYPO3\CMS\Core\Localization\LanguageService
Definition: LanguageService.php:42
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor\synchronizeFieldValues
‪synchronizeFieldValues(DataMapItem $item, string $fieldName, array $fromRecord, array $forRecord)
Definition: DataMapProcessor.php:419
‪TYPO3\CMS\Core\Database\ConnectionPool
Definition: ConnectionPool.php:46
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor\sanitizeTranslationItem
‪sanitizeTranslationItem(DataMapItem $item)
Definition: DataMapProcessor.php:292
‪TYPO3\CMS\Core\Utility\GeneralUtility
Definition: GeneralUtility.php:50
‪TYPO3\CMS\Core\Utility\StringUtility
Definition: StringUtility.php:22
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapItem\getId
‪mixed getId()
Definition: DataMapItem.php:158
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor\synchronizeTranslationItem
‪synchronizeTranslationItem(DataMapItem $item, array $fieldNames, $fromId)
Definition: DataMapProcessor.php:322
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor\getLocalizationModeExcludeFieldNames
‪string[] getLocalizationModeExcludeFieldNames(string $tableName)
Definition: DataMapProcessor.php:1438
‪TYPO3\CMS\Core\DataHandling\ReferenceIndexUpdater
Definition: ReferenceIndexUpdater.php:35
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor\finishTranslationItem
‪finishTranslationItem(DataMapItem $item)
Definition: DataMapProcessor.php:399
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapItem\SCOPE_PARENT
‪const SCOPE_PARENT
Definition: DataMapItem.php:31
‪TYPO3\CMS\Core\DataHandling\Localization\State\export
‪string null export()
Definition: State.php:193
‪TYPO3\CMS\Core\Database\Query\Restriction\WorkspaceRestriction
Definition: WorkspaceRestriction.php:40