diff --git a/CustomFields/CustomFieldLongChoice.php b/CustomFields/CustomFieldLongChoice.php
new file mode 100644
index 000000000..a66de9dc2
--- /dev/null
+++ b/CustomFields/CustomFieldLongChoice.php
@@ -0,0 +1,156 @@
+
+ *
+ * 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 Symfony\Component\Form\FormBuilderInterface;
+use Chill\CustomFieldsBundle\Entity\CustomField;
+use Chill\CustomFieldsBundle\EntityRepository\CustomFieldLongChoice\OptionRepository;
+use Chill\MainBundle\Templating\TranslatableStringHelper;
+use Chill\CustomFieldsBundle\Entity\CustomFieldLongChoice\Option;
+use Chill\CustomFieldsBundle\Form\DataTransformer\CustomFieldDataTransformer;
+use Symfony\Bridge\Twig\TwigEngine;
+
+/**
+ *
+ *
+ * @author Julien Fastré
+ */
+class CustomFieldLongChoice implements CustomFieldInterface
+{
+ /**
+ *
+ * @var OptionRepository
+ */
+ private $optionRepository;
+
+ /**
+ *
+ * @var TranslatableStringHelper
+ */
+ private $translatableStringHelper;
+
+ /**
+ * @var TwigEngine
+ */
+ private $templating;
+
+ const KEY = 'key';
+
+ public function __construct(OptionRepository $optionRepository,
+ TranslatableStringHelper $translatableStringHelper,
+ TwigEngine $twigEngine)
+ {
+ $this->optionRepository = $optionRepository;
+ $this->translatableStringHelper = $translatableStringHelper;
+ $this->templating = $twigEngine;
+ }
+
+ public function buildForm(FormBuilderInterface $builder, CustomField $customField)
+ {
+ $options = $customField->getOptions();
+ $entries = $this->optionRepository->findFilteredByKey($options[self::KEY],
+ false, true);
+ //create a local copy of translatable string helper
+ $translatableStringHelper = $this->translatableStringHelper;
+ $builder->add($customField->getSlug(), 'select2_choice', array(
+ 'choices' => $entries,
+ 'choice_label' => function(Option $option) use ($translatableStringHelper) {
+ return $translatableStringHelper->localize($option->getText());
+ },
+ 'choice_value' => function ($key) use ($entries) {
+ if ($key === NULL) {
+ return null;
+ }
+ return $key->getId();
+ },
+ 'choices_as_values' => true,
+ 'multiple' => false,
+ 'expanded' => false,
+ 'group_by' => function(Option $option) use ($translatableStringHelper) {
+ if ($option->hasParent()) {
+ return $translatableStringHelper->localize($option->getParent()->getText());
+ } else {
+ return $translatableStringHelper->localize($option->getText());
+ }
+ },
+ 'label' => $translatableStringHelper->localize($customField->getName())
+ ));
+ $builder->get($customField->getSlug())
+ ->addModelTransformer(new CustomFieldDataTransformer($this, $customField));
+
+ }
+
+ public function buildOptionsForm(FormBuilderInterface $builder)
+ {
+ //create a selector between different keys
+ $keys = $this->optionRepository->getKeys();
+ $choices = array();
+ foreach ($keys as $key) {
+ $choices[$key] = $key;
+ }
+
+ return $builder->add(self::KEY, 'choice', array(
+ 'choices' => $choices,
+ 'label' => 'Options key'
+ ));
+
+ }
+
+ public function deserialize($serialized, \Chill\CustomFieldsBundle\Entity\CustomField $customField)
+ {
+ if ($serialized === NULL) {
+ return NULL;
+ }
+
+
+ return $this->optionRepository->find($serialized);
+ }
+
+ public function getName()
+ {
+ return 'Long Choice';
+ }
+
+ public function render($value, \Chill\CustomFieldsBundle\Entity\CustomField $customField, $documentType = 'html')
+ {
+ $option = $this->deserialize($value, $customField);
+ $template = 'ChillCustomFieldsBundle:CustomFieldsRendering:choice_long.'
+ .$documentType.'.twig';
+
+ return $this->templating
+ ->render($template, array(
+ 'values' => array($option)
+ ));
+ }
+
+ public function serialize($value, \Chill\CustomFieldsBundle\Entity\CustomField $customField)
+ {
+ if (!$value instanceof Option) {
+ throw new \LogicException('the value should be an instance of '
+ . 'Chill\CustomFieldsBundle\Entity\CustomFieldLongChoice\Option, '
+ . is_object($value) ? get_class($value) : gettype($value).' given');
+ }
+
+ // we place the id in array, to allow in the future multiple select
+ return $value->getId();
+ }
+
+}
diff --git a/DataFixtures/ORM/LoadOption.php b/DataFixtures/ORM/LoadOption.php
new file mode 100644
index 000000000..9dd9a5aaf
--- /dev/null
+++ b/DataFixtures/ORM/LoadOption.php
@@ -0,0 +1,172 @@
+
+ *
+ * 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\DataFixtures\ORM;
+
+use Doctrine\Common\DataFixtures\AbstractFixture;
+use Doctrine\Common\DataFixtures\OrderedFixtureInterface;
+use Chill\CustomFieldsBundle\Entity\CustomFieldLongChoice\Option;
+
+/**
+ * Load some Options
+ *
+ *
+ * @author Julien Fastré
+ */
+class LoadOption extends AbstractFixture implements OrderedFixtureInterface
+{
+ /**
+ *
+ * @var \Faker\Generator
+ */
+ public $fakerFr;
+
+ /**
+ *
+ * @var \Faker\Generator
+ */
+ public $fakerEn;
+
+ /**
+ *
+ * @var \Faker\Generator
+ */
+ public $fakerNl;
+
+ public function __construct()
+ {
+ $this->fakerFr = \Faker\Factory::create('fr_FR');
+ $this->fakerEn = \Faker\Factory::create('en_EN');
+ $this->fakerNl = \Faker\Factory::create('nl_NL');
+ }
+
+ public function getOrder()
+ {
+ return 1000;
+ }
+
+ public function load(\Doctrine\Common\Persistence\ObjectManager $manager)
+ {
+ echo "Loading Options \n";
+ // load companies
+ $this->loadingCompanies($manager);
+ $this->loadingWords($manager);
+
+
+ $manager->flush();
+
+ }
+
+ private function loadingWords(\Doctrine\Common\Persistence\ObjectManager $manager)
+ {
+ echo "Loading some words...\n";
+
+ $parents = array(
+ array(
+ 'fr' => 'Categorie 1',
+ 'nl' => 'Categorie 1',
+ 'en' => 'Category 1'
+ ),
+ array(
+ 'fr' => 'Categorie 2',
+ 'nl' => 'Categorie 2',
+ 'en' => 'Category 2'
+ )
+ );
+
+ foreach ($parents as $text) {
+ $parent = (new Option())
+ ->setText($text)
+ ->setKey('word')
+ ;
+ $manager->persist($parent);
+ //Load children
+ $expected_nb_children = rand(10, 50);
+ for ($i=0; $i < $expected_nb_children; $i++) {
+ $manager->persist($this->createChildOption($parent, array(
+ 'fr' => $this->fakerFr->word,
+ 'nl' => $this->fakerNl->word,
+ 'en' => $this->fakerEn->word
+ )));
+ }
+ }
+ }
+
+ private function loadingCompanies(\Doctrine\Common\Persistence\ObjectManager $manager)
+ {
+ echo "Loading companies \n";
+ $companiesParents = array(
+ array(
+ 'fr' => 'Grandes Entreprises',
+ 'nl' => 'Grotes Bedrijven',
+ 'en' => 'Big Companies'
+ ),
+ array(
+ 'fr' => 'Moyennes Entreprises',
+ 'nl' => 'Middelbare Bedrijven',
+ 'en' => 'Middle Companies'
+ ),
+ array(
+ 'fr' => 'Petites Entreprises',
+ 'nl' => 'Kleine Bedrijven',
+ 'en' => 'Little Companies'
+ )
+ );
+
+
+ foreach ($companiesParents as $text) {
+ $parent = (new Option())
+ ->setText($text)
+ ->setKey('company')
+ ;
+ $manager->persist($parent);
+ //Load children
+ $expected_nb_children = rand(10, 50);
+ for ($i=0; $i < $expected_nb_children; $i++) {
+ $companyName = $this->fakerFr->company;
+ $manager->persist($this->createChildOption($parent, array(
+ 'fr' => $companyName,
+ 'nl' => $companyName,
+ 'en' => $companyName
+ )));
+ }
+ }
+ }
+
+ private $counter = 0;
+
+ /**
+ *
+ * @param Option $parent
+ * @param array $text
+ * @return Option
+ */
+ private function createChildOption(Option $parent, array $text)
+ {
+ $this->counter ++;
+
+ return (new Option())
+ ->setText($text)
+ ->setParent($parent)
+ ->setActive(true)
+ ->setInternalKey($parent->getKey().'-'.$this->counter);
+ ;
+ }
+
+}
diff --git a/Entity/CustomFieldLongChoice/Option.php b/Entity/CustomFieldLongChoice/Option.php
new file mode 100644
index 000000000..9037ee117
--- /dev/null
+++ b/Entity/CustomFieldLongChoice/Option.php
@@ -0,0 +1,153 @@
+
+ *
+ * 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\Entity\CustomFieldLongChoice;
+
+/**
+ *
+ *
+ * @author Julien Fastré
+ */
+class Option
+{
+ /**
+ *
+ * @var int
+ */
+ private $id;
+
+ /**
+ *
+ * @var string
+ */
+ private $key;
+
+ /**
+ * a json representation of text (multilingual)
+ *
+ * @var array
+ */
+ private $text;
+
+ /**
+ *
+ * @var \Doctrine\Common\Collections\Collection
+ */
+ private $children;
+
+ /**
+ *
+ * @var Option
+ */
+ private $parent;
+
+ /**
+ *
+ * @var string
+ */
+ private $internalKey = '';
+
+ /**
+ *
+ * @var boolean
+ */
+ private $active = true;
+
+ public function getId()
+ {
+ return $this->id;
+ }
+
+ public function getKey()
+ {
+ return $this->key;
+ }
+
+ public function getText()
+ {
+ return $this->text;
+ }
+
+ public function getChildren()
+ {
+ return $this->children;
+ }
+
+ public function getParent()
+ {
+ return $this->parent;
+ }
+
+ public function setKey($key)
+ {
+ $this->key = $key;
+ return $this;
+ }
+
+ public function setText(array $text)
+ {
+ $this->text = $text;
+ return $this;
+ }
+
+ public function setParent(Option $parent = null)
+ {
+ $this->parent = $parent;
+ $this->key = $parent->getKey();
+ return $this;
+ }
+
+ /**
+ *
+ * @return boolean
+ */
+ public function hasParent()
+ {
+ return $this->parent === NULL ? false : true;
+ }
+
+ public function getInternalKey()
+ {
+ return $this->internalKey;
+ }
+
+ public function isActive()
+ {
+ return $this->active;
+ }
+
+ public function getActive()
+ {
+ return $this->isActive();
+ }
+
+ public function setInternalKey($internal_key)
+ {
+ $this->internalKey = $internal_key;
+ return $this;
+ }
+
+ public function setActive($active)
+ {
+ $this->active = $active;
+ return $this;
+ }
+
+
+}
diff --git a/EntityRepository/CustomFieldLongChoice/OptionRepository.php b/EntityRepository/CustomFieldLongChoice/OptionRepository.php
new file mode 100644
index 000000000..58f8ee302
--- /dev/null
+++ b/EntityRepository/CustomFieldLongChoice/OptionRepository.php
@@ -0,0 +1,77 @@
+
+ *
+ * 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\EntityRepository\CustomFieldLongChoice;
+
+use Doctrine\ORM\EntityRepository;
+use Chill\CustomFieldsBundle\Entity\CustomFieldLongChoice\Option;
+
+/**
+ *
+ *
+ * @author Julien Fastré
+ */
+class OptionRepository extends EntityRepository
+{
+ /**
+ *
+ * @param string $key
+ * @return Option[]
+ */
+ public function findFilteredByKey($key, $includeParents = true, $active = true)
+ {
+ $qb = $this->createQueryBuilder('option');
+ $qb->where('option.key = :key');
+
+ if ($active === true){
+ $qb->andWhere('option.active = true');
+ }
+
+ if ($includeParents === false) {
+ $qb->andWhere('option.parent IS NOT NULL');
+
+ if ($active === TRUE) {
+ $qb->join('option.parent', 'p');
+ $qb->andWhere('p.active = true');
+ }
+ }
+
+ $qb->setParameter('key', $key);
+
+ return $qb->getQuery()->getResult();
+ }
+
+ /**
+ *
+ * @return string[]
+ */
+ public function getKeys()
+ {
+ $keys = $this->createQueryBuilder('option')
+ ->select('option.key')
+ ->distinct()
+ ->getQuery()
+ ->getScalarResult();
+
+ return array_map(function($r) {
+ return $r['key'];
+ }, $keys);
+ }
+
+}
diff --git a/Resources/config/doctrine/CustomFieldLongChoice.Option.orm.yml b/Resources/config/doctrine/CustomFieldLongChoice.Option.orm.yml
new file mode 100644
index 000000000..3d0b8fbdc
--- /dev/null
+++ b/Resources/config/doctrine/CustomFieldLongChoice.Option.orm.yml
@@ -0,0 +1,33 @@
+Chill\CustomFieldsBundle\Entity\CustomFieldLongChoice\Option:
+ type: entity
+ table: custom_field_long_choice_options
+ repositoryClass: Chill\CustomFieldsBundle\EntityRepository\CustomFieldLongChoice\OptionRepository
+ id:
+ id:
+ type: integer
+ id: true
+ generator:
+ strategy: AUTO
+ fields:
+ key:
+ type: string
+ length: 15
+ text:
+ type: json_array
+ internalKey:
+ type: string
+ length: 50
+ column: internal_key
+ active:
+ type: boolean
+ default: true
+ oneToMany:
+ children:
+ targetEntity: Chill\CustomFieldsBundle\Entity\CustomFieldLongChoice\Option
+ mappedBy: parent
+ manyToOne:
+ parent:
+ targetEntity: Chill\CustomFieldsBundle\Entity\CustomFieldLongChoice\Option
+ inversedBy: children
+ nullable: true
+
\ No newline at end of file
diff --git a/Resources/config/services.yml b/Resources/config/services.yml
index 4641910b2..e2c971c75 100644
--- a/Resources/config/services.yml
+++ b/Resources/config/services.yml
@@ -107,4 +107,20 @@ services:
calls:
- [setContainer, ["@service_container"]]
tags:
- - { name: twig.extension }
\ No newline at end of file
+ - { name: twig.extension }
+
+ chill.custom_field.custom_field_long_choice:
+ class: Chill\CustomFieldsBundle\CustomFields\CustomFieldLongChoice
+ arguments:
+ - "@chill.custom_field.custom_field_long_choice_option_repository"
+ - "@chill.main.helper.translatable_string"
+ - "@templating"
+ tags:
+ - { name: 'chill.custom_field', type: 'long_choice' }
+
+ chill.custom_field.custom_field_long_choice_option_repository:
+ class: Chill\CustomFieldsBundle\EntityRepository\CustomFieldLongChoice\OptionRepository
+ factory: ["@doctrine", getRepository]
+ arguments:
+ - "Chill\CustomFieldsBundle\Entity\CustomFieldLongChoice\Option"
+
\ No newline at end of file
diff --git a/Resources/migrations/Version20151210205610.php b/Resources/migrations/Version20151210205610.php
new file mode 100644
index 000000000..01cb61728
--- /dev/null
+++ b/Resources/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)
+ {
+ // 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/Resources/translations/messages.fr.yml b/Resources/translations/messages.fr.yml
index 0c5965eb8..f77f08028 100644
--- a/Resources/translations/messages.fr.yml
+++ b/Resources/translations/messages.fr.yml
@@ -84,3 +84,6 @@ 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
+
+#custom field long choice
+Options key: Clé des options
diff --git a/Resources/views/CustomFieldsRendering/choice_long.html.twig b/Resources/views/CustomFieldsRendering/choice_long.html.twig
new file mode 100644
index 000000000..6849f1d0c
--- /dev/null
+++ b/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/composer.json b/composer.json
index 68f716242..9c1240b5a 100644
--- a/composer.json
+++ b/composer.json
@@ -34,8 +34,9 @@
"chill-project/main": "dev-master"
},
"require-dev": {
- "doctrine/doctrine-fixtures-bundle": "~2.2@dev",
- "chill-project/person": "dev-master@dev"
+ "chill-project/person": "dev-master@dev",
+ "fzaninotto/faker": "~1",
+ "doctrine/doctrine-fixtures-bundle": "~2.2"
},
"scripts": {
"post-install-cmd": [