+ {%- set title_div_opened = true -%}
+ {%- else -%}
+ {%- if subtitle_div_opened == true -%}
+
+ {%- set title_div_opened = false -%}
+ {%- endif -%}
+
+ {%- endif -%}
+ {%- else -%}
+ {%- if subtitle_div_opened == false -%}
+
+ {%- set subtitle_div_opened = true -%}
+ {%- else -%}
+
+ {%- endif -%}
+ {%- endif -%}
+ {{ chill_custom_field_widget(cFData , customField) }}
+ {%- else -%}
+ {# we keep the customfield in memory, and print it only if 'something' has been filled after the title #}
+ {%- if customField.options[type] == type_title -%}
+ {%- set title = customField -%}
+ {# we have to reset the title hierarchy if we misused titles hierarchy #}
+ {%- set subtitle = null -%}
+ {%- elseif customField.options[type] == type_subtitle -%}
+ {%- set subtitle = customField -%}
+ {%- endif -%}
+ {%- endif -%}
+ {% else %}
+ {%- if show_empty == true or (chill_custom_field_is_empty(cFData, customField) == false) -%}
+ {%- if title is not empty -%}
+ {%- if title_div_opened == false -%}
+
+ {%- set title_div_opened = true -%}
+ {%- else -%}
+ {%- if subtitle_div_opened == true -%}
+
+ {%- set title_div_opened = false -%}
+ {%- endif -%}
+
+ {%- endif -%}
+ {{ chill_custom_field_widget(cFData, title) }}
+ {%- set title = null -%}
+ {%- endif -%}
+ {%- if subtitle is not empty -%}
+ {%- if subtitle_div_opened == false -%}
+
+ {%- set subtitle_div_opened = true -%}
+ {%- else -%}
+
+ {%- endif -%}
+ {{ chill_custom_field_widget(cFData, subtitle) }}
+ {%- set subtitle = null -%}
+ {%- endif -%}
+
{{ chill_custom_field_label(customField) }}
+ {{ chill_custom_field_widget(cFData , customField) }}
+ {%- set something_has_been_printed = true -%}
+ {%- endif -%}
+ {%- endif -%}
+{% endfor %}
+{%- if subtitle_div_opened == true -%}
+
+{%- endif -%}
+{%- if title_div_opened == true -%}
+
+{%- endif -%}
+{% if something_has_been_printed == false %}
+
{{ 'Empty data'|trans }}
+
{{ 'No data to show' | trans }}
+{% endif %}
\ No newline at end of file
diff --git a/src/Bundle/ChillCustomFields/Resources/views/CustomFieldsGroup/render_for_test.html.twig b/src/Bundle/ChillCustomFields/Resources/views/CustomFieldsGroup/render_for_test.html.twig
new file mode 100644
index 000000000..a3c5ff8fe
--- /dev/null
+++ b/src/Bundle/ChillCustomFields/Resources/views/CustomFieldsGroup/render_for_test.html.twig
@@ -0,0 +1,7 @@
+
+ {{ customFieldsGroup.name(app.request.locale) }}
+ {% for customField in customFieldsGroup.customFields %}
+ - {{ chill_custom_field_label(customField) }}
+ - {{ chill_custom_field_widget(fields, customField) }}
+ {% endfor %}
+
\ No newline at end of file
diff --git a/src/Bundle/ChillCustomFields/Resources/views/CustomFieldsGroup/show.html.twig b/src/Bundle/ChillCustomFields/Resources/views/CustomFieldsGroup/show.html.twig
new file mode 100644
index 000000000..c6e4fada1
--- /dev/null
+++ b/src/Bundle/ChillCustomFields/Resources/views/CustomFieldsGroup/show.html.twig
@@ -0,0 +1,116 @@
+{#
+ * Copyright (C) 2014, Champs Libres Cooperative SCRLFS,
+ *
+ * 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 .
+#}
+{% extends "@ChillCustomFields/Admin/layout.html.twig" %}
+
+{% block title %}{{ 'CustomFieldsGroup details'|trans }}{% endblock %}
+
+{% block admin_content %}
+ {{ 'CustomFieldsGroup details'|trans }}
+
+
+
+
+ {{ 'Name'|trans }} |
+ {{ entity.getName|localize_translatable_string }} |
+
+
+ {{ 'Entity'|trans }} |
+ {{ entity.entity|trans }} |
+
+ {%- for key in options -%}
+
+ {{ key ~ '_label'|trans }} |
+
+ {%- if entity.options[key] is not defined -%}
+ {{ 'No value defined for this option'|trans }}
+ {%- elseif entity.options[key] is iterable -%}
+ {{ entity.options[key]|join(', ') }}
+ {% else %}
+ {{ entity.options[key] }}
+ {%- endif -%}
+ |
+
+ {%- else -%}
+
+ {%- endfor -%}
+
+
+
+
+
+ {{ 'Fields associated with this group'|trans }}
+
+ {%- if entity.customFields|length > 0 -%}
+
+
+
+ {{ 'ordering'|trans|capitalize }} |
+ {{ 'label_field'|trans|capitalize }} |
+ {{ 'type'|trans|capitalize }} |
+ {{ 'active'|trans|capitalize }} |
+ |
+
+
+
+ {%- for field in entity.customFields -%}
+
+ {{ field.ordering }} |
+ {{ field.name|localize_translatable_string }} |
+ {{ field.type|trans }} |
+
+ {%- if field.active -%}
+
+ {%- else -%}
+
+ {%- endif -%}
+ |
+
+ {{ 'edit'|trans|capitalize }}
+ |
+
+ {%- endfor -%}
+
+
+ {{ form_start(create_field_form) }}
+
+ {{ form_widget(create_field_form.type) }}
+
+ {{ form_widget(create_field_form.submit, { 'attr': { 'class': 'sc-button bt-create' }, 'label': 'Add a new field' } ) }}
+ {{ form_end(create_field_form) }}
+ {%- else -%}
+
+ {{ 'Any field is currently associated with this group'|trans }}
+
+ {{ form_start(create_field_form) }}
+
+ {{ form_widget(create_field_form.type) }}
+
+ {{ form_widget(create_field_form.submit, { 'attr': { 'class': 'sc-button bt-create' }, 'label': 'Create a new field' } ) }}
+ {{ form_end(create_field_form) }}
+ {%- endif -%}
+{% endblock %}
diff --git a/src/Bundle/ChillCustomFields/Resources/views/CustomFieldsRendering/choice.csv.twig b/src/Bundle/ChillCustomFields/Resources/views/CustomFieldsRendering/choice.csv.twig
new file mode 100644
index 000000000..1538fd437
--- /dev/null
+++ b/src/Bundle/ChillCustomFields/Resources/views/CustomFieldsRendering/choice.csv.twig
@@ -0,0 +1,13 @@
+{% if selected|length > 0 %}
+{%- for choice in choices -%}
+ {% if choice['slug'] in selected %}
+ {%- if choice['slug'] is not same as('_other') -%}
+ {{ choice['name']|localize_translatable_string|csv_cell }}
+ {%- else -%}
+ {{ choice['name']|csv_cell }}
+ {%- endif -%}
+ {% endif %}
+{%- endfor -%}
+{% else %}
+ {{ 'None'|trans }}
+{% endif %}
\ No newline at end of file
diff --git a/src/Bundle/ChillCustomFields/Resources/views/CustomFieldsRendering/choice.html.twig b/src/Bundle/ChillCustomFields/Resources/views/CustomFieldsRendering/choice.html.twig
new file mode 100644
index 000000000..02f6ab13b
--- /dev/null
+++ b/src/Bundle/ChillCustomFields/Resources/views/CustomFieldsRendering/choice.html.twig
@@ -0,0 +1,29 @@
+{% if selected|length > 0 %}
+
+ {%- for choice in choices -%}
+ {% if choice['slug'] in selected %}
+ {%- set is_selected = true -%}
+ {%- else -%}
+ {%- set is_selected = false -%}
+ {%- endif -%}
+
+ {%- if is_selected -%}
+ -
+ {%- if is_selected -%}
+
+ {%- else -%}
+
+ {%- endif -%}
+
+ {%- if choice['slug'] is not same as('_other') -%}
+ {{ choice['name']|localize_translatable_string }}
+ {%- else -%}
+ {{ choice['name'] }}
+ {%- endif -%}
+
+ {%- endif -%}
+ {%- endfor -%}
+
+{% else %}
+ {{ 'None'|trans }}
+{% endif %}
\ No newline at end of file
diff --git a/src/Bundle/ChillCustomFields/Resources/views/CustomFieldsRendering/choice_long.csv.twig b/src/Bundle/ChillCustomFields/Resources/views/CustomFieldsRendering/choice_long.csv.twig
new file mode 100644
index 000000000..ec7084776
--- /dev/null
+++ b/src/Bundle/ChillCustomFields/Resources/views/CustomFieldsRendering/choice_long.csv.twig
@@ -0,0 +1 @@
+{% if values|length == 1 %}{{ values[0].text|localize_translatable_string|default("")|csv_cell }}{% elseif values|length ==0 %}{{ 'emtpy'|csv_cell }}{% else %}{{ 'pas de rendu multiple'|csv_cell }}{% endif %}
\ No newline at end of file
diff --git a/src/Bundle/ChillCustomFields/Resources/views/CustomFieldsRendering/choice_long.html.twig b/src/Bundle/ChillCustomFields/Resources/views/CustomFieldsRendering/choice_long.html.twig
new file mode 100644
index 000000000..6849f1d0c
--- /dev/null
+++ b/src/Bundle/ChillCustomFields/Resources/views/CustomFieldsRendering/choice_long.html.twig
@@ -0,0 +1,11 @@
+{% if values|length > 0 %}
+
+ {%- for value in values -%}
+ -
+ {{ value.text|localize_translatable_string }}
+
+ {%- endfor -%}
+
+{% else %}
+ {{ 'None'|trans }}
+{% endif %}
\ No newline at end of file
diff --git a/src/Bundle/ChillCustomFields/Resources/views/CustomFieldsRendering/date.html.twig b/src/Bundle/ChillCustomFields/Resources/views/CustomFieldsRendering/date.html.twig
new file mode 100644
index 000000000..e0bb6c865
--- /dev/null
+++ b/src/Bundle/ChillCustomFields/Resources/views/CustomFieldsRendering/date.html.twig
@@ -0,0 +1 @@
+{% if value is not empty %}{{ value|format_date(format)}}{% endif %}
diff --git a/src/Bundle/ChillCustomFields/Resources/views/CustomFieldsRendering/number.csv.twig b/src/Bundle/ChillCustomFields/Resources/views/CustomFieldsRendering/number.csv.twig
new file mode 100644
index 000000000..e48f2f878
--- /dev/null
+++ b/src/Bundle/ChillCustomFields/Resources/views/CustomFieldsRendering/number.csv.twig
@@ -0,0 +1 @@
+{% if number is not empty %}{{ number|number_format(scale)|csv_cell }}{% endif %}
diff --git a/src/Bundle/ChillCustomFields/Resources/views/CustomFieldsRendering/number.html.twig b/src/Bundle/ChillCustomFields/Resources/views/CustomFieldsRendering/number.html.twig
new file mode 100644
index 000000000..894d34fae
--- /dev/null
+++ b/src/Bundle/ChillCustomFields/Resources/views/CustomFieldsRendering/number.html.twig
@@ -0,0 +1 @@
+{% if number is not empty %}{{ number|number_format(scale) }} {{ post|default('') }}{% endif %}
diff --git a/src/Bundle/ChillCustomFields/Resources/views/CustomFieldsRendering/text.csv.twig b/src/Bundle/ChillCustomFields/Resources/views/CustomFieldsRendering/text.csv.twig
new file mode 100644
index 000000000..03b805bbb
--- /dev/null
+++ b/src/Bundle/ChillCustomFields/Resources/views/CustomFieldsRendering/text.csv.twig
@@ -0,0 +1 @@
+{% if text is not empty %}{{ text|csv_cell}}{% else %}{{ 'None'|trans }}{% endif %}
\ No newline at end of file
diff --git a/src/Bundle/ChillCustomFields/Resources/views/CustomFieldsRendering/text.html.twig b/src/Bundle/ChillCustomFields/Resources/views/CustomFieldsRendering/text.html.twig
new file mode 100644
index 000000000..6b977f123
--- /dev/null
+++ b/src/Bundle/ChillCustomFields/Resources/views/CustomFieldsRendering/text.html.twig
@@ -0,0 +1 @@
+{% if text is not empty %}{{ text|nl2br }}{% else %}{{ 'None'|trans }}{% endif %}
\ No newline at end of file
diff --git a/src/Bundle/ChillCustomFields/Resources/views/CustomFieldsRendering/title.html.twig b/src/Bundle/ChillCustomFields/Resources/views/CustomFieldsRendering/title.html.twig
new file mode 100644
index 000000000..59973725c
--- /dev/null
+++ b/src/Bundle/ChillCustomFields/Resources/views/CustomFieldsRendering/title.html.twig
@@ -0,0 +1,5 @@
+{% if type == "title"%}
+ {{ title | localize_translatable_string }}
+{% else %}
+ {{ title | localize_translatable_string }}
+{% endif %}
\ No newline at end of file
diff --git a/src/Bundle/ChillCustomFields/Resources/views/Form/fields.html.twig b/src/Bundle/ChillCustomFields/Resources/views/Form/fields.html.twig
new file mode 100644
index 000000000..1fc722d77
--- /dev/null
+++ b/src/Bundle/ChillCustomFields/Resources/views/Form/fields.html.twig
@@ -0,0 +1,111 @@
+{#
+ * Copyright (C) 2014, Champs Libres Cooperative SCRLFS,
+ *
+ * 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 .
+#}
+
+{# CustomFields Title #}
+{% block custom_field_title_widget %}
+
+ {{ form.vars.attr.title }}
+
+{% endblock custom_field_title_widget %}
+
+{# CustomFields Choice #}
+{# render an alement in a choice list #}
+{% block cf_choices_list_widget %}
+ {{ form_row(form.name) }}
+ {{ form_row(form.active) }}
+ {{ form_row(form.slug) }}
+{% endblock cf_choices_list_widget %}
+
+{# CFChoice : render the different elements in a choice list #}
+{% block cf_choices_row %}
+ {{ 'Choices'|trans }}
+
+
+
+
+ {# we use javascrit to add an additional element. All functions are personnalized with the id ( = form.vars.id) #}
+
+{% endblock cf_choices_row %}
+
+{# 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 %}
+
+{# extend the number 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 %}
+
+{# The choice_with_other_widget widget is defined in the main bundle #}
+
diff --git a/src/Bundle/ChillCustomFields/Service/CustomFieldProvider.php b/src/Bundle/ChillCustomFields/Service/CustomFieldProvider.php
new file mode 100644
index 000000000..d2871560e
--- /dev/null
+++ b/src/Bundle/ChillCustomFields/Service/CustomFieldProvider.php
@@ -0,0 +1,103 @@
+,
+ *
+ * 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\Service;
+
+use Symfony\Component\DependencyInjection\ContainerAwareInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * Receive all the services tagged with 'chill.custom_field'.
+ *
+ * The services tagged with 'chill.custom_field' are services used to declare
+ * a new custom field type. The tag must contain a 'type' variable (that must
+ * be unique), this type is used to identify this custom field in the form
+ * declration
+ *
+ * For example (in services.yml) :
+ * services:
+ * chill.icp2.type:
+ * tags:
+ * - { name: 'chill.custom_field', type: 'ICPC2' }
+ *
+ * @author Julien Fastré
+ */
+class CustomFieldProvider implements ContainerAwareInterface
+{
+ /** @var array $servicesByType The services indexes by the type */
+ private $servicesByType = array();
+
+ /** @var Container $container The container */
+ private $container;
+
+ /**
+ * Add a new custom field to the provider
+ *
+ * @param type $serviceName The name of the service (declared in service.yml)
+ * @param type $type The type of the service (that is used in the form to
+ * add this type)
+ */
+ public function addCustomField($serviceName, $type)
+ {
+ $this->servicesByType[$type] = $serviceName;
+ }
+
+ /**
+ * Get a custom field stored in the provider. The custom field is identified
+ * by its type.
+ *
+ * @param string $type The type of the wanted service
+ * @return CustomFieldInterface
+ */
+ public function getCustomFieldByType($type)
+ {
+ if (isset($this->servicesByType[$type])) {
+ return $this->servicesByType[$type];
+ } else {
+ throw new \LogicException('the custom field with type '.$type.' '
+ . 'is not found');
+ }
+ }
+
+ /*
+ * (non-PHPdoc)
+ * @see \Symfony\Component\DependencyInjection\ContainerAwareInterface::setContainer()
+ */
+ public function setContainer(ContainerInterface $container = null)
+ {
+ if ($container === null) {
+ throw new \LogicException('container should not be null');
+ }
+
+ $this->container = $container;
+ }
+
+ /**
+ * Get all the custom fields known by the provider
+ *
+ * @return array Array of the known custom fields indexed by the type.
+ */
+ public function getAllFields()
+ {
+ return $this->servicesByType;
+ }
+}
diff --git a/src/Bundle/ChillCustomFields/Service/CustomFieldsHelper.php b/src/Bundle/ChillCustomFields/Service/CustomFieldsHelper.php
new file mode 100644
index 000000000..f5bc2cc2e
--- /dev/null
+++ b/src/Bundle/ChillCustomFields/Service/CustomFieldsHelper.php
@@ -0,0 +1,90 @@
+,
+ *
+ * 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\Service;
+
+use Doctrine\ORM\EntityManagerInterface;
+use Chill\CustomFieldsBundle\Service\CustomFieldProvider;
+use Chill\CustomFieldsBundle\Entity\CustomFieldsGroup;
+use Chill\CustomFieldsBundle\Entity\CustomField;
+
+/**
+ * Helpers for manipulating custom fields.
+ *
+ * Herlpers for getting a certain custom field, for getting the raw value
+ * of a custom field and for rendering the value of a custom field.
+ *
+ * @author Julien Fastré
+ *
+ */
+class CustomFieldsHelper
+{
+ /** @var EntityManagerInterface $em The entity manager */
+ private $em;
+
+ /** @var CustomFieldProvider $provider Provider of all the declared custom
+ * fields */
+ private $provider;
+
+ /**
+ * Constructor
+ *
+ * @param EntityManagerInterface $em The entity manager
+ * @param CustomFieldProvider $provider The customfield provider that
+ * contains all the declared custom fields
+ */
+ public function __construct(EntityManagerInterface $em,
+ CustomFieldProvider $provider)
+ {
+ $this->em = $em;
+ $this->provider = $provider;
+ }
+
+ public function isEmptyValue(array $fields, CustomField $customField)
+ {
+ $slug = $customField->getSlug();
+ $rawValue = (isset($fields[$slug])) ? $fields[$slug] : null;
+ $customFieldType = $this->provider->getCustomFieldByType($customField->getType());
+
+ $deserializedValue = $customFieldType->deserialize($rawValue, $customField);
+
+ return $customFieldType->isEmptyValue($deserializedValue, $customField);
+ }
+
+ /**
+ * Render the value of a custom field
+ *
+ * @param array $fields the **raw** array, as stored in the db
+ * @param CustomField $customField the customField entity
+ * @param string $documentType The type of document in which the rendered value is displayed ('html' or 'csv').
+ * @throws CustomFieldsHelperException if slug is missing
+ * @return The representation of the value the customField.
+ */
+ public function renderCustomField(array $fields, CustomField $customField, $documentType='html')
+ {
+ $slug = $customField->getSlug();
+ $rawValue = (isset($fields[$slug])) ? $fields[$slug] : null;
+ $customFieldType = $this->provider->getCustomFieldByType($customField->getType());
+
+ return $customFieldType->render($rawValue, $customField, $documentType);
+ }
+}
\ No newline at end of file
diff --git a/src/Bundle/ChillCustomFields/Service/CustomFieldsHelperException.php b/src/Bundle/ChillCustomFields/Service/CustomFieldsHelperException.php
new file mode 100644
index 000000000..497c96720
--- /dev/null
+++ b/src/Bundle/ChillCustomFields/Service/CustomFieldsHelperException.php
@@ -0,0 +1,36 @@
+,
+ *
+ * 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\Service;
+
+class CustomFieldsHelperException extends \Exception
+{
+ public static function customFieldsGroupNotFound($entity)
+ {
+ return new CustomFieldsHelperException("The customFieldsGroups associated with $entity are not found");
+ }
+
+ public static function slugIsMissing()
+ {
+ return new CustomFieldsHelperException("The slug is missing");
+ }
+}
\ No newline at end of file
diff --git a/src/Bundle/ChillCustomFields/Templating/Twig/CustomFieldRenderingTwig.php b/src/Bundle/ChillCustomFields/Templating/Twig/CustomFieldRenderingTwig.php
new file mode 100644
index 000000000..abbbace5d
--- /dev/null
+++ b/src/Bundle/ChillCustomFields/Templating/Twig/CustomFieldRenderingTwig.php
@@ -0,0 +1,146 @@
+,
+ *
+ * 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\Templating\Twig;
+
+use Chill\CustomFieldsBundle\Service\CustomFieldsHelper;
+use Symfony\Component\DependencyInjection\ContainerAwareInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+use Symfony\Component\DependencyInjection\Container;
+use Chill\CustomFieldsBundle\Entity\CustomField;
+use Twig\Extension\AbstractExtension;
+use Twig\TwigFunction;
+
+/**
+ * Add the following Twig Extension :
+ * * chill_custom_field_widget : to render the value of the custom field,
+ * * chill_custom_field_label : to render the label of the custom field,
+ *
+ * @author Julien Fastré
+ */
+class CustomFieldRenderingTwig extends AbstractExtension implements ContainerAwareInterface
+{
+
+ /** @var Container $container The container */
+ private $container;
+
+ /** @var array $defaultParams The default parameters */
+ private $defaultParams = array(
+ 'label_layout' => 'ChillCustomFieldsBundle:CustomField:render_label.html.twig'
+ );
+
+ /**
+ * @var CustomFieldsHelper
+ */
+ private $customFieldsHelper;
+
+ public function __construct(CustomFieldsHelper $customFieldsHelper)
+ {
+ $this->customFieldsHelper = $customFieldsHelper;
+ }
+
+ /*
+ * (non-PHPdoc)
+ * @see \Symfony\Component\DependencyInjection\ContainerAwareInterface::setContainer()
+ */
+ public function setContainer(ContainerInterface $container = null)
+ {
+ $this->container = $container;
+ }
+
+ /*
+ * (non-PHPdoc)
+ * @see Twig_Extension::getFunctions()
+ */
+ public function getFunctions()
+ {
+ return [
+ new TwigFunction('chill_custom_field_widget', array(
+ $this,
+ 'renderWidget'
+ ), array(
+ 'is_safe' => array(
+ 'html'
+ )
+ )),
+ new TwigFunction('chill_custom_field_label', array(
+ $this,
+ 'renderLabel'
+ ), array(
+ 'is_safe' => array(
+ 'html'
+ )
+ )),
+ new TwigFunction('chill_custom_field_is_empty', array(
+ $this,
+ 'isEmptyValue'
+ ))
+ ];
+ }
+
+
+ public function isEmptyValue($fields, CustomField $customField)
+ {
+ return $this->customFieldsHelper
+ ->isEmptyValue($fields, $customField);
+ }
+
+ /* (non-PHPdoc)
+ * @see Twig_ExtensionInterface::getName()
+ */
+ public function getName()
+ {
+ return 'chill_custom_fields_rendering';
+ }
+
+ /**
+ * Twig Extension that is used to render the label of a custom field.
+ *
+ * @param CustomField $customField Either a customField OR a customizable_entity OR the FQDN of the entity
+ * @param array $params The parameters for rendering. Currently, 'label_layout' allow to choose a different label. Default is 'ChillCustomFieldsBundle:CustomField:render_label.html.twig'
+ * @return string HTML representation of the custom field label.
+ */
+ public function renderLabel(CustomField $customField, array $params = array())
+ {
+ $resolvedParams = array_merge($this->defaultParams, $params);
+
+ return $this->container->get('templating')
+ ->render($resolvedParams['label_layout'], array('customField' => $customField));
+ }
+
+ /**
+ * Twig extension that is used to render the value of a custom field.
+ *
+ * The presentation of the value is influenced by the document type.
+ *
+ * @param array $fields The array raw, as stored in the db
+ * @param CustomField $customField Either a customField OR a customizable_entity OR the FQDN of the entity
+ * @param string $documentType The type of the document (csv, html)
+ * @param string $slug The slug of the custom field ONLY necessary if the first argument is NOT a CustomField instance
+ * @return string HTML representation of the custom field value, as described in the CustomFieldInterface. Is HTML safe
+ */
+ public function renderWidget(array $fields, CustomField $customField, $documentType='html')
+ {
+ return $this->customFieldsHelper
+ ->renderCustomField($fields, $customField, $documentType);
+ }
+}
\ No newline at end of file
diff --git a/src/Bundle/ChillCustomFields/Templating/Twig/CustomFieldsGroupRenderingTwig.php b/src/Bundle/ChillCustomFields/Templating/Twig/CustomFieldsGroupRenderingTwig.php
new file mode 100644
index 000000000..10758d26d
--- /dev/null
+++ b/src/Bundle/ChillCustomFields/Templating/Twig/CustomFieldsGroupRenderingTwig.php
@@ -0,0 +1,120 @@
+,
+ *
+ * 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\Templating\Twig;
+
+use Symfony\Component\DependencyInjection\ContainerAwareInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+use Symfony\Component\DependencyInjection\Container;
+use Twig\Extension\AbstractExtension;
+use Twig\TwigFunction;
+
+/**
+ * Add the following Twig Extension :
+ * * chill_custom_fields_group_widget : to render the value of a custom field
+ * * group
+ *
+ * @author Julien Fastré
+ * @author Marc Ducobu
+ */
+class CustomFieldsGroupRenderingTwig extends AbstractExtension implements ContainerAwareInterface
+{
+
+ /** @var Container $container The container */
+ private $container;
+
+ /** @var array $defaultParams The default parameters */
+ private $defaultParams = array(
+ 'layout' => 'ChillCustomFieldsBundle:CustomFieldsGroup:render.html.twig',
+ 'show_empty' => True
+ );
+
+ /**
+ *
+ * @param boolean $showEmptyValues whether the empty values must be rendered
+ */
+ public function __construct($showEmptyValues)
+ {
+ $this->defaultParams['show_empty'] = $showEmptyValues;
+ }
+
+ /*
+ * (non-PHPdoc)
+ * @see \Symfony\Component\DependencyInjection\ContainerAwareInterface::setContainer()
+ */
+ public function setContainer(ContainerInterface $container = null)
+ {
+ $this->container = $container;
+ }
+
+ /*
+ * (non-PHPdoc)
+ * @see Twig_Extension::getFunctions()
+ */
+ public function getFunctions()
+ {
+ return [
+ new TwigFunction('chill_custom_fields_group_widget', array(
+ $this,
+ 'renderWidget'
+ ), array(
+ 'is_safe' => array(
+ 'html'
+ )
+ )),
+ ];
+ }
+
+ /* (non-PHPdoc)
+ * @see Twig_ExtensionInterface::getName()
+ */
+ public function getName()
+ {
+ return 'chill_custom_fields_group_rendering';
+ }
+
+ /**
+ * Twig extension that is used to render the value of a custom field group.
+ *
+ * The presentation of the value is influenced by the document type.
+ *
+ * @param array $fields The array raw, as stored in the db
+ * @param CustomFieldsGroud $customFielsGroup The custom field group
+ * @param string $documentType The type of the document (csv, html)
+ * @param array $params The parameters for rendering :
+ * - layout : allow to choose a different layout by default :
+ * ChillCustomFieldsBundle:CustomFieldsGroup:render.html.twig
+ * - show_empty : force show empty field
+ * @return string HTML representation of the custom field group value, as described in
+ * the CustomFieldInterface. Is HTML safe
+ */
+ public function renderWidget(array $fields, $customFielsGroup, $documentType='html', array $params = array())
+ {
+ $resolvedParams = array_merge($this->defaultParams, $params);
+
+ return $this->container->get('templating')
+ ->render($resolvedParams['layout'], array(
+ 'cFGroup' => $customFielsGroup,
+ 'cFData' => $fields,
+ 'show_empty' => $resolvedParams['show_empty']));
+ }
+}
\ No newline at end of file
diff --git a/src/Bundle/ChillCustomFields/Tests/Config/ConfigCustomizablesEntitiesTest.php b/src/Bundle/ChillCustomFields/Tests/Config/ConfigCustomizablesEntitiesTest.php
new file mode 100644
index 000000000..df66ab56c
--- /dev/null
+++ b/src/Bundle/ChillCustomFields/Tests/Config/ConfigCustomizablesEntitiesTest.php
@@ -0,0 +1,70 @@
+
+ *
+ * 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\Config;
+
+use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
+
+/**
+ * Test the option Customizables_entities
+ *
+ * @author Julien Fastré
+ */
+class ConfigCustomizablesEntitiesTest extends KernelTestCase
+{
+ /**
+ * Test that the config does work if the option
+ * chill_custom_fields.customizables_entities IS NOT present
+ *
+ * In this case, parameter 'chill_custom_fields.customizables_entities'
+ * should be an empty array in container
+ */
+ public function testNotPresentInConfig()
+ {
+ self::bootKernel(array('environment' => 'test'));
+ $customizableEntities = static::$kernel->getContainer()
+ ->getParameter('chill_custom_fields.customizables_entities');
+
+ $this->assertInternalType('array', $customizableEntities);
+ $this->assertCount(1, $customizableEntities);
+ }
+
+ /**
+ * Test that the 'chill_custom_fields.customizables_entities' is filled
+ * correctly with a minimal configuration.
+ *
+ * @internal use a custom config environment
+ */
+ public function testNotEmptyConfig()
+ {
+ self::bootKernel(array('environment' => 'test_customizable_entities_test_not_empty_config'));
+ $customizableEntities = static::$kernel->getContainer()
+ ->getParameter('chill_custom_fields.customizables_entities');
+
+ $this->assertInternalType('array', $customizableEntities);
+ $this->assertCount(2, $customizableEntities);
+
+ foreach($customizableEntities as $key => $config) {
+ $this->assertInternalType('array', $config);
+ $this->assertArrayHasKey('name', $config);
+ $this->assertArrayHasKey('class', $config);
+ }
+ }
+}
diff --git a/src/Bundle/ChillCustomFields/Tests/Controller/CustomFieldControllerTest_TODO.php b/src/Bundle/ChillCustomFields/Tests/Controller/CustomFieldControllerTest_TODO.php
new file mode 100644
index 000000000..c41de20e0
--- /dev/null
+++ b/src/Bundle/ChillCustomFields/Tests/Controller/CustomFieldControllerTest_TODO.php
@@ -0,0 +1,55 @@
+request('GET', '/customfield/');
+ $this->assertEquals(200, $client->getResponse()->getStatusCode(), "Unexpected HTTP status code for GET /customfield/");
+ $crawler = $client->click($crawler->selectLink('Create a new entry')->link());
+
+ // Fill in the form and submit it
+ $form = $crawler->selectButton('Create')->form(array(
+ 'cl_customfieldsbundle_customfield[field_name]' => 'Test',
+ // ... other fields to fill
+ ));
+
+ $client->submit($form);
+ $crawler = $client->followRedirect();
+
+ // Check data in the show view
+ $this->assertGreaterThan(0, $crawler->filter('td:contains("Test")')->count(), 'Missing element td:contains("Test")');
+
+ // Edit the entity
+ $crawler = $client->click($crawler->selectLink('Edit')->link());
+
+ $form = $crawler->selectButton('Update')->form(array(
+ 'cl_customfieldsbundle_customfield[field_name]' => 'Foo',
+ // ... other fields to fill
+ ));
+
+ $client->submit($form);
+ $crawler = $client->followRedirect();
+
+ // Check the element contains an attribute with value equals "Foo"
+ $this->assertGreaterThan(0, $crawler->filter('[value="Foo"]')->count(), 'Missing element [value="Foo"]');
+
+ // Delete the entity
+ $client->submit($crawler->selectButton('Delete')->form());
+ $crawler = $client->followRedirect();
+
+ // Check the entity has been delete on the list
+ $this->assertNotRegExp('/Foo/', $client->getResponse()->getContent());
+ }
+
+ */
+}
diff --git a/src/Bundle/ChillCustomFields/Tests/Controller/CustomFieldsGroupControllerTest.php b/src/Bundle/ChillCustomFields/Tests/Controller/CustomFieldsGroupControllerTest.php
new file mode 100644
index 000000000..8822aec15
--- /dev/null
+++ b/src/Bundle/ChillCustomFields/Tests/Controller/CustomFieldsGroupControllerTest.php
@@ -0,0 +1,74 @@
+ 'test_customizable_entities_test_not_empty_config'));
+ // Create a new client to browse the application
+ $client = static::createClient(array(), array(
+ 'PHP_AUTH_USER' => 'admin',
+ 'PHP_AUTH_PW' => 'olala',
+ ));
+
+ //create the entity
+ $this->createCustomFieldsGroup($client);
+
+ // Edit the entity
+ $this->editCustomFieldsGroup($client);
+ }
+
+ private function createCustomFieldsGroup(Client &$client)
+ {
+ // Create a new entry in the database
+ $crawler = $client->request('GET', '/fr/admin/customfieldsgroup/');
+
+ $this->assertEquals(200, $client->getResponse()->getStatusCode(),
+ "Unexpected HTTP status code for GET /customfieldsgroup/");
+
+ $crawler = $client->click($crawler->selectLink('Créer un nouveau groupe')->link());
+
+ // Fill in the form and submit it
+ $form = $crawler->selectButton('Créer')->form(array(
+ 'custom_fields_group[name][fr]' => 'Test',
+ 'custom_fields_group[entity]' => 'Chill\PersonBundle\Entity\Person'
+ ));
+
+ $crawler = $client->submit($form);
+
+ $crawler = $client->followRedirect();
+
+ // Check data in the show view
+ $this->assertGreaterThan(0, $crawler->filter('td:contains("Test")')->count(),
+ 'Missing element td:contains("Test")');
+ }
+
+ private function editCustomFieldsGroup(Client $client)
+ {
+ $crawler = $client->request('GET', '/fr/admin/customfieldsgroup/');
+ $links = $crawler->selectLink('Modifier');
+
+ $this->assertGreaterThan(0, $links->count(),
+ "We can't find a 'Modifier' link on the index page");
+ $crawler = $client->click($links->last()->link());
+
+ $this->assertEquals(200, $client->getResponse()->getStatusCode());
+
+ $form = $crawler->selectButton('Mettre à jour')->form(array(
+ 'custom_fields_group[name][fr]' => 'Foo',
+ ));
+
+ $client->submit($form);
+ $crawler = $client->followRedirect();
+
+ // Check the element contains an attribute with value equals "Foo"
+ $this->assertGreaterThan(0, $crawler->filter('[value="Foo"]')->count(),
+ 'Missing element [value="Foo"]');
+ }
+}
diff --git a/src/Bundle/ChillCustomFields/Tests/CustomFieldTestHelper.php b/src/Bundle/ChillCustomFields/Tests/CustomFieldTestHelper.php
new file mode 100644
index 000000000..3d4c58fe0
--- /dev/null
+++ b/src/Bundle/ChillCustomFields/Tests/CustomFieldTestHelper.php
@@ -0,0 +1,86 @@
+
+ *
+ * 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 Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
+use Chill\CustomFieldsBundle\Entity\CustomField;
+use Symfony\Component\HttpKernel\KernelInterface;
+use Symfony\Component\DomCrawler\Crawler;
+
+/**
+ * Give useful method to prepare tests regarding custom fields
+ *
+ * @author Julien Fastré
+ */
+trait CustomFieldTestHelper
+{
+
+ /**
+ * Prepare a crawler containing the rendering of a customField
+ *
+ * @internal This method will mock a customFieldGroup containing $field, and
+ * rendering the customfield, using Type\CustomFieldType, to a simple form row
+ *
+ * @param CustomField $field
+ * @param KernelTestCase $testCase
+ * @param KernelInterface $kernel
+ * @param type $locale
+ * @return Crawler
+ */
+ public function getCrawlerForField(CustomField $field, $locale = 'en')
+ {
+ $kernel = static::$kernel;
+
+ //check a kernel is accessible
+ $customFieldsGroup = $this->createMock('Chill\CustomFieldsBundle\Entity\CustomFieldsGroup');
+ $customFieldsGroup->expects($this->once())
+ ->method('getActiveCustomFields')
+ ->will($this->returnValue(array($field)));
+
+ $request = $this->createMock('Symfony\Component\HttpFoundation\Request');
+ $request->expects($this->any())
+ ->method('getLocale')
+ ->will($this->returnValue($locale));
+
+ $kernel->getContainer()->get('request_stack')->push($request);
+
+ $builder = $kernel->getContainer()->get('form.factory')->createBuilder();
+ $form = $builder->add('tested', 'custom_field',
+ array('group' => $customFieldsGroup))
+ ->getForm();
+
+ $kernel->getContainer()->get('twig.loader')
+ ->addPath($kernel->getContainer()->getParameter('kernel.root_dir').
+ '/Resources/views/', $namespace = 'test');
+
+ $content = $kernel
+ ->getContainer()->get('templating')
+ ->render('@test/CustomField/simple_form_render.html.twig', array(
+ 'form' => $form->createView(),
+ 'inputKeys' => array('tested')
+ ));
+
+ $crawler = new Crawler();
+ $crawler->addHtmlContent($content);
+
+ return $crawler;
+ }
+}
diff --git a/src/Bundle/ChillCustomFields/Tests/CustomFields/CustomFieldsChoiceTest.php b/src/Bundle/ChillCustomFields/Tests/CustomFields/CustomFieldsChoiceTest.php
new file mode 100644
index 000000000..28351711c
--- /dev/null
+++ b/src/Bundle/ChillCustomFields/Tests/CustomFields/CustomFieldsChoiceTest.php
@@ -0,0 +1,468 @@
+
+ *
+ * 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 Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
+use Chill\CustomFieldsBundle\Entity\CustomField;
+use Chill\CustomFieldsBundle\CustomFields\CustomFieldChoice;
+
+/**
+ * This class cover the test of CustomFieldChoice.
+ *
+ * Function currently covered:
+ *
+ * - deserialize
+ *
+ * @author Julien Fastré
+ */
+class CustomFieldsChoiceTest extends KernelTestCase
+{
+
+ /**
+ *
+ * @var \Chill\CustomFieldsBundle\Service\CustomFieldProvider
+ */
+ private $cfProvider;
+
+ /**
+ *
+ * @var \Chill\CustomFieldsBundle\CustomFields\CustomFieldChoice
+ */
+ private $cfChoice;
+
+ public function setUp()
+ {
+ static::bootKernel();
+
+ $this->cfProvider = static::$kernel->getContainer()
+ ->get('chill.custom_field.provider');
+ $this->cfChoice = $this->cfProvider->getCustomFieldByType('choice');
+ }
+
+ public function tearDown()
+ {
+ parent::tearDown();
+ }
+
+ /**
+ *
+ * @param array $options
+ * @return CustomField
+ */
+ private function generateCustomField($options)
+ {
+ return (new CustomField())
+ ->setActive(true)
+ ->setSlug('slug')
+ ->setOptions($options)
+ ->setType('choice')
+ ;
+ }
+
+ /////////////////////////////////////////
+ //
+ // test function deserialize
+ //
+ ////////////////////////////////////////
+
+ /**
+ * Test if the representation of the data is deserialized to a single text.
+ *
+ * If the value is in _other, the _other value should not be returned.
+ *
+ * @param type $data
+ * @dataProvider serializedRepresentationDataProvider
+ */
+ public function testDeserializeSingleChoiceWithoutOther($data)
+ {
+ $customField = $this->generateCustomField(array(
+ CustomFieldChoice::ALLOW_OTHER => false,
+ CustomFieldChoice::MULTIPLE => false
+ ));
+
+ $deserialized = $this->cfChoice->deserialize($data, $customField);
+
+ $this->assertSame('my-value', $deserialized);
+ }
+
+
+ public function testDeserializeSingleChoiceWithoutOtherDataIsNull()
+ {
+ $customField = $this->generateCustomField(array(
+ CustomFieldChoice::ALLOW_OTHER => false,
+ CustomFieldChoice::MULTIPLE => false
+ ));
+
+ $deserialized = $this->cfChoice->deserialize(null, $customField);
+ $this->assertSame(null, $deserialized);
+
+ $deserialized = $this->cfChoice->deserialize('', $customField);
+ $this->assertSame('', $deserialized);
+
+ $deserialized = $this->cfChoice->deserialize(array(null), $customField);
+ $this->assertSame(null, $deserialized);
+
+ $deserialized = $this->cfChoice->deserialize(array('_other' => null, '_choices' => null), $customField);
+ $this->assertSame(null, $deserialized);
+
+ $deserialized = $this->cfChoice->deserialize(array('_other' => null, '_choices' => ''), $customField);
+ $this->assertSame('', $deserialized);
+
+ $deserialized = $this->cfChoice->deserialize(array(null), $customField);
+ $this->assertSame(null, $deserialized);
+ }
+
+ /**
+ * Test if the representation of the data is deserialized to a single text
+ * with an "allow_other" field.
+ *
+ * If the value is in _other, the _other value should be in the _other field.
+ *
+ * @param type $data
+ * @dataProvider serializedRepresentationDataProvider
+ */
+ public function testDeserializeSingleChoiceWithOther($data)
+ {
+ $customField = $this->generateCustomField(array(
+ CustomFieldChoice::ALLOW_OTHER => true,
+ CustomFieldChoice::MULTIPLE => false
+ ));
+
+ $deserialized = $this->cfChoice->deserialize($data, $customField);
+
+ $this->assertSame(array('_other' => '', '_choices' => 'my-value'), $deserialized);
+ }
+
+ /**
+ * Other cases :
+ *
+ * - Test if the selected value is '_other
+ * - Test with null data
+ *
+ * @param type $data
+ */
+ public function testDeserializeSingleChoiceWithOtherOtherCases()
+ {
+ $customField = $this->generateCustomField(array(
+ CustomFieldChoice::ALLOW_OTHER => true,
+ CustomFieldChoice::MULTIPLE => false
+ ));
+
+ // from a single to a single
+ $data = array('_other' => 'something', '_choices' => '_other');
+ $deserialized = $this->cfChoice->deserialize($data, $customField);
+ $this->assertSame(array('_other' => 'something', '_choices' => '_other'), $deserialized);
+
+
+ // from a multiple to a single
+ $data = array('_other' => 'something', '_choices' => array('some', '_other'));
+ $deserialized = $this->cfChoice->deserialize($data, $customField);
+ $this->assertSame(array('_other' => 'something', '_choices' => '_other'), $deserialized);
+
+ //test with null data
+ //from a single to a single :
+ $data = array('_other' => 'something', '_choices' => null);
+ $deserialized = $this->cfChoice->deserialize($data, $customField);
+ $this->assertSame(array('_other' => 'something', '_choices' => null), $deserialized);
+
+ $data = array('_other' => 'something', '_choices' => '');
+ $deserialized = $this->cfChoice->deserialize($data, $customField);
+ $this->assertSame(array('_other' => 'something', '_choices' => ''), $deserialized);
+
+ // from a multiple to a signle
+ $data = array('_other' => 'something', '_choices' => array());
+ $deserialized = $this->cfChoice->deserialize($data, $customField);
+ $this->assertSame(array('_other' => 'something', '_choices' => ''), $deserialized);
+
+ $data = array('_other' => 'something', '_choices' => array(''));
+ $deserialized = $this->cfChoice->deserialize($data, $customField);
+ $this->assertSame(array('_other' => 'something', '_choices' => ''), $deserialized);
+
+ $deserialized = $this->cfChoice->deserialize(array('_other' => null, '_choices' => null), $customField);
+ $this->assertSame(array('_other' => '', '_choices' => null), $deserialized);
+
+ $deserialized = $this->cfChoice->deserialize(array('_other' => null, '_choices' => ''), $customField);
+ $this->assertSame(array('_other' => '', '_choices' => ''), $deserialized);
+
+ $deserialized = $this->cfChoice->deserialize(array(null), $customField);
+ $this->assertSame(array('_other' => '', '_choices' => null), $deserialized);
+
+ }
+
+ /**
+ * Test if the representation of the data is deserialized to an array text
+ * with an "allow_other" field.
+ *
+ * This test does not covers the case when the selected value is `_other`
+ *
+ * @param type $data
+ * @dataProvider serializedRepresentationDataProvider
+ */
+ public function testDeserializeMultipleChoiceWithOther($data)
+ {
+ $customField = $this->generateCustomField(array(
+ CustomFieldChoice::ALLOW_OTHER => true,
+ CustomFieldChoice::MULTIPLE => true
+ ));
+
+ $deserialized = $this->cfChoice->deserialize($data, $customField);
+
+ $this->assertSame(array('_other' => '', '_choices' => array('my-value')),
+ $deserialized);
+ }
+
+ /**
+ * Test if the representation of the data is deserialized to an array text
+ * with an "allow_other" field.
+ *
+ * This test covers :
+ * - the case when the selected value is `_other`
+ * - result is null
+ *
+ * @param type $data
+ */
+ public function testDeserializeMultipleChoiceWithOtherOtherCases()
+ {
+ $customField = $this->generateCustomField(array(
+ CustomFieldChoice::ALLOW_OTHER => true,
+ CustomFieldChoice::MULTIPLE => true
+ ));
+
+ // selected value is _other
+ // from single to multiple
+ $data = array('_other' => 'something', '_choices' => '_other');
+ $deserialized = $this->cfChoice->deserialize($data, $customField);
+
+ $this->assertSame(array('_other' => 'something', '_choices' => array('_other')),
+ $deserialized);
+
+ // from multiple to multiple
+ $data = array('_other' => 'something', '_choices' => array('_other', 'something'));
+ $deserialized = $this->cfChoice->deserialize($data, $customField);
+
+ $this->assertSame(array('_other' => 'something', '_choices' => array('_other', 'something')),
+ $deserialized);
+
+ // test with null value
+ // from single to multiple
+ $data = array('_other' => '', '_choices' => '');
+ $deserialized = $this->cfChoice->deserialize($data, $customField);
+
+ $this->assertSame(array('_other' => '', '_choices' => array('')),
+ $deserialized);
+
+ // from multiple to multiple
+ $data = array('_other' => '', '_choices' => array());
+ $deserialized = $this->cfChoice->deserialize($data, $customField);
+
+ $this->assertSame(array('_other' => '', '_choices' => array()),
+ $deserialized);
+
+ $deserialized = $this->cfChoice->deserialize(array('_other' => null, '_choices' => null), $customField);
+ $this->assertSame(array('_other' => '', '_choices' => array(null)), $deserialized);
+
+ $deserialized = $this->cfChoice->deserialize(array('_other' => null, '_choices' => ''), $customField);
+ $this->assertSame(array('_other' => '', '_choices' => array('')), $deserialized);
+
+ $deserialized = $this->cfChoice->deserialize(array(null), $customField);
+ $this->assertSame(array('_other' => '', '_choices' => array(null)), $deserialized);
+ }
+
+ /**
+ * Test if the representation of the data is deserialized to an array text
+ * **without** an "allow_other" field.
+ *
+ *
+ * @param type $data
+ * @dataProvider serializedRepresentationDataProvider
+ */
+ public function testDeserializeMultipleChoiceWithoutOther($data)
+ {
+ $customField = $this->generateCustomField(array(
+ CustomFieldChoice::ALLOW_OTHER => false,
+ CustomFieldChoice::MULTIPLE => true
+ ));
+
+ $deserialized = $this->cfChoice->deserialize($data, $customField);
+
+ $this->assertSame(array('my-value'), $deserialized);
+ }
+
+ /**
+ * Test if the representation of the data is deserialized to an array text
+ * **without** an "allow_other" field.
+ *
+ * Covered cases :
+ * - NULL values
+ *
+ *
+ * @param type $data
+ */
+ public function testDeserializeMultipleChoiceWithoutOtherOtherCases()
+ {
+ $customField = $this->generateCustomField(array(
+ CustomFieldChoice::ALLOW_OTHER => false,
+ CustomFieldChoice::MULTIPLE => true
+ ));
+
+ // from single to multiple
+ $data = 'my-value';
+ $deserialized = $this->cfChoice->deserialize($data, $customField);
+ $this->assertSame(array('my-value'), $deserialized);
+
+ // from multiple to multiple
+ $data = array('my-value');
+ $deserialized = $this->cfChoice->deserialize($data, $customField);
+ $this->assertSame(array('my-value'), $deserialized);
+
+ // from multiple
+ $data = array(null);
+ $deserialized = $this->cfChoice->deserialize($data, $customField);
+ $this->assertSame(array(null), $deserialized);
+
+ $data = array('_other' => null, '_choices' => array(null));
+ $deserialized = $this->cfChoice->deserialize($data, $customField);
+ $this->assertSame(array(null), $deserialized);
+
+ //from single
+ $data = array('_other' => null, '_choices' => null);
+ $deserialized = $this->cfChoice->deserialize($data, $customField);
+ $this->assertSame(array(null), $deserialized);
+ }
+
+ public function serializedRepresentationDataProvider()
+ {
+ return array(
+ array(
+ // multiple => false, allow_other => false
+ 'my-value'
+ ),
+ array(
+ // multiple => true, allow_ther => false
+ array('my-value')
+ ),
+ array(
+ // multiple => false, allow_other => true, current value not in other
+ array('_other' => '', '_choices' => 'my-value')
+ ),
+ array(
+ // multiple => true, allow_other => true, current value not in other
+ array('_other' => '', '_choices'=> array('my-value'))
+ ),
+ );
+ }
+
+
+
+ /////////////////////////////////////////
+ //
+ // test function isEmptyValue
+ //
+ ////////////////////////////////////////
+
+ /**
+ * test the not empty with the not-empty data provider
+ *
+ * @param mixed $data
+ * @dataProvider serializedRepresentationDataProvider
+ */
+ public function testIsEmptyValueNotEmpty($data)
+ {
+ $customField = $this->generateCustomField(array(
+ CustomFieldChoice::ALLOW_OTHER => false,
+ CustomFieldChoice::MULTIPLE => true
+ ));
+
+ $deserialized = $this->cfChoice->deserialize($data, $customField);
+ $isEmpty = $this->cfChoice->isEmptyValue($deserialized, $customField);
+
+ $this->assertFalse($isEmpty);
+ }
+
+ /**
+ *
+ * @dataProvider emptyDataProvider
+ * @param mixed $data deserialized data
+ */
+ public function testIsEmptyValueEmpty($data)
+ {
+ $customField = $this->generateCustomField(array(
+ CustomFieldChoice::ALLOW_OTHER => false,
+ CustomFieldChoice::MULTIPLE => true
+ ));
+
+ $isEmpty = $this->cfChoice->isEmptyValue($data, $customField);
+
+ $this->assertTrue($isEmpty);
+ }
+
+ /**
+ * provide empty data in different possible representations.
+ * Those data are supposed to be deserialized.
+ *
+ * @return array
+ */
+ public function emptyDataProvider()
+ {
+ return array(
+ // 0
+ array(
+ // signle
+ ''
+ ),
+ // 1
+ array(
+ // single
+ null
+ ),
+ // 2
+ array(
+ // signle with allow other
+ array('_other' => 'something', '_choices' => '')
+ ),
+ // 3
+ array(
+ // multiple
+ array()
+ ),
+ // 4
+ array(
+ // multiple with allow other
+ array('_other' => 'something', '_choices' => array())
+ ),
+ // 5
+ array(
+ // multiple with allow other
+ array('_other' => '', '_choices' => array())
+ ),
+ // 6
+ array(
+ // empty
+ array('_other' => null, '_choices' => null)
+ ),
+ // 7
+ array(
+ // empty
+ array(null)
+ )
+ );
+ }
+
+}
diff --git a/src/Bundle/ChillCustomFields/Tests/CustomFields/CustomFieldsNumberTest.php b/src/Bundle/ChillCustomFields/Tests/CustomFields/CustomFieldsNumberTest.php
new file mode 100644
index 000000000..a206be5c7
--- /dev/null
+++ b/src/Bundle/ChillCustomFields/Tests/CustomFields/CustomFieldsNumberTest.php
@@ -0,0 +1,191 @@
+
+ *
+ * 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;
+use Chill\CustomFieldsBundle\Entity\CustomFieldsGroup;
+use Chill\CustomFieldsBundle\Form\CustomFieldsGroupType;
+
+/**
+ * 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()));
+ }
+
+ public function testRequiredFieldIsFalse()
+ {
+ $cf = $this->createCustomFieldNumber(array(
+ 'min' => 1000,
+ 'max' => null,
+ 'scale' => null,
+ 'post_text' => null
+ ));
+ $cf->setRequired(false);
+
+ $cfGroup = (new \Chill\CustomFieldsBundle\Entity\CustomFieldsGroup())
+ ->addCustomField($cf);
+
+ $form = static::$kernel->getContainer()->get('form.factory')
+ ->createBuilder(\Chill\CustomFieldsBundle\Form\Type\CustomFieldType::class, array(), array(
+ 'group' => $cfGroup
+ ))
+ ->getForm();
+
+ $this->assertFalse($form['default']->isRequired(),
+ "The field should not be required");
+ }
+
+ public function testRequiredFieldIsTrue()
+ {
+ $cf = $this->createCustomFieldNumber(array(
+ 'min' => 1000,
+ 'max' => null,
+ 'scale' => null,
+ 'post_text' => null
+ ));
+ $cf->setRequired(true);
+
+ $cfGroup = (new \Chill\CustomFieldsBundle\Entity\CustomFieldsGroup())
+ ->addCustomField($cf);
+
+ $form = static::$kernel->getContainer()->get('form.factory')
+ ->createBuilder(\Chill\CustomFieldsBundle\Form\Type\CustomFieldType::class, array(), array(
+ 'group' => $cfGroup
+ ))
+ ->getForm();
+
+ $this->assertTrue($form['default']->isRequired(),
+ "The field should be required");
+ }
+
+
+}
diff --git a/src/Bundle/ChillCustomFields/Tests/CustomFields/CustomFieldsTextTest.php b/src/Bundle/ChillCustomFields/Tests/CustomFields/CustomFieldsTextTest.php
new file mode 100644
index 000000000..ad031e4a1
--- /dev/null
+++ b/src/Bundle/ChillCustomFields/Tests/CustomFields/CustomFieldsTextTest.php
@@ -0,0 +1,106 @@
+.
+ */
+
+namespace Chill\CustomFieldsBundle\Tests;
+
+use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
+use Chill\CustomFieldsBundle\Entity\CustomField;
+use Chill\CustomFieldsBundle\CustomFields\CustomFieldText;
+use Symfony\Component\HttpFoundation\RequestStack;
+use Symfony\Component\DomCrawler\Crawler;
+use Chill\CustomFieldsBundle\Tests\CustomFieldTestHelper;
+
+/**
+ *
+ *
+ * @author Julien Fastré
+ */
+class CustomFieldsTextTest extends WebTestCase
+{
+ /**
+ *
+ * @var \Chill\CustomFieldsBundle\Service\CustomFieldProvider
+ */
+ private $customFieldProvider;
+
+ use CustomFieldTestHelper;
+
+ public function setUp()
+ {
+ static::bootKernel();
+ $this->customFieldProvider = static::$kernel->getContainer()
+ ->get('chill.custom_field.provider');
+ }
+
+ public function testCustomFieldsTextExists()
+ {
+ $customField = $this->customFieldProvider->getCustomFieldByType('text');
+
+ $this->assertInstanceOf('Chill\CustomFieldsBundle\CustomFields\CustomFieldInterface',
+ $customField);
+ $this->assertInstanceOf('Chill\CustomFieldsBundle\CustomFields\CustomFieldText',
+ $customField);
+ }
+
+ public function testPublicFormRenderingLengthLessThan256()
+ {
+ $customField = new CustomField();
+ $customField->setType('text')
+ ->setOptions(array(CustomFieldText::MAX_LENGTH => 255))
+ ->setSlug('slug')
+ ->setOrdering(10)
+ ->setActive(true)
+ ->setName(array('en' => 'my label'));
+
+ $crawler = $this->getCrawlerForField($customField);
+
+ $this->assertCount(1, $crawler->filter("input[type=text]"));
+ $this->assertCount(1, $crawler->filter("label:contains('my label')"));
+ }
+
+ public function testPublicFormRenderingLengthMoreThan25()
+ {
+ $customField = new CustomField();
+ $customField->setType('text')
+ ->setOptions(array(CustomFieldText::MAX_LENGTH => 256))
+ ->setSlug('slug')
+ ->setOrdering(10)
+ ->setActive(true)
+ ->setName(array('en' => 'my label'));
+
+ $crawler = $this->getCrawlerForField($customField);
+
+ $this->assertCount(1, $crawler->filter("textarea"));
+ $this->assertCount(1, $crawler->filter("label:contains('my label')"));
+ }
+
+ public function testFormTextNew()
+ {
+ $client = static::createClient();
+
+ $crawler = $client->request('GET', '/fr/admin/customfield/new?type=text');
+
+ $this->assertTrue($client->getResponse()->isSuccessful());
+
+ $form = $crawler->selectButton('custom_field_choice_submit')->form();
+ $this->assertTrue($form->has('custom_field_choice[options][maxLength]'));
+ }
+
+}
diff --git a/src/Bundle/ChillCustomFields/Tests/Form/Extension/PostTextIntegerExtensionTest.php b/src/Bundle/ChillCustomFields/Tests/Form/Extension/PostTextIntegerExtensionTest.php
new file mode 100644
index 000000000..5f395abe5
--- /dev/null
+++ b/src/Bundle/ChillCustomFields/Tests/Form/Extension/PostTextIntegerExtensionTest.php
@@ -0,0 +1,59 @@
+
+ *
+ * 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\CustomFields\Tests\Form\Extension;
+
+use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
+use Symfony\Component\Form\Extension\Core\Type\IntegerType;
+
+/**
+ * Test the post-text extension
+ *
+ * @author Julien Fastré
+ */
+class PostTextIntegerExtensionTest extends KernelTestCase
+{
+ /**
+ *
+ * @var \Symfony\Component\Form\FormBuilderInterface
+ */
+ private $formBuilder;
+
+ public function setUp()
+ {
+ self::bootKernel();
+
+ $container = self::$kernel->getContainer();
+
+ $this->formBuilder = $container->get('form.factory')
+ ->createBuilder('form', null);
+ }
+
+ public function testCreateView()
+ {
+ $form = $this->formBuilder->add('test', IntegerType::class, array(
+ 'post_text' => 'my text'
+ ))->getForm();
+
+ $view = $form->createView();
+
+ $this->assertEquals('my text', $view['test']->vars['post_text']);
+ }
+
+}
diff --git a/src/Bundle/ChillCustomFields/Tests/Form/Extension/PostTextNumberExtensionTest.php b/src/Bundle/ChillCustomFields/Tests/Form/Extension/PostTextNumberExtensionTest.php
new file mode 100644
index 000000000..86098ac21
--- /dev/null
+++ b/src/Bundle/ChillCustomFields/Tests/Form/Extension/PostTextNumberExtensionTest.php
@@ -0,0 +1,59 @@
+
+ *
+ * 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\CustomFields\Tests\Form\Extension;
+
+use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
+use Symfony\Component\Form\Extension\Core\Type\NumberType;
+
+/**
+ * Test the post-text extension
+ *
+ * @author Julien Fastré
+ */
+class PostTextNumberExtensionTest extends KernelTestCase
+{
+ /**
+ *
+ * @var \Symfony\Component\Form\FormBuilderInterface
+ */
+ private $formBuilder;
+
+ public function setUp()
+ {
+ self::bootKernel();
+
+ $container = self::$kernel->getContainer();
+
+ $this->formBuilder = $container->get('form.factory')
+ ->createBuilder('form', null);
+ }
+
+ public function testCreateView()
+ {
+ $form = $this->formBuilder->add('test', NumberType::class, array(
+ 'post_text' => 'my text'
+ ))->getForm();
+
+ $view = $form->createView();
+
+ $this->assertEquals('my text', $view['test']->vars['post_text']);
+ }
+
+}
diff --git a/src/Bundle/ChillCustomFields/Tests/Routing/RoutingLoaderTest.php b/src/Bundle/ChillCustomFields/Tests/Routing/RoutingLoaderTest.php
new file mode 100644
index 000000000..290cce37b
--- /dev/null
+++ b/src/Bundle/ChillCustomFields/Tests/Routing/RoutingLoaderTest.php
@@ -0,0 +1,45 @@
+
+ *
+ * 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 Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
+use Symfony\Component\HttpFoundation\Response;
+
+/**
+ * Test that routes are correctly loaded
+ *
+ * @author Julien Fastré
+ */
+class RoutingLoaderTest extends WebTestCase
+{
+ public function testRoutesAreLoaded()
+ {
+ $client = static::createClient();
+
+ $client->request('GET','/fr/admin/customfield/');
+
+ $this->assertEquals(
+ Response::HTTP_OK,
+ $client->getResponse()->getStatusCode()
+ );
+
+ }
+}
diff --git a/src/Bundle/ChillCustomFields/Tests/Service/CustomFieldsHelperTest.php b/src/Bundle/ChillCustomFields/Tests/Service/CustomFieldsHelperTest.php
new file mode 100644
index 000000000..fc7276cf7
--- /dev/null
+++ b/src/Bundle/ChillCustomFields/Tests/Service/CustomFieldsHelperTest.php
@@ -0,0 +1,92 @@
+
+ *
+ * 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\CustomFields\Tests\Service;
+
+use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
+use Chill\CustomFieldsBundle\Service\CustomFieldsHelper;
+use Chill\CustomFieldsBundle\Entity\CustomField;
+
+/**
+ * Tests for custom fields helper
+ *
+ * @author Julien Fastré
+ * @author Champs Libres
+ */
+class CustomFieldsHelperTest extends KernelTestCase
+{
+ /**
+ *
+ * @var CustomFieldsHelper
+ */
+ private $cfHelper;
+
+ /**
+ *
+ * @var CustomField
+ */
+ private $randomCFText;
+
+ public function setUp()
+ {
+ self::bootKernel();
+
+ $container = self::$kernel->getContainer();
+
+ $this->cfHelper = $container->get('chill.custom_field.helper');
+
+ $this->randomCFText = (new CustomField())
+ ->setSlug('my-slug')
+ ->setActive(true)
+ ->setName(array('fr' => 'my cf'))
+ ->setOptions(array('maxLength' => 1000))
+ ->setType('text')
+ ;
+ }
+
+ public function testRenderCustomField()
+ {
+ $data = array(
+ $this->randomCFText->getSlug() => 'Sample text'
+ );
+
+ $text = $this->cfHelper->renderCustomField($data, $this->randomCFText);
+
+ $this->assertContains('Sample text', $text);
+ }
+
+ public function testIsEmptyValue()
+ {
+ // not empty value
+ $data = array(
+ $this->randomCFText->getSlug() => 'Sample text'
+ );
+
+ $this->assertFalse($this->cfHelper->isEmptyValue($data, $this->randomCFText));
+
+ //empty value
+ $data = array(
+ $this->randomCFText->getSlug() => ''
+ );
+
+ $this->assertTrue($this->cfHelper->isEmptyValue($data, $this->randomCFText));
+ }
+
+}
diff --git a/src/Bundle/ChillCustomFields/Tests/Templating/Twig/CustomFieldRenderingTwigTest.php b/src/Bundle/ChillCustomFields/Tests/Templating/Twig/CustomFieldRenderingTwigTest.php
new file mode 100644
index 000000000..2f6f51df7
--- /dev/null
+++ b/src/Bundle/ChillCustomFields/Tests/Templating/Twig/CustomFieldRenderingTwigTest.php
@@ -0,0 +1,128 @@
+
+ *
+ * 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\CustomFields\Tests\Templating\Twig;
+
+use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
+use Chill\CustomFieldsBundle\Templating\Twig\CustomFieldRenderingTwig;
+use Chill\CustomFieldsBundle\Entity\CustomField;
+use Chill\CustomFieldsBundle\Service\CustomFieldProvider;
+
+/**
+ * Test the rendering of twig function which renders custom fields
+ *
+ * @author Julien Fastré
+ * @author Champs Libres
+ */
+class CustomFieldRenderingTwigTest extends KernelTestCase
+{
+ /**
+ *
+ * @var CustomFieldRenderingTwig
+ */
+ private $cfRendering;
+
+ /**
+ *
+ * @var CustomFieldProvider
+ */
+ private $cfProvider;
+
+ public function setUp()
+ {
+ self::bootKernel();
+ $this->cfRendering = self::$kernel->getContainer()
+ ->get('chill.custom_field.twig.custom_fields_rendering')
+ ;
+
+ $this->cfProvider = self::$kernel->getContainer()
+ ->get('chill.custom_field.provider');
+
+ // set locale to fr
+ $prophet = new \Prophecy\Prophet;
+ $request = $prophet->prophesize();
+ $request->willExtend('Symfony\Component\HttpFoundation\Request');
+ $request->getLocale()->willReturn('fr');
+ self::$kernel->getContainer()->get('request_stack')
+ ->push($request->reveal());
+ }
+
+ /**
+ *
+ * @return CustomField
+ */
+ private function getSimpleCustomFieldText()
+ {
+ return (new CustomField())
+ ->setSlug('test')
+ ->setName(array('fr' => 'Test'))
+ ->setType('text')
+ ->setOrdering(10)
+ ->setOptions(array("maxLength" => 255))
+ ->setActive(true)
+ ;
+ }
+
+ public function testLabelRendering()
+ {
+ $cf = $this->getSimpleCustomFieldText();
+
+ $text = $this->cfRendering->renderLabel($cf);
+
+ $this->assertContains('Test', $text,
+ "The rendering text should contains the 'test' text");
+ }
+
+ public function testWidgetRendering()
+ {
+ $cf = $this->getSimpleCustomFieldText();
+ $fields = array(
+ 'test' => "My tailor is rich"
+ );
+
+ $text = $this->cfRendering->renderWidget($fields, $cf);
+
+ $this->assertContains('My tailor is rich', $text,
+ "The rendering text should contains the 'test' text");
+ }
+
+ public function testIsEmpty()
+ {
+ $cf = $this->getSimpleCustomFieldText();
+
+ // value is not empty
+ $fields = array(
+ 'test' => "My tailor is rich"
+ );
+
+ $result = $this->cfRendering->isEmptyValue($fields, $cf);
+
+ $this->assertFalse($result);
+
+ // value is empty
+ $fields = array(
+ 'text' => ''
+ );
+
+ $result = $this->cfRendering->isEmptyValue($fields, $cf);
+
+ $this->assertTrue($result);
+ }
+}
diff --git a/src/Bundle/ChillCustomFields/Tests/Templating/Twig/CustomFieldsGroupRenderingTwigTest.php b/src/Bundle/ChillCustomFields/Tests/Templating/Twig/CustomFieldsGroupRenderingTwigTest.php
new file mode 100644
index 000000000..d1cbe0209
--- /dev/null
+++ b/src/Bundle/ChillCustomFields/Tests/Templating/Twig/CustomFieldsGroupRenderingTwigTest.php
@@ -0,0 +1,130 @@
+
+ *
+ * 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\CustomFields\Tests\Templating\Twig;
+
+use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
+use Chill\CustomFieldsBundle\Templating\Twig\CustomFieldsGroupRenderingTwig;
+use Chill\CustomFieldsBundle\Entity\CustomField;
+use Chill\CustomFieldsBundle\Service\CustomFieldProvider;
+use Chill\CustomFieldsBundle\Entity\CustomFieldsGroup;
+
+/**
+ * Test the rendering of a custom fields group through
+ * the `chill_custom_fields_group_widget`
+ *
+ * @author Julien Fastré
+ * @author Champs Libres
+ */
+class CustomFieldsGroupRenderingTwigTest extends KernelTestCase
+{
+ /**
+ *
+ * @var CustomFieldsGroupRenderingTwig
+ */
+ private $cfRendering;
+
+ /**
+ *
+ * @var CustomFieldProvider
+ */
+ private $cfProvider;
+
+ public function setUp()
+ {
+ self::bootKernel();
+ $this->cfRendering = self::$kernel->getContainer()
+ ->get('chill.custom_field.twig.custom_fields_group_rendering')
+ ;
+
+ $this->cfProvider = self::$kernel->getContainer()
+ ->get('chill.custom_field.provider');
+
+ // set locale to fr
+ $prophet = new \Prophecy\Prophet;
+ $request = $prophet->prophesize();
+ $request->willExtend('Symfony\Component\HttpFoundation\Request');
+ $request->getLocale()->willReturn('fr');
+ self::$kernel->getContainer()->get('request_stack')
+ ->push($request->reveal());
+ }
+
+ /**
+ *
+ * @return CustomField
+ */
+ private function getSimpleCustomFieldText($slug, $name)
+ {
+ return (new CustomField())
+ ->setSlug($slug)
+ ->setName(array('fr' => $name))
+ ->setType('text')
+ ->setOrdering(10)
+ ->setOptions(array("maxLength" => 255))
+ ->setActive(true)
+ ;
+ }
+
+ /**
+ *
+ * @return CustomFieldsGroup
+ */
+ private function getCustomFieldsGroup()
+ {
+ return (new CustomFieldsGroup())
+ ->setEntity('\Dummy')
+ ->setName(array("fr" => "A cf group"))
+ ->addCustomField($this->getSimpleCustomFieldText("horses", "Do you like horses ?."))
+ ->addCustomField($this->getSimpleCustomFieldText("sure", "Are you sure ?"))
+ ;
+ }
+
+ public function testRenderingWidget()
+ {
+ $cfGroup = $this->getCustomFieldsGroup();
+
+ $text = $this->cfRendering->renderWidget(array(
+ 'horses' => 'I like horses',
+ 'sure' => 'Yes !'
+ ), $cfGroup);
+
+ $this->assertContains('Do you like horses', $text);
+ $this->assertContains('I like horses', $text);
+ $this->assertContains('Are you sure', $text);
+ $this->assertContains('Yes', $text);
+ }
+
+ public function testRenderingWidgetDoNotShowEmpty()
+ {
+ $cfGroup = $this->getCustomFieldsGroup();
+ $cfGroup->addCustomField($this->getSimpleCustomFieldText('empty', 'Do not answer'));
+
+ $text = $this->cfRendering->renderWidget(array(
+ 'horses' => 'I like horses',
+ 'sure' => 'Yes !'
+ ), $cfGroup, 'html', array('show_empty' => false));
+
+ $this->assertContains('Do you like horses', $text);
+ $this->assertContains('I like horses', $text);
+ $this->assertContains('Are you sure', $text);
+ $this->assertContains('Yes', $text);
+ $this->assertNotContains('Do not answer', $text);
+ }
+}
diff --git a/src/Bundle/ChillCustomFields/Tests/bootstrap.php b/src/Bundle/ChillCustomFields/Tests/bootstrap.php
new file mode 100644
index 000000000..9211155e5
--- /dev/null
+++ b/src/Bundle/ChillCustomFields/Tests/bootstrap.php
@@ -0,0 +1,8 @@
+addSql("CREATE SEQUENCE CustomField_id_seq INCREMENT BY 1 MINVALUE 1 START 1;");
+ $this->addSql("CREATE SEQUENCE CustomFieldsDefaultGroup_id_seq INCREMENT BY 1 MINVALUE 1 START 1;");
+ $this->addSql("CREATE SEQUENCE CustomFieldsGroup_id_seq INCREMENT BY 1 MINVALUE 1 START 1;");
+ $this->addSql("CREATE TABLE CustomField (id INT NOT NULL, name JSON NOT NULL, slug VARCHAR(255) NOT NULL, type VARCHAR(255) NOT NULL, active BOOLEAN NOT NULL, ordering DOUBLE PRECISION NOT NULL, options JSON NOT NULL, customFieldGroup_id INT DEFAULT NULL, PRIMARY KEY(id));");
+ $this->addSql("CREATE INDEX IDX_40FB5D6DFEC418B ON CustomField (customFieldGroup_id);");
+ $this->addSql("CREATE TABLE CustomFieldsDefaultGroup (id INT NOT NULL, entity VARCHAR(255) NOT NULL, customFieldsGroup_id INT DEFAULT NULL, PRIMARY KEY(id));");
+ $this->addSql("CREATE INDEX IDX_286DC95DF53B66 ON CustomFieldsDefaultGroup (customFieldsGroup_id);");
+ $this->addSql("CREATE UNIQUE INDEX unique_entity ON CustomFieldsDefaultGroup (entity);");
+ $this->addSql("CREATE TABLE CustomFieldsGroup (id INT NOT NULL, name JSON NOT NULL, entity VARCHAR(255) NOT NULL, PRIMARY KEY(id));");
+ $this->addSql("ALTER TABLE CustomField ADD CONSTRAINT FK_40FB5D6DFEC418B FOREIGN KEY (customFieldGroup_id) REFERENCES CustomFieldsGroup (id) NOT DEFERRABLE INITIALLY IMMEDIATE;");
+ $this->addSql("ALTER TABLE CustomFieldsDefaultGroup ADD CONSTRAINT FK_286DC95DF53B66 FOREIGN KEY (customFieldsGroup_id) REFERENCES CustomFieldsGroup (id) NOT DEFERRABLE INITIALLY IMMEDIATE;");
+
+ }
+
+ public function down(Schema $schema): void
+ {
+ // this down() migration is auto-generated, please modify it to your needs
+
+ }
+}
diff --git a/src/Bundle/ChillCustomFields/migrations/Version20150224164531.php b/src/Bundle/ChillCustomFields/migrations/Version20150224164531.php
new file mode 100644
index 000000000..4034dba80
--- /dev/null
+++ b/src/Bundle/ChillCustomFields/migrations/Version20150224164531.php
@@ -0,0 +1,22 @@
+addSql('ALTER TABLE customfieldsgroup ADD options JSON DEFAULT \'{}\'::json');
+ }
+
+ public function down(Schema $schema): void
+ {
+ $this->addSql('ALTER TABLE CustomFieldsGroup DROP options');
+ }
+}
diff --git a/src/Bundle/ChillCustomFields/migrations/Version20151210155904.php b/src/Bundle/ChillCustomFields/migrations/Version20151210155904.php
new file mode 100644
index 000000000..9bcd83cbc
--- /dev/null
+++ b/src/Bundle/ChillCustomFields/migrations/Version20151210155904.php
@@ -0,0 +1,35 @@
+abortIf($this->connection->getDatabasePlatform()->getName() != 'postgresql', 'Migration can only be executed safely on \'postgresql\'.');
+
+ $this->addSql('ALTER TABLE customfield ADD required BOOLEAN DEFAULT FALSE');
+ }
+
+ /**
+ * @param Schema $schema
+ */
+ public function down(Schema $schema): void
+ {
+ // this down() migration is auto-generated, please modify it to your needs
+ $this->abortIf($this->connection->getDatabasePlatform()->getName() != 'postgresql', 'Migration can only be executed safely on \'postgresql\'.');
+
+ $this->addSql('ALTER TABLE CustomField DROP required');
+
+ }
+}
diff --git a/src/Bundle/ChillCustomFields/migrations/Version20151210205610.php b/src/Bundle/ChillCustomFields/migrations/Version20151210205610.php
new file mode 100644
index 000000000..b2f332da4
--- /dev/null
+++ b/src/Bundle/ChillCustomFields/migrations/Version20151210205610.php
@@ -0,0 +1,47 @@
+abortIf($this->connection->getDatabasePlatform()->getName() != 'postgresql', 'Migration can only be executed safely on \'postgresql\'.');
+
+ $this->addSql('CREATE SEQUENCE custom_field_long_choice_options_id_seq INCREMENT BY 1 MINVALUE 1 START 1');
+ $this->addSql('CREATE TABLE custom_field_long_choice_options (id INT NOT NULL, '
+ . 'parent_id INT DEFAULT NULL, '
+ . 'key VARCHAR(15) NOT NULL, '
+ . 'text jsonb NOT NULL, '
+ . 'active boolean NOT NULL,'
+ . 'internal_key VARCHAR(50) NOT NULL DEFAULT \'\', '
+ . 'PRIMARY KEY(id))');
+ $this->addSql('CREATE INDEX IDX_14BBB8E0727ACA70 ON custom_field_long_choice_options (parent_id)');
+ $this->addSql('ALTER TABLE custom_field_long_choice_options ADD CONSTRAINT cf_long_choice_self_referencing '
+ . 'FOREIGN KEY (parent_id) REFERENCES custom_field_long_choice_options (id) '
+ . 'NOT DEFERRABLE INITIALLY IMMEDIATE');
+
+ }
+
+ /**
+ * @param Schema $schema
+ */
+ public function down(Schema $schema): void
+ {
+ // this down() migration is auto-generated, please modify it to your needs
+ $this->abortIf($this->connection->getDatabasePlatform()->getName() != 'postgresql', 'Migration can only be executed safely on \'postgresql\'.');
+
+ $this->addSql('ALTER TABLE custom_field_long_choice_options DROP CONSTRAINT cf_long_choice_self_referencing');
+ $this->addSql('DROP SEQUENCE custom_field_long_choice_options_id_seq CASCADE');
+ $this->addSql('DROP TABLE custom_field_long_choice_options');
+ }
+}
diff --git a/src/Bundle/ChillCustomFields/phpunit.xml.dist b/src/Bundle/ChillCustomFields/phpunit.xml.dist
new file mode 100644
index 000000000..85bbef1f9
--- /dev/null
+++ b/src/Bundle/ChillCustomFields/phpunit.xml.dist
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+ ./Tests
+
+
+
+
+ ./
+
+ ./Resources
+ ./Tests
+ ./vendor
+
+
+
+
+
+
+
+
diff --git a/src/Bundle/ChillCustomFields/run-server.sh b/src/Bundle/ChillCustomFields/run-server.sh
new file mode 100755
index 000000000..e12b22a86
--- /dev/null
+++ b/src/Bundle/ChillCustomFields/run-server.sh
@@ -0,0 +1 @@
+php Tests/Fixtures/App/app/console.php server:run --docroot=Tests/Fixtures/App/web/
diff --git a/src/Bundle/ChillCustomFields/translations/messages.fr.yml b/src/Bundle/ChillCustomFields/translations/messages.fr.yml
new file mode 100644
index 000000000..446176c51
--- /dev/null
+++ b/src/Bundle/ChillCustomFields/translations/messages.fr.yml
@@ -0,0 +1,101 @@
+'Not available in your language': 'Traduction pas disponible dans votre langue'
+'Other value': 'Autre valeur'
+'None': 'Pas spécifié'
+
+#customfieldsgroup rendering
+Empty data: Données vides
+No data to show: Pas de valeurs à afficher
+
+#customfieldsgroup administration
+CustomFieldsGroup list: Groupes de champs personnalisés
+CustomFieldsGroup creation: Nouveau groupe de champs personnalisés
+Entity: Entité
+"Is default ?": "Par défaut ?"
+"Some module select default groups for some usage. Example: the default person group is shown under person page.": "Certains modules sélectionnent en priorité les formulaires par défaut. Exemple: le formulaire par défaut pour une personne est affiché sur la page principale pour la personne"
+Make default: Rendre groupe par défaut
+Create a new group: Créer un nouveau groupe
+CustomFieldsGroup details: Détail du groupe de champs personnalisés
+Fields associated with this group: Champs associés à ce groupe
+Any field is currently associated with this group: Aucun champ n'est associé à ce groupe actuellement
+Create a new field: Créer un champ personnalisé
+Add a new field: Ajouter un champ personnalisé
+ordering: ordre
+label_field: label du champ
+active: actif
+No value defined for this option: Pas de valeur pour cette option
+CustomFieldsGroup edit: Edition d'un groupe de champs personnalisé
+type: type
+The custom fields group has been created: Le groupe de champs personnalisés a été créé
+The custom fields group has been updated: Le groupe de champs personnalisés a été mis à jour
+The custom fields group form contains errors: Le formulaire contient des erreurs
+The default custom fields group has been changed: Le groupe par défaut a été changé
+
+
+#menu entries
+Custom fields configuration: Champs personnalisés
+CustomFields List: Liste des champs personnalisés
+CustomFields Groups: Groupe de champs personnalisés
+
+#customfield administration
+CustomField edit: Modification d'un champ personnalisé
+CustomField creation: Nouveau champ personnalisé
+General informations: Informations générales
+Options: Options
+Custom fields group: Groupe de champ personnalisé
+Ordering: Ordre d'apparition
+Required field: Champs requis
+An answer is required: Une réponse est requise
+Any answer is required: Aucune réponse n'est requise
+Back to the group: Retour au groupe de champs personnalisé
+Slug: Identifiant textuel
+The custom field has been created: Le champ personnalisé est créé
+The custom field form contains errors: Le formulaire contient des erreurs
+The custom field has been updated: Le champ personnalisé a été mis à jour
+
+#custom field name
+choice: choix
+Title: Titre
+text: texte
+Text field: Champ texte
+Date field: Champ date
+
+#custom field choice
+Multiplicity: Multiplicité
+Multiple: Multiple
+Unique: Un seul choix possible
+Choice display: Affichage des choix
+Expanded: Choix étendus (boutons radio)
+Non expanded: Choix rassemblés
+Allow other: Autoriser une autre valeur
+No: Non
+Yes: Oui
+Other value label (empty if use by default): Label du champ "autre valeur"
+Choices: Choix
+Add an element: Ajouter un élément
+
+#custom field text
+Max length: Longueur maximum
+Box appearance: Apparence du champ
+Multiple boxes on the line: Plusieurs champs sur la ligne
+One box on the line: Un seul champ sur la ligne
+
+#custom field title
+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
+Number field: Champ nombre
+
+#custom field long choice
+Options key: Clé des options
+Choose a value: Choisissez une valeur
+Long choice field: Champ à choix pré-enregistrés
+
+#Custom field date
+Greater or equal than (expression like 1 day ago, 2 years ago, +1 month, today, tomorrow, or date with format YYYY-mm-dd): Date après (indiquer une expression PHP comme '1 day ago', '2 years ago', '+1 month', 'today', 'tomorrow', ...)
+Lesser or equal than (expression like 1 day ago, 2 years ago, +1 month, today, tomorrow, or date with format YYYY-mm-dd): Date avant (indiquer une expression PHP comme '1 day ago', '2 years ago', '+1 month', 'today', 'tomorrow', ...)
\ No newline at end of file
diff --git a/src/Bundle/ChillCustomFields/translations/messages.nl.yml b/src/Bundle/ChillCustomFields/translations/messages.nl.yml
new file mode 100644
index 000000000..5fdb3bafe
--- /dev/null
+++ b/src/Bundle/ChillCustomFields/translations/messages.nl.yml
@@ -0,0 +1,3 @@
+'Not available in your language': 'Vertaling niet mogelijk in het Nederlands'
+'Other value': 'Andere mogelijkheid'
+'None': 'Niet gespecifieerd'
\ No newline at end of file
diff --git a/src/Bundle/ChillCustomFields/translations/validators.fr.yml b/src/Bundle/ChillCustomFields/translations/validators.fr.yml
new file mode 100644
index 000000000..9747f4498
--- /dev/null
+++ b/src/Bundle/ChillCustomFields/translations/validators.fr.yml
@@ -0,0 +1,3 @@
+Characters not allowed. Only lowercase letters, numbers and "-" are allowed.: Caractères non autorisés. Seules les lettres minuscules, les nombres et le tiret ("-") sont autorisés.
+'This date must be after or equal to %date%': Cette date doit être égale ou postérieure au %date%
+'This date must be before or equal to %date%': Cette date doit être antérieure ou égale au %date%
\ No newline at end of file