‪TYPO3CMS  9.5
DataMapProcessor.php
Go to the documentation of this file.
1 <?php
3 
4 /*
5  * This file is part of the TYPO3 CMS project.
6  *
7  * It is free software; you can redistribute it and/or modify it under
8  * the terms of the GNU General Public License, either version 2
9  * of the License, or any later version.
10  *
11  * For the full copyright and license information, please read the
12  * LICENSE.txt file that was distributed with this source code.
13  *
14  * The TYPO3 project - inspiring people to share!
15  */
16 
29 
48 {
52  protected ‪$allDataMap = [];
53 
57  protected ‪$modifiedDataMap = [];
58 
62  protected ‪$sanitizationMap = [];
63 
67  protected ‪$backendUser;
68 
72  protected ‪$allItems = [];
73 
77  protected ‪$nextItems = [];
78 
86  public static function ‪instance(array $dataMap, ‪BackendUserAuthentication ‪$backendUser)
87  {
88  return GeneralUtility::makeInstance(
89  static::class,
90  $dataMap,
92  );
93  }
94 
99  public function ‪__construct(array $dataMap, ‪BackendUserAuthentication ‪$backendUser)
100  {
101  $this->allDataMap = $dataMap;
102  $this->modifiedDataMap = $dataMap;
103  $this->backendUser = ‪$backendUser;
104  }
105 
112  public function ‪process()
113  {
114  $iterations = 0;
115 
116  while (!empty($this->modifiedDataMap)) {
117  $this->nextItems = [];
118  foreach ($this->modifiedDataMap as $tableName => $idValues) {
119  $this->‪collectItems($tableName, $idValues);
120  }
121 
122  $this->modifiedDataMap = [];
123  if (empty($this->nextItems)) {
124  break;
125  }
126 
127  if ($iterations++ === 0) {
128  $this->‪sanitize($this->allItems);
129  }
130  $this->‪enrich($this->nextItems);
131  }
132 
133  $this->allDataMap = $this->‪purgeDataMap($this->allDataMap);
134  return ‪$this->allDataMap;
135  }
136 
143  protected function ‪purgeDataMap(array $dataMap): array
144  {
145  foreach ($dataMap as $tableName => $idValues) {
146  foreach ($idValues as $id => $values) {
147  if (empty($values)) {
148  unset($dataMap[$tableName][$id]);
149  }
150  }
151  if (empty($dataMap[$tableName])) {
152  unset($dataMap[$tableName]);
153  }
154  }
155  return $dataMap;
156  }
157 
164  protected function ‪collectItems(string $tableName, array $idValues)
165  {
166  if (!$this->‪isApplicable($tableName)) {
167  return;
168  }
169 
170  $fieldNames = [
171  'uid' => 'uid',
172  'l10n_state' => 'l10n_state',
173  'language' => ‪$GLOBALS['TCA'][$tableName]['ctrl']['languageField'],
174  'parent' => ‪$GLOBALS['TCA'][$tableName]['ctrl']['transOrigPointerField'],
175  ];
176  if (!empty(‪$GLOBALS['TCA'][$tableName]['ctrl']['translationSource'])) {
177  $fieldNames['source'] = ‪$GLOBALS['TCA'][$tableName]['ctrl']['translationSource'];
178  }
179 
180  $translationValues = $this->‪fetchTranslationValues(
181  $tableName,
182  $fieldNames,
183  $this->‪filterNewItemIds(
184  $tableName,
185  $this->‪filterNumericIds(array_keys($idValues))
186  )
187  );
188 
189  $dependencies = $this->‪fetchDependencies(
190  $tableName,
191  $this->‪filterNewItemIds($tableName, array_keys($idValues))
192  );
193 
194  foreach ($idValues as $id => $values) {
195  $item = $this->‪findItem($tableName, $id);
196  // build item if it has not been created in a previous iteration
197  if ($item === null) {
198  $recordValues = $translationValues[$id] ?? [];
199  $item = ‪DataMapItem::build(
200  $tableName,
201  $id,
202  $values,
203  $recordValues,
204  $fieldNames
205  );
206 
207  // elements using "all language" cannot be localized
208  if ($item->getLanguage() === -1) {
209  unset($item);
210  continue;
211  }
212  // must be any kind of localization and in connected mode
213  if ($item->getLanguage() > 0 && empty($item->getParent())) {
214  unset($item);
215  continue;
216  }
217  // add dependencies
218  if (!empty($dependencies[$id])) {
219  $item->setDependencies($dependencies[$id]);
220  }
221  }
222  // add item to $this->allItems and $this->nextItems
223  $this->‪addNextItem($item);
224  }
225  }
226 
233  protected function ‪sanitize(array $items)
234  {
235  foreach (['directChild', 'grandChild'] as $type) {
236  foreach ($this->‪filterItemsByType($type, $items) as $item) {
237  $this->‪sanitizeTranslationItem($item);
238  }
239  }
240  }
241 
247  protected function ‪enrich(array $items)
248  {
249  foreach (['directChild', 'grandChild'] as $type) {
250  foreach ($this->‪filterItemsByType($type, $items) as $item) {
251  foreach ($item->getApplicableScopes() as $scope) {
252  $fromId = $item->getIdForScope($scope);
253  $fieldNames = $this->‪getFieldNamesForItemScope($item, $scope, !$item->isNew());
254  $this->‪synchronizeTranslationItem($item, $fieldNames, $fromId);
255  }
256  $this->‪populateTranslationItem($item);
257  $this->‪finishTranslationItem($item);
258  }
259  }
260  foreach ($this->‪filterItemsByType('parent', $items) as $item) {
261  $this->‪populateTranslationItem($item);
262  }
263  }
264 
272  protected function ‪sanitizeTranslationItem(‪DataMapItem $item)
273  {
274  $fieldNames = [];
275  foreach ($item->‪getApplicableScopes() as $scope) {
276  $fieldNames = array_merge(
277  $fieldNames,
278  $this->‪getFieldNamesForItemScope($item, $scope, false)
279  );
280  }
281 
282  $fieldNameMap = array_combine($fieldNames, $fieldNames);
283  // separate fields, that are submitted in data-map, but not defined as custom
284  $this->sanitizationMap[$item->‪getTableName()][$item->‪getId()] = array_intersect_key(
285  $this->allDataMap[$item->‪getTableName()][$item->‪getId()],
286  $fieldNameMap
287  );
288  // remove fields, that are submitted in data-map, but not defined as custom
289  $this->allDataMap[$item->‪getTableName()][$item->‪getId()] = array_diff_key(
290  $this->allDataMap[$item->‪getTableName()][$item->‪getId()],
291  $fieldNameMap
292  );
293  }
294 
302  protected function ‪synchronizeTranslationItem(DataMapItem $item, array $fieldNames, $fromId)
303  {
304  if (empty($fieldNames)) {
305  return;
306  }
307 
308  $fieldNameList = 'uid,' . implode(',', $fieldNames);
309 
310  $fromRecord = ['uid' => $fromId];
312  $fromRecord = ‪BackendUtility::getRecordWSOL(
313  $item->getTableName(),
314  $fromId,
315  $fieldNameList
316  );
317  }
318 
319  $forRecord = [];
320  if (!$item->isNew()) {
322  $item->getTableName(),
323  $item->getId(),
324  $fieldNameList
325  );
326  }
327 
328  if (is_array($fromRecord) && is_array($forRecord)) {
329  foreach ($fieldNames as $fieldName) {
331  $item,
332  $fieldName,
333  $fromRecord,
334  $forRecord
335  );
336  }
337  }
338  }
339 
346  protected function ‪populateTranslationItem(DataMapItem $item)
347  {
349  foreach ($item->findDependencies($scope) as $dependentItem) {
350  // use suggested item, if it was submitted in data-map
351  $suggestedDependentItem = $this->‪findItem(
352  $dependentItem->getTableName(),
353  $dependentItem->getId()
354  );
355  if ($suggestedDependentItem !== null) {
356  $dependentItem = $suggestedDependentItem;
357  }
358  foreach ([$scope, ‪DataMapItem::SCOPE_EXCLUDE] as $dependentScope) {
359  $fieldNames = $this->‪getFieldNamesForItemScope(
360  $dependentItem,
361  $dependentScope,
362  false
363  );
365  $dependentItem,
366  $fieldNames,
367  $item->getId()
368  );
369  }
370  }
371  }
372  }
373 
379  protected function ‪finishTranslationItem(‪DataMapItem $item)
380  {
381  if (
382  $item->‪isParentType()
384  ) {
385  return;
386  }
387 
388  $this->allDataMap[$item->‪getTableName()][$item->‪getId()]['l10n_state'] = $item->‪getState()->‪export();
389  }
390 
399  protected function ‪synchronizeFieldValues(‪DataMapItem $item, string $fieldName, array $fromRecord, array $forRecord)
400  {
401  // skip if this field has been processed already, assumed that proper sanitation happened
402  if ($this->‪isSetInDataMap($item->‪getTableName(), $item->‪getId(), $fieldName)) {
403  return;
404  }
405 
406  $fromId = $fromRecord['uid'];
407  // retrieve value from in-memory data-map
408  if ($this->‪isSetInDataMap($item->‪getTableName(), $fromId, $fieldName)) {
409  $fromValue = $this->allDataMap[$item->‪getTableName()][$fromId][$fieldName];
410  } elseif (array_key_exists($fieldName, $fromRecord)) {
411  // retrieve value from record
412  $fromValue = $fromRecord[$fieldName];
413  } else {
414  // otherwise abort synchronization
415  return;
416  }
417 
418  // plain values
419  if (!$this->‪isRelationField($item->‪getTableName(), $fieldName)) {
420  $this->‪modifyDataMap(
421  $item->‪getTableName(),
422  $item->‪getId(),
423  [$fieldName => $fromValue]
424  );
425  } elseif (!$this->‪isInlineRelationField($item->‪getTableName(), $fieldName)) {
426  // direct relational values
427  $this->‪synchronizeDirectRelations($item, $fieldName, $fromRecord);
428  } else {
429  // inline relational values
430  $this->‪synchronizeInlineRelations($item, $fieldName, $fromRecord, $forRecord);
431  }
432  }
433 
441  protected function ‪synchronizeDirectRelations(DataMapItem $item, string $fieldName, array $fromRecord)
442  {
443  $configuration = ‪$GLOBALS['TCA'][$item->getTableName()]['columns'][$fieldName];
444  $isSpecialLanguageField = ($configuration['config']['special'] ?? null) === 'languages';
445 
446  $fromId = $fromRecord['uid'];
447  if ($this->‪isSetInDataMap($item->getTableName(), $fromId, $fieldName)) {
448  $fromValue = $this->allDataMap[$item->getTableName()][$fromId][$fieldName];
449  } else {
450  $fromValue = $fromRecord[$fieldName];
451  }
452 
453  // non-MM relations are stored as comma separated values, just use them
454  // if values are available in data-map already, just use them as well
455  if (
456  empty($configuration['config']['MM'])
457  || $this->‪isSetInDataMap($item->getTableName(), $fromId, $fieldName)
458  || $isSpecialLanguageField
459  ) {
460  $this->‪modifyDataMap(
461  $item->getTableName(),
462  $item->getId(),
463  [$fieldName => $fromValue]
464  );
465  return;
466  }
467  // resolve the language special table name
468  if ($isSpecialLanguageField) {
469  $specialTableName = 'sys_language';
470  }
471  // fetch MM relations from storage
472  $type = $configuration['config']['type'];
473  $manyToManyTable = $configuration['config']['MM'];
474  if ($type === 'group' && $configuration['config']['internal_type'] === 'db') {
475  $tableNames = trim($configuration['config']['allowed'] ?? '');
476  } elseif ($configuration['config']['type'] === 'select') {
477  $tableNames = ($specialTableName ?? $configuration['config']['foreign_table'] ?? '');
478  } else {
479  return;
480  }
481 
482  $relationHandler = $this->‪createRelationHandler();
483  $relationHandler->start(
484  '',
485  $tableNames,
486  $manyToManyTable,
487  $fromId,
488  $item->getTableName(),
489  $configuration['config']
490  );
491 
492  // provide list of relations, optionally prepended with table name
493  // e.g. "13,19,23" or "tt_content_27,tx_extension_items_28"
494  $this->‪modifyDataMap(
495  $item->getTableName(),
496  $item->getId(),
497  [$fieldName => implode(',', $relationHandler->getValueArray())]
498  );
499  }
500 
513  protected function ‪synchronizeInlineRelations(DataMapItem $item, string $fieldName, array $fromRecord, array $forRecord)
514  {
515  $configuration = ‪$GLOBALS['TCA'][$item->getTableName()]['columns'][$fieldName];
516  $isLocalizationModeExclude = ($configuration['l10n_mode'] ?? null) === 'exclude';
517  $foreignTableName = $configuration['config']['foreign_table'];
518 
519  $fieldNames = [
520  'language' => ‪$GLOBALS['TCA'][$foreignTableName]['ctrl']['languageField'] ?? null,
521  'parent' => ‪$GLOBALS['TCA'][$foreignTableName]['ctrl']['transOrigPointerField'] ?? null,
522  'source' => ‪$GLOBALS['TCA'][$foreignTableName]['ctrl']['translationSource'] ?? null,
523  ];
524  $isTranslatable = (!empty($fieldNames['language']) && !empty($fieldNames['parent']));
525  $isLocalized = !empty($item->getLanguage());
526 
527  $suggestedAncestorIds = $this->‪resolveSuggestedInlineRelations(
528  $item,
529  $fieldName,
530  $fromRecord
531  );
532  $persistedIds = $this->‪resolvePersistedInlineRelations(
533  $item,
534  $fieldName,
535  $forRecord
536  );
537 
538  // The dependent ID map points from language parent/source record to
539  // localization, thus keys: parents/sources & values: localizations
540  $dependentIdMap = $this->‪fetchDependentIdMap($foreignTableName, $suggestedAncestorIds, $item->getLanguage());
541  // filter incomplete structures - this is a drawback of DataHandler's remap stack, since
542  // just created IRRE translations still belong to the language parent - filter them out
543  $suggestedAncestorIds = array_diff($suggestedAncestorIds, array_values($dependentIdMap));
544  // compile element differences to be resolved
545  // remove elements that are persisted at the language translation, but not required anymore
546  $removeIds = array_diff($persistedIds, array_values($dependentIdMap));
547  // remove elements that are persisted at the language parent/source, but not required anymore
548  $removeAncestorIds = array_diff(array_keys($dependentIdMap), $suggestedAncestorIds);
549  // missing elements that are persisted at the language parent/source, but not translated yet
550  $missingAncestorIds = array_diff($suggestedAncestorIds, array_keys($dependentIdMap));
551  // persisted elements that should be copied or localized
552  $createAncestorIds = $this->‪filterNumericIds($missingAncestorIds);
553  // non-persisted elements that should be duplicated in data-map directly
554  $populateAncestorIds = array_diff($missingAncestorIds, $createAncestorIds);
555  // this desired state map defines the final result of child elements in their parent translation
556  $desiredIdMap = array_combine($suggestedAncestorIds, $suggestedAncestorIds);
557  // update existing translations in the desired state map
558  foreach ($dependentIdMap as $ancestorId => $translationId) {
559  if (isset($desiredIdMap[$ancestorId])) {
560  $desiredIdMap[$ancestorId] = $translationId;
561  }
562  }
563  // no children to be synchronized, but element order could have been changed
564  if (empty($removeAncestorIds) && empty($missingAncestorIds)) {
565  $this->‪modifyDataMap(
566  $item->getTableName(),
567  $item->getId(),
568  [$fieldName => implode(',', array_values($desiredIdMap))]
569  );
570  return;
571  }
572  // In case only missing elements shall be created, re-use previously sanitized
573  // values IF the relation parent item is new and the count of missing relations
574  // equals the count of previously sanitized relations.
575  // This is caused during copy processes, when the child relations
576  // already have been cloned in DataHandler::copyRecord_procBasedOnFieldType()
577  // without the possibility to resolve the initial connections at this point.
578  // Otherwise child relations would superfluously be duplicated again here.
579  // @todo Invalid manually injected child relations cannot be determined here
580  $sanitizedValue = $this->sanitizationMap[$item->getTableName()][$item->getId()][$fieldName] ?? null;
581  if (
582  !empty($missingAncestorIds) && $item->isNew() && $sanitizedValue !== null
583  && count(GeneralUtility::trimExplode(',', $sanitizedValue, true)) === count($missingAncestorIds)
584  ) {
585  $this->‪modifyDataMap(
586  $item->getTableName(),
587  $item->getId(),
588  [$fieldName => $sanitizedValue]
589  );
590  return;
591  }
592 
593  $localCommandMap = [];
594  foreach ($removeIds as $removeId) {
595  $localCommandMap[$foreignTableName][$removeId]['delete'] = true;
596  }
597  foreach ($removeAncestorIds as $removeAncestorId) {
598  $removeId = $dependentIdMap[$removeAncestorId];
599  $localCommandMap[$foreignTableName][$removeId]['delete'] = true;
600  }
601  foreach ($createAncestorIds as $createAncestorId) {
602  // if child table is not aware of localization, just copy
603  if ($isLocalizationModeExclude || !$isTranslatable) {
604  $localCommandMap[$foreignTableName][$createAncestorId]['copy'] = [
605  'target' => -$createAncestorId,
606  'ignoreLocalization' => true,
607  ];
608  } else {
609  // otherwise, trigger the localization process
610  $localCommandMap[$foreignTableName][$createAncestorId]['localize'] = $item->getLanguage();
611  }
612  }
613  // execute copy, localize and delete actions on persisted child records
614  if (!empty($localCommandMap)) {
615  $localDataHandler = GeneralUtility::makeInstance(DataHandler::class);
616  $localDataHandler->start([], $localCommandMap, $this->backendUser);
617  $localDataHandler->process_cmdmap();
618  // update copied or localized ids
619  foreach ($createAncestorIds as $createAncestorId) {
620  if (empty($localDataHandler->copyMappingArray_merged[$foreignTableName][$createAncestorId])) {
621  $additionalInformation = '';
622  if (!empty($localDataHandler->errorLog)) {
623  $additionalInformation = ', reason "'
624  . implode(', ', $localDataHandler->errorLog) . '"';
625  }
626  throw new \RuntimeException(
627  'Child record was not processed' . $additionalInformation,
628  1486233164
629  );
630  }
631  $newLocalizationId = $localDataHandler->copyMappingArray_merged[$foreignTableName][$createAncestorId];
632  $newLocalizationId = $localDataHandler->getAutoVersionId($foreignTableName, $newLocalizationId) ?? $newLocalizationId;
633  $desiredIdMap[$createAncestorId] = $newLocalizationId;
634  // apply localization references to l10n_mode=exclude children
635  // (without keeping their reference to their origin, synchronization is not possible)
636  if ($isLocalizationModeExclude && $isTranslatable && $isLocalized) {
637  $adjustCopiedValues = $this->‪applyLocalizationReferences(
638  $foreignTableName,
639  $createAncestorId,
640  $item->getLanguage(),
641  $fieldNames,
642  []
643  );
644  $this->‪modifyDataMap(
645  $foreignTableName,
646  $newLocalizationId,
647  $adjustCopiedValues
648  );
649  }
650  }
651  }
652  // populate new child records in data-map
653  if (!empty($populateAncestorIds)) {
654  foreach ($populateAncestorIds as $populateAncestorId) {
655  $newLocalizationId = ‪StringUtility::getUniqueId('NEW');
656  $desiredIdMap[$populateAncestorId] = $newLocalizationId;
657  $duplicatedValues = $this->allDataMap[$foreignTableName][$populateAncestorId] ?? [];
658  // applies localization references to given raw data-map item
659  if ($isTranslatable && $isLocalized) {
660  $duplicatedValues = $this->‪applyLocalizationReferences(
661  $foreignTableName,
662  $populateAncestorId,
663  $item->getLanguage(),
664  $fieldNames,
665  $duplicatedValues
666  );
667  }
668  // prefixes language title if applicable for the accordant field name in raw data-map item
669  if ($isTranslatable && $isLocalized && !$isLocalizationModeExclude) {
670  $duplicatedValues = $this->‪prefixLanguageTitle(
671  $foreignTableName,
672  $populateAncestorId,
673  $item->getLanguage(),
674  $duplicatedValues
675  );
676  }
677  $this->‪modifyDataMap(
678  $foreignTableName,
679  $newLocalizationId,
680  $duplicatedValues
681  );
682  }
683  }
684  // update inline parent field references - required to update pointer fields
685  $this->‪modifyDataMap(
686  $item->getTableName(),
687  $item->getId(),
688  [$fieldName => implode(',', array_values($desiredIdMap))]
689  );
690  }
691 
702  protected function ‪resolveSuggestedInlineRelations(DataMapItem $item, string $fieldName, array $fromRecord): array
703  {
704  $suggestedAncestorIds = [];
705  $fromId = $fromRecord['uid'];
706  $configuration = ‪$GLOBALS['TCA'][$item->getTableName()]['columns'][$fieldName];
707  $foreignTableName = $configuration['config']['foreign_table'];
708  $manyToManyTable = ($configuration['config']['MM'] ?? '');
709 
710  // determine suggested elements of either translation parent or source record
711  // from data-map, in case the accordant language parent/source record was modified
712  if ($this->‪isSetInDataMap($item->getTableName(), $fromId, $fieldName)) {
713  $suggestedAncestorIds = GeneralUtility::trimExplode(
714  ',',
715  $this->allDataMap[$item->getTableName()][$fromId][$fieldName],
716  true
717  );
718  } elseif (‪MathUtility::canBeInterpretedAsInteger($fromId)) {
719  // determine suggested elements of either translation parent or source record from storage
720  $relationHandler = $this->‪createRelationHandler();
721  $relationHandler->start(
722  $fromRecord[$fieldName],
723  $foreignTableName,
724  $manyToManyTable,
725  $fromId,
726  $item->getTableName(),
727  $configuration['config']
728  );
729  $suggestedAncestorIds = $this->‪mapRelationItemId($relationHandler->itemArray);
730  }
731 
732  return array_filter($suggestedAncestorIds);
733  }
734 
743  private function ‪resolvePersistedInlineRelations(DataMapItem $item, string $fieldName, array $forRecord): array
744  {
745  $persistedIds = [];
746  $configuration = ‪$GLOBALS['TCA'][$item->getTableName()]['columns'][$fieldName];
747  $foreignTableName = $configuration['config']['foreign_table'];
748  $manyToManyTable = ($configuration['config']['MM'] ?? '');
749 
750  // determine persisted elements for the current data-map item
751  if (!$item->isNew()) {
752  $relationHandler = $this->‪createRelationHandler();
753  $relationHandler->start(
754  $forRecord[$fieldName] ?? '',
755  $foreignTableName,
756  $manyToManyTable,
757  $item->getId(),
758  $item->getTableName(),
759  $configuration['config']
760  );
761  $persistedIds = $this->‪mapRelationItemId($relationHandler->itemArray);
762  }
763 
764  return array_filter($persistedIds);
765  }
766 
777  protected function ‪isSetInDataMap(string $tableName, $id, string $fieldName)
778  {
779  return
780  // directly look-up field name
781  isset($this->allDataMap[$tableName][$id][$fieldName])
782  // check existence of field name as key for null values
783  || isset($this->allDataMap[$tableName][$id])
784  && is_array($this->allDataMap[$tableName][$id])
785  && array_key_exists($fieldName, $this->allDataMap[$tableName][$id]);
786  }
787 
798  protected function ‪modifyDataMap(string $tableName, $id, array $values)
799  {
800  // avoid superfluous iterations by data-map changes with values
801  // that actually have not been changed and were available already
802  $sameValues = array_intersect_assoc(
803  $this->allDataMap[$tableName][$id] ?? [],
804  $values
805  );
806  if (!empty($sameValues)) {
807  $fieldNames = implode(', ', array_keys($sameValues));
808  throw new \RuntimeException(
809  sprintf(
810  'Issued data-map change for table %s with same values '
811  . 'for these fields names %s',
812  $tableName,
813  $fieldNames
814  ),
815  1488634845
816  );
817  }
818 
819  $this->modifiedDataMap[$tableName][$id] = array_merge(
820  $this->modifiedDataMap[$tableName][$id] ?? [],
821  $values
822  );
823  $this->allDataMap[$tableName][$id] = array_merge(
824  $this->allDataMap[$tableName][$id] ?? [],
825  $values
826  );
827  }
828 
832  protected function ‪addNextItem(‪DataMapItem $item)
833  {
834  $identifier = $item->‪getTableName() . ':' . $item->‪getId();
835  if (!isset($this->allItems[$identifier])) {
836  $this->allItems[$identifier] = $item;
837  }
838  $this->nextItems[$identifier] = $item;
839  }
840 
850  protected function ‪fetchTranslationValues(string $tableName, array $fieldNames, array $ids)
851  {
852  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
853  ->getQueryBuilderForTable($tableName);
854  $queryBuilder->getRestrictions()
855  ->removeAll()
856  ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
857  ->add(GeneralUtility::makeInstance(BackendWorkspaceRestriction::class, $this->backendUser->workspace, false));
858  $statement = $queryBuilder
859  ->select(...array_values($fieldNames))
860  ->from($tableName)
861  ->where(
862  $queryBuilder->expr()->in(
863  'uid',
864  $queryBuilder->createNamedParameter($ids, Connection::PARAM_INT_ARRAY)
865  )
866  )
867  ->execute();
868 
869  $translationValues = [];
870  foreach ($statement as $record) {
871  $translationValues[$record['uid']] = $record;
872  }
873  return $translationValues;
874  }
875 
893  protected function ‪fetchDependencies(string $tableName, array $ids)
894  {
895  if (!‪BackendUtility::isTableLocalizable($tableName)) {
896  return [];
897  }
898 
899  $fieldNames = [
900  'uid' => 'uid',
901  'l10n_state' => 'l10n_state',
902  'language' => ‪$GLOBALS['TCA'][$tableName]['ctrl']['languageField'],
903  'parent' => ‪$GLOBALS['TCA'][$tableName]['ctrl']['transOrigPointerField'],
904  ];
905  if (!empty(‪$GLOBALS['TCA'][$tableName]['ctrl']['translationSource'])) {
906  $fieldNames['source'] = ‪$GLOBALS['TCA'][$tableName]['ctrl']['translationSource'];
907  }
908  $fieldNamesMap = array_combine($fieldNames, $fieldNames);
909 
910  $persistedIds = $this->‪filterNumericIds($ids);
911  $createdIds = array_diff($ids, $persistedIds);
912  $dependentElements = $this->‪fetchDependentElements($tableName, $persistedIds, $fieldNames);
913 
914  foreach ($createdIds as $createdId) {
915  $data = $this->allDataMap[$tableName][$createdId] ?? null;
916  if ($data === null) {
917  continue;
918  }
919  $dependentElements[] = array_merge(
920  ['uid' => $createdId],
921  array_intersect_key($data, $fieldNamesMap)
922  );
923  }
924 
925  $dependencyMap = [];
926  foreach ($dependentElements as $dependentElement) {
927  $dependentItem = ‪DataMapItem::build(
928  $tableName,
929  $dependentElement['uid'],
930  [],
931  $dependentElement,
932  $fieldNames
933  );
934 
935  if ($dependentItem->isDirectChildType()) {
936  $dependencyMap[$dependentItem->getParent()][‪State::STATE_PARENT][] = $dependentItem;
937  }
938  if ($dependentItem->isGrandChildType()) {
939  $dependencyMap[$dependentItem->getParent()][‪State::STATE_PARENT][] = $dependentItem;
940  $dependencyMap[$dependentItem->getSource()][‪State::STATE_SOURCE][] = $dependentItem;
941  }
942  }
943  return $dependencyMap;
944  }
945 
970  protected function ‪fetchDependentIdMap(string $tableName, array $ids, int $desiredLanguage)
971  {
972  $ids = $this->‪filterNumericIds($ids);
973  $isTranslatable = ‪BackendUtility::isTableLocalizable($tableName);
974  $originFieldName = (‪$GLOBALS['TCA'][$tableName]['ctrl']['origUid'] ?? null);
975 
976  if (!$isTranslatable && $originFieldName === null) {
977  // @todo Possibly throw an error, since pointing to original entity is not possible (via origin/parent)
978  return [];
979  }
980 
981  if ($isTranslatable) {
982  $fieldNames = [
983  'uid' => 'uid',
984  'l10n_state' => 'l10n_state',
985  'language' => ‪$GLOBALS['TCA'][$tableName]['ctrl']['languageField'],
986  'parent' => ‪$GLOBALS['TCA'][$tableName]['ctrl']['transOrigPointerField'],
987  ];
988  if (!empty(‪$GLOBALS['TCA'][$tableName]['ctrl']['translationSource'])) {
989  $fieldNames['source'] = ‪$GLOBALS['TCA'][$tableName]['ctrl']['translationSource'];
990  }
991  } else {
992  $fieldNames = [
993  'uid' => 'uid',
994  'origin' => $originFieldName,
995  ];
996  }
997 
998  $fetchIds = $ids;
999  if ($isTranslatable) {
1000  // expand search criteria via parent and source elements
1001  $translationValues = $this->‪fetchTranslationValues($tableName, $fieldNames, $ids);
1002  $ancestorIdMap = $this->‪buildElementAncestorIdMap($fieldNames, $translationValues);
1003  $fetchIds = array_unique(array_merge($ids, array_keys($ancestorIdMap)));
1004  }
1005 
1006  $dependentElements = $this->‪fetchDependentElements($tableName, $fetchIds, $fieldNames);
1007 
1008  $dependentIdMap = [];
1009  foreach ($dependentElements as $dependentElement) {
1010  $dependentId = $dependentElement['uid'];
1011  // implicit: use origin pointer if table cannot be translated
1012  if (!$isTranslatable) {
1013  $ancestorId = (int)$dependentElement[$fieldNames['origin']];
1014  // only consider element if it reflects the desired language
1015  } elseif ((int)$dependentElement[$fieldNames['language']] === $desiredLanguage) {
1016  $ancestorId = $this->‪resolveAncestorId($fieldNames, $dependentElement);
1017  } else {
1018  // otherwise skip the element completely
1019  continue;
1020  }
1021  // only keep ancestors that were initially requested before expanding
1022  if (in_array($ancestorId, $ids, true)) {
1023  $dependentIdMap[$ancestorId] = $dependentId;
1024  } elseif (!empty($ancestorIdMap[$ancestorId])) {
1025  // resolve from previously expanded search criteria
1026  $possibleChainedIds = array_intersect(
1027  $ids,
1028  $ancestorIdMap[$ancestorId]
1029  );
1030  if (!empty($possibleChainedIds)) {
1031  $ancestorId = $possibleChainedIds[0];
1032  $dependentIdMap[$ancestorId] = $dependentId;
1033  }
1034  }
1035  }
1036  return $dependentIdMap;
1037  }
1038 
1050  protected function ‪fetchDependentElements(string $tableName, array $ids, array $fieldNames)
1051  {
1052  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
1053  ->getQueryBuilderForTable($tableName);
1054  $queryBuilder->getRestrictions()
1055  ->removeAll()
1056  ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
1057  ->add(GeneralUtility::makeInstance(BackendWorkspaceRestriction::class, $this->backendUser->workspace, false));
1058 
1059  $zeroParameter = $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT);
1060  $ids = $this->‪filterNumericIds($ids);
1061  $idsParameter = $queryBuilder->createNamedParameter($ids, Connection::PARAM_INT_ARRAY);
1062 
1063  // fetch by language dependency
1064  if (!empty($fieldNames['language']) && !empty($fieldNames['parent'])) {
1065  $ancestorPredicates = [
1066  $queryBuilder->expr()->in(
1067  $fieldNames['parent'],
1068  $idsParameter
1069  )
1070  ];
1071  if (!empty($fieldNames['source'])) {
1072  $ancestorPredicates[] = $queryBuilder->expr()->in(
1073  $fieldNames['source'],
1074  $idsParameter
1075  );
1076  }
1077  $predicates = [
1078  // must be any kind of localization
1079  $queryBuilder->expr()->gt(
1080  $fieldNames['language'],
1081  $zeroParameter
1082  ),
1083  // must be in connected mode
1084  $queryBuilder->expr()->gt(
1085  $fieldNames['parent'],
1086  $zeroParameter
1087  ),
1088  // any parent or source pointers
1089  $queryBuilder->expr()->orX(...$ancestorPredicates),
1090  ];
1091  } elseif (!empty($fieldNames['origin'])) {
1092  // fetch by origin dependency ("copied from")
1093  $predicates = [
1094  $queryBuilder->expr()->in(
1095  $fieldNames['origin'],
1096  $idsParameter
1097  )
1098  ];
1099  } else {
1100  // otherwise: stop execution
1101  throw new \InvalidArgumentException(
1102  'Invalid combination of query field names given',
1103  1487192370
1104  );
1105  }
1106 
1107  $statement = $queryBuilder
1108  ->select(...array_values($fieldNames))
1109  ->from($tableName)
1110  ->andWhere(...$predicates)
1111  ->execute();
1112 
1113  $dependentElements = [];
1114  foreach ($statement as $record) {
1115  $dependentElements[] = $record;
1116  }
1117  return $dependentElements;
1118  }
1119 
1127  protected function ‪filterItemsByType(string $type, array $items)
1128  {
1129  return array_filter(
1130  $items,
1131  function (DataMapItem $item) use ($type) {
1132  return $item->getType() === $type;
1133  }
1134  );
1135  }
1136 
1143  protected function ‪filterNumericIds(array $ids)
1144  {
1145  $ids = array_filter(
1146  $ids,
1147  function ($id) {
1149  }
1150  );
1151  return array_map('intval', $ids);
1152  }
1153 
1161  protected function ‪filterNewItemIds(string $tableName, array $ids)
1162  {
1163  return array_filter(
1164  $ids,
1165  function ($id) use ($tableName) {
1166  return $this->‪findItem($tableName, $id) === null;
1167  }
1168  );
1169  }
1170 
1177  protected function ‪mapRelationItemId(array $relationItems)
1178  {
1179  return array_map(
1180  function (array $relationItem) {
1181  return (int)$relationItem['id'];
1182  },
1183  $relationItems
1184  );
1185  }
1192  protected function ‪resolveAncestorId(array $fieldNames, array $element)
1193  {
1194  $sourceName = $fieldNames['source'] ?? null;
1195  if ($sourceName !== null && !empty($element[$sourceName])) {
1196  // implicit: use source pointer if given (not empty)
1197  return (int)$element[$sourceName];
1198  }
1199  $parentName = $fieldNames['parent'] ?? null;
1200  if ($parentName !== null && !empty($element[$parentName])) {
1201  // implicit: use parent pointer if given (not empty)
1202  return (int)$element[$parentName];
1203  }
1204  return null;
1205  }
1206 
1217  protected function ‪buildElementAncestorIdMap(array $fieldNames, array $elements)
1218  {
1219  $ancestorIdMap = [];
1220  foreach ($elements as $element) {
1221  $ancestorId = $this->‪resolveAncestorId($fieldNames, $element);
1222  if ($ancestorId !== null) {
1223  $ancestorIdMap[$ancestorId][] = (int)$element['uid'];
1224  }
1225  }
1226  return $ancestorIdMap;
1227  }
1228 
1236  protected function ‪findItem(string $tableName, $id)
1237  {
1238  return $this->allItems[$tableName . ':' . $id] ?? null;
1239  }
1240 
1253  protected function ‪duplicateFromDataMap(string $tableName, $fromId, int $language, array $fieldNames, bool $localize): array
1254  {
1255  $data = $this->allDataMap[$tableName][$fromId] ?? [];
1256  // just return if localization cannot be applied
1257  if (empty($language) || !$localize) {
1258  return $data;
1259  }
1260  $data = $this->‪applyLocalizationReferences($tableName, $fromId, $language, $fieldNames, $data);
1261  $data = $this->‪prefixLanguageTitle($tableName, $fromId, $language, $data);
1262  return $data;
1263  }
1264 
1275  protected function ‪applyLocalizationReferences(string $tableName, $fromId, int $language, array $fieldNames, array $data): array
1276  {
1277  // just return if localization cannot be applied
1278  if (empty($language)) {
1279  return $data;
1280  }
1281 
1282  // apply `languageField`, e.g. `sys_language_uid`
1283  $data[$fieldNames['language']] = $language;
1284  // apply `transOrigPointerField`, e.g. `l10n_parent`
1285  if (empty($data[$fieldNames['parent']])) {
1286  // @todo Only $id used in TCA type 'select' is resolved in DataHandler's remapStack
1287  $data[$fieldNames['parent']] = $fromId;
1288  }
1289  // apply `translationSource`, e.g. `l10n_source`
1290  if (!empty($fieldNames['source'])) {
1291  // @todo Not sure, whether $id is resolved in DataHandler's remapStack
1292  $data[$fieldNames['source']] = $fromId;
1293  }
1294  // unset field names that are expected to be handled in this processor
1295  foreach ($this->‪getFieldNamesToBeHandled($tableName) as $fieldName) {
1296  unset($data[$fieldName]);
1297  }
1298 
1299  return $data;
1300  }
1301 
1311  protected function ‪prefixLanguageTitle(string $tableName, $fromId, int $language, array $data): array
1312  {
1313  $prefixFieldNames = array_intersect(
1314  array_keys($data),
1315  $this->‪getPrefixLanguageTitleFieldNames($tableName)
1316  );
1317  if (empty($prefixFieldNames)) {
1318  return $data;
1319  }
1320 
1321  $languageService = $this->‪getLanguageService();
1322  $languageRecord = ‪BackendUtility::getRecord('sys_language', $language, 'title');
1323  list($pageId) = ‪BackendUtility::getTSCpid($tableName, $fromId, $data['pid'] ?? null);
1324 
1325  $tsConfigTranslateToMessage = ‪BackendUtility::getPagesTSconfig($pageId)['TCEMAIN.']['translateToMessage'] ?? '';
1326  if (!empty($tsConfigTranslateToMessage)) {
1327  $prefix = $tsConfigTranslateToMessage;
1328  if ($languageService !== null) {
1329  $prefix = $languageService->sL($prefix);
1330  }
1331  $prefix = sprintf($prefix, $languageRecord['title']);
1332  }
1333  if (empty($prefix)) {
1334  $prefix = 'Translate to ' . $languageRecord['title'] . ':';
1335  }
1336 
1337  foreach ($prefixFieldNames as $prefixFieldName) {
1338  // @todo The hook in DataHandler is not applied here
1339  $data[$prefixFieldName] = '[' . $prefix . '] ' . $data[$prefixFieldName];
1340  }
1341 
1342  return $data;
1343  }
1344 
1353  protected function ‪getFieldNamesForItemScope(
1354  DataMapItem $item,
1355  string $scope,
1356  bool $modified
1357  ) {
1358  if (
1359  $scope === ‪DataMapItem::SCOPE_PARENT
1360  || $scope === ‪DataMapItem::SCOPE_SOURCE
1361  ) {
1362  if (!‪State::isApplicable($item->getTableName())) {
1363  return [];
1364  }
1365  return $item->getState()->filterFieldNames($scope, $modified);
1366  }
1367  if ($scope === ‪DataMapItem::SCOPE_EXCLUDE) {
1369  $item->getTableName()
1370  );
1371  }
1372  return [];
1373  }
1374 
1381  protected function ‪getLocalizationModeExcludeFieldNames(string $tableName)
1382  {
1383  $localizationExcludeFieldNames = [];
1384  if (empty(‪$GLOBALS['TCA'][$tableName]['columns'])) {
1385  return $localizationExcludeFieldNames;
1386  }
1387 
1388  foreach (‪$GLOBALS['TCA'][$tableName]['columns'] as $fieldName => $configuration) {
1389  if (($configuration['l10n_mode'] ?? null) === 'exclude'
1390  && ($configuration['config']['type'] ?? null) !== 'none'
1391  ) {
1392  $localizationExcludeFieldNames[] = $fieldName;
1393  }
1394  }
1395 
1396  return $localizationExcludeFieldNames;
1397  }
1398 
1406  protected function ‪getFieldNamesToBeHandled(string $tableName)
1407  {
1408  return array_merge(
1409  ‪State::getFieldNames($tableName),
1410  $this->‪getLocalizationModeExcludeFieldNames($tableName)
1411  );
1412  }
1413 
1420  protected function ‪getPrefixLanguageTitleFieldNames(string $tableName)
1421  {
1422  $prefixLanguageTitleFieldNames = [];
1423  if (empty(‪$GLOBALS['TCA'][$tableName]['columns'])) {
1424  return $prefixLanguageTitleFieldNames;
1425  }
1426 
1427  foreach (‪$GLOBALS['TCA'][$tableName]['columns'] as $fieldName => $configuration) {
1428  $type = $configuration['config']['type'] ?? null;
1429  if (
1430  ($configuration['l10n_mode'] ?? null) === 'prefixLangTitle'
1431  && ($type === 'input' || $type === 'text')
1432  ) {
1433  $prefixLanguageTitleFieldNames[] = $fieldName;
1434  }
1435  }
1436 
1437  return $prefixLanguageTitleFieldNames;
1438  }
1439 
1447  protected function ‪isRelationField(string $tableName, string $fieldName): bool
1448  {
1449  if (empty(‪$GLOBALS['TCA'][$tableName]['columns'][$fieldName]['config']['type'])) {
1450  return false;
1451  }
1452 
1453  $configuration = ‪$GLOBALS['TCA'][$tableName]['columns'][$fieldName]['config'];
1454 
1455  return
1456  $configuration['type'] === 'group'
1457  && ($configuration['internal_type'] ?? null) === 'db'
1458  && !empty($configuration['allowed'])
1459  || $configuration['type'] === 'select'
1460  && (
1461  !empty($configuration['foreign_table'])
1462  && !empty(‪$GLOBALS['TCA'][$configuration['foreign_table']])
1463  || ($configuration['special'] ?? null) === 'languages'
1464  )
1465  || $this->‪isInlineRelationField($tableName, $fieldName)
1466  ;
1467  }
1468 
1476  protected function ‪isInlineRelationField(string $tableName, string $fieldName): bool
1477  {
1478  if (empty(‪$GLOBALS['TCA'][$tableName]['columns'][$fieldName]['config']['type'])) {
1479  return false;
1480  }
1481 
1482  $configuration = ‪$GLOBALS['TCA'][$tableName]['columns'][$fieldName]['config'];
1483 
1484  return
1485  $configuration['type'] === 'inline'
1486  && !empty($configuration['foreign_table'])
1487  && !empty(‪$GLOBALS['TCA'][$configuration['foreign_table']])
1488  ;
1489  }
1490 
1498  protected function ‪isApplicable(string $tableName): bool
1499  {
1500  return
1501  ‪State::isApplicable($tableName)
1503  && count($this->‪getLocalizationModeExcludeFieldNames($tableName)) > 0
1504  ;
1505  }
1506 
1510  protected function ‪createRelationHandler()
1511  {
1512  $relationHandler = GeneralUtility::makeInstance(RelationHandler::class);
1513  $relationHandler->setWorkspaceId($this->backendUser->workspace);
1514  return $relationHandler;
1515  }
1516 
1520  protected function ‪getLanguageService()
1521  {
1522  return ‪$GLOBALS['LANG'] ?? null;
1523  }
1524 }
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapItem\getType
‪string getType()
Definition: DataMapItem.php:228
‪TYPO3\CMS\Core\DataHandling\DataHandler
Definition: DataHandler.php:81
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor\applyLocalizationReferences
‪array applyLocalizationReferences(string $tableName, $fromId, int $language, array $fieldNames, array $data)
Definition: DataMapProcessor.php:1269
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor\createRelationHandler
‪RelationHandler createRelationHandler()
Definition: DataMapProcessor.php:1504
‪TYPO3\CMS\Core\Utility\MathUtility\canBeInterpretedAsInteger
‪static bool canBeInterpretedAsInteger($var)
Definition: MathUtility.php:73
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor\resolveSuggestedInlineRelations
‪int[] string[] resolveSuggestedInlineRelations(DataMapItem $item, string $fieldName, array $fromRecord)
Definition: DataMapProcessor.php:696
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor\purgeDataMap
‪array purgeDataMap(array $dataMap)
Definition: DataMapProcessor.php:137
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor\sanitize
‪sanitize(array $items)
Definition: DataMapProcessor.php:227
‪TYPO3\CMS\Core\Database\RelationHandler
Definition: RelationHandler.php:32
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapItem\findDependencies
‪DataMapItem[] findDependencies(string $scope)
Definition: DataMapItem.php:369
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor\getFieldNamesToBeHandled
‪string[] getFieldNamesToBeHandled(string $tableName)
Definition: DataMapProcessor.php:1400
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor\filterNewItemIds
‪array filterNewItemIds(string $tableName, array $ids)
Definition: DataMapProcessor.php:1155
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor\resolvePersistedInlineRelations
‪int[] resolvePersistedInlineRelations(DataMapItem $item, string $fieldName, array $forRecord)
Definition: DataMapProcessor.php:737
‪TYPO3\CMS\Core\DataHandling\Localization\State\filterFieldNames
‪string[] filterFieldNames(string $desiredState, bool $modified=false)
Definition: State.php:281
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor\$sanitizationMap
‪array $sanitizationMap
Definition: DataMapProcessor.php:59
‪TYPO3\CMS\Core\DataHandling\Localization\State\STATE_PARENT
‪const STATE_PARENT
Definition: State.php:25
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor\$allItems
‪DataMapItem[] $allItems
Definition: DataMapProcessor.php:67
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor\getLanguageService
‪LanguageService null getLanguageService()
Definition: DataMapProcessor.php:1514
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor\fetchDependentElements
‪array fetchDependentElements(string $tableName, array $ids, array $fieldNames)
Definition: DataMapProcessor.php:1044
‪TYPO3\CMS\Core\Database\Query\Restriction\BackendWorkspaceRestriction
Definition: BackendWorkspaceRestriction.php:28
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor\getFieldNamesForItemScope
‪string[] getFieldNamesForItemScope(DataMapItem $item, string $scope, bool $modified)
Definition: DataMapProcessor.php:1347
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor\$nextItems
‪DataMapItem[] $nextItems
Definition: DataMapProcessor.php:71
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor\fetchDependentIdMap
‪array fetchDependentIdMap(string $tableName, array $ids, int $desiredLanguage)
Definition: DataMapProcessor.php:964
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor\getPrefixLanguageTitleFieldNames
‪array getPrefixLanguageTitleFieldNames(string $tableName)
Definition: DataMapProcessor.php:1414
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapItem\getTableName
‪string getTableName()
Definition: DataMapItem.php:147
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor\buildElementAncestorIdMap
‪array buildElementAncestorIdMap(array $fieldNames, array $elements)
Definition: DataMapProcessor.php:1211
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapItem\SCOPE_EXCLUDE
‪const SCOPE_EXCLUDE
Definition: DataMapItem.php:32
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor\fetchDependencies
‪DataMapItem[][] fetchDependencies(string $tableName, array $ids)
Definition: DataMapProcessor.php:887
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor\modifyDataMap
‪modifyDataMap(string $tableName, $id, array $values)
Definition: DataMapProcessor.php:792
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor\__construct
‪__construct(array $dataMap, BackendUserAuthentication $backendUser)
Definition: DataMapProcessor.php:93
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapItem\isParentType
‪bool isParentType()
Definition: DataMapItem.php:251
‪TYPO3\CMS\Core\DataHandling\Localization\State\isApplicable
‪static bool isApplicable(string $tableName)
Definition: State.php:67
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor
Definition: DataMapProcessor.php:48
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor\populateTranslationItem
‪populateTranslationItem(DataMapItem $item)
Definition: DataMapProcessor.php:340
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor\collectItems
‪collectItems(string $tableName, array $idValues)
Definition: DataMapProcessor.php:158
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor\enrich
‪enrich(array $items)
Definition: DataMapProcessor.php:241
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor\$modifiedDataMap
‪array $modifiedDataMap
Definition: DataMapProcessor.php:55
‪TYPO3\CMS\Core\DataHandling\Localization\State\getFieldNames
‪static array getFieldNames(string $tableName)
Definition: State.php:81
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapItem\getApplicableScopes
‪string[] getApplicableScopes()
Definition: DataMapItem.php:377
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor\fetchTranslationValues
‪array fetchTranslationValues(string $tableName, array $fieldNames, array $ids)
Definition: DataMapProcessor.php:844
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor\prefixLanguageTitle
‪array prefixLanguageTitle(string $tableName, $fromId, int $language, array $data)
Definition: DataMapProcessor.php:1305
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor\mapRelationItemId
‪string[] mapRelationItemId(array $relationItems)
Definition: DataMapProcessor.php:1171
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor\process
‪array process()
Definition: DataMapProcessor.php:106
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor\duplicateFromDataMap
‪array duplicateFromDataMap(string $tableName, $fromId, int $language, array $fieldNames, bool $localize)
Definition: DataMapProcessor.php:1247
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor\$backendUser
‪BackendUserAuthentication $backendUser
Definition: DataMapProcessor.php:63
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapItem\isNew
‪bool isNew()
Definition: DataMapItem.php:220
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapItem\build
‪static object DataMapItem build(string $tableName, $id, array $suggestedValues, array $persistedValues, array $configurationFieldNames)
Definition: DataMapItem.php:93
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor\isSetInDataMap
‪bool isSetInDataMap(string $tableName, $id, string $fieldName)
Definition: DataMapProcessor.php:771
‪TYPO3\CMS\Core\Authentication\BackendUserAuthentication
Definition: BackendUserAuthentication.php:45
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor\filterItemsByType
‪DataMapItem[] filterItemsByType(string $type, array $items)
Definition: DataMapProcessor.php:1121
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor\addNextItem
‪addNextItem(DataMapItem $item)
Definition: DataMapProcessor.php:826
‪TYPO3\CMS\Core\DataHandling\Localization
Definition: DataMapItem.php:2
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor\isApplicable
‪bool isApplicable(string $tableName)
Definition: DataMapProcessor.php:1492
‪TYPO3\CMS\Backend\Utility\BackendUtility
Definition: BackendUtility.php:72
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor\filterNumericIds
‪int[] filterNumericIds(array $ids)
Definition: DataMapProcessor.php:1137
‪TYPO3\CMS\Backend\Utility\BackendUtility\getRecordWSOL
‪static array getRecordWSOL( $table, $uid, $fields=' *', $where='', $useDeleteClause=true, $unsetMovePointers=false)
Definition: BackendUtility.php:174
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor\synchronizeInlineRelations
‪synchronizeInlineRelations(DataMapItem $item, string $fieldName, array $fromRecord, array $forRecord)
Definition: DataMapProcessor.php:507
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor\isInlineRelationField
‪bool isInlineRelationField(string $tableName, string $fieldName)
Definition: DataMapProcessor.php:1470
‪TYPO3\CMS\Backend\Utility\BackendUtility\getRecord
‪static array null getRecord($table, $uid, $fields=' *', $where='', $useDeleteClause=true)
Definition: BackendUtility.php:130
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapItem
Definition: DataMapItem.php:25
‪TYPO3\CMS\Core\DataHandling\Localization\State\STATE_SOURCE
‪const STATE_SOURCE
Definition: State.php:26
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor\synchronizeDirectRelations
‪synchronizeDirectRelations(DataMapItem $item, string $fieldName, array $fromRecord)
Definition: DataMapProcessor.php:435
‪TYPO3\CMS\Core\Database\Connection
Definition: Connection.php:31
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapItem\getState
‪State getState()
Definition: DataMapItem.php:275
‪TYPO3\CMS\Core\Utility\StringUtility\getUniqueId
‪static string getUniqueId($prefix='')
Definition: StringUtility.php:91
‪TYPO3\CMS\Backend\Utility\BackendUtility\getTSCpid
‪static array getTSCpid($table, $uid, $pid)
Definition: BackendUtility.php:3607
‪TYPO3\CMS\Backend\Utility\BackendUtility\isTableLocalizable
‪static bool isTableLocalizable($table)
Definition: BackendUtility.php:616
‪TYPO3\CMS\Backend\Utility\BackendUtility\getPagesTSconfig
‪static array getPagesTSconfig($id, $rootLine=null, $returnPartArray=false)
Definition: BackendUtility.php:864
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapItem\getLanguage
‪string int getLanguage()
Definition: DataMapItem.php:286
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor\resolveAncestorId
‪int null resolveAncestorId(array $fieldNames, array $element)
Definition: DataMapProcessor.php:1186
‪$GLOBALS
‪$GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['adminpanel']['modules']
Definition: ext_localconf.php:5
‪TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction
Definition: DeletedRestriction.php:26
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor\instance
‪static DataMapProcessor instance(array $dataMap, BackendUserAuthentication $backendUser)
Definition: DataMapProcessor.php:80
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapItem\SCOPE_SOURCE
‪const SCOPE_SOURCE
Definition: DataMapItem.php:31
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor\isRelationField
‪bool isRelationField(string $tableName, string $fieldName)
Definition: DataMapProcessor.php:1441
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor\$allDataMap
‪array $allDataMap
Definition: DataMapProcessor.php:51
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor\findItem
‪DataMapItem null findItem(string $tableName, $id)
Definition: DataMapProcessor.php:1230
‪TYPO3\CMS\Core\Utility\MathUtility
Definition: MathUtility.php:21
‪TYPO3\CMS\Core\Localization\LanguageService
Definition: LanguageService.php:29
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor\synchronizeFieldValues
‪synchronizeFieldValues(DataMapItem $item, string $fieldName, array $fromRecord, array $forRecord)
Definition: DataMapProcessor.php:393
‪TYPO3\CMS\Core\Database\ConnectionPool
Definition: ConnectionPool.php:44
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor\sanitizeTranslationItem
‪sanitizeTranslationItem(DataMapItem $item)
Definition: DataMapProcessor.php:266
‪TYPO3\CMS\Core\Utility\GeneralUtility
Definition: GeneralUtility.php:45
‪TYPO3\CMS\Core\Utility\StringUtility
Definition: StringUtility.php:21
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapItem\getId
‪mixed getId()
Definition: DataMapItem.php:157
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor\synchronizeTranslationItem
‪synchronizeTranslationItem(DataMapItem $item, array $fieldNames, $fromId)
Definition: DataMapProcessor.php:296
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor\getLocalizationModeExcludeFieldNames
‪string[] getLocalizationModeExcludeFieldNames(string $tableName)
Definition: DataMapProcessor.php:1375
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor\finishTranslationItem
‪finishTranslationItem(DataMapItem $item)
Definition: DataMapProcessor.php:373
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapItem\SCOPE_PARENT
‪const SCOPE_PARENT
Definition: DataMapItem.php:30
‪TYPO3\CMS\Core\DataHandling\Localization\State\export
‪string null export()
Definition: State.php:192