-
-
Save pakaufmann/2701676 to your computer and use it in GitHub Desktop.
| <?php | |
| namespace Edge5\TestprojectBundle\Form; | |
| use Symfony\Component\Form\Event\DataEvent; | |
| use Symfony\Component\Form\FormFactoryInterface; | |
| use Symfony\Component\EventDispatcher\EventSubscriberInterface; | |
| use Symfony\Component\Form\FormEvents; | |
| use Symfony\Component\DependencyInjection\ContainerInterface; | |
| use Symfony\Component\Form\FormError; | |
| class AddTranslatedFieldSubscriber implements EventSubscriberInterface | |
| { | |
| private $factory; | |
| private $options; | |
| private $container; | |
| public function __construct(FormFactoryInterface $factory, ContainerInterface $container, Array $options) | |
| { | |
| $this->factory = $factory; | |
| $this->options = $options; | |
| $this->container = $container; | |
| } | |
| public static function getSubscribedEvents() | |
| { | |
| // Tells the dispatcher that we want to listen on the form.pre_set_data | |
| // , form.post_data and form.bind_norm_data event | |
| return array( | |
| FormEvents::PRE_SET_DATA => 'preSetData', | |
| FormEvents::POST_BIND => 'postBind', | |
| FormEvents::BIND_NORM_DATA => 'bindNormData' | |
| ); | |
| } | |
| private function bindTranslations($data) | |
| { | |
| //Small helper function to extract all Personal Translation | |
| //from the Entity for the field we are interested in | |
| //and combines it with the fields | |
| $collection = array(); | |
| $availableTranslations = array(); | |
| foreach($data as $Translation) | |
| { | |
| //if the translations object is null, skip it (happens when a subform is called on a new entity) | |
| if($Translation == null) | |
| { | |
| return; | |
| } | |
| if(strtolower($Translation->getProperty()) == strtolower($this->options['field'])) | |
| { | |
| $availableTranslations[ strtolower($Translation->getLocale()) ] = $Translation; | |
| } | |
| } | |
| foreach($this->getFieldNames() as $locale => $fieldName) | |
| { | |
| if(isset($availableTranslations[ strtolower($locale) ])) | |
| { | |
| $Translation = $availableTranslations[ strtolower($locale) ]; | |
| } | |
| else | |
| { | |
| $Translation = $this->createPersonalTranslation($locale, $this->options['field'], NULL); | |
| } | |
| $collection[] = array( | |
| 'locale' => $locale, | |
| 'fieldName' => $fieldName, | |
| 'translation' => $Translation, | |
| ); | |
| } | |
| return $collection; | |
| } | |
| private function getFieldNames() | |
| { | |
| //helper function to generate all field names in format: | |
| // '<locale>' => '<field>|<locale>' | |
| $collection = array(); | |
| foreach($this->options['locales'] as $locale) | |
| { | |
| $collection[ $locale ] = $this->options['field'] ."|". $locale; | |
| } | |
| return $collection; | |
| } | |
| private function createPersonalTranslation($locale, $field, $content) | |
| { | |
| //creates a new Personal Translation | |
| $className = $this->options['personal_translation']; | |
| $Translation = new $className(); | |
| $Translation->setLocale($locale); | |
| $Translation->setProperty($field); | |
| $Translation->setValue($content); | |
| return $Translation; | |
| } | |
| public function bindNormData(DataEvent $event) | |
| { | |
| //Validates the submitted form | |
| $data = $event->getData(); | |
| $form = $event->getForm(); | |
| $validator = $this->container->get('validator'); | |
| //if the form count is null, skip it (happens when a subform is called on a new entity) | |
| if($form->count() == 0) | |
| { | |
| return; | |
| } | |
| foreach($this->getFieldNames() as $locale => $fieldName) | |
| { | |
| $content = $form->get($fieldName)->getData(); | |
| if( | |
| NULL === $content && | |
| in_array($locale, $this->options['required_locale'])) | |
| { | |
| $form->addError(new FormError(sprintf("Field '%s' for locale '%s' cannot be blank", $this->options['field'], $locale))); | |
| } | |
| else | |
| { | |
| $Translation = $this->createPersonalTranslation($locale, $fieldName, $content); | |
| $errors = $validator->validate($Translation, array(sprintf("%s:%s", $this->options['field'], $locale))); | |
| if(count($errors) > 0) | |
| { | |
| foreach($errors as $error) | |
| { | |
| $form->addError(new FormError($error->getMessage())); | |
| } | |
| } | |
| } | |
| } | |
| } | |
| public function postBind(DataEvent $event) | |
| { | |
| //if the form passed the validation then set the corresponding Personal Translations | |
| $form = $event->getForm(); | |
| $data = $form->getData(); | |
| $entity = $form->getParent()->getData(); | |
| //if the entity is null, skip it (happens when the subform is called with a new entity instead of an updated one) | |
| if($entity == null) | |
| { | |
| return; | |
| } | |
| foreach($this->bindTranslations($data) as $binded) | |
| { | |
| $content = $form->get($binded['fieldName'])->getData(); | |
| $Translation = $binded['translation']; | |
| //if default translation don't add the translation, instead write it directly into the entity | |
| if($Translation->getLocale() === $this->options['default_locale']) | |
| { | |
| $setFunction = 'set'.$Translation->getProperty(); | |
| $entity->$setFunction($content); | |
| } | |
| else | |
| { | |
| // set the submitted content | |
| $Translation->setValue($content); | |
| //test if its new | |
| if($Translation->getId()) | |
| { | |
| //Delete the Personal Translation if its empty | |
| if(NULL === $content && $this->options['remove_empty']) | |
| { | |
| $data->removeElement($Translation); | |
| if($this->options['entity_manager_removal']) | |
| { | |
| $this->container->get('doctrine.orm.entity_manager')->remove($Translation); | |
| } | |
| } | |
| } | |
| elseif(NULL !== $content) | |
| { | |
| $class = explode('\\', get_class($Translation)); | |
| $translationFunction = 'add'.end($class); | |
| $entity->$translationFunction($Translation); | |
| if(! $data->contains($Translation)) | |
| { | |
| $data->add($Translation); | |
| } | |
| } | |
| } | |
| } | |
| } | |
| public function preSetData(DataEvent $event) | |
| { | |
| //Builds the custom 'form' based on the provided locales | |
| $data = $event->getData(); | |
| $form = $event->getForm(); | |
| // During form creation setData() is called with null as an argument | |
| // by the FormBuilder constructor. We're only concerned with when | |
| // setData is called with an actual Entity object in it (whether new, | |
| // or fetched with Doctrine). This if statement let's us skip right | |
| // over the null condition. | |
| if (null === $data) | |
| { | |
| return; | |
| } | |
| $entity = $form->getParent()->getData(); | |
| foreach($this->bindTranslations($data) as $binded) | |
| { | |
| $t = $binded['translation']; | |
| if($t->getLocale() === $this->options['default_locale']) | |
| { | |
| //get it out of the actual entity instead of the translation object | |
| $getFunction = 'get'.$t->getProperty(); | |
| $translation = $entity->$getFunction(); | |
| } | |
| else | |
| { | |
| $translation = $binded['translation']->getValue(); | |
| } | |
| $form->add($this->factory->createNamed( | |
| $this->options['widget'], | |
| $binded['fieldName'], | |
| $translation, | |
| array( | |
| 'label' => $binded['locale'], | |
| 'required' => in_array($binded['locale'], $this->options['required_locale']), | |
| 'property_path'=> false, | |
| ) | |
| )); | |
| } | |
| } | |
| } |
| services: | |
| form.type.translatable: | |
| class: ExampleBundle\Form\TranslatedFieldType | |
| arguments: [ @service_container ] | |
| tags: | |
| - { name: form.type, alias: translatable_field } |
| <?php | |
| namespace ExampleBundle\Form; | |
| use Symfony\Component\Form\AbstractType; | |
| use Symfony\Component\Form\FormBuilder; | |
| use Symfony\Component\DependencyInjection\ContainerInterface; | |
| use Edge5\TestProjectBundle\Form\addTranslatedFieldSubscriber; | |
| class TranslatedFieldType extends AbstractType | |
| { | |
| protected $container; | |
| public function __construct(ContainerInterface $container) | |
| { | |
| $this->container = $container; | |
| } | |
| public function buildForm(FormBuilder $builder, array $options) | |
| { | |
| if(! class_exists($options['personal_translation'])) | |
| { | |
| Throw new \InvalidArgumentException(sprintf("Unable to find personal translation class: '%s'", $options['personal_translation'])); | |
| } | |
| if(! $options['field']) | |
| { | |
| Throw new \InvalidArgumentException("You should provide a field to translate"); | |
| } | |
| $subscriber = new addTranslatedFieldSubscriber($builder->getFormFactory(), $this->container, $options); | |
| $builder->addEventSubscriber($subscriber); | |
| } | |
| public function getDefaultOptions(array $options = array()) | |
| { | |
| $options['remove_empty'] = true; //Personal Translations without content are removed | |
| $options['default_locale'] = 'en'; //the default language to use | |
| $options['csrf_protection'] = false; | |
| $options['personal_translation'] = false; //Personal Translation class | |
| $options['locales'] = array('en'); //the locales you wish to edit | |
| $options['required_locale'] = array('en'); //the required locales cannot be blank | |
| $options['field'] = false; //the field that you wish to translate | |
| $options['widget'] = "text"; //change this to another widget like 'texarea' if needed | |
| $options['entity_manager_removal'] = true; //auto removes the Personal Translation thru entity manager | |
| return $options; | |
| } | |
| public function getName() | |
| { | |
| return 'translator'; | |
| } | |
| } |
Yeah, I know. That's a problem of in the current release of the Sonata Admin generator. They broke the display behaviour of 1 to n relations (which also broke this field type) . You can resolve it with the following code:
Replace the following lines in the block "field_row" in the file vendor/bundles/Sonata/AdminBundle/Resources/views/Form/form_admin_fields.html.twig.
{% if sonata_admin is not defined or not sonata_admin_enabled or not sonata_admin.field_description %}
{% form_row(form) %}
{% else %}
with the following code:
{% if sonata_admin is not defined or not sonata_admin_enabled or not sonata_admin.field_description %}
<div class="clearfix{% if errors|length > 0%} error{%endif%}" id="sonata-ba-field-container-{{ id }}">
{{ form_label(form) }}
<div class="input">
{{ form_widget(form) }}
{% if errors|length > 0 %}
<div class="sonata-ba-field-error-messages">
{{ form_errors(form) }}
</div>
{% endif %}
</div>
</div>
{% else %}
This should fix it.
Hm, I never had a problem with the translation relation not getting set correctly.
What does your entity mapping information look like?
Yeah sorry, my bad. I had this problem with all one-to-many relationships in Sonata. I fixed this problem by overwriting the prePersist and preUpdate methods in the Sonata Admin class. There I loop over all translations and set the translatable to the correct entity.
and I keep getting
The options "field", "personal_translation" do not exist
weird… what am I doing wrong?
oh, I've just returned an array in setDefaultOptions :)
you need to do $resolver->setDefaults();
Hello,
I followed the same knp blogpost and I used your file but I keep getting this error :
An exception has been thrown during the rendering of a template ("Notice: Undefined offset: -1 in /.../vendor/symfony/src/Symfony/Bridge/Twig/Extension/FormExtension.php line 245") in SonataAdminBundle:Form:form_admin_fields.html.twig at line 81.
when I try to edit my translatable entity in sonata admin !
I really don't get it, any idea ?