diff --git a/CustomFields/CustomFieldNumber.php b/CustomFields/CustomFieldNumber.php new file mode 100644 index 000000000..d880f8a17 --- /dev/null +++ b/CustomFields/CustomFieldNumber.php @@ -0,0 +1,181 @@ +, + * + * 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\CustomFields\CustomFieldInterface; +use Chill\CustomFieldsBundle\Entity\CustomField; +use Symfony\Component\Form\FormBuilderInterface; +use Symfony\Component\Validator\Constraints\GreaterThanOrEqual; +use Symfony\Component\Validator\Constraints\LessThanOrEqual; +use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Bundle\TwigBundle\TwigEngine; +use Chill\MainBundle\Templating\TranslatableStringHelper; + +/** + * Create a custom field number. + * + * This number may have a min and max value, and a precision. + * + * @author Julien Fastré + * @author Marc Ducobu + */ +class CustomFieldNumber implements CustomFieldInterface +{ + /** + * key for the minimal value of the field + */ + const MIN = 'min'; + const MAX = 'max'; + const SCALE = 'scale'; + const POST_TEXT = 'post_text'; + + /** + * + * @var TwigEngine + */ + private $templating = NULL; + + /** + * + * @var TranslatableStringHelper + */ + private $translatableStringHelper = NULL; + + public function __construct(TwigEngine $templating, TranslatableStringHelper $translatableStringHelper) + { + $this->templating = $templating; + $this->translatableStringHelper = $translatableStringHelper; + } + + public function buildForm(FormBuilderInterface $builder, CustomField $customField) + { + $options = $customField->getOptions(); + + //select the type depending to the SCALE + $type = ($options[self::SCALE] === 0 or $options[self::SCALE] === NULL)? + 'integer' : 'number'; + + $fieldOptions = $this->prepareFieldOptions($customField, $type); + + $builder->add($customField->getSlug(), $type, $fieldOptions); + } + + /** + * prepare the options'form field + * + * @param CustomField $customField + * @param string $type + * @return mixed[] + */ + private function prepareFieldOptions(CustomField $customField, $type) + { + $options = $customField->getOptions(); + + /** + * @var mixed[] the formField options + */ + $fieldOptions = array(); + + // add required + $fieldOptions['required'] = False; + + //add label + $fieldOptions['label'] = $this->translatableStringHelper->localize($customField->getName()); + + // add constraints if required + if ($options[self::MIN] !== NULL) { + $fieldOptions['constraints'][] = new GreaterThanOrEqual(array('value' => $options[self::MIN])); + } + if ($options[self::MAX] !== NULL) { + $fieldOptions['constraints'][] = new LessThanOrEqual(array('value' => $options[self::MAX])); + } + + // add precision to options if required + if ($type === 'number') { + $fieldOptions['scale'] = $options[self::SCALE]; + } + + if (!empty($options[self::POST_TEXT])) { + $fieldOptions['post_text'] = $options[self::POST_TEXT]; + } + + return $fieldOptions; + } + + public function buildOptionsForm(FormBuilderInterface $builder) + { + return $builder + ->add(self::MIN, 'number', array( + 'scale' => 2, + 'label' => 'Greater or equal than', + 'required' => false + )) + ->add(self::MAX, 'number', array( + 'scale' => 2, + 'label' => 'Lesser or equal than', + 'required' => false + )) + ->add(self::SCALE, 'integer', array( + 'scale' => 0, + 'label' => 'Precision', + 'constraints' => array( + new GreaterThanOrEqual(array('value' => 0)) + ) + )) + ->add(self::POST_TEXT, 'text', array( + 'label' => 'Text after the field' + )) + ; + + } + + public function deserialize($serialized, CustomField $customField) + { + return $serialized; + } + + public function getName() + { + return 'Number'; + } + + public function render($value, CustomField $customField, $documentType = 'html') + { + $template = 'ChillCustomFieldsBundle:CustomFieldsRendering:number.' + .$documentType.'.twig'; + $options = $customField->getOptions(); + + return $this->templating + ->render($template, array( + 'number' => $value, + 'scale' => $options[self::SCALE], + 'post' => $options[self::POST_TEXT] + )); + } + + public function serialize($value, CustomField $customField) + { + return $value; + } + +} diff --git a/Form/CustomFieldType.php b/Form/CustomFieldType.php index 7130b8e60..9638cefca 100644 --- a/Form/CustomFieldType.php +++ b/Form/CustomFieldType.php @@ -87,6 +87,7 @@ class CustomFieldType extends AbstractType ->buildOptionsForm( $builder ->create('options', null, array('compound' => true)) + ->setRequired(false) ) ); } diff --git a/Form/Extension/PostTextExtension.php b/Form/Extension/PostTextExtension.php new file mode 100644 index 000000000..24cf4e4f4 --- /dev/null +++ b/Form/Extension/PostTextExtension.php @@ -0,0 +1,53 @@ + + * + * 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\Form\Extension; + +use Symfony\Component\Form\AbstractTypeExtension; +use Symfony\Component\OptionsResolver\OptionsResolver; +use Symfony\Component\Form\FormView; +use Symfony\Component\Form\FormInterface; +use Symfony\Component\PropertyAccess\PropertyAccess; + +/** + * This extension create the possibility to add some text + * after the input. + * + * This can be used to print the units of the field, or some text. + * + * This class must be extended by Extension class specifics to each input. + * + * @author Julien Fastré + */ +abstract class PostTextExtension extends AbstractTypeExtension +{ + public function configureOptions(OptionsResolver $resolver) + { + $resolver->setDefined(array('post_text')); + } + + public function buildView(FormView $view, FormInterface $form, array $options) + { + if (array_key_exists('post_text', $options)) { + //set the post text variable to the view + $view->vars['post_text'] = $options['post_text']; + } + } + +} diff --git a/Form/Extension/PostTextIntegerExtension.php b/Form/Extension/PostTextIntegerExtension.php new file mode 100644 index 000000000..1829501b8 --- /dev/null +++ b/Form/Extension/PostTextIntegerExtension.php @@ -0,0 +1,37 @@ + + * + * 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\Form\Extension; + +use Symfony\Component\Form\Extension\Core\Type\IntegerType; + +/** + * This class add the PostTextExtension to integer fields + * + * @author Julien Fastré + */ +class PostTextIntegerExtension extends PostTextExtension +{ + public function getExtendedType() + { + // return IntegerType::class; !! only for symfony 2.8 + return 'integer'; + } + +} diff --git a/Form/Extension/PostTextNumberExtension.php b/Form/Extension/PostTextNumberExtension.php new file mode 100644 index 000000000..5cbaeca34 --- /dev/null +++ b/Form/Extension/PostTextNumberExtension.php @@ -0,0 +1,34 @@ + + * + * 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\Form\Extension; + +/** + * This class add the PostTextExtension to number fields + * + * @author Julien Fastré + */ +class PostTextNumberExtension extends PostTextExtension +{ + public function getExtendedType() + { + return 'number'; + } + +} diff --git a/Resources/config/services.yml b/Resources/config/services.yml index 1f073c09b..4641910b2 100644 --- a/Resources/config/services.yml +++ b/Resources/config/services.yml @@ -39,6 +39,24 @@ services: - "@chill.main.helper.translatable_string" tags: - { name: 'chill.custom_field', type: 'text' } + + chill.custom_field.number: + class: Chill\CustomFieldsBundle\CustomFields\CustomFieldNumber + arguments: + - "@templating" + - "@chill.main.helper.translatable_string" + tags: + - { name: 'chill.custom_field', type: 'number' } + + chill.form_extension.post_text_integer: + class: Chill\CustomFieldsBundle\Form\Extension\PostTextIntegerExtension + tags: + - { name: form.type_extension, alias: 'integer' } + + chill.form_extension.post_text_number: + class: Chill\CustomFieldsBundle\Form\Extension\PostTextNumberExtension + tags: + - { name: form.type_extension, alias: 'number' } chill.custom_field.choice: class: Chill\CustomFieldsBundle\CustomFields\CustomFieldChoice diff --git a/Resources/translations/messages.fr.yml b/Resources/translations/messages.fr.yml index 77c13fd1c..2f4f55746 100644 --- a/Resources/translations/messages.fr.yml +++ b/Resources/translations/messages.fr.yml @@ -75,3 +75,9 @@ One box on the line: Un seul champ sur la ligne Title level: Niveau de titre Main title: Titre principal Subtitle: Sous-titre + +#custom field number +Greater or equal than: Plus grand ou égal à +Lesser or equal than: Plus petit ou égal à +Precision: Précision +Text after the field: Texte après le champ diff --git a/Resources/views/CustomFieldsRendering/number.html.twig b/Resources/views/CustomFieldsRendering/number.html.twig new file mode 100644 index 000000000..894d34fae --- /dev/null +++ b/Resources/views/CustomFieldsRendering/number.html.twig @@ -0,0 +1 @@ +{% if number is not empty %}{{ number|number_format(scale) }} {{ post|default('') }}{% endif %} diff --git a/Resources/views/Form/fields.html.twig b/Resources/views/Form/fields.html.twig index 04febae3c..b3d35ec94 100644 --- a/Resources/views/Form/fields.html.twig +++ b/Resources/views/Form/fields.html.twig @@ -94,4 +94,28 @@ {% endfor -%} -{% endblock choice_with_other_widget %} \ No newline at end of file +{% endblock choice_with_other_widget %} + +{# extend the integer type to add post_text extension #} +{% block integer_widget %} +{%- if post_text is defined and post_text is not empty-%} +
+{%- endif -%} +{{ block('form_widget') }} +{%- if post_text is defined and post_text is not empty-%} +{{ post_text }} +
+{%- endif -%} +{% endblock %} + +{# extend the number type to add post_text extension #} +{% block number_widget %} +{%- if post_text is defined and post_text is not empty-%} +
+{%- endif -%} +{{ block('form_widget') }} +{%- if post_text is defined and post_text is not empty-%} +{{ post_text }} +
+{%- endif -%} +{% endblock %} diff --git a/Tests/CustomFields/CustomFieldsNumberTest.php b/Tests/CustomFields/CustomFieldsNumberTest.php new file mode 100644 index 000000000..466e03ab4 --- /dev/null +++ b/Tests/CustomFields/CustomFieldsNumberTest.php @@ -0,0 +1,143 @@ + + * + * 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\Tests; + +use Chill\CustomFieldsBundle\CustomFields\CustomFieldNumber; +use Symfony\Component\Form\FormBuilderInterface; +use Chill\CustomFieldsBundle\Entity\CustomField; + +/** + * Test CustomFieldsNumber + * + * @author Julien Fastré + */ +class CustomFieldsNumberTest extends \Symfony\Bundle\FrameworkBundle\Test\WebTestCase +{ + /** + * + * @var CustomFieldNumber + */ + private $customFieldNumber; + + /** + * + * @var FormBuilderInterface + */ + private $formBuilder; + + public function setUp() + { + self::bootKernel(); + + $this->customFieldNumber = self::$kernel->getContainer() + ->get('chill.custom_field.number'); + + $this->formBuilder = self::$kernel->getContainer() + ->get('form.factory') + ->createBuilder('form', null, array( + 'csrf_protection' => false, + 'csrf_field_name' => '_token' + )); + + $request = new \Symfony\Component\HttpFoundation\Request(); + $request->setLocale('fr'); + + self::$kernel->getContainer() + ->get('request_stack') + ->push($request); + } + + /** + * + * @param mixed[] $options + * @return CustomField + */ + private function createCustomFieldNumber($options) + { + return (new CustomField()) + ->setType('number') + ->setActive(true) + ->setOrdering(10) + ->setSlug('default') + ->setName(array('fr' => 'default')) + ->setOptions($options); + } + + public function testCreateValidForm() + { + $cf = $this->createCustomFieldNumber(array( + 'min' => null, + 'max' => null, + 'scale' => null, + 'post_text' => null + )); + + $this->customFieldNumber->buildForm($this->formBuilder, $cf); + + $form = $this->formBuilder->getForm(); + + $form->submit(array('default' => 10)); + + $this->assertTrue($form->isSynchronized()); + $this->assertEquals(10, $form['default']->getData()); + } + + public function testCreateInvalidFormValueGreaterThanMaximum() + { + $cf = $this->createCustomFieldNumber(array( + 'min' => null, + 'max' => 10, + 'scale' => null, + 'post_text' => null + )); + + $this->customFieldNumber->buildForm($this->formBuilder, $cf); + + $form = $this->formBuilder->getForm(); + + $form->submit(array('default' => 100)); + + $this->assertTrue($form->isSynchronized()); + $this->assertFalse($form->isValid()); + $this->assertEquals(1, count($form['default']->getErrors())); + } + + public function testCreateInvalidFormValueLowerThanMinimum() + { + $cf = $this->createCustomFieldNumber(array( + 'min' => 1000, + 'max' => null, + 'scale' => null, + 'post_text' => null + )); + + $this->customFieldNumber->buildForm($this->formBuilder, $cf); + + $form = $this->formBuilder->getForm(); + + $form->submit(array('default' => 100)); + + $this->assertTrue($form->isSynchronized()); + $this->assertFalse($form->isValid()); + $this->assertEquals(1, count($form['default']->getErrors())); + } + + +} diff --git a/Tests/Fixtures/App/app/config/config.yml b/Tests/Fixtures/App/app/config/config.yml index 9fdeaf68d..48bd7abed 100644 --- a/Tests/Fixtures/App/app/config/config.yml +++ b/Tests/Fixtures/App/app/config/config.yml @@ -66,7 +66,7 @@ security: anonymous: ~ form_login: csrf_parameter: _csrf_token - intention: authenticate + csrf_token_id: authenticate csrf_provider: form.csrf_provider logout: ~ access_control: diff --git a/composer.json b/composer.json index 0e0224354..68f716242 100644 --- a/composer.json +++ b/composer.json @@ -17,7 +17,7 @@ ], "require": { "php": "~5.5", - "symfony/symfony": "2.7.*", + "symfony/symfony": "~2.7", "doctrine/orm": "~2.4", "doctrine/dbal" : "~2.5", "doctrine/common": "~2.4", @@ -57,4 +57,4 @@ "symfony-app-dir": "Tests/Fixtures/App/app", "app-migrations-dir": "Tests/Fixtures/App/app/DoctrineMigrations" } -} \ No newline at end of file +}