‪TYPO3CMS  10.4
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 
31 
52 {
56  protected ‪$allDataMap = [];
57 
61  protected ‪$modifiedDataMap = [];
62 
66  protected ‪$sanitizationMap = [];
67 
71  protected ‪$backendUser;
72 
76  protected ‪$referenceIndexUpdater;
77 
81  protected ‪$allItems = [];
82 
86  protected ‪$nextItems = [];
87 
96  public static function ‪instance(
97  array $dataMap,
100  ) {
101  return GeneralUtility::makeInstance(
102  static::class,
103  $dataMap,
106  );
107  }
108 
114  public function ‪__construct(
115  array $dataMap,
118  ) {
119  $this->allDataMap = $dataMap;
120  $this->modifiedDataMap = $dataMap;
121  $this->backendUser = ‪$backendUser;
122  if (‪$referenceIndexUpdater === null) {
123  ‪$referenceIndexUpdater = GeneralUtility::makeInstance(ReferenceIndexUpdater::class);
124  }
125  $this->referenceIndexUpdater = ‪$referenceIndexUpdater;
126  }
127 
134  public function ‪process()
135  {
136  $iterations = 0;
137 
138  while (!empty($this->modifiedDataMap)) {
139  $this->nextItems = [];
140  foreach ($this->modifiedDataMap as $tableName => $idValues) {
141  $this->‪collectItems($tableName, $idValues);
142  }
143 
144  $this->modifiedDataMap = [];
145  if (empty($this->nextItems)) {
146  break;
147  }
148 
149  if ($iterations++ === 0) {
150  $this->‪sanitize($this->allItems);
151  }
152  $this->‪enrich($this->nextItems);
153  }
154 
155  $this->allDataMap = $this->‪purgeDataMap($this->allDataMap);
156  return ‪$this->allDataMap;
157  }
158 
165  protected function ‪purgeDataMap(array $dataMap): array
166  {
167  foreach ($dataMap as $tableName => $idValues) {
168  foreach ($idValues as $id => $values) {
169  if (empty($values)) {
170  unset($dataMap[$tableName][$id]);
171  }
172  }
173  if (empty($dataMap[$tableName])) {
174  unset($dataMap[$tableName]);
175  }
176  }
177  return $dataMap;
178  }
179 
186  protected function ‪collectItems(string $tableName, array $idValues)
187  {
188  if (!$this->‪isApplicable($tableName)) {
189  return;
190  }
191 
192  $fieldNames = [
193  'uid' => 'uid',
194  'l10n_state' => 'l10n_state',
195  'language' => ‪$GLOBALS['TCA'][$tableName]['ctrl']['languageField'],
196  'parent' => ‪$GLOBALS['TCA'][$tableName]['ctrl']['transOrigPointerField'],
197  ];
198  if (!empty(‪$GLOBALS['TCA'][$tableName]['ctrl']['translationSource'])) {
199  $fieldNames['source'] = ‪$GLOBALS['TCA'][$tableName]['ctrl']['translationSource'];
200  }
201 
202  $translationValues = $this->‪fetchTranslationValues(
203  $tableName,
204  $fieldNames,
205  $this->‪filterNewItemIds(
206  $tableName,
207  $this->‪filterNumericIds(array_keys($idValues))
208  )
209  );
210 
211  $dependencies = $this->‪fetchDependencies(
212  $tableName,
213  $this->‪filterNewItemIds($tableName, array_keys($idValues))
214  );
215 
216  foreach ($idValues as $id => $values) {
217  $item = $this->‪findItem($tableName, $id);
218  // build item if it has not been created in a previous iteration
219  if ($item === null) {
220  $recordValues = $translationValues[$id] ?? [];
221  $item = ‪DataMapItem::build(
222  $tableName,
223  $id,
224  $values,
225  $recordValues,
226  $fieldNames
227  );
228 
229  // elements using "all language" cannot be localized
230  if ($item->getLanguage() === -1) {
231  unset($item);
232  continue;
233  }
234  // must be any kind of localization and in connected mode
235  if ($item->getLanguage() > 0 && empty($item->getParent())) {
236  unset($item);
237  continue;
238  }
239  // add dependencies
240  if (!empty($dependencies[$id])) {
241  $item->setDependencies($dependencies[$id]);
242  }
243  }
244  // add item to $this->allItems and $this->nextItems
245  $this->‪addNextItem($item);
246  }
247  }
248 
255  protected function ‪sanitize(array $items)
256  {
257  foreach (['directChild', 'grandChild'] as $type) {
258  foreach ($this->‪filterItemsByType($type, $items) as $item) {
259  $this->‪sanitizeTranslationItem($item);
260  }
261  }
262  }
263 
269  protected function ‪enrich(array $items)
270  {
271  foreach (['directChild', 'grandChild'] as $type) {
272  foreach ($this->‪filterItemsByType($type, $items) as $item) {
273  foreach ($item->getApplicableScopes() as $scope) {
274  $fromId = $item->getIdForScope($scope);
275  $fieldNames = $this->‪getFieldNamesForItemScope($item, $scope, !$item->isNew());
276  $this->‪synchronizeTranslationItem($item, $fieldNames, $fromId);
277  }
278  $this->‪populateTranslationItem($item);
279  $this->‪finishTranslationItem($item);
280  }
281  }
282  foreach ($this->‪filterItemsByType('parent', $items) as $item) {
283  $this->‪populateTranslationItem($item);
284  }
285  }
286 
294  protected function ‪sanitizeTranslationItem(DataMapItem $item)
295  {
296  $fieldNames = [];
297  foreach ($item->getApplicableScopes() as $scope) {
298  $fieldNames = array_merge(
299  $fieldNames,
300  $this->‪getFieldNamesForItemScope($item, $scope, false)
301  );
302  }
303 
304  $fieldNameMap = array_combine($fieldNames, $fieldNames);
305  // separate fields, that are submitted in data-map, but not defined as custom
306  $this->sanitizationMap[$item->getTableName()][$item->getId()] = array_intersect_key(
307  $this->allDataMap[$item->getTableName()][$item->getId()],
308  $fieldNameMap
309  );
310  // remove fields, that are submitted in data-map, but not defined as custom
311  $this->allDataMap[$item->getTableName()][$item->getId()] = array_diff_key(
312  $this->allDataMap[$item->getTableName()][$item->getId()],
313  $fieldNameMap
314  );
315  }
316 
324  protected function ‪synchronizeTranslationItem(DataMapItem $item, array $fieldNames, $fromId)
325  {
326  if (empty($fieldNames)) {
327  return;
328  }
329 
330  $fieldNameList = 'uid,' . implode(',', $fieldNames);
331 
332  $fromRecord = ['uid' => $fromId];
334  $fromRecord = ‪BackendUtility::getRecordWSOL(
335  $item->getTableName(),
336  $fromId,
337  $fieldNameList
338  );
339  }
340 
341  $forRecord = [];
342  if (!$item->isNew()) {
344  $item->getTableName(),
345  $item->getId(),
346  $fieldNameList
347  );
348  }
349 
350  if (is_array($fromRecord) && is_array($forRecord)) {
351  foreach ($fieldNames as $fieldName) {
353  $item,
354  $fieldName,
355  $fromRecord,
356  $forRecord
357  );
358  }
359  }
360  }
361 
368  protected function ‪populateTranslationItem(‪DataMapItem $item)
369  {
371  foreach ($item->‪findDependencies($scope) as $dependentItem) {
372  // use suggested item, if it was submitted in data-map
373  $suggestedDependentItem = $this->‪findItem(
374  $dependentItem->getTableName(),
375  $dependentItem->getId()
376  );
377  if ($suggestedDependentItem !== null) {
378  $dependentItem = $suggestedDependentItem;
379  }
380  foreach ([$scope, ‪DataMapItem::SCOPE_EXCLUDE] as $dependentScope) {
381  $fieldNames = $this->‪getFieldNamesForItemScope(
382  $dependentItem,
383  $dependentScope,
384  false
385  );
387  $dependentItem,
388  $fieldNames,
389  $item->‪getId()
390  );
391  }
392  }
393  }
394  }
395 
401  protected function ‪finishTranslationItem(‪DataMapItem $item)
402  {
403  if (
404  $item->‪isParentType()
406  ) {
407  return;
408  }
409 
410  $this->allDataMap[$item->‪getTableName()][$item->‪getId()]['l10n_state'] = $item->‪getState()->‪export();
411  }
412 
421  protected function ‪synchronizeFieldValues(DataMapItem $item, string $fieldName, array $fromRecord, array $forRecord)
422  {
423  // skip if this field has been processed already, assumed that proper sanitation happened
424  if ($this->‪isSetInDataMap($item->getTableName(), $item->getId(), $fieldName)) {
425  return;
426  }
427 
428  $fromId = $fromRecord['uid'];
429  // retrieve value from in-memory data-map
430  if ($this->‪isSetInDataMap($item->getTableName(), $fromId, $fieldName)) {
431  $fromValue = $this->allDataMap[$item->getTableName()][$fromId][$fieldName];
432  } elseif (array_key_exists($fieldName, $fromRecord)) {
433  // retrieve value from record
434  $fromValue = $fromRecord[$fieldName];
435  } else {
436  // otherwise abort synchronization
437  return;
438  }
439 
440  // plain values
441  if (!$this->‪isRelationField($item->getTableName(), $fieldName)) {
442  $this->‪modifyDataMap(
443  $item->getTableName(),
444  $item->getId(),
445  [$fieldName => $fromValue]
446  );
447  } elseif (!$this->‪isInlineRelationField($item->getTableName(), $fieldName)) {
448  // direct relational values
449  $this->‪synchronizeDirectRelations($item, $fieldName, $fromRecord);
450  } else {
451  // inline relational values
452  $this->‪synchronizeInlineRelations($item, $fieldName, $fromRecord, $forRecord);
453  }
454  }
455 
463  protected function ‪synchronizeDirectRelations(DataMapItem $item, string $fieldName, array $fromRecord)
464  {
465  $specialTableName = null;
466  $configuration = ‪$GLOBALS['TCA'][$item->getTableName()]['columns'][$fieldName];
467  $isSpecialLanguageField = ($configuration['config']['special'] ?? null) === 'languages';
468 
469  $fromId = $fromRecord['uid'];
470  if ($this->‪isSetInDataMap($item->getTableName(), $fromId, $fieldName)) {
471  $fromValue = $this->allDataMap[$item->getTableName()][$fromId][$fieldName];
472  } else {
473  $fromValue = $fromRecord[$fieldName];
474  }
475 
476  // non-MM relations are stored as comma separated values, just use them
477  // if values are available in data-map already, just use them as well
478  if (
479  empty($configuration['config']['MM'])
480  || $this->‪isSetInDataMap($item->getTableName(), $fromId, $fieldName)
481  || $isSpecialLanguageField
482  ) {
483  $this->‪modifyDataMap(
484  $item->getTableName(),
485  $item->getId(),
486  [$fieldName => $fromValue]
487  );
488  return;
489  }
490  // resolve the language special table name
491  if ($isSpecialLanguageField) {
492  $specialTableName = 'sys_language';
493  }
494  // fetch MM relations from storage
495  $type = $configuration['config']['type'];
496  $manyToManyTable = $configuration['config']['MM'];
497  if ($type === 'group' && $configuration['config']['internal_type'] === 'db') {
498  $tableNames = trim($configuration['config']['allowed'] ?? '');
499  } elseif ($configuration['config']['type'] === 'select') {
500  $tableNames = ($specialTableName ?? $configuration['config']['foreign_table'] ?? '');
501  } else {
502  return;
503  }
504 
505  $relationHandler = $this->‪createRelationHandler();
506  $relationHandler->start(
507  '',
508  $tableNames,
509  $manyToManyTable,
510  $fromId,
511  $item->getTableName(),
512  $configuration['config']
513  );
514 
515  // provide list of relations, optionally prepended with table name
516  // e.g. "13,19,23" or "tt_content_27,tx_extension_items_28"
517  $this->‪modifyDataMap(
518  $item->getTableName(),
519  $item->getId(),
520  [$fieldName => implode(',', $relationHandler->getValueArray())]
521  );
522  }
523 
536  protected function ‪synchronizeInlineRelations(DataMapItem $item, string $fieldName, array $fromRecord, array $forRecord)
537  {
538  $configuration = ‪$GLOBALS['TCA'][$item->getTableName()]['columns'][$fieldName];
539  $isLocalizationModeExclude = ($configuration['l10n_mode'] ?? null) === 'exclude';
540  $foreignTableName = $configuration['config']['foreign_table'];
541 
542  $fieldNames = [
543  'language' => ‪$GLOBALS['TCA'][$foreignTableName]['ctrl']['languageField'] ?? null,
544  'parent' => ‪$GLOBALS['TCA'][$foreignTableName]['ctrl']['transOrigPointerField'] ?? null,
545  'source' => ‪$GLOBALS['TCA'][$foreignTableName]['ctrl']['translationSource'] ?? null,
546  ];
547  $isTranslatable = (!empty($fieldNames['language']) && !empty($fieldNames['parent']));
548  $isLocalized = !empty($item->getLanguage());
549 
550  $suggestedAncestorIds = $this->‪resolveSuggestedInlineRelations(
551  $item,
552  $fieldName,
553  $fromRecord
554  );
555  $persistedIds = $this->‪resolvePersistedInlineRelations(
556  $item,
557  $fieldName,
558  $forRecord
559  );
560 
561  // The dependent ID map points from language parent/source record to
562  // localization, thus keys: parents/sources & values: localizations
563  $dependentIdMap = $this->‪fetchDependentIdMap($foreignTableName, $suggestedAncestorIds, $item->getLanguage());
564  // filter incomplete structures - this is a drawback of DataHandler's remap stack, since
565  // just created IRRE translations still belong to the language parent - filter them out
566  $suggestedAncestorIds = array_diff($suggestedAncestorIds, array_values($dependentIdMap));
567  // compile element differences to be resolved
568  // remove elements that are persisted at the language translation, but not required anymore
569  $removeIds = array_diff($persistedIds, array_values($dependentIdMap));
570  // remove elements that are persisted at the language parent/source, but not required anymore
571  $removeAncestorIds = array_diff(array_keys($dependentIdMap), $suggestedAncestorIds);
572  // missing elements that are persisted at the language parent/source, but not translated yet
573  $missingAncestorIds = array_diff($suggestedAncestorIds, array_keys($dependentIdMap));
574  // persisted elements that should be copied or localized
575  $createAncestorIds = $this->‪filterNumericIds($missingAncestorIds);
576  // non-persisted elements that should be duplicated in data-map directly
577  $populateAncestorIds = array_diff($missingAncestorIds, $createAncestorIds);
578  // this desired state map defines the final result of child elements in their parent translation
579  $desiredIdMap = array_combine($suggestedAncestorIds, $suggestedAncestorIds);
580  // update existing translations in the desired state map
581  foreach ($dependentIdMap as $ancestorId => $translationId) {
582  if (isset($desiredIdMap[$ancestorId])) {
583  $desiredIdMap[$ancestorId] = $translationId;
584  }
585  }
586  // no children to be synchronized, but element order could have been changed
587  if (empty($removeAncestorIds) && empty($missingAncestorIds)) {
588  $this->‪modifyDataMap(
589  $item->getTableName(),
590  $item->getId(),
591  [$fieldName => implode(',', array_values($desiredIdMap))]
592  );
593  return;
594  }
595  // In case only missing elements shall be created, re-use previously sanitized
596  // values IF the relation parent item is new and the count of missing relations
597  // equals the count of previously sanitized relations.
598  // This is caused during copy processes, when the child relations
599  // already have been cloned in DataHandler::copyRecord_procBasedOnFieldType()
600  // without the possibility to resolve the initial connections at this point.
601  // Otherwise child relations would superfluously be duplicated again here.
602  // @todo Invalid manually injected child relations cannot be determined here
603  $sanitizedValue = $this->sanitizationMap[$item->getTableName()][$item->getId()][$fieldName] ?? null;
604  if (
605  !empty($missingAncestorIds) && $item->isNew() && $sanitizedValue !== null
606  && count(‪GeneralUtility::trimExplode(',', $sanitizedValue, true)) === count($missingAncestorIds)
607  ) {
608  $this->‪modifyDataMap(
609  $item->getTableName(),
610  $item->getId(),
611  [$fieldName => $sanitizedValue]
612  );
613  return;
614  }
615 
616  $localCommandMap = [];
617  foreach ($removeIds as $removeId) {
618  $localCommandMap[$foreignTableName][$removeId]['delete'] = true;
619  }
620  foreach ($removeAncestorIds as $removeAncestorId) {
621  $removeId = $dependentIdMap[$removeAncestorId];
622  $localCommandMap[$foreignTableName][$removeId]['delete'] = true;
623  }
624  foreach ($createAncestorIds as $createAncestorId) {
625  // if child table is not aware of localization, just copy
626  if ($isLocalizationModeExclude || !$isTranslatable) {
627  $localCommandMap[$foreignTableName][$createAncestorId]['copy'] = [
628  'target' => -$createAncestorId,
629  'ignoreLocalization' => true,
630  ];
631  } else {
632  // otherwise, trigger the localization process
633  $localCommandMap[$foreignTableName][$createAncestorId]['localize'] = $item->getLanguage();
634  }
635  }
636  // execute copy, localize and delete actions on persisted child records
637  if (!empty($localCommandMap)) {
638  $localDataHandler = GeneralUtility::makeInstance(DataHandler::class, $this->referenceIndexUpdater);
639  $localDataHandler->start([], $localCommandMap, $this->backendUser);
640  $localDataHandler->process_cmdmap();
641  // update copied or localized ids
642  foreach ($createAncestorIds as $createAncestorId) {
643  if (empty($localDataHandler->copyMappingArray_merged[$foreignTableName][$createAncestorId])) {
644  $additionalInformation = '';
645  if (!empty($localDataHandler->errorLog)) {
646  $additionalInformation = ', reason "'
647  . implode(', ', $localDataHandler->errorLog) . '"';
648  }
649  throw new \RuntimeException(
650  'Child record was not processed' . $additionalInformation,
651  1486233164
652  );
653  }
654  $newLocalizationId = $localDataHandler->copyMappingArray_merged[$foreignTableName][$createAncestorId];
655  $newLocalizationId = $localDataHandler->getAutoVersionId($foreignTableName, $newLocalizationId) ?? $newLocalizationId;
656  $desiredIdMap[$createAncestorId] = $newLocalizationId;
657  // apply localization references to l10n_mode=exclude children
658  // (without keeping their reference to their origin, synchronization is not possible)
659  if ($isLocalizationModeExclude && $isTranslatable && $isLocalized) {
660  $adjustCopiedValues = $this->‪applyLocalizationReferences(
661  $foreignTableName,
662  $createAncestorId,
663  $item->getLanguage(),
664  $fieldNames,
665  []
666  );
667  $this->‪modifyDataMap(
668  $foreignTableName,
669  $newLocalizationId,
670  $adjustCopiedValues
671  );
672  }
673  }
674  }
675  // populate new child records in data-map
676  if (!empty($populateAncestorIds)) {
677  foreach ($populateAncestorIds as $populateAncestorId) {
678  $newLocalizationId = ‪StringUtility::getUniqueId('NEW');
679  $desiredIdMap[$populateAncestorId] = $newLocalizationId;
680  $duplicatedValues = $this->allDataMap[$foreignTableName][$populateAncestorId] ?? [];
681  // applies localization references to given raw data-map item
682  if ($isTranslatable && $isLocalized) {
683  $duplicatedValues = $this->‪applyLocalizationReferences(
684  $foreignTableName,
685  $populateAncestorId,
686  $item->getLanguage(),
687  $fieldNames,
688  $duplicatedValues
689  );
690  }
691  // prefixes language title if applicable for the accordant field name in raw data-map item
692  if ($isTranslatable && $isLocalized && !$isLocalizationModeExclude) {
693  $duplicatedValues = $this->‪prefixLanguageTitle(
694  $foreignTableName,
695  $populateAncestorId,
696  $item->getLanguage(),
697  $duplicatedValues
698  );
699  }
700  $this->‪modifyDataMap(
701  $foreignTableName,
702  $newLocalizationId,
703  $duplicatedValues
704  );
705  }
706  }
707  // update inline parent field references - required to update pointer fields
708  $this->‪modifyDataMap(
709  $item->getTableName(),
710  $item->getId(),
711  [$fieldName => implode(',', array_values($desiredIdMap))]
712  );
713  }
714 
725  protected function ‪resolveSuggestedInlineRelations(DataMapItem $item, string $fieldName, array $fromRecord): array
726  {
727  $suggestedAncestorIds = [];
728  $fromId = $fromRecord['uid'];
729  $configuration = ‪$GLOBALS['TCA'][$item->getTableName()]['columns'][$fieldName];
730  $foreignTableName = $configuration['config']['foreign_table'];
731  $manyToManyTable = ($configuration['config']['MM'] ?? '');
732 
733  // determine suggested elements of either translation parent or source record
734  // from data-map, in case the accordant language parent/source record was modified
735  if ($this->‪isSetInDataMap($item->getTableName(), $fromId, $fieldName)) {
736  $suggestedAncestorIds = ‪GeneralUtility::trimExplode(
737  ',',
738  $this->allDataMap[$item->getTableName()][$fromId][$fieldName],
739  true
740  );
741  } elseif (‪MathUtility::canBeInterpretedAsInteger($fromId)) {
742  // determine suggested elements of either translation parent or source record from storage
743  $relationHandler = $this->‪createRelationHandler();
744  $relationHandler->start(
745  $fromRecord[$fieldName],
746  $foreignTableName,
747  $manyToManyTable,
748  $fromId,
749  $item->getTableName(),
750  $configuration['config']
751  );
752  $suggestedAncestorIds = $this->‪mapRelationItemId($relationHandler->itemArray);
753  }
754 
755  return array_filter($suggestedAncestorIds);
756  }
757 
766  private function ‪resolvePersistedInlineRelations(DataMapItem $item, string $fieldName, array $forRecord): array
767  {
768  $persistedIds = [];
769  $configuration = ‪$GLOBALS['TCA'][$item->getTableName()]['columns'][$fieldName];
770  $foreignTableName = $configuration['config']['foreign_table'];
771  $manyToManyTable = ($configuration['config']['MM'] ?? '');
772 
773  // determine persisted elements for the current data-map item
774  if (!$item->isNew()) {
775  $relationHandler = $this->‪createRelationHandler();
776  $relationHandler->start(
777  $forRecord[$fieldName] ?? '',
778  $foreignTableName,
779  $manyToManyTable,
780  $item->getId(),
781  $item->getTableName(),
782  $configuration['config']
783  );
784  $persistedIds = $this->‪mapRelationItemId($relationHandler->itemArray);
785  }
786 
787  return array_filter($persistedIds);
788  }
789 
800  protected function ‪isSetInDataMap(string $tableName, $id, string $fieldName)
801  {
802  return
803  // directly look-up field name
804  isset($this->allDataMap[$tableName][$id][$fieldName])
805  // check existence of field name as key for null values
806  || isset($this->allDataMap[$tableName][$id])
807  && is_array($this->allDataMap[$tableName][$id])
808  && array_key_exists($fieldName, $this->allDataMap[$tableName][$id]);
809  }
810 
821  protected function ‪modifyDataMap(string $tableName, $id, array $values)
822  {
823  // avoid superfluous iterations by data-map changes with values
824  // that actually have not been changed and were available already
825  $sameValues = array_intersect_assoc(
826  $this->allDataMap[$tableName][$id] ?? [],
827  $values
828  );
829  if (!empty($sameValues)) {
830  $fieldNames = implode(', ', array_keys($sameValues));
831  throw new \RuntimeException(
832  sprintf(
833  'Issued data-map change for table %s with same values '
834  . 'for these fields names %s',
835  $tableName,
836  $fieldNames
837  ),
838  1488634845
839  );
840  }
841 
842  $this->modifiedDataMap[$tableName][$id] = array_merge(
843  $this->modifiedDataMap[$tableName][$id] ?? [],
844  $values
845  );
846  $this->allDataMap[$tableName][$id] = array_merge(
847  $this->allDataMap[$tableName][$id] ?? [],
848  $values
849  );
850  }
851 
855  protected function ‪addNextItem(‪DataMapItem $item)
856  {
857  $identifier = $item->‪getTableName() . ':' . $item->‪getId();
858  if (!isset($this->allItems[$identifier])) {
859  $this->allItems[$identifier] = $item;
860  }
861  $this->nextItems[$identifier] = $item;
862  }
863 
873  protected function ‪fetchTranslationValues(string $tableName, array $fieldNames, array $ids)
874  {
875  if (empty($ids)) {
876  return [];
877  }
878 
879  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
880  ->getQueryBuilderForTable($tableName);
881  $queryBuilder->getRestrictions()
882  ->removeAll()
883  ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
884  ->add(GeneralUtility::makeInstance(WorkspaceRestriction::class, $this->backendUser->workspace));
885  $statement = $queryBuilder
886  ->select(...array_values($fieldNames))
887  ->from($tableName)
888  ->where(
889  $queryBuilder->expr()->in(
890  'uid',
891  $queryBuilder->createNamedParameter($ids, Connection::PARAM_INT_ARRAY)
892  )
893  )
894  ->execute();
895 
896  $translationValues = [];
897  foreach ($statement as $record) {
898  $translationValues[$record['uid']] = $record;
899  }
900  return $translationValues;
901  }
902 
920  protected function ‪fetchDependencies(string $tableName, array $ids)
921  {
922  if (empty($ids) || !‪BackendUtility::isTableLocalizable($tableName)) {
923  return [];
924  }
925 
926  $fieldNames = [
927  'uid' => 'uid',
928  'l10n_state' => 'l10n_state',
929  'language' => ‪$GLOBALS['TCA'][$tableName]['ctrl']['languageField'],
930  'parent' => ‪$GLOBALS['TCA'][$tableName]['ctrl']['transOrigPointerField'],
931  ];
932  if (!empty(‪$GLOBALS['TCA'][$tableName]['ctrl']['translationSource'])) {
933  $fieldNames['source'] = ‪$GLOBALS['TCA'][$tableName]['ctrl']['translationSource'];
934  }
935  $fieldNamesMap = array_combine($fieldNames, $fieldNames);
936 
937  $persistedIds = $this->‪filterNumericIds($ids);
938  $createdIds = array_diff($ids, $persistedIds);
939  $dependentElements = $this->‪fetchDependentElements($tableName, $persistedIds, $fieldNames);
940 
941  foreach ($createdIds as $createdId) {
942  $data = $this->allDataMap[$tableName][$createdId] ?? null;
943  if ($data === null) {
944  continue;
945  }
946  $dependentElements[] = array_merge(
947  ['uid' => $createdId],
948  array_intersect_key($data, $fieldNamesMap)
949  );
950  }
951 
952  $dependencyMap = [];
953  foreach ($dependentElements as $dependentElement) {
954  $dependentItem = ‪DataMapItem::build(
955  $tableName,
956  $dependentElement['uid'],
957  [],
958  $dependentElement,
959  $fieldNames
960  );
961 
962  if ($dependentItem->isDirectChildType()) {
963  $dependencyMap[$dependentItem->getParent()][‪State::STATE_PARENT][] = $dependentItem;
964  }
965  if ($dependentItem->isGrandChildType()) {
966  $dependencyMap[$dependentItem->getParent()][‪State::STATE_PARENT][] = $dependentItem;
967  $dependencyMap[$dependentItem->getSource()][‪State::STATE_SOURCE][] = $dependentItem;
968  }
969  }
970  return $dependencyMap;
971  }
972 
997  protected function ‪fetchDependentIdMap(string $tableName, array $ids, int $desiredLanguage)
998  {
999  $ancestorIdMap = [];
1000  if (empty($ids)) {
1001  return [];
1002  }
1003 
1004  $ids = $this->‪filterNumericIds($ids);
1005  $isTranslatable = ‪BackendUtility::isTableLocalizable($tableName);
1006  $originFieldName = (‪$GLOBALS['TCA'][$tableName]['ctrl']['origUid'] ?? null);
1007 
1008  if (!$isTranslatable && $originFieldName === null) {
1009  // @todo Possibly throw an error, since pointing to original entity is not possible (via origin/parent)
1010  return [];
1011  }
1012 
1013  if ($isTranslatable) {
1014  $fieldNames = [
1015  'uid' => 'uid',
1016  'l10n_state' => 'l10n_state',
1017  'language' => ‪$GLOBALS['TCA'][$tableName]['ctrl']['languageField'],
1018  'parent' => ‪$GLOBALS['TCA'][$tableName]['ctrl']['transOrigPointerField'],
1019  ];
1020  if (!empty(‪$GLOBALS['TCA'][$tableName]['ctrl']['translationSource'])) {
1021  $fieldNames['source'] = ‪$GLOBALS['TCA'][$tableName]['ctrl']['translationSource'];
1022  }
1023  } else {
1024  $fieldNames = [
1025  'uid' => 'uid',
1026  'origin' => $originFieldName,
1027  ];
1028  }
1029 
1030  $fetchIds = $ids;
1031  if ($isTranslatable) {
1032  // expand search criteria via parent and source elements
1033  $translationValues = $this->‪fetchTranslationValues($tableName, $fieldNames, $ids);
1034  $ancestorIdMap = $this->‪buildElementAncestorIdMap($fieldNames, $translationValues);
1035  $fetchIds = array_unique(array_merge($ids, array_keys($ancestorIdMap)));
1036  }
1037 
1038  $dependentElements = $this->‪fetchDependentElements($tableName, $fetchIds, $fieldNames);
1039 
1040  $dependentIdMap = [];
1041  foreach ($dependentElements as $dependentElement) {
1042  $dependentId = $dependentElement['uid'];
1043  // implicit: use origin pointer if table cannot be translated
1044  if (!$isTranslatable) {
1045  $ancestorId = (int)$dependentElement[$fieldNames['origin']];
1046  // only consider element if it reflects the desired language
1047  } elseif ((int)$dependentElement[$fieldNames['language']] === $desiredLanguage) {
1048  $ancestorId = $this->‪resolveAncestorId($fieldNames, $dependentElement);
1049  } else {
1050  // otherwise skip the element completely
1051  continue;
1052  }
1053  // only keep ancestors that were initially requested before expanding
1054  if (in_array($ancestorId, $ids, true)) {
1055  $dependentIdMap[$ancestorId] = $dependentId;
1056  } elseif (!empty($ancestorIdMap[$ancestorId])) {
1057  // resolve from previously expanded search criteria
1058  $possibleChainedIds = array_intersect(
1059  $ids,
1060  $ancestorIdMap[$ancestorId]
1061  );
1062  if (!empty($possibleChainedIds)) {
1063  $ancestorId = $possibleChainedIds[0];
1064  $dependentIdMap[$ancestorId] = $dependentId;
1065  }
1066  }
1067  }
1068  return $dependentIdMap;
1069  }
1070 
1082  protected function ‪fetchDependentElements(string $tableName, array $ids, array $fieldNames)
1083  {
1084  if (empty($ids)) {
1085  return [];
1086  }
1087 
1088  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
1089  ->getQueryBuilderForTable($tableName);
1090  $queryBuilder->getRestrictions()
1091  ->removeAll()
1092  ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
1093  ->add(GeneralUtility::makeInstance(WorkspaceRestriction::class, $this->backendUser->workspace));
1094 
1095  $zeroParameter = $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT);
1096  $ids = $this->‪filterNumericIds($ids);
1097  $idsParameter = $queryBuilder->createNamedParameter($ids, Connection::PARAM_INT_ARRAY);
1098 
1099  // fetch by language dependency
1100  if (!empty($fieldNames['language']) && !empty($fieldNames['parent'])) {
1101  $ancestorPredicates = [
1102  $queryBuilder->expr()->in(
1103  $fieldNames['parent'],
1104  $idsParameter
1105  )
1106  ];
1107  if (!empty($fieldNames['source'])) {
1108  $ancestorPredicates[] = $queryBuilder->expr()->in(
1109  $fieldNames['source'],
1110  $idsParameter
1111  );
1112  }
1113  $predicates = [
1114  // must be any kind of localization
1115  $queryBuilder->expr()->gt(
1116  $fieldNames['language'],
1117  $zeroParameter
1118  ),
1119  // must be in connected mode
1120  $queryBuilder->expr()->gt(
1121  $fieldNames['parent'],
1122  $zeroParameter
1123  ),
1124  // any parent or source pointers
1125  $queryBuilder->expr()->orX(...$ancestorPredicates),
1126  ];
1127  } elseif (!empty($fieldNames['origin'])) {
1128  // fetch by origin dependency ("copied from")
1129  $predicates = [
1130  $queryBuilder->expr()->in(
1131  $fieldNames['origin'],
1132  $idsParameter
1133  )
1134  ];
1135  } else {
1136  // otherwise: stop execution
1137  throw new \InvalidArgumentException(
1138  'Invalid combination of query field names given',
1139  1487192370
1140  );
1141  }
1142 
1143  $statement = $queryBuilder
1144  ->select(...array_values($fieldNames))
1145  ->from($tableName)
1146  ->andWhere(...$predicates)
1147  ->execute();
1148 
1149  $dependentElements = [];
1150  foreach ($statement as $record) {
1151  $dependentElements[] = $record;
1152  }
1153  return $dependentElements;
1154  }
1155 
1163  protected function ‪filterItemsByType(string $type, array $items)
1164  {
1165  return array_filter(
1166  $items,
1167  function (DataMapItem $item) use ($type) {
1168  return $item->getType() === $type;
1169  }
1170  );
1171  }
1179  protected function ‪filterNumericIds(array $ids)
1180  {
1181  $ids = array_filter(
1182  $ids,
1183  function ($id) {
1185  }
1186  );
1187  return array_map('intval', $ids);
1188  }
1189 
1197  protected function ‪filterNewItemIds(string $tableName, array $ids)
1198  {
1199  return array_filter(
1200  $ids,
1201  function ($id) use ($tableName) {
1202  return $this->‪findItem($tableName, $id) === null;
1203  }
1204  );
1205  }
1213  protected function ‪mapRelationItemId(array $relationItems)
1214  {
1215  return array_map(
1216  function (array $relationItem) {
1217  return (int)$relationItem['id'];
1218  },
1219  $relationItems
1220  );
1221  }
1222 
1228  protected function ‪resolveAncestorId(array $fieldNames, array $element)
1229  {
1230  $sourceName = $fieldNames['source'] ?? null;
1231  if ($sourceName !== null && !empty($element[$sourceName])) {
1232  // implicit: use source pointer if given (not empty)
1233  return (int)$element[$sourceName];
1234  }
1235  $parentName = $fieldNames['parent'] ?? null;
1236  if ($parentName !== null && !empty($element[$parentName])) {
1237  // implicit: use parent pointer if given (not empty)
1238  return (int)$element[$parentName];
1239  }
1240  return null;
1241  }
1242 
1253  protected function ‪buildElementAncestorIdMap(array $fieldNames, array $elements)
1254  {
1255  $ancestorIdMap = [];
1256  foreach ($elements as $element) {
1257  $ancestorId = $this->‪resolveAncestorId($fieldNames, $element);
1258  if ($ancestorId !== null) {
1259  $ancestorIdMap[$ancestorId][] = (int)$element['uid'];
1260  }
1261  }
1262  return $ancestorIdMap;
1263  }
1264 
1272  protected function ‪findItem(string $tableName, $id)
1273  {
1274  return $this->allItems[$tableName . ':' . $id] ?? null;
1275  }
1276 
1289  protected function ‪duplicateFromDataMap(string $tableName, $fromId, int $language, array $fieldNames, bool $localize): array
1290  {
1291  $data = $this->allDataMap[$tableName][$fromId] ?? [];
1292  // just return if localization cannot be applied
1293  if (empty($language) || !$localize) {
1294  return $data;
1295  }
1296  $data = $this->‪applyLocalizationReferences($tableName, $fromId, $language, $fieldNames, $data);
1297  $data = $this->‪prefixLanguageTitle($tableName, $fromId, $language, $data);
1298  return $data;
1299  }
1300 
1311  protected function ‪applyLocalizationReferences(string $tableName, $fromId, int $language, array $fieldNames, array $data): array
1312  {
1313  // just return if localization cannot be applied
1314  if (empty($language)) {
1315  return $data;
1316  }
1317 
1318  // apply `languageField`, e.g. `sys_language_uid`
1319  $data[$fieldNames['language']] = $language;
1320  // apply `transOrigPointerField`, e.g. `l10n_parent`
1321  if (empty($data[$fieldNames['parent']])) {
1322  // @todo Only $id used in TCA type 'select' is resolved in DataHandler's remapStack
1323  $data[$fieldNames['parent']] = $fromId;
1324  }
1325  // apply `translationSource`, e.g. `l10n_source`
1326  if (!empty($fieldNames['source'])) {
1327  // @todo Not sure, whether $id is resolved in DataHandler's remapStack
1328  $data[$fieldNames['source']] = $fromId;
1329  }
1330  // unset field names that are expected to be handled in this processor
1331  foreach ($this->‪getFieldNamesToBeHandled($tableName) as $fieldName) {
1332  unset($data[$fieldName]);
1333  }
1334 
1335  return $data;
1336  }
1337 
1347  protected function ‪prefixLanguageTitle(string $tableName, $fromId, int $language, array $data): array
1348  {
1349  $prefix = '';
1350  $prefixFieldNames = array_intersect(
1351  array_keys($data),
1352  $this->‪getPrefixLanguageTitleFieldNames($tableName)
1353  );
1354  if (empty($prefixFieldNames)) {
1355  return $data;
1356  }
1357 
1358  $languageService = $this->‪getLanguageService();
1359  $languageRecord = ‪BackendUtility::getRecord('sys_language', $language, 'title');
1360  [$pageId] = ‪BackendUtility::getTSCpid($tableName, $fromId, $data['pid'] ?? null);
1361 
1362  $tsConfigTranslateToMessage = ‪BackendUtility::getPagesTSconfig($pageId)['TCEMAIN.']['translateToMessage'] ?? '';
1363  if (!empty($tsConfigTranslateToMessage)) {
1364  $prefix = $tsConfigTranslateToMessage;
1365  if ($languageService !== null) {
1366  $prefix = $languageService->sL($prefix);
1367  }
1368  $prefix = sprintf($prefix, $languageRecord['title']);
1369  }
1370  if (empty($prefix)) {
1371  $prefix = 'Translate to ' . $languageRecord['title'] . ':';
1372  }
1373 
1374  foreach ($prefixFieldNames as $prefixFieldName) {
1375  // @todo The hook in DataHandler is not applied here
1376  $data[$prefixFieldName] = '[' . $prefix . '] ' . $data[$prefixFieldName];
1377  }
1378 
1379  return $data;
1380  }
1381 
1390  protected function ‪getFieldNamesForItemScope(
1391  DataMapItem $item,
1392  string $scope,
1393  bool $modified
1394  ) {
1395  if (
1396  $scope === ‪DataMapItem::SCOPE_PARENT
1397  || $scope === ‪DataMapItem::SCOPE_SOURCE
1398  ) {
1399  if (!‪State::isApplicable($item->getTableName())) {
1400  return [];
1401  }
1402  return $item->getState()->filterFieldNames($scope, $modified);
1403  }
1404  if ($scope === ‪DataMapItem::SCOPE_EXCLUDE) {
1406  $item->getTableName()
1407  );
1408  }
1409  return [];
1410  }
1418  protected function ‪getLocalizationModeExcludeFieldNames(string $tableName)
1419  {
1420  $localizationExcludeFieldNames = [];
1421  if (empty(‪$GLOBALS['TCA'][$tableName]['columns'])) {
1422  return $localizationExcludeFieldNames;
1423  }
1424 
1425  foreach (‪$GLOBALS['TCA'][$tableName]['columns'] as $fieldName => $configuration) {
1426  if (($configuration['l10n_mode'] ?? null) === 'exclude'
1427  && ($configuration['config']['type'] ?? null) !== 'none'
1428  ) {
1429  $localizationExcludeFieldNames[] = $fieldName;
1430  }
1431  }
1432 
1433  return $localizationExcludeFieldNames;
1434  }
1435 
1443  protected function ‪getFieldNamesToBeHandled(string $tableName)
1444  {
1445  return array_merge(
1446  ‪State::getFieldNames($tableName),
1447  $this->‪getLocalizationModeExcludeFieldNames($tableName)
1448  );
1449  }
1457  protected function ‪getPrefixLanguageTitleFieldNames(string $tableName)
1458  {
1459  $prefixLanguageTitleFieldNames = [];
1460  if (empty(‪$GLOBALS['TCA'][$tableName]['columns'])) {
1461  return $prefixLanguageTitleFieldNames;
1462  }
1463 
1464  foreach (‪$GLOBALS['TCA'][$tableName]['columns'] as $fieldName => $configuration) {
1465  $type = $configuration['config']['type'] ?? null;
1466  if (
1467  ($configuration['l10n_mode'] ?? null) === 'prefixLangTitle'
1468  && ($type === 'input' || $type === 'text')
1469  ) {
1470  $prefixLanguageTitleFieldNames[] = $fieldName;
1471  }
1472  }
1473 
1474  return $prefixLanguageTitleFieldNames;
1475  }
1476 
1484  protected function ‪isRelationField(string $tableName, string $fieldName): bool
1485  {
1486  if (empty(‪$GLOBALS['TCA'][$tableName]['columns'][$fieldName]['config']['type'])) {
1487  return false;
1488  }
1489 
1490  $configuration = ‪$GLOBALS['TCA'][$tableName]['columns'][$fieldName]['config'];
1491 
1492  return
1493  $configuration['type'] === 'group'
1494  && ($configuration['internal_type'] ?? null) === 'db'
1495  && !empty($configuration['allowed'])
1496  || $configuration['type'] === 'select'
1497  && (
1498  !empty($configuration['foreign_table'])
1499  && !empty(‪$GLOBALS['TCA'][$configuration['foreign_table']])
1500  || ($configuration['special'] ?? null) === 'languages'
1501  )
1502  || $this->‪isInlineRelationField($tableName, $fieldName)
1503  ;
1504  }
1505 
1513  protected function ‪isInlineRelationField(string $tableName, string $fieldName): bool
1514  {
1515  if (empty(‪$GLOBALS['TCA'][$tableName]['columns'][$fieldName]['config']['type'])) {
1516  return false;
1517  }
1518 
1519  $configuration = ‪$GLOBALS['TCA'][$tableName]['columns'][$fieldName]['config'];
1520 
1521  return
1522  $configuration['type'] === 'inline'
1523  && !empty($configuration['foreign_table'])
1524  && !empty(‪$GLOBALS['TCA'][$configuration['foreign_table']])
1525  ;
1526  }
1527 
1535  protected function ‪isApplicable(string $tableName): bool
1536  {
1537  return
1538  ‪State::isApplicable($tableName)
1540  && count($this->‪getLocalizationModeExcludeFieldNames($tableName)) > 0
1541  ;
1542  }
1543 
1547  protected function ‪createRelationHandler()
1548  {
1549  $relationHandler = GeneralUtility::makeInstance(RelationHandler::class);
1550  $relationHandler->setWorkspaceId($this->backendUser->workspace);
1551  return $relationHandler;
1552  }
1553 
1557  protected function ‪getLanguageService()
1558  {
1559  return ‪$GLOBALS['LANG'] ?? null;
1560  }
1561 }
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapItem\getType
‪string getType()
Definition: DataMapItem.php:229
‪TYPO3\CMS\Core\DataHandling\DataHandler
Definition: DataHandler.php:84
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor\applyLocalizationReferences
‪array applyLocalizationReferences(string $tableName, $fromId, int $language, array $fieldNames, array $data)
Definition: DataMapProcessor.php:1304
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor\createRelationHandler
‪RelationHandler createRelationHandler()
Definition: DataMapProcessor.php:1540
‪TYPO3\CMS\Core\Utility\MathUtility\canBeInterpretedAsInteger
‪static bool canBeInterpretedAsInteger($var)
Definition: MathUtility.php:74
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor\resolveSuggestedInlineRelations
‪int[] string[] resolveSuggestedInlineRelations(DataMapItem $item, string $fieldName, array $fromRecord)
Definition: DataMapProcessor.php:718
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor\instance
‪static DataMapProcessor instance(array $dataMap, BackendUserAuthentication $backendUser, ReferenceIndexUpdater $referenceIndexUpdater=null)
Definition: DataMapProcessor.php:89
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor\purgeDataMap
‪array purgeDataMap(array $dataMap)
Definition: DataMapProcessor.php:158
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor\sanitize
‪sanitize(array $items)
Definition: DataMapProcessor.php:248
‪TYPO3\CMS\Core\Database\RelationHandler
Definition: RelationHandler.php:35
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapItem\findDependencies
‪DataMapItem[] findDependencies(string $scope)
Definition: DataMapItem.php:370
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor\__construct
‪__construct(array $dataMap, BackendUserAuthentication $backendUser, ReferenceIndexUpdater $referenceIndexUpdater=null)
Definition: DataMapProcessor.php:107
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor\getFieldNamesToBeHandled
‪string[] getFieldNamesToBeHandled(string $tableName)
Definition: DataMapProcessor.php:1436
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor\filterNewItemIds
‪array filterNewItemIds(string $tableName, array $ids)
Definition: DataMapProcessor.php:1190
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor\resolvePersistedInlineRelations
‪int[] resolvePersistedInlineRelations(DataMapItem $item, string $fieldName, array $forRecord)
Definition: DataMapProcessor.php:759
‪TYPO3\CMS\Core\DataHandling\Localization\State\filterFieldNames
‪string[] filterFieldNames(string $desiredState, bool $modified=false)
Definition: State.php:282
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor\$sanitizationMap
‪array $sanitizationMap
Definition: DataMapProcessor.php:63
‪TYPO3\CMS\Core\DataHandling\Localization\State\STATE_PARENT
‪const STATE_PARENT
Definition: State.php:26
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor\$allItems
‪DataMapItem[] $allItems
Definition: DataMapProcessor.php:75
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor\getLanguageService
‪LanguageService null getLanguageService()
Definition: DataMapProcessor.php:1550
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor\fetchDependentElements
‪array fetchDependentElements(string $tableName, array $ids, array $fieldNames)
Definition: DataMapProcessor.php:1075
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor\getFieldNamesForItemScope
‪string[] getFieldNamesForItemScope(DataMapItem $item, string $scope, bool $modified)
Definition: DataMapProcessor.php:1383
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor\$nextItems
‪DataMapItem[] $nextItems
Definition: DataMapProcessor.php:79
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor\fetchDependentIdMap
‪array fetchDependentIdMap(string $tableName, array $ids, int $desiredLanguage)
Definition: DataMapProcessor.php:990
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor\getPrefixLanguageTitleFieldNames
‪array getPrefixLanguageTitleFieldNames(string $tableName)
Definition: DataMapProcessor.php:1450
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapItem\getTableName
‪string getTableName()
Definition: DataMapItem.php:148
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor\buildElementAncestorIdMap
‪array buildElementAncestorIdMap(array $fieldNames, array $elements)
Definition: DataMapProcessor.php:1246
‪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:913
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor\modifyDataMap
‪modifyDataMap(string $tableName, $id, array $values)
Definition: DataMapProcessor.php:814
‪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:52
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor\populateTranslationItem
‪populateTranslationItem(DataMapItem $item)
Definition: DataMapProcessor.php:361
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor\collectItems
‪collectItems(string $tableName, array $idValues)
Definition: DataMapProcessor.php:179
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor\enrich
‪enrich(array $items)
Definition: DataMapProcessor.php:262
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor\$modifiedDataMap
‪array $modifiedDataMap
Definition: DataMapProcessor.php:59
‪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:866
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor\prefixLanguageTitle
‪array prefixLanguageTitle(string $tableName, $fromId, int $language, array $data)
Definition: DataMapProcessor.php:1340
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor\mapRelationItemId
‪string[] mapRelationItemId(array $relationItems)
Definition: DataMapProcessor.php:1206
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor\process
‪array process()
Definition: DataMapProcessor.php:127
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor\duplicateFromDataMap
‪array duplicateFromDataMap(string $tableName, $fromId, int $language, array $fieldNames, bool $localize)
Definition: DataMapProcessor.php:1282
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor\$backendUser
‪BackendUserAuthentication $backendUser
Definition: DataMapProcessor.php:67
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapItem\isNew
‪bool isNew()
Definition: DataMapItem.php:221
‪TYPO3\CMS\Backend\Utility\BackendUtility\getPagesTSconfig
‪static array getPagesTSconfig($id)
Definition: BackendUtility.php:698
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapItem\build
‪static object DataMapItem build(string $tableName, $id, array $suggestedValues, array $persistedValues, array $configurationFieldNames)
Definition: DataMapItem.php:94
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor\isSetInDataMap
‪bool isSetInDataMap(string $tableName, $id, string $fieldName)
Definition: DataMapProcessor.php:793
‪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:1156
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor\addNextItem
‪addNextItem(DataMapItem $item)
Definition: DataMapProcessor.php:848
‪TYPO3\CMS\Core\DataHandling\Localization
Definition: DataMapItem.php:16
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor\isApplicable
‪bool isApplicable(string $tableName)
Definition: DataMapProcessor.php:1528
‪TYPO3\CMS\Backend\Utility\BackendUtility
Definition: BackendUtility.php:75
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor\filterNumericIds
‪int[] filterNumericIds(array $ids)
Definition: DataMapProcessor.php:1172
‪TYPO3\CMS\Backend\Utility\BackendUtility\getRecordWSOL
‪static array getRecordWSOL( $table, $uid, $fields=' *', $where='', $useDeleteClause=true, $unsetMovePointers=false)
Definition: BackendUtility.php:139
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor\synchronizeInlineRelations
‪synchronizeInlineRelations(DataMapItem $item, string $fieldName, array $fromRecord, array $forRecord)
Definition: DataMapProcessor.php:529
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor\isInlineRelationField
‪bool isInlineRelationField(string $tableName, string $fieldName)
Definition: DataMapProcessor.php:1506
‪TYPO3\CMS\Backend\Utility\BackendUtility\getRecord
‪static array null getRecord($table, $uid, $fields=' *', $where='', $useDeleteClause=true)
Definition: BackendUtility.php:95
‪TYPO3\CMS\Core\Utility\GeneralUtility\trimExplode
‪static string[] trimExplode($delim, $string, $removeEmptyValues=false, $limit=0)
Definition: GeneralUtility.php:1059
‪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:456
‪TYPO3\CMS\Core\Database\Connection
Definition: Connection.php:36
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor\$referenceIndexUpdater
‪ReferenceIndexUpdater $referenceIndexUpdater
Definition: DataMapProcessor.php:71
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapItem\getState
‪State getState()
Definition: DataMapItem.php:276
‪TYPO3\CMS\Core\Utility\StringUtility\getUniqueId
‪static string getUniqueId($prefix='')
Definition: StringUtility.php:92
‪TYPO3\CMS\Backend\Utility\BackendUtility\getTSCpid
‪static array getTSCpid($table, $uid, $pid)
Definition: BackendUtility.php:3208
‪TYPO3\CMS\Backend\Utility\BackendUtility\isTableLocalizable
‪static bool isTableLocalizable($table)
Definition: BackendUtility.php:578
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapItem\getLanguage
‪string int getLanguage()
Definition: DataMapItem.php:287
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor\resolveAncestorId
‪int null resolveAncestorId(array $fieldNames, array $element)
Definition: DataMapProcessor.php:1221
‪$GLOBALS
‪$GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['adminpanel']['modules']
Definition: ext_localconf.php:5
‪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:1477
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor\$allDataMap
‪array $allDataMap
Definition: DataMapProcessor.php:55
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor\findItem
‪DataMapItem null findItem(string $tableName, $id)
Definition: DataMapProcessor.php:1265
‪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:414
‪TYPO3\CMS\Core\Database\ConnectionPool
Definition: ConnectionPool.php:46
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor\sanitizeTranslationItem
‪sanitizeTranslationItem(DataMapItem $item)
Definition: DataMapProcessor.php:287
‪TYPO3\CMS\Core\Utility\GeneralUtility
Definition: GeneralUtility.php:46
‪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:317
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor\getLocalizationModeExcludeFieldNames
‪string[] getLocalizationModeExcludeFieldNames(string $tableName)
Definition: DataMapProcessor.php:1411
‪TYPO3\CMS\Core\DataHandling\ReferenceIndexUpdater
Definition: ReferenceIndexUpdater.php:34
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor\finishTranslationItem
‪finishTranslationItem(DataMapItem $item)
Definition: DataMapProcessor.php:394
‪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:39