Skip to content

Commit 7292c3d

Browse files
committed
Add support to copy translated values
see issue #598 fix issue #139
1 parent 23aa72d commit 7292c3d

5 files changed

Lines changed: 461 additions & 1 deletion

File tree

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
<?php
2+
3+
/**
4+
* This file is part of MetaModels/core.
5+
*
6+
* (c) 2012-2026 The MetaModels team.
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*
11+
* This project is provided in good faith and hope to be usable by anyone.
12+
*
13+
* @package MetaModels/core
14+
* @author Ingolf Steinhardt <info@e-spin.de>
15+
* @copyright 2012-2026 The MetaModels team.
16+
* @license https://github.com/MetaModels/core/blob/master/LICENSE LGPL-3.0-or-later
17+
* @filesource
18+
*/
19+
20+
namespace MetaModels\Attribute;
21+
22+
/**
23+
* Extended interface for translated attributes that support fetching raw per-language data without automatic fallback
24+
* to the main language.
25+
*
26+
* This separate interface allows opt-in without breaking existing ITranslated implementors.
27+
* Consumers check instanceof before calling getTranslatedDataForWithoutFallback().
28+
*/
29+
interface ITranslatedWithFallbackControl extends ITranslated
30+
{
31+
/**
32+
* Get values for the given items in a certain language without falling back to the main language.
33+
*
34+
* Unlike getTranslatedDataFor(), this method returns only data actually stored for $strLangCode.
35+
* No second pass against the main language is performed.
36+
*
37+
* @param list<string> $arrIds The ids for which values shall be retrieved.
38+
* @param string $strLangCode The language code for which the data shall be retrieved.
39+
*
40+
* @return array<string, array<string, mixed>> the values.
41+
*/
42+
public function getTranslatedDataForWithoutFallback(array $arrIds, string $strLangCode): array;
43+
44+
/**
45+
* Copy source data to a new item for a given language.
46+
*
47+
* Implementations may transform the data before persisting it — e.g. a unique-alias attribute
48+
* re-generates the slug to satisfy its uniqueness constraint. The default behaviour is to call
49+
* setTranslatedDataFor() verbatim.
50+
*
51+
* @param array<string, mixed> $sourceData The raw attribute data of the source item for $strLangCode.
52+
* @param string $newId The id of the new (copied) item.
53+
* @param string $strLangCode The language code being processed.
54+
*
55+
* @return void
56+
*/
57+
public function copyTranslatedDataFor(array $sourceData, string $newId, string $strLangCode): void;
58+
}

‎src/Attribute/TranslatedReference.php‎

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@
3838
*
3939
* @SuppressWarnings(PHPMD.ExcessiveClassComplexity)
4040
*/
41-
abstract class TranslatedReference extends BaseComplex implements ITranslated
41+
abstract class TranslatedReference extends BaseComplex implements ITranslatedWithFallbackControl
4242
{
4343
/**
4444
* Database connection.
@@ -455,6 +455,24 @@ protected function fetchExistingIdsFor($idList, $langCode)
455455
return $queryBuilder->executeQuery()->fetchFirstColumn();
456456
}
457457

458+
/**
459+
* {@inheritDoc}
460+
*/
461+
#[\Override]
462+
public function getTranslatedDataForWithoutFallback(array $arrIds, string $strLangCode): array
463+
{
464+
return $this->getTranslatedDataFor($arrIds, $strLangCode);
465+
}
466+
467+
/**
468+
* {@inheritDoc}
469+
*/
470+
#[\Override]
471+
public function copyTranslatedDataFor(array $sourceData, string $newId, string $strLangCode): void
472+
{
473+
$this->setTranslatedDataFor([$newId => $sourceData], $strLangCode);
474+
}
475+
458476
/**
459477
* {@inheritDoc}
460478
*/

‎src/CoreBundle/Resources/config/listeners.yml‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,15 @@ services:
4848
event: dc-general.model.pre-duplicate
4949
method: handle
5050

51+
metamodels.copy_translated_data_listener:
52+
class: MetaModels\DcGeneral\Events\MetaModel\CopyTranslatedData
53+
arguments:
54+
- "@metamodels.factory"
55+
tags:
56+
- name: kernel.event_listener
57+
event: dc-general.model.post-duplicate
58+
method: handle
59+
5160
metamodels.sub_system_boot:
5261
class: MetaModels\CoreBundle\EventListener\SubSystemBootListener
5362
arguments:
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
<?php
2+
3+
/**
4+
* This file is part of MetaModels/core.
5+
*
6+
* (c) 2012-2026 The MetaModels team.
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*
11+
* This project is provided in good faith and hope to be usable by anyone.
12+
*
13+
* @package MetaModels/core
14+
* @author Ingolf Steinhardt <info@e-spin.de>
15+
* @copyright 2012-2026 The MetaModels team.
16+
* @license https://github.com/MetaModels/core/blob/master/LICENSE LGPL-3.0-or-later
17+
* @filesource
18+
*/
19+
20+
namespace MetaModels\DcGeneral\Events\MetaModel;
21+
22+
use ContaoCommunityAlliance\DcGeneral\Event\PostDuplicateModelEvent;
23+
use MetaModels\Attribute\ITranslatedWithFallbackControl;
24+
use MetaModels\IFactory;
25+
use MetaModels\ITranslatedMetaModel;
26+
27+
/**
28+
* Copies translated attribute data for all languages when an item is duplicated.
29+
*
30+
* The normal DC_General duplicate path only saves data for the currently active language. This listener runs after
31+
* the copy has been persisted and iterates every language, copying each translated attribute's raw (non-fallback) data
32+
* from the source item to the new item.
33+
*/
34+
final class CopyTranslatedData
35+
{
36+
/**
37+
* The MetaModels factory.
38+
*
39+
* @var IFactory
40+
*/
41+
private IFactory $factory;
42+
43+
/**
44+
* Create a new instance.
45+
*
46+
* @param IFactory $factory The factory.
47+
*/
48+
public function __construct(IFactory $factory)
49+
{
50+
$this->factory = $factory;
51+
}
52+
53+
/**
54+
* Copy translated data for all languages after a model has been duplicated.
55+
*
56+
* @param PostDuplicateModelEvent $event The event.
57+
*
58+
* @return void
59+
*/
60+
public function handle(PostDuplicateModelEvent $event): void
61+
{
62+
$sourceId = (string) $event->getSourceModel()->getId();
63+
$newId = (string) $event->getModel()->getId();
64+
65+
if ('' === $sourceId || '' === $newId || $sourceId === $newId) {
66+
return;
67+
}
68+
69+
$metaModel = $this->factory->getMetaModel($event->getModel()->getProviderName());
70+
if (!$metaModel instanceof ITranslatedMetaModel) {
71+
return;
72+
}
73+
74+
foreach ($metaModel->getLanguages() as $language) {
75+
$this->copyLanguage($metaModel, $language, $sourceId, $newId);
76+
}
77+
}
78+
79+
/**
80+
* Copy all translated attributes for a single language.
81+
*
82+
* @param ITranslatedMetaModel $metaModel The MetaModel.
83+
* @param string $language The language code.
84+
* @param string $sourceId The source item ID.
85+
* @param string $newId The new item ID.
86+
*
87+
* @return void
88+
*/
89+
private function copyLanguage(
90+
ITranslatedMetaModel $metaModel,
91+
string $language,
92+
string $sourceId,
93+
string $newId
94+
): void {
95+
foreach ($metaModel->getAttributes() as $attribute) {
96+
if (!$attribute instanceof ITranslatedWithFallbackControl) {
97+
continue;
98+
}
99+
100+
$data = $attribute->getTranslatedDataForWithoutFallback([$sourceId], $language);
101+
if ([] === $data || !isset($data[$sourceId])) {
102+
continue;
103+
}
104+
105+
$attribute->copyTranslatedDataFor($data[$sourceId], $newId, $language);
106+
}
107+
}
108+
}

0 commit comments

Comments
 (0)