Skip to content

Commit

Permalink
add xliff export feature
Browse files Browse the repository at this point in the history
  • Loading branch information
solverat committed Aug 10, 2023
1 parent 6b6318a commit fd7260f
Show file tree
Hide file tree
Showing 31 changed files with 530 additions and 237 deletions.
6 changes: 6 additions & 0 deletions UPGRADE.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@

### Global Changes
- Recommended folder structure by symfony adopted
- SEO changes are not getting persisted at auto-save events anymore

### New Features
- Xliff Import/Export Support see [#31](https://github.com/dachcom-digital/pimcore-seo/issues/31)
- Introduced `XliffAwareIntegratorInterface` to specify xliff translation states for given integrator
- Properties for `OpenGraph` and `TwitterCard` integrator can be extended by an 3. argument to include/exclude them for xliff translations (Default `false`)

***

Expand Down
4 changes: 2 additions & 2 deletions config/services/extractors/coreshop.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ services:

SeoBundle\MetaData\Extractor\ThirdParty\CoreShop\TitleDescriptionExtractor:
tags:
- {name: seo.meta_data.extractor, identifier: coreshop_title_description, priority: 10 }
- { name: seo.meta_data.extractor, identifier: coreshop_title_description, priority: 10 }

SeoBundle\MetaData\Extractor\ThirdParty\CoreShop\OGExtractor:
tags:
- {name: seo.meta_data.extractor, identifier: coreshop_og_tags, priority: 10 }
- { name: seo.meta_data.extractor, identifier: coreshop_og_tags, priority: 10 }
2 changes: 1 addition & 1 deletion config/services/extractors/news.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,4 @@ services:

SeoBundle\MetaData\Extractor\ThirdParty\News\EntryMetaExtractor:
tags:
- {name: seo.meta_data.extractor, identifier: news_entry_meta, priority: 10 }
- { name: seo.meta_data.extractor, identifier: news_entry_meta, priority: 10 }
10 changes: 10 additions & 0 deletions config/services/xliff_bundle/services.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
services:

_defaults:
autowire: true
autoconfigure: true
public: false

SeoBundle\EventListener\Admin\XliffListener:
tags:
- { name: kernel.event_subscriber }
2 changes: 1 addition & 1 deletion docs/MetaData/Integrator/11_OpenGraphIntegrator.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ seo:
types:
- ['my_type', 'my_type']
properties:
- ['og:test', 'og:test']
- ['og:test', 'og:test', true] # 3. argument: allow export to xliff translation
presets:
- label: 'My Preset'
icon_class: 'pimcore_icon_user'
Expand Down
2 changes: 1 addition & 1 deletion docs/MetaData/Integrator/12_TwitterCardIntegrator.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,5 +30,5 @@ seo:
types:
- ['my_type', 'my_type']
properties:
- ['twitter:definition', 'twitter:definition']
- ['twitter:definition', 'twitter:definition', true] # 3. argument: allow export to xliff translation
```
2 changes: 1 addition & 1 deletion public/js/metaData/integrator/propertyIntegrator/item.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ Seo.MetaData.Integrator.PropertyIntegratorItem = Class.create({
user = pimcore.globalmanager.get('user');

propertyTypeStore = new Ext.data.ArrayStore({
fields: ['label', 'key'],
fields: ['label', 'key', 'xliffExportAware'],
data: configuration.hasOwnProperty('properties') ? configuration.properties : []
});

Expand Down
8 changes: 8 additions & 0 deletions public/js/plugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,10 @@ class SeoCore {

const document = ev.detail.document;

if (ev.detail.task === 'autoSave' || ev.detail.task === 'version') {
return;
}

if (document.hasOwnProperty('seoPanel')) {
document.seoPanel.save();
}
Expand All @@ -71,6 +75,10 @@ class SeoCore {

const object = ev.detail.object;

if (ev.detail.task === 'autoSave' || ev.detail.task === 'version') {
return;
}

if (object.hasOwnProperty('seoPanel')) {
object.seoPanel.save();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,14 @@

namespace SeoBundle\DependencyInjection\Compiler\ThirdParty;

use SeoBundle\Tool\Bundle;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;

final class RemoveCoreShopExtractorListenerPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container): void
{
if (Bundle::hasBundle('CoreShopSEOBundle', $container->getParameter('kernel.bundles')) === false) {
if (!in_array('core_shop_seo', $container->getParameter('seo.third_party.enabled'), true)) {
return;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,19 @@

namespace SeoBundle\DependencyInjection\Compiler\ThirdParty;

use SeoBundle\Tool\Bundle;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;

final class RemoveNewsMetaDataListenerPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container): void
{
$definition = 'NewsBundle\EventListener\MetaDataListener';

if (Bundle::hasDachcomBundle('NewsBundle', $container->getParameter('kernel.bundles')) === false) {
if (!in_array('dachcom_news', $container->getParameter('seo.third_party.enabled'), true)) {
return;
}

$definition = 'NewsBundle\EventListener\MetaDataListener';

if ($container->hasDefinition($definition)) {
$container->removeDefinition($definition);
}
Expand Down

This file was deleted.

60 changes: 43 additions & 17 deletions src/DependencyInjection/SeoExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@

namespace SeoBundle\DependencyInjection;

use SeoBundle\Tool\Bundle;
use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException;
use Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface;
use Symfony\Component\DependencyInjection\Loader\YamlFileLoader;
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\Config\FileLocator;

class SeoExtension extends Extension
class SeoExtension extends Extension implements PrependExtensionInterface
{
public function load(array $configs, ContainerBuilder $container): void
{
Expand All @@ -36,11 +36,50 @@ public function load(array $configs, ContainerBuilder $container): void
$container->setParameter('seo.meta_data_provider.configuration', $config['meta_data_configuration']['meta_data_provider']);
$container->setParameter('seo.meta_data_integrator.configuration', $config['meta_data_configuration']['meta_data_integrator']);
$container->setParameter('seo.index.pimcore_element_watcher.enabled', $config['index_provider_configuration']['pimcore_element_watcher']['enabled']);
}

public function prepend(ContainerBuilder $container): void
{
$configs = $container->getExtensionConfig($this->getAlias());

$loader = new YamlFileLoader($container, new FileLocator(__DIR__ . '/../../config'));

$enabledThirdPartyConfigs = [];

$xliffBundleEnabled = $container->hasExtension('pimcore_xliff');
$newsBundleEnabled = $container->hasExtension('news');
$coreShopSeoBundleEnabled = $container->hasExtension('core_shop_seo');

foreach ($configs as $config) {

$thirdPartyConfig = $config['meta_data_configuration']['meta_data_provider']['third_party'] ?? null;

if ($thirdPartyConfig === null) {
continue;
}

if ($coreShopSeoBundleEnabled && ($thirdPartyConfig['coreshop']['disable_default_extractors'] ?? false) === false) {
$enabledThirdPartyConfigs['core_shop_seo'] = 'services/extractors/coreshop.yaml';
}

if ($newsBundleEnabled && ($thirdPartyConfig['news']['disable_default_extractors'] ?? false) === false) {
$enabledThirdPartyConfigs['dachcom_news'] = 'services/extractors/news.yaml';
}
}

if ($xliffBundleEnabled) {
$enabledThirdPartyConfigs['pimcore_xliff'] = 'services/xliff_bundle/services.yaml';
}

foreach ($enabledThirdPartyConfigs as $enabledThirdPartyConfig) {
$loader->load($enabledThirdPartyConfig);
}

$container->setParameter('seo.third_party.enabled', array_keys($enabledThirdPartyConfigs));

$this->checkThirdPartyExtractors($container, $loader, $config['meta_data_configuration']['meta_data_provider']['third_party']);
}

protected function validateConfiguration(array $config): void
private function validateConfiguration(array $config): void
{
$enabledIntegrators = [];
foreach ($config['meta_data_configuration']['meta_data_integrator']['enabled_integrator'] as $dataIntegrator) {
Expand All @@ -51,17 +90,4 @@ protected function validateConfiguration(array $config): void
$enabledIntegrators[] = $dataIntegrator['integrator_name'];
}
}

protected function checkThirdPartyExtractors(ContainerBuilder $container, YamlFileLoader $loader, array $thirdPartyOptions): void
{
$bundles = $container->getParameter('kernel.bundles');

if (Bundle::hasBundle('CoreShopSEOBundle', $bundles) === true && $thirdPartyOptions['coreshop']['disable_default_extractors'] === false) {
$loader->load('services/extractors/coreshop.yaml');
}

if (Bundle::hasDachcomBundle('NewsBundle', $bundles) === true && $thirdPartyOptions['news']['disable_default_extractors'] === false) {
$loader->load('services/extractors/news.yaml');
}
}
}
112 changes: 112 additions & 0 deletions src/EventListener/Admin/XliffListener.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
<?php

namespace SeoBundle\EventListener\Admin;

use Pimcore\Bundle\XliffBundle\Event\Model\TranslationXliffEvent;
use Pimcore\Bundle\XliffBundle\Event\XliffEvents;
use Pimcore\Model\DataObject;
use Pimcore\Model\Document;
use Pimcore\Model\Element\ElementInterface;
use SeoBundle\Manager\ElementMetaDataManagerInterface;
use SeoBundle\MetaData\MetaDataProviderInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;

class XliffListener implements EventSubscriberInterface
{
protected const XLIFF_TYPE = 'dachcom_seo';

public function __construct(
protected ElementMetaDataManagerInterface $elementMetaDataManager,
protected MetaDataProviderInterface $metaDataProvider
) {
}

public static function getSubscribedEvents(): array
{
return [
XliffEvents::XLIFF_ATTRIBUTE_SET_EXPORT => 'export',
XliffEvents::XLIFF_ATTRIBUTE_SET_IMPORT => 'import',
];
}

public function export(TranslationXliffEvent $event): void
{
$attributeSet = $event->getAttributeSet();
$element = $attributeSet->getTranslationItem()->getElement();

if (!$element instanceof ElementInterface) {
return;
}

$sourceLanguage = $attributeSet->getSourceLanguage();
$elementType = $this->determinateType($element);

if ($elementType === null) {
return;
}

$metaData = $this->elementMetaDataManager->getElementDataForXliffExport($elementType, $element->getId(), $sourceLanguage);

foreach ($metaData as $integrator => $integratorValues) {
foreach ($integratorValues as $property => $value) {
$attributeSet->addAttribute(
self::XLIFF_TYPE,
sprintf('%s#%s', $integrator, $property),
$value,
false,
[]
);
}
}
}

public function import(TranslationXliffEvent $event): void
{
$attributeSet = $event->getAttributeSet();
$element = $attributeSet->getTranslationItem()->getElement();

if (!$element instanceof ElementInterface || $attributeSet->isEmpty()) {
return;
}

$targetLanguage = $attributeSet->getTargetLanguages()[0];
$elementType = $this->determinateType($element);

if ($elementType === null) {
return;
}

$rawData = [];
foreach ($attributeSet->getAttributes() as $attribute) {

if ($attribute->getType() !== self::XLIFF_TYPE) {
continue;
}

$attributeName = $attribute->getName();

[$integrator, $property] = explode('#', $attributeName);

if (!array_key_exists($integrator, $rawData)) {
$rawData[$integrator] = [];
}

$rawData[$integrator][$property] = $attribute->getContent();
}

$this->elementMetaDataManager->saveElementDataFromXliffImport($elementType, $element->getId(), $rawData, $targetLanguage);
}

private function determinateType(ElementInterface $element): ?string
{
if ($element instanceof Document) {
return 'document';
}

if ($element instanceof DataObject) {
return 'object';
}

return null;
}
}
21 changes: 5 additions & 16 deletions src/EventListener/AutoMetaDataAttachListener.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,24 +13,13 @@

class AutoMetaDataAttachListener implements EventSubscriberInterface
{
protected array $configuration;
protected MetaDataProviderInterface $metaDataProvider;
protected RequestHelper $requestHelper;
protected PimcoreContextResolver $pimcoreContextResolver;
protected DocumentResolver $documentResolverService;

public function __construct(
array $configuration,
MetaDataProviderInterface $metaDataProvider,
RequestHelper $requestHelper,
PimcoreContextResolver $contextResolver,
DocumentResolver $documentResolverService
protected array $configuration,
protected MetaDataProviderInterface $metaDataProvider,
protected RequestHelper $requestHelper,
protected PimcoreContextResolver $pimcoreContextResolver,
protected DocumentResolver $documentResolverService
) {
$this->configuration = $configuration;
$this->metaDataProvider = $metaDataProvider;
$this->requestHelper = $requestHelper;
$this->pimcoreContextResolver = $contextResolver;
$this->documentResolverService = $documentResolverService;
}

public static function getSubscribedEvents(): array
Expand Down
Loading

0 comments on commit fd7260f

Please sign in to comment.