diff --git a/.gitignore b/.gitignore index cad17f35..ff665f69 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ vendor composer.phar composer.lock docker-compose.yml +.idea/ \ No newline at end of file diff --git a/Connector/Processor/Denormalization/ReferenceDataProcessor.php b/Connector/Processor/Denormalization/ReferenceDataProcessor.php index 1ede82e0..2b0f673c 100644 --- a/Connector/Processor/Denormalization/ReferenceDataProcessor.php +++ b/Connector/Processor/Denormalization/ReferenceDataProcessor.php @@ -10,6 +10,7 @@ use Akeneo\Component\StorageUtils\Detacher\ObjectDetacherInterface; use Akeneo\Component\StorageUtils\Updater\ObjectUpdaterInterface; use Doctrine\ORM\EntityManagerInterface; +use Pim\Bundle\CatalogBundle\Doctrine\ORM\Repository\LocaleRepository; use Pim\Bundle\CustomEntityBundle\Configuration\Registry; use Pim\Bundle\CustomEntityBundle\Entity\Repository\CustomEntityRepository; use Pim\Component\Connector\Exception\InvalidItemFromViolationsException; @@ -44,25 +45,32 @@ class ReferenceDataProcessor implements ItemProcessorInterface, StepExecutionAwa /** @var StepExecution */ protected $stepExecution; + /** @var LocaleRepository */ + protected $localeRepository; + /** + * ReferenceDataProcessor constructor. * @param Registry $confRegistry * @param EntityManagerInterface $em * @param ObjectUpdaterInterface $updater * @param ValidatorInterface $validator * @param ObjectDetacherInterface $detacher + * @param LocaleRepository $localeRepository */ public function __construct( Registry $confRegistry, EntityManagerInterface $em, ObjectUpdaterInterface $updater, ValidatorInterface $validator, - ObjectDetacherInterface $detacher + ObjectDetacherInterface $detacher, + LocaleRepository $localeRepository ) { - $this->confRegistry = $confRegistry; - $this->em = $em; - $this->updater = $updater; - $this->validator = $validator; - $this->detacher = $detacher; + $this->confRegistry = $confRegistry; + $this->em = $em; + $this->updater = $updater; + $this->validator = $validator; + $this->detacher = $detacher; + $this->localeRepository = $localeRepository; } /** @@ -75,6 +83,11 @@ public function process($item) } $entity = $this->findOrCreateObject($item); + + if ($entity instanceof \Pim\Bundle\CustomEntityBundle\Entity\AbstractTranslatableCustomEntity) { + $item = $this->denormalizeTranslations($item); + } + try { $this->updater->update($entity, $item); } catch (\Exception $e) { @@ -90,6 +103,33 @@ public function process($item) return $entity; } + /** + * @param array $item + * @return array + */ + protected function denormalizeTranslations(array $item):array + { + $activatedLocales = $this->localeRepository->getActivatedLocaleCodes(); + + foreach ($item as $key => $value) { + foreach ($activatedLocales as $localeCode) { + if (preg_match('/.*-(' . preg_quote($localeCode) . ')$/', $key, $match) === 1) { + $attributeCode = str_replace('-' . $localeCode, '', $key); + + $item[$attributeCode] = $item[$attributeCode] ?? []; + + if (!empty($value)) { + $item[$attributeCode][$localeCode] = $value; + } + + unset($item[$key]); + } + } + } + + return $item; + } + /** * {@inheritdoc} */ diff --git a/Connector/Processor/Normalization/ReferenceDataProcessor.php b/Connector/Processor/Normalization/ReferenceDataProcessor.php index 5c8eec38..305ec7ba 100644 --- a/Connector/Processor/Normalization/ReferenceDataProcessor.php +++ b/Connector/Processor/Normalization/ReferenceDataProcessor.php @@ -23,16 +23,15 @@ class ReferenceDataProcessor implements ItemProcessorInterface protected $normalizer; /** @var string[] */ - protected $skippedFields = ['id', 'created', 'updated']; + protected $skippedFields = ['id', 'created', 'updated', 'locale']; /** * @param PropertyAccessorInterface $propertyAccessor * @param NormalizerInterface $normalizer */ - public function __construct(PropertyAccessorInterface $propertyAccessor, NormalizerInterface $normalizer) - { + public function __construct(PropertyAccessorInterface $propertyAccessor, NormalizerInterface $normalizer) { $this->propertyAccessor = $propertyAccessor; - $this->normalizer = $normalizer; + $this->normalizer = $normalizer; } /** @@ -48,13 +47,38 @@ public function process($item) continue; } - $value = $this->propertyAccessor->getValue($item, $property->getName()); - $normalizedData[$property->getName()] = $this->normalizer->normalize($value, 'flat'); + $value = $this->normalizer + ->normalize($this->propertyAccessor->getValue($item, $property->getName()), 'flat'); + + if (is_array($value)) { + $normalizedData = array_merge($normalizedData, $this->normalizeArray($value)); + } else { + $normalizedData[$property->getName()] = $value; + } } return $normalizedData; } + /** + * @param array $values + * @return array + */ + protected function normalizeArray(array $values): array + { + $returnValue = []; + + foreach ($values as $key => $value) { + if (is_array($value)) { + $returnValue = array_merge($returnValue, $this->normalizeArray($value)); + } else { + $returnValue[$key] = $value; + } + } + + return $returnValue; + } + /** * @param array $skippedFields */ diff --git a/Resources/config/connectors.yml b/Resources/config/connectors.yml index bb325490..1b67db25 100644 --- a/Resources/config/connectors.yml +++ b/Resources/config/connectors.yml @@ -28,6 +28,7 @@ services: - '@pim_custom_entity.updater.custom_entity' - '@validator' - '@akeneo_storage_utils.doctrine.object_detacher' + - '@pim_catalog.repository.locale' pim_custom_entity.processor.normalization.reference_data: class: '%pim_custom_entity.processor.normalization.reference_data.class%' diff --git a/Resources/config/requirejs.yml b/Resources/config/requirejs.yml index 781a6144..359c66a3 100644 --- a/Resources/config/requirejs.yml +++ b/Resources/config/requirejs.yml @@ -11,6 +11,11 @@ config: custom_entity/controller/edit: pimcustomentity/js/controller/custom_entity-edit custom_entity/fetcher: pimcustomentity/js/fetcher/custom_entity-fetcher custom_entity/remover/reference-data: pimcustomentity/js/remover/reference-data-remover + custom_entity/form/localizable/switcher: pimcustomentity/js/form/localizable/switcher + custom_entity/form/localizable/text: pimcustomentity/js/form/localizable/text + custom_entity/form/localizable/textarea: pimcustomentity/js/form/localizable/textarea + custom_entity/template/localizable/text: pimcustomentity/templates/form/localizable/text.html + custom_entity/template/localizable/textarea: pimcustomentity/templates/form/localizable/textarea.html config: pim/fetcher-registry: diff --git a/Resources/public/js/form/localizable/switcher.js b/Resources/public/js/form/localizable/switcher.js new file mode 100644 index 00000000..1138374c --- /dev/null +++ b/Resources/public/js/form/localizable/switcher.js @@ -0,0 +1,65 @@ +define([ + 'pim/product-edit-form/locale-switcher', + 'underscore', + 'pim/i18n', + 'oro/translator', + 'pim/user-context', +], function (LocalSwitcher, _, i18n, __, UserContext) { + "use strict"; + + return LocalSwitcher.extend({ + currentLocaleCode: null, + + /** + * {@inheritdoc} + */ + configure: function () { + this.update(UserContext.get('uiLocale')); + }, + + /** + * Method triggered on the 'change locale' event + * + * @param {Object} event + */ + changeLocale: function (event) { + this.update(event.target.dataset.locale); + }, + + /** + * @param {String} localeCode + */ + update: function(localeCode) { + this.currentLocaleCode = localeCode; + this.getRoot().trigger('custom_entity:form:custom:translatable:switcher:change', {localeCode: localeCode}); + this.render(); + }, + + /** + * {@inheritdoc} + */ + render: function () { + this.getDisplayedLocales() + .done(function (locales) { + + if (!this.currentLocaleCode) { + this.currentLocaleCode = _.first(locales).code; + } + + this.$el.html( + this.template({ + locales: locales, + currentLocale: _.findWhere(locales, {code: this.currentLocaleCode}), + i18n: i18n, + displayInline: this.displayInline, + displayLabel: this.displayLabel, + label: __('pim_enrich.entity.product.meta.locale') + }) + ); + this.delegateEvents(); + }.bind(this)); + + return this; + }, + }); +}); \ No newline at end of file diff --git a/Resources/public/js/form/localizable/text.js b/Resources/public/js/form/localizable/text.js new file mode 100644 index 00000000..5fa0e440 --- /dev/null +++ b/Resources/public/js/form/localizable/text.js @@ -0,0 +1,95 @@ +define([ + 'pim/common/properties/translation', + 'custom_entity/template/localizable/text', + 'pim/form', + 'underscore', + 'jquery', + 'pim/user-context', + 'oro/translator', +], function (Translation, template, BaseForm, _, $, UserContext, __) { + "use strict"; + + return Translation.extend({ + template: _.template(template), + localeCode: null, + + /** + * {@inheritdoc} + */ + configure: function () { + this.listenTo( + this.getRoot(), + 'pim_enrich:form:entity:pre_save', + this.onPreSave + ); + + this.listenTo( + this.getRoot(), + 'pim_enrich:form:entity:bad_request', + this.onValidationError + ); + + this.listenTo( + this.getRoot(), + 'pim_enrich:form:entity:locales_updated', + this.onLocalesUpdated.bind(this) + ); + + this.listenTo( + this.getRoot(), + 'custom_entity:form:custom:translatable:switcher:change', + this.onLocaleChanged.bind(this) + ); + + return $.when( + this.getLocales(true) + .then(function (locales) { + this.locales = locales; + this.localeCode = UserContext.get('uiLocale'); + }.bind(this)), + BaseForm.prototype.configure.apply(this, arguments) + ); + }, + + /** + * {@inheritdoc} + */ + render: function () { + this.$el.html(this.template({ + model: this.getFormData(), + locales: this.locales, + currentLocaleCode: this.localeCode, + errors: this.validationErrors, + label: __(this.config.label), + fieldName: this.config.fieldName, + isReadOnly: this.isReadOnly() + })); + + this.delegateEvents(); + this.renderExtensions(); + }, + + /** + * @param {{}} context + */ + onLocaleChanged: function (context) { + this.localeCode = context.localeCode; + this.render(); + }, + + /** + * @param {Object} event + */ + updateModel: function (event) { + var data = this.getFormData(); + + if (Array.isArray(data[this.config.fieldName])) { + data[this.config.fieldName] = {}; + } + + data[this.config.fieldName][event.target.dataset.locale] = event.target.value; + + this.setData(data); + }, + }); +}); \ No newline at end of file diff --git a/Resources/public/js/form/localizable/textarea.js b/Resources/public/js/form/localizable/textarea.js new file mode 100644 index 00000000..9dd8574b --- /dev/null +++ b/Resources/public/js/form/localizable/textarea.js @@ -0,0 +1,10 @@ +define([ + 'custom_entity/form/localizable/text', + 'custom_entity/template/localizable/textarea' +], function (Text, template) { + "use strict"; + + return Text.extend({ + template: _.template(template) + }); +}); \ No newline at end of file diff --git a/Resources/public/templates/form/localizable/text.html b/Resources/public/templates/form/localizable/text.html new file mode 100644 index 00000000..bbb55b70 --- /dev/null +++ b/Resources/public/templates/form/localizable/text.html @@ -0,0 +1,41 @@ +<% _.each(locales, function (locale) { %> + <% if (currentLocaleCode === locale.code) { %> +