‪TYPO3CMS  ‪main
DataMapProcessor.php
Go to the documentation of this file.
1 <?php
2 
3 declare(strict_types=1);
4 
5 /*
6  * This file is part of the TYPO3 CMS project.
7  *
8  * It is free software; you can redistribute it and/or modify it under
9  * the terms of the GNU General Public License, either version 2
10  * of the License, or any later version.
11  *
12  * For the full copyright and license information, please read the
13  * LICENSE.txt file that was distributed with this source code.
14  *
15  * The TYPO3 project - inspiring people to share!
16  */
17 
19 
20 use TYPO3\CMS\Backend\Utility\BackendUtility;
38 
59 {
63  protected ‪$allDataMap = [];
64 
68  protected $modifiedDataMap = [];
69 
73  protected $sanitizationMap = [];
74 
78  protected $backendUser;
79 
83  protected $referenceIndexUpdater;
84 
88  protected $allItems = [];
89 
93  protected $nextItems = [];
94 
103  public static function instance(
104  array $dataMap,
105  ‪BackendUserAuthentication $backendUser,
106  ‪ReferenceIndexUpdater $referenceIndexUpdater = null
107  ) {
108  return GeneralUtility::makeInstance(
109  static::class,
110  $dataMap,
111  $backendUser,
112  $referenceIndexUpdater
113  );
114  }
115 
121  public function __construct(
122  array $dataMap,
123  ‪BackendUserAuthentication $backendUser,
124  ‪ReferenceIndexUpdater $referenceIndexUpdater = null
125  ) {
126  $this->allDataMap = $dataMap;
127  $this->‪modifiedDataMap = $dataMap;
128  $this->‪backendUser = $backendUser;
129  if ($referenceIndexUpdater === null) {
130  $referenceIndexUpdater = GeneralUtility::makeInstance(ReferenceIndexUpdater::class);
131  }
132  $this->‪referenceIndexUpdater = $referenceIndexUpdater;
133  }
134 
141  public function ‪process()
142  {
143  $iterations = 0;
144 
145  while (!empty($this->‪modifiedDataMap)) {
146  $this->nextItems = [];
147  foreach ($this->‪modifiedDataMap as $tableName => $idValues) {
148  $this->‪collectItems($tableName, $idValues);
149  }
150 
151  $this->‪modifiedDataMap = [];
152  if (empty($this->nextItems)) {
153  break;
154  }
155 
156  if ($iterations++ === 0) {
157  $this->‪sanitize($this->allItems);
158  }
159  $this->‪enrich($this->nextItems);
160  }
161 
162  $this->allDataMap = $this->‪purgeDataMap($this->allDataMap);
163  return ‪$this->allDataMap;
164  }
165 
169  protected function ‪purgeDataMap(array $dataMap): array
170  {
171  foreach ($dataMap as $tableName => $idValues) {
172  foreach ($idValues as $id => $values) {
173  // `l10n_state` should be serialized JSON at this point,
174  // in case it's not, it most probably was ignored in `collectItems()`
175  if (is_array($values['l10n_state'] ?? null)) {
176  unset($dataMap[$tableName][$id]['l10n_state'], $values['l10n_state']);
177  }
178  if (empty($values)) {
179  unset($dataMap[$tableName][$id]);
180  }
181  }
182  if (empty($dataMap[$tableName])) {
183  unset($dataMap[$tableName]);
184  }
185  }
186  return $dataMap;
187  }
188 
192  protected function ‪collectItems(string $tableName, array $idValues)
193  {
194  if (!$this->‪isApplicable($tableName)) {
195  return;
196  }
197 
198  $fieldNames = [
199  'uid' => 'uid',
200  'l10n_state' => 'l10n_state',
201  'language' => ‪$GLOBALS['TCA'][$tableName]['ctrl']['languageField'],
202  'parent' => ‪$GLOBALS['TCA'][$tableName]['ctrl']['transOrigPointerField'],
203  ];
204  if (!empty(‪$GLOBALS['TCA'][$tableName]['ctrl']['translationSource'])) {
205  $fieldNames['source'] = ‪$GLOBALS['TCA'][$tableName]['ctrl']['translationSource'];
206  }
207 
208  $translationValues = $this->‪fetchTranslationValues(
209  $tableName,
210  $fieldNames,
211  $this->‪filterNewItemIds(
212  $tableName,
213  $this->‪filterNumericIds(array_keys($idValues))
214  )
215  );
216 
217  $dependencies = $this->‪fetchDependencies(
218  $tableName,
219  $this->‪filterNewItemIds($tableName, array_keys($idValues))
220  );
221 
222  foreach ($idValues as $id => $values) {
223  $item = $this->‪findItem($tableName, $id);
224  // build item if it has not been created in a previous iteration
225  if ($item === null) {
226  $recordValues = $translationValues[$id] ?? [];
227  $item = ‪DataMapItem::build(
228  $tableName,
229  $id,
230  $values,
231  $recordValues,
232  $fieldNames
233  );
234 
235  // elements using "all language" cannot be localized
236  if ($item->getLanguage() === -1) {
237  unset($item);
238  continue;
239  }
240  // must be any kind of localization and in connected mode
241  if ($item->getLanguage() > 0 && empty($item->getParent())) {
242  unset($item);
243  continue;
244  }
245  // add dependencies
246  if (!empty($dependencies[$id])) {
247  $item->setDependencies($dependencies[$id]);
248  }
249  }
250  // add item to $this->allItems and $this->nextItems
251  $this->‪addNextItem($item);
252  }
253  }
254 
261  protected function ‪sanitize(array $items)
262  {
263  foreach (['directChild', 'grandChild'] as $type) {
264  foreach ($this->‪filterItemsByType($type, $items) as $item) {
265  $this->‪sanitizeTranslationItem($item);
266  }
267  }
268  }
269 
275  protected function ‪enrich(array $items)
276  {
277  foreach (['directChild', 'grandChild'] as $type) {
278  foreach ($this->‪filterItemsByType($type, $items) as $item) {
279  foreach ($item->getApplicableScopes() as $scope) {
280  $fromId = $item->getIdForScope($scope);
281  $fieldNames = $this->‪getFieldNamesForItemScope($item, $scope, !$item->isNew());
282  $this->‪synchronizeTranslationItem($item, $fieldNames, $fromId);
283  }
284  $this->‪populateTranslationItem($item);
285  $this->‪finishTranslationItem($item);
286  }
287  }
288  foreach ($this->‪filterItemsByType('parent', $items) as $item) {
289  $this->‪populateTranslationItem($item);
290  }
291  }
292 
298  protected function ‪sanitizeTranslationItem(‪DataMapItem $item)
299  {
300  $fieldNames = [];
301  foreach ($item->‪getApplicableScopes() as $scope) {
302  $fieldNames = array_merge(
303  $fieldNames,
304  $this->‪getFieldNamesForItemScope($item, $scope, false)
305  );
306  }
307 
308  $fieldNameMap = array_combine($fieldNames, $fieldNames) ?: [];
309  // separate fields, that are submitted in data-map, but not defined as custom
310  $this->sanitizationMap[$item->‪getTableName()][$item->‪getId()] = array_intersect_key(
311  $this->allDataMap[$item->‪getTableName()][$item->‪getId()],
312  $fieldNameMap
313  );
314  // remove fields, that are submitted in data-map, but not defined as custom
315  $this->allDataMap[$item->‪getTableName()][$item->‪getId()] = array_diff_key(
316  $this->allDataMap[$item->‪getTableName()][$item->‪getId()],
317  $fieldNameMap
318  );
319  }
320 
326  protected function ‪synchronizeTranslationItem(‪DataMapItem $item, array $fieldNames, $fromId)
327  {
328  if (empty($fieldNames)) {
329  return;
330  }
331 
332  $fieldNameList = 'uid,' . implode(',', $fieldNames);
333 
334  $fromRecord = ['uid' => $fromId];
336  $fromRecord = BackendUtility::getRecordWSOL(
337  $item->‪getTableName(),
338  (int)$fromId,
339  $fieldNameList
340  );
341  }
342 
343  $forRecord = [];
344  if (!$item->‪isNew()) {
345  $forRecord = BackendUtility::getRecordWSOL(
346  $item->‪getTableName(),
347  $item->‪getId(),
348  $fieldNameList
349  );
350  }
351 
352  if (is_array($fromRecord) && is_array($forRecord)) {
353  foreach ($fieldNames as $fieldName) {
355  $item,
356  $fieldName,
357  $fromRecord,
358  $forRecord
359  );
360  }
361  }
362  }
363 
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 
399  protected function ‪finishTranslationItem(DataMapItem $item)
400  {
401  if (
402  $item->isParentType()
403  || !‪State::isApplicable($item->getTableName())
404  ) {
405  return;
406  }
407 
408  $this->allDataMap[$item->getTableName()][$item->getId()]['l10n_state'] = $item->getState()->export();
409  }
410 
414  protected function ‪synchronizeFieldValues(‪DataMapItem $item, string $fieldName, array $fromRecord, array $forRecord)
415  {
416  // skip if this field has been processed already, assumed that proper sanitation happened
417  if ($this->‪isSetInDataMap($item->‪getTableName(), $item->‪getId(), $fieldName)) {
418  return;
419  }
420 
421  $fromId = $fromRecord['uid'];
422  // retrieve value from in-memory data-map
423  if ($this->‪isSetInDataMap($item->‪getTableName(), $fromId, $fieldName)) {
424  $fromValue = $this->allDataMap[$item->‪getTableName()][$fromId][$fieldName];
425  } elseif (array_key_exists($fieldName, $fromRecord)) {
426  // retrieve value from record
427  $fromValue = $fromRecord[$fieldName];
428  } else {
429  // otherwise abort synchronization
430  return;
431  }
432 
433  // plain values
434  if (!$this->‪isRelationField($item->‪getTableName(), $fieldName)) {
435  $this->‪modifyDataMap(
436  $item->‪getTableName(),
437  $item->‪getId(),
438  [$fieldName => $fromValue]
439  );
440  } elseif (!$this->‪isReferenceField($item->‪getTableName(), $fieldName)) {
441  // direct relational values
442  $this->‪synchronizeDirectRelations($item, $fieldName, $fromRecord);
443  } else {
444  // reference values
445  $this->‪synchronizeReferences($item, $fieldName, $fromRecord, $forRecord);
446  }
447  }
448 
452  protected function ‪synchronizeDirectRelations(DataMapItem $item, string $fieldName, array $fromRecord)
453  {
454  $configuration = ‪$GLOBALS['TCA'][$item->getTableName()]['columns'][$fieldName];
455  $fromId = $fromRecord['uid'];
456  if ($this->‪isSetInDataMap($item->getTableName(), $fromId, $fieldName)) {
457  $fromValue = $this->allDataMap[$item->getTableName()][$fromId][$fieldName];
458  } else {
459  $fromValue = $fromRecord[$fieldName];
460  }
461 
462  // non-MM relations are stored as comma separated values, just use them
463  // if values are available in data-map already, just use them as well
464  if (
465  empty($configuration['config']['MM'])
466  || $this->‪isSetInDataMap($item->getTableName(), $fromId, $fieldName)
467  ) {
468  $this->‪modifyDataMap(
469  $item->getTableName(),
470  $item->getId(),
471  [$fieldName => $fromValue]
472  );
473  return;
474  }
475  // fetch MM relations from storage
476  $type = $configuration['config']['type'];
477  $manyToManyTable = $configuration['config']['MM'];
478  if ($type === 'group' && !empty(trim($configuration['config']['allowed'] ?? ''))) {
479  $tableNames = trim($configuration['config']['allowed']);
480  } elseif ($configuration['config']['type'] === 'select' || $configuration['config']['type'] === 'category') {
481  $tableNames = $configuration['config']['foreign_table'] ?? '';
482  } else {
483  return;
484  }
485 
486  $relationHandler = $this->‪createRelationHandler();
487  $relationHandler->start(
488  '',
489  $tableNames,
490  $manyToManyTable,
491  $fromId,
492  $item->getTableName(),
493  $configuration['config']
494  );
495 
496  // provide list of relations, optionally prepended with table name
497  // e.g. "13,19,23" or "tt_content_27,tx_extension_items_28"
498  $this->‪modifyDataMap(
499  $item->getTableName(),
500  $item->getId(),
501  [$fieldName => implode(',', $relationHandler->getValueArray())]
502  );
503  }
504 
514  protected function ‪synchronizeReferences(DataMapItem $item, string $fieldName, array $fromRecord, array $forRecord)
515  {
516  $configuration = ‪$GLOBALS['TCA'][$item->getTableName()]['columns'][$fieldName];
517  $isLocalizationModeExclude = ($configuration['l10n_mode'] ?? null) === 'exclude';
518  $foreignTableName = $configuration['config']['foreign_table'];
519 
520  $fieldNames = [
521  'language' => ‪$GLOBALS['TCA'][$foreignTableName]['ctrl']['languageField'] ?? null,
522  'parent' => ‪$GLOBALS['TCA'][$foreignTableName]['ctrl']['transOrigPointerField'] ?? null,
523  'source' => ‪$GLOBALS['TCA'][$foreignTableName]['ctrl']['translationSource'] ?? null,
524  ];
525  $isTranslatable = (!empty($fieldNames['language']) && !empty($fieldNames['parent']));
526  $isLocalized = !empty($item->getLanguage());
527 
528  $suggestedAncestorIds = $this->‪resolveSuggestedInlineRelations(
529  $item,
530  $fieldName,
531  $fromRecord
532  );
533  $persistedIds = $this->‪resolvePersistedInlineRelations(
534  $item,
535  $fieldName,
536  $forRecord
537  );
538 
539  // The dependent ID map points from language parent/source record to
540  // localization, thus keys: parents/sources & values: localizations
541  $dependentIdMap = $this->‪fetchDependentIdMap($foreignTableName, $suggestedAncestorIds, (int)$item->getLanguage());
542  // filter incomplete structures - this is a drawback of DataHandler's remap stack, since
543  // just created IRRE translations still belong to the language parent - filter them out
544  $suggestedAncestorIds = array_diff($suggestedAncestorIds, array_values($dependentIdMap));
545  // compile element differences to be resolved
546  // remove elements that are persisted at the language translation, but not required anymore
547  $removeIds = array_diff($persistedIds, array_values($dependentIdMap));
548  // remove elements that are persisted at the language parent/source, but not required anymore
549  $removeAncestorIds = array_diff(array_keys($dependentIdMap), $suggestedAncestorIds);
550  // missing elements that are persisted at the language parent/source, but not translated yet
551  $missingAncestorIds = array_diff($suggestedAncestorIds, array_keys($dependentIdMap));
552  // persisted elements that should be copied or localized
553  $createAncestorIds = $this->‪filterNumericIds($missingAncestorIds);
554  // non-persisted elements that should be duplicated in data-map directly
555  $populateAncestorIds = array_diff($missingAncestorIds, $createAncestorIds);
556  // this desired state map defines the final result of child elements in their parent translation
557  $desiredIdMap = array_combine($suggestedAncestorIds, $suggestedAncestorIds) ?: [];
558  // update existing translations in the desired state map
559  foreach ($dependentIdMap as $ancestorId => $translationId) {
560  if (isset($desiredIdMap[$ancestorId])) {
561  $desiredIdMap[$ancestorId] = $translationId;
562  }
563  }
564  // no children to be synchronized, but element order could have been changed
565  if (empty($removeAncestorIds) && empty($missingAncestorIds)) {
566  $this->‪modifyDataMap(
567  $item->getTableName(),
568  $item->getId(),
569  [$fieldName => implode(',', array_values($desiredIdMap))]
570  );
571  return;
572  }
573  // In case only missing elements shall be created, re-use previously sanitized
574  // values IF the relation parent item is new and the count of missing relations
575  // equals the count of previously sanitized relations.
576  // This is caused during copy processes, when the child relations
577  // already have been cloned in DataHandler::copyRecord_procBasedOnFieldType()
578  // without the possibility to resolve the initial connections at this point.
579  // Otherwise child relations would superfluously be duplicated again here.
580  // @todo Invalid manually injected child relations cannot be determined here
581  $sanitizedValue = $this->sanitizationMap[$item->getTableName()][$item->getId()][$fieldName] ?? null;
582  if (
583  !empty($missingAncestorIds) && $item->isNew() && $sanitizedValue !== null
584  && count(‪GeneralUtility::trimExplode(',', $sanitizedValue, true)) === count($missingAncestorIds)
585  ) {
586  $this->‪modifyDataMap(
587  $item->getTableName(),
588  $item->getId(),
589  [$fieldName => $sanitizedValue]
590  );
591  return;
592  }
593 
594  $localCommandMap = [];
595  foreach ($removeIds as $removeId) {
596  $localCommandMap[$foreignTableName][$removeId]['delete'] = true;
597  }
598  foreach ($removeAncestorIds as $removeAncestorId) {
599  $removeId = $dependentIdMap[$removeAncestorId];
600  $localCommandMap[$foreignTableName][$removeId]['delete'] = true;
601  }
602  foreach ($createAncestorIds as $createAncestorId) {
603  // if child table is not aware of localization, just copy
604  if ($isLocalizationModeExclude || !$isTranslatable) {
605  $localCommandMap[$foreignTableName][$createAncestorId]['copy'] = [
606  'target' => -$createAncestorId,
607  'ignoreLocalization' => true,
608  ];
609  } else {
610  // otherwise, trigger the localization process
611  $localCommandMap[$foreignTableName][$createAncestorId]['localize'] = $item->getLanguage();
612  }
613  }
614  // execute copy, localize and delete actions on persisted child records
615  if (!empty($localCommandMap)) {
616  $localDataHandler = GeneralUtility::makeInstance(DataHandler::class, $this->‪referenceIndexUpdater);
617  $localDataHandler->start([], $localCommandMap, $this->‪backendUser);
618  $localDataHandler->process_cmdmap();
619  // update copied or localized ids
620  foreach ($createAncestorIds as $createAncestorId) {
621  if (empty($localDataHandler->copyMappingArray_merged[$foreignTableName][$createAncestorId])) {
622  $additionalInformation = '';
623  if (!empty($localDataHandler->errorLog)) {
624  $additionalInformation = ', reason "'
625  . implode(', ', $localDataHandler->errorLog) . '"';
626  }
627  throw new \RuntimeException(
628  'Child record was not processed' . $additionalInformation,
629  1486233164
630  );
631  }
632  $newLocalizationId = $localDataHandler->copyMappingArray_merged[$foreignTableName][$createAncestorId];
633  $newLocalizationId = $localDataHandler->getAutoVersionId($foreignTableName, $newLocalizationId) ?? $newLocalizationId;
634  $desiredIdMap[$createAncestorId] = $newLocalizationId;
635  // apply localization references to l10n_mode=exclude children
636  // (without keeping their reference to their origin, synchronization is not possible)
637  if ($isLocalizationModeExclude && $isTranslatable && $isLocalized) {
638  $adjustCopiedValues = $this->‪applyLocalizationReferences(
639  $foreignTableName,
640  $createAncestorId,
641  (int)$item->getLanguage(),
642  $fieldNames,
643  []
644  );
645  $this->‪modifyDataMap(
646  $foreignTableName,
647  $newLocalizationId,
648  $adjustCopiedValues
649  );
650  }
651  }
652  }
653  // populate new child records in data-map
654  if (!empty($populateAncestorIds)) {
655  foreach ($populateAncestorIds as $populateAncestorId) {
656  $newLocalizationId = ‪StringUtility::getUniqueId('NEW');
657  $desiredIdMap[$populateAncestorId] = $newLocalizationId;
658  $duplicatedValues = $this->allDataMap[$foreignTableName][$populateAncestorId] ?? [];
659  // applies localization references to given raw data-map item
660  if ($isTranslatable && $isLocalized) {
661  $duplicatedValues = $this->‪applyLocalizationReferences(
662  $foreignTableName,
663  $populateAncestorId,
664  (int)$item->getLanguage(),
665  $fieldNames,
666  $duplicatedValues
667  );
668  }
669  // prefixes language title if applicable for the accordant field name in raw data-map item
670  if ($isTranslatable && $isLocalized && !$isLocalizationModeExclude) {
671  $duplicatedValues = $this->‪prefixLanguageTitle(
672  $foreignTableName,
673  $populateAncestorId,
674  (int)$item->getLanguage(),
675  $duplicatedValues
676  );
677  }
678  $this->‪modifyDataMap(
679  $foreignTableName,
680  $newLocalizationId,
681  $duplicatedValues
682  );
683  }
684  }
685  // update inline parent field references - required to update pointer fields
686  $this->‪modifyDataMap(
687  $item->getTableName(),
688  $item->getId(),
689  [$fieldName => implode(',', array_values($desiredIdMap))]
690  );
691  }
692 
700  protected function ‪resolveSuggestedInlineRelations(DataMapItem $item, string $fieldName, array $fromRecord): array
701  {
702  $suggestedAncestorIds = [];
703  $fromId = $fromRecord['uid'];
704  $configuration = ‪$GLOBALS['TCA'][$item->getTableName()]['columns'][$fieldName];
705  $foreignTableName = $configuration['config']['foreign_table'];
706  $manyToManyTable = ($configuration['config']['MM'] ?? '');
707 
708  // determine suggested elements of either translation parent or source record
709  // from data-map, in case the accordant language parent/source record was modified
710  if ($this->‪isSetInDataMap($item->getTableName(), $fromId, $fieldName)) {
711  $suggestedAncestorIds = ‪GeneralUtility::trimExplode(
712  ',',
713  $this->allDataMap[$item->getTableName()][$fromId][$fieldName],
714  true
715  );
716  } elseif (‪MathUtility::canBeInterpretedAsInteger($fromId)) {
717  // determine suggested elements of either translation parent or source record from storage
718  $relationHandler = $this->‪createRelationHandler();
719  $relationHandler->start(
720  $fromRecord[$fieldName],
721  $foreignTableName,
722  $manyToManyTable,
723  $fromId,
724  $item->getTableName(),
725  $configuration['config']
726  );
727  $suggestedAncestorIds = $this->‪mapRelationItemId($relationHandler->itemArray);
728  }
729 
730  return array_filter($suggestedAncestorIds);
731  }
732 
738  private function ‪resolvePersistedInlineRelations(‪DataMapItem $item, string $fieldName, array $forRecord): array
739  {
740  $persistedIds = [];
741  $configuration = ‪$GLOBALS['TCA'][$item->‪getTableName()]['columns'][$fieldName];
742  $foreignTableName = $configuration['config']['foreign_table'];
743  $manyToManyTable = ($configuration['config']['MM'] ?? '');
744 
745  // determine persisted elements for the current data-map item
746  if (!$item->‪isNew()) {
747  $relationHandler = $this->‪createRelationHandler();
748  $relationHandler->start(
749  $forRecord[$fieldName] ?? '',
750  $foreignTableName,
751  $manyToManyTable,
752  $item->‪getId(),
753  $item->‪getTableName(),
754  $configuration['config']
755  );
756  $persistedIds = $this->‪mapRelationItemId($relationHandler->itemArray);
757  }
758 
759  return array_filter($persistedIds);
760  }
761 
770  protected function ‪isSetInDataMap(string $tableName, $id, string $fieldName)
771  {
772  return
773  // directly look-up field name
774  isset($this->allDataMap[$tableName][$id][$fieldName])
775  // check existence of field name as key for null values
776  || isset($this->allDataMap[$tableName][$id])
777  && is_array($this->allDataMap[$tableName][$id])
778  && array_key_exists($fieldName, $this->allDataMap[$tableName][$id]);
779  }
780 
789  protected function ‪modifyDataMap(string $tableName, $id, array $values)
790  {
791  // avoid superfluous iterations by data-map changes with values
792  // that actually have not been changed and were available already
793  $sameValues = array_intersect_assoc(
794  $this->allDataMap[$tableName][$id] ?? [],
795  $values
796  );
797  if (!empty($sameValues)) {
798  $fieldNames = implode(', ', array_keys($sameValues));
799  throw new \RuntimeException(
800  sprintf(
801  'Issued data-map change for table %s with same values '
802  . 'for these fields names %s',
803  $tableName,
804  $fieldNames
805  ),
806  1488634845
807  );
808  }
809 
810  $this->‪modifiedDataMap[$tableName][$id] = array_merge(
811  $this->‪modifiedDataMap[$tableName][$id] ?? [],
812  $values
813  );
814  $this->allDataMap[$tableName][$id] = array_merge(
815  $this->allDataMap[$tableName][$id] ?? [],
816  $values
817  );
818  }
819 
820  protected function ‪addNextItem(‪DataMapItem $item)
821  {
822  ‪$identifier = $item->‪getTableName() . ':' . $item->‪getId();
823  if (!isset($this->allItems[‪$identifier])) {
824  $this->allItems[‪$identifier] = $item;
825  }
826  $this->nextItems[‪$identifier] = $item;
827  }
828 
835  protected function ‪fetchTranslationValues(string $tableName, array $fieldNames, array $ids)
836  {
837  if (empty($ids)) {
838  return [];
839  }
840 
841  $connection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable($tableName);
842  $queryBuilder = $connection->createQueryBuilder();
843  $queryBuilder->getRestrictions()->removeAll()
844  // NOT using WorkspaceRestriction here since it's wrong in this case. See ws OR restriction below.
845  ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
846 
847  $expressions = [];
848  if (BackendUtility::isTableWorkspaceEnabled($tableName)) {
849  $expressions[] = $queryBuilder->expr()->eq('t3ver_wsid', 0);
850  }
851  if ($this->‪backendUser->workspace > 0 && BackendUtility::isTableWorkspaceEnabled($tableName)) {
852  // If this is a workspace record (t3ver_wsid = be-user-workspace), then fetch this one
853  // if it is NOT a deleted placeholder (t3ver_state=2), but ok with casual overlay (t3ver_state=0),
854  // new ws-record (t3ver_state=1), or moved record (t3ver_state=4).
855  // It *might* be possible to simplify this since it may be the case that ws-deleted records are
856  // impossible to be incoming here at all? But this query is a safe thing, so we go with it for now.
857  $expressions[] = $queryBuilder->expr()->and(
858  $queryBuilder->expr()->eq('t3ver_wsid', $queryBuilder->createNamedParameter($this->backendUser->workspace, ‪Connection::PARAM_INT)),
859  $queryBuilder->expr()->in(
860  't3ver_state',
861  $queryBuilder->createNamedParameter(
862  [VersionState::DEFAULT_STATE->value, VersionState::NEW_PLACEHOLDER->value, VersionState::MOVE_POINTER->value],
864  )
865  ),
866  );
867  }
868 
869  $translationValues = [];
870  $maxBindParameters = ‪PlatformInformation::getMaxBindParameters($connection->getDatabasePlatform());
871  // We are using the max bind parameter value as way to retrieve the data in chunks. However, we are not
872  // using up the placeholders by providing the id list directly, we keep this calculation to avoid hitting
873  // max query size limitation in most cases. If that is hit, it can be increased by adjusting the dbms setting.
874  foreach (array_chunk($ids, $maxBindParameters, true) as $chunk) {
875  $result = $queryBuilder
876  ->select(...array_values($fieldNames))
877  ->from($tableName)
878  ->where(
879  $queryBuilder->expr()->in(
880  'uid',
881  $queryBuilder->quoteArrayBasedValueListToIntegerList($chunk)
882  ),
883  $queryBuilder->expr()->or(...$expressions)
884  )
885  ->executeQuery();
886  while (‪$record = $result->fetchAssociative()) {
887  $translationValues[‪$record['uid']] = ‪$record;
888  }
889  }
890  return $translationValues;
891  }
892 
910  protected function ‪fetchDependencies(string $tableName, array $ids)
911  {
912  if (empty($ids) || !BackendUtility::isTableLocalizable($tableName)) {
913  return [];
914  }
915 
916  $fieldNames = [
917  'uid' => 'uid',
918  'l10n_state' => 'l10n_state',
919  'language' => ‪$GLOBALS['TCA'][$tableName]['ctrl']['languageField'],
920  'parent' => ‪$GLOBALS['TCA'][$tableName]['ctrl']['transOrigPointerField'],
921  ];
922  if (!empty(‪$GLOBALS['TCA'][$tableName]['ctrl']['translationSource'])) {
923  $fieldNames['source'] = ‪$GLOBALS['TCA'][$tableName]['ctrl']['translationSource'];
924  }
925  $fieldNamesMap = array_combine($fieldNames, $fieldNames);
926 
927  $persistedIds = $this->‪filterNumericIds($ids);
928  $createdIds = array_diff($ids, $persistedIds);
929  $dependentElements = $this->‪fetchDependentElements($tableName, $persistedIds, $fieldNames);
930 
931  foreach ($createdIds as $createdId) {
932  $data = $this->allDataMap[$tableName][$createdId] ?? null;
933  if ($data === null) {
934  continue;
935  }
936  $dependentElements[] = array_merge(
937  ['uid' => $createdId],
938  array_intersect_key($data, $fieldNamesMap)
939  );
940  }
941 
942  $dependencyMap = [];
943  foreach ($dependentElements as $dependentElement) {
944  $dependentItem = ‪DataMapItem::build(
945  $tableName,
946  $dependentElement['uid'],
947  [],
948  $dependentElement,
949  $fieldNames
950  );
951 
952  if ($dependentItem->isDirectChildType()) {
953  $dependencyMap[$dependentItem->getParent()][‪State::STATE_PARENT][] = $dependentItem;
954  }
955  if ($dependentItem->isGrandChildType()) {
956  $dependencyMap[$dependentItem->getParent()][‪State::STATE_PARENT][] = $dependentItem;
957  $dependencyMap[$dependentItem->getSource()][‪State::STATE_SOURCE][] = $dependentItem;
958  }
959  }
960  return $dependencyMap;
961  }
962 
987  protected function ‪fetchDependentIdMap(string $tableName, array $ids, int $desiredLanguage)
988  {
989  $ancestorIdMap = [];
990  if (empty($ids)) {
991  return [];
992  }
993 
994  $ids = $this->‪filterNumericIds($ids);
995  $isTranslatable = BackendUtility::isTableLocalizable($tableName);
996  $originFieldName = (‪$GLOBALS['TCA'][$tableName]['ctrl']['origUid'] ?? null);
997 
998  if (!$isTranslatable && $originFieldName === null) {
999  // @todo Possibly throw an error, since pointing to original entity is not possible (via origin/parent)
1000  return [];
1001  }
1002 
1003  if ($isTranslatable) {
1004  $fieldNames = [
1005  'uid' => 'uid',
1006  'l10n_state' => 'l10n_state',
1007  'language' => ‪$GLOBALS['TCA'][$tableName]['ctrl']['languageField'],
1008  'parent' => ‪$GLOBALS['TCA'][$tableName]['ctrl']['transOrigPointerField'],
1009  ];
1010  if (!empty(‪$GLOBALS['TCA'][$tableName]['ctrl']['translationSource'])) {
1011  $fieldNames['source'] = ‪$GLOBALS['TCA'][$tableName]['ctrl']['translationSource'];
1012  }
1013  } else {
1014  $fieldNames = [
1015  'uid' => 'uid',
1016  'origin' => $originFieldName,
1017  ];
1018  }
1019 
1020  $fetchIds = $ids;
1021  if ($isTranslatable) {
1022  // expand search criteria via parent and source elements
1023  $translationValues = $this->‪fetchTranslationValues($tableName, $fieldNames, $ids);
1024  $ancestorIdMap = $this->‪buildElementAncestorIdMap($fieldNames, $translationValues);
1025  $fetchIds = array_unique(array_merge($ids, array_keys($ancestorIdMap)));
1026  }
1027 
1028  $dependentElements = $this->‪fetchDependentElements($tableName, $fetchIds, $fieldNames);
1029 
1030  $dependentIdMap = [];
1031  foreach ($dependentElements as $dependentElement) {
1032  $dependentId = $dependentElement['uid'];
1033  // implicit: use origin pointer if table cannot be translated
1034  if (!$isTranslatable) {
1035  $ancestorId = (int)$dependentElement[$fieldNames['origin']];
1036  // only consider element if it reflects the desired language
1037  } elseif ((int)$dependentElement[$fieldNames['language']] === $desiredLanguage) {
1038  $ancestorId = $this->‪resolveAncestorId($fieldNames, $dependentElement);
1039  } else {
1040  // otherwise skip the element completely
1041  continue;
1042  }
1043  // only keep ancestors that were initially requested before expanding
1044  if (in_array($ancestorId, $ids, true)) {
1045  $dependentIdMap[$ancestorId] = $dependentId;
1046  } elseif (!empty($ancestorIdMap[$ancestorId])) {
1047  // resolve from previously expanded search criteria
1048  $possibleChainedIds = array_intersect(
1049  $ids,
1050  $ancestorIdMap[$ancestorId]
1051  );
1052  if (!empty($possibleChainedIds)) {
1053  // use the first found id from `$possibleChainedIds`
1054  $ancestorId = reset($possibleChainedIds);
1055  $dependentIdMap[$ancestorId] = $dependentId;
1056  }
1057  }
1058  }
1059  return $dependentIdMap;
1060  }
1061 
1070  protected function ‪fetchDependentElements(string $tableName, array $ids, array $fieldNames)
1071  {
1072  if (empty($ids)) {
1073  return [];
1074  }
1075 
1076  $connection = GeneralUtility::makeInstance(ConnectionPool::class)
1077  ->getConnectionForTable($tableName);
1078  $ids = $this->‪filterNumericIds($ids);
1079  $maxBindParameters = ‪PlatformInformation::getMaxBindParameters($connection->getDatabasePlatform());
1080  $dependentElements = [];
1081  foreach (array_chunk($ids, $maxBindParameters, true) as $idsChunked) {
1082  $queryBuilder = $connection->createQueryBuilder();
1083  $queryBuilder->getRestrictions()
1084  ->removeAll()
1085  ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
1086  ->add(GeneralUtility::makeInstance(WorkspaceRestriction::class, $this->‪backendUser->workspace));
1087 
1088  $zeroParameter = $queryBuilder->createNamedParameter(0, ‪Connection::PARAM_INT);
1089  $idsParameter = $queryBuilder->quoteArrayBasedValueListToIntegerList($idsChunked);
1090 
1091  // fetch by language dependency
1092  if (!empty($fieldNames['language']) && !empty($fieldNames['parent'])) {
1093  $ancestorPredicates = [
1094  $queryBuilder->expr()->in(
1095  $fieldNames['parent'],
1096  $idsParameter
1097  ),
1098  ];
1099  if (!empty($fieldNames['source'])) {
1100  $ancestorPredicates[] = $queryBuilder->expr()->in(
1101  $fieldNames['source'],
1102  $idsParameter
1103  );
1104  }
1105  $predicates = [
1106  // must be any kind of localization
1107  $queryBuilder->expr()->gt(
1108  $fieldNames['language'],
1109  $zeroParameter
1110  ),
1111  // must be in connected mode
1112  $queryBuilder->expr()->gt(
1113  $fieldNames['parent'],
1114  $zeroParameter
1115  ),
1116  // any parent or source pointers
1117  $queryBuilder->expr()->or(...$ancestorPredicates),
1118  ];
1119  } elseif (!empty($fieldNames['origin'])) {
1120  // fetch by origin dependency ("copied from")
1121  $predicates = [
1122  $queryBuilder->expr()->in(
1123  $fieldNames['origin'],
1124  $idsParameter
1125  ),
1126  ];
1127  } else {
1128  // otherwise: stop execution
1129  throw new \InvalidArgumentException(
1130  'Invalid combination of query field names given',
1131  1487192370
1132  );
1133  }
1134 
1135  $statement = $queryBuilder
1136  ->select(...array_values($fieldNames))
1137  ->from($tableName)
1138  ->andWhere(...$predicates)
1139  ->executeQuery();
1140 
1141  while (‪$record = $statement->fetchAssociative()) {
1142  $dependentElements[] = ‪$record;
1143  }
1144  }
1145  return $dependentElements;
1146  }
1154  protected function ‪filterItemsByType(string $type, array $items)
1155  {
1156  return array_filter(
1157  $items,
1158  static function (‪DataMapItem $item) use ($type): bool {
1159  return $item->‪getType() === $type;
1160  }
1161  );
1162  }
1170  protected function ‪filterNumericIds(array $ids)
1171  {
1172  $ids = array_filter(
1173  $ids,
1175  );
1176  return array_map(intval(...), $ids);
1177  }
1185  protected function ‪filterNewItemIds(string $tableName, array $ids)
1186  {
1187  return array_filter(
1188  $ids,
1189  function (string|int $id) use ($tableName): bool {
1190  return $this->‪findItem($tableName, $id) === null;
1191  }
1192  );
1193  }
1194 
1200  protected function ‪mapRelationItemId(array $relationItems): array
1201  {
1202  return array_map(
1203  static function (array $relationItem): int {
1204  return (int)$relationItem['id'];
1205  },
1206  $relationItems
1207  );
1208  }
1209 
1215  protected function ‪resolveAncestorId(array $fieldNames, array $element)
1216  {
1217  $sourceName = $fieldNames['source'] ?? null;
1218  if ($sourceName !== null && !empty($element[$sourceName])) {
1219  // implicit: use source pointer if given (not empty)
1220  return (int)$element[$sourceName];
1221  }
1222  $parentName = $fieldNames['parent'] ?? null;
1223  if ($parentName !== null && !empty($element[$parentName])) {
1224  // implicit: use parent pointer if given (not empty)
1225  return (int)$element[$parentName];
1226  }
1227  return null;
1228  }
1229 
1240  protected function ‪buildElementAncestorIdMap(array $fieldNames, array $elements)
1241  {
1242  $ancestorIdMap = [];
1243  foreach ($elements as $element) {
1244  $ancestorId = $this->‪resolveAncestorId($fieldNames, $element);
1245  if ($ancestorId !== null) {
1246  $ancestorIdMap[$ancestorId][] = (int)$element['uid'];
1247  }
1248  }
1249  return $ancestorIdMap;
1250  }
1258  protected function ‪findItem(string $tableName, $id)
1259  {
1260  return $this->allItems[$tableName . ':' . $id] ?? null;
1261  }
1262 
1268  protected function ‪applyLocalizationReferences(string $tableName, $fromId, int $language, array $fieldNames, array $data): array
1269  {
1270  // just return if localization cannot be applied
1271  if (empty($language)) {
1272  return $data;
1273  }
1274 
1275  // apply `languageField`, e.g. `sys_language_uid`
1276  $data[$fieldNames['language']] = $language;
1277  // apply `transOrigPointerField`, e.g. `l10n_parent`
1278  if (empty($data[$fieldNames['parent']])) {
1279  // @todo Only $id used in TCA type 'select' is resolved in DataHandler's remapStack
1280  $data[$fieldNames['parent']] = $fromId;
1281  }
1282  // apply `translationSource`, e.g. `l10n_source`
1283  if (!empty($fieldNames['source'])) {
1284  // @todo Not sure, whether $id is resolved in DataHandler's remapStack
1285  $data[$fieldNames['source']] = $fromId;
1286  }
1287  // unset field names that are expected to be handled in this processor
1288  foreach ($this->‪getFieldNamesToBeHandled($tableName) as $fieldName) {
1289  unset($data[$fieldName]);
1290  }
1291 
1292  return $data;
1293  }
1294 
1300  protected function ‪prefixLanguageTitle(string $tableName, $fromId, int $language, array $data): array
1301  {
1302  $prefixFieldNames = array_intersect(
1303  array_keys($data),
1304  $this->‪getPrefixLanguageTitleFieldNames($tableName)
1305  );
1306  if (empty($prefixFieldNames)) {
1307  return $data;
1308  }
1309 
1310  [$pageId] = BackendUtility::getTSCpid($tableName, (int)$fromId, $data['pid'] ?? null);
1311  $tsConfig = BackendUtility::getPagesTSconfig($pageId)['TCEMAIN.'] ?? [];
1312  if (($translateToMessage = (string)($tsConfig['translateToMessage'] ?? '')) === '') {
1313  // Return in case translateToMessage had been unset
1314  return $data;
1315  }
1316 
1317  $tableRelatedConfig = $tsConfig['default.'] ?? [];
1318  ArrayUtility::mergeRecursiveWithOverrule(
1319  $tableRelatedConfig,
1320  $tsConfig['table.'][$tableName . '.'] ?? []
1321  );
1322  if ($tableRelatedConfig['disablePrependAtCopy'] ?? false) {
1323  // Return in case "disablePrependAtCopy" is set for this table
1324  return $data;
1325  }
1326 
1327  try {
1328  $site = GeneralUtility::makeInstance(SiteFinder::class)->getSiteByPageId($pageId);
1329  $siteLanguage = $site->getLanguageById($language);
1330  $languageTitle = $siteLanguage->getTitle();
1331  } catch (SiteNotFoundException | \InvalidArgumentException $e) {
1332  $languageTitle = '';
1333  }
1334 
1335  $languageService = $this->‪getLanguageService();
1336  if ($languageService !== null) {
1337  $translateToMessage = $languageService->sL($translateToMessage);
1338  }
1339  $translateToMessage = sprintf($translateToMessage, $languageTitle);
1340 
1341  if ($translateToMessage === '') {
1342  // Return for edge cases when the translateToMessage got empty, e.g. because the referenced LLL
1343  // label is empty or only contained a placeholder which is replaced by an empty language title.
1344  return $data;
1345  }
1346 
1347  foreach ($prefixFieldNames as $prefixFieldName) {
1348  // @todo The hook in DataHandler is not applied here
1349  $data[$prefixFieldName] = '[' . $translateToMessage . '] ' . $data[$prefixFieldName];
1350  }
1351 
1352  return $data;
1353  }
1354 
1360  protected function ‪getFieldNamesForItemScope(
1361  ‪DataMapItem $item,
1362  string $scope,
1363  bool $modified
1364  ) {
1365  if (
1366  $scope === ‪DataMapItem::SCOPE_PARENT
1367  || $scope === ‪DataMapItem::SCOPE_SOURCE
1368  ) {
1369  if (!‪State::isApplicable($item->‪getTableName())) {
1370  return [];
1371  }
1372  return $item->‪getState()->filterFieldNames($scope, $modified);
1373  }
1374  if ($scope === ‪DataMapItem::SCOPE_EXCLUDE) {
1376  $item->‪getTableName()
1377  );
1378  }
1379  return [];
1380  }
1381 
1387  protected function ‪getLocalizationModeExcludeFieldNames(string $tableName)
1388  {
1389  $localizationExcludeFieldNames = [];
1390  if (empty(‪$GLOBALS['TCA'][$tableName]['columns'])) {
1391  return $localizationExcludeFieldNames;
1392  }
1393 
1394  foreach (‪$GLOBALS['TCA'][$tableName]['columns'] as $fieldName => $configuration) {
1395  if (($configuration['l10n_mode'] ?? null) === 'exclude'
1396  && ($configuration['config']['type'] ?? null) !== 'none'
1397  ) {
1398  $localizationExcludeFieldNames[] = $fieldName;
1399  }
1400  }
1401 
1402  return $localizationExcludeFieldNames;
1403  }
1411  protected function ‪getFieldNamesToBeHandled(string $tableName)
1412  {
1413  return array_merge(
1414  ‪State::getFieldNames($tableName),
1415  $this->‪getLocalizationModeExcludeFieldNames($tableName)
1416  );
1417  }
1418 
1424  protected function ‪getPrefixLanguageTitleFieldNames(string $tableName)
1425  {
1426  $prefixLanguageTitleFieldNames = [];
1427  if (empty(‪$GLOBALS['TCA'][$tableName]['columns'])) {
1428  return $prefixLanguageTitleFieldNames;
1429  }
1430 
1431  foreach (‪$GLOBALS['TCA'][$tableName]['columns'] as $fieldName => $configuration) {
1432  $type = $configuration['config']['type'] ?? null;
1433  if (
1434  ($configuration['l10n_mode'] ?? null) === 'prefixLangTitle'
1435  && ($type === 'input' || $type === 'text' || $type === 'email')
1436  ) {
1437  $prefixLanguageTitleFieldNames[] = $fieldName;
1438  }
1439  }
1440 
1441  return $prefixLanguageTitleFieldNames;
1442  }
1443 
1449  protected function ‪isRelationField(string $tableName, string $fieldName): bool
1450  {
1451  if (empty(‪$GLOBALS['TCA'][$tableName]['columns'][$fieldName]['config']['type'])) {
1452  return false;
1453  }
1454 
1455  $configuration = ‪$GLOBALS['TCA'][$tableName]['columns'][$fieldName]['config'];
1456 
1457  return ($configuration['type'] === 'group' && !empty($configuration['allowed']))
1458  || (
1459  ($configuration['type'] === 'select' || $configuration['type'] === 'category')
1460  && !empty($configuration['foreign_table'])
1461  && !empty(‪$GLOBALS['TCA'][$configuration['foreign_table']])
1462  )
1463  || $this->‪isReferenceField($tableName, $fieldName)
1464  ;
1465  }
1466 
1472  protected function ‪isReferenceField(string $tableName, string $fieldName): bool
1473  {
1474  if (empty(‪$GLOBALS['TCA'][$tableName]['columns'][$fieldName]['config']['type'])) {
1475  return false;
1476  }
1477 
1478  $configuration = ‪$GLOBALS['TCA'][$tableName]['columns'][$fieldName]['config'];
1479 
1480  return
1481  ($configuration['type'] === 'inline' || $configuration['type'] === 'file')
1482  && !empty($configuration['foreign_table'])
1483  && !empty(‪$GLOBALS['TCA'][$configuration['foreign_table']])
1484  ;
1485  }
1486 
1491  protected function ‪isApplicable(string $tableName): bool
1492  {
1493  return
1494  ‪State::isApplicable($tableName)
1495  || BackendUtility::isTableLocalizable($tableName)
1496  && count($this->‪getLocalizationModeExcludeFieldNames($tableName)) > 0
1497  ;
1498  }
1499 
1503  protected function ‪createRelationHandler()
1504  {
1505  $relationHandler = GeneralUtility::makeInstance(RelationHandler::class);
1506  $relationHandler->setWorkspaceId($this->‪backendUser->workspace);
1507  return $relationHandler;
1508  }
1509 
1510  protected function ‪getLanguageService(): ?‪LanguageService
1511  {
1512  return ‪$GLOBALS['LANG'] ?? null;
1513  }
1514 }
‪TYPO3\CMS\Core\DataHandling\DataHandler
Definition: DataHandler.php:94
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapItem\getType
‪getType()
Definition: DataMapItem.php:200
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor\createRelationHandler
‪RelationHandler createRelationHandler()
Definition: DataMapProcessor.php:1496
‪TYPO3\CMS\Core\Database\Connection\PARAM_INT
‪const PARAM_INT
Definition: Connection.php:52
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor\resolveSuggestedInlineRelations
‪int[] string[] resolveSuggestedInlineRelations(DataMapItem $item, string $fieldName, array $fromRecord)
Definition: DataMapProcessor.php:693
‪TYPO3\CMS\Core\Database\Platform\PlatformInformation\getMaxBindParameters
‪static getMaxBindParameters(DoctrineAbstractPlatform $platform)
Definition: PlatformInformation.php:106
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapItem\isParentType
‪isParentType()
Definition: DataMapItem.php:221
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor\synchronizeReferences
‪synchronizeReferences(DataMapItem $item, string $fieldName, array $fromRecord, array $forRecord)
Definition: DataMapProcessor.php:507
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor\sanitize
‪sanitize(array $items)
Definition: DataMapProcessor.php:254
‪TYPO3\CMS\Core\Database\RelationHandler
Definition: RelationHandler.php:36
‪TYPO3\CMS\Core\Versioning\VersionState
‪VersionState
Definition: VersionState.php:22
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapItem\findDependencies
‪DataMapItem[] findDependencies(string $scope)
Definition: DataMapItem.php:329
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapItem\build
‪static DataMapItem build(string $tableName, $id, array $suggestedValues, array $persistedValues, array $configurationFieldNames)
Definition: DataMapItem.php:90
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor\getFieldNamesToBeHandled
‪string[] getFieldNamesToBeHandled(string $tableName)
Definition: DataMapProcessor.php:1404
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor\filterNewItemIds
‪array filterNewItemIds(string $tableName, array $ids)
Definition: DataMapProcessor.php:1178
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor\resolvePersistedInlineRelations
‪int[] resolvePersistedInlineRelations(DataMapItem $item, string $fieldName, array $forRecord)
Definition: DataMapProcessor.php:731
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor\backendUser
‪$this backendUser
Definition: DataMapProcessor.php:121
‪TYPO3\CMS\Core\DataHandling\Localization\State\STATE_PARENT
‪const STATE_PARENT
Definition: State.php:28
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor\referenceIndexUpdater
‪if($referenceIndexUpdater===null) $this referenceIndexUpdater
Definition: DataMapProcessor.php:125
‪TYPO3\CMS\Core\Exception\SiteNotFoundException
Definition: SiteNotFoundException.php:25
‪TYPO3\CMS\Core\Site\SiteFinder
Definition: SiteFinder.php:31
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor\fetchDependentElements
‪array fetchDependentElements(string $tableName, array $ids, array $fieldNames)
Definition: DataMapProcessor.php:1063
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor\getFieldNamesForItemScope
‪string[] getFieldNamesForItemScope(DataMapItem $item, string $scope, bool $modified)
Definition: DataMapProcessor.php:1353
‪TYPO3\CMS\Core\DataHandling\Localization\State\getFieldNames
‪static string[] getFieldNames(string $tableName)
Definition: State.php:70
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor\fetchDependentIdMap
‪array fetchDependentIdMap(string $tableName, array $ids, int $desiredLanguage)
Definition: DataMapProcessor.php:980
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor\getPrefixLanguageTitleFieldNames
‪array getPrefixLanguageTitleFieldNames(string $tableName)
Definition: DataMapProcessor.php:1417
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor\buildElementAncestorIdMap
‪array buildElementAncestorIdMap(array $fieldNames, array $elements)
Definition: DataMapProcessor.php:1233
‪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:903
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor\modifyDataMap
‪modifyDataMap(string $tableName, $id, array $values)
Definition: DataMapProcessor.php:782
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor\isReferenceField
‪bool isReferenceField(string $tableName, string $fieldName)
Definition: DataMapProcessor.php:1465
‪TYPO3\CMS\Core\Utility\MathUtility\canBeInterpretedAsInteger
‪static bool canBeInterpretedAsInteger(mixed $var)
Definition: MathUtility.php:69
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor
Definition: DataMapProcessor.php:59
‪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:185
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor\enrich
‪enrich(array $items)
Definition: DataMapProcessor.php:268
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapItem\getApplicableScopes
‪string[] getApplicableScopes()
Definition: DataMapItem.php:337
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor\fetchTranslationValues
‪array fetchTranslationValues(string $tableName, array $fieldNames, array $ids)
Definition: DataMapProcessor.php:828
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor\mapRelationItemId
‪int[] mapRelationItemId(array $relationItems)
Definition: DataMapProcessor.php:1193
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor\process
‪array process()
Definition: DataMapProcessor.php:134
‪TYPO3\CMS\Webhooks\Message\$record
‪identifier readonly int readonly array $record
Definition: PageModificationMessage.php:36
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapItem\isNew
‪isNew()
Definition: DataMapItem.php:195
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor\modifiedDataMap
‪array< string, $modifiedDataMap=array();protected array< string, $sanitizationMap=array();protected BackendUserAuthentication $backendUser;protected ReferenceIndexUpdater $referenceIndexUpdater;protected DataMapItem[] $allItems=array();protected DataMapItem[] $nextItems=array();public static DataMapProcessor function instance(array $dataMap, BackendUserAuthentication $backendUser, ReferenceIndexUpdater $referenceIndexUpdater=null) { return GeneralUtility::makeInstance(static::class, $dataMap, $backendUser, $referenceIndexUpdater);} public function __construct(array $dataMap, BackendUserAuthentication $backendUser, ReferenceIndexUpdater $referenceIndexUpdater=null) { $this->allDataMap=$dataMap;$this-> modifiedDataMap
Definition: DataMapProcessor.php:120
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapItem\getState
‪getState()
Definition: DataMapItem.php:236
‪TYPO3\CMS\Core\DataHandling\Localization\State\isApplicable
‪static isApplicable(string $tableName)
Definition: State.php:57
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapItem\getTableName
‪getTableName()
Definition: DataMapItem.php:138
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor\isSetInDataMap
‪bool isSetInDataMap(string $tableName, $id, string $fieldName)
Definition: DataMapProcessor.php:763
‪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:1147
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor\addNextItem
‪addNextItem(DataMapItem $item)
Definition: DataMapProcessor.php:813
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor\prefixLanguageTitle
‪prefixLanguageTitle(string $tableName, $fromId, int $language, array $data)
Definition: DataMapProcessor.php:1293
‪TYPO3\CMS\Core\DataHandling\Localization
Definition: DataMapItem.php:16
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor\filterNumericIds
‪int[] filterNumericIds(array $ids)
Definition: DataMapProcessor.php:1163
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapItem
Definition: DataMapItem.php:26
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor\getLanguageService
‪getLanguageService()
Definition: DataMapProcessor.php:1503
‪TYPO3\CMS\Core\DataHandling\Localization\State\STATE_SOURCE
‪const STATE_SOURCE
Definition: State.php:29
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor\synchronizeDirectRelations
‪synchronizeDirectRelations(DataMapItem $item, string $fieldName, array $fromRecord)
Definition: DataMapProcessor.php:445
‪TYPO3\CMS\Core\Database\Connection
Definition: Connection.php:41
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapItem\getLanguage
‪string int getLanguage()
Definition: DataMapItem.php:247
‪TYPO3\CMS\Core\Utility\ArrayUtility
Definition: ArrayUtility.php:26
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor\resolveAncestorId
‪int null resolveAncestorId(array $fieldNames, array $element)
Definition: DataMapProcessor.php:1208
‪$GLOBALS
‪$GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['adminpanel']['modules']
Definition: ext_localconf.php:25
‪TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction
Definition: DeletedRestriction.php:28
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapItem\SCOPE_SOURCE
‪const SCOPE_SOURCE
Definition: DataMapItem.php:32
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor\isRelationField
‪bool isRelationField(string $tableName, string $fieldName)
Definition: DataMapProcessor.php:1442
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor\applyLocalizationReferences
‪applyLocalizationReferences(string $tableName, $fromId, int $language, array $fieldNames, array $data)
Definition: DataMapProcessor.php:1261
‪TYPO3\CMS\Core\Database\Platform\PlatformInformation
Definition: PlatformInformation.php:33
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor\$allDataMap
‪array $allDataMap
Definition: DataMapProcessor.php:62
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor\findItem
‪DataMapItem null findItem(string $tableName, $id)
Definition: DataMapProcessor.php:1251
‪TYPO3\CMS\Core\Utility\MathUtility
Definition: MathUtility.php:24
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor\isApplicable
‪isApplicable(string $tableName)
Definition: DataMapProcessor.php:1484
‪TYPO3\CMS\Core\Localization\LanguageService
Definition: LanguageService.php:46
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor\synchronizeFieldValues
‪synchronizeFieldValues(DataMapItem $item, string $fieldName, array $fromRecord, array $forRecord)
Definition: DataMapProcessor.php:407
‪TYPO3\CMS\Core\Database\ConnectionPool
Definition: ConnectionPool.php:46
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor\sanitizeTranslationItem
‪sanitizeTranslationItem(DataMapItem $item)
Definition: DataMapProcessor.php:291
‪TYPO3\CMS\Core\Utility\GeneralUtility
Definition: GeneralUtility.php:52
‪TYPO3\CMS\Core\Utility\StringUtility
Definition: StringUtility.php:24
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor\synchronizeTranslationItem
‪synchronizeTranslationItem(DataMapItem $item, array $fieldNames, $fromId)
Definition: DataMapProcessor.php:319
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor\getLocalizationModeExcludeFieldNames
‪string[] getLocalizationModeExcludeFieldNames(string $tableName)
Definition: DataMapProcessor.php:1380
‪TYPO3\CMS\Core\DataHandling\ReferenceIndexUpdater
Definition: ReferenceIndexUpdater.php:35
‪TYPO3\CMS\Core\Database\Connection\PARAM_INT_ARRAY
‪const PARAM_INT_ARRAY
Definition: Connection.php:72
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor\finishTranslationItem
‪finishTranslationItem(DataMapItem $item)
Definition: DataMapProcessor.php:392
‪TYPO3\CMS\Core\Utility\GeneralUtility\trimExplode
‪static list< string > trimExplode(string $delim, string $string, bool $removeEmptyValues=false, int $limit=0)
Definition: GeneralUtility.php:822
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapItem\getId
‪int string getId()
Definition: DataMapItem.php:148
‪TYPO3\CMS\Webhooks\Message\$identifier
‪identifier readonly string $identifier
Definition: FileAddedMessage.php:37
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapItem\SCOPE_PARENT
‪const SCOPE_PARENT
Definition: DataMapItem.php:31
‪TYPO3\CMS\Core\Utility\StringUtility\getUniqueId
‪static getUniqueId(string $prefix='')
Definition: StringUtility.php:57
‪TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor\purgeDataMap
‪purgeDataMap(array $dataMap)
Definition: DataMapProcessor.php:162
‪TYPO3\CMS\Core\Database\Query\Restriction\WorkspaceRestriction
Definition: WorkspaceRestriction.php:39