Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 57 additions & 0 deletions src/Attribute/ITranslatedWithFallbackControl.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
<?php

/**
* This file is part of MetaModels/core.
*
* (c) 2012-2026 The MetaModels team.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* This project is provided in good faith and hope to be usable by anyone.
*
* @package MetaModels/core
* @author Ingolf Steinhardt <info@e-spin.de>
* @copyright 2012-2026 The MetaModels team.
* @license https://github.com/MetaModels/core/blob/master/LICENSE LGPL-3.0-or-later
* @filesource
*/

namespace MetaModels\Attribute;

/**
* Extended interface for translated attributes that support fetching raw per-language data without automatic fallback
* to the main language.
*
* This separate interface allows opt-in without breaking existing ITranslated implementors.
* Consumers check instanceof before calling getTranslatedDataForWithoutFallback().
*/
interface ITranslatedWithFallbackControl extends ITranslated
{
/**
* Get values for the given items in a certain language without falling back to the main language.
*
* Unlike getTranslatedDataFor(), this method returns only data actually stored for $strLangCode.
* No second pass against the main language is performed.
*
* @param list<string> $arrIds The ids for which values shall be retrieved.
* @param string $strLangCode The language code for which the data shall be retrieved.
*
* @return array<string, array<string, mixed>> the values.
*/
public function getTranslatedDataForWithoutFallback(array $arrIds, string $strLangCode): array;

/**
* Save translated data for a given language, potentially with attribute-specific transformation.
*
* Implementations may transform the data before persisting it — e.g. a unique-alias attribute
* re-generates the slug to satisfy its uniqueness constraint. The default behaviour is to call
* setTranslatedDataFor() verbatim.
*
* @param array<string, array<string, mixed>> $arrValues The values keyed by item id.
* @param string $strLangCode The language code being processed.
*
* @return void
*/
public function applyTranslatedDataFor(array $arrValues, string $strLangCode): void;
}
24 changes: 21 additions & 3 deletions src/Attribute/TranslatedReference.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
/**
* This file is part of MetaModels/core.
*
* (c) 2012-2024 The MetaModels team.
* (c) 2012-2026 The MetaModels team.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
Expand All @@ -19,7 +19,7 @@
* @author Sven Baumann <baumann.sv@gmail.com>
* @author David Molineus <david.molineus@netzmacht.de>
* @author Andreas Fischer <anfischer@kaffee-partner.de>
* @copyright 2012-2024 The MetaModels team.
* @copyright 2012-2026 The MetaModels team.
* @license https://github.com/MetaModels/core/blob/master/LICENSE LGPL-3.0-or-later
* @filesource
*/
Expand All @@ -38,7 +38,7 @@
*
* @SuppressWarnings(PHPMD.ExcessiveClassComplexity)
*/
abstract class TranslatedReference extends BaseComplex implements ITranslated
abstract class TranslatedReference extends BaseComplex implements ITranslatedWithFallbackControl
{
/**
* Database connection.
Expand Down Expand Up @@ -455,6 +455,24 @@ protected function fetchExistingIdsFor($idList, $langCode)
return $queryBuilder->executeQuery()->fetchFirstColumn();
}

/**
* {@inheritDoc}
*/
#[\Override]
public function getTranslatedDataForWithoutFallback(array $arrIds, string $strLangCode): array
{
return $this->getTranslatedDataFor($arrIds, $strLangCode);
}

/**
* {@inheritDoc}
*/
#[\Override]
public function applyTranslatedDataFor(array $arrValues, string $strLangCode): void
{
$this->setTranslatedDataFor($arrValues, $strLangCode);
}

/**
* {@inheritDoc}
*/
Expand Down
18 changes: 18 additions & 0 deletions src/CoreBundle/Resources/config/listeners.yml
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,24 @@ services:
event: dc-general.model.pre-duplicate
method: handle

metamodels.copy_translated_data_listener:
class: MetaModels\DcGeneral\Events\MetaModel\CopyTranslatedData
arguments:
- "@metamodels.factory"
tags:
- name: kernel.event_listener
event: dc-general.model.post-duplicate
method: handle

metamodels.reset_language_after_duplicate_listener:
class: MetaModels\DcGeneral\Events\MetaModel\ResetLanguageAfterDuplicate
arguments:
- "@metamodels.factory"
tags:
- name: kernel.event_listener
event: dc-general.model.post-duplicate
method: handle

metamodels.sub_system_boot:
class: MetaModels\CoreBundle\EventListener\SubSystemBootListener
arguments:
Expand Down
108 changes: 108 additions & 0 deletions src/DcGeneral/Events/MetaModel/CopyTranslatedData.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
<?php

/**
* This file is part of MetaModels/core.
*
* (c) 2012-2026 The MetaModels team.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* This project is provided in good faith and hope to be usable by anyone.
*
* @package MetaModels/core
* @author Ingolf Steinhardt <info@e-spin.de>
* @copyright 2012-2026 The MetaModels team.
* @license https://github.com/MetaModels/core/blob/master/LICENSE LGPL-3.0-or-later
* @filesource
*/

namespace MetaModels\DcGeneral\Events\MetaModel;

use ContaoCommunityAlliance\DcGeneral\Event\PostDuplicateModelEvent;
use MetaModels\Attribute\ITranslatedWithFallbackControl;
use MetaModels\IFactory;
use MetaModels\ITranslatedMetaModel;

/**
* Copies translated attribute data for all languages when an item is duplicated.
*
* The normal DC_General duplicate path only saves data for the currently active language. This listener runs after
* the copy has been persisted and iterates every language, copying each translated attribute's raw (non-fallback) data
* from the source item to the new item.
*/
final class CopyTranslatedData
{
/**
* The MetaModels factory.
*
* @var IFactory
*/
private IFactory $factory;

/**
* Create a new instance.
*
* @param IFactory $factory The factory.
*/
public function __construct(IFactory $factory)
{
$this->factory = $factory;
}

/**
* Copy translated data for all languages after a model has been duplicated.
*
* @param PostDuplicateModelEvent $event The event.
*
* @return void
*/
public function handle(PostDuplicateModelEvent $event): void
{
$sourceId = (string) $event->getSourceModel()->getId();
$newId = (string) $event->getModel()->getId();

if ('' === $sourceId || '' === $newId || $sourceId === $newId) {
return;
}

$metaModel = $this->factory->getMetaModel($event->getModel()->getProviderName());
if (!$metaModel instanceof ITranslatedMetaModel) {
return;
}

foreach ($metaModel->getLanguages() as $language) {
$this->copyLanguage($metaModel, $language, $sourceId, $newId);
}
}

/**
* Copy all translated attributes for a single language.
*
* @param ITranslatedMetaModel $metaModel The MetaModel.
* @param string $language The language code.
* @param string $sourceId The source item ID.
* @param string $newId The new item ID.
*
* @return void
*/
private function copyLanguage(
ITranslatedMetaModel $metaModel,
string $language,
string $sourceId,
string $newId
): void {
foreach ($metaModel->getAttributes() as $attribute) {
if (!$attribute instanceof ITranslatedWithFallbackControl) {
continue;
}

$data = $attribute->getTranslatedDataForWithoutFallback([$sourceId], $language);
if ([] === $data || !isset($data[$sourceId])) {
continue;
}

$attribute->applyTranslatedDataFor([$newId => $data[$sourceId]], $language);
}
}
}
79 changes: 79 additions & 0 deletions src/DcGeneral/Events/MetaModel/ResetLanguageAfterDuplicate.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
<?php

/**
* This file is part of MetaModels/core.
*
* (c) 2012-2026 The MetaModels team.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* This project is provided in good faith and hope to be usable by anyone.
*
* @package MetaModels/core
* @author Ingolf Steinhardt <info@e-spin.de>
* @copyright 2012-2026 The MetaModels team.
* @license https://github.com/MetaModels/core/blob/master/LICENSE LGPL-3.0-or-later
* @filesource
*/

namespace MetaModels\DcGeneral\Events\MetaModel;

use ContaoCommunityAlliance\DcGeneral\Event\PostDuplicateModelEvent;
use ContaoCommunityAlliance\DcGeneral\SessionStorageInterface;
use MetaModels\IFactory;
use MetaModels\ITranslatedMetaModel;

/**
* Resets the active language to the fallback language after a model is duplicated.
*
* When a new record is created, LanguageFilter automatically resets to the fallback language. Copying a record
* redirects to act=edit with the new ID, so LanguageFilter does not apply the reset. This listener writes the
* fallback language into the dc-general session so that the subsequent edit opens in the fallback language,
* consistent with the behaviour for new records.
*/
final class ResetLanguageAfterDuplicate
{
/**
* The MetaModels factory.
*
* @var IFactory
*/
private IFactory $factory;

/**
* Create a new instance.
*
* @param IFactory $factory The factory.
*/
public function __construct(IFactory $factory)
{
$this->factory = $factory;
}

/**
* Reset the session language to the fallback after a model has been duplicated.
*
* @param PostDuplicateModelEvent $event The event.
*
* @return void
*/
public function handle(PostDuplicateModelEvent $event): void
{
$model = $event->getModel();
$providerName = $model->getProviderName();

$metaModel = $this->factory->getMetaModel($providerName);
if (!$metaModel instanceof ITranslatedMetaModel) {
return;
}

$environment = $event->getEnvironment();
$sessionStorage = $environment->getSessionStorage();
assert($sessionStorage instanceof SessionStorageInterface);

$session = (array) $sessionStorage->get('dc_general');
$session['ml_support'][$providerName] = $metaModel->getMainLanguage();
$sessionStorage->set('dc_general', $session);
}
}
Loading
Loading