diff --git a/.gitignore b/.gitignore index 08abe3eac..b11589291 100644 --- a/.gitignore +++ b/.gitignore @@ -15,6 +15,7 @@ .gitignore~ *~ composer.phar +composer.lock /nbproject/private/ parameters.yml app/config/parameters.yml @@ -27,3 +28,5 @@ src/Chill/CustomFieldsBundle/vendor/* bootstrap.php.cache #the file created by composer to store creds auth.json +Tests/Fixtures/App/app/config/parameters.yml + diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 104cafa19..000000000 --- a/.travis.yml +++ /dev/null @@ -1,27 +0,0 @@ -language: php -php: -- 5.5 -- 5.6 -- hhvm-nightly -matrix: - allow_failures: - - php: hhvm-nightly -addons: - postgresql: '9.3' -sudo: false -install: - - composer config -g github-oauth.github.com $GITHUB_COMPOSER_AUTH - - composer install --dev - - cp Tests/Fixtures/App/app/config/parameters.travis.yml Tests/Fixtures/App/app/config/parameters.yml -before_script: - - psql -c 'create database test0;' -U postgres - - "./console.sh --env=test cache:warmup" - - "./console.sh doctrine:migrations:migrate --no-interaction" - - "./console.sh doctrine:fixtures:load --no-interaction --env=test" -script: phpunit --coverage-text -notifications: - email: - - info@champs-libres.coop -env: - global: - secure: MoVsISTKaeamuFNoylDv/nM6NBXBtCoH5tuGwX3RHpRK/zRXh421RgO8z/GTHxGt63r04EfYrsCXXng8fN4ZVA2Bjb9chGAozYZJOSwZ9Vmfjycu3k0v/+hikAj33DT+CEdVk4fggEZh5dcVbaJDRgaUZkwMLUtyCqeiv+J5X68= diff --git a/Controller/AdminController.php b/Controller/AdminController.php new file mode 100644 index 000000000..b538d9092 --- /dev/null +++ b/Controller/AdminController.php @@ -0,0 +1,37 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace Chill\CustomFieldsBundle\Controller; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Bundle\FrameworkBundle\Controller\Controller; + +/** + * Controller for the custom fields configuration section (in + * the admin) + * + */ +class AdminController extends Controller +{ + public function indexAction() + { + return $this->render('ChillCustomFieldsBundle:Admin:layout.html.twig'); + } +} \ No newline at end of file diff --git a/Controller/CustomFieldController.php b/Controller/CustomFieldController.php index f9958d70d..46b013f82 100644 --- a/Controller/CustomFieldController.php +++ b/Controller/CustomFieldController.php @@ -4,7 +4,6 @@ namespace Chill\CustomFieldsBundle\Controller; use Symfony\Component\HttpFoundation\Request; use Symfony\Bundle\FrameworkBundle\Controller\Controller; - use Chill\CustomFieldsBundle\Entity\CustomField; /** @@ -14,40 +13,6 @@ use Chill\CustomFieldsBundle\Entity\CustomField; class CustomFieldController extends Controller { - /** - * Lists all CustomField entities. - * - */ - public function indexAction() - { - $em = $this->getDoctrine()->getManager(); - - $entities = $em->getRepository('ChillCustomFieldsBundle:CustomField')->findAll(); - - //prepare form for new custom type - $fieldChoices = array(); - foreach ($this->get('chill.custom_field.provider')->getAllFields() - as $key => $customType) { - $fieldChoices[$key] = $customType->getName(); - } - $form = $this->get('form.factory') - ->createNamedBuilder(null, 'form', null, array( - 'method' => 'GET', - 'action' => $this->generateUrl('customfield_new'), - 'csrf_protection' => false - )) - ->add('type', 'choice', array( - 'choices' => $fieldChoices - )) - ->getForm(); - - return $this->render('ChillCustomFieldsBundle:CustomField:index.html.twig', array( - 'entities' => $entities, - 'form' => $form->createView() - )); - } - - /** * Creates a new CustomField entity. * @@ -62,9 +27,16 @@ class CustomFieldController extends Controller $em = $this->getDoctrine()->getManager(); $em->persist($entity); $em->flush(); + + $this->addFlash('success', $this->get('translator') + ->trans('The custom field has been created')); - return $this->redirect($this->generateUrl('customfield_show', array('id' => $entity->getId()))); - } + return $this->redirect($this->generateUrl('customfieldsgroup_show', + array('id' => $entity->getCustomFieldsGroup()->getId()))); + } + + $this->addFlash('error', $this->get('translator') + ->trans("The custom field form contains errors")); return $this->render('ChillCustomFieldsBundle:CustomField:new.html.twig', array( 'entity' => $entity, @@ -85,9 +57,10 @@ class CustomFieldController extends Controller 'action' => $this->generateUrl('customfield_create', array('type' => $type)), 'method' => 'POST', - 'type' => $type + 'type' => $type, + 'group_widget' => ($entity->getCustomFieldsGroup()) ? 'hidden' :'entity' )); - + $form->add('submit', 'submit', array('label' => 'Create')); return $form; @@ -100,6 +73,21 @@ class CustomFieldController extends Controller public function newAction(Request $request) { $entity = new CustomField(); + + //add the custom field group if defined in URL + $cfGroupId = $request->query->get('customFieldsGroup', null); + + if ($cfGroupId !== null) { + $cfGroup = $this->getDoctrine()->getManager() + ->getRepository('ChillCustomFieldsBundle:CustomFieldsGroup') + ->find($cfGroupId); + if (!$cfGroup) { + throw $this->createNotFoundException('CustomFieldsGroup with id ' + . $cfGroupId.' is not found !'); + } + $entity->setCustomFieldsGroup($cfGroup); + } + $form = $this->createCreateForm($entity, $request->query->get('type')); return $this->render('ChillCustomFieldsBundle:CustomField:new.html.twig', array( @@ -111,6 +99,7 @@ class CustomFieldController extends Controller /** * Finds and displays a CustomField entity. * + * @deprecated is not used since there is no link to show action */ public function showAction($id) { @@ -122,12 +111,8 @@ class CustomFieldController extends Controller throw $this->createNotFoundException('Unable to find CustomField entity.'); } - $deleteForm = $this->createDeleteForm($id); - return $this->render('ChillCustomFieldsBundle:CustomField:show.html.twig', array( - 'entity' => $entity, - 'delete_form' => $deleteForm->createView(), - )); + 'entity' => $entity, )); } /** @@ -145,12 +130,10 @@ class CustomFieldController extends Controller } $editForm = $this->createEditForm($entity, $entity->getType()); - $deleteForm = $this->createDeleteForm($id); - + return $this->render('ChillCustomFieldsBundle:CustomField:edit.html.twig', array( 'entity' => $entity, 'edit_form' => $editForm->createView(), - 'delete_form' => $deleteForm->createView(), )); } @@ -166,7 +149,8 @@ class CustomFieldController extends Controller $form = $this->createForm('custom_field_choice', $entity, array( 'action' => $this->generateUrl('customfield_update', array('id' => $entity->getId())), 'method' => 'PUT', - 'type' => $type + 'type' => $type, + 'group_widget' => 'hidden' )); $form->add('submit', 'submit', array('label' => 'Update')); @@ -187,61 +171,24 @@ class CustomFieldController extends Controller throw $this->createNotFoundException('Unable to find CustomField entity.'); } - $deleteForm = $this->createDeleteForm($id); $editForm = $this->createEditForm($entity, $entity->getType()); $editForm->handleRequest($request); if ($editForm->isValid()) { $em->flush(); + + $this->addFlash('success', $this->get('translator') + ->trans("The custom field has been updated")); return $this->redirect($this->generateUrl('customfield_edit', array('id' => $id))); } + + $this->addFlash('error', $this->get('translator') + ->trans("The custom field form contains errors")); return $this->render('ChillCustomFieldsBundle:CustomField:edit.html.twig', array( 'entity' => $entity, 'edit_form' => $editForm->createView(), - 'delete_form' => $deleteForm->createView(), )); } - /** - * Deletes a CustomField entity. - * - */ - public function deleteAction(Request $request, $id) - { - $form = $this->createDeleteForm($id); - $form->handleRequest($request); - - if ($form->isValid()) { - $em = $this->getDoctrine()->getManager(); - $entity = $em->getRepository('ChillCustomFieldsBundle:CustomField')->find($id); - - if (!$entity) { - throw $this->createNotFoundException('Unable to find CustomField entity.'); - } - - $em->remove($entity); - $em->flush(); - } - - return $this->redirect($this->generateUrl('customfield')); - } - - /** - * Creates a form to delete a CustomField entity by id. - * - * @param mixed $id The entity id - * - * @return \Symfony\Component\Form\Form The form - */ - private function createDeleteForm($id) - { - return $this->createFormBuilder() - ->setAction($this->generateUrl('customfield_delete', array('id' => $id))) - ->setMethod('DELETE') - ->add('submit', 'submit', array('label' => 'Delete')) - ->getForm() - ; - } - } diff --git a/Controller/CustomFieldsDefaultGroupController.php b/Controller/CustomFieldsDefaultGroupController.php deleted file mode 100644 index 860d3c554..000000000 --- a/Controller/CustomFieldsDefaultGroupController.php +++ /dev/null @@ -1,77 +0,0 @@ -getDoctrine()->getManager(); - - $defaultGroups = $em->getRepository('ChillCustomFieldsBundle:CustomFieldsDefaultGroup')->findAll(); - - $form = $this->get('form.factory') - ->createNamedBuilder(null, 'form', null, array( - 'method' => 'GET', - 'action' => $this->generateUrl('customfieldsdefaultgroup_set'), - 'csrf_protection' => false - )) - ->add('cFGroup', 'entity', array( - 'class' => 'ChillCustomFieldsBundle:CustomFieldsGroup', - 'property' => 'name[fr]' - )) - ->getForm(); - - return $this->render('ChillCustomFieldsBundle:CustomFieldsDefaultGroup:list.html.twig', array( - 'defaultGroups' => $defaultGroups, - 'form' => $form->createView() - )); - } - - /** - * Set the CustomField Group with id $cFGroupId as default - */ - public function setAGroupAsDefaultAction(Request $request) - { - $cFGroupId = $request->query->get('cFGroup'); - - $em = $this->getDoctrine()->getManager(); - - $cFGroup = $em->getRepository('ChillCustomFieldsBundle:CustomFieldsGroup')->findOneById($cFGroupId); - - if(!$cFGroup) { - throw new Exception("No CF GROUP with ID".$cFGroupId, 1); - } - - $cFDefaultGroup = $em->getRepository('ChillCustomFieldsBundle:CustomFieldsDefaultGroup') - ->findOneByEntity($cFGroup->getEntity()); - - if($cFDefaultGroup) { - $em->remove($cFDefaultGroup); - $em->flush(); - } - - $newCFDefaultGroup = new CustomFieldsDefaultGroup(); - $newCFDefaultGroup->setCustomFieldsGroup($cFGroup); - $newCFDefaultGroup->setEntity($cFGroup->getEntity()); - - $em->persist($newCFDefaultGroup); - $em->flush(); - - return $this->redirect($this->generateUrl('customfieldsdefaultgroup')); - } -} \ No newline at end of file diff --git a/Controller/CustomFieldsGroupController.php b/Controller/CustomFieldsGroupController.php index 9897da5f4..46b95fd5a 100644 --- a/Controller/CustomFieldsGroupController.php +++ b/Controller/CustomFieldsGroupController.php @@ -4,8 +4,11 @@ namespace Chill\CustomFieldsBundle\Controller; use Symfony\Component\HttpFoundation\Request; use Symfony\Bundle\FrameworkBundle\Controller\Controller; - +use Doctrine\ORM\Query; use Chill\CustomFieldsBundle\Entity\CustomFieldsGroup; +use Chill\CustomFieldsBundle\Entity\CustomField; +use Chill\CustomFieldsBundle\Form\DataTransformer\CustomFieldsGroupToIdTransformer; +use Chill\CustomFieldsBundle\Entity\CustomFieldsDefaultGroup; /** * CustomFieldsGroup controller. @@ -22,12 +25,64 @@ class CustomFieldsGroupController extends Controller { $em = $this->getDoctrine()->getManager(); - $entities = $em->getRepository('ChillCustomFieldsBundle:CustomFieldsGroup')->findAll(); + $cfGroups = $em->getRepository('ChillCustomFieldsBundle:CustomFieldsGroup')->findAll(); + $defaultGroups = $this->getDefaultGroupsId(); + + $makeDefaultFormViews = array(); + foreach ($cfGroups as $group) { + if (!in_array($group->getId(), $defaultGroups)){ + $makeDefaultFormViews[$group->getId()] = $this->createMakeDefaultForm($group)->createView(); + } + } return $this->render('ChillCustomFieldsBundle:CustomFieldsGroup:index.html.twig', array( - 'entities' => $entities, + 'entities' => $cfGroups, + 'default_groups' => $defaultGroups, + 'make_default_forms' => $makeDefaultFormViews )); } + + /** + * Get an array of CustomFieldsGroupId which are marked as default + * for their entity + * + * @return int[] + */ + private function getDefaultGroupsId() + { + $em = $this->getDoctrine()->getManager(); + + $customFieldsGroupIds = $em->createQuery('SELECT g.id FROM ' + . 'ChillCustomFieldsBundle:CustomFieldsDefaultGroup d ' + . 'JOIN d.customFieldsGroup g') + ->getResult(Query::HYDRATE_SCALAR); + + $result = array(); + foreach ($customFieldsGroupIds as $row) { + $result[] = $row['id']; + } + + return $result; + } + + /** + * create a form to make the group default + * + * @param CustomFieldsGroup $group + * @return \Symfony\Component\Form\Form + */ + private function createMakeDefaultForm(CustomFieldsGroup $group = null) + { + return $this->createFormBuilder($group, array( + 'method' => 'POST', + 'action' => $this->generateUrl('customfieldsgroup_makedefault') + )) + ->add('id', 'hidden') + ->add('submit', 'submit', array('label' => 'Make default')) + ->getForm(); + } + + /** * Creates a new CustomFieldsGroup entity. * @@ -42,9 +97,15 @@ class CustomFieldsGroupController extends Controller $em = $this->getDoctrine()->getManager(); $em->persist($entity); $em->flush(); + + $this->addFlash('success', $this->get('translator') + ->trans("The custom fields group has been created")); return $this->redirect($this->generateUrl('customfieldsgroup_show', array('id' => $entity->getId()))); } + + $this->addFlash('error', $this->get('translator') + ->trans("The custom fields group form contains errors")); return $this->render('ChillCustomFieldsBundle:CustomFieldsGroup:new.html.twig', array( 'entity' => $entity, @@ -99,14 +160,36 @@ class CustomFieldsGroupController extends Controller if (!$entity) { throw $this->createNotFoundException('Unable to find CustomFieldsGroup entity.'); } - - $deleteForm = $this->createDeleteForm($id); + + $options = $this->getOptionsAvailable($entity->getEntity()); return $this->render('ChillCustomFieldsBundle:CustomFieldsGroup:show.html.twig', array( 'entity' => $entity, - 'delete_form' => $deleteForm->createView(), + 'create_field_form' => $this->createCreateFieldForm($entity)->createView(), + 'options' => $options )); } + + /** + * Return an array of available key option for custom fields group + * on the given entity + * + * @param string $entity the entity to filter + */ + private function getOptionsAvailable($entity) + { + $options = $this->getParameter('chill_custom_fields.' + . 'customizables_entities'); + + foreach($options as $key => $definition) { + if ($definition['class'] == $entity) { + foreach ($definition['options'] as $key => $value) { + yield $key; + } + } + } + // [$entity->getEntity()]; + } /** * Displays a form to edit an existing CustomFieldsGroup entity. @@ -123,12 +206,10 @@ class CustomFieldsGroupController extends Controller } $editForm = $this->createEditForm($entity); - $deleteForm = $this->createDeleteForm($id); return $this->render('ChillCustomFieldsBundle:CustomFieldsGroup:edit.html.twig', array( 'entity' => $entity, 'edit_form' => $editForm->createView(), - 'delete_form' => $deleteForm->createView(), )); } @@ -149,6 +230,37 @@ class CustomFieldsGroupController extends Controller return $form; } + + private function createCreateFieldForm(CustomFieldsGroup $customFieldsGroup) + { + + $fieldChoices = array(); + foreach ($this->get('chill.custom_field.provider')->getAllFields() + as $key => $customType) { + $fieldChoices[$key] = $customType->getName(); + } + + $customfield = (new CustomField()) + ->setCustomFieldsGroup($customFieldsGroup); + + $builder = $this->get('form.factory') + ->createNamedBuilder(null, 'form', $customfield, array( + 'method' => 'GET', + 'action' => $this->generateUrl('customfield_new'), + 'csrf_protection' => false + )) + ->add('type', 'choice', array( + 'choices' => $fieldChoices + )) + ->add('customFieldsGroup', 'hidden') + ->add('submit', 'submit'); + $builder->get('customFieldsGroup') + ->addViewTransformer(new CustomFieldsGroupToIdTransformer( + $this->getDoctrine()->getManager())); + + return $builder->getForm(); + } + /** * Edits an existing CustomFieldsGroup entity. * @@ -163,61 +275,72 @@ class CustomFieldsGroupController extends Controller throw $this->createNotFoundException('Unable to find CustomFieldsGroup entity.'); } - $deleteForm = $this->createDeleteForm($id); $editForm = $this->createEditForm($entity); $editForm->handleRequest($request); if ($editForm->isValid()) { $em->flush(); + + $this->addFlash('success', $this->get('translator') + ->trans("The custom fields group has been updated")); return $this->redirect($this->generateUrl('customfieldsgroup_edit', array('id' => $id))); } + + $this->addFlash('error', $this->get('translator') + ->trans("The custom fields group form contains errors")); return $this->render('ChillCustomFieldsBundle:CustomFieldsGroup:edit.html.twig', array( 'entity' => $entity, 'edit_form' => $editForm->createView(), - 'delete_form' => $deleteForm->createView(), - )); + + )); } + /** - * Deletes a CustomFieldsGroup entity. - * + * Set the CustomField Group with id $cFGroupId as default */ - public function deleteAction(Request $request, $id) + public function makeDefaultAction(Request $request) { - $form = $this->createDeleteForm($id); + + $form = $this->createMakeDefaultForm(null); $form->handleRequest($request); - if ($form->isValid()) { - $em = $this->getDoctrine()->getManager(); - $entity = $em->getRepository('ChillCustomFieldsBundle:CustomFieldsGroup')->find($id); + $cFGroupId = $form->get('id')->getData(); - if (!$entity) { - throw $this->createNotFoundException('Unable to find CustomFieldsGroup entity.'); - } + $em = $this->getDoctrine()->getManager(); - $em->remove($entity); - $em->flush(); + $cFGroup = $em->getRepository('ChillCustomFieldsBundle:CustomFieldsGroup')->findOneById($cFGroupId); + + if(!$cFGroup) { + throw $this + ->createNotFoundException("customFieldsGroup not found with " + . "id $cFGroupId"); } - return $this->redirect($this->generateUrl('customfieldsgroup')); - } + $cFDefaultGroup = $em->getRepository('ChillCustomFieldsBundle:CustomFieldsDefaultGroup') + ->findOneByEntity($cFGroup->getEntity()); - /** - * Creates a form to delete a CustomFieldsGroup entity by id. - * - * @param mixed $id The entity id - * - * @return \Symfony\Component\Form\Form The form - */ - private function createDeleteForm($id) - { - return $this->createFormBuilder() - ->setAction($this->generateUrl('customfieldsgroup_delete', array('id' => $id))) - ->setMethod('DELETE') - ->add('submit', 'submit', array('label' => 'Delete')) - ->getForm() - ; + if($cFDefaultGroup) { + $em->remove($cFDefaultGroup); + $em->flush(); /*this is necessary, if not doctrine + * will not remove old entity before adding a new one, + * and this leads to violation constraint of unique entity + * in postgresql + */ + } + + $newCFDefaultGroup = new CustomFieldsDefaultGroup(); + $newCFDefaultGroup->setCustomFieldsGroup($cFGroup); + $newCFDefaultGroup->setEntity($cFGroup->getEntity()); + + $em->persist($newCFDefaultGroup); + $em->flush(); + + $this->addFlash('success', $this->get('translator') + ->trans("The default custom fields group has been changed")); + + return $this->redirect($this->generateUrl('customfieldsgroup')); } /** @@ -258,7 +381,7 @@ class CustomFieldsGroupController extends Controller } var_dump($form->getData()); - var_dump(json_encode($form->getData())); + var_dump(json_enccode($form->getData())); } diff --git a/CustomFields/AbstractCustomField.php b/CustomFields/AbstractCustomField.php new file mode 100644 index 000000000..ddd019176 --- /dev/null +++ b/CustomFields/AbstractCustomField.php @@ -0,0 +1,37 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace Chill\CustomFieldsBundle\CustomFields; + +use Chill\CustomFieldsBundle\CustomFields\CustomFieldInterface; +use Chill\CustomFieldsBundle\Entity\CustomField; + +/** + * + * + * @author Julien Fastré + */ +abstract class AbstractCustomField implements CustomFieldInterface +{ + public function isEmptyValue($value, CustomField $customField) + { + return (empty($value) and $value !== FALSE); + } + +} diff --git a/CustomFields/CustomFieldChoice.php b/CustomFields/CustomFieldChoice.php index 76e9b2355..782f5ab17 100644 --- a/CustomFields/CustomFieldChoice.php +++ b/CustomFields/CustomFieldChoice.php @@ -20,14 +20,14 @@ namespace Chill\CustomFieldsBundle\CustomFields; +use Chill\CustomFieldsBundle\Form\Type\ChoicesListType; +use Chill\CustomFieldsBundle\Form\Type\ChoicesType; +use Chill\CustomFieldsBundle\Form\Type\ChoiceWithOtherType; use Chill\CustomFieldsBundle\CustomFields\CustomFieldInterface; use Symfony\Component\Form\FormBuilderInterface; use Chill\CustomFieldsBundle\Entity\CustomField; -use Chill\CustomFieldsBundle\Form\Type\ChoicesType; use Symfony\Component\HttpFoundation\RequestStack; -use Chill\CustomFieldsBundle\Form\Type\ChoicesListType; use Chill\CustomFieldsBundle\Form\DataTransformer\CustomFieldDataTransformer; -use Chill\CustomFieldsBundle\Form\Type\ChoiceWithOtherType; use Symfony\Bridge\Twig\TwigEngine; use Chill\MainBundle\Templating\TranslatableStringHelper; use Symfony\Component\Translation\Translator; @@ -38,45 +38,44 @@ use Symfony\Component\Translation\Translator; * @author Julien Fastré * @author Marc Ducobu */ -class CustomFieldChoice implements CustomFieldInterface +class CustomFieldChoice extends AbstractCustomField { - const ALLOW_OTHER = 'other'; + const ALLOW_OTHER = 'other'; const OTHER_VALUE_LABEL = 'otherValueLabel'; - const MULTIPLE = 'multiple'; - const EXPANDED = 'expanded'; - const CHOICES = 'choices'; + const MULTIPLE = 'multiple'; + const EXPANDED = 'expanded'; + const CHOICES = 'choices'; - /** - * - * @var RequestStack - */ - private $requestStack; - - private $defaultLocales; - - /** - * - * @var TwigEngine - */ - private $templating; + /** + * + * @var RequestStack + */ + private $requestStack; + + private $defaultLocales; + + /** + * + * @var TwigEngine + */ + private $templating; /** * @var TranslatableStringHelper Helper that find the string in current locale from an array of translation */ private $translatableStringHelper; - public function __construct( - RequestStack $requestStack, - Translator $translator, - TwigEngine $templating, - TranslatableStringHelper $translatableStringHelper - ) - { - $this->requestStack = $requestStack; - $this->defaultLocales = $translator->getFallbackLocales(); - $this->templating = $templating; + public function __construct( + RequestStack $requestStack, + Translator $translator, + TwigEngine $templating, + TranslatableStringHelper $translatableStringHelper) + { + $this->requestStack = $requestStack; + $this->defaultLocales = $translator->getFallbackLocales(); + $this->templating = $templating; $this->translatableStringHelper = $translatableStringHelper; - } + } public function buildForm(FormBuilderInterface $builder, CustomField $customField) { @@ -93,11 +92,10 @@ class CustomFieldChoice implements CustomFieldInterface //prepare $options $options = array( - 'multiple' => $customFieldOptions[self::MULTIPLE], - 'choices' => $choices, - 'required' => false, - 'label' => $this->translatableStringHelper->localize($customField->getName()) - ); + 'multiple' => $customFieldOptions[self::MULTIPLE], + 'choices' => $choices, + 'required' => $customField->isRequired(), + 'label' => $this->translatableStringHelper->localize($customField->getName())); //if allow_other = true if ($customFieldOptions[self::ALLOW_OTHER] == true) { @@ -124,37 +122,35 @@ class CustomFieldChoice implements CustomFieldInterface $builder->create($customField->getSlug(), 'choice', $options) ->addModelTransformer(new CustomFieldDataTransformer($this, $customField)) ); - } - } public function buildOptionsForm(FormBuilderInterface $builder) { - $builder->add(self::MULTIPLE, 'choice', array( - 'expanded' => true, - 'multiple' => false, - 'choices' => array( - 1 => 'Multiple', - 0 => 'Unique' - ), - 'empty_data' => 0 - )) + $builder + ->add(self::MULTIPLE, 'choice', array( + 'expanded' => true, + 'multiple' => false, + 'choices' => array( + 1 => 'Multiple', + 0 => 'Unique'), + 'empty_data' => 0, + 'label' => 'Multiplicity' + )) ->add(self::EXPANDED, 'choice', array( 'expanded' => true, 'multiple' => false, 'choices' => array( 1 => 'Expanded', - 0 => 'Non expanded' - ), - 'empty_data' => 0 - )) + 0 => 'Non expanded'), + 'empty_data' => 0, + 'label' => 'Choice display' + )) ->add(self::ALLOW_OTHER, 'choice', array( 'label' => 'Allow other', 'choices' => array( 0 => 'No', - 1 => 'Yes' - ), + 1 => 'Yes'), 'empty_data' => 0, 'expanded' => true, 'multiple' => false @@ -164,21 +160,133 @@ class CustomFieldChoice implements CustomFieldInterface ->add(self::CHOICES, new ChoicesType(), array( 'type' => new ChoicesListType($this->defaultLocales), 'allow_add' => true - )) - ; + )); return $builder; } public function deserialize($serialized, CustomField $customField) { + // we always have to adapt to what the current data should be + $options = $customField->getOptions(); + + if ($options[self::MULTIPLE]) { + return $this->deserializeToMultiple($serialized, $options[self::ALLOW_OTHER]); + } else { + return $this->deserializeToUnique($serialized, $options[self::ALLOW_OTHER]); + } return $serialized; } + + private function deserializeToUnique($serialized, $allowOther) + { + $value = $this->guessValue($serialized); + + // set in a single value. We must have a single string + $fixedValue = is_array($value) ? + // check if the array has an element, if not replace by empty string + count($value) > 0 ? end($value) : '' + : + $value; + + if ($allowOther) { + return $this->deserializeWithAllowOther($serialized, $fixedValue); + } else { + return $fixedValue; + } + } + + /** + * deserialized the data from the database to a multiple + * field + * + * @param mixed $serialized + * @param boolean $allowOther + */ + private function deserializeToMultiple($serialized, $allowOther) + { + $value = $this->guessValue($serialized); + + // set in an array : we want a multiple + $fixedValue = is_array($value) ? $value : array($value); + + if ($allowOther) { + return $this->deserializeWithAllowOther($serialized, $fixedValue); + } else { + return $fixedValue; + } + } + + private function deserializeWithAllowOther($serialized, $value) + { + $existingOther = isset($serialized['_other']) ? $serialized['_other'] : ''; + + return array( + '_other' => $existingOther, + '_choices' => $value + ); + } + + /** + * Guess the value from the representation of it. + * + * If the value had an 'allow_other' = true option, the returned value + * **is not** the content of the _other field, but the `_other` string. + * + * @param array|string $value + * @return mixed + * @throws \LogicException if the case is not covered by this + */ + private function guessValue($value) + { + if ($value === NULL) { + return NULL; + } + + if (!is_array($value)) { + return $value; + } else { + // we have a field with "allow other" + if (isset($value['_choices'])) { + return $value['_choices']; + } else { + // we have a field with "multiple" + return $value; + } + } + + throw \LogicException("This case is not expected."); + } public function getName() { return 'Choices'; } + + public function isEmptyValue($value, CustomField $customField) + { + if ($value === NULL) { + return true; + } + + // if multiple choice OR multiple/single choice with other + if (is_array($value)) + { + // if allow other + if (isset($value['_choices'])) { + if ($value['_choices'] === NULL) { + return true; + } + return empty($value['_choices']); + } else { // we do not have 'allow other' + return empty($value); + } + } else { + return empty($value); + } + + throw \LogicException("This case is not expected."); + } /** * diff --git a/CustomFields/CustomFieldInterface.php b/CustomFields/CustomFieldInterface.php index 149e5500b..ce341142d 100644 --- a/CustomFields/CustomFieldInterface.php +++ b/CustomFields/CustomFieldInterface.php @@ -13,7 +13,9 @@ interface CustomFieldInterface { /** - * + * Return a form type to edit the custom field. This form is shown to the + * user. + * * @param \Chill\CustomFieldsBundle\CustomField\FormBuilderInterface $builder * @param \Chill\CustomFieldsBundle\CustomField\CustomField $customField * @return \Symfony\Component\Form\FormTypeInterface the form type @@ -21,7 +23,7 @@ interface CustomFieldInterface public function buildForm(FormBuilderInterface $builder, CustomField $customField); /** - * transform the value into a format that can be stored in DB + * Transform the value into a format that can be stored in DB * * @param mixed $value * @param \Chill\CustomFieldsBundle\CustomField\CustomField $customField @@ -38,6 +40,7 @@ interface CustomFieldInterface public function deserialize($serialized, CustomField $customField); /** + * Return a repsentation of the value of the CustomField. * * @param mixed $value the raw value, **not deserialized** (= as stored in the db) * @param \Chill\CustomFieldsBundle\CustomField\CustomField $customField @@ -48,11 +51,19 @@ interface CustomFieldInterface public function getName(); /** - * return a formType which allow to edit option for the custom type. + * Return a formType which allow to edit option for the custom type. * This FormType is shown in admin * * @param \Chill\CustomFieldsBundle\CustomField\FormBuilderInterface $builder * @return \Symfony\Component\Form\FormTypeInterface|null the form type */ public function buildOptionsForm(FormBuilderInterface $builder); + + /** + * Return if the value can be considered as empty + * + * @param mixed $value the value passed throug the deserialize function + * @param CustomField $customField + */ + public function isEmptyValue($value, CustomField $customField); } diff --git a/CustomFields/CustomFieldLongChoice.php b/CustomFields/CustomFieldLongChoice.php new file mode 100644 index 000000000..4d9f6400e --- /dev/null +++ b/CustomFields/CustomFieldLongChoice.php @@ -0,0 +1,161 @@ + + * + * 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 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 extends AbstractCustomField +{ + /** + * + * @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, + 'required' => $customField->isRequired(), + 'placeholder' => 'Choose a value', + '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 field'; + } + + 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' => $option === NULL ? array() : array($option) + )); + } + + public function serialize($value, \Chill\CustomFieldsBundle\Entity\CustomField $customField) + { + if ($value === NULL) { + return NULL; + } + + 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/CustomFields/CustomFieldNumber.php b/CustomFields/CustomFieldNumber.php new file mode 100644 index 000000000..91ccac8d0 --- /dev/null +++ b/CustomFields/CustomFieldNumber.php @@ -0,0 +1,181 @@ +, + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace Chill\CustomFieldsBundle\CustomFields; + +use Chill\CustomFieldsBundle\CustomFields\CustomFieldInterface; +use Chill\CustomFieldsBundle\Entity\CustomField; +use Symfony\Component\Form\FormBuilderInterface; +use Symfony\Component\Validator\Constraints\GreaterThanOrEqual; +use Symfony\Component\Validator\Constraints\LessThanOrEqual; +use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Bundle\TwigBundle\TwigEngine; +use Chill\MainBundle\Templating\TranslatableStringHelper; + +/** + * Create a custom field number. + * + * This number may have a min and max value, and a precision. + * + * @author Julien Fastré + * @author Marc Ducobu + */ +class CustomFieldNumber extends AbstractCustomField +{ + /** + * key for the minimal value of the field + */ + const MIN = 'min'; + const MAX = 'max'; + const SCALE = 'scale'; + const POST_TEXT = 'post_text'; + + /** + * + * @var TwigEngine + */ + private $templating = NULL; + + /** + * + * @var TranslatableStringHelper + */ + private $translatableStringHelper = NULL; + + public function __construct(TwigEngine $templating, TranslatableStringHelper $translatableStringHelper) + { + $this->templating = $templating; + $this->translatableStringHelper = $translatableStringHelper; + } + + public function buildForm(FormBuilderInterface $builder, CustomField $customField) + { + $options = $customField->getOptions(); + + //select the type depending to the SCALE + $type = ($options[self::SCALE] === 0 or $options[self::SCALE] === NULL)? + 'integer' : 'number'; + + $fieldOptions = $this->prepareFieldOptions($customField, $type); + + $builder->add($customField->getSlug(), $type, $fieldOptions); + } + + /** + * prepare the options'form field + * + * @param CustomField $customField + * @param string $type + * @return mixed[] + */ + private function prepareFieldOptions(CustomField $customField, $type) + { + $options = $customField->getOptions(); + + /** + * @var mixed[] the formField options + */ + $fieldOptions = array(); + + // add required + $fieldOptions['required'] = False; + + //add label + $fieldOptions['label'] = $this->translatableStringHelper->localize($customField->getName()); + + // add constraints if required + if ($options[self::MIN] !== NULL) { + $fieldOptions['constraints'][] = new GreaterThanOrEqual(array('value' => $options[self::MIN])); + } + if ($options[self::MAX] !== NULL) { + $fieldOptions['constraints'][] = new LessThanOrEqual(array('value' => $options[self::MAX])); + } + + // add precision to options if required + if ($type === 'number') { + $fieldOptions['scale'] = $options[self::SCALE]; + } + + if (!empty($options[self::POST_TEXT])) { + $fieldOptions['post_text'] = $options[self::POST_TEXT]; + } + + return $fieldOptions; + } + + public function buildOptionsForm(FormBuilderInterface $builder) + { + return $builder + ->add(self::MIN, 'number', array( + 'scale' => 2, + 'label' => 'Greater or equal than', + 'required' => false + )) + ->add(self::MAX, 'number', array( + 'scale' => 2, + 'label' => 'Lesser or equal than', + 'required' => false + )) + ->add(self::SCALE, 'integer', array( + 'scale' => 0, + 'label' => 'Precision', + 'constraints' => array( + new GreaterThanOrEqual(array('value' => 0)) + ) + )) + ->add(self::POST_TEXT, 'text', array( + 'label' => 'Text after the field' + )) + ; + + } + + public function deserialize($serialized, CustomField $customField) + { + return $serialized; + } + + public function getName() + { + return 'Number field'; + } + + public function render($value, CustomField $customField, $documentType = 'html') + { + $template = 'ChillCustomFieldsBundle:CustomFieldsRendering:number.' + .$documentType.'.twig'; + $options = $customField->getOptions(); + + return $this->templating + ->render($template, array( + 'number' => $value, + 'scale' => $options[self::SCALE], + 'post' => $options[self::POST_TEXT] + )); + } + + public function serialize($value, CustomField $customField) + { + return $value; + } + +} diff --git a/CustomFields/CustomFieldText.php b/CustomFields/CustomFieldText.php index 08e4ab58f..1205d2c93 100644 --- a/CustomFields/CustomFieldText.php +++ b/CustomFields/CustomFieldText.php @@ -33,7 +33,7 @@ use Chill\MainBundle\Templating\TranslatableStringHelper; * @author Julien Fastré * @author Marc Ducobu */ -class CustomFieldText implements CustomFieldInterface +class CustomFieldText extends AbstractCustomField { private $requestStack; @@ -114,7 +114,7 @@ class CustomFieldText implements CustomFieldInterface public function getName() { - return 'text field'; + return 'Text field'; } public function buildOptionsForm(FormBuilderInterface $builder) @@ -122,7 +122,13 @@ class CustomFieldText implements CustomFieldInterface return $builder ->add(self::MAX_LENGTH, 'integer', array('empty_data' => 256)) ->add(self::MULTIPLE_CF_INLINE, 'choice', array( - 'choices' => array('1' => 'True', '0' => 'False'))) + 'choices' => array( + '1' => 'Multiple boxes on the line', + '0' => 'One box on the line' + ), + 'label' => 'Box appearance', + 'expanded' => True + )) ; } } diff --git a/CustomFields/CustomFieldTitle.php b/CustomFields/CustomFieldTitle.php index 78b6f08d6..11470df28 100644 --- a/CustomFields/CustomFieldTitle.php +++ b/CustomFields/CustomFieldTitle.php @@ -28,7 +28,7 @@ use Symfony\Component\HttpFoundation\RequestStack; use Symfony\Bundle\TwigBundle\TwigEngine; use Chill\MainBundle\Templating\TranslatableStringHelper; -class CustomFieldTitle implements CustomFieldInterface +class CustomFieldTitle extends AbstractCustomField { const TYPE = 'type'; const TYPE_TITLE = 'title'; @@ -90,16 +90,24 @@ class CustomFieldTitle implements CustomFieldInterface public function getName() { - return 'title'; + return 'Title'; + } + + public function isEmptyValue($value, CustomField $customField) + { + return false; } public function buildOptionsForm(FormBuilderInterface $builder) { return $builder->add(self::TYPE, 'choice', - array('choices' => array( - self::TYPE_TITLE => self::TYPE_TITLE, - self::TYPE_SUBTITLE => self::TYPE_SUBTITLE - )) + array( + 'choices' => array( + self::TYPE_TITLE => 'Main title', + self::TYPE_SUBTITLE => 'Subtitle' + ), + 'label' => 'Title level' + ) ); } } 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/DependencyInjection/ChillCustomFieldsExtension.php b/DependencyInjection/ChillCustomFieldsExtension.php index 3aaab5933..05c4108e4 100644 --- a/DependencyInjection/ChillCustomFieldsExtension.php +++ b/DependencyInjection/ChillCustomFieldsExtension.php @@ -33,6 +33,8 @@ class ChillCustomFieldsExtension extends Extension implements PrependExtensionIn $container->setParameter('chill_custom_fields.customizables_entities', $config['customizables_entities']); + $container->setParameter('chill_custom_fields.show_empty_values', + $config['show_empty_values_in_views']); } /* (non-PHPdoc) diff --git a/DependencyInjection/Configuration.php b/DependencyInjection/Configuration.php index 232a1a9a2..84c2147bb 100644 --- a/DependencyInjection/Configuration.php +++ b/DependencyInjection/Configuration.php @@ -35,27 +35,35 @@ class Configuration implements ConfigurationInterface ->defaultValue(array()) ->prototype('array') ->children() - ->scalarNode('class')->isRequired()->info($classInfo)->end() - ->scalarNode('name') ->isRequired()->info($nameInfo) ->end() + ->scalarNode('class')->isRequired()->info($classInfo) + ->end() + ->scalarNode('name') ->isRequired()->info($nameInfo) + ->end() ->arrayNode('options') - ->info($optionsInfo) - ->defaultValue(array()) - ->useAttributeAsKey('key') - ->prototype('array') - ->children() - ->scalarNode('form_type') - ->isRequired() - ->info($optionsFormType) - ->end() - ->variableNode('form_options') - ->info($optionsFormOptionsInfos) - ->defaultValue(array()) - ->end() + ->info($optionsInfo) + ->defaultValue(array()) + ->useAttributeAsKey('key') + ->prototype('array') + ->children() + ->scalarNode('form_type') + ->isRequired() + ->info($optionsFormType) ->end() + ->variableNode('form_options') + ->info($optionsFormOptionsInfos) + ->defaultValue(array()) + ->end() + ->end() ->end() ->end() ->end() ->end() + ->end() + ->booleanNode('show_empty_values_in_views') + ->info('Show the empty value for custom fields in the views, timeline, ...') + ->defaultValue(true) + ->end() + ->end() ; return $treeBuilder; diff --git a/Entity/CustomField.php b/Entity/CustomField.php index 2b45b498c..fe1a3bc89 100644 --- a/Entity/CustomField.php +++ b/Entity/CustomField.php @@ -60,6 +60,12 @@ class CustomField */ private $ordering; + /** + * + * @var bolean + */ + private $required = FALSE; + const ONE_TO_ONE = 1; const ONE_TO_MANY = 2; @@ -246,6 +252,34 @@ class CustomField $this->slug = $slug; return $this; } + + /** + * alias for isRequired + * + * @return boolean + */ + public function getRequired() + { + return $this->isRequired(); + } + + /** + * return true if the field required + * + * @return boolean + */ + public function isRequired() + { + return $this->required; + } + + public function setRequired($required) + { + $this->required = $required; + return $this; + } + + } 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/Form/CustomFieldType.php b/Form/CustomFieldType.php index 459397e2d..1d3594718 100644 --- a/Form/CustomFieldType.php +++ b/Form/CustomFieldType.php @@ -6,9 +6,11 @@ use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\OptionsResolver\OptionsResolver; use Chill\CustomFieldsBundle\Service\CustomFieldProvider; -use Chill\CustomFieldsBundle\Entity\CustomField; use Symfony\Component\Form\FormEvent; use Symfony\Component\Form\FormEvents; +use Doctrine\Common\Persistence\ObjectManager; +use Chill\CustomFieldsBundle\Form\DataTransformer\CustomFieldsGroupToIdTransformer; + class CustomFieldType extends AbstractType { @@ -20,10 +22,17 @@ class CustomFieldType extends AbstractType private $culture = 'fr'; + /** + * @var ObjectManager + */ + private $om; - public function __construct(CustomFieldProvider $compiler) + + public function __construct(CustomFieldProvider $compiler, + ObjectManager $om) { $this->customFieldProvider = $compiler; + $this->om = $om; } /** * @param FormBuilderInterface $builder @@ -40,12 +49,28 @@ class CustomFieldType extends AbstractType $builder ->add('name', 'translatable_string') - ->add('active', 'checkbox', array('required' => false)) - ->add('customFieldsGroup', 'entity', array( + ->add('active', 'checkbox', array('required' => false)); + + if ($options['group_widget'] === 'entity') { + $builder->add('customFieldsGroup', 'entity', array( 'class' => 'ChillCustomFieldsBundle:CustomFieldsGroup', 'property' => 'name['.$this->culture.']' - )) + )); + } elseif ($options['group_widget'] === 'hidden') { + $builder->add('customFieldsGroup', 'hidden'); + $builder->get('customFieldsGroup') + ->addViewTransformer(new CustomFieldsGroupToIdTransformer($this->om)); + } else { + throw new \LogicException('The value of group_widget is not handled'); + } + + $builder ->add('ordering', 'number') + ->add('required', 'checkbox', array( + 'required' => false, + //'expanded' => TRUE, + 'label' => 'Required field' + )) ->add('type', 'hidden', array('data' => $options['type'])) ->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) { @@ -62,14 +87,14 @@ class CustomFieldType extends AbstractType $builder->add( - $this->customFieldProvider - ->getCustomFieldByType($options['type']) - ->buildOptionsForm( - $builder - ->create('options', null, array('compound' => true)) + $this->customFieldProvider + ->getCustomFieldByType($options['type']) + ->buildOptionsForm( + $builder + ->create('options', null, array('compound' => true)) + ->setRequired(false) ) - ); - + ); } /** @@ -81,10 +106,12 @@ class CustomFieldType extends AbstractType 'data_class' => 'Chill\CustomFieldsBundle\Entity\CustomField' )); - $resolver->setRequired(array('type')) - ->addAllowedValues(array('type' => - array_keys($this->customFieldProvider->getAllFields()) - )); + $resolver->setRequired(array('type', 'group_widget')) + ->addAllowedValues(array( + 'type' => array_keys($this->customFieldProvider->getAllFields()), + 'group_widget' => array('hidden', 'entity') + )) + ->setDefault('group_widget', 'entity'); } /** diff --git a/Form/DataTransformer/CustomFieldsGroupToIdTransformer.php b/Form/DataTransformer/CustomFieldsGroupToIdTransformer.php index 662ef42f1..f2dac9b0c 100644 --- a/Form/DataTransformer/CustomFieldsGroupToIdTransformer.php +++ b/Form/DataTransformer/CustomFieldsGroupToIdTransformer.php @@ -33,6 +33,14 @@ class CustomFieldsGroupToIdTransformer implements DataTransformerInterface if (null === $customFieldsGroup) { return ""; } + + if (!$customFieldsGroup instanceof CustomFieldsGroup) { + throw new TransformationFailedException(sprintf('Transformation failed: ' + . 'the expected type of the transforme function is an ' + . 'object of type Chill\CustomFieldsBundle\Entity\CustomFieldsGroup, ' + . '%s given (value : %s)', gettype($customFieldsGroup), + $customFieldsGroup)); + } return $customFieldsGroup->getId(); } @@ -49,6 +57,14 @@ class CustomFieldsGroupToIdTransformer implements DataTransformerInterface if (!$id) { return null; } + + if ($id instanceof CustomFieldsGroup) { + throw new TransformationFailedException(sprintf( + 'The transformation failed: the expected argument on ' + . 'reverseTransform is an object of type int,' + . 'Chill\CustomFieldsBundle\Entity\CustomFieldsGroup, ' + . 'given', gettype($id))); + } $customFieldsGroup = $this->om ->getRepository('ChillCustomFieldsBundle:customFieldsGroup')->find($id) diff --git a/Form/Extension/PostTextExtension.php b/Form/Extension/PostTextExtension.php new file mode 100644 index 000000000..24cf4e4f4 --- /dev/null +++ b/Form/Extension/PostTextExtension.php @@ -0,0 +1,53 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace Chill\CustomFieldsBundle\Form\Extension; + +use Symfony\Component\Form\AbstractTypeExtension; +use Symfony\Component\OptionsResolver\OptionsResolver; +use Symfony\Component\Form\FormView; +use Symfony\Component\Form\FormInterface; +use Symfony\Component\PropertyAccess\PropertyAccess; + +/** + * This extension create the possibility to add some text + * after the input. + * + * This can be used to print the units of the field, or some text. + * + * This class must be extended by Extension class specifics to each input. + * + * @author Julien Fastré + */ +abstract class PostTextExtension extends AbstractTypeExtension +{ + public function configureOptions(OptionsResolver $resolver) + { + $resolver->setDefined(array('post_text')); + } + + public function buildView(FormView $view, FormInterface $form, array $options) + { + if (array_key_exists('post_text', $options)) { + //set the post text variable to the view + $view->vars['post_text'] = $options['post_text']; + } + } + +} diff --git a/Form/Extension/PostTextIntegerExtension.php b/Form/Extension/PostTextIntegerExtension.php new file mode 100644 index 000000000..1829501b8 --- /dev/null +++ b/Form/Extension/PostTextIntegerExtension.php @@ -0,0 +1,37 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace Chill\CustomFieldsBundle\Form\Extension; + +use Symfony\Component\Form\Extension\Core\Type\IntegerType; + +/** + * This class add the PostTextExtension to integer fields + * + * @author Julien Fastré + */ +class PostTextIntegerExtension extends PostTextExtension +{ + public function getExtendedType() + { + // return IntegerType::class; !! only for symfony 2.8 + return 'integer'; + } + +} diff --git a/Form/Extension/PostTextNumberExtension.php b/Form/Extension/PostTextNumberExtension.php new file mode 100644 index 000000000..5cbaeca34 --- /dev/null +++ b/Form/Extension/PostTextNumberExtension.php @@ -0,0 +1,34 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace Chill\CustomFieldsBundle\Form\Extension; + +/** + * This class add the PostTextExtension to number fields + * + * @author Julien Fastré + */ +class PostTextNumberExtension extends PostTextExtension +{ + public function getExtendedType() + { + return 'number'; + } + +} diff --git a/Form/Type/ChoicesListType.php b/Form/Type/ChoicesListType.php index 26ad33bf5..120559920 100644 --- a/Form/Type/ChoicesListType.php +++ b/Form/Type/ChoicesListType.php @@ -25,8 +25,7 @@ class ChoicesListType extends AbstractType $builder->add('name', 'translatable_string') ->add('active', 'checkbox', array( - 'required' => false, - 'empty_data' => true + 'required' => false )) ->add('slug', 'hidden', array( @@ -39,9 +38,9 @@ class ChoicesListType extends AbstractType if (NULL === $formData['slug']) { $slug = $form['name'][$locales[0]]->getData(); - $slug= strtolower($slug); - $slug= preg_replace('/[^a-zA-Z0-9 -]/','', $slug); // only take alphanumerical characters, but keep the spaces and dashes too... - $slug= str_replace(' ','-', $slug); // replace spaces by dashes + $slug = strtolower($slug); + $slug = preg_replace('/[^a-zA-Z0-9 -]/','', $slug); // only take alphanumerical characters, but keep the spaces and dashes too... + $slug = str_replace(' ','-', $slug); // replace spaces by dashes $data['slug'] = $slug; $event->setData($data); diff --git a/Form/Type/ChoicesType.php b/Form/Type/ChoicesType.php index 75d409bfc..581b2620b 100644 --- a/Form/Type/ChoicesType.php +++ b/Form/Type/ChoicesType.php @@ -21,6 +21,4 @@ class ChoicesType extends AbstractType { return 'collection'; } - - } \ No newline at end of file diff --git a/Form/Type/CustomFieldType.php b/Form/Type/CustomFieldType.php index 8a202e369..5e6ad0379 100644 --- a/Form/Type/CustomFieldType.php +++ b/Form/Type/CustomFieldType.php @@ -20,6 +20,7 @@ use Chill\CustomFieldsBundle\Service\CustomFieldProvider; use Chill\CustomFieldsBundle\Form\DataTransformer\CustomFieldDataTransformer; use Chill\CustomFieldsBundle\Entity\CustomFieldsGroup; use Symfony\Component\OptionsResolver\OptionsResolver; +use Chill\CustomFieldsBundle\CustomFields\CustomFieldTitle; class CustomFieldType extends AbstractType { @@ -50,6 +51,7 @@ class CustomFieldType extends AbstractType $this->customFieldCompiler ->getCustomFieldByType($cf->getType()) ->buildForm($builder, $cf); + $builder->get($cf->getSlug())->setRequired($cf->isRequired()); } } diff --git a/README.md b/README.md index fa6aeb184..08a2f468c 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,9 @@ CustomFields ============ -[![Build Status](https://travis-ci.org/Chill-project/CustomFields.png)](http://travis-ci.org/#!/Chill-project/CustomFields) +The bundle for adding custom fields to Chill. This bundle is part of the Chill project. -Add custom fields to entities +Documentation & installation +============================ -This bundle is part of the Chill project. +Read documentation here : http://chill.readthedocs.org diff --git a/Resources/config/doctrine/CustomField.orm.yml b/Resources/config/doctrine/CustomField.orm.yml index 595e96db5..5e6651158 100644 --- a/Resources/config/doctrine/CustomField.orm.yml +++ b/Resources/config/doctrine/CustomField.orm.yml @@ -22,6 +22,8 @@ Chill\CustomFieldsBundle\Entity\CustomField: type: float options: type: json_array + required: + type: boolean lifecycleCallbacks: { } manyToOne: customFieldGroup: 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/routing.yml b/Resources/config/routing.yml index 42718a48c..a6b554e69 100644 --- a/Resources/config/routing.yml +++ b/Resources/config/routing.yml @@ -5,7 +5,3 @@ chill_customfields_customfieldsgroup: chill_customfields_customfield: resource: "@ChillCustomFieldsBundle/Resources/config/routing/customfield.yml" prefix: / - -chill_customfields_customfieldsdefaultgroup: - resource: "@ChillCustomFieldsBundle/Resources/config/routing/customfieldsdefaultgroup.yml" - prefix: / \ No newline at end of file diff --git a/Resources/config/routing/customfield.yml b/Resources/config/routing/customfield.yml index eb92fdc96..eabef0fad 100644 --- a/Resources/config/routing/customfield.yml +++ b/Resources/config/routing/customfield.yml @@ -1,15 +1,12 @@ -customfield: +customfield_section: path: /{_locale}/admin/customfield/ - defaults: { _controller: "ChillCustomFieldsBundle:CustomField:index" } + defaults: { _controller: "ChillCustomFieldsBundle:Admin:index" } options: menus: - admin: + admin_section: order: 1000 - label: "CustomFields" - -customfield_show: - path: /{_locale}/admin/customfield/{id}/show - defaults: { _controller: "ChillCustomFieldsBundle:CustomField:show" } + label: "Custom fields configuration" + icons: ['asterisk'] customfield_new: path: /{_locale}/admin/customfield/new @@ -28,8 +25,3 @@ customfield_update: path: /{_locale}/admin/customfield/{id}/update defaults: { _controller: "ChillCustomFieldsBundle:CustomField:update" } requirements: { _method: post|put } - -customfield_delete: - path: /{_locale}/admin/customfield/{id}/delete - defaults: { _controller: "ChillCustomFieldsBundle:CustomField:delete" } - requirements: { _method: post|delete } diff --git a/Resources/config/routing/customfieldsdefaultgroup.yml b/Resources/config/routing/customfieldsdefaultgroup.yml deleted file mode 100644 index d16434e8d..000000000 --- a/Resources/config/routing/customfieldsdefaultgroup.yml +++ /dev/null @@ -1,12 +0,0 @@ -customfieldsdefaultgroup: - path: /{_locale}/admin/customfieldsdefaultgroup/ - defaults: { _controller: "ChillCustomFieldsBundle:CustomFieldsDefaultGroup:list" } - options: - menus: - admin: - order: 1000 - label: "CustomFields Default Groups : List" - -customfieldsdefaultgroup_set: - path: /{_locale}/admin/customfieldsdefaultgroup/set/group/as/default/ - defaults: { _controller: "ChillCustomFieldsBundle:CustomFieldsDefaultGroup:setAGroupAsDefault" } \ No newline at end of file diff --git a/Resources/config/routing/customfieldsgroup.yml b/Resources/config/routing/customfieldsgroup.yml index c8a992856..446a15aa3 100644 --- a/Resources/config/routing/customfieldsgroup.yml +++ b/Resources/config/routing/customfieldsgroup.yml @@ -3,13 +3,17 @@ customfieldsgroup: defaults: { _controller: "ChillCustomFieldsBundle:CustomFieldsGroup:index" } options: menus: - admin: + admin_custom_fields: order: 1010 label: "CustomFields Groups" customfieldsgroup_show: path: /{_locale}/admin/customfieldsgroup/{id}/show defaults: { _controller: "ChillCustomFieldsBundle:CustomFieldsGroup:show" } + +customfieldsgroup_makedefault: + path: /{_locale}/admin/customfieldsgroup/make_default + defaults: { _controller: "ChillCustomFieldsBundle:CustomFieldsGroup:makeDefault" } customfieldsgroup_new: path: /{_locale}/admin/customfieldsgroup/new diff --git a/Resources/config/services.yml b/Resources/config/services.yml index af7b05b91..9af084616 100644 --- a/Resources/config/services.yml +++ b/Resources/config/services.yml @@ -11,6 +11,7 @@ services: class: Chill\CustomFieldsBundle\Form\CustomFieldType arguments: - "@chill.custom_field.provider" + - "@doctrine.orm.entity_manager" tags: - { name: 'form.type', alias: 'custom_field_choice' } @@ -38,6 +39,24 @@ services: - "@chill.main.helper.translatable_string" tags: - { name: 'chill.custom_field', type: 'text' } + + chill.custom_field.number: + class: Chill\CustomFieldsBundle\CustomFields\CustomFieldNumber + arguments: + - "@templating" + - "@chill.main.helper.translatable_string" + tags: + - { name: 'chill.custom_field', type: 'number' } + + chill.form_extension.post_text_integer: + class: Chill\CustomFieldsBundle\Form\Extension\PostTextIntegerExtension + tags: + - { name: form.type_extension, alias: 'integer' } + + chill.form_extension.post_text_number: + class: Chill\CustomFieldsBundle\Form\Extension\PostTextNumberExtension + tags: + - { name: form.type_extension, alias: 'number' } chill.custom_field.choice: class: Chill\CustomFieldsBundle\CustomFields\CustomFieldChoice @@ -87,5 +106,23 @@ services: class: Chill\CustomFieldsBundle\Templating\Twig\CustomFieldsGroupRenderingTwig calls: - [setContainer, ["@service_container"]] + arguments: + - "%chill_custom_fields.show_empty_values%" 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/Version20151210155904.php b/Resources/migrations/Version20151210155904.php new file mode 100644 index 000000000..f3d9eadf3 --- /dev/null +++ b/Resources/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) + { + // 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/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 78b57984c..a80f4ea08 100644 --- a/Resources/translations/messages.fr.yml +++ b/Resources/translations/messages.fr.yml @@ -1,3 +1,96 @@ '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 + +#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 diff --git a/Resources/views/Admin/layout.html.twig b/Resources/views/Admin/layout.html.twig new file mode 100644 index 000000000..27c0c61ac --- /dev/null +++ b/Resources/views/Admin/layout.html.twig @@ -0,0 +1,31 @@ +{# + * Copyright (C) 2014-2015, 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 "ChillMainBundle::Admin/layoutWithVerticalMenu.html.twig" %} + +{% block vertical_menu_content %} + {{ chill_menu('admin_custom_fields', { + 'layout': 'ChillCustomFieldsBundle::Admin/menu.html.twig', + }) }} +{% endblock %} + +{% block layout_wvm_content %} + {% block admin_content %} +

{{ 'CustomFields configuration' |trans }}

+ {% endblock %} +{% endblock %} \ No newline at end of file diff --git a/Resources/views/Admin/menu.html.twig b/Resources/views/Admin/menu.html.twig new file mode 100644 index 000000000..aeefd1013 --- /dev/null +++ b/Resources/views/Admin/menu.html.twig @@ -0,0 +1,20 @@ +{# + * Copyright (C) 2014-2015, 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 "ChillMainBundle::Menu/verticalMenu.html.twig" %} +{% block v_menu_title %}{{ 'Custom fields configuration menu'|trans }}{% endblock %} \ No newline at end of file diff --git a/Resources/views/CustomField/edit.html.twig b/Resources/views/CustomField/edit.html.twig index 47f98ee92..019a02d95 100644 --- a/Resources/views/CustomField/edit.html.twig +++ b/Resources/views/CustomField/edit.html.twig @@ -14,19 +14,36 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . #} -{% extends "ChillMainBundle::Admin/layout.html.twig" %} +{% extends "ChillCustomFieldsBundle::Admin/layout.html.twig" %} + +{% block title %}{{ 'CustomField edit'|trans }}{% endblock title %} {% block admin_content %} -

CustomField edit

+

{{ 'CustomField edit'|trans }}

- {{ form(edit_form) }} +

{{ 'General informations'|trans }}

+ {{ form_start(edit_form) }} + {{ form_row(edit_form.name) }} + {{ form_row(edit_form.active) }} + {% if edit_form.customFieldsGroup is defined %} + {{ form_row(edit_form.customFieldsGroup) }} + {% endif %} + {{ form_row(edit_form.ordering) }} + {{ form_row(edit_form.required) }} + {% if edit_form.options is not empty %} +

{{ 'Options'|trans }}

+ {% for option in edit_form.options %} + {{ form_row(option) }} + {% endfor %} + {% endif %} + {{ form_row(edit_form.submit, {'attr': { 'class': 'sc-button btn-update' } } ) }} + {{ form_end(edit_form) }} {% endblock %} diff --git a/Resources/views/CustomField/index.html.twig b/Resources/views/CustomField/index.html.twig deleted file mode 100644 index 1802c3a9c..000000000 --- a/Resources/views/CustomField/index.html.twig +++ /dev/null @@ -1,66 +0,0 @@ -{# - * 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 "ChillMainBundle::Admin/layout.html.twig" %} - -{% block admin_content %} -

CustomField list

- - - - - - - - - - - - - {% for entity in entities %} - - - - - - - - {% endfor %} - -
IdLabelTypeActiveActions
{{ entity.id }}{{ entity.name(app.request.locale) }}{{ entity.type }}{{ entity.active }} - -
- -
    -
  • - {{ form_start(form) }} - - {{ form_row(form) }} - - - {{ form_end(form) }} -
  • -
- {% endblock %} diff --git a/Resources/views/CustomField/new.html.twig b/Resources/views/CustomField/new.html.twig index 771abbd10..d06ee0cc3 100644 --- a/Resources/views/CustomField/new.html.twig +++ b/Resources/views/CustomField/new.html.twig @@ -14,17 +14,43 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . #} -{% extends "ChillMainBundle::Admin/layout.html.twig" %} +{% extends "ChillCustomFieldsBundle::Admin/layout.html.twig" %} + +{% block title %}{{ 'CustomField creation'|trans }}{% endblock title %} {% block admin_content %} -

CustomField creation

+

{{ 'CustomField creation'|trans }}

- {{ form(form) }} -
    +

    {{ 'General informations'|trans }}

    + {{ form_start(form) }} + {{ form_row(form.name) }} + {{ form_row(form.active) }} + {{ form_row(form.slug) }} + {% if form.customFieldsGroup is defined %} + {{ form_row(form.customFieldsGroup) }} + {% endif %} + {{ form_row(form.ordering) }} + {{ form_row(form.required) }} + {% if form.options is not empty %} +

    {{ 'Options'|trans }}

    + {% for option in form.options %} + {{ form_row(option) }} + {% endfor %} + {% endif %} + {{ form_row(form.submit, {'attr': { 'class': 'sc-button btn-create' } } ) }} + {{ form_end(form) }} + + {% endblock %} diff --git a/Resources/views/CustomField/render_label.html.twig b/Resources/views/CustomField/render_label.html.twig index bf4b186fc..05bc19cc3 100644 --- a/Resources/views/CustomField/render_label.html.twig +++ b/Resources/views/CustomField/render_label.html.twig @@ -1 +1 @@ -{{ customField.name(app.request.locale) }} \ No newline at end of file +{{ customField.name|localize_translatable_string }} \ No newline at end of file diff --git a/Resources/views/CustomField/show.html.twig b/Resources/views/CustomField/show.html.twig index 451a544db..69a4d9e25 100644 --- a/Resources/views/CustomField/show.html.twig +++ b/Resources/views/CustomField/show.html.twig @@ -14,7 +14,7 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . #} -{% extends "ChillMainBundle::Admin/layout.html.twig" %} +{% extends "ChillCustomFieldsBundle::Admin/layout.html.twig" %} {% block admin_content %}

    CustomField

    diff --git a/Resources/views/CustomFieldsDefaultGroup/list.html.twig b/Resources/views/CustomFieldsDefaultGroup/list.html.twig deleted file mode 100644 index 560aedc8d..000000000 --- a/Resources/views/CustomFieldsDefaultGroup/list.html.twig +++ /dev/null @@ -1,46 +0,0 @@ -{# - * 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 "ChillMainBundle::Admin/layout.html.twig" %} - -{% block admin_content %} -

    CustomFieldsDefaultGroup list

    - - - - - - - - - - {% for defaultGroup in defaultGroups %} - - - - - {% endfor %} - -
    EntityCustomFieldGroup
    {{ defaultGroup.entity }}{{ defaultGroup.customFieldsGroup.name['fr'] }}
    - - {{ form_start(form) }} - {{ form_row(form.cFGroup) }} - - - {{ form_end(form) }} -{% endblock %} diff --git a/Resources/views/CustomFieldsGroup/edit.html.twig b/Resources/views/CustomFieldsGroup/edit.html.twig index 57f86745e..823f492c2 100644 --- a/Resources/views/CustomFieldsGroup/edit.html.twig +++ b/Resources/views/CustomFieldsGroup/edit.html.twig @@ -14,19 +14,32 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . #} -{% extends "ChillMainBundle::Admin/layout.html.twig" %} +{% extends "ChillCustomFieldsBundle::Admin/layout.html.twig" %} + +{% block title %}{{ 'CustomFieldsGroup edit'|trans }}{% endblock %} {% block admin_content %} -

    CustomFieldsGroup edit

    +

    {{ 'CustomFieldsGroup edit'|trans }}

    - {{ form(edit_form) }} + {{ form_start(edit_form) }} + {{ form_row(edit_form.name) }} + {{ form_row(edit_form.entity) }} + {% if edit_form.options is defined %} + {{ form_row(edit_form.options) }} + {% endif %} + {{ form_row(edit_form.submit, { 'attr': { 'class': 'sc-button bt-edit' } } ) }} + {{ form_end(edit_form) }} {% endblock %} diff --git a/Resources/views/CustomFieldsGroup/index.html.twig b/Resources/views/CustomFieldsGroup/index.html.twig index bea9b6409..28ec9713a 100644 --- a/Resources/views/CustomFieldsGroup/index.html.twig +++ b/Resources/views/CustomFieldsGroup/index.html.twig @@ -14,33 +14,43 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . #} -{% extends "ChillMainBundle::Admin/layout.html.twig" %} +{% extends "ChillCustomFieldsBundle::Admin/layout.html.twig" %} + +{% block title %}{{ 'CustomFieldsGroup list'|trans }}{% endblock %} {% block admin_content %} -

    CustomFieldsGroup list

    +

    {{ 'CustomFieldsGroup list'|trans }}

    - - - - + + + + {% for entity in entities %} - - - + + + @@ -49,11 +59,9 @@
    IdNameEntityActions{{ 'Name'|trans }}{{ 'Entity'|trans }}{{ 'Is default ?'|trans }} {{ 'Actions'|trans }}
    {{ entity.id }}{{ entity.name['fr'] }}{{ entity.entity }}{{ entity.name|localize_translatable_string }}{{ entity.entity|trans }} + {%- if entity.id in default_groups -%} + + {%- else -%} + {{ form_start(make_default_forms[entity.id]) }} + {{ form_widget(make_default_forms[entity.id].submit, { 'attr' : { 'class' : 'sc-button bt-action' } } ) }} + {{ form_end(make_default_forms[entity.id]) }} + {%- endif -%} + -
    - +

    + + {{ 'Create a new group'|trans }} + +

    {% endblock %} diff --git a/Resources/views/CustomFieldsGroup/new.html.twig b/Resources/views/CustomFieldsGroup/new.html.twig index b83aec347..3c0692773 100644 --- a/Resources/views/CustomFieldsGroup/new.html.twig +++ b/Resources/views/CustomFieldsGroup/new.html.twig @@ -14,18 +14,19 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . #} -{% extends "ChillMainBundle::Admin/layout.html.twig" %} +{% extends "ChillCustomFieldsBundle::Admin/layout.html.twig" %} {% block admin_content %} -

    CustomFieldsGroup creation

    +

    {{ 'CustomFieldsGroup creation'|trans }}

    - {{ form(form) }} - - +

    + {{ form_end(form) }} {% endblock %} diff --git a/Resources/views/CustomFieldsGroup/render.html.twig b/Resources/views/CustomFieldsGroup/render.html.twig index a63183233..fb293cf92 100644 --- a/Resources/views/CustomFieldsGroup/render.html.twig +++ b/Resources/views/CustomFieldsGroup/render.html.twig @@ -1,8 +1,43 @@ +{#- a customField element will be stored in title variable -#} +{%- set title = null -%} +{#- a customField element will be stored in subtitle variable -#} +{%- set subtitle = null -%} +{%- set type = constant('Chill\\CustomFieldsBundle\\CustomFields\\CustomFieldTitle::TYPE') -%} +{%- set type_subtitle = constant('Chill\\CustomFieldsBundle\\CustomFields\\CustomFieldTitle::TYPE_SUBTITLE') -%} +{%- set type_title = constant('Chill\\CustomFieldsBundle\\CustomFields\\CustomFieldTitle::TYPE_TITLE') -%} +{# a variable to store that "something has been printed #} +{%- set something_has_been_printed = false -%} {% for customField in cFGroup.activeCustomFields %} {% if customField.type == 'title' %} + {%- if show_empty == true %} {{ 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 %} -
    {{ chill_custom_field_label(customField) }}
    -
    {{ chill_custom_field_widget(cFData , customField) }}
    - {% endif %} -{% endfor %} \ No newline at end of file + {%- if show_empty == true or (chill_custom_field_is_empty(customField, cFData) == false) -%} + {%- if title is not empty -%} + {{ chill_custom_field_widget(cFData, title) }} + {%- set title = null -%} + {%- endif -%} + {%- if subtitle is not empty -%} + {{ 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 something_has_been_printed == false %} +
    {{ 'Empty data'|trans }}
    +
    {{ 'No data to show' | trans }}
    +{% endif %} \ No newline at end of file diff --git a/Resources/views/CustomFieldsGroup/show.html.twig b/Resources/views/CustomFieldsGroup/show.html.twig index 5fd8681bb..d2f9d4e33 100644 --- a/Resources/views/CustomFieldsGroup/show.html.twig +++ b/Resources/views/CustomFieldsGroup/show.html.twig @@ -14,39 +14,103 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . #} -{% extends "ChillMainBundle::Admin/layout.html.twig" %} +{% extends "ChillCustomFieldsBundle::Admin/layout.html.twig" %} + +{% block title %}{{ 'CustomFieldsGroup details'|trans }}{% endblock %} {% block admin_content %} -

    CustomFieldsGroup

    +

    {{ 'CustomFieldsGroup details'|trans }}

    - - + + - - + + + {%- for key in options -%} - - + + + {%- else -%} + + {%- endfor -%}
    Id{{ entity.id }}{{ 'Name'|trans }}{{ entity.getName|localize_translatable_string }}
    Name{{ entity.getName(app.request.locale) }}{{ 'Entity'|trans }}{{ entity.entity|trans }}
    Entity{{ entity.entity }}{{ 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 -%} +
    - + + +

    {{ 'Fields associated with this group'|trans }}

    + + {%- if entity.customFields|length > 0 -%} + + + + + + + + + + + + {%- for field in entity.customFields -%} + + + + + + + + {%- endfor -%} + +
    {{ 'ordering'|trans|capitalize }}{{ 'label_field'|trans|capitalize }}{{ 'type'|trans|capitalize }}{{ 'active'|trans|capitalize }} 
    {{ field.ordering }}{{ field.name|localize_translatable_string }}{{ field.type|trans }} + {%- if field.active -%} + + {%- else -%} + + {%- endif -%} + + {{ 'edit'|trans|capitalize }} +
    + {{ 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/Resources/views/CustomFieldsRendering/choice.html.twig b/Resources/views/CustomFieldsRendering/choice.html.twig index c4dc4328f..02f6ab13b 100644 --- a/Resources/views/CustomFieldsRendering/choice.html.twig +++ b/Resources/views/CustomFieldsRendering/choice.html.twig @@ -1,23 +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 -%} -
    +
      + {%- 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/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/Resources/views/CustomFieldsRendering/number.html.twig b/Resources/views/CustomFieldsRendering/number.html.twig new file mode 100644 index 000000000..894d34fae --- /dev/null +++ b/Resources/views/CustomFieldsRendering/number.html.twig @@ -0,0 +1 @@ +{% if number is not empty %}{{ number|number_format(scale) }} {{ post|default('') }}{% endif %} diff --git a/Resources/views/Form/fields.html.twig b/Resources/views/Form/fields.html.twig index a097bc78f..990f9719f 100644 --- a/Resources/views/Form/fields.html.twig +++ b/Resources/views/Form/fields.html.twig @@ -25,83 +25,60 @@ {# 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) }} - + {{ form_row(form.name) }} + {{ form_row(form.active) }} + {{ form_row(form.slug) }} {% endblock cf_choices_list_widget %} -{# render the possibility to add different elements in a choice list #} -{% block cf_choices_widget %} +{# CFChoice : render the different elements in a choice list #} +{% block cf_choices_row %} +

    {{ 'Choices'|trans }}

    + +
    + + {% for choice in form %} + + {% endfor %} +
    + {{ form_row(choice.name) }} + {{ form_row(choice.active) }} + {{ form_row(choice.slug) }} +
    +
    + + + {# we use javascrit to add an additional element. All functions are personnalized with the id ( = form.vars.id) #} + +{% endblock cf_choices_row %} - // add the "add a tag" anchor and li to the tags ul - $collectionHolder_{{ form.vars.id }}.append($newLinkLi_{{ form.vars.id }}); +{# The choice_with_other_widget widget is defined in the main bundle #} - // count the current form inputs we have (e.g. 2), use that as the new - // index when inserting a new item (e.g. 2) - $collectionHolder_{{ form.vars.id }}.data('index', $collectionHolder_{{ form.vars.id }}.find(':input').length); - - $addTagLink_{{ form.vars.id }}.on('click', function(e) { - // prevent the link from creating a "#" on the URL - e.preventDefault(); - - // add a new tag form (see next code block) - addTagForm_{{ form.vars.id }}($collectionHolder_{{ form.vars.id }}, $newLinkLi_{{ form.vars.id }}); - }); -}; - -function addTagForm_{{ form.vars.id }}(collection, newLinkLi) { - console.log($collectionHolder_{{ form.vars.id }}); - // Get the data-prototype explained earlier - var prototype = $( '#' + '{{ form.vars.id }}').data('prototype'); - console.log(prototype); - // get the new index - var index = collection.data('index'); - - // Replace '__name__' in the prototype's HTML to - // instead be a number based on how many items we have - var newForm = prototype.replace(/__name__/g, index); - - // increase the index with one for the next item - collection.data('index', index + 1); - - // Display the form in the page in an li, before the "Add a tag" link li - var $newFormLi = $('
  • ').append(newForm); - newLinkLi.before($newFormLi); -}; - - -jQuery(document).ready(initialize_{{ form.vars.id }}('ul_' + '{{ form.vars.id }}')); - - - -{% endblock cf_choices_widget %} - -{% block choice_with_other_widget %} -
    -{%- for child in form.children._choices %} -{{- form_widget(child) -}} -{{- form_label(child) -}} -{%- if child.vars.value == '_other' -%} -{{- form_widget(form.children._other) -}} -{%- endif -%} -{% endfor -%} -
    - -{% endblock choice_with_other_widget %} \ No newline at end of file diff --git a/Service/CustomFieldsHelper.php b/Service/CustomFieldsHelper.php index 477c190ad..07c8c4f04 100644 --- a/Service/CustomFieldsHelper.php +++ b/Service/CustomFieldsHelper.php @@ -137,6 +137,17 @@ class CustomFieldsHelper : null; } + public function isEmptyValue(array $fields, $classOrCustomField, $slug = null) + { + $customField = ($classOrCustomField instanceof CustomField) ? $classOrCustomField : $this->getCustomField($classOrCustomField, $slug); + $slug = $customField->getSlug(); + $rawValue = (isset($fields[$slug])) ? $fields[$slug] : null; + + $customFieldType = $this->provider->getCustomFieldByType($customField->getType()); + + return $customFieldType->isEmptyValue($rawValue, $customField); + } + /** * Render the value of a custom field * @@ -144,16 +155,17 @@ class CustomFieldsHelper * @param CustomField|object|string $classOrCustomField the object OR the get_class($object) string OR The CustomField * @param string $documentType The type of document in which the rendered value is displayed ('html' or 'csv'). * @param string $slug The slug of the custom field to render. + * @param boolean $showIfEmpty If the widget must be rendered if the value is empty. An empty value is all values described as http://php.net/manual/fr/function.empty.php, except `FALSE` * @throws CustomFieldsHelperException if slug is missing * @return The representation of the value the customField. */ - public function renderCustomField(array $fields, $classOrCustomField, $documentType='html', $slug = null) + public function renderCustomField(array $fields, $classOrCustomField, $documentType='html', $slug = null, $showIfEmpty = true) { $customField = ($classOrCustomField instanceof CustomField) ? $classOrCustomField : $this->getCustomField($classOrCustomField, $slug); $slug = $customField->getSlug(); $rawValue = (isset($fields[$slug])) ? $fields[$slug] : null; + $customFieldType = $this->provider->getCustomFieldByType($customField->getType()); - return $this->provider->getCustomFieldByType($customField->getType()) - ->render($rawValue, $customField, $documentType); + return $customFieldType->render($rawValue, $customField, $documentType); } } \ No newline at end of file diff --git a/Templating/Twig/CustomFieldRenderingTwig.php b/Templating/Twig/CustomFieldRenderingTwig.php index be155abe3..8561d26f8 100644 --- a/Templating/Twig/CustomFieldRenderingTwig.php +++ b/Templating/Twig/CustomFieldRenderingTwig.php @@ -76,10 +76,21 @@ class CustomFieldRenderingTwig extends \Twig_Extension implements ContainerAware 'is_safe' => array( 'html' ) + )), + new \Twig_SimpleFunction('chill_custom_field_is_empty', array( + $this, + 'isEmptyValue' )) ]; } + + public function isEmptyValue($customFieldorClass, $fields, $slug = null) + { + return $this->container->get('chill.custom_field.helper') + ->isEmptyValue($fields, $customFieldorClass); + } + /* (non-PHPdoc) * @see Twig_ExtensionInterface::getName() */ diff --git a/Templating/Twig/CustomFieldsGroupRenderingTwig.php b/Templating/Twig/CustomFieldsGroupRenderingTwig.php index af6dbb585..a6b9305c0 100644 --- a/Templating/Twig/CustomFieldsGroupRenderingTwig.php +++ b/Templating/Twig/CustomFieldsGroupRenderingTwig.php @@ -37,15 +37,25 @@ use Chill\CustomFieldsBundle\Entity\CustomField; */ class CustomFieldsGroupRenderingTwig extends \Twig_Extension implements ContainerAwareInterface { - + /** @var Container $container The container */ private $container; /** @var array $defaultParams The default parameters */ private $defaultParams = array( - 'layout' => 'ChillCustomFieldsBundle:CustomFieldsGroup:render.html.twig' + '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() @@ -92,6 +102,7 @@ class CustomFieldsGroupRenderingTwig extends \Twig_Extension implements Containe * @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 */ @@ -102,6 +113,7 @@ class CustomFieldsGroupRenderingTwig extends \Twig_Extension implements Containe return $this->container->get('templating') ->render($resolvedParams['layout'], array( 'cFGroup' => $customFielsGroup, - 'cFData' => $fields)); + 'cFData' => $fields, + 'show_empty' => $resolvedParams['show_empty'])); } } \ No newline at end of file diff --git a/Tests/Config/ConfigCustomizablesEntitiesTest.php b/Tests/Config/ConfigCustomizablesEntitiesTest.php index deb9585e2..df66ab56c 100644 --- a/Tests/Config/ConfigCustomizablesEntitiesTest.php +++ b/Tests/Config/ConfigCustomizablesEntitiesTest.php @@ -43,7 +43,7 @@ class ConfigCustomizablesEntitiesTest extends KernelTestCase ->getParameter('chill_custom_fields.customizables_entities'); $this->assertInternalType('array', $customizableEntities); - $this->assertCount(0, $customizableEntities); + $this->assertCount(1, $customizableEntities); } /** @@ -59,7 +59,7 @@ class ConfigCustomizablesEntitiesTest extends KernelTestCase ->getParameter('chill_custom_fields.customizables_entities'); $this->assertInternalType('array', $customizableEntities); - $this->assertCount(1, $customizableEntities); + $this->assertCount(2, $customizableEntities); foreach($customizableEntities as $key => $config) { $this->assertInternalType('array', $config); diff --git a/Tests/Controller/CustomFieldsGroupControllerTest.php b/Tests/Controller/CustomFieldsGroupControllerTest.php new file mode 100644 index 000000000..420c4bf4c --- /dev/null +++ b/Tests/Controller/CustomFieldsGroupControllerTest.php @@ -0,0 +1,70 @@ + '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'); + $crawler = $client->click($links->last()->link()); + + $this->assertEquals(200, $client->getResponse()->getStatusCode()); + + $form = $crawler->selectButton('Update')->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/Tests/Controller/CustomFieldsGroupControllerTest_TODO.php b/Tests/Controller/CustomFieldsGroupControllerTest_TODO.php deleted file mode 100644 index afa7bad25..000000000 --- a/Tests/Controller/CustomFieldsGroupControllerTest_TODO.php +++ /dev/null @@ -1,55 +0,0 @@ -request('GET', '/customfieldsgroup/'); - $this->assertEquals(200, $client->getResponse()->getStatusCode(), "Unexpected HTTP status code for GET /customfieldsgroup/"); - $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_customfieldsgroup[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_customfieldsgroup[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/Tests/CustomFields/CustomFieldsChoiceTest.php b/Tests/CustomFields/CustomFieldsChoiceTest.php new file mode 100644 index 000000000..6fbc0b67e --- /dev/null +++ b/Tests/CustomFields/CustomFieldsChoiceTest.php @@ -0,0 +1,422 @@ + + * + * 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); + } + + /** + * 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); + + } + + /** + * 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); + } + + /** + * 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); + } + + 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 + )); + + $isEmpty = $this->cfChoice->isEmptyValue($data, $customField); + + $this->assertFalse($isEmpty); + } + + /** + * + * @dataProvider emptyDataProvider + * @param mixed $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 reprsentation + * + * @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()) + ), + ); + } + +} diff --git a/Tests/CustomFields/CustomFieldsNumberTest.php b/Tests/CustomFields/CustomFieldsNumberTest.php new file mode 100644 index 000000000..fb4d44a26 --- /dev/null +++ b/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('custom_field', 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('custom_field', array(), array( + 'group' => $cfGroup + )) + ->getForm(); + + $this->assertTrue($form['default']->isRequired(), + "The field should be required"); + } + + +} diff --git a/Tests/CustomFields/CustomFieldsTextTest.php b/Tests/CustomFields/CustomFieldsTextTest.php index 205dc7dfe..00130df80 100644 --- a/Tests/CustomFields/CustomFieldsTextTest.php +++ b/Tests/CustomFields/CustomFieldsTextTest.php @@ -100,4 +100,5 @@ class CustomFieldsTextTest extends WebTestCase $form = $crawler->selectButton('custom_field_choice_submit')->form(); $this->assertTrue($form->has('custom_field_choice[options][maxLength]')); } + } diff --git a/Tests/Fixtures/App/app/AppKernel.php b/Tests/Fixtures/App/app/AppKernel.php index 59ec46bfa..d2e57ab94 100644 --- a/Tests/Fixtures/App/app/AppKernel.php +++ b/Tests/Fixtures/App/app/AppKernel.php @@ -17,7 +17,8 @@ class AppKernel extends Kernel new Doctrine\Bundle\DoctrineBundle\DoctrineBundle(), new \Chill\MainBundle\ChillMainBundle, new \Doctrine\Bundle\FixturesBundle\DoctrineFixturesBundle(), - new Doctrine\Bundle\MigrationsBundle\DoctrineMigrationsBundle() + new Doctrine\Bundle\MigrationsBundle\DoctrineMigrationsBundle(), + new Chill\PersonBundle\ChillPersonBundle(), #add here all the required bundle (some bundle are not required) ); } diff --git a/Tests/Fixtures/App/app/config/config.yml b/Tests/Fixtures/App/app/config/config.yml index 9fdeaf68d..48bd7abed 100644 --- a/Tests/Fixtures/App/app/config/config.yml +++ b/Tests/Fixtures/App/app/config/config.yml @@ -66,7 +66,7 @@ security: anonymous: ~ form_login: csrf_parameter: _csrf_token - intention: authenticate + csrf_token_id: authenticate csrf_provider: form.csrf_provider logout: ~ access_control: diff --git a/Tests/Fixtures/App/app/config/parameters.travis.yml b/Tests/Fixtures/App/app/config/parameters.travis.yml deleted file mode 100644 index 28d5cd26c..000000000 --- a/Tests/Fixtures/App/app/config/parameters.travis.yml +++ /dev/null @@ -1,7 +0,0 @@ -parameters: - database_host: 127.0.0.1 - database_port: 5432 - database_name: test0 - database_user: postgres - database_password: postgres - locale: fr \ No newline at end of file diff --git a/Tests/Templating/Twig/CustomFieldRenderingTwigTest.php b/Tests/Templating/Twig/CustomFieldRenderingTwigTest.php new file mode 100644 index 000000000..7f4923349 --- /dev/null +++ b/Tests/Templating/Twig/CustomFieldRenderingTwigTest.php @@ -0,0 +1,105 @@ + + * + * 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"); + } +} diff --git a/composer.json b/composer.json index af1c0c6b4..9c1240b5a 100644 --- a/composer.json +++ b/composer.json @@ -34,7 +34,9 @@ "chill-project/main": "dev-master" }, "require-dev": { - "doctrine/doctrine-fixtures-bundle": "~2.2@dev" + "chill-project/person": "dev-master@dev", + "fzaninotto/faker": "~1", + "doctrine/doctrine-fixtures-bundle": "~2.2" }, "scripts": { "post-install-cmd": [