* * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ namespace Chill\CustomFieldsBundle\CustomFields; use Chill\CustomFieldsBundle\Form\Type\ChoicesListType; use Chill\CustomFieldsBundle\Form\Type\ChoicesType; use Chill\CustomFieldsBundle\Form\Type\ChoiceWithOtherType; use Chill\CustomFieldsBundle\CustomFields\CustomFieldInterface; use Symfony\Component\Form\FormBuilderInterface; use Chill\CustomFieldsBundle\Entity\CustomField; use Symfony\Component\HttpFoundation\RequestStack; use Chill\CustomFieldsBundle\Form\DataTransformer\CustomFieldDataTransformer; use Symfony\Bridge\Twig\TwigEngine; use Chill\MainBundle\Templating\TranslatableStringHelper; use Symfony\Component\Translation\Translator; use Symfony\Component\Form\Extension\Core\Type\ChoiceType; use Chill\MainBundle\Form\Type\TranslatableStringFormType; /** * * * @author Julien Fastré * @author Marc Ducobu */ class CustomFieldChoice extends AbstractCustomField { const ALLOW_OTHER = 'other'; const OTHER_VALUE_LABEL = 'otherValueLabel'; const MULTIPLE = 'multiple'; const EXPANDED = 'expanded'; const CHOICES = 'choices'; private $defaultLocales; /** * * @var TwigEngine */ private $templating; /** * @var TranslatableStringHelper Helper that find the string in current locale from an array of translation */ private $translatableStringHelper; public function __construct( Translator $translator, TwigEngine $templating, TranslatableStringHelper $translatableStringHelper) { $this->defaultLocales = $translator->getFallbackLocales(); $this->templating = $templating; $this->translatableStringHelper = $translatableStringHelper; } public function buildForm(FormBuilderInterface $builder, CustomField $customField) { //prepare choices $choices = array(); $customFieldOptions = $customField->getOptions(); foreach($customFieldOptions[self::CHOICES] as $persistedChoices) { if ($persistedChoices['active']){ $choices[$persistedChoices['slug']] = $this->translatableStringHelper->localize($persistedChoices['name']); } } //prepare $options $options = array( 'multiple' => $customFieldOptions[self::MULTIPLE], 'choices' => array_combine(array_values($choices),array_keys($choices)), 'required' => $customField->isRequired(), 'label' => $this->translatableStringHelper->localize($customField->getName()) ); //if allow_other = true if ($customFieldOptions[self::ALLOW_OTHER] == true) { $otherValueLabel = null; if(array_key_exists(self::OTHER_VALUE_LABEL, $customFieldOptions)) { $otherValueLabel = $this->translatableStringHelper->localize( $customFieldOptions[self::OTHER_VALUE_LABEL] ); } $builder->add( $builder ->create( $customField->getSlug(), ChoiceWithOtherType::class, $options, array('other_value_label'=> $otherValueLabel) ) ->addModelTransformer(new CustomFieldDataTransformer($this, $customField))); } else { //if allow_other = false //we add the 'expanded' to options $options['expanded'] = $customFieldOptions[self::EXPANDED]; $builder->add( $builder->create($customField->getSlug(), ChoiceType::class, $options) ->addModelTransformer(new CustomFieldDataTransformer($this, $customField)) ); } } public function buildOptionsForm(FormBuilderInterface $builder) { $builder ->add(self::MULTIPLE, ChoiceType::class, array( 'expanded' => true, 'multiple' => false, 'choices' => array( 'Multiple' => '1', 'Unique' => '0'), 'choices_as_values' => true, 'empty_data' => '0', 'label' => 'Multiplicity' )) ->add(self::EXPANDED, ChoiceType::class, array( 'expanded' => true, 'multiple' => false, 'choices' => array( 'Expanded' => '1', 'Non expanded' => '0'), 'choices_as_values' => true, 'empty_data' => '0', 'label' => 'Choice display' )) ->add(self::ALLOW_OTHER, ChoiceType::class, array( 'label' => 'Allow other', 'choices' => array( 'No' => '0', 'Yes' => '1'), 'choices_as_values' => true, 'empty_data' => '0', 'expanded' => true, 'multiple' => false )) ->add(self::OTHER_VALUE_LABEL, TranslatableStringFormType::class, array( 'label' => 'Other value label (empty if use by default)')) ->add(self::CHOICES, ChoicesType::class, array( 'entry_type' => ChoicesListType::class, 'allow_add' => true )); return $builder; } public function deserialize($serialized, CustomField $customField) { // we always have to adapt to what the current data should be $options = $customField->getOptions(); if ($options[self::MULTIPLE]) { return $this->deserializeToMultiple($serialized, $options[self::ALLOW_OTHER]); } else { return $this->deserializeToUnique($serialized, $options[self::ALLOW_OTHER]); } return $serialized; } private function deserializeToUnique($serialized, $allowOther) { $value = $this->guessValue($serialized); // set in a single value. We must have a single string $fixedValue = is_array($value) ? // check if the array has an element, if not replace by empty string count($value) > 0 ? end($value) : '' : $value; if ($allowOther) { return $this->deserializeWithAllowOther($serialized, $fixedValue); } else { return $fixedValue; } } /** * deserialized the data from the database to a multiple * field * * @param mixed $serialized * @param boolean $allowOther */ private function deserializeToMultiple($serialized, $allowOther) { $value = $this->guessValue($serialized); // set in an array : we want a multiple $fixedValue = is_array($value) ? $value : array($value); if ($allowOther) { return $this->deserializeWithAllowOther($serialized, $fixedValue); } else { return $fixedValue; } } private function deserializeWithAllowOther($serialized, $value) { $existingOther = isset($serialized['_other']) ? $serialized['_other'] : ''; return array( '_other' => $existingOther, '_choices' => $value ); } /** * Guess the value from the representation of it. * * If the value had an 'allow_other' = true option, the returned value * **is not** the content of the _other field, but the `_other` string. * * @param array|string $value * @return mixed * @throws \LogicException if the case is not covered by this */ private function guessValue($value) { if ($value === NULL) { return NULL; } if (!is_array($value)) { return $value; } else { // we have a field with "allow other" if (array_key_exists('_choices', $value)) { return $value['_choices']; } else { // we have a field with "multiple" return $value; } } throw \LogicException("This case is not expected."); } public function getName() { return 'Choices'; } public function isEmptyValue($value, CustomField $customField) { if ($value === NULL) { return true; } // if multiple choice OR multiple/single choice with other if (is_array($value)) { // if allow other if (array_key_exists('_choices', $value)) { if ($value['_choices'] === NULL) { return true; } return empty($value['_choices']); } else { // we do not have 'allow other' if (count($value) === 1){ return empty($value[0]); } else { return empty($value); } } } else { return empty($value); } throw \LogicException("This case is not expected."); } /** * * @internal this function is able to receive data whichever is the value of "other", "multiple" * @param mixed $value * @param CustomField $customField * @return string html representation */ public function render($value, CustomField $customField, $documentType = 'html') { //extract the data. They are under a _choice key if they are stored with allow_other $data = (isset($value['_choices'])) ? $value['_choices'] : $value; $selected = (is_array($data)) ? $data : array($data); $choices = $customField->getOptions()[self::CHOICES]; if (in_array('_other', $selected)){ $choices[] = array('name' => $value['_other'], 'slug' => '_other'); } $template = 'ChillCustomFieldsBundle:CustomFieldsRendering:choice.html.twig'; if($documentType == 'csv') { $template = 'ChillCustomFieldsBundle:CustomFieldsRendering:choice.csv.twig'; } return $this->templating ->render($template, array( 'choices' => $choices, 'selected' => $selected, 'multiple' => $customField->getOptions()[self::MULTIPLE], 'expanded' => $customField->getOptions()[self::EXPANDED], 'locales' => $this->defaultLocales ) ); } public function serialize($value, CustomField $customField) { return $value; } public function getChoices(CustomField $cf) { if ($cf->getOptions()[self::MULTIPLE]) { $choices = array(); foreach ($cf->getOptions()[self::CHOICES] as $choice) { if ($choices['active'] === false) { continue; } $choices[$choice["slug"]] = $this->translatableStringHelper ->localize($choice["name"]); } if ($this->allowOtherChoice($cf)) { $labels = $cf->getOptions()[self::OTHER_VALUE_LABEL]; if (!is_array($labels) or count($labels) === 0) { $labels['back'] = 'other value'; } $choices['_other'] = $this->translatableStringHelper ->localize($labels); } return $choices; } else { return [ $cf->getSlug() => $this->translatableStringHelper->localize($cf->getName()) ]; } } /** * Return true if the choice given in $choiceSlug is checked inside $data. * * Used in list exports. * * @param CustomField $cf * @param string $choiceSlug the slug of the choice we want to know if it was checked * @param array|string $data the data of the field * @return boolean */ public function isChecked(CustomField $cf, $choiceSlug, $data) { if ($data === null) { return false; } if ($cf->getOptions()[self::MULTIPLE]) { if ($cf->getOptions()[self::ALLOW_OTHER]) { return \in_array($choiceSlug, $this->deserialize($data, $cf)['_choices']); } else { return \in_array($choiceSlug, $this->deserialize($data, $cf)); } } else { if ($cf->getOptions()[self::ALLOW_OTHER]) { return $this->deserialize($data, $cf)['_choices'] === $choiceSlug; } else { return $this->deserialize($data, $cf) === $choiceSlug; } } } public function isMultiple(CustomField $cf) { return $cf->getOptions()[self::MULTIPLE]; } public function allowOtherChoice(CustomField $cf) { return $cf->getOptions()[self::ALLOW_OTHER]; } public function extractOtherValue(CustomField $cf, array $data = null) { return $data['_other']; } }