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": [