getOptions()[self::ALLOW_OTHER]; } public function buildForm(FormBuilderInterface $builder, CustomField $customField) { // prepare choices $choices = []; $customFieldOptions = $customField->getOptions(); foreach ($customFieldOptions[self::CHOICES] as $persistedChoices) { if ($persistedChoices['active']) { $choices[$persistedChoices['slug']] = $this->translatableStringHelper->localize($persistedChoices['name']); } } // prepare $options $options = [ '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 (true === $customFieldOptions[self::ALLOW_OTHER]) { $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, ['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, [ 'expanded' => true, 'multiple' => false, 'choices' => [ 'Multiple' => '1', 'Unique' => '0', ], 'empty_data' => '0', 'label' => 'Multiplicity', ]) ->add(self::EXPANDED, ChoiceType::class, [ 'expanded' => true, 'multiple' => false, 'choices' => [ 'Expanded' => '1', 'Non expanded' => '0', ], 'empty_data' => '0', 'label' => 'Choice display', ]) ->add(self::ALLOW_OTHER, ChoiceType::class, [ 'label' => 'Allow other', 'choices' => [ 'No' => '0', 'Yes' => '1', ], 'empty_data' => '0', 'expanded' => true, 'multiple' => false, ]) ->add(self::OTHER_VALUE_LABEL, TranslatableStringFormType::class, [ 'label' => 'Other value label (empty if use by default)', ]) ->add(self::CHOICES, ChoicesType::class, [ '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]); } return $this->deserializeToUnique($serialized, $options[self::ALLOW_OTHER]); return $serialized; } public function extractOtherValue(CustomField $cf, array $data = null) { return $data['_other']; } public function getChoices(CustomField $cf) { if ($cf->getOptions()[self::MULTIPLE]) { $choices = []; foreach ($cf->getOptions()[self::CHOICES] as $choice) { if (false === $choice['active']) { continue; } $choices[$choice['slug']] = $this->translatableStringHelper ->localize($choice['name']); } if ($this->allowOtherChoice($cf)) { $labels = $cf->getOptions()[self::OTHER_VALUE_LABEL]; if (!\is_array($labels) || 0 === \count($labels)) { $labels['back'] = 'other value'; } $choices['_other'] = $this->translatableStringHelper ->localize($labels); } return $choices; } return [ $cf->getSlug() => $this->translatableStringHelper->localize($cf->getName()), ]; } public function getName() { return 'Choices'; } /** * Return true if the choice given in $choiceSlug is checked inside $data. * * Used in list exports. * * @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 bool */ public function isChecked(CustomField $cf, $choiceSlug, array|string $data) { if (null === $data) { return false; } if ($cf->getOptions()[self::MULTIPLE]) { if ($cf->getOptions()[self::ALLOW_OTHER]) { return \in_array($choiceSlug, $this->deserialize($data, $cf)['_choices'], true); } return \in_array($choiceSlug, $this->deserialize($data, $cf), true); } if ($cf->getOptions()[self::ALLOW_OTHER]) { return $this->deserialize($data, $cf)['_choices'] === $choiceSlug; } return $this->deserialize($data, $cf) === $choiceSlug; } public function isEmptyValue($value, CustomField $customField) { if (null === $value) { 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 (null === $value['_choices']) { return true; } return empty($value['_choices']); } // we do not have 'allow other' if (1 === \count($value)) { return empty($value[0]); } return empty($value); } return empty($value); throw \LogicException('This case is not expected.'); } public function isMultiple(CustomField $cf) { return $cf->getOptions()[self::MULTIPLE]; } /** * @internal this function is able to receive data whichever is the value of "other", "multiple" * * @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 = $value['_choices'] ?? $value; $selected = (\is_array($data)) ? $data : [$data]; $choices = $customField->getOptions()[self::CHOICES]; if (\in_array('_other', $selected, true)) { $choices[] = ['name' => $value['_other'], 'slug' => '_other']; } $template = '@ChillCustomFields/CustomFieldsRendering/choice.html.twig'; if ('csv' === $documentType) { $template = '@ChillCustomFields/CustomFieldsRendering/choice.csv.twig'; } return $this->templating ->render( $template, [ 'choices' => $choices, 'selected' => $selected, 'multiple' => $customField->getOptions()[self::MULTIPLE], 'expanded' => $customField->getOptions()[self::EXPANDED], ] ); } public function serialize($value, CustomField $customField) { return $value; } /** * deserialized the data from the database to a multiple * field. * * @param bool $allowOther */ private function deserializeToMultiple(mixed $serialized, $allowOther) { $value = $this->guessValue($serialized); // set in an array : we want a multiple $fixedValue = \is_array($value) ? $value : [$value]; if ($allowOther) { return $this->deserializeWithAllowOther($serialized, $fixedValue); } return $fixedValue; } 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); } return $fixedValue; } private function deserializeWithAllowOther($serialized, $value) { $existingOther = $serialized['_other'] ?? ''; return [ '_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. */ private function guessValue(null|array|string $value) { if (null === $value) { return null; } if (!\is_array($value)) { return $value; } // we have a field with "allow other" if (\array_key_exists('_choices', $value)) { return $value['_choices']; } // we have a field with "multiple" return $value; throw \LogicException('This case is not expected.'); } }