diff --git a/src/Bundle/ChillPerson/.gitignore b/src/Bundle/ChillPerson/.gitignore
new file mode 100644
index 000000000..f443cb81e
--- /dev/null
+++ b/src/Bundle/ChillPerson/.gitignore
@@ -0,0 +1,11 @@
+composer.lock
+vendor/*
+parameters.yml
+*~
+*.DS_Store
+*.sass-cache
+Resources/node_modules/
+Tests/Fixtures/App/app/config/parameters.yml
+/nbproject/private/
+Resources/test/Fixtures/App/bootstrap.php.cache
+
diff --git a/src/Bundle/ChillPerson/.gitlab-ci.yml b/src/Bundle/ChillPerson/.gitlab-ci.yml
new file mode 100644
index 000000000..be0b6078a
--- /dev/null
+++ b/src/Bundle/ChillPerson/.gitlab-ci.yml
@@ -0,0 +1,64 @@
+.test_definition: &test_definition
+ services:
+ - chill/database:latest
+ before_script:
+ - if [ -z ${GITHUB_TOKEN+x} ]; then composer config github-oauth.github.com $GITHUB_TOKEN; fi
+ - php -d memory_limit=-1 /usr/local/bin/composer install --no-interaction
+ - cp Resources/test/Fixtures/App/app/config/parameters.gitlab-ci.yml Resources/test/Fixtures/App/app/config/parameters.yml
+ - php Resources/test/Fixtures/App/app/console --env=test cache:warmup
+ - php Resources/test/Fixtures/App/app/console doctrine:migrations:migrate --env=test --no-interaction
+ - php Resources/test/Fixtures/App/app/console doctrine:fixtures:load --env=test --no-interaction
+
+stages:
+ - deploy
+ - test
+ - build-doc
+ - deploy-doc
+
+test:php-7.2:
+ image: chill/ci-image:php-7.2
+ stage: test
+ <<: *test_definition
+ script: APP_ENV=test php vendor/bin/phpunit
+
+deploy-packagist:
+ stage: deploy
+ image: chill/ci-image:php-7.2
+ before_script:
+ # test that PACKAGIST USERNAME and PACKAGIST_TOKEN variable are set
+ - if [ -z ${PACKAGIST_USERNAME+x} ]; then echo "Please set PACKAGIST_USERNAME variable"; exit -1; fi
+ - if [ -z ${PACKAGIST_TOKEN+x} ]; then echo "Please set PACKAGIST_TOKEN variable"; exit -1; fi
+ script:
+ - STATUSCODE=$(curl -XPOST -H'content-type:application/json' "https://packagist.org/api/update-package?username=$PACKAGIST_USERNAME&apiToken=$PACKAGIST_TOKEN" -d"{\"repository\":{\"url\":\"$CI_PROJECT_URL.git\"}}" --silent --output /dev/stderr --write-out "%{http_code}")
+ - if [ $STATUSCODE = "202" ]; then exit 0; else exit $STATUSCODE; fi
+
+# deploy documentation
+api-doc-build:
+ stage: build-doc
+ environment: api-doc
+ image: chill/ci-image:php-7.2
+ before_script:
+ - mkdir api-doc
+ script: apigen generate --destination api-doc/$CI_BUILD_REF_NAME/$CI_PROJECT_NAME
+ artifacts:
+ paths:
+ - "api-doc/"
+ name: api
+ expire_in: '2h'
+ only:
+ - master
+ - tags
+
+api-doc-deploy:
+ stage: deploy-doc
+ image: pallet/swiftclient:latest
+ before_script:
+ # test that CONTAINER_API variable is set
+ - if [ -z ${CONTAINER_API+x} ]; then echo "Please set CONTAINER_API variable"; exit -1; fi
+ # go to api-doc to have and url with PROJECT/BUILD
+ - cd api-doc
+ # upload, and keep files during 1 year
+ script: "swift upload --header \"X-Delete-After: 31536000\" $CONTAINER_API $CI_BUILD_REF_NAME/$CI_PROJECT_NAME"
+ only:
+ - master
+ - tags
diff --git a/src/Bundle/ChillPerson/Actions/ActionEvent.php b/src/Bundle/ChillPerson/Actions/ActionEvent.php
new file mode 100644
index 000000000..5ac3055cb
--- /dev/null
+++ b/src/Bundle/ChillPerson/Actions/ActionEvent.php
@@ -0,0 +1,143 @@
+
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+namespace Chill\PersonBundle\Actions;
+
+use Symfony\Component\EventDispatcher\Event;
+
+/**
+ * Event triggered when an entity attached to a person is removed.
+ *
+ *
+ */
+class ActionEvent extends Event
+{
+ const DELETE = 'CHILL_PERSON.DELETE_ASSOCIATED_ENTITY';
+ const MOVE = 'CHILL_PERSON.MOVE_ASSOCIATED_ENTITY';
+
+ /**
+ *
+ * @var int
+ */
+ protected $personId;
+
+ /**
+ * the FQDN class name as recorded in doctrine
+ *
+ * @var string
+ */
+ protected $entity;
+
+ /**
+ * an array of key value data to describe the movement
+ *
+ * @var array
+ */
+ protected $metadata;
+
+ /**
+ * the sql statement
+ *
+ * @var string
+ */
+ protected $sqlStatement;
+
+ /**
+ *
+ * @var string[]
+ */
+ protected $preSql = [];
+
+ /**
+ *
+ * @var string[]
+ */
+ protected $postSql = [];
+
+ public function __construct($personId, $entity, $sqlStatement, $metadata = [])
+ {
+ $this->personId = $personId;
+ $this->entity = $entity;
+ $this->sqlStatement = $sqlStatement;
+ $this->metadata = $metadata;
+ }
+
+ /**
+ *
+ * @return string[]
+ */
+ public function getPreSql(): array
+ {
+ return $this->preSql;
+ }
+
+ /**
+ *
+ * @return string[]
+ */
+ public function getPostSql(): array
+ {
+ return $this->postSql;
+ }
+
+ /*
+ * Add Sql which will be executed **before** the delete statement
+ */
+ public function addPreSql(string $preSql)
+ {
+ $this->preSql[] = $preSql;
+ return $this;
+ }
+
+ /**
+ * Add Sql which will be executed **after** the delete statement
+ *
+ * @param type $postSql
+ * @return $this
+ */
+ public function addPostSql(string $postSql)
+ {
+ $this->postSql[] = $postSql;
+ return $this;
+ }
+
+ public function getPersonId(): int
+ {
+ return $this->personId;
+ }
+
+ /**
+ * get the entity name, as recorded in doctrine
+ *
+ * @return string
+ */
+ public function getEntity(): string
+ {
+ return $this->entity;
+ }
+
+ public function getSqlStatement()
+ {
+ return $this->sqlStatement;
+ }
+
+ public function getMetadata()
+ {
+ return $this->metadata;
+ }
+
+}
diff --git a/src/Bundle/ChillPerson/Actions/Remove/PersonMove.php b/src/Bundle/ChillPerson/Actions/Remove/PersonMove.php
new file mode 100644
index 000000000..fb7e6b9b4
--- /dev/null
+++ b/src/Bundle/ChillPerson/Actions/Remove/PersonMove.php
@@ -0,0 +1,184 @@
+
+ *
+ * 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\PersonBundle\Actions\Remove;
+
+use Doctrine\ORM\EntityManagerInterface;
+use Chill\PersonBundle\Entity\Person;
+use Chill\PersonBundle\Entity\AccompanyingPeriod;
+use Doctrine\ORM\Mapping\ClassMetadata;
+use Symfony\Component\EventDispatcher\EventDispatcherInterface;
+use Chill\PersonBundle\Actions\ActionEvent;
+
+/**
+ * Move or delete entities associated to a person to a new one, and delete the
+ * old person. The data associated to a person (birthdate, name, ...) are left
+ * untouched on the "new one".
+ *
+ * See `getSql` for details.
+ *
+ *
+ */
+class PersonMove
+{
+ /**
+ *
+ * @var EntityManagerInterface
+ */
+ protected $em;
+
+ /**
+ *
+ * @var EventDispatcherInterface
+ */
+ protected $eventDispatcher;
+
+ public function __construct(
+ EntityManagerInterface $em,
+ EventDispatcherInterface $eventDispatcher
+ ) {
+ $this->em = $em;
+ $this->eventDispatcher = $eventDispatcher;
+ }
+
+ /**
+ * Return the sql used to move or delete entities associated to a person to
+ * a new one, and delete the old person. The data associated to a person
+ * (birthdate, name, ...) are left untouched on the "new one".
+ *
+ * The accompanying periods associated to a person are always removed. The other
+ * associated entity are updated: the new person id is associated to the entity.
+ *
+ * Optionnaly, you can ask for removing entity by passing them in $deleteEntities
+ * parameters.
+ *
+ * The following events are triggered:
+ * - `'CHILL_PERSON.DELETE_ASSOCIATED_ENTITY'` is triggered when an entity
+ * will be removed ;
+ * - `'CHILL_PERSON.MOVE_ASSOCIATED_ENTITY'` is triggered when an entity
+ * will be moved ;
+ *
+ * Those events have the following metadata:
+ *
+ * - 'original_action' : always 'move' ;
+ * - 'to': the person id to move ;
+ *
+ * @param Person $from
+ * @param Person $to
+ * @param array $deleteEntities
+ * @return type
+ */
+ public function getSQL(Person $from, Person $to, array $deleteEntities = [])
+ {
+ $sqls = [];
+ $toDelete = \array_merge($deleteEntities, $this->getDeleteEntities());
+
+ foreach ($this->em->getMetadataFactory()->getAllMetadata() as $metadata) {
+ if ($metadata->isMappedSuperclass) {
+ continue;
+ }
+
+ foreach ($metadata->getAssociationMappings() as $field => $mapping) {
+ if ($mapping['targetEntity'] === Person::class) {
+
+ if (\in_array($metadata->getName(), $toDelete)) {
+ $sql = $this->createDeleteSQL($metadata, $from, $field);
+ $event = new ActionEvent($from->getId(), $metadata->getName(), $sql,
+ ['to' => $to->getId(), 'original_action' => 'move']);
+ $this->eventDispatcher->dispatch(ActionEvent::DELETE, $event);
+
+ } else {
+ $sql = $this->createMoveSQL($metadata, $from, $to, $field);
+ $event = new ActionEvent($from->getId(), $metadata->getName(), $sql,
+ ['to' => $to->getId(), 'original_action' => 'move']);
+ $this->eventDispatcher->dispatch(ActionEvent::MOVE, $event);
+ }
+
+ $sqls = \array_merge($sqls, $event->getPreSql(), [$event->getSqlStatement()], $event->getPostSql());
+ }
+ }
+ }
+
+ $personMetadata = $this->em->getClassMetadata(Person::class);
+ $sqls[] = sprintf("DELETE FROM %s WHERE id = %d",
+ $this->getTableName($personMetadata),
+ $from->getId());
+
+ return $sqls ?? [];
+ }
+
+ protected function createMoveSQL(ClassMetadata $metadata, Person $from, Person $to, $field): string
+ {
+ $mapping = $metadata->getAssociationMapping($field);
+
+ // Set part of the query, aka in "UPDATE table SET "
+ $sets = [];
+ foreach ($mapping["joinColumns"] as $columns) {
+ $sets[] = sprintf("%s = %d", $columns["name"], $to->getId());
+ }
+
+ $conditions = [];
+ foreach ($mapping["joinColumns"] as $columns) {
+ $conditions[] = sprintf("%s = %d", $columns["name"], $from->getId());
+ }
+
+ return \sprintf("UPDATE %s SET %s WHERE %s",
+ $this->getTableName($metadata),
+ \implode(" ", $sets),
+ \implode(" AND ", $conditions)
+ );
+ }
+
+ protected function createDeleteSQL(ClassMetadata $metadata, Person $from, $field): string
+ {
+ $mapping = $metadata->getAssociationMapping($field);
+
+ $conditions = [];
+ foreach ($mapping["joinColumns"] as $columns) {
+ $conditions[] = sprintf("%s = %d", $columns["name"], $from->getId());
+ }
+
+ return \sprintf("DELETE FROM %s WHERE %s",
+ $this->getTableName($metadata),
+ \implode(" AND ", $conditions)
+ );
+ }
+
+ /**
+ * return an array of classes where entities should be deleted
+ * instead of moved
+ *
+ * @return array
+ */
+ protected function getDeleteEntities(): array
+ {
+ return [
+ AccompanyingPeriod::class
+ ];
+ }
+
+ /**
+ * get the full table name with schema if it does exists
+ */
+ private function getTableName(ClassMetadata $metadata): string
+ {
+ return empty($metadata->getSchemaName()) ?
+ $metadata->getTableName() :
+ $metadata->getSchemaName().".".$metadata->getTableName();
+ }
+
+}
diff --git a/src/Bundle/ChillPerson/CHANGELOG.md b/src/Bundle/ChillPerson/CHANGELOG.md
new file mode 100644
index 000000000..5ba71f288
--- /dev/null
+++ b/src/Bundle/ChillPerson/CHANGELOG.md
@@ -0,0 +1,117 @@
+
+Version 1.5.1
+=============
+
+- Improve import of person to allow multiple centers by file ;
+- Launch an event on person import ;
+- Allow person to have a `null` gender ;
+- Allow filters and aggregator to handle null gender ;
+- remove inexistant `person.css` file
+- fix bug in accompanying person validation
+
+Version 1.5.2
+==============
+
+- Add an column with fullname canonical (lowercase and unaccent) to persons entity ;
+- Add a trigram index on fullname canonical ;
+- Add a "similar person matcher", which allow to detect person with similar names when adding a person ;
+- Add a research of persons by fuzzy name, returning result with a similarity of 0.15 ;
+
+Thanks to @matla :-)
+
+Version 1.5.3
+=============
+
+- add filtering on accompanying period
+- fix problems in gender filter
+
+Version 1.5.4
+=============
+
+- add filenumber in person header
+
+Version 1.5.5
+=============
+
+- Fix bug in accompanying period filter
+
+Version 1.5.6
+=============
+
+- Update address validation
+- Add command to move person and all data of a person to a new one, and delete the old one.
+
+Version 1.5.7
+=============
+
+- fix error on macro renderPerson / withLink not taken into account
+- add a link between accompanying person and user
+- add an icon when the file is opened / closed in result list, and in person rendering macro
+- improve command to move person and all data: allow to delete some entities during move and add events
+
+Version 1.5.8
+=============
+
+- add search by phonenumber, with a custom SearchInterface
+
+ This can be activated or desactivated by config:
+
+ ```
+ chill_person:
+ enabled: true
+ search:
+ enabled: true
+
+ # enable search by phone. 'always' show the result on every result. 'on-domain' will show the result only if the domain is given in the search box. 'never' disable this feature
+ search_by_phone: on-domain # One of "always"; "on-domain"; "never"
+ ```
+- format phonenumber using twilio (if available) ;
+- add `record_actions` in person search result list: users can click on a little eye to open person page ;
+- add new fields (email, mobilenumber, gender) into importPeopleFromCSV command
+- configure asset using a function
+
+
+Version 1.5.9
+=============
+
+- create CRUD
+- add the ability to add alt names to persons
+- [UI] set action button bottom of edit form according to crud template
+- [closing motive] add an hierarchy for closing motives ;
+- [closing motive] Add an admin section for closing motives ;
+
+<<<<<<< HEAD
+Version 1.5.10
+==============
+
+- [closing motive] display closing motive in remark
+
+Version 1.5.11
+==============
+
+- Fix versioning constraint to chill main
+
+Version 1.5.12
+==============
+
+- [addresses] add a homeless to person's addresses, and this information into
+ person list
+
+Version 1.5.13
+==============
+
+- [CRUD] add step delete
+- [CRUD] improve index view in person CRUD
+- [CRUD] filter by basis on person by default in EntityPersonCRUDController
+- [CRUD] override relevant part of the main CRUD template
+- [CRUD] fix redirection on person view: add a `person_id` to every page redirected.
+
+Version 1.5.14
+==============
+
+- [Accompanying period list] Fix period label in list
+- [Accompanying period list] Fix label of closing motive
+- [Person details] Add an "empty" statement on place of birth
+- [Person list] Add a lock/unlock icon instead of open/closed folder in result list;
+- [Admin closing motive] Remove links to Closing motive View;
+- [Admin closing motive] Improve icons for active in list of closing motive;
diff --git a/src/Bundle/ChillPerson/CRUD/Controller/EntityPersonCRUDController.php b/src/Bundle/ChillPerson/CRUD/Controller/EntityPersonCRUDController.php
new file mode 100644
index 000000000..549792e51
--- /dev/null
+++ b/src/Bundle/ChillPerson/CRUD/Controller/EntityPersonCRUDController.php
@@ -0,0 +1,197 @@
+
+ *
+ * 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\PersonBundle\CRUD\Controller;
+
+use Chill\MainBundle\CRUD\Controller\CRUDController;
+use Symfony\Component\HttpFoundation\Request;
+use Chill\PersonBundle\Entity\Person;
+use Doctrine\ORM\QueryBuilder;
+
+/**
+ * Class EntityPersonCRUDController
+ * CRUD Controller for entities attached to a Person
+ *
+ * @package Chill\PersonBundle\CRUD\Controller
+ */
+class EntityPersonCRUDController extends CRUDController
+{
+ /**
+ * Extract the person from the request
+ *
+ * the person parameter will be `person_id` and must be
+ * present in the query
+ *
+ * If the parameter is not set, this method will return null.
+ *
+ * If the person id does not exists, the method will throw a
+ * Symfony\Component\HttpKernel\Exception\NotFoundHttpException
+ *
+ * @param Request $request
+ * @throws Symfony\Component\HttpKernel\Exception\NotFoundHttpException if the person with given id is not found
+ */
+ protected function getPerson(Request $request): ?Person
+ {
+ if (FALSE === $request->query->has('person_id')) {
+ return null;
+ }
+
+ $person = $this->getDoctrine()
+ ->getRepository(Person::class)
+ ->find($request->query->getInt('person_id'))
+ ;
+
+ if (NULL === $person) {
+ throw $this->createNotFoundException('the person with this id is not found');
+ }
+
+ return $person;
+ }
+
+ /**
+ * @param \Chill\MainBundle\CRUD\Controller\string|string $action
+ * @param Request $request
+ * @return object
+ */
+ protected function createEntity($action, Request $request): object
+ {
+ $entity = parent::createEntity($action, $request);
+
+ $person = $this->getPerson($request);
+
+ $entity->setPerson($person);
+
+ return $entity;
+ }
+
+ /**
+ * @param string $action
+ * @param mixed $entity
+ * @param Request $request
+ * @param array $defaultTemplateParameters
+ * @return array
+ * @throws \Exception
+ */
+ protected function generateTemplateParameter(string $action, $entity, Request $request, array $defaultTemplateParameters = array()): array
+ {
+ $person = $this->getPerson($request);
+
+ if (NULL === $person) {
+ throw new \Exception("the `person_id` parameter is not set in the query. "
+ . "You should set it or override the current method to allow another "
+ . "behaviour: ".__METHOD__);
+ }
+
+ return parent::generateTemplateParameter(
+ $action,
+ $entity,
+ $request,
+ \array_merge([ 'person' => $person ], $defaultTemplateParameters)
+ );
+ }
+
+ /**
+ * @param string $action
+ * @param mixed $entity
+ * @param Request $request
+ * @return string
+ */
+ protected function getTemplateFor($action, $entity, Request $request)
+ {
+ if ($this->hasCustomTemplate($action, $entity, $request)) {
+ return $this->getActionConfig($action)['template'];
+ }
+
+ switch ($action) {
+ case 'new':
+ return '@ChillPerson/CRUD/new.html.twig';
+ case 'edit':
+ return '@ChillPerson/CRUD/edit.html.twig';
+ case 'view':
+ return '@ChillPerson/CRUD/view.html.twig';
+ case 'delete':
+ return '@ChillPerson/CRUD/delete.html.twig';
+ case 'index':
+ return '@ChillPerson/CRUD/index.html.twig';
+ default:
+ return parent::getTemplateFor($action, $entity, $request);
+ }
+ }
+
+ /**
+ * @param string $action
+ * @param mixed $entity
+ * @param \Symfony\Component\Form\FormInterface $form
+ * @param Request $request
+ * @return \Symfony\Component\HttpFoundation\RedirectResponse
+ */
+ protected function onBeforeRedirectAfterSubmission(string $action, $entity, \Symfony\Component\Form\FormInterface $form, Request $request)
+ {
+ $next = $request->request->get("submit", "save-and-close");
+
+ switch ($next) {
+ case "save-and-close":
+ return $this->redirectToRoute('chill_crud_'.$this->getCrudName().'_index', [
+ 'person_id' => $this->getPerson($request)->getId()
+ ]);
+ case "save-and-new":
+ return $this->redirectToRoute('chill_crud_'.$this->getCrudName().'_new', [
+ 'person_id' => $this->getPerson($request)->getId()
+ ]);
+ case "new":
+ return $this->redirectToRoute('chill_crud_'.$this->getCrudName().'_view', [
+ 'id' => $entity->getId(),
+ 'person_id' => $this->getPerson($request)->getId()
+ ]);
+ default:
+ return $this->redirectToRoute('chill_crud_'.$this->getCrudName().'_view', [
+ 'id' => $entity->getId(),
+ 'person_id' => $this->getPerson($request)->getId()
+ ]);
+ }
+ }
+
+ /**
+ * Override the base method to add a filtering step to a person.
+ *
+ * @param string $action
+ * @param Request $request
+ * @return QueryBuilder
+ */
+ protected function buildQueryEntities(string $action, Request $request)
+ {
+ $qb = parent::buildQueryEntities($action, $request);
+
+ return $this->filterQueryEntitiesByPerson($action, $qb, $request);
+ }
+
+ /**
+ * Add a where clause to the buildQuery
+ *
+ * @param string $action
+ * @param \Chill\PersonBundle\CRUD\Controller\QueryBuilder $qb
+ * @param Request $request
+ * @return \Chill\PersonBundle\CRUD\Controller\QueryBuilder
+ */
+ protected function filterQueryEntitiesByPerson(string $action, QueryBuilder $qb, Request $request): QueryBuilder
+ {
+ $qb->andWhere($qb->expr()->eq('e.person', ':person'));
+ $qb->setParameter('person', $this->getPerson($request));
+
+ return $qb;
+ }
+}
diff --git a/src/Bundle/ChillPerson/CRUD/Controller/OneToOneEntityPersonCRUDController.php b/src/Bundle/ChillPerson/CRUD/Controller/OneToOneEntityPersonCRUDController.php
new file mode 100644
index 000000000..720de560c
--- /dev/null
+++ b/src/Bundle/ChillPerson/CRUD/Controller/OneToOneEntityPersonCRUDController.php
@@ -0,0 +1,89 @@
+
+ *
+ * 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\PersonBundle\CRUD\Controller;
+
+use Chill\MainBundle\CRUD\Controller\CRUDController;
+use Symfony\Component\HttpFoundation\Request;
+use Chill\PersonBundle\Entity\Person;
+use Symfony\Component\Form\FormInterface;
+use Symfony\Component\HttpFoundation\Response;
+use Symfony\Component\HttpFoundation\RedirectResponse;
+
+/**
+ * Controller for entities attached as one-to-on to a person
+ *
+ */
+class OneToOneEntityPersonCRUDController extends CRUDController
+{
+ protected function getTemplateFor($action, $entity, Request $request)
+ {
+ if (!empty($this->crudConfig[$action]['template'])) {
+ return $this->crudConfig[$action]['template'];
+ }
+
+ switch ($action) {
+ case 'new':
+ return '@ChillPerson/CRUD/new.html.twig';
+ case 'edit':
+ return '@ChillPerson/CRUD/edit.html.twig';
+ case 'index':
+ return '@ChillPerson/CRUD/index.html.twig';
+ default:
+ throw new \LogicException("the view for action $action is not "
+ . "defined. You should override ".__METHOD__." to add this "
+ . "action");
+ }
+ }
+
+ protected function getEntity($action, $id, Request $request): ?object
+ {
+ $entity = parent::getEntity($action, $id, $request);
+
+ if (NULL === $entity) {
+ $entity = $this->createEntity($action, $request);
+ $person = $this->getDoctrine()
+ ->getManager()
+ ->getRepository(Person::class)
+ ->find($id);
+
+ $entity->setPerson($person);
+ }
+
+ return $entity;
+ }
+
+ protected function onPreFlush(string $action, $entity, FormInterface $form, Request $request)
+ {
+ $this->getDoctrine()->getManager()->persist($entity);
+ }
+
+ protected function onPostFetchEntity($action, Request $request, $entity): ?Response
+ {
+ if (FALSE === $this->getDoctrine()->getManager()->contains($entity)) {
+ return new RedirectResponse($this->generateRedirectOnCreateRoute($action, $request, $entity));
+ }
+
+ return null;
+ }
+
+ protected function generateRedirectOnCreateRoute($action, Request $request, $entity)
+ {
+ throw new BadMethodCallException("not implemtented yet");
+ }
+
+}
diff --git a/src/Bundle/ChillPerson/ChillPersonBundle.php b/src/Bundle/ChillPerson/ChillPersonBundle.php
new file mode 100644
index 000000000..18bf8fb79
--- /dev/null
+++ b/src/Bundle/ChillPerson/ChillPersonBundle.php
@@ -0,0 +1,21 @@
+getExtension('chill_main')
+ ->addWidgetFactory(new PersonListWidgetFactory());
+
+ $container->addCompilerPass(new AccompanyingPeriodTimelineCompilerPass());
+ }
+}
diff --git a/src/Bundle/ChillPerson/Command/ChillPersonMoveCommand.php b/src/Bundle/ChillPerson/Command/ChillPersonMoveCommand.php
new file mode 100644
index 000000000..4f2b2098a
--- /dev/null
+++ b/src/Bundle/ChillPerson/Command/ChillPersonMoveCommand.php
@@ -0,0 +1,150 @@
+
+ *
+ * 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\PersonBundle\Command;
+
+use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
+use Symfony\Component\Console\Input\InputArgument;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Input\InputOption;
+use Symfony\Component\Console\Output\OutputInterface;
+use Chill\PersonBundle\Actions\Remove\PersonMove;
+use Doctrine\ORM\EntityManagerInterface;
+use Chill\PersonBundle\Entity\Person;
+use Symfony\Component\Console\Exception\RuntimeException;
+use Psr\Log\LoggerInterface;
+
+class ChillPersonMoveCommand extends ContainerAwareCommand
+{
+ /**
+ *
+ * @var PersonMove
+ */
+ protected $mover;
+
+ /**
+ *
+ * @var EntityManagerInterface
+ */
+ protected $em;
+
+ /**
+ *
+ * @var LoggerInterface
+ */
+ protected $chillLogger;
+
+ public function __construct(
+ PersonMove $mover,
+ EntityManagerInterface $em,
+ LoggerInterface $chillLogger
+ ) {
+ parent::__construct('chill:person:move');
+
+ $this->mover = $mover;
+ $this->em = $em;
+ $this->chillLogger = $chillLogger;
+ }
+
+ protected function configure()
+ {
+ $this
+ ->setName('chill:person:move')
+ ->setDescription('Move all the associated entities on a "from" person to a "to" person and remove the old person')
+ ->addOption('from', 'f', InputOption::VALUE_REQUIRED, "The person id to delete, all associated data will be moved before")
+ ->addOption('to', 't', InputOption::VALUE_REQUIRED, "The person id which will received data")
+ ->addOption('dump-sql', null, InputOption::VALUE_NONE, "dump sql to stdout")
+ ->addOption('force', null, InputOption::VALUE_NONE, "execute sql instead of dumping it")
+ ->addOption('delete-entity', null, InputOption::VALUE_REQUIRED|InputOption::VALUE_IS_ARRAY, "entity to delete", [])
+ ;
+ }
+
+ protected function interact(InputInterface $input, OutputInterface $output)
+ {
+ if (FALSE === $input->hasOption('dump-sql') && FALSE === $input->hasOption('force')) {
+ $msg = "You must use \"--dump-sql\" or \"--force\"";
+ throw new RuntimeException($msg);
+ }
+
+ foreach (["from", "to"] as $name) {
+ if (empty($input->getOption($name))) {
+ throw new RuntimeException("You must set a \"$name\" option");
+ }
+ $id = $input->getOption($name);
+ if (\ctype_digit($id) === FALSE) {
+ throw new RuntimeException("The id in \"$name\" field does not contains "
+ . "only digits: $id");
+ }
+ }
+ }
+
+ protected function execute(InputInterface $input, OutputInterface $output)
+ {
+ $repository = $this->em->getRepository(Person::class);
+ $from = $repository->find($input->getOption('from'));
+ $to = $repository->find($input->getOption('to'));
+ $deleteEntities = $input->getOption('delete-entity');
+
+ if ($from === NULL) {
+ throw new RuntimeException(sprintf("Person \"from\" with id %d not found", $input->getOption('from')));
+ }
+ if ($to === NULL) {
+ throw new RuntimeException(sprintf("Person \"to\" with id %d not found", $input->getOption('to')));
+ }
+
+ $sqls = $this->mover->getSQL($from, $to, $deleteEntities);
+
+ if ($input->getOption('dump-sql')) {
+ foreach($sqls as $sql) {
+ $output->writeln($sql);
+ }
+ } else {
+ $ctxt = $this->buildLoggingContext($from, $to, $deleteEntities, $sqls);
+ $this->chillLogger->notice("Trying to move a person from command line", $ctxt);
+ $connection = $this->em->getConnection();
+ $connection->beginTransaction();
+ foreach($sqls as $sql) {
+ if ($output->isVerbose()) {
+ $output->writeln($sql);
+ }
+ $connection->executeQuery($sql);
+ }
+ $connection->commit();
+
+ $this->chillLogger->notice("Move a person from command line succeeded", $ctxt);
+ }
+ }
+
+ protected function buildLoggingContext(Person $from, Person $to, $deleteEntities, $sqls)
+ {
+ $ctxt = [
+ 'from' => $from->getId(),
+ 'to' => $to->getId()
+ ];
+
+ foreach ($deleteEntities as $key => $de) {
+ $ctxt['delete_entity_'.$key] = $de;
+ }
+ foreach ($sqls as $key => $sql) {
+ $ctxt['sql_'.$key] = $sql;
+ }
+
+ return $ctxt;
+ }
+
+}
diff --git a/src/Bundle/ChillPerson/Command/ImportPeopleFromCSVCommand.php b/src/Bundle/ChillPerson/Command/ImportPeopleFromCSVCommand.php
new file mode 100644
index 000000000..d5e4c597c
--- /dev/null
+++ b/src/Bundle/ChillPerson/Command/ImportPeopleFromCSVCommand.php
@@ -0,0 +1,1130 @@
+
+ *
+ * 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\PersonBundle\Command;
+
+use Chill\MainBundle\Templating\TranslatableStringHelper;
+use Doctrine\ORM\EntityManagerInterface;
+use Psr\Log\LoggerInterface;
+use Symfony\Component\Console\Command\Command;
+use Symfony\Component\Console\Input\InputOption;
+use Symfony\Component\Console\Input\InputArgument;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Output\OutputInterface;
+use Symfony\Component\Console\Logger\ConsoleLogger;
+use Symfony\Component\Filesystem\Filesystem;
+use Symfony\Component\Console\Question\ChoiceQuestion;
+use Symfony\Component\Console\Helper\Table;
+use Chill\PersonBundle\Entity\Person;
+use Chill\MainBundle\Entity\Address;
+use Chill\MainBundle\Entity\PostalCode;
+use Chill\MainBundle\Entity\Center;
+use Chill\CustomFieldsBundle\Service\CustomFieldProvider;
+use Symfony\Component\Console\Question\ConfirmationQuestion;
+use Symfony\Component\EventDispatcher\EventDispatcherInterface;
+use Symfony\Component\EventDispatcher\Event;
+use Symfony\Component\Form\FormFactory;
+
+/**
+ * Class ImportPeopleFromCSVCommand
+ *
+ * @package Chill\PersonBundle\Command
+ * @author Julien Fastré
+ */
+class ImportPeopleFromCSVCommand extends Command
+{
+ /**
+ * @var InputInterface
+ */
+ protected $input;
+
+ /**
+ * @var OutputInterface
+ */
+ protected $output;
+
+ /**
+ * @var \Psr\Log\LoggerInterface
+ */
+ protected $logger;
+
+ /**
+ * @var \Chill\MainBundle\Templating\TranslatableStringHelper
+ */
+ protected $helper;
+
+ /**
+ * @var \Doctrine\Persistence\ObjectManager
+ */
+ protected $em;
+
+ /**
+ * @var EventDispatcherInterface
+ */
+ protected $eventDispatcher;
+
+ /**
+ * the line currently read
+ *
+ * @var int
+ */
+ protected $line;
+
+ /**
+ * @var array where key are column names, and value the custom field slug
+ */
+ protected $customFieldMapping = array();
+
+ /**
+ * @var CustomFieldProvider
+ */
+ protected $customFieldProvider;
+
+ /**
+ * Contains an array of information searched in the file.
+ *
+ * position 0: the information key (which will be used in this process)
+ * position 1: the helper
+ * position 2: the default value
+ *
+ * @var array
+ */
+ protected static $mapping = array(
+ ['firstname', 'The column header for firstname', 'firstname'],
+ ['lastname', 'The column header for lastname', 'lastname'],
+ ['birthdate', 'The column header for birthdate', 'birthdate'],
+ ['gender', 'The column header for gender', 'gender'],
+ ['opening_date', 'The column header for opening date', 'opening_date'],
+ ['closing_date', 'The column header for closing date', 'closing_date'],
+ ['memo', 'The column header for memo', 'memo'],
+ ['email', 'The column header for email', 'email'],
+ ['phonenumber', 'The column header for phonenumber', 'phonenumber'],
+ ['mobilenumber', 'The column header for mobilenumber', 'mobilenumber'],
+ ['street1', 'The column header for street 1', 'street1'],
+ ['postalcode', 'The column header for postal code', 'postalcode'],
+ ['locality', 'The column header for locality', 'locality'],
+ ['center', 'The column header for center', 'center']
+ );
+
+ /**
+ * Different possible format to interpret a date
+ *
+ * @var string
+ */
+ protected static $defaultDateInterpreter = "%d/%m/%Y|%e/%m/%y|%d/%m/%Y|%e/%m/%Y";
+
+ /**
+ * @var FormFactory
+ */
+ protected $formFactory;
+
+ /**
+ * ImportPeopleFromCSVCommand constructor.
+ *
+ * @param LoggerInterface $logger
+ * @param TranslatableStringHelper $helper
+ * @param EntityManagerInterface $em
+ * @param CustomFieldProvider $customFieldProvider
+ * @param EventDispatcherInterface $eventDispatcher
+ * @param FormFactory $formFactory
+ */
+ public function __construct(
+ LoggerInterface $logger,
+ TranslatableStringHelper $helper,
+ EntityManagerInterface $em,
+ CustomFieldProvider $customFieldProvider,
+ EventDispatcherInterface $eventDispatcher,
+ FormFactory $formFactory
+ ) {
+ $this->logger = $logger;
+ $this->helper = $helper;
+ $this->em = $em;
+ $this->customFieldProvider = $customFieldProvider;
+ $this->eventDispatcher = $eventDispatcher;
+ $this->formFactory = $formFactory;
+
+ parent::__construct('chill:person:import');
+ }
+
+ /**
+ *
+ */
+ protected function configure()
+ {
+ $this
+ ->addArgument('csv_file', InputArgument::REQUIRED, "The CSV file to import")
+ ->setDescription("Import people from a csv file")
+ ->setHelp(<<addArgument('locale', InputArgument::REQUIRED,
+ "The locale to use in displaying translatable strings from entities")
+ ->addOption(
+ 'force-center',
+ null,
+ InputOption::VALUE_REQUIRED,
+ "The id of the center"
+ )
+ ->addOption(
+ 'force',
+ null,
+ InputOption::VALUE_NONE,
+ "Persist people in the database (default is not to persist people)"
+ )
+ ->addOption(
+ 'delimiter',
+ 'd',
+ InputOption::VALUE_OPTIONAL,
+ "The delimiter character of the csv file",
+ ",")
+ ->addOption(
+ 'enclosure',
+ null,
+ InputOption::VALUE_OPTIONAL,
+ "The enclosure character of the csv file",
+ '"'
+ )
+ ->addOption(
+ 'escape',
+ null,
+ InputOption::VALUE_OPTIONAL,
+ "The escape character of the csv file",
+ "\\"
+ )
+ ->addOption(
+ 'length',
+ null,
+ InputOption::VALUE_OPTIONAL,
+ "The length of line to read. 0 means unlimited.",
+ 0
+ )
+ ->addOption(
+ 'dump-choice-matching',
+ null,
+ InputOption::VALUE_REQUIRED,
+ "The path of the file to dump the matching between label in CSV and answers"
+ )
+ ->addOption(
+ 'load-choice-matching',
+ null,
+ InputOption::VALUE_OPTIONAL,
+ "The path of the file to load the matching between label in CSV and answers"
+ )
+ ;
+
+ // mapping columns
+ foreach (self::$mapping as $m) {
+ $this->addOptionShortcut($m[0], $m[1], $m[2]);
+ }
+
+ // other information
+ $this->addOptionShortcut('birthdate_format', 'Format preference for '
+ . 'birthdate. See help for date formats preferences.',
+ self::$defaultDateInterpreter);
+ $this->addOptionShortcut('opening_date_format', 'Format preference for '
+ . 'opening date. See help for date formats preferences.',
+ self::$defaultDateInterpreter);
+ $this->addOptionShortcut('closing_date_format', 'Format preference for '
+ . 'closing date. See help for date formats preferences.',
+ self::$defaultDateInterpreter);
+
+ // mapping column to custom fields
+ $this->addOption('custom-field', NULL, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY,
+ "Mapping a column to a custom fields key. Example: 1=cf_slug");
+ $this->addOption('skip-interactive-field-mapping', null, InputOption::VALUE_NONE,
+ "Do not ask for interactive mapping");
+ }
+
+ /**
+ * This function is a shortcut to addOption.
+ *
+ * @param string $name
+ * @param string $description
+ * @param string $default
+ * @return ImportPeopleFromCSVCommand
+ */
+ protected function addOptionShortcut($name, $description, $default)
+ {
+ $this->addOption($name, null, InputOption::VALUE_OPTIONAL, $description, $default);
+
+ return $this;
+ }
+
+ /**
+ * @param InputInterface $input
+ * @param OutputInterface $output
+ */
+ protected function interact(InputInterface $input, OutputInterface $output)
+ {
+ // preparing the basic
+ $this->input = $input;
+ $this->output = $output;
+ $this->logger = new ConsoleLogger($output);
+
+ $csv = $this->openCSV();
+
+ // getting the first row
+ if (($row = fgetcsv(
+ $csv,
+ $input->getOption('length'),
+ $input->getOption('delimiter'),
+ $input->getOption('enclosure'),
+ $input->getOption('escape'))) !== false) {
+
+ try {
+ $this->matchColumnToCustomField($row);
+ } finally {
+ $this->logger->debug('closing csv', array('method' => __METHOD__));
+ fclose($csv);
+ }
+ }
+
+ // load the matching between csv and label
+ $this->loadAnswerMatching();
+ }
+
+ /**
+ * @param $row
+ */
+ protected function matchColumnToCustomField($row)
+ {
+
+ $cfMappingsOptions = $this->input->getOption('custom-field');
+ /* @var $em \Doctrine\Persistence\ObjectManager */
+ $em = $this->em;
+
+ foreach($cfMappingsOptions as $cfMappingStringOption) {
+ list($rowNumber, $cfSlug) = preg_split('|=|', $cfMappingStringOption);
+
+ // check that the column exists, getting the column name
+ $column = $row[$rowNumber];
+
+ if (empty($column)) {
+ $message = "The column with row $rowNumber is empty.";
+ $this->logger->error($message);
+ throw new \RuntimeException($message);
+ }
+
+ // check a custom field exists
+ try {
+ $customField = $em->createQuery("SELECT cf "
+ . "FROM ChillCustomFieldsBundle:CustomField cf "
+ . "JOIN cf.customFieldGroup g "
+ . "WHERE cf.slug = :slug "
+ . "AND g.entity = :entity")
+ ->setParameters(array(
+ 'slug' => $cfSlug,
+ 'entity' => Person::class
+ ))
+ ->getSingleResult();
+ } catch (\Doctrine\ORM\NoResultException $e) {
+ $message = sprintf(
+ "The customfield with slug '%s' does not exists. It was associated with column number %d",
+ $cfSlug,
+ $rowNumber
+ );
+ $this->logger->error($message);
+ throw new \RuntimeException($message);
+ }
+ // skip if custom field does not exists
+ if ($customField === NULL) {
+ $this->logger->error("The custom field with slug $cfSlug could not be found. "
+ . "Stopping this command.");
+ throw new \RuntimeException("The custom field with slug $cfSlug could not be found. "
+ . "Stopping this command.");
+ }
+
+ $this->logger->notice(sprintf("Matched custom field %s (question : '%s') on column %d (displayed in the file as '%s')",
+ $customField->getSlug(), $this->helper->localize($customField->getName()), $rowNumber, $column));
+
+ $this->customFieldMapping[$rowNumber] = $customField;
+ }
+ }
+
+ /**
+ * Load the mapping between answer in CSV and value in choices from a json file
+ */
+ protected function loadAnswerMatching()
+ {
+ if ($this->input->hasOption('load-choice-matching')) {
+ $fs = new Filesystem();
+ $filename = $this->input->getOption('load-choice-matching');
+
+ if (!$fs->exists($filename)) {
+ $this->logger->warning("The file $filename is not found. Choice matching not loaded");
+ } else {
+ $this->logger->debug("Loading $filename as choice matching");
+ $this->cacheAnswersMapping = \json_decode(\file_get_contents($filename), true);
+ }
+ }
+ }
+
+ /**
+ *
+ */
+ protected function dumpAnswerMatching()
+ {
+ if ($this->input->hasOption('dump-choice-matching') && !empty($this->input->getOption('dump-choice-matching'))) {
+ $this->logger->debug("Dump the matching between answer and choices");
+ $str = json_encode($this->cacheAnswersMapping, JSON_PRETTY_PRINT);
+
+ $fs = new Filesystem();
+ $filename = $this->input->getOption('dump-choice-matching');
+
+ $fs->dumpFile($filename, $str);
+ }
+ }
+
+ /**
+ * @param InputInterface $input
+ * @param OutputInterface $output
+ * @return int|null|void
+ * @throws \Exception
+ */
+ protected function execute(InputInterface $input, OutputInterface $output)
+ {
+ $this->input = $input;
+ $this->output = $output;
+
+ $this->logger->debug("Setting locale to ".$input->getArgument('locale'));
+ setlocale(LC_TIME, $input->getArgument('locale'));
+
+ // opening csv as resource
+ $csv = $this->openCSV();
+
+ $num = 0;
+ $line = $this->line = 1;
+
+ try {
+ while (($row = fgetcsv(
+ $csv,
+ $input->getOption('length'),
+ $input->getOption('delimiter'),
+ $input->getOption('enclosure'),
+ $input->getOption('escape'))) !== false) {
+ $this->logger->debug("Processing line ".$this->line);
+ if ($line === 1 ) {
+ $this->logger->debug('Processing line 1, headers');
+ $rawHeaders = $row;
+ $headers = $this->processingHeaders($row);
+ } else {
+ $person = $this->createPerson($row, $headers);
+
+ if (count($this->customFieldMapping) > 0) {
+ $this->processingCustomFields($person, $row);
+ }
+
+ $event = new Event();
+ $event->person = $person;
+ $event->rawHeaders = $rawHeaders;
+ $event->row = $row;
+ $event->headers = $headers;
+ $event->skipPerson = false;
+ $event->force = $this->input->getOption('force');
+ $event->input = $this->input;
+ $event->output = $this->output;
+ $event->helperSet = $this->getHelperSet();
+
+ $this->eventDispatcher->dispatch('chill_person.person_import', $event);
+
+ if ($this->input->getOption('force') === TRUE
+ && $event->skipPerson === false) {
+ $this->em->persist($person);
+ }
+
+ $num ++;
+ }
+
+ $line ++;
+ $this->line++;
+ }
+
+ if ($this->input->getOption('force') === true) {
+ $this->logger->debug('persisting entitites');
+ $this->em->flush();
+ }
+ } finally {
+ $this->logger->debug('closing csv', array('method' => __METHOD__));
+ fclose($csv);
+ // dump the matching between answer and choices
+ $this->dumpAnswerMatching();
+ }
+ }
+
+ /**
+ *
+ * @return resource
+ * @throws \RuntimeException
+ */
+ protected function openCSV()
+ {
+ $fs = new Filesystem();
+ $filename = $this->input->getArgument('csv_file');
+
+ if (!$fs->exists($filename)) {
+ throw new \RuntimeException("The file does not exists or you do not "
+ . "have the right to read it.");
+ }
+
+ $resource = fopen($filename, 'r');
+
+ if ($resource == FALSE) {
+ throw new \RuntimeException("The file '$filename' could not be opened.");
+ }
+
+ return $resource;
+ }
+
+ /**
+ *
+ * @param type $firstRow
+ * @return array where keys are column number, and value is information mapped
+ */
+ protected function processingHeaders($firstRow)
+ {
+ $availableOptions = array_map(function($m) { return $m[0]; }, self::$mapping);
+ $matchedColumnHeaders = array();
+ $headers = array();
+
+ foreach($availableOptions as $option) {
+ $matchedColumnHeaders[$option] = $this->input->getOption($option);
+ }
+
+ foreach($firstRow as $key => $content) {
+ $content = trim($content);
+ if (in_array($content, $matchedColumnHeaders)) {
+ $information = array_search($content, $matchedColumnHeaders);
+ $headers[$key] = $information;
+ $this->logger->notice("Matched $information on column $key (displayed in the file as '$content')");
+ } else {
+ $this->logger->notice("Column with content '$content' is ignored");
+ }
+ }
+
+ return $headers;
+ }
+
+ /**
+ *
+ * @param array $row
+ * @param array $headers the processed header : an array as prepared by self::processingHeaders
+ * @return Person
+ * @throws \Exception
+ */
+ protected function createPerson($row, $headers)
+ {
+ // trying to get the opening date
+ $openingDateString = trim($row[array_search('opening_date', $headers)]);
+ $openingDate = $this->processDate($openingDateString, $this->input->getOption('opening_date_format'));
+
+ $person = $openingDate instanceof \DateTime ? new Person($openingDate) : new Person();
+ // add the center
+ $center = $this->getCenter($row, $headers);
+
+ if ($center === null) {
+ throw new \Exception("center not found");
+ }
+
+ $person->setCenter($center);
+
+ foreach($headers as $column => $info) {
+
+ $value = trim($row[$column]);
+
+ switch($info) {
+ case 'firstname':
+ $person->setFirstName($value);
+ break;
+ case 'lastname':
+ $person->setLastName($value);
+ break;
+ case 'birthdate':
+ $this->processBirthdate($person, $value);
+ break;
+ case 'gender':
+ $person->setGender($value);
+ break;
+ case 'opening_date':
+ // we have processed this when creating the person object, skipping;
+ break;
+ case 'closing_date':
+ $this->processClosingDate($person, $value);
+ break;
+ case 'memo':
+ $person->setMemo($value);
+ break;
+ case 'email':
+ $person->setEmail($value);
+ break;
+ case 'phonenumber':
+ $person->setPhonenumber($value);
+ break;
+ case 'mobilenumber':
+ $person->setMobilenumber($value);
+ break;
+
+ // we just keep the column number for those data
+ case 'postalcode':
+ $postalCodeValue = $value;
+ break;
+ case 'street1':
+ $street1Value = $value;
+ break;
+ case 'locality':
+ $localityValue = $value;
+ break;
+ }
+ }
+
+ // handle address
+ if (\in_array('postalcode', $headers)) {
+
+ if (! empty($postalCodeValue)) {
+
+ $address = new Address();
+ $postalCode = $this->guessPostalCode($postalCodeValue, $localityValue ?? '');
+
+ if ($postalCode === null) {
+ throw new \Exception("The locality is not found");
+ }
+
+ $address->setPostcode($postalCode);
+
+ if (\in_array('street1', $headers)) {
+ $address->setStreetAddress1($street1Value);
+ }
+ $address->setValidFrom(new \DateTime('today'));
+
+ $person->addAddress($address);
+ }
+ }
+
+ return $person;
+ }
+
+ /**
+ * @param $row
+ * @param $headers
+ * @return Center|mixed|null|object
+ */
+ protected function getCenter($row, $headers)
+ {
+ if ($this->input->hasOption('force-center') && !empty($this->input->getOption('force-center'))) {
+ return $this->em->getRepository('ChillMainBundle:Center')
+ ->find($this->input->getOption('force-center'));
+ } else {
+ $columnCenter = \array_search('center', $headers);
+ $centerName = \trim($row[$columnCenter]);
+
+ try {
+ return $this->em->createQuery('SELECT c FROM ChillMainBundle:Center c '
+ . 'WHERE c.name = :center_name')
+ ->setParameter('center_name', $centerName)
+ ->getSingleResult()
+ ;
+ } catch (\Doctrine\ORM\NonUniqueResultException $e) {
+ return $this->guessCenter($centerName);
+ } catch (\Doctrine\ORM\NoResultException $e) {
+ return $this->guessCenter($centerName);
+ }
+ }
+ }
+
+ /**
+ * @param $centerName
+ * @return Center|mixed|null|object
+ */
+ protected function guessCenter($centerName)
+ {
+ if (!\array_key_exists('_center_picked', $this->cacheAnswersMapping)) {
+ $this->cacheAnswersMapping['_center_picked'] = [];
+ }
+
+ if (\array_key_exists($centerName, $this->cacheAnswersMapping['_center_picked'])) {
+ $id = $this->cacheAnswersMapping['_center_picked'][$centerName];
+
+ return $this->em->getRepository(Center::class)
+ ->find($id);
+ }
+
+ $centers = $this->em->createQuery("SELECT c FROM ChillMainBundle:Center c "
+ . "ORDER BY SIMILARITY(c.name, :center_name) DESC")
+ ->setParameter('center_name', $centerName)
+ ->setMaxResults(10)
+ ->getResult()
+ ;
+
+ if (count($centers) > 1) {
+ if (\strtolower($centers[0]->getName()) === \strtolower($centerName)) {
+ return $centers[0];
+ }
+ }
+
+ $centersByName = [];
+ $names = \array_map(function(Center $c) use (&$centersByName) {
+ $n = $c->getName();
+ $centersByName[$n] = $c;
+ return $n;
+
+ }, $centers);
+ $names[] = "none of them";
+
+ $helper = $this->getHelper('question');
+ $question = new ChoiceQuestion(sprintf("Which center match the name \"%s\" ? (default to \"%s\")", $centerName, $names[0]),
+ $names,
+ 0);
+
+ $answer = $helper->ask($this->input, $this->output, $question);
+
+ if ($answer === 'none of them') {
+ $questionCreate = new ConfirmationQuestion("Would you like to create it ?", false);
+ $create = $helper->ask($this->input, $this->output, $questionCreate);
+
+ if ($create) {
+ $center = (new Center())
+ ->setName($centerName)
+ ;
+
+ if ($this->input->getOption('force') === TRUE) {
+ $this->em->persist($center);
+ $this->em->flush();
+ }
+
+ return $center;
+ }
+ }
+
+ $center = $centersByName[$answer];
+
+ $this->cacheAnswersMapping['_center_picked'][$centerName] = $center->getId();
+
+ return $center;
+ }
+
+ /**
+ * @param $postalCode
+ * @param $locality
+ * @return mixed|null
+ */
+ protected function guessPostalCode($postalCode, $locality)
+ {
+ if (!\array_key_exists('_postal_code_picked', $this->cacheAnswersMapping)) {
+ $this->cacheAnswersMapping['_postal_code_picked'] = [];
+ }
+
+ if (\array_key_exists($postalCode, $this->cacheAnswersMapping['_postal_code_picked'])) {
+ if (\array_key_exists($locality, $this->cacheAnswersMapping['_postal_code_picked'][$postalCode])) {
+ $id = $this->cacheAnswersMapping['_postal_code_picked'][$postalCode][$locality];
+
+ return $this->em->getRepository(PostalCode::class)->find($id);
+ }
+ }
+
+ $postalCodes = $this->em->createQuery("SELECT pc FROM ".PostalCode::class." pc "
+ . "WHERE pc.code = :postal_code "
+ . "ORDER BY SIMILARITY(pc.name, :locality) DESC "
+ )
+ ->setMaxResults(10)
+ ->setParameter('postal_code', $postalCode)
+ ->setParameter('locality', $locality)
+ ->getResult()
+ ;
+
+ if (count($postalCodes) >= 1) {
+ if ($postalCodes[0]->getCode() === $postalCode
+ && $postalCodes[0]->getName() === $locality) {
+ return $postalCodes[0];
+ }
+ }
+
+ if (count($postalCodes) === 0) {
+ return null;
+ }
+
+ $postalCodeByName = [];
+ $names = \array_map(function(PostalCode $pc) use (&$postalCodeByName) {
+ $n = $pc->getName();
+ $postalCodeByName[$n] = $pc;
+
+ return $n;
+ }, $postalCodes);
+ $names[] = 'none of them';
+
+ $helper = $this->getHelper('question');
+ $question = new ChoiceQuestion(sprintf("Which postal code match the "
+ . "name \"%s\" with postal code \"%s\" ? (default to \"%s\")",
+ $locality, $postalCode, $names[0]),
+ $names,
+ 0);
+
+ $answer = $helper->ask($this->input, $this->output, $question);
+
+ if ($answer === 'none of them') {
+ return null;
+ }
+
+ $pc = $postalCodeByName[$answer];
+
+ $this->cacheAnswersMapping['_postal_code_picked'][$postalCode][$locality] =
+ $pc->getId();
+
+ return $pc;
+ }
+
+ /**
+ * @param Person $person
+ * @param $value
+ * @throws \Exception
+ */
+ protected function processBirthdate(Person $person, $value)
+ {
+ if (empty($value)) { return; }
+
+ $date = $this->processDate($value, $this->input->getOption('birthdate_format'));
+
+ if ($date instanceof \DateTime) {
+ // we correct birthdate if the date is in the future
+ // the most common error is to set date 100 years to late (ex. 2063 instead of 1963)
+ if ($date > new \DateTime('yesterday')) {
+ $date = $date->sub(new \DateInterval('P100Y'));
+ }
+
+ $person->setBirthdate($date);
+
+ return;
+ }
+
+ // if we arrive here, we could not process the date
+ $this->logger->warning(sprintf(
+ "Line %d : the birthdate could not be interpreted. Was %s.",
+ $this->line,
+ $value));
+
+ }
+
+ /**
+ * @param Person $person
+ * @param $value
+ * @throws \Exception
+ */
+ protected function processClosingDate(Person $person, $value)
+ {
+ if (empty($value)) { return; }
+
+ // we skip if the opening date is now (or after yesterday)
+ /* @var $period \Chill\PersonBundle\Entity\AccompanyingPeriod */
+ $period = $person->getCurrentAccompanyingPeriod();
+
+ if ($period->getOpeningDate() > new \DateTime('yesterday')) {
+ $this->logger->debug(sprintf("skipping a closing date because opening date is after yesterday (%s)",
+ $period->getOpeningDate()->format('Y-m-d')));
+ return;
+ }
+
+
+ $date = $this->processDate($value, $this->input->getOption('closing_date_format'));
+
+ if ($date instanceof \DateTime) {
+ // we correct birthdate if the date is in the future
+ // the most common error is to set date 100 years to late (ex. 2063 instead of 1963)
+ if ($date > new \DateTime('yesterday')) {
+ $date = $date->sub(new \DateInterval('P100Y'));
+ }
+
+ $period->setClosingDate($date);
+ $person->close();
+ return;
+ }
+
+ // if we arrive here, we could not process the date
+ $this->logger->warning(sprintf(
+ "Line %d : the closing date could not be interpreted. Was %s.",
+ $this->line,
+ $value));
+ }
+
+ /**
+ * @param Person $person
+ * @param $row
+ * @throws \Exception
+ */
+ protected function processingCustomFields(Person $person, $row)
+ {
+
+ /* @var $cfProvider \Chill\CustomFieldsBundle\Service\CustomFieldProvider */
+ $cfProvider = $this->customFieldProvider;
+ $cfData = array();
+
+ /* @var $$customField \Chill\CustomFieldsBundle\Entity\CustomField */
+ foreach($this->customFieldMapping as $rowNumber => $customField) {
+ $builder = $this->formFactory->createBuilder();
+ $cfProvider->getCustomFieldByType($customField->getType())
+ ->buildForm($builder, $customField);
+ $form = $builder->getForm();
+
+ // get the type of the form
+ $type = get_class($form->get($customField->getSlug())
+ ->getConfig()->getType()->getInnerType());
+ $this->logger->debug(sprintf("Processing a form of type %s",
+ $type));
+
+ switch ($type) {
+ case \Symfony\Component\Form\Extension\Core\Type\TextType::class:
+ $cfData[$customField->getSlug()] =
+ $this->processTextType($row[$rowNumber], $form, $customField);
+ break;
+ case \Symfony\Component\Form\Extension\Core\Type\ChoiceType::class:
+ case \Chill\MainBundle\Form\Type\Select2ChoiceType::class:
+ $cfData[$customField->getSlug()] =
+ $this->processChoiceType($row[$rowNumber], $form, $customField);
+ }
+
+ }
+
+ $person->setCFData($cfData);
+ }
+
+ /**
+ * Process a text type on a custom field
+ *
+ * @param type $value
+ * @param \Symfony\Component\Form\FormInterface $form
+ * @return type
+ */
+ protected function processTextType(
+ $value,
+ \Symfony\Component\Form\FormInterface $form,
+ \Chill\CustomFieldsBundle\Entity\CustomField $cf
+ )
+ {
+ $form->submit(array($cf->getSlug() => $value));
+
+ $value = $form->getData()[$cf->getSlug()];
+
+ $this->logger->debug(sprintf("Found value : %s for custom field with question "
+ . "'%s'", $value, $this->helper->localize($cf->getName())));
+
+ return $value;
+ }
+
+ protected $cacheAnswersMapping = array();
+
+
+ /**
+ * Process a custom field choice.
+ *
+ * The method try to guess if the result exists amongst the text of the possible
+ * choices. If the texts exists, then this is picked. Else, ask the user.
+ *
+ * @param string $value
+ * @param \Symfony\Component\Form\FormInterface $form
+ * @param \Chill\CustomFieldsBundle\Entity\CustomField $cf
+ * @return string
+ * @throws \Exception
+ */
+ protected function processChoiceType(
+ $value,
+ \Symfony\Component\Form\FormInterface $form,
+ \Chill\CustomFieldsBundle\Entity\CustomField $cf
+ )
+ {
+ // getting the possible answer and their value :
+ $view = $form->get($cf->getSlug())->createView();
+ $answers = $this->collectChoicesAnswers($view->vars['choices']);
+
+ // if we do not have any answer on the question, throw an error.
+ if (count($answers) === 0) {
+ $message = sprintf(
+ "The question '%s' with slug '%s' does not count any answer.",
+ $this->helper->localize($cf->getName()),
+ $cf->getSlug()
+ );
+
+ $this->logger->error($message, array(
+ 'method' => __METHOD__,
+ 'slug' => $cf->getSlug(),
+ 'question' => $this->helper->localize($cf->getName())
+ ));
+
+ throw new \RuntimeException($message);
+ }
+
+ if ($view->vars['required'] === false) {
+ $answers[null] = '** no answer';
+ }
+
+ // the answer does not exists in cache. Try to find it, or asks the user
+ if (!isset($this->cacheAnswersMapping[$cf->getSlug()][$value])) {
+
+ // try to find the answer (with array_keys and a search value
+ $values = array_keys(
+ array_map(function($label) { return trim(strtolower($label)); }, $answers),
+ trim(strtolower($value)),
+ true
+ );
+
+ if (count($values) === 1) {
+ // we could guess an answer !
+ $this->logger->info("This question accept multiple answers");
+ $this->cacheAnswersMapping[$cf->getSlug()][$value] =
+ $view->vars['multiple'] == false ? $values[0] : array($values[0]);
+ $this->logger->info(sprintf("Guessed that value '%s' match with key '%s' "
+ . "because the CSV and the label are equals.",
+ $value, $values[0]));
+ } else {
+ // we could nog guess an answer. Asking the user.
+ $this->output->writeln("I do not know the answer to this question : ");
+ $this->output->writeln($this->helper->localize($cf->getName()));
+
+ // printing the possible answers
+ /* @var $table \Symfony\Component\Console\Helper\Table */
+ $table = new Table($this->output);
+ $table->setHeaders(array('#', 'label', 'value'));
+ $i = 0;
+
+ foreach($answers as $key => $answer) {
+ $table->addRow(array(
+ $i, $answer, $key
+ ));
+ $matchingTableRowAnswer[$i] = $key;
+ $i++;
+ }
+ $table->render($this->output);
+
+ $question = new ChoiceQuestion(
+ sprintf('Please pick your choice for the value "%s"', $value),
+ array_keys($matchingTableRowAnswer)
+ );
+ $question->setErrorMessage("This choice is not possible");
+
+ if ($view->vars['multiple']) {
+ $this->logger->debug("this question is multiple");
+ $question->setMultiselect(true);
+ }
+
+ $selected = $this->getHelper('question')->ask($this->input, $this->output, $question);
+
+ $this->output->writeln(sprintf('You have selected "%s"',
+ is_array($answers[$matchingTableRowAnswer[$selected]]) ?
+ implode(',', $answers[$matchingTableRowAnswer[$selected]]) :
+ $answers[$matchingTableRowAnswer[$selected]])
+ );
+
+ // recording value in cache
+ $this->cacheAnswersMapping[$cf->getSlug()][$value] = $matchingTableRowAnswer[$selected];
+ $this->logger->debug(sprintf("Setting the value '%s' in cache for customfield '%s' and answer '%s'",
+ is_array($this->cacheAnswersMapping[$cf->getSlug()][$value]) ?
+ implode(', ', $this->cacheAnswersMapping[$cf->getSlug()][$value]) :
+ $this->cacheAnswersMapping[$cf->getSlug()][$value],
+ $cf->getSlug(),
+ $value));
+ }
+ }
+
+ $form->submit(array($cf->getSlug() => $this->cacheAnswersMapping[$cf->getSlug()][$value]));
+ $value = $form->getData()[$cf->getSlug()];
+
+ $this->logger->debug(sprintf(
+ "Found value : %s for custom field with question '%s'",
+ is_array($value) ? implode(',', $value) : $value,
+ $this->helper->localize($cf->getName()))
+ );
+
+ return $value;
+ }
+
+ /**
+ * Recursive method to collect the possibles answer from a ChoiceType (or
+ * its inherited types).
+ *
+ * @param \Symfony\Component\Form\FormInterface $form
+ * @return array where
+ * @throws \Exception
+ */
+ private function collectChoicesAnswers($choices)
+ {
+ $answers = array();
+
+ /* @var $choice \Symfony\Component\Form\ChoiceList\View\ChoiceView */
+ foreach($choices as $choice) {
+ if ($choice instanceof \Symfony\Component\Form\ChoiceList\View\ChoiceView) {
+ $answers[$choice->value] = $choice->label;
+ } elseif ($choice instanceof \Symfony\Component\Form\ChoiceList\View\ChoiceGroupView) {
+ $answers = $answers + $this->collectChoicesAnswers($choice->choices);
+ } else {
+ throw new \Exception(sprintf(
+ "The choice type is not know. Expected '%s' or '%s', get '%s'",
+ \Symfony\Component\Form\ChoiceList\View\ChoiceView::class,
+ \Symfony\Component\Form\ChoiceList\View\ChoiceGroupView::class,
+ get_class($choice)
+ ));
+ }
+ }
+
+ return $answers;
+ }
+
+ /**
+ * @param $value
+ * @param $formats
+ * @return bool|\DateTime
+ */
+ protected function processDate($value, $formats)
+ {
+ $possibleFormats = explode("|", $formats);
+
+ foreach($possibleFormats as $format) {
+ $this->logger->debug("Trying format $format", array(__METHOD__));
+ $dateR = strptime($value, $format);
+
+ if (is_array($dateR) && $dateR['unparsed'] === '') {
+ $string = sprintf("%04d-%02d-%02d %02d:%02d:%02d",
+ ($dateR['tm_year']+1900),
+ ($dateR['tm_mon']+1),
+ ($dateR['tm_mday']),
+ ($dateR['tm_hour']),
+ ($dateR['tm_min']),
+ ($dateR['tm_sec']));
+ $date = \DateTime::createFromFormat("Y-m-d H:i:s", $string);
+ $this->logger->debug(sprintf("Interpreting %s as date %s", $value, $date->format("Y-m-d H:i:s")));
+
+ return $date;
+ }
+ }
+
+ // if we arrive here, we could not process the date
+ $this->logger->debug(sprintf(
+ "Line %d : a date could not be interpreted. Was %s.",
+ $this->line,
+ $value));
+
+ return false;
+ }
+
+
+}
diff --git a/src/Bundle/ChillPerson/Config/ConfigPersonAltNamesHelper.php b/src/Bundle/ChillPerson/Config/ConfigPersonAltNamesHelper.php
new file mode 100644
index 000000000..92c093385
--- /dev/null
+++ b/src/Bundle/ChillPerson/Config/ConfigPersonAltNamesHelper.php
@@ -0,0 +1,70 @@
+config = $config;
+ }
+
+ /**
+ * Return true if at least one alt name is configured
+ *
+ * @return bool
+ */
+ public function hasAltNames(): bool
+ {
+ return count($this->config) > 0;
+ }
+
+ /**
+ * get the choices as key => values
+ *
+ * @return array
+ */
+ public function getChoices(): array
+ {
+ $choices = [];
+ foreach ($this->config as $entry) {
+
+ $labels = $entry['labels'];
+ $lang = false;
+ $label = false;
+ $cur = reset($labels);
+ while ($cur) {
+ if (key($labels) === 'lang') {
+ $lang = current($labels);
+ }
+
+ if (key($labels) === 'label') {
+ $label = current($labels);
+ }
+
+ if ($lang !== FALSE && $label !== FALSE) {
+ $choices[$entry['key']][$lang] = $label;
+ $lang = false;
+ $label = false;
+ }
+ $cur = next($labels);
+ }
+ }
+
+ return $choices;
+ }
+
+}
diff --git a/src/Bundle/ChillPerson/Controller/AccompanyingPeriodController.php b/src/Bundle/ChillPerson/Controller/AccompanyingPeriodController.php
new file mode 100644
index 000000000..2f6925373
--- /dev/null
+++ b/src/Bundle/ChillPerson/Controller/AccompanyingPeriodController.php
@@ -0,0 +1,441 @@
+,
+ *
+ * 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\PersonBundle\Controller;
+
+use Chill\PersonBundle\Privacy\PrivacyEvent;
+use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
+use Chill\PersonBundle\Entity\Person;
+use Chill\PersonBundle\Form\AccompanyingPeriodType;
+use Chill\PersonBundle\Entity\AccompanyingPeriod;
+use Doctrine\Common\Collections\Criteria;
+use Chill\PersonBundle\Security\Authorization\PersonVoter;
+use Symfony\Component\EventDispatcher\EventDispatcherInterface;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\Response;
+
+/**
+ * Class AccompanyingPeriodController
+ *
+ * @package Chill\PersonBundle\Controller
+ */
+class AccompanyingPeriodController extends AbstractController
+{
+ /**
+ * @var EventDispatcherInterface
+ */
+ protected $eventDispatcher;
+
+ /**
+ * ReportController constructor.
+ *
+ * @param EventDispatcherInterface $eventDispatcher
+ */
+ public function __construct(EventDispatcherInterface $eventDispatcher)
+ {
+ $this->eventDispatcher = $eventDispatcher;
+ }
+
+ /**
+ * @param $person_id
+ * @return Response
+ */
+ public function listAction($person_id){
+
+ $person = $this->_getPerson($person_id);
+
+ $event = new PrivacyEvent($person, array(
+ 'element_class' => AccompanyingPeriod::class,
+ 'action' => 'list'
+ ));
+ $this->eventDispatcher->dispatch(PrivacyEvent::PERSON_PRIVACY_EVENT, $event);
+
+ return $this->render('ChillPersonBundle:AccompanyingPeriod:list.html.twig',
+ array('accompanying_periods' => $person->getAccompanyingPeriodsOrdered(),
+ 'person' => $person));
+ }
+
+ /**
+ * @param $person_id
+ * @param Request $request
+ * @return \Symfony\Component\HttpFoundation\RedirectResponse|Response
+ */
+ public function createAction($person_id, Request $request)
+ {
+
+ $person = $this->_getPerson($person_id);
+
+ $this->denyAccessUnlessGranted(PersonVoter::UPDATE, $person,
+ 'You are not allowed to update this person');
+
+ $accompanyingPeriod = new AccompanyingPeriod(new \DateTime());
+ $accompanyingPeriod->setClosingDate(new \DateTime());
+
+ $person->addAccompanyingPeriod(
+ $accompanyingPeriod);
+
+ $form = $this->createForm(
+ AccompanyingPeriodType::class,
+ $accompanyingPeriod,
+ [
+ 'period_action' => 'create',
+ 'center' => $person->getCenter()
+ ]);
+
+ if ($request->getMethod() === 'POST') {
+ $form->handleRequest($request);
+ $errors = $this->_validatePerson($person);
+ $flashBag = $this->get('session')->getFlashBag();
+
+ if ($form->isValid(array('Default', 'closed'))
+ && count($errors) === 0) {
+
+ $em = $this->getDoctrine()->getManager();
+ $em->persist($accompanyingPeriod);
+ $em->flush();
+ $flashBag->add('success',
+ $this->get('translator')->trans(
+ 'A period has been created.'));
+
+ return $this->redirect($this->generateUrl('chill_person_accompanying_period_list',
+ array('person_id' => $person->getId())));
+ } else {
+ $flashBag->add('error', $this->get('translator')
+ ->trans('Error! Period not created!'));
+
+ foreach($errors as $error) {
+ $flashBag->add('info', $error->getMessage());
+ }
+ }
+ }
+
+ return $this->render('ChillPersonBundle:AccompanyingPeriod:form.html.twig',
+ array(
+ 'form' => $form->createView(),
+ 'person' => $person,
+ 'accompanying_period' => $accompanyingPeriod
+ )
+ );
+ }
+
+ /**
+ * @param $person_id
+ * @param $period_id
+ * @param Request $request
+ * @return \Symfony\Component\HttpFoundation\RedirectResponse|Response|\Symfony\Component\HttpKernel\Exception\NotFoundHttpException
+ */
+ public function updateAction($person_id, $period_id, Request $request){
+ $em = $this->getDoctrine()->getManager();
+
+ $accompanyingPeriod = $em->getRepository('ChillPersonBundle:AccompanyingPeriod')
+ ->find($period_id);
+
+ if ($accompanyingPeriod === null) {
+ return $this->createNotFoundException("Period with id ".$period_id.
+ " is not found");
+ }
+
+ $person = $accompanyingPeriod->getPerson();
+
+ $this->denyAccessUnlessGranted(PersonVoter::UPDATE, $person,
+ 'You are not allowed to update this person');
+
+ $form = $this->createForm(AccompanyingPeriodType::class,
+ $accompanyingPeriod, array('period_action' => 'update',
+ 'center' => $person->getCenter()));
+
+ if ($request->getMethod() === 'POST') {
+ $form->handleRequest($request);
+ $errors = $this->_validatePerson($person);
+ $flashBag = $this->get('session')->getFlashBag();
+
+ if ($form->isValid(array('Default', 'closed'))
+ && count($errors) === 0) {
+ $em->flush();
+
+ $flashBag->add('success',
+ $this->get('translator')->trans(
+ 'An accompanying period has been updated.'));
+
+ return $this->redirect($this->generateUrl('chill_person_accompanying_period_list',
+ array('person_id' => $person->getId())));
+ } else {
+ $flashBag->add('error', $this->get('translator')
+ ->trans('Error when updating the period'));
+
+ foreach($errors as $error) {
+ $flashBag->add('info', $error->getMessage());
+ }
+ }
+ }
+
+ return $this->render('ChillPersonBundle:AccompanyingPeriod:form.html.twig',
+ array(
+ 'form' => $form->createView(),
+ 'person' => $person,
+ 'accompanying_period' => $accompanyingPeriod
+ ) );
+ }
+
+ /**
+ * @param $person_id
+ * @param Request $request
+ * @return \Symfony\Component\HttpFoundation\RedirectResponse|Response
+ * @throws \Exception
+ */
+ public function closeAction($person_id, Request $request)
+ {
+
+ $person = $this->_getPerson($person_id);
+
+ $this->denyAccessUnlessGranted(PersonVoter::UPDATE, $person,
+ 'You are not allowed to update this person');
+
+ if ($person->isOpen() === false) {
+ $this->get('session')->getFlashBag()
+ ->add('error', $this->get('translator')
+ ->trans('Beware period is closed',
+ array('%name%' => $person->__toString())));
+
+ return $this->redirect(
+ $this->generateUrl('chill_person_accompanying_period_list', array(
+ 'person_id' => $person->getId()
+ )));
+ }
+
+ $current = $person->getCurrentAccompanyingPeriod();
+
+ $form = $this->createForm(AccompanyingPeriodType::class, $current, array(
+ 'period_action' => 'close',
+ 'center' => $person->getCenter()
+ ));
+
+ if ($request->getMethod() === 'POST') {
+ $form->handleRequest($request);
+
+ if ($form->isValid()){
+ $person->close($current);
+ $errors = $this->_validatePerson($person);
+
+ if (count($errors) === 0) {
+ $this->get('session')->getFlashBag()
+ ->add('success', $this->get('translator')
+ ->trans('An accompanying period has been closed.',
+ array('%name%' => $person->__toString())));
+
+ $this->getDoctrine()->getManager()->flush();
+
+ return $this->redirect(
+ $this->generateUrl('chill_person_accompanying_period_list', array(
+ 'person_id' => $person->getId()
+ ))
+ );
+ } else {
+ $this->get('session')->getFlashBag()
+ ->add('error', $this->get('translator')
+ ->trans('Error! Period not closed!'));
+
+ foreach ($errors as $error) {
+ $this->get('session')->getFlashBag()
+ ->add('info', $error->getMessage());
+ }
+ }
+ } else { //if form is not valid
+ $this->get('session')->getFlashBag()
+ ->add('error',
+ $this->get('translator')
+ ->trans('Pediod closing form is not valid')
+ );
+
+ foreach ($form->getErrors() as $error) {
+ $this->get('session')->getFlashBag()
+ ->add('info', $error->getMessage());
+ }
+ }
+ }
+
+ return $this->render('ChillPersonBundle:AccompanyingPeriod:form.html.twig',
+ array(
+ 'form' => $form->createView(),
+ 'person' => $person,
+ 'accompanying_period' => $current
+ ));
+ }
+
+ /**
+ * @param Person $person
+ * @return \Symfony\Component\Validator\ConstraintViolationListInterface
+ */
+ private function _validatePerson(Person $person) {
+ $errors = $this->get('validator')->validate($person, null,
+ array('Default'));
+ $errors_accompanying_period = $this->get('validator')->validate($person, null,
+ array('accompanying_period_consistent'));
+
+ foreach($errors_accompanying_period as $error ) {
+ $errors->add($error);
+ }
+
+ return $errors;
+ }
+
+ /**
+ * @param $person_id
+ * @param Request $request
+ * @return \Symfony\Component\HttpFoundation\RedirectResponse|Response
+ */
+ public function openAction($person_id, Request $request) {
+ $person = $this->_getPerson($person_id);
+
+ $this->denyAccessUnlessGranted(PersonVoter::UPDATE, $person,
+ 'You are not allowed to update this person');
+
+ //in case the person is already open
+ if ($person->isOpen()) {
+ $this->get('session')->getFlashBag()
+ ->add('error', $this->get('translator')
+ ->trans('Error! Period %name% is not closed ; it can be open',
+ array('%name%' => $person->__toString())));
+
+ return $this->redirect(
+ $this->generateUrl('chill_person_accompanying_period_list', array(
+ 'person_id' => $person->getId()
+ )));
+ }
+
+ $accompanyingPeriod = new AccompanyingPeriod(new \DateTime());
+
+ $form = $this->createForm(AccompanyingPeriodType::class,
+ $accompanyingPeriod, array('period_action' => 'open',
+ 'center' => $person->getCenter()));
+
+ if ($request->getMethod() === 'POST') {
+ $form->handleRequest($request);
+
+ if ($form->isValid()) {
+ $person->open($accompanyingPeriod);
+
+ $errors = $this->_validatePerson($person);
+
+ if (count($errors) <= 0) {
+ $this->get('session')->getFlashBag()
+ ->add('success', $this->get('translator')
+ ->trans('An accompanying period has been opened.',
+ array('%name%' => $person->__toString())));
+
+ $this->getDoctrine()->getManager()->flush();
+
+ return $this->redirect(
+ $this->generateUrl('chill_person_accompanying_period_list', array(
+ 'person_id' => $person->getId()
+ )));
+ } else {
+ $this->get('session')->getFlashBag()
+ ->add('error', $this->get('translator')
+ ->trans('Period not opened'));
+
+ foreach ($errors as $error) {
+ $this->get('session')->getFlashBag()
+ ->add('info', $error->getMessage());
+ }
+ }
+ } else { // if errors in forms
+ $this->get('session')->getFlashBag()
+ ->add('error', $this->get('translator')
+ ->trans('Period not opened : form is invalid'));
+ }
+ }
+
+ return $this->render('ChillPersonBundle:AccompanyingPeriod:form.html.twig',
+ array('form' => $form->createView(),
+ 'person' => $person,
+ 'accompanying_period' => $accompanyingPeriod));
+ }
+
+ /**
+ * @param $person_id
+ * @param $period_id
+ * @param Request $request
+ * @return object|\Symfony\Component\HttpFoundation\RedirectResponse|Response
+ */
+ public function reOpenAction($person_id, $period_id, Request $request)
+ {
+ $person = $this->_getPerson($person_id);
+
+ $criteria = Criteria::create();
+ $criteria->where($criteria->expr()->eq('id', $period_id));
+
+ /* @var $period AccompanyingPeriod */
+ $period = $person->getAccompanyingPeriods()
+ ->matching($criteria)
+ ->first();
+
+ if ($period === NULL) {
+ throw $this->createNotFoundException('period not found');
+ }
+
+ $confirm = $request->query->getBoolean('confirm', false);
+
+ if ($confirm === true && $period->canBeReOpened()) {
+ $period->reOpen();
+
+ $this->_validatePerson($person);
+
+ $this->getDoctrine()->getManager()->flush();
+
+ $this->addFlash('success', $this->get('translator')->trans(
+ 'The period has been re-opened'));
+
+ return $this->redirectToRoute('chill_person_accompanying_period_list',
+ array('person_id' => $person->getId()));
+ } elseif ($confirm === false && $period->canBeReOpened()) {
+ return $this->render('ChillPersonBundle:AccompanyingPeriod:re_open.html.twig', array(
+ 'period' => $period,
+ 'person' => $person
+ ));
+ } else {
+ return (new Response())
+ ->setStatusCode(Response::HTTP_BAD_REQUEST)
+ ->setContent("You cannot re-open this period");
+ }
+ }
+
+ /**
+ *
+ * @param int $id
+ * @return Person
+ * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException if the person is not found
+ */
+ private function _getPerson($id) {
+ $person = $this->getDoctrine()->getManager()
+ ->getRepository('ChillPersonBundle:Person')->find($id);
+
+ if ($person === null) {
+ throw $this->createNotFoundException('Person not found');
+ }
+
+ $this->denyAccessUnlessGranted(PersonVoter::SEE, $person,
+ "You are not allowed to see this person");
+
+ return $person;
+ }
+}
diff --git a/src/Bundle/ChillPerson/Controller/AdminClosingMotiveController.php b/src/Bundle/ChillPerson/Controller/AdminClosingMotiveController.php
new file mode 100644
index 000000000..483619be6
--- /dev/null
+++ b/src/Bundle/ChillPerson/Controller/AdminClosingMotiveController.php
@@ -0,0 +1,55 @@
+query->has('parent_id')) {
+ $parentId = $request->query->getInt('parent_id');
+
+ $parent = $this->getDoctrine()->getManager()
+ ->getRepository($this->getEntityClass())
+ ->find($parentId);
+
+ if (NULL === $parent) {
+ throw $this->createNotFoundException('parent id not found');
+ }
+
+ $entity->setParent($parent);
+ }
+ return $entity;
+ }
+
+ /**
+ * @param string $action
+ * @param \Doctrine\ORM\QueryBuilder|mixed $query
+ * @param Request $request
+ * @param PaginatorInterface $paginator
+ * @return \Doctrine\ORM\QueryBuilder|mixed
+ */
+ protected function orderQuery(string $action, $query, Request $request, PaginatorInterface $paginator)
+ {
+ /** @var \Doctrine\ORM\QueryBuilder $query */
+ return $query->orderBy('e.ordering', 'ASC');
+ }
+}
diff --git a/src/Bundle/ChillPerson/Controller/AdminController.php b/src/Bundle/ChillPerson/Controller/AdminController.php
new file mode 100644
index 000000000..751beb7a8
--- /dev/null
+++ b/src/Bundle/ChillPerson/Controller/AdminController.php
@@ -0,0 +1,31 @@
+render('ChillPersonBundle:Admin:layout.html.twig', []);
+ }
+
+ /**
+ * @return \Symfony\Component\HttpFoundation\RedirectResponse
+ */
+ public function redirectToAdminIndexAction()
+ {
+ return $this->redirectToRoute('chill_main_admin_central');
+ }
+}
diff --git a/src/Bundle/ChillPerson/Controller/AdminMaritalStatusController.php b/src/Bundle/ChillPerson/Controller/AdminMaritalStatusController.php
new file mode 100644
index 000000000..da46ac5c5
--- /dev/null
+++ b/src/Bundle/ChillPerson/Controller/AdminMaritalStatusController.php
@@ -0,0 +1,15 @@
+,
+ *
+ * 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\PersonBundle\Controller;
+
+use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
+use Symfony\Component\Form\Extension\Core\Type\SubmitType;
+use Chill\PersonBundle\Entity\Person;
+use Chill\MainBundle\Form\Type\AddressType;
+use Chill\MainBundle\Entity\Address;
+use Doctrine\Common\Collections\Criteria;
+use Symfony\Component\HttpFoundation\Request;
+
+/**
+ * Class PersonAddressController
+ * Controller for addresses associated with person
+ *
+ * @package Chill\PersonBundle\Controller
+ * @author Julien Fastré
+ * @author Champs Libres
+ */
+class PersonAddressController extends AbstractController
+{
+
+ public function listAction($person_id)
+ {
+ $person = $this->getDoctrine()->getManager()
+ ->getRepository('ChillPersonBundle:Person')
+ ->find($person_id);
+
+ if ($person === null) {
+ throw $this->createNotFoundException("Person with id $person_id not"
+ . " found ");
+ }
+
+ $this->denyAccessUnlessGranted(
+ 'CHILL_PERSON_SEE',
+ $person,
+ "You are not allowed to edit this person."
+ );
+
+ return $this->render('ChillPersonBundle:Address:list.html.twig', array(
+ 'person' => $person
+ ));
+ }
+
+ public function newAction($person_id)
+ {
+ $person = $this->getDoctrine()->getManager()
+ ->getRepository('ChillPersonBundle:Person')
+ ->find($person_id);
+
+ if ($person === null) {
+ throw $this->createNotFoundException("Person with id $person_id not"
+ . " found ");
+ }
+
+ $this->denyAccessUnlessGranted(
+ 'CHILL_PERSON_UPDATE',
+ $person,
+ "You are not allowed to edit this person."
+ );
+
+ $address = new Address();
+
+ $form = $this->createCreateForm($person, $address);
+
+ return $this->render('ChillPersonBundle:Address:new.html.twig', array(
+ 'person' => $person,
+ 'form' => $form->createView()
+ ));
+ }
+
+ public function createAction($person_id, Request $request)
+ {
+ $person = $this->getDoctrine()->getManager()
+ ->getRepository('ChillPersonBundle:Person')
+ ->find($person_id);
+
+ if ($person === null) {
+ throw $this->createNotFoundException("Person with id $person_id not"
+ . " found ");
+ }
+
+ $this->denyAccessUnlessGranted(
+ 'CHILL_PERSON_UPDATE',
+ $person,
+ "You are not allowed to edit this person."
+ );
+
+ $address = new Address();
+
+ $form = $this->createCreateForm($person, $address);
+ $form->handleRequest($request);
+
+ $person->addAddress($address);
+
+ if ($form->isSubmitted() && $form->isValid()) {
+ $validatePersonErrors = $this->validatePerson($person);
+
+ if (count($validatePersonErrors) !== 0) {
+ foreach ($validatePersonErrors as $error) {
+ $this->addFlash('error', $error->getMessage());
+ }
+ } elseif ($form->isValid()) {
+
+ $em = $this->getDoctrine()->getManager();
+ $em->flush();
+
+ $this->addFlash(
+ 'success',
+ $this->get('translator')->trans('The new address was created successfully')
+ );
+
+ return $this->redirectToRoute('chill_person_address_list', array(
+ 'person_id' => $person->getId()
+ ));
+ } else {
+ $this->addFlash('error', $this->get('translator')
+ ->trans('Error! Address not created!'));
+ }
+ }
+
+ return $this->render('ChillPersonBundle:Address:new.html.twig', array(
+ 'person' => $person,
+ 'form' => $form->createView()
+ ));
+ }
+
+ public function editAction($person_id, $address_id)
+ {
+ $person = $this->getDoctrine()->getManager()
+ ->getRepository('ChillPersonBundle:Person')
+ ->find($person_id);
+
+ if ($person === null) {
+ throw $this->createNotFoundException("Person with id $person_id not"
+ . " found ");
+ }
+
+ $this->denyAccessUnlessGranted(
+ 'CHILL_PERSON_UPDATE',
+ $person,
+ "You are not allowed to edit this person."
+ );
+
+ $address = $this->findAddressById($person, $address_id);
+
+ $form = $this->createEditForm($person, $address);
+
+ return $this->render('ChillPersonBundle:Address:edit.html.twig', array(
+ 'person' => $person,
+ 'address' => $address,
+ 'form' => $form->createView()
+ ));
+ }
+
+ public function updateAction($person_id, $address_id, Request $request)
+ {
+ $person = $this->getDoctrine()->getManager()
+ ->getRepository('ChillPersonBundle:Person')
+ ->find($person_id);
+
+ if ($person === null) {
+ throw $this->createNotFoundException("Person with id $person_id not"
+ . " found ");
+ }
+
+ $this->denyAccessUnlessGranted(
+ 'CHILL_PERSON_UPDATE',
+ $person,
+ "You are not allowed to edit this person."
+ );
+
+ $address = $this->findAddressById($person, $address_id);
+
+ $form = $this->createEditForm($person, $address);
+ $form->handleRequest($request);
+
+ if ($form->isSubmitted() and $form->isValid()) {
+ $validatePersonErrors = $this->validatePerson($person);
+
+ if (count($validatePersonErrors) !== 0) {
+ foreach ($validatePersonErrors as $error) {
+ $this->addFlash('error', $error->getMessage());
+ }
+ } elseif ($form->isValid()) {
+ $this->getDoctrine()->getManager()
+ ->flush();
+
+ $this->addFlash('success', $this->get('translator')->trans(
+ "The address has been successfully updated"
+ ));
+
+ return $this->redirectToRoute('chill_person_address_list', array(
+ 'person_id' => $person->getId()
+ ));
+ } else {
+ $this->addFlash('error', $this->get('translator')
+ ->trans('Error when updating the period'));
+ }
+ }
+
+ return $this->render('ChillPersonBundle:Address:edit.html.twig', array(
+ 'person' => $person,
+ 'address' => $address,
+ 'form' => $form->createView()
+ ));
+ }
+
+ /**
+ * @param Person $person
+ * @param Address $address
+ * @return \Symfony\Component\Form\Form
+ */
+ protected function createEditForm(Person $person, Address $address)
+ {
+ $form = $this->createForm(AddressType::class, $address, array(
+ 'method' => 'POST',
+ 'action' => $this->generateUrl('chill_person_address_update', array(
+ 'person_id' => $person->getId(),
+ 'address_id' => $address->getId()
+ )),
+ 'has_no_address' => true
+ ));
+
+ $form->add('submit', SubmitType::class, array(
+ 'label' => 'Submit'
+ ));
+
+ return $form;
+ }
+
+ /**
+ *
+ * @param Person $person
+ * @param Address $address
+ * @return \Symfony\Component\Form\Form
+ */
+ protected function createCreateForm(Person $person, Address $address)
+ {
+ $form = $this->createForm(AddressType::class, $address, array(
+ 'method' => 'POST',
+ 'action' => $this->generateUrl('chill_person_address_create', array(
+ 'person_id' => $person->getId()
+ )),
+ 'has_no_address' => true
+ ));
+
+ $form->add('submit', SubmitType::class, array(
+ 'label' => 'Submit'
+ ));
+
+ return $form;
+ }
+
+ /**
+ *
+ * @param Person $person
+ * @param int $address_id
+ * @return Address
+ * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException if the address id does not exists or is not associated with given person
+ */
+ protected function findAddressById(Person $person, $address_id)
+ {
+ // filtering address
+ $criteria = Criteria::create()
+ ->where(Criteria::expr()->eq('id', $address_id))
+ ->setMaxResults(1);
+ $addresses = $person->getAddresses()->matching($criteria);
+
+ if (count($addresses) === 0) {
+ throw $this->createNotFoundException("Address with id $address_id "
+ . "matching person $person_id not found ");
+ }
+
+ return $addresses->first();
+ }
+
+ /**
+ * @param Chill\PersonBundle\Entity\Person $person
+ * @return \Symfony\Component\Validator\ConstraintViolationListInterface
+ */
+ private function validatePerson(Person $person)
+ {
+ $errors = $this->get('validator')
+ ->validate($person, null, array('Default'));
+ $errors_addresses_consistent = $this->get('validator')
+ ->validate($person, null, array('addresses_consistent'));
+
+ foreach($errors_addresses_consistent as $error) {
+ $errors->add($error);
+ }
+
+ return $errors;
+ }
+}
diff --git a/src/Bundle/ChillPerson/Controller/PersonController.php b/src/Bundle/ChillPerson/Controller/PersonController.php
new file mode 100644
index 000000000..28a960520
--- /dev/null
+++ b/src/Bundle/ChillPerson/Controller/PersonController.php
@@ -0,0 +1,416 @@
+,
+ *
+ * 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\PersonBundle\Controller;
+
+use Chill\PersonBundle\Privacy\PrivacyEvent;
+use Psr\Log\LoggerInterface;
+use Chill\PersonBundle\Entity\Person;
+use Chill\PersonBundle\Form\PersonType;
+use Chill\PersonBundle\Form\CreationPersonType;
+use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
+use Symfony\Component\EventDispatcher\EventDispatcherInterface;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\Response;
+use Symfony\Component\Security\Core\Role\Role;
+use Chill\PersonBundle\Security\Authorization\PersonVoter;
+use Chill\PersonBundle\Search\SimilarPersonMatcher;
+use Symfony\Component\Translation\TranslatorInterface;
+use Chill\MainBundle\Search\SearchProvider;
+use Chill\PersonBundle\Repository\PersonRepository;
+use Chill\PersonBundle\Config\ConfigPersonAltNamesHelper;
+
+/**
+ * Class PersonController
+ *
+ * @package Chill\PersonBundle\Controller
+ */
+class PersonController extends AbstractController
+{
+ /**
+ *
+ * @var SimilarPersonMatcher
+ */
+ protected $similarPersonMatcher;
+
+ /**
+ *
+ * @var TranslatorInterface
+ */
+ protected $translator;
+
+
+ /**
+ * @var EventDispatcherInterface
+ */
+ protected $eventDispatcher;
+
+ /**
+ *
+ * @var PersonRepository;
+ */
+ protected $personRepository;
+
+ /**
+ *
+ * @var ConfigPersonAltNamesHelper
+ */
+ protected $configPersonAltNameHelper;
+
+ /**
+ * @var \Psr\Log\LoggerInterface
+ */
+ private $logger;
+
+ public function __construct(
+ SimilarPersonMatcher $similarPersonMatcher,
+ TranslatorInterface $translator,
+ EventDispatcherInterface $eventDispatcher,
+ PersonRepository $personRepository,
+ ConfigPersonAltNamesHelper $configPersonAltNameHelper,
+ LoggerInterface $logger
+ ) {
+ $this->similarPersonMatcher = $similarPersonMatcher;
+ $this->translator = $translator;
+ $this->eventDispatcher = $eventDispatcher;
+ $this->configPersonAltNameHelper = $configPersonAltNameHelper;
+ $this->personRepository = $personRepository;
+ $this->logger = $logger;
+ }
+
+ public function getCFGroup()
+ {
+ $cFGroup = null;
+
+ $em = $this->getDoctrine()->getManager();
+ $cFDefaultGroup = $em->getRepository("ChillCustomFieldsBundle:CustomFieldsDefaultGroup")
+ ->findOneByEntity("Chill\PersonBundle\Entity\Person");
+
+ if($cFDefaultGroup) {
+ $cFGroup = $cFDefaultGroup->getCustomFieldsGroup();
+ }
+
+ return $cFGroup;
+ }
+
+ public function viewAction($person_id)
+ {
+ $person = $this->_getPerson($person_id);
+
+ if ($person === null) {
+ throw $this->createNotFoundException("Person with id $person_id not"
+ . " found on this server");
+ }
+
+ $this->denyAccessUnlessGranted('CHILL_PERSON_SEE', $person,
+ "You are not allowed to see this person.");
+
+ $event = new PrivacyEvent($person);
+ $this->eventDispatcher->dispatch(PrivacyEvent::PERSON_PRIVACY_EVENT, $event);
+
+ return $this->render('ChillPersonBundle:Person:view.html.twig',
+ array(
+ "person" => $person,
+ "cFGroup" => $this->getCFGroup(),
+ "alt_names" => $this->configPersonAltNameHelper->getChoices(),
+ ));
+ }
+
+ public function editAction($person_id)
+ {
+ $person = $this->_getPerson($person_id);
+
+ if ($person === null) {
+ return $this->createNotFoundException();
+ }
+
+ $this->denyAccessUnlessGranted('CHILL_PERSON_UPDATE', $person,
+ 'You are not allowed to edit this person');
+
+ $form = $this->createForm(PersonType::class, $person,
+ array(
+ "action" => $this->generateUrl('chill_person_general_update',
+ array("person_id" => $person_id)),
+ "cFGroup" => $this->getCFGroup()
+ )
+ );
+
+ return $this->render('ChillPersonBundle:Person:edit.html.twig',
+ array('person' => $person, 'form' => $form->createView()));
+ }
+
+ public function updateAction($person_id, Request $request)
+ {
+ $person = $this->_getPerson($person_id);
+
+ if ($person === null) {
+ return $this->createNotFoundException();
+ }
+
+ $this->denyAccessUnlessGranted('CHILL_PERSON_UPDATE', $person,
+ 'You are not allowed to edit this person');
+
+ $form = $this->createForm(PersonType::class, $person,
+ array("cFGroup" => $this->getCFGroup()));
+
+ if ($request->getMethod() === 'POST') {
+ $form->handleRequest($request);
+
+ if ( ! $form->isValid() ) {
+ $this->get('session')
+ ->getFlashBag()->add('error', $this->translator
+ ->trans('This form contains errors'));
+
+ return $this->render('ChillPersonBundle:Person:edit.html.twig',
+ array('person' => $person,
+ 'form' => $form->createView()));
+ }
+
+ $this->get('session')->getFlashBag()
+ ->add('success',
+ $this->get('translator')
+ ->trans('The person data has been updated')
+ );
+
+ $em = $this->getDoctrine()->getManager();
+ $em->flush();
+
+ $url = $this->generateUrl('chill_person_view', array(
+ 'person_id' => $person->getId()
+ ));
+
+ return $this->redirect($url);
+ }
+ }
+
+ public function newAction()
+ {
+ // this is a dummy default center.
+ $defaultCenter = $this->get('security.token_storage')
+ ->getToken()
+ ->getUser()
+ ->getGroupCenters()[0]
+ ->getCenter();
+
+ $person = (new Person(new \DateTime('now')))
+ ->setCenter($defaultCenter);
+
+ $form = $this->createForm(
+ CreationPersonType::class,
+ $person,
+ array(
+ 'action' => $this->generateUrl('chill_person_review'),
+ 'form_status' => CreationPersonType::FORM_NOT_REVIEWED
+ ));
+
+ return $this->_renderNewForm($form);
+ }
+
+ private function _renderNewForm($form)
+ {
+ return $this->render('ChillPersonBundle:Person:create.html.twig',
+ array(
+ 'form' => $form->createView()
+ ));
+ }
+
+ /**
+ *
+ * @param type $form
+ * @return \Chill\PersonBundle\Entity\Person
+ */
+ private function _bindCreationForm($form)
+ {
+ /**
+ * @var Person
+ */
+ $person = $form->getData();
+
+ $periods = $person->getAccompanyingPeriodsOrdered();
+ $period = $periods[0];
+ $period->setOpeningDate($form['creation_date']->getData());
+// $person = new Person($form['creation_date']->getData());
+//
+// $person->setFirstName($form['firstName']->getData())
+// ->setLastName($form['lastName']->getData())
+// ->setGender($form['gender']->getData())
+// ->setBirthdate($form['birthdate']->getData())
+// ->setCenter($form['center']->getData())
+// ;
+
+ return $person;
+ }
+
+ /**
+ *
+ * @param \Chill\PersonBundle\Entity\Person $person
+ * @return \Symfony\Component\Validator\ConstraintViolationListInterface
+ */
+ private function _validatePersonAndAccompanyingPeriod(Person $person)
+ {
+ $errors = $this->get('validator')
+ ->validate($person, null, array('creation'));
+
+ //validate accompanying periods
+ $periods = $person->getAccompanyingPeriods();
+
+ foreach ($periods as $period) {
+ $period_errors = $this->get('validator')
+ ->validate($period);
+
+ //group errors :
+ foreach($period_errors as $error) {
+ $errors->add($error);
+ }
+ }
+
+ return $errors;
+ }
+
+ public function reviewAction(Request $request)
+ {
+ if ($request->getMethod() !== 'POST') {
+ $r = new Response("You must send something to review the creation of a new Person");
+ $r->setStatusCode(400);
+ return $r;
+ }
+
+ $form = $this->createForm(
+ //CreationPersonType::NAME,
+ CreationPersonType::class,
+ new Person(),
+ array(
+ 'action' => $this->generateUrl('chill_person_create'),
+ 'form_status' => CreationPersonType::FORM_BEING_REVIEWED
+ ));
+
+ $form->handleRequest($request);
+
+ $person = $this->_bindCreationForm($form);
+
+ $errors = $this->_validatePersonAndAccompanyingPeriod($person);
+ $this->logger->info(sprintf('Person created with %d errors ', count($errors)));
+
+ if ($errors->count() > 0) {
+ $this->logger->info('The created person has errors');
+ $flashBag = $this->get('session')->getFlashBag();
+ $translator = $this->get('translator');
+
+ $flashBag->add('error', $translator->trans('The person data are not valid'));
+
+ foreach($errors as $error) {
+ $flashBag->add('info', $error->getMessage());
+ }
+
+ $form = $this->createForm(
+ CreationPersonType::NAME,
+ $person,
+ array(
+ 'action' => $this->generateUrl('chill_person_review'),
+ 'form_status' => CreationPersonType::FORM_NOT_REVIEWED
+ ));
+
+ $form->handleRequest($request);
+
+ return $this->_renderNewForm($form);
+ } else {
+ $this->logger->info('Person created without errors');
+ }
+
+ $alternatePersons = $this->similarPersonMatcher
+ ->matchPerson($person);
+
+ if (count($alternatePersons) === 0) {
+ return $this->forward('ChillPersonBundle:Person:create');
+ }
+
+ $this->get('session')->getFlashBag()->add('info',
+ $this->get('translator')->trans(
+ '%nb% person with similar name. Please verify that this is a new person',
+ array('%nb%' => count($alternatePersons)))
+ );
+
+ return $this->render('ChillPersonBundle:Person:create_review.html.twig',
+ array(
+ 'person' => $person,
+ 'alternatePersons' => $alternatePersons,
+ 'firstName' => $form['firstName']->getData(),
+ 'lastName' => $form['lastName']->getData(),
+ 'birthdate' => $form['birthdate']->getData(),
+ 'gender' => $form['gender']->getData(),
+ 'creation_date' => $form['creation_date']->getData(),
+ 'form' => $form->createView()));
+ }
+
+ public function createAction(Request $request)
+ {
+
+ if ($request->getMethod() !== 'POST') {
+ $r = new Response('You must send something to create a person !');
+ $r->setStatusCode(400);
+ return $r;
+ }
+
+ $form = $this->createForm(CreationPersonType::class, null, array(
+ 'form_status' => CreationPersonType::FORM_REVIEWED
+ ));
+
+ $form->handleRequest($request);
+
+ $person = $this->_bindCreationForm($form);
+
+ $errors = $this->_validatePersonAndAccompanyingPeriod($person);
+
+ $this->denyAccessUnlessGranted('CHILL_PERSON_CREATE', $person,
+ 'You are not allowed to create this person');
+
+ if ($errors->count() === 0) {
+ $em = $this->getDoctrine()->getManager();
+
+ $em->persist($person);
+
+ $em->flush();
+
+ return $this->redirect($this->generateUrl('chill_person_general_edit',
+ array('person_id' => $person->getId())));
+ } else {
+ $text = "this should not happen if you reviewed your submission\n";
+ foreach ($errors as $error) {
+ $text .= $error->getMessage()."\n";
+ }
+ $r = new Response($text);
+ $r->setStatusCode(400);
+ return $r;
+ }
+ }
+
+ /**
+ * easy getting a person by his id
+ * @return \Chill\PersonBundle\Entity\Person
+ */
+ private function _getPerson($id)
+ {
+ $person = $this->personRepository->find($id);
+
+ return $person;
+ }
+}
diff --git a/src/Bundle/ChillPerson/Controller/TimelinePersonController.php b/src/Bundle/ChillPerson/Controller/TimelinePersonController.php
new file mode 100644
index 000000000..774351581
--- /dev/null
+++ b/src/Bundle/ChillPerson/Controller/TimelinePersonController.php
@@ -0,0 +1,109 @@
+
+ *
+ * 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\PersonBundle\Controller;
+
+use Chill\PersonBundle\Privacy\PrivacyEvent;
+use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
+use Symfony\Component\EventDispatcher\EventDispatcherInterface;
+use Symfony\Component\HttpFoundation\Response;
+use Symfony\Component\HttpFoundation\Request;
+use Chill\MainBundle\Timeline\TimelineBuilder;
+use Chill\MainBundle\Pagination\PaginatorFactory;
+use Chill\PersonBundle\Security\Authorization\PersonVoter;
+
+/**
+ * Class TimelinePersonController
+ *
+ * @package Chill\PersonBundle\Controller
+ * @author Julien Fastré
+ */
+class TimelinePersonController extends AbstractController
+{
+
+ /**
+ * @var EventDispatcherInterface
+ */
+ protected $eventDispatcher;
+
+ /**
+ *
+ * @var TimelineBuilder
+ */
+ protected $timelineBuilder;
+
+ /**
+ *
+ * @var PaginatorFactory
+ */
+ protected $paginatorFactory;
+
+ /**
+ * TimelinePersonController constructor.
+ *
+ * @param EventDispatcherInterface $eventDispatcher
+ */
+ public function __construct(
+ EventDispatcherInterface $eventDispatcher,
+ TimelineBuilder $timelineBuilder,
+ PaginatorFactory $paginatorFactory
+ ) {
+ $this->eventDispatcher = $eventDispatcher;
+ $this->timelineBuilder = $timelineBuilder;
+ $this->paginatorFactory = $paginatorFactory;
+ }
+
+
+ public function personAction(Request $request, $person_id)
+ {
+ $person = $this->getDoctrine()
+ ->getRepository('ChillPersonBundle:Person')
+ ->find($person_id);
+
+ if ($person === NULL) {
+ throw $this->createNotFoundException();
+ }
+
+ $this->denyAccessUnlessGranted(PersonVoter::SEE, $person);
+
+ $nbItems = $this->timelineBuilder->countItems('person',
+ [ 'person' => $person ]
+ );
+
+ $paginator = $this->paginatorFactory->create($nbItems);
+
+ $event = new PrivacyEvent($person, array('action' => 'timeline'));
+ $this->eventDispatcher->dispatch(PrivacyEvent::PERSON_PRIVACY_EVENT, $event);
+
+ return $this->render('ChillPersonBundle:Timeline:index.html.twig', array
+ (
+ 'timeline' => $this->timelineBuilder->getTimelineHTML(
+ 'person',
+ array('person' => $person),
+ $paginator->getCurrentPage()->getFirstItemNumber(),
+ $paginator->getItemsPerPage()
+ ),
+ 'person' => $person,
+ 'nb_items' => $nbItems,
+ 'paginator' => $paginator
+ )
+ );
+ }
+
+}
diff --git a/src/Bundle/ChillPerson/DataFixtures/ORM/LoadAccompanyingPeriodClosingMotive.php b/src/Bundle/ChillPerson/DataFixtures/ORM/LoadAccompanyingPeriodClosingMotive.php
new file mode 100644
index 000000000..fad0596a6
--- /dev/null
+++ b/src/Bundle/ChillPerson/DataFixtures/ORM/LoadAccompanyingPeriodClosingMotive.php
@@ -0,0 +1,89 @@
+,
+ *
+ * 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\PersonBundle\DataFixtures\ORM;
+
+use Doctrine\Common\DataFixtures\AbstractFixture;
+use Doctrine\Common\DataFixtures\OrderedFixtureInterface;
+use Doctrine\Persistence\ObjectManager;
+use Chill\PersonBundle\Entity\AccompanyingPeriod\ClosingMotive;
+
+/**
+ * Load closing motives into database
+ *
+ * @author Julien Fastré
+ */
+class LoadAccompanyingPeriodClosingMotive extends AbstractFixture
+ implements OrderedFixtureInterface
+{
+
+ public function getOrder() {
+ return 9500;
+ }
+
+ public static $closingMotives = array(
+ 'nothing_to_do' => array(
+ 'name' => array(
+ 'fr' => 'Plus rien à faire',
+ 'en' => 'Nothing to do',
+ 'nl' => 'nieks meer te doen'
+ )
+ ),
+ 'did_not_come_back' => array(
+ 'name' => array(
+ 'fr' => "N'est plus revenu",
+ 'en' => "Did'nt come back",
+ 'nl' => "Niet teruggekomen"
+ )
+ ),
+ 'no_more_money' => array(
+ 'active' => false,
+ 'name' => array(
+ 'fr' => "Plus d'argent",
+ 'en' => "No more money",
+ 'nl' => "Geen geld"
+ )
+ )
+ );
+
+ public static $references = array();
+
+ public function load(ObjectManager $manager)
+ {
+ foreach (static::$closingMotives as $ref => $new) {
+ $motive = new ClosingMotive();
+ $motive->setName($new['name'])
+ ->setActive((isset($new['active']) ? $new['active'] : true))
+ ;
+
+ $manager->persist($motive);
+ $this->addReference($ref, $motive);
+ echo "Adding ClosingMotive $ref\n";
+ }
+
+ $manager->flush();
+ }
+
+
+
+
+}
diff --git a/src/Bundle/ChillPerson/DataFixtures/ORM/LoadCustomFields.php b/src/Bundle/ChillPerson/DataFixtures/ORM/LoadCustomFields.php
new file mode 100644
index 000000000..235cadd95
--- /dev/null
+++ b/src/Bundle/ChillPerson/DataFixtures/ORM/LoadCustomFields.php
@@ -0,0 +1,229 @@
+
+ *
+ * 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\PersonBundle\DataFixtures\ORM;
+
+use Chill\MainBundle\Templating\TranslatableStringHelper;
+use Doctrine\Common\DataFixtures\AbstractFixture;
+use Doctrine\Common\DataFixtures\OrderedFixtureInterface;
+use Doctrine\Persistence\ObjectManager;
+use Symfony\Component\DependencyInjection\ContainerAwareInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+use Chill\CustomFieldsBundle\Entity\CustomField;
+use Chill\CustomFieldsBundle\Entity\CustomFieldsGroup;
+use Chill\CustomFieldsBundle\CustomFields\CustomFieldTitle;
+use Chill\CustomFieldsBundle\CustomFields\CustomFieldText;
+use Chill\CustomFieldsBundle\CustomFields\CustomFieldChoice;
+use Chill\CustomFieldsBundle\Entity\CustomFieldsDefaultGroup;
+use Chill\PersonBundle\Entity\Person;
+use Symfony\Contracts\Translation\TranslatorInterface;
+
+/**
+ *
+ *
+ * @author Julien Fastré
+ */
+class LoadCustomFields extends AbstractFixture implements OrderedFixtureInterface,
+ ContainerAwareInterface
+{
+ /**
+ *
+ * @var ContainerInterface
+ */
+ private $container;
+
+ /**
+ *
+ * @var CustomField
+ */
+ private $customFieldText;
+
+ /**
+ *
+ * @var CustomField
+ */
+ private $customFieldChoice;
+
+ /**
+ * @var TranslatableStringHelper
+ */
+ private $translatableStringHelper;
+
+ /**
+ * @var TranslatorInterface
+ */
+ private $translator;
+
+ /**
+ * LoadCustomFields constructor.
+ *
+ * @param TranslatableStringHelper $translatableStringHelper
+ * @param TranslatorInterface $translator
+ */
+ public function __construct(
+ TranslatableStringHelper $translatableStringHelper,
+ TranslatorInterface $translator
+ ) {
+ $this->translatableStringHelper = $translatableStringHelper;
+ $this->translator = $translator;
+ }
+
+ //put your code here
+ public function getOrder()
+ {
+ return 10003;
+ }
+
+ public function setContainer(ContainerInterface $container = null)
+ {
+ if ($container === null) {
+ throw new \RuntimeException("The given container should not be null");
+ }
+
+ $this->container = $container;
+ }
+
+ public function load(ObjectManager $manager)
+ {
+ $this->loadFields($manager);
+ $this->loadData($manager);
+ $manager->flush();
+ }
+
+ private function loadData(ObjectManager $manager)
+ {
+ $personIds = $this->container->get('doctrine.orm.entity_manager')
+ ->createQuery("SELECT person.id FROM ChillPersonBundle:Person person")
+ ->getScalarResult();
+
+ // get possible values for cfGroup
+ $choices = array_map(
+ function($a) { return $a["slug"]; },
+ $this->customFieldChoice->getOptions()["choices"]
+ );
+ // create faker
+ $faker = \Faker\Factory::create('fr_FR');
+ // select a set of people and add data
+ foreach ($personIds as $id) {
+ // add info on 1 person on 2
+ if (rand(0,1) === 1) {
+ /* @var $person Person */
+ $person = $manager->getRepository(Person::class)->find($id);
+ $person->setCFData(array(
+ "remarques" => $this->createCustomFieldText()
+ ->serialize($faker->text(rand(150, 250)), $this->customFieldText),
+ "document-d-identite" => $this->createCustomFieldChoice()
+ ->serialize(array($choices[array_rand($choices)]), $this->customFieldChoice)
+ ));
+ }
+ }
+ }
+
+ private function createCustomFieldText()
+ {
+ return new CustomFieldText(
+ $this->container->get('request_stack'),
+ $this->container->get('templating'),
+ $this->translatableStringHelper
+ );
+ }
+
+ private function createCustomFieldChoice()
+ {
+ return new CustomFieldChoice(
+ $this->translator,
+ $this->container->get('templating'),
+ $this->translatableStringHelper
+ );
+ }
+
+ private function loadFields(ObjectManager $manager)
+ {
+ $cfGroup = (new CustomFieldsGroup())
+ ->setEntity(Person::class)
+ ->setName(array("fr" => "Données"))
+ ;
+ $manager->persist($cfGroup);
+
+ // make this group default for Person::class
+ $manager->persist(
+ (new CustomFieldsDefaultGroup())
+ ->setCustomFieldsGroup($cfGroup)
+ ->setEntity(Person::class)
+ );
+
+ // create title field
+ $customField0 = (new CustomField())
+ ->setActive(true)
+ ->setName(array("fr" => "Données personnalisées"))
+ ->setSlug("personal-data")
+ ->setOrdering(10)
+ ->setType('title')
+ ->setOptions(array(CustomFieldTitle::TYPE => CustomFieldTitle::TYPE_TITLE))
+ ->setCustomFieldsGroup($cfGroup)
+ ;
+ $manager->persist($customField0);
+
+ // create text field
+ $this->customFieldText = (new CustomField())
+ ->setActive(true)
+ ->setName(array("fr" => "Remarques"))
+ ->setSlug("remarques")
+ ->setOrdering(20)
+ ->setType('text')
+ ->setOptions(array('maxLength' => 5000))
+ ->setCustomFieldsGroup($cfGroup)
+ ;
+ $manager->persist($this->customFieldText);
+
+ // create choice field
+ $this->customFieldChoice = (new CustomField())
+ ->setActive(true)
+ ->setName(array("fr" => "Document d'identité"))
+ ->setSlug("document-d-identite")
+ ->setOrdering(30)
+ ->setType('choice')
+ ->setCustomFieldsGroup($cfGroup)
+ ->setOptions(array(
+ "multiple" => true,
+ "other" => false,
+ "expanded" => true,
+ "active" => true,
+ "slug" => "document-d-identite",
+ "choices" => array(
+ array(
+ "name" => array("fr" => "Carte d'identité"),
+ "active" => true,
+ "slug" => "carte-d-identite"
+ ),
+ array(
+ "name" => array("fr" => "Passeport"),
+ "active" => true,
+ "slug" => "passeport"
+ ),
+ array(
+ "name" => array("fr" => "Titre de séjour"),
+ "active" => true,
+ "slug" => "passeport"
+ )
+ )
+ ))
+ ;
+ $manager->persist($this->customFieldChoice);
+ }
+
+}
diff --git a/src/Bundle/ChillPerson/DataFixtures/ORM/LoadMaritalStatus.php b/src/Bundle/ChillPerson/DataFixtures/ORM/LoadMaritalStatus.php
new file mode 100644
index 000000000..b0cf57637
--- /dev/null
+++ b/src/Bundle/ChillPerson/DataFixtures/ORM/LoadMaritalStatus.php
@@ -0,0 +1,66 @@
+
+ *
+ * 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\PersonBundle\DataFixtures\ORM;
+
+use Doctrine\Common\DataFixtures\AbstractFixture;
+use Doctrine\Common\DataFixtures\OrderedFixtureInterface;
+use Doctrine\Persistence\ObjectManager;
+use Chill\PersonBundle\Entity\MaritalStatus;
+
+/**
+ * Load marital status into database
+ *
+ * @author Marc Ducobu
+ */
+class LoadMaritalStatus extends AbstractFixture implements OrderedFixtureInterface
+{
+ private $maritalStatuses = [
+ ['id' => 'single', 'name' =>['en' => 'single', 'fr' => 'célibataire']],
+ ['id' => 'married', 'name' =>['en' => 'married', 'fr' => 'marié(e)']],
+ ['id' => 'widow', 'name' =>['en' => 'widow', 'fr' => 'veuf – veuve ']],
+ ['id' => 'separat', 'name' =>['en' => 'separated', 'fr' => 'séparé(e)']],
+ ['id' => 'divorce', 'name' =>['en' => 'divorced', 'fr' => 'divorcé(e)']],
+ ['id' => 'legalco', 'name' =>['en' => 'legal cohabitant', 'fr' => 'cohabitant(e) légal(e)']],
+ ['id' => 'unknown', 'name' =>['en' => 'unknown', 'fr' => 'indéterminé']]
+ ];
+
+ public function getOrder()
+ {
+ return 9999;
+ }
+
+ public function load(ObjectManager $manager)
+ {
+ echo "loading maritalStatuses... \n";
+
+ foreach ($this->maritalStatuses as $ms) {
+ echo $ms['name']['en'].' ';
+ $new_ms = new MaritalStatus();
+ $new_ms->setId($ms['id']);
+ $new_ms->setName($ms['name']);
+ $this->addReference('ms_'.$ms['id'], $new_ms);
+ $manager->persist($new_ms);
+ }
+
+ $manager->flush();
+ }
+}
diff --git a/src/Bundle/ChillPerson/DataFixtures/ORM/LoadPeople.php b/src/Bundle/ChillPerson/DataFixtures/ORM/LoadPeople.php
new file mode 100644
index 000000000..0977eef2c
--- /dev/null
+++ b/src/Bundle/ChillPerson/DataFixtures/ORM/LoadPeople.php
@@ -0,0 +1,326 @@
+
+ *
+ * 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\PersonBundle\DataFixtures\ORM;
+
+use Doctrine\Common\DataFixtures\AbstractFixture;
+use Doctrine\Common\DataFixtures\OrderedFixtureInterface;
+use Doctrine\Persistence\ObjectManager;
+use Chill\PersonBundle\Entity\Person;
+use Symfony\Component\DependencyInjection\ContainerAwareInterface;
+use Chill\MainBundle\DataFixtures\ORM\LoadPostalCodes;
+use Chill\MainBundle\Entity\Address;
+
+/**
+ * Load people into database
+ *
+ * @author Julien Fastré
+ * @author Marc Ducobu
+ */
+class LoadPeople extends AbstractFixture implements OrderedFixtureInterface, ContainerAwareInterface
+{
+
+ use \Symfony\Component\DependencyInjection\ContainerAwareTrait;
+
+ protected $faker;
+
+ public function __construct()
+ {
+ $this->faker = \Faker\Factory::create('fr_FR');
+ }
+
+ public function prepare()
+ {
+ //prepare days, month, years
+ $y = 1950;
+ do {
+ $this->years[] = $y;
+ $y = $y +1;
+ } while ($y >= 1990);
+
+ $m = 1;
+ do {
+ $this->month[] = $m;
+ $m = $m +1;
+ } while ($m >= 12);
+
+ $d = 1;
+ do {
+ $this->day[] = $d;
+ $d = $d + 1;
+ } while ($d <= 28);
+ }
+
+ public function getOrder()
+ {
+ return 10000;
+ }
+
+ public function load(ObjectManager $manager)
+ {
+ $this->loadRandPeople($manager);
+ $this->loadExpectedPeople($manager);
+
+ $manager->flush();
+ }
+
+ public function loadExpectedPeople(ObjectManager $manager)
+ {
+ echo "loading expected people...\n";
+
+ foreach ($this->peoples as $person) {
+ $this->addAPerson($this->fillWithDefault($person), $manager);
+ }
+ }
+
+ public function loadRandPeople(ObjectManager $manager)
+ {
+ echo "loading rand people...\n";
+
+ $this->prepare();
+
+ $chooseLastNameOrTri = array('tri', 'tri', 'name', 'tri');
+
+ $i = 0;
+
+ do {
+ $i++;
+
+ $sex = $this->genders[array_rand($this->genders)];
+
+ if ($chooseLastNameOrTri[array_rand($chooseLastNameOrTri)] === 'tri' ) {
+ $length = rand(2, 3);
+ $lastName = '';
+ for ($j = 0; $j <= $length; $j++) {
+ $lastName .= $this->lastNamesTrigrams[array_rand($this->lastNamesTrigrams)];
+ }
+ $lastName = ucfirst($lastName);
+ } else {
+ $lastName = $this->lastNames[array_rand($this->lastNames)];
+ }
+
+ if ($sex === Person::MALE_GENDER) {
+ $firstName = $this->firstNamesMale[array_rand($this->firstNamesMale)];
+ } else {
+ $firstName = $this->firstNamesFemale[array_rand($this->firstNamesFemale)];
+ }
+
+ // add an address on 80% of the created people
+ if (rand(0,100) < 80) {
+ $address = $this->getRandomAddress();
+ // on 30% of those person, add multiple addresses
+ if (rand(0,10) < 4) {
+ $address = array(
+ $address,
+ $this->getRandomAddress()
+ );
+ }
+ } else {
+ $address = null;
+ }
+
+ $person = array(
+ 'FirstName' => $firstName,
+ 'LastName' => $lastName,
+ 'Gender' => $sex,
+ 'Nationality' => (rand(0,100) > 50) ? NULL: 'BE',
+ 'center' => (rand(0,1) == 0) ? 'centerA': 'centerB',
+ 'Address' => $address,
+ 'maritalStatus' => $this->maritalStatusRef[array_rand($this->maritalStatusRef)]
+ );
+
+ $this->addAPerson($this->fillWithDefault($person), $manager);
+
+ } while ($i <= 100);
+ }
+
+ /**
+ * fill a person array with default value
+ *
+ * @param string[] $specific
+ */
+ private function fillWithDefault(array $specific)
+ {
+ return array_merge(array(
+ 'Birthdate' => "1960-10-12",
+ 'PlaceOfBirth' => "Ottignies Louvain-La-Neuve",
+ 'Gender' => Person::MALE_GENDER,
+ 'Email' => "Email d'un ami: roger@tt.com",
+ 'CountryOfBirth' => 'BE',
+ 'Nationality' => 'BE',
+ 'CFData' => array(),
+ 'Address' => null
+ ), $specific);
+ }
+
+ /**
+ * create a new person from array data
+ *
+ * @param array $person
+ * @param ObjectManager $manager
+ * @throws \Exception
+ */
+ private function addAPerson(array $person, ObjectManager $manager)
+ {
+ $p = new Person();
+
+ foreach ($person as $key => $value) {
+ switch ($key) {
+ case 'CountryOfBirth':
+ case 'Nationality':
+ $value = $this->getCountry($value);
+ break;
+ case 'Birthdate':
+ $value = new \DateTime($value);
+ break;
+ case 'center':
+ case 'maritalStatus':
+ $value = $this->getReference($value);
+ break;
+ }
+
+ //try to add the data using the setSomething function,
+ // if not possible, fallback to addSomething function
+ if (method_exists($p, 'set'.$key)) {
+ call_user_func(array($p, 'set'.$key), $value);
+ } elseif (method_exists($p, 'add'.$key)) {
+ // if we have a "addSomething", we may have multiple items to add
+ // so, we set the value in an array if it is not an array, and
+ // will call the function addSomething multiple times
+ if (!is_array($value)) {
+ $value = array($value);
+ }
+
+ foreach($value as $v) {
+ if ($v !== NULL) {
+ call_user_func(array($p, 'add'.$key), $v);
+ }
+ }
+
+ }
+ }
+
+ $manager->persist($p);
+ echo "add person'".$p->__toString()."'\n";
+ }
+
+ /**
+ * Creata a random address
+ *
+ * @return Address
+ */
+ private function getRandomAddress()
+ {
+ return (new Address())
+ ->setStreetAddress1($this->faker->streetAddress)
+ ->setStreetAddress2(
+ rand(0,9) > 5 ? $this->faker->streetAddress : ''
+ )
+ ->setPostcode($this->getReference(
+ LoadPostalCodes::$refs[array_rand(LoadPostalCodes::$refs)]
+ ))
+ ->setValidFrom($this->faker->dateTimeBetween('-5 years'))
+ ;
+ }
+
+ private function getCountry($countryCode)
+ {
+ if ($countryCode === NULL) {
+ return NULL;
+ }
+ return $this->container->get('doctrine.orm.entity_manager')
+ ->getRepository('ChillMainBundle:Country')
+ ->findOneByCountryCode($countryCode);
+ }
+
+ private $maritalStatusRef = ['ms_single', 'ms_married', 'ms_widow', 'ms_separat',
+ 'ms_divorce', 'ms_legalco', 'ms_unknown'];
+
+ private $firstNamesMale = array("Jean", "Mohamed", "Alfred", "Robert",
+ "Compère", "Jean-de-Dieu",
+ "Charles", "Pierre", "Luc", "Mathieu", "Alain", "Etienne", "Eric",
+ "Corentin", "Gaston", "Spirou", "Fantasio", "Mahmadou", "Mohamidou",
+ "Vursuv" );
+ private $firstNamesFemale = array("Svedana", "Sevlatina","Irène", "Marcelle",
+ "Corentine", "Alfonsine","Caroline","Solange","Gostine", "Fatoumata",
+ "Groseille", "Chana", "Oxana", "Ivana");
+
+ private $lastNames = array("Diallo", "Bah", "Gaillot");
+ private $lastNamesTrigrams = array("fas", "tré", "hu", 'blart', 'van', 'der', 'lin', 'den',
+ 'ta', 'mi', 'gna', 'bol', 'sac', 'ré', 'jo', 'du', 'pont', 'cas', 'tor', 'rob', 'al',
+ 'ma', 'gone', 'car',"fu", "ka", "lot", "no", "va", "du", "bu", "su",
+ "lo", 'to', "cho", "car", 'mo','zu', 'qi', 'mu');
+
+ private $genders = array(Person::MALE_GENDER, Person::FEMALE_GENDER);
+
+ private $years = array();
+
+ private $month = array();
+
+ private $day = array();
+
+ private $peoples = array(
+ array(
+ 'FirstName' => "Depardieu",
+ 'LastName' => "Gérard",
+ 'Birthdate' => "1948-12-27",
+ 'PlaceOfBirth' => "Châteauroux",
+ 'Gender' => Person::MALE_GENDER,
+ 'CountryOfBirth' => 'FR',
+ 'Nationality' => 'RU',
+ 'center' => 'centerA',
+ 'maritalStatus' => 'ms_divorce'
+ ),
+ array(
+ //to have a person with same firstname as Gérard Depardieu
+ 'FirstName' => "Depardieu",
+ 'LastName' => "Jean",
+ 'Birthdate' => "1960-10-12",
+ 'CountryOfBirth' => 'FR',
+ 'Nationality' => 'FR',
+ 'center' => 'centerA',
+ 'maritalStatus' => 'ms_divorce'
+ ),
+ array(
+ //to have a person with same birthdate of Gérard Depardieu
+ 'FirstName' => 'Van Snick',
+ 'LastName' => 'Bart',
+ 'Birthdate' => '1948-12-27',
+ 'center' => 'centerA',
+ 'maritalStatus' => 'ms_legalco'
+ ),
+ array(
+ //to have a woman with Depardieu as FirstName
+ 'FirstName' => 'Depardieu',
+ 'LastName' => 'Charline',
+ 'Gender' => Person::FEMALE_GENDER,
+ 'center' => 'centerA',
+ 'maritalStatus' => 'ms_legalco'
+ ),
+ array(
+ //to have a special character in lastName
+ 'FirstName' => 'Manço',
+ 'LastName' => 'Étienne',
+ 'center' => 'centerA',
+ 'maritalStatus' => 'ms_unknown'
+ )
+ );
+}
diff --git a/src/Bundle/ChillPerson/DataFixtures/ORM/LoadPersonACL.php b/src/Bundle/ChillPerson/DataFixtures/ORM/LoadPersonACL.php
new file mode 100644
index 000000000..0e3cb6dc9
--- /dev/null
+++ b/src/Bundle/ChillPerson/DataFixtures/ORM/LoadPersonACL.php
@@ -0,0 +1,87 @@
+
+ *
+ * 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\PersonBundle\DataFixtures\ORM;
+
+use Doctrine\Common\DataFixtures\AbstractFixture;
+use Doctrine\Common\DataFixtures\OrderedFixtureInterface;
+use Doctrine\Persistence\ObjectManager;
+use Chill\MainBundle\DataFixtures\ORM\LoadPermissionsGroup;
+use Chill\MainBundle\Entity\RoleScope;
+use Chill\PersonBundle\Security\Authorization\PersonVoter;
+
+/**
+ * Add a role CHILL_PERSON_UPDATE & CHILL_PERSON_CREATE for all groups except administrative,
+ * and a role CHILL_PERSON_SEE for administrative
+ *
+ * @author Julien Fastré
+ */
+class LoadPersonACL extends AbstractFixture implements OrderedFixtureInterface
+{
+ public function getOrder()
+ {
+ return 9600;
+ }
+
+
+ public function load(ObjectManager $manager)
+ {
+ foreach (LoadPermissionsGroup::$refs as $permissionsGroupRef) {
+ $permissionsGroup = $this->getReference($permissionsGroupRef);
+
+ //create permission group
+ switch ($permissionsGroup->getName()) {
+ case 'social':
+ case 'direction':
+ printf("Adding CHILL_PERSON_UPDATE & CHILL_PERSON_CREATE to %s permission group \n", $permissionsGroup->getName());
+ $roleScopeUpdate = (new RoleScope())
+ ->setRole('CHILL_PERSON_UPDATE')
+ ->setScope(null);
+ $permissionsGroup->addRoleScope($roleScopeUpdate);
+ $roleScopeCreate = (new RoleScope())
+ ->setRole('CHILL_PERSON_CREATE')
+ ->setScope(null);
+ $permissionsGroup->addRoleScope($roleScopeCreate);
+ $roleScopeList = (new RoleScope())
+ ->setRole(PersonVoter::LISTS)
+ ->setScope(null);
+ $permissionsGroup->addRoleScope($roleScopeList);
+ $roleScopeStats = (new RoleScope())
+ ->setRole(PersonVoter::STATS)
+ ->setScope(null);
+ $permissionsGroup->addRoleScope($roleScopeStats);
+ $manager->persist($roleScopeUpdate);
+ $manager->persist($roleScopeCreate);
+ break;
+ case 'administrative':
+ printf("Adding CHILL_PERSON_SEE to %s permission group \n", $permissionsGroup->getName());
+ $roleScopeSee = (new RoleScope())
+ ->setRole('CHILL_PERSON_SEE')
+ ->setScope(null);
+ $permissionsGroup->addRoleScope($roleScopeSee);
+ $manager->persist($roleScopeSee);
+ break;
+ }
+
+ }
+
+ $manager->flush();
+ }
+
+}
diff --git a/src/Bundle/ChillPerson/DependencyInjection/ChillPersonExtension.php b/src/Bundle/ChillPerson/DependencyInjection/ChillPersonExtension.php
new file mode 100644
index 000000000..c1ea40486
--- /dev/null
+++ b/src/Bundle/ChillPerson/DependencyInjection/ChillPersonExtension.php
@@ -0,0 +1,310 @@
+
+ *
+ * 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\PersonBundle\DependencyInjection;
+
+use Symfony\Component\DependencyInjection\ContainerBuilder;
+use Symfony\Component\Config\FileLocator;
+use Symfony\Component\HttpKernel\DependencyInjection\Extension;
+use Symfony\Component\DependencyInjection\Loader;
+use Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface;
+use Chill\MainBundle\DependencyInjection\MissingBundleException;
+use Chill\PersonBundle\Security\Authorization\PersonVoter;
+use Chill\MainBundle\Security\Authorization\ChillExportVoter;
+use Chill\PersonBundle\Doctrine\DQL\AddressPart;
+
+/**
+ * Class ChillPersonExtension
+ * Loads and manages your bundle configuration
+ *
+ * To learn more see {@link http://symfony.com/doc/current/cookbook/bundles/extension.html}
+ * @package Chill\PersonBundle\DependencyInjection
+ */
+class ChillPersonExtension extends Extension implements PrependExtensionInterface
+{
+
+ /**
+ * {@inheritDoc}
+ * @param array $configs
+ * @param ContainerBuilder $container
+ * @throws \Exception
+ */
+ public function load(array $configs, ContainerBuilder $container)
+ {
+ $configuration = new Configuration();
+ $config = $this->processConfiguration($configuration, $configs);
+
+ // set configuration for validation
+ $container->setParameter('chill_person.validation.birtdate_not_before',
+ $config['validation']['birthdate_not_after']);
+
+ $this->handlePersonFieldsParameters($container, $config['person_fields']);
+ $this->handleAccompanyingPeriodsFieldsParameters($container, $config['accompanying_periods_fields']);
+
+ $loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__.'/../config'));
+ $loader->load('services.yaml');
+ $loader->load('services/widgets.yaml');
+ $loader->load('services/exports.yaml');
+ $loader->load('services/fixtures.yaml');
+ $loader->load('services/controller.yaml');
+ $loader->load('services/search.yaml');
+ $loader->load('services/menu.yaml');
+ $loader->load('services/privacyEvent.yaml');
+ $loader->load('services/command.yaml');
+ $loader->load('services/actions.yaml');
+ $loader->load('services/form.yaml');
+ $loader->load('services/repository.yaml');
+ $loader->load('services/templating.yaml');
+ $loader->load('services/alt_names.yaml');
+
+ // load service advanced search only if configure
+ if ($config['search']['search_by_phone'] != 'never') {
+ $loader->load('services/search_by_phone.yaml');
+ $container->setParameter('chill_person.search.search_by_phone',
+ $config['search']['search_by_phone']);
+ }
+
+ if ($container->getParameter('chill_person.accompanying_period') !== 'hidden') {
+ $loader->load('services/exports_accompanying_period.yaml');
+ }
+ }
+
+ /**
+ * @param ContainerBuilder $container
+ * @param $config
+ */
+ private function handlePersonFieldsParameters(ContainerBuilder $container, $config)
+ {
+ if (array_key_exists('enabled', $config)) {
+ unset($config['enabled']);
+ }
+
+ $container->setParameter('chill_person.person_fields', $config);
+
+ foreach ($config as $key => $value) {
+ switch($key) {
+ case 'accompanying_period':
+ $container->setParameter('chill_person.accompanying_period', $value);
+ break;
+ default:
+ $container->setParameter('chill_person.person_fields.'.$key, $value);
+ break;
+ }
+ }
+ }
+
+ /**
+ * @param ContainerBuilder $container
+ * @param $config
+ */
+ private function handleAccompanyingPeriodsFieldsParameters(ContainerBuilder $container, $config)
+ {
+ $container->setParameter('chill_person.accompanying_period_fields', $config);
+
+ foreach ($config as $key => $value) {
+ switch($key) {
+ case 'enabled':
+ break;
+ default:
+ $container->setParameter('chill_person.accompanying_period_fields.'.$key, $value);
+ break;
+ }
+ }
+ }
+
+ /**
+ * @param ContainerBuilder $container
+ * @throws MissingBundleException
+ */
+ private function declarePersonAsCustomizable (ContainerBuilder $container)
+ {
+ $bundles = $container->getParameter('kernel.bundles');
+ if (!isset($bundles['ChillCustomFieldsBundle'])) {
+ throw new MissingBundleException('ChillCustomFieldsBundle');
+ }
+
+ $container->prependExtensionConfig('chill_custom_fields',
+ array('customizables_entities' =>
+ array(
+ array('class' => 'Chill\PersonBundle\Entity\Person', 'name' => 'PersonEntity')
+ )
+ )
+ );
+ }
+
+ /**
+ * @param ContainerBuilder $container
+ * @throws MissingBundleException
+ */
+ public function prepend(ContainerBuilder $container)
+ {
+ $this->prependRoleHierarchy($container);
+ $this->prependHomepageWidget($container);
+ $this->prependDoctrineDQL($container);
+ $this->prependCruds($container);
+
+ //add person_fields parameter as global
+ $chillPersonConfig = $container->getExtensionConfig($this->getAlias());
+ $config = $this->processConfiguration(new Configuration(), $chillPersonConfig);
+ $twigConfig = array(
+ 'globals' => array(
+ 'chill_person' => array(
+ 'fields' => $config['person_fields']
+ ),
+ 'chill_accompanying_periods' => [
+ 'fields' => $config['accompanying_periods_fields']
+ ]
+ ),
+ 'form_themes' => array('ChillPersonBundle:Export:ListPersonFormFields.html.twig')
+ );
+ $container->prependExtensionConfig('twig', $twigConfig);
+
+ $this-> declarePersonAsCustomizable($container);
+
+ //declare routes for person bundle
+ $container->prependExtensionConfig('chill_main', array(
+ 'routing' => array(
+ 'resources' => array(
+ '@ChillPersonBundle/config/routes.yaml'
+ )
+ )
+ ));
+ }
+
+ /**
+ * Add a widget "add a person" on the homepage, automatically
+ *
+ * @param \Chill\PersonBundle\DependencyInjection\containerBuilder $container
+ */
+ protected function prependHomepageWidget(containerBuilder $container)
+ {
+ $container->prependExtensionConfig('chill_main', array(
+ 'widgets' => array(
+ 'homepage' => array(
+ array(
+ 'widget_alias' => 'add_person',
+ 'order' => 2
+ )
+ )
+ )
+ ));
+ }
+
+ /**
+ * Add role hierarchy.
+ *
+ * @param ContainerBuilder $container
+ */
+ protected function prependRoleHierarchy(ContainerBuilder $container)
+ {
+ $container->prependExtensionConfig('security', array(
+ 'role_hierarchy' => array(
+ 'CHILL_PERSON_UPDATE' => array('CHILL_PERSON_SEE'),
+ 'CHILL_PERSON_CREATE' => array('CHILL_PERSON_SEE'),
+ PersonVoter::LISTS => [ ChillExportVoter::EXPORT ],
+ PersonVoter::STATS => [ ChillExportVoter::EXPORT ]
+ )
+ ));
+ }
+
+ /**
+ * Add DQL function linked with person
+ *
+ * @param ContainerBuilder $container
+ */
+ protected function prependDoctrineDQL(ContainerBuilder $container)
+ {
+ //add DQL function to ORM (default entity_manager)
+
+ $container->prependExtensionConfig('doctrine', array(
+ 'orm' => array(
+ 'dql' => array(
+ 'string_functions' => array(
+ 'GET_PERSON_ADDRESS_ADDRESS_ID' => AddressPart\AddressPartAddressId::class,
+ 'GET_PERSON_ADDRESS_STREET_ADDRESS_1' => AddressPart\AddressPartStreetAddress1::class,
+ 'GET_PERSON_ADDRESS_STREET_ADDRESS_2' => AddressPart\AddressPartStreetAddress2::class,
+ 'GET_PERSON_ADDRESS_VALID_FROM' => AddressPart\AddressPartValidFrom::class,
+ 'GET_PERSON_ADDRESS_POSTCODE_LABEL' => AddressPart\AddressPartPostCodeLabel::class,
+ 'GET_PERSON_ADDRESS_POSTCODE_CODE' => AddressPart\AddressPartPostCodeCode::class,
+ 'GET_PERSON_ADDRESS_POSTCODE_ID' => AddressPart\AddressPartPostCodeId::class,
+ 'GET_PERSON_ADDRESS_COUNTRY_NAME' => AddressPart\AddressPartCountryName::class,
+ 'GET_PERSON_ADDRESS_COUNTRY_CODE' => AddressPart\AddressPartCountryCode::class,
+ 'GET_PERSON_ADDRESS_COUNTRY_ID' => AddressPart\AddressPartCountryId::class,
+ ),
+ 'numeric_functions' => [
+ 'GET_PERSON_ADDRESS_ISNOADDRESS' => AddressPart\AddressPartIsNoAddress::class,
+ ]
+ )
+ )
+ ));
+ }
+
+ /**
+ * @param ContainerBuilder $container
+ */
+ protected function prependCruds(ContainerBuilder $container)
+ {
+ $container->prependExtensionConfig('chill_main', [
+ 'cruds' => [
+ [
+ 'class' => \Chill\PersonBundle\Entity\AccompanyingPeriod\ClosingMotive::class,
+ 'name' => 'closing_motive',
+ 'base_path' => '/admin/closing-motive',
+ 'form_class' => \Chill\PersonBundle\Form\ClosingMotiveType::class,
+ 'controller' => \Chill\PersonBundle\Controller\AdminClosingMotiveController::class,
+ 'actions' => [
+ 'index' => [
+ 'template' => '@ChillPerson/ClosingMotive/index.html.twig',
+ 'role' => 'ROLE_ADMIN'
+ ],
+ 'new' => [
+ 'role' => 'ROLE_ADMIN',
+ 'template' => '@ChillPerson/ClosingMotive/new.html.twig',
+ ],
+ 'edit' => [
+ 'role' => 'ROLE_ADMIN',
+ 'template' => '@ChillPerson/ClosingMotive/edit.html.twig',
+ ]
+ ]
+ ],
+ [
+ 'class' => \Chill\PersonBundle\Entity\MaritalStatus::class,
+ 'name' => 'marital_status',
+ 'base_path' => '/admin/marital-status',
+ 'form_class' => \Chill\PersonBundle\Form\MaritalStatusType::class,
+ 'controller' => \Chill\PersonBundle\Controller\AdminMaritalStatusController::class,
+ 'actions' => [
+ 'index' => [
+ 'role' => 'ROLE_ADMIN',
+ 'template' => '@ChillPerson/MaritalStatus/index.html.twig',
+ ],
+ 'new' => [
+ 'role' => 'ROLE_ADMIN',
+ 'template' => '@ChillPerson/MaritalStatus/new.html.twig',
+ ],
+ 'edit' => [
+ 'role' => 'ROLE_ADMIN',
+ 'template' => '@ChillPerson/MaritalStatus/edit.html.twig',
+ ]
+ ]
+ ]
+ ]
+ ]);
+ }
+}
diff --git a/src/Bundle/ChillPerson/DependencyInjection/CompilerPass/AccompanyingPeriodTimelineCompilerPass.php b/src/Bundle/ChillPerson/DependencyInjection/CompilerPass/AccompanyingPeriodTimelineCompilerPass.php
new file mode 100644
index 000000000..1ee7782d3
--- /dev/null
+++ b/src/Bundle/ChillPerson/DependencyInjection/CompilerPass/AccompanyingPeriodTimelineCompilerPass.php
@@ -0,0 +1,66 @@
+
+ *
+ * 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\PersonBundle\DependencyInjection\CompilerPass;
+
+use Symfony\Component\DependencyInjection\ContainerBuilder;
+use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
+
+/**
+ * Remove services which add AccompanyingPeriod to timeline if
+ * accompanying_periods are set to `hidden`
+ *
+ */
+class AccompanyingPeriodTimelineCompilerPass implements CompilerPassInterface
+{
+ public function process(ContainerBuilder $container)
+ {
+ // remove services when accompanying period are hidden
+ if ($container->getParameter('chill_person.accompanying_period') !== 'hidden') {
+ return;
+ }
+
+ $definitions = [
+ 'chill.person.timeline.accompanying_period_opening',
+ 'chill.person.timeline.accompanying_period_closing'
+ ];
+
+ foreach($definitions as $definition) {
+ $container
+ ->removeDefinition($definition)
+ ;
+ }
+
+ $definition = $container->getDefinition('chill.main.timeline_builder');
+
+ // we have to remove all methods call, and re-add them if not linked
+ // to this service
+ $calls = $definition->getMethodCalls();
+
+ foreach($calls as list($method, $arguments)) {
+ if ($method !== 'addProvider') {
+ continue;
+ }
+
+ $definition->removeMethodCall('addProvider');
+
+ if (FALSE === \in_array($arguments[1], $definitions)) {
+ $definition->addMethodCall($method, $arguments);
+ }
+ }
+ }
+}
diff --git a/src/Bundle/ChillPerson/DependencyInjection/Configuration.php b/src/Bundle/ChillPerson/DependencyInjection/Configuration.php
new file mode 100644
index 000000000..9784e8523
--- /dev/null
+++ b/src/Bundle/ChillPerson/DependencyInjection/Configuration.php
@@ -0,0 +1,138 @@
+getRootNode('cl_chill_person');
+
+ $rootNode
+ ->canBeDisabled()
+ ->children()
+ ->arrayNode('search')
+ ->canBeDisabled()
+ ->children()
+ ->enumNode('search_by_phone')
+ ->values(['always', 'on-domain', 'never'])
+ ->defaultValue('on-domain')
+ ->info('enable search by phone. \'always\' show the result '
+ . 'on every result. \'on-domain\' will show the result '
+ . 'only if the domain is given in the search box. '
+ . '\'never\' disable this feature')
+ ->end()
+ ->end() //children for 'search', parent = array node 'search'
+ ->end() // array 'search', parent = children of root
+ ->arrayNode('validation')
+ ->canBeDisabled()
+ ->children()
+ ->scalarNode('birthdate_not_after')
+ ->info($this->validationBirthdateNotAfterInfos)
+ ->defaultValue('P1D')
+ ->validate()
+ ->ifTrue(function($period) {
+ try {
+ $interval = new \DateInterval($period);
+ } catch (\Exception $ex) {
+ return true;
+ }
+ return false;
+ })
+ ->thenInvalid('Invalid period for birthdate validation : "%s" '
+ . 'The parameter should match duration as defined by ISO8601 : '
+ . 'https://en.wikipedia.org/wiki/ISO_8601#Durations')
+ ->end() // birthdate_not_after, parent = children of validation
+
+ ->end() // children for 'validation', parent = validation
+ ->end() //validation, parent = children of root
+ ->end() // children of root, parent = root
+ ->arrayNode('person_fields')
+ ->canBeDisabled()
+ ->children()
+ ->append($this->addFieldNode('place_of_birth'))
+ ->append($this->addFieldNode('email'))
+ ->append($this->addFieldNode('phonenumber'))
+ ->append($this->addFieldNode('mobilenumber'))
+ ->append($this->addFieldNode('contact_info'))
+ ->append($this->addFieldNode('nationality'))
+ ->append($this->addFieldNode('country_of_birth'))
+ ->append($this->addFieldNode('marital_status'))
+ ->append($this->addFieldNode('spoken_languages'))
+ ->append($this->addFieldNode('address'))
+ ->append($this->addFieldNode('accompanying_period'))
+ ->append($this->addFieldNode('memo'))
+ ->arrayNode('alt_names')
+ ->defaultValue([])
+ ->arrayPrototype()
+ ->children()
+ ->scalarNode('key')
+ ->isRequired()->cannotBeEmpty()
+ ->end()
+ ->arrayNode('labels')
+ ->children()
+ ->scalarNode('lang')->isRequired()->cannotBeEmpty()
+ ->example('fr')
+ ->end()
+ ->scalarNode('label')->isRequired()->cannotBeEmpty()
+ ->example('Nom de jeune fille')
+ ->end()
+ ->end()
+ ->end()
+ ->end()
+ ->end()
+ ->end()
+ ->end() //children for 'person_fields', parent = array 'person_fields'
+ ->end() // person_fields, parent = children of root
+ ->arrayNode('accompanying_periods_fields')
+ ->canBeDisabled()
+ ->children()
+ ->append($this->addFieldNode('user'))
+ ->end() //children for 'accompanying_person_fields', parent = array 'person_fields'
+ ->end() // paccompanying_person_fields, parent = children of root
+ ->end() // children of 'root', parent = root
+ ;
+
+
+ return $treeBuilder;
+ }
+
+ private function addFieldNode($key)
+ {
+ $tree = new TreeBuilder($key,'enum');
+ $node = $tree->getRootNode($key);
+
+ switch($key) {
+ case 'accompanying_period':
+ $info = "If the accompanying periods are shown";
+ break;
+ default:
+ $info = "If the field $key must be shown";
+ break;
+ }
+
+ $node
+ ->values(array('hidden', 'visible'))
+ ->defaultValue('visible')
+ ->info($info)
+ ->end();
+
+ return $node;
+ }
+}
diff --git a/src/Bundle/ChillPerson/Doctrine/DQL/AddressPart.php b/src/Bundle/ChillPerson/Doctrine/DQL/AddressPart.php
new file mode 100644
index 000000000..98e362dfb
--- /dev/null
+++ b/src/Bundle/ChillPerson/Doctrine/DQL/AddressPart.php
@@ -0,0 +1,103 @@
+
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+namespace Chill\PersonBundle\Doctrine\DQL;
+
+use Doctrine\ORM\Query\AST\Functions\FunctionNode;
+use Doctrine\ORM\Query\Lexer;
+use Doctrine\ORM\Query\Parser;
+use Doctrine\ORM\Query\SqlWalker;
+
+/**
+ *
+ * USAGE GET_ADDRESS_(person.id, :date, 'postcode') where part
+ * should be replace by the part of the address.
+ *
+ * This function return the current address part at the given date, for the
+ * given person (identified by his id)
+ *
+ * The aim of this function is to be used within reports
+ *
+ * @author Julien Fastré
+ */
+abstract class AddressPart extends FunctionNode
+{
+ public $fields = array(
+ 'address_id',
+ 'streetaddress1',
+ 'streetaddress2',
+ 'validfrom',
+ 'postcode_label',
+ 'postcode_code',
+ 'postcode_id',
+ 'country_name',
+ 'country_code',
+ 'country_id'
+ );
+
+ /**
+ *
+ * @var \Doctrine\ORM\Query\AST\Node
+ */
+ private $pid;
+
+ /**
+ *
+ * @var \Doctrine\ORM\Query\AST\Node
+ */
+ private $date;
+
+ /**
+ *
+ * @var \Doctrine\ORM\Query\AST\Node
+ */
+ private $part;
+
+ /**
+ * return the part of the address
+ *
+ * Should be one value of the "public" amongst
+ * 'address_id', 'streetaddress1',
+ * 'streetaddress2', 'validfrom', 'postcode_label', 'postcode_code',
+ * 'postcode_id', 'country_name', 'country_code', 'country_id', 'isnoaddress'
+ *
+ * @return string
+ */
+ abstract public function getPart();
+
+ public function getSql(SqlWalker $sqlWalker)
+ {
+ return sprintf(
+ 'get_last_address_%s(%s, %s)',
+ $this->getPart(),
+ $this->pid->dispatch($sqlWalker),
+ $this->date->dispatch($sqlWalker)
+ );
+ }
+
+ public function parse(Parser $parser)
+ {
+ $a = $parser->match(Lexer::T_IDENTIFIER);
+ $parser->match(Lexer::T_OPEN_PARENTHESIS);
+ // person id
+ $this->pid = $parser->SingleValuedPathExpression();
+ $parser->match(Lexer::T_COMMA);
+ // date
+ $this->date = $parser->ArithmeticPrimary();
+ $parser->match(Lexer::T_CLOSE_PARENTHESIS);
+ }
+}
diff --git a/src/Bundle/ChillPerson/Doctrine/DQL/AddressPart/AddressPartAddressId.php b/src/Bundle/ChillPerson/Doctrine/DQL/AddressPart/AddressPartAddressId.php
new file mode 100644
index 000000000..2f40796cf
--- /dev/null
+++ b/src/Bundle/ChillPerson/Doctrine/DQL/AddressPart/AddressPartAddressId.php
@@ -0,0 +1,33 @@
+
+ *
+ * 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\PersonBundle\Doctrine\DQL\AddressPart;
+
+use Chill\PersonBundle\Doctrine\DQL\AddressPart;
+
+/**
+ *
+ *
+ * @author Julien Fastré
+ */
+class AddressPartAddressId extends AddressPart
+{
+ public function getPart()
+ {
+ return 'address_id';
+ }
+}
diff --git a/src/Bundle/ChillPerson/Doctrine/DQL/AddressPart/AddressPartCountryCode.php b/src/Bundle/ChillPerson/Doctrine/DQL/AddressPart/AddressPartCountryCode.php
new file mode 100644
index 000000000..e997dbb91
--- /dev/null
+++ b/src/Bundle/ChillPerson/Doctrine/DQL/AddressPart/AddressPartCountryCode.php
@@ -0,0 +1,33 @@
+
+ *
+ * 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\PersonBundle\Doctrine\DQL\AddressPart;
+
+use Chill\PersonBundle\Doctrine\DQL\AddressPart;
+
+/**
+ *
+ *
+ * @author Julien Fastré
+ */
+class AddressPartCountryCode extends AddressPart
+{
+ public function getPart()
+ {
+ return 'country_code';
+ }
+}
diff --git a/src/Bundle/ChillPerson/Doctrine/DQL/AddressPart/AddressPartCountryId.php b/src/Bundle/ChillPerson/Doctrine/DQL/AddressPart/AddressPartCountryId.php
new file mode 100644
index 000000000..ab0ab0da2
--- /dev/null
+++ b/src/Bundle/ChillPerson/Doctrine/DQL/AddressPart/AddressPartCountryId.php
@@ -0,0 +1,33 @@
+
+ *
+ * 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\PersonBundle\Doctrine\DQL\AddressPart;
+
+use Chill\PersonBundle\Doctrine\DQL\AddressPart;
+
+/**
+ *
+ *
+ * @author Julien Fastré
+ */
+class AddressPartCountryId extends AddressPart
+{
+ public function getPart()
+ {
+ return 'country_id';
+ }
+}
diff --git a/src/Bundle/ChillPerson/Doctrine/DQL/AddressPart/AddressPartCountryName.php b/src/Bundle/ChillPerson/Doctrine/DQL/AddressPart/AddressPartCountryName.php
new file mode 100644
index 000000000..c37902909
--- /dev/null
+++ b/src/Bundle/ChillPerson/Doctrine/DQL/AddressPart/AddressPartCountryName.php
@@ -0,0 +1,33 @@
+
+ *
+ * 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\PersonBundle\Doctrine\DQL\AddressPart;
+
+use Chill\PersonBundle\Doctrine\DQL\AddressPart;
+
+/**
+ *
+ *
+ * @author Julien Fastré
+ */
+class AddressPartCountryName extends AddressPart
+{
+ public function getPart()
+ {
+ return 'country_name';
+ }
+}
diff --git a/src/Bundle/ChillPerson/Doctrine/DQL/AddressPart/AddressPartIsNoAddress.php b/src/Bundle/ChillPerson/Doctrine/DQL/AddressPart/AddressPartIsNoAddress.php
new file mode 100644
index 000000000..bd08bb4df
--- /dev/null
+++ b/src/Bundle/ChillPerson/Doctrine/DQL/AddressPart/AddressPartIsNoAddress.php
@@ -0,0 +1,33 @@
+
+ *
+ * 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\PersonBundle\Doctrine\DQL\AddressPart;
+
+use Chill\PersonBundle\Doctrine\DQL\AddressPart;
+
+/**
+ *
+ *
+ * @author Julien Fastré
+ */
+class AddressPartIsNoAddress extends AddressPart
+{
+ public function getPart()
+ {
+ return 'isnoaddress';
+ }
+}
diff --git a/src/Bundle/ChillPerson/Doctrine/DQL/AddressPart/AddressPartPostCodeCode.php b/src/Bundle/ChillPerson/Doctrine/DQL/AddressPart/AddressPartPostCodeCode.php
new file mode 100644
index 000000000..ce792cede
--- /dev/null
+++ b/src/Bundle/ChillPerson/Doctrine/DQL/AddressPart/AddressPartPostCodeCode.php
@@ -0,0 +1,33 @@
+
+ *
+ * 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\PersonBundle\Doctrine\DQL\AddressPart;
+
+use Chill\PersonBundle\Doctrine\DQL\AddressPart;
+
+/**
+ *
+ *
+ * @author Julien Fastré
+ */
+class AddressPartPostCodeCode extends AddressPart
+{
+ public function getPart()
+ {
+ return 'postcode_code';
+ }
+}
diff --git a/src/Bundle/ChillPerson/Doctrine/DQL/AddressPart/AddressPartPostCodeId.php b/src/Bundle/ChillPerson/Doctrine/DQL/AddressPart/AddressPartPostCodeId.php
new file mode 100644
index 000000000..5400a04a7
--- /dev/null
+++ b/src/Bundle/ChillPerson/Doctrine/DQL/AddressPart/AddressPartPostCodeId.php
@@ -0,0 +1,33 @@
+
+ *
+ * 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\PersonBundle\Doctrine\DQL\AddressPart;
+
+use Chill\PersonBundle\Doctrine\DQL\AddressPart;
+
+/**
+ *
+ *
+ * @author Julien Fastré
+ */
+class AddressPartPostCodeId extends AddressPart
+{
+ public function getPart()
+ {
+ return 'postcode_id';
+ }
+}
diff --git a/src/Bundle/ChillPerson/Doctrine/DQL/AddressPart/AddressPartPostCodeLabel.php b/src/Bundle/ChillPerson/Doctrine/DQL/AddressPart/AddressPartPostCodeLabel.php
new file mode 100644
index 000000000..ab36a8c46
--- /dev/null
+++ b/src/Bundle/ChillPerson/Doctrine/DQL/AddressPart/AddressPartPostCodeLabel.php
@@ -0,0 +1,33 @@
+
+ *
+ * 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\PersonBundle\Doctrine\DQL\AddressPart;
+
+use Chill\PersonBundle\Doctrine\DQL\AddressPart;
+
+/**
+ *
+ *
+ * @author Julien Fastré
+ */
+class AddressPartPostCodeLabel extends AddressPart
+{
+ public function getPart()
+ {
+ return 'postcode_label';
+ }
+}
diff --git a/src/Bundle/ChillPerson/Doctrine/DQL/AddressPart/AddressPartStreetAddress1.php b/src/Bundle/ChillPerson/Doctrine/DQL/AddressPart/AddressPartStreetAddress1.php
new file mode 100644
index 000000000..c06cf2b07
--- /dev/null
+++ b/src/Bundle/ChillPerson/Doctrine/DQL/AddressPart/AddressPartStreetAddress1.php
@@ -0,0 +1,33 @@
+
+ *
+ * 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\PersonBundle\Doctrine\DQL\AddressPart;
+
+use Chill\PersonBundle\Doctrine\DQL\AddressPart;
+
+/**
+ *
+ *
+ * @author Julien Fastré
+ */
+class AddressPartStreetAddress1 extends AddressPart
+{
+ public function getPart()
+ {
+ return 'streetaddress1';
+ }
+}
diff --git a/src/Bundle/ChillPerson/Doctrine/DQL/AddressPart/AddressPartStreetAddress2.php b/src/Bundle/ChillPerson/Doctrine/DQL/AddressPart/AddressPartStreetAddress2.php
new file mode 100644
index 000000000..739120e5e
--- /dev/null
+++ b/src/Bundle/ChillPerson/Doctrine/DQL/AddressPart/AddressPartStreetAddress2.php
@@ -0,0 +1,33 @@
+
+ *
+ * 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\PersonBundle\Doctrine\DQL\AddressPart;
+
+use Chill\PersonBundle\Doctrine\DQL\AddressPart;
+
+/**
+ *
+ *
+ * @author Julien Fastré
+ */
+class AddressPartStreetAddress2 extends AddressPart
+{
+ public function getPart()
+ {
+ return 'streetaddress2';
+ }
+}
diff --git a/src/Bundle/ChillPerson/Doctrine/DQL/AddressPart/AddressPartValidFrom.php b/src/Bundle/ChillPerson/Doctrine/DQL/AddressPart/AddressPartValidFrom.php
new file mode 100644
index 000000000..4a366bf1a
--- /dev/null
+++ b/src/Bundle/ChillPerson/Doctrine/DQL/AddressPart/AddressPartValidFrom.php
@@ -0,0 +1,33 @@
+
+ *
+ * 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\PersonBundle\Doctrine\DQL\AddressPart;
+
+use Chill\PersonBundle\Doctrine\DQL\AddressPart;
+
+/**
+ *
+ *
+ * @author Julien Fastré
+ */
+class AddressPartValidFrom extends AddressPart
+{
+ public function getPart()
+ {
+ return 'validfrom';
+ }
+}
diff --git a/src/Bundle/ChillPerson/Entity/AccompanyingPeriod.php b/src/Bundle/ChillPerson/Entity/AccompanyingPeriod.php
new file mode 100644
index 000000000..255cf056f
--- /dev/null
+++ b/src/Bundle/ChillPerson/Entity/AccompanyingPeriod.php
@@ -0,0 +1,330 @@
+,
+ *
+ * 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\PersonBundle\Entity;
+
+use Doctrine\ORM\Mapping as ORM;
+use Symfony\Component\Validator\Context\ExecutionContextInterface;
+use Chill\MainBundle\Entity\User;
+
+/**
+ * AccompanyingPeriod
+ *
+ * @ORM\Entity()
+ * @ORM\Table(name="chill_person_accompanying_period")
+ */
+class AccompanyingPeriod
+{
+ /**
+ * @var integer
+ *
+ * @ORM\Id
+ * @ORM\Column(name="id", type="integer")
+ * @ORM\GeneratedValue(strategy="AUTO")
+ */
+ private $id;
+
+ /**
+ * @var \DateTime
+ *
+ * @ORM\Column(type="date")
+ */
+ private $openingDate;
+
+ /**
+ * @var \DateTime
+ *
+ * @ORM\Column(type="date", nullable=true)
+ */
+ private $closingDate = null;
+
+ /**
+ * @var string
+ *
+ * @ORM\Column(type="text")
+ */
+ private $remark = '';
+
+ /**
+ * @var Person
+ *
+ * @ORM\ManyToOne(
+ * targetEntity="Chill\PersonBundle\Entity\Person",
+ * inversedBy="accompanyingPeriods",
+ * cascade={"refresh"})
+ */
+ private $person;
+
+ /**
+ * @var AccompanyingPeriod\ClosingMotive
+ *
+ * @ORM\ManyToOne(
+ * targetEntity="Chill\PersonBundle\Entity\AccompanyingPeriod\ClosingMotive")
+ * @ORM\JoinColumn(nullable=true)
+ */
+ private $closingMotive = null;
+
+ /**
+ * The user making the accompanying
+ * @var User
+ *
+ * @ORM\ManyToOne(targetEntity="Chill\MainBundle\Entity\User")
+ * @ORM\JoinColumn(nullable=true)
+ */
+ private $user;
+
+
+ /**
+ * AccompanyingPeriod constructor.
+ *
+ * @param \DateTime $dateOpening
+ * @uses AccompanyingPeriod::setClosingDate()
+ */
+ public function __construct(\DateTime $dateOpening) {
+ $this->setOpeningDate($dateOpening);
+ }
+
+ /**
+ * Get id
+ *
+ * @return integer
+ */
+ public function getId()
+ {
+ return $this->id;
+ }
+
+ /**
+ * Set openingDate
+ *
+ * @param \DateTime $dateOpening
+ * @return AccompanyingPeriod
+ */
+ public function setOpeningDate($openingDate)
+ {
+ $this->openingDate = $openingDate;
+
+ return $this;
+ }
+
+ /**
+ * Get openingDate
+ *
+ * @return \DateTime
+ */
+ public function getOpeningDate()
+ {
+ return $this->openingDate;
+ }
+
+ /**
+ * Set closingDate
+ *
+ * For closing a Person file, you should use Person::setClosed instead.
+ *
+ * @param \DateTime $dateClosing
+ * @return AccompanyingPeriod
+ *
+ */
+ public function setClosingDate($closingDate)
+ {
+ $this->closingDate = $closingDate;
+
+ return $this;
+ }
+
+ /**
+ * Get closingDate
+ *
+ * @return \DateTime
+ */
+ public function getClosingDate()
+ {
+ return $this->closingDate;
+ }
+
+ /**
+ * @return boolean
+ */
+ public function isOpen(): bool
+ {
+ if ($this->getOpeningDate() > new \DateTime('now')) {
+ return false;
+ }
+
+ if ($this->getClosingDate() === null) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Set remark
+ *
+ * @param string $remark
+ * @return AccompanyingPeriod
+ */
+ public function setRemark($remark)
+ {
+ if ($remark === null) {
+ $remark = '';
+ }
+
+ $this->remark = $remark;
+
+ return $this;
+ }
+
+ /**
+ * Get remark
+ *
+ * @return string
+ */
+ public function getRemark()
+ {
+ return $this->remark;
+ }
+
+ /**
+ * Set person.
+ *
+ * For consistency, you should use Person::addAccompanyingPeriod instead.
+ *
+ * @param Person $person
+ * @return AccompanyingPeriod
+ * @see Person::addAccompanyingPeriod
+ */
+ public function setPerson(Person $person = null)
+ {
+ $this->person = $person;
+
+ return $this;
+ }
+
+ /**
+ * Get person
+ *
+ * @return Person
+ */
+ public function getPerson()
+ {
+ return $this->person;
+ }
+
+ /**
+ * @return AccompanyingPeriod\ClosingMotive
+ */
+ public function getClosingMotive()
+ {
+ return $this->closingMotive;
+ }
+
+ /**
+ * @param AccompanyingPeriod\ClosingMotive|null $closingMotive
+ * @return $this
+ */
+ public function setClosingMotive(AccompanyingPeriod\ClosingMotive $closingMotive = null)
+ {
+ $this->closingMotive = $closingMotive;
+ return $this;
+ }
+
+ /**
+ * If the period can be reopened.
+ *
+ * This function test if the period is closed and if the period is the last
+ * for the associated person
+ *
+ * @return boolean
+ */
+ public function canBeReOpened()
+ {
+ if ($this->isOpen() === true) {
+ return false;
+ }
+
+ $periods = $this->getPerson()->getAccompanyingPeriodsOrdered();
+
+ return end($periods) === $this;
+ }
+
+ /**
+ */
+ public function reOpen()
+ {
+ $this->setClosingDate(null);
+ $this->setClosingMotive(null);
+ }
+
+ /**
+ * Validation function
+ */
+ public function isDateConsistent(ExecutionContextInterface $context)
+ {
+ if ($this->isOpen()) {
+ return;
+ }
+
+ if (! $this->isClosingAfterOpening()) {
+ $context->buildViolation('The date of closing is before the date of opening')
+ ->atPath('dateClosing')
+ ->addViolation();
+ }
+ }
+
+ /**
+ * Returns true if the closing date is after the opening date.
+ *
+ * @return boolean
+ */
+ public function isClosingAfterOpening()
+ {
+ $diff = $this->getOpeningDate()->diff($this->getClosingDate());
+
+ if ($diff->invert === 0) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * @return User|null
+ */
+ function getUser(): ?User
+ {
+ return $this->user;
+ }
+
+ /**
+ * @param User $user
+ * @return AccompanyingPeriod
+ */
+ function setUser(User $user): self
+ {
+ $this->user = $user;
+
+ return $this;
+ }
+
+}
diff --git a/src/Bundle/ChillPerson/Entity/AccompanyingPeriod/ClosingMotive.php b/src/Bundle/ChillPerson/Entity/AccompanyingPeriod/ClosingMotive.php
new file mode 100644
index 000000000..05149e23f
--- /dev/null
+++ b/src/Bundle/ChillPerson/Entity/AccompanyingPeriod/ClosingMotive.php
@@ -0,0 +1,276 @@
+
+ *
+ * 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\PersonBundle\Entity\AccompanyingPeriod;
+
+use Doctrine\ORM\Mapping as ORM;
+use Doctrine\Common\Collections\Collection;
+use Doctrine\Common\Collections\ArrayCollection;
+
+/**
+ * ClosingMotive give an explanation why we closed the Accompanying period
+ *
+ * @ORM\Entity(
+ * repositoryClass="Chill\PersonBundle\Repository\ClosingMotiveRepository")
+ * @ORM\Table(name="chill_person_closingmotive")
+ */
+class ClosingMotive
+{
+ /**
+ * @var integer
+ *
+ * @ORM\Id
+ * @ORM\Column(name="id", type="integer")
+ * @ORM\GeneratedValue(strategy="AUTO")
+ */
+ private $id;
+
+ /**
+ * @var array
+ *
+ * @ORM\Column(type="json_array")
+ */
+ private $name;
+
+ /**
+ * @var boolean
+ *
+ * @ORM\Column(type="boolean")
+ */
+ private $active = true;
+
+ /**
+ * @var self
+ *
+ * @ORM\ManyToOne(
+ * targetEntity="Chill\PersonBundle\Entity\AccompanyingPeriod\ClosingMotive",
+ * inversedBy="children")
+ */
+ private $parent = null;
+
+ /**
+ * Child Accompanying periods
+ * @var Collection
+ *
+ * @ORM\OneToMany(
+ * targetEntity="Chill\PersonBundle\Entity\AccompanyingPeriod\ClosingMotive",
+ * mappedBy="parent")
+ */
+ private $children;
+
+ /**
+ * @var float
+ *
+ * @ORM\Column(type="float")
+ */
+ private $ordering = 0.0;
+
+
+ /**
+ * ClosingMotive constructor.
+ */
+ public function __construct()
+ {
+ $this->children = new ArrayCollection();
+ }
+
+ /**
+ * Get id
+ *
+ * @return integer
+ */
+ public function getId()
+ {
+ return $this->id;
+ }
+
+ /**
+ * Set name
+ *
+ * @param array $name
+ *
+ * @return ClosingMotive
+ */
+ public function setName($name)
+ {
+ $this->name = $name;
+
+ return $this;
+ }
+
+ /**
+ * Get name
+ *
+ * @return array
+ */
+ public function getName()
+ {
+ return $this->name;
+ }
+
+ /**
+ * @return bool
+ */
+ public function isActive(): bool
+ {
+ return $this->active;
+ }
+
+ /**
+ * @param bool $active
+ * @return $this
+ */
+ public function setActive(bool $active)
+ {
+ $this->active = $active;
+
+ if ($this->active === FALSE) {
+ foreach ($this->getChildren() as $child) {
+ $child->setActive(FALSE);
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * @return ClosingMotive
+ */
+ public function getParent()
+ {
+ return $this->parent;
+ }
+
+ /**
+ * @return Collection
+ */
+ public function getChildren(): Collection
+ {
+ return $this->children;
+ }
+
+ /**
+ * @param ClosingMotive|null $parent
+ * @return ClosingMotive
+ */
+ public function setParent(?ClosingMotive $parent): ClosingMotive
+ {
+ $this->parent = $parent;
+
+ if (NULL !== $parent) {
+ //$parent->addChildren($this);
+ }
+
+ return $this;
+ }
+
+ /**
+ * @param Collection $children
+ * @return ClosingMotive
+ */
+ public function setChildren(Collection $children): ClosingMotive
+ {
+ $this->children = $children;
+
+ return $this;
+ }
+
+ /**
+ * @param ClosingMotive $child
+ * @return ClosingMotive
+ */
+ public function addChildren(ClosingMotive $child): ClosingMotive
+ {
+ if ($this->children->contains($child)) {
+ return $this;
+ }
+
+ $this->children->add($child);
+ $child->setParent($this);
+
+ return $this;
+ }
+
+ /**
+ * @param ClosingMotive $child
+ * @return ClosingMotive
+ */
+ public function removeChildren(ClosingMotive $child): ClosingMotive
+ {
+ if ($this->children->removeElement($child)) {
+ $child->setParent(null);
+ }
+
+ return $this;
+ }
+
+ /**
+ * @return float
+ */
+ public function getOrdering(): float
+ {
+ return $this->ordering;
+ }
+
+ /**
+ * @param float $ordering
+ * @return $this
+ */
+ public function setOrdering(float $ordering)
+ {
+ $this->ordering = $ordering;
+
+ return $this;
+ }
+
+ /**
+ * @return bool
+ */
+ public function isChild(): bool
+ {
+ return $this->parent !== null;
+ }
+
+ /**
+ * @return bool
+ */
+ public function isParent(): bool
+ {
+ return $this->children->count() > 0;
+ }
+
+ /**
+ * @return bool
+ */
+ public function isLeaf(): bool
+ {
+ return $this->children->count() === 0;
+ }
+
+ /**
+ * @return bool
+ */
+ public function hasParent(): bool
+ {
+ return $this->parent !== null;
+ }
+
+}
+
diff --git a/src/Bundle/ChillPerson/Entity/HasPerson.php b/src/Bundle/ChillPerson/Entity/HasPerson.php
new file mode 100644
index 000000000..e200182bc
--- /dev/null
+++ b/src/Bundle/ChillPerson/Entity/HasPerson.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\PersonBundle\Entity;
+
+use Chill\PersonBundle\Entity\Person;
+
+/**
+ * Interface which applies to entities which are associated to a single person
+ *
+ */
+interface HasPerson
+{
+ public function setPerson(Person $person = null): HasPerson;
+
+ public function getPerson(): ?Person;
+}
diff --git a/src/Bundle/ChillPerson/Entity/MaritalStatus.php b/src/Bundle/ChillPerson/Entity/MaritalStatus.php
new file mode 100644
index 000000000..f1addbb2c
--- /dev/null
+++ b/src/Bundle/ChillPerson/Entity/MaritalStatus.php
@@ -0,0 +1,92 @@
+
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+namespace Chill\PersonBundle\Entity;
+
+use Doctrine\ORM\Mapping as ORM;
+
+/**
+ * MaritalStatus
+ *
+ * @ORM\Entity()
+ * @ORM\Table(name="chill_person_marital_status")
+ * @ORM\HasLifecycleCallbacks()
+ */
+class MaritalStatus
+{
+ /**
+ * @var string
+ *
+ * @ORM\Id()
+ * @ORM\Column(type="string", length=7)
+ */
+ private $id;
+
+ /**
+ * @var string array
+ * @ORM\Column(type="json_array")
+ */
+ private $name;
+
+ /**
+ * Get id
+ *
+ * @return string
+ */
+ public function getId()
+ {
+ return $this->id;
+ }
+
+ /**
+ * Set id
+ *
+ * @param string $id
+ * @return MaritalStatus
+ */
+ public function setId($id)
+ {
+ $this->id = $id;
+ return $this;
+ }
+
+ /**
+ * Set name
+ *
+ * @param string array $name
+ * @return MaritalStatus
+ */
+ public function setName($name)
+ {
+ $this->name = $name;
+
+ return $this;
+ }
+
+ /**
+ * Get name
+ *
+ * @return string array
+ */
+ public function getName()
+ {
+ return $this->name;
+ }
+}
diff --git a/src/Bundle/ChillPerson/Entity/Person.php b/src/Bundle/ChillPerson/Entity/Person.php
new file mode 100644
index 000000000..36993ce3b
--- /dev/null
+++ b/src/Bundle/ChillPerson/Entity/Person.php
@@ -0,0 +1,1054 @@
+,
+ *
+ * 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 .
+ */
+
+use Doctrine\ORM\Mapping as ORM;
+use Doctrine\Common\Collections\Collection;
+use Doctrine\Common\Collections\ArrayCollection;
+use Doctrine\Common\Collections\Criteria;
+use Chill\MainBundle\Entity\Center;
+use Chill\MainBundle\Entity\Country;
+use Chill\PersonBundle\Entity\MaritalStatus;
+use Chill\MainBundle\Entity\HasCenterInterface;
+use Chill\MainBundle\Entity\Address;
+use Symfony\Component\Validator\Context\ExecutionContextInterface;
+
+/**
+ * Person Class
+ *
+ * @ORM\Entity(repositoryClass="Chill\PersonBundle\Repository\PersonRepository")
+ * @ORM\Table(name="chill_person_person",
+ * indexes={@ORM\Index(
+ * name="person_names",
+ * columns={"firstName", "lastName"}
+ * )})
+ * sf4 check index name
+ * @ORM\HasLifecycleCallbacks()
+ */
+class Person implements HasCenterInterface
+{
+ /**
+ * The person's id
+ * @var integer
+ *
+ * @ORM\Id
+ * @ORM\Column(name="id", type="integer")
+ * @ORM\GeneratedValue(strategy="AUTO")
+ */
+ private $id;
+
+ /**
+ * The person's first name
+ * @var string
+ *
+ * @ORM\Column(type="string", length=255)
+ */
+ private $firstName;
+
+ /**
+ * The person's last name
+ * @var string
+ *
+ * @ORM\Column(type="string", length=255)
+ */
+ private $lastName;
+
+ /**
+ * @var Collection
+ *
+ * @ORM\OneToMany(
+ * targetEntity="Chill\PersonBundle\Entity\PersonAltName",
+ * mappedBy="person",
+ * cascade={"persist", "remove", "merge", "detach"},
+ * orphanRemoval=true)
+ */
+ private $altNames;
+
+ /**
+ * The person's birthdate
+ * @var \DateTime
+ *
+ * @ORM\Column(type="date", nullable=true)
+ */
+ private $birthdate; //to change in birthdate
+
+ /**
+ * The person's place of birth
+ * @var string
+ *
+ * @ORM\Column(type="string", length=255, name="place_of_birth")
+ */
+ private $placeOfBirth = '';
+
+ /**
+ * The person's country of birth
+ * @var Country
+ *
+ * @ORM\ManyToOne(targetEntity="Chill\MainBundle\Entity\Country")
+ *
+ * sf4 check: option inversedBy="birthsIn" return error mapping !!
+ *
+ * @ORM\JoinColumn(nullable=true)
+ */
+ private $countryOfBirth;
+
+ /**
+ * The person's nationality
+ * @var Country
+ *
+ * @ORM\ManyToOne(targetEntity="Chill\MainBundle\Entity\Country")
+ *
+ * sf4 check: option inversedBy="nationals" return error mapping !!
+ *
+ * @ORM\JoinColumn(nullable=true)
+ */
+ private $nationality;
+
+ /**
+ * The person's gender
+ * @var string
+ *
+ * @ORM\Column(type="string", length=9, nullable=true)
+ */
+ private $gender;
+
+ const MALE_GENDER = 'man';
+ const FEMALE_GENDER = 'woman';
+ const BOTH_GENDER = 'both';
+
+ /**
+ * The marital status of the person
+ * @var MaritalStatus
+ *
+ * @ORM\ManyToOne(targetEntity="Chill\PersonBundle\Entity\MaritalStatus")
+ * @ORM\JoinColumn(nullable=true)
+ */
+ private $maritalStatus;
+
+ /**
+ * Contact information for contacting the person
+ * @var string
+ *
+ * @ORM\Column(type="text", nullable=true)
+ */
+ private $contactInfo = '';
+
+ /**
+ * The person's email
+ * @var string
+ *
+ * @ORM\Column(type="text", nullable=true)
+ */
+ private $email = '';
+
+ /**
+ * The person's phonenumber
+ * @var string
+ *
+ * @ORM\Column(type="text", length=40, nullable=true)
+ */
+ private $phonenumber = '';
+
+ /**
+ * The person's mobile phone number
+ * @var string
+ *
+ * @ORM\Column(type="text", length=40, nullable=true)
+ */
+ private $mobilenumber = '';
+
+ //TO-ADD caseOpeningDate
+ //TO-ADD nativeLanguag
+
+ /**
+ * The person's spoken languages
+ * @var ArrayCollection
+ *
+ * @ORM\ManyToMany(targetEntity="Chill\MainBundle\Entity\Language")
+ * @ORM\JoinTable(
+ * name="persons_spoken_languages",
+ * joinColumns={@ORM\JoinColumn(name="person_id", referencedColumnName="id")},
+ * inverseJoinColumns={@ORM\JoinColumn(name="language_id", referencedColumnName="id")}
+ * )
+ */
+ private $spokenLanguages;
+
+ /**
+ * The person's center
+ * @var Center
+ *
+ * @ORM\ManyToOne(targetEntity="Chill\MainBundle\Entity\Center")
+ * @ORM\JoinColumn(nullable=false)
+ */
+ private $center;
+
+ /**
+ * The person's accompanying periods (when the person was accompanied by the center)
+ * @var ArrayCollection
+ *
+ * @ORM\OneToMany(
+ * targetEntity="Chill\PersonBundle\Entity\AccompanyingPeriod",
+ * mappedBy="person",
+ * cascade={"persist", "remove", "merge", "detach"})
+ */
+ private $accompanyingPeriods; //TO-CHANGE in accompanyingHistory
+
+ /**
+ * A remark over the person
+ * @var string
+ *
+ * @ORM\Column(type="text")
+ */
+ private $memo = ''; // TO-CHANGE in remark
+
+ /**
+ * @var boolean
+ * @deprecated
+ *
+ * @ORM\Column(type="boolean")
+ */
+ private $proxyAccompanyingPeriodOpenState = false; //TO-DELETE ?
+
+ /**
+ * Array where customfield's data are stored
+ * @var array
+ *
+ * @ORM\Column(type="json_array")
+ */
+ private $cFData;
+
+ /**
+ * Addresses
+ * @var Collection
+ *
+ * @ORM\ManyToMany(
+ * targetEntity="Chill\MainBundle\Entity\Address",
+ * cascade={"persist", "remove", "merge", "detach"})
+ * @ORM\JoinTable(name="chill_person_persons_to_addresses")
+ * @ORM\OrderBy({"validFrom" = "DESC"})
+ */
+ private $addresses;
+
+ /**
+ * @var string
+ *
+ * @ORM\Column(type="text", nullable=true)
+ */
+ private $fullnameCanonical;
+
+
+ /**
+ * Person constructor.
+ *
+ * @param \DateTime|null $opening
+ */
+ public function __construct(\DateTime $opening = null)
+ {
+ $this->accompanyingPeriods = new ArrayCollection();
+ $this->spokenLanguages = new ArrayCollection();
+ $this->addresses = new ArrayCollection();
+ $this->altNames = new ArrayCollection();
+
+ if ($opening === null) {
+ $opening = new \DateTime();
+ }
+
+ $this->open(new AccompanyingPeriod($opening));
+ }
+
+ /**
+ * @param AccompanyingPeriod $accompanyingPeriod
+ * @uses AccompanyingPeriod::setPerson
+ */
+ public function addAccompanyingPeriod(AccompanyingPeriod $accompanyingPeriod)
+ {
+ $accompanyingPeriod->setPerson($this);
+ $this->accompanyingPeriods->add($accompanyingPeriod);
+ }
+
+ /**
+ * @param AccompanyingPeriod $accompanyingPeriod
+ */
+ public function removeAccompanyingPeriod(AccompanyingPeriod $accompanyingPeriod)
+ {
+ $this->accompanyingPeriods->remove($accompanyingPeriod);
+ }
+
+ /**
+ * set the Person file as open at the given date.
+ *
+ * For updating a opening's date, you should update AccompanyingPeriod instance
+ * directly.
+ *
+ * For closing a file, @see this::close
+ *
+ * To check if the Person and its accompanying period is consistent, use validation.
+ *
+ * @param AccompanyingPeriod $accompanyingPeriod
+ */
+ public function open(AccompanyingPeriod $accompanyingPeriod)
+ {
+ $this->proxyAccompanyingPeriodOpenState = true;
+ $this->addAccompanyingPeriod($accompanyingPeriod);
+ }
+
+ /**
+ * Set the Person file as closed at the given date.
+ *
+ * For update a closing date, you should update AccompanyingPeriod instance
+ * directly.
+ *
+ * To check if the Person and its accompanying period are consistent, use validation.
+ *
+ * @param accompanyingPeriod
+ * @throws \Exception if two lines of the accompanying period are open.
+ */
+ public function close(AccompanyingPeriod $accompanyingPeriod = null)
+ {
+ $this->proxyAccompanyingPeriodOpenState = false;
+ }
+
+ /**
+ * Return the opened accompanying period.
+ *
+ * @return AccompanyingPeriod
+ */
+ public function getOpenedAccompanyingPeriod()
+ {
+ if ($this->isOpen() === false) {
+ return null;
+ }
+
+ foreach ($this->accompanyingPeriods as $period) {
+ if ($period->isOpen()) {
+ return $period;
+ }
+ }
+ }
+
+ /**
+ * Returns the opened accompanying period.
+ *
+ * @return AccompanyingPeriod
+ * @deprecated since 1.1 use `getOpenedAccompanyingPeriod instead
+ */
+ public function getCurrentAccompanyingPeriod()
+ {
+ return $this->getOpenedAccompanyingPeriod();
+ }
+
+ /**
+ * @return ArrayCollection
+ */
+ public function getAccompanyingPeriods()
+ {
+ return $this->accompanyingPeriods;
+ }
+
+ /**
+ * Get the accompanying periods of a give person with the
+ * chronological order.
+ *
+ * @return AccompanyingPeriod[]
+ */
+ public function getAccompanyingPeriodsOrdered()
+ {
+ $periods = $this->getAccompanyingPeriods()->toArray();
+
+ //order by date :
+ usort($periods, function($a, $b) {
+ $dateA = $a->getOpeningDate();
+ $dateB = $b->getOpeningDate();
+
+ if ($dateA == $dateB) {
+ $dateEA = $a->getClosingDate();
+ $dateEB = $b->getClosingDate();
+
+ if ($dateEA == $dateEB) {
+ return 0;
+ }
+
+ if ($dateEA < $dateEB) {
+ return -1;
+ } else {
+ return +1;
+ }
+ }
+
+ if ($dateA < $dateB) {
+ return -1 ;
+ } else {
+ return 1;
+ }
+ });
+
+ return $periods;
+ }
+
+ /**
+ * check if the person is opened
+ *
+ * @return boolean
+ */
+ public function isOpen()
+ {
+ foreach ($this->getAccompanyingPeriods() as $period) {
+ if ($period->isOpen()) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Get id
+ *
+ * @return integer
+ */
+ public function getId()
+ {
+ return $this->id;
+ }
+
+ /**
+ * Set firstName
+ *
+ * @param string $firstName
+ * @return Person
+ */
+ public function setFirstName($firstName)
+ {
+ $this->firstName = $firstName;
+
+ return $this;
+ }
+
+ /**
+ * Get firstName
+ *
+ * @return string
+ */
+ public function getFirstName()
+ {
+ return $this->firstName;
+ }
+
+ /**
+ * Set lastName
+ *
+ * @param string $lastName
+ * @return Person
+ */
+ public function setLastName($lastName)
+ {
+ $this->lastName = $lastName;
+
+ return $this;
+ }
+
+ /**
+ * Get lastName
+ *
+ * @return string
+ */
+ public function getLastName()
+ {
+ return $this->lastName;
+ }
+
+ /**
+ * @return Collection
+ */
+ public function getAltNames(): Collection
+ {
+ return $this->altNames;
+ }
+
+ /**
+ * @param Collection $altNames
+ * @return $this
+ */
+ public function setAltNames(Collection $altNames)
+ {
+ $this->altNames = $altNames;
+
+ return $this;
+ }
+
+ /**
+ * @param PersonAltName $altName
+ * @return $this
+ */
+ public function addAltName(PersonAltName $altName)
+ {
+ if (FALSE === $this->altNames->contains($altName)) {
+ $this->altNames->add($altName);
+ $altName->setPerson($this);
+ }
+
+ return $this;
+ }
+
+ /**
+ * @param PersonAltName $altName
+ * @return $this
+ */
+ public function removeAltName(PersonAltName $altName)
+ {
+ if ($this->altNames->contains($altName)) {
+ $altName->setPerson(null);
+ $this->altNames->removeElement($altName);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Set birthdate
+ *
+ * @param \DateTime $birthdate
+ * @return Person
+ */
+ public function setBirthdate($birthdate)
+ {
+ $this->birthdate = $birthdate;
+
+ return $this;
+ }
+
+ /**
+ * Get birthdate
+ *
+ * @return \DateTime
+ */
+ public function getBirthdate()
+ {
+ return $this->birthdate;
+ }
+
+ /**
+ * Set placeOfBirth
+ *
+ * @param string $placeOfBirth
+ * @return Person
+ */
+ public function setPlaceOfBirth($placeOfBirth)
+ {
+ if ($placeOfBirth === null) {
+ $placeOfBirth = '';
+ }
+
+ $this->placeOfBirth = $placeOfBirth;
+
+ return $this;
+ }
+
+ /**
+ * Get placeOfBirth
+ *
+ * @return string
+ */
+ public function getPlaceOfBirth()
+ {
+ return $this->placeOfBirth;
+ }
+
+ /**
+ * Set gender
+ *
+ * @param string $gender
+ * @return Person
+ */
+ public function setGender($gender)
+ {
+ $this->gender = $gender;
+
+ return $this;
+ }
+
+ /**
+ * Get gender
+ *
+ * @return string
+ */
+ public function getGender()
+ {
+ return $this->gender;
+ }
+
+ /**
+ * return gender as a Numeric form.
+ * This is used for translations
+ * @return int
+ */
+ public function getGenderNumeric()
+ {
+ if ($this->getGender() == self::FEMALE_GENDER) {
+ return 1;
+ } else {
+ return 0;
+ }
+ }
+
+ /**
+ * Set memo
+ *
+ * @param string $memo
+ * @return Person
+ */
+ public function setMemo($memo)
+ {
+ if ($memo === null) {
+ $memo = '';
+ }
+
+ if ($this->memo !== $memo) {
+ $this->memo = $memo;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get memo
+ *
+ * @return string
+ */
+ public function getMemo()
+ {
+ return $this->memo;
+ }
+
+ /**
+ * Set maritalStatus
+ *
+ * @param MaritalStatus $maritalStatus
+ * @return Person
+ */
+ public function setMaritalStatus(MaritalStatus $maritalStatus = null)
+ {
+ $this->maritalStatus = $maritalStatus;
+ return $this;
+ }
+
+ /**
+ * Get maritalStatus
+ *
+ * @return MaritalStatus
+ */
+ public function getMaritalStatus()
+ {
+ return $this->maritalStatus;
+ }
+
+ /**
+ * Set contactInfo
+ *
+ * @param string $contactInfo
+ * @return Person
+ */
+ public function setcontactInfo($contactInfo)
+ {
+ if ($contactInfo === null) {
+ $contactInfo = '';
+ }
+
+ $this->contactInfo = $contactInfo;
+
+ return $this;
+ }
+
+ /**
+ * Get contactInfo
+ *
+ * @return string
+ */
+ public function getcontactInfo()
+ {
+ return $this->contactInfo;
+ }
+
+ /**
+ * Set email
+ *
+ * @param string $email
+ * @return Person
+ */
+ public function setEmail($email)
+ {
+ if ($email === null) {
+ $email = '';
+ }
+
+ $this->email = $email;
+
+ return $this;
+ }
+
+ /**
+ * Get email
+ *
+ * @return string
+ */
+ public function getEmail()
+ {
+ return $this->email;
+ }
+
+ /**
+ * Set countryOfBirth
+ *
+ * @param Chill\MainBundle\Entity\Country $countryOfBirth
+ * @return Person
+ */
+ public function setCountryOfBirth(Country $countryOfBirth = null)
+ {
+ $this->countryOfBirth = $countryOfBirth;
+ return $this;
+ }
+
+ /**
+ * Get countryOfBirth
+ *
+ * @return Chill\MainBundle\Entity\Country
+ */
+ public function getCountryOfBirth()
+ {
+ return $this->countryOfBirth;
+ }
+
+ /**
+ * Set nationality
+ *
+ * @param Chill\MainBundle\Entity\Country $nationality
+ * @return Person
+ */
+ public function setNationality(Country $nationality = null)
+ {
+ $this->nationality = $nationality;
+
+ return $this;
+ }
+
+ /**
+ * Get nationality
+ *
+ * @return Chill\MainBundle\Entity\Country
+ */
+ public function getNationality()
+ {
+ return $this->nationality;
+ }
+
+ /**
+ * @return string
+ */
+ public function getLabel()
+ {
+ return $this->getFirstName()." ".$this->getLastName();
+ }
+
+ /**
+ * Get center
+ *
+ * @return Center
+ */
+ public function getCenter()
+ {
+ return $this->center;
+ }
+
+ /**
+ * Set the center
+ *
+ * @param Center $center
+ * @return \Chill\PersonBundle\Entity\Person
+ */
+ public function setCenter(Center $center)
+ {
+ $this->center = $center;
+ return $this;
+ }
+
+ /**
+ * Set cFData
+ *
+ * @param array $cFData
+ *
+ * @return Report
+ */
+ public function setCFData($cFData)
+ {
+ $this->cFData = $cFData;
+
+ return $this;
+ }
+
+ /**
+ * Get cFData
+ *
+ * @return array
+ */
+ public function getCFData()
+ {
+ if ($this->cFData === null) {
+ $this->cFData = [];
+ }
+ return $this->cFData;
+ }
+
+ /**
+ * Set phonenumber
+ *
+ * @param string $phonenumber
+ * @return Person
+ */
+ public function setPhonenumber($phonenumber = '')
+ {
+ $this->phonenumber = $phonenumber;
+
+ return $this;
+ }
+
+ /**
+ * Get phonenumber
+ *
+ * @return string
+ */
+ public function getPhonenumber()
+ {
+ return $this->phonenumber;
+ }
+
+ /**
+ * Set mobilenumber
+ *
+ * @param string $mobilenumber
+ * @return Person
+ */
+ public function setMobilenumber($mobilenumber = '')
+ {
+ $this->mobilenumber = $mobilenumber;
+
+ return $this;
+ }
+
+ /**
+ * Get mobilenumber
+ *
+ * @return string
+ */
+ public function getMobilenumber()
+ {
+ return $this->mobilenumber;
+ }
+
+ /**
+ * @return string
+ */
+ public function __toString()
+ {
+ return $this->getLabel();
+ }
+
+ /**
+ * Set spokenLanguages
+ *
+ * @param type $spokenLanguages
+ * @return Person
+ */
+ public function setSpokenLanguages($spokenLanguages)
+ {
+ $this->spokenLanguages = $spokenLanguages;
+
+ return $this;
+ }
+
+ /**
+ * Get spokenLanguages
+ *
+ * @return ArrayCollection
+ */
+ public function getSpokenLanguages()
+ {
+ return $this->spokenLanguages;
+ }
+
+ /**
+ * @param Address $address
+ * @return $this
+ */
+ public function addAddress(Address $address)
+ {
+ $this->addresses[] = $address;
+
+ return $this;
+ }
+
+ /**
+ * @param Address $address
+ */
+ public function removeAddress(Address $address)
+ {
+ $this->addresses->removeElement($address);
+ }
+
+ /**
+ * By default, the addresses are ordered by date, descending (the most
+ * recent first)
+ *
+ * @return \Chill\MainBundle\Entity\Address[]
+ */
+ public function getAddresses()
+ {
+ return $this->addresses;
+ }
+
+ /**
+ * @param \DateTime|null $date
+ * @return null
+ */
+ public function getLastAddress(\DateTime $date = null)
+ {
+ if ($date === null) {
+ $date = new \DateTime('now');
+ }
+
+ $addresses = $this->getAddresses();
+
+ if ($addresses == null) {
+
+ return null;
+ }
+
+ return $addresses->first();
+ }
+
+ /**
+ * Validation callback that checks if the accompanying periods are valid
+ *
+ * This method add violation errors.
+ */
+ public function isAccompanyingPeriodValid(ExecutionContextInterface $context)
+ {
+ $r = $this->checkAccompanyingPeriodsAreNotCollapsing();
+
+ if ($r !== true) {
+ if ($r['result'] === self::ERROR_PERIODS_ARE_COLLAPSING) {
+ $context->buildViolation('Two accompanying periods have days in commun')
+ ->atPath('accompanyingPeriods')
+ ->addViolation();
+ }
+
+ if ($r['result'] === self::ERROR_ADDIND_PERIOD_AFTER_AN_OPEN_PERIOD) {
+ $context->buildViolation('A period is opened and a period is added after it')
+ ->atPath('accompanyingPeriods')
+ ->addViolation();
+ }
+ }
+ }
+
+ /**
+ * Return true if the person has two addresses with the
+ * same validFrom date (in format 'Y-m-d')
+ */
+ public function hasTwoAdressWithSameValidFromDate()
+ {
+ $validYMDDates = array();
+
+ foreach ($this->addresses as $ad) {
+ $validDate = $ad->getValidFrom()->format('Y-m-d');
+
+ if (in_array($validDate, $validYMDDates)) {
+ return true;
+ }
+ $validYMDDates[] = $validDate;
+ }
+
+ return false;
+ }
+
+ /**
+ * Validation callback that checks if the addresses are valid (do not have
+ * two addresses with the same validFrom date)
+ *
+ * This method add violation errors.
+ */
+ public function isAddressesValid(ExecutionContextInterface $context)
+ {
+ if ($this->hasTwoAdressWithSameValidFromDate()) {
+ $context
+ ->buildViolation('Two addresses has the same validFrom date')
+ ->atPath('addresses')
+ ->addViolation()
+ ;
+ }
+ }
+
+
+ const ERROR_PERIODS_ARE_COLLAPSING = 1; // when two different periods
+ // have days in commun
+ const ERROR_ADDIND_PERIOD_AFTER_AN_OPEN_PERIOD = 2; // where there exist
+ // a period opened and another one after it
+
+ /**
+ * Function used for validation that check if the accompanying periods of
+ * the person are not collapsing (i.e. have not shared days) or having
+ * a period after an open period.
+ *
+ * @return true | array True if the accompanying periods are not collapsing,
+ * an array with data for displaying the error
+ */
+ public function checkAccompanyingPeriodsAreNotCollapsing()
+ {
+ $periods = $this->getAccompanyingPeriodsOrdered();
+ $periodsNbr = sizeof($periods);
+ $i = 0;
+
+ while($i < $periodsNbr - 1) {
+ $periodI = $periods[$i];
+ $periodAfterI = $periods[$i + 1];
+
+ if($periodI->isOpen()) {
+ return array(
+ 'result' => self::ERROR_ADDIND_PERIOD_AFTER_AN_OPEN_PERIOD,
+ 'dateOpening' => $periodAfterI->getOpeningDate(),
+ 'dateClosing' => $periodAfterI->getClosingDate(),
+ 'date' => $periodI->getOpeningDate()
+ );
+ } elseif ($periodI->getClosingDate() >= $periodAfterI->getOpeningDate()) {
+ return array(
+ 'result' => self::ERROR_PERIODS_ARE_COLLAPSING,
+ 'dateOpening' => $periodI->getOpeningDate(),
+
+ 'dateClosing' => $periodI->getClosingDate(),
+ 'date' => $periodAfterI->getOpeningDate()
+ );
+ }
+ $i++;
+ }
+
+ return true;
+ }
+}
diff --git a/src/Bundle/ChillPerson/Entity/PersonAltName.php b/src/Bundle/ChillPerson/Entity/PersonAltName.php
new file mode 100644
index 000000000..c5295487e
--- /dev/null
+++ b/src/Bundle/ChillPerson/Entity/PersonAltName.php
@@ -0,0 +1,125 @@
+id;
+ }
+
+ /**
+ * Set key.
+ *
+ * @param string $key
+ *
+ * @return PersonAltName
+ */
+ public function setKey($key)
+ {
+ $this->key = $key;
+
+ return $this;
+ }
+
+ /**
+ * Get key.
+ *
+ * @return string
+ */
+ public function getKey()
+ {
+ return $this->key;
+ }
+
+ /**
+ * Set label.
+ *
+ * @param string $label
+ *
+ * @return PersonAltName
+ */
+ public function setLabel($label)
+ {
+ $this->label = $label;
+
+ return $this;
+ }
+
+ /**
+ * Get label.
+ *
+ * @return string
+ */
+ public function getLabel()
+ {
+ return $this->label;
+ }
+
+ /**
+ * @return Person
+ */
+ public function getPerson(): Person
+ {
+ return $this->person;
+ }
+
+ /**
+ * @param Person|null $person
+ * @return $this
+ */
+ public function setPerson(?Person $person = null)
+ {
+ $this->person = $person;
+
+ return $this;
+ }
+}
diff --git a/src/Bundle/ChillPerson/Export/AbstractAccompanyingPeriodExportElement.php b/src/Bundle/ChillPerson/Export/AbstractAccompanyingPeriodExportElement.php
new file mode 100644
index 000000000..c21d5733a
--- /dev/null
+++ b/src/Bundle/ChillPerson/Export/AbstractAccompanyingPeriodExportElement.php
@@ -0,0 +1,59 @@
+
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+namespace Chill\PersonBundle\Export;
+
+use Doctrine\ORM\QueryBuilder;
+
+/**
+ *
+ *
+ */
+class AbstractAccompanyingPeriodExportElement
+{
+ /**
+ * Return true if "accompanying_period" alias is present in the query alises.
+ *
+ * @param QueryBuilder $query
+ * @return bool
+ */
+ protected function havingAccompanyingPeriodInJoin(QueryBuilder $query): bool
+ {
+ $joins = $query->getDQLPart('join') ?? [];
+
+ return (\in_array('accompanying_period', $query->getAllAliases()));
+ }
+
+ /**
+ * Add the accompanying period alias to the query
+ *
+ * @param QueryBuilder $query
+ * @return void
+ * @throws \LogicException if the "person" alias is not present and attaching accompanying period is not possible
+ */
+ protected function addJoinAccompanyingPeriod(QueryBuilder $query): void
+ {
+ if (FALSE === $this->havingAccompanyingPeriodInJoin($query)) {
+ if (FALSE === \in_array('person', $query->getAllAliases())) {
+ throw new \LogicException("the alias 'person' does not exists in "
+ . "query builder");
+ }
+
+ $query->join('person.accompanyingPeriods', 'accompanying_period');
+ }
+ }
+}
diff --git a/src/Bundle/ChillPerson/Export/Aggregator/AgeAggregator.php b/src/Bundle/ChillPerson/Export/Aggregator/AgeAggregator.php
new file mode 100644
index 000000000..7b2e6c1b4
--- /dev/null
+++ b/src/Bundle/ChillPerson/Export/Aggregator/AgeAggregator.php
@@ -0,0 +1,112 @@
+
+ *
+ * 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\PersonBundle\Export\Aggregator;
+
+use Chill\MainBundle\Export\AggregatorInterface;
+use Doctrine\ORM\QueryBuilder;
+use Symfony\Component\Form\Extension\Core\Type\DateType;
+use Chill\MainBundle\Export\ExportElementValidatedInterface;
+use Symfony\Component\Validator\Context\ExecutionContextInterface;
+use Symfony\Component\Translation\TranslatorInterface;
+
+/**
+ *
+ *
+ * @author Julien Fastré
+ */
+class AgeAggregator implements AggregatorInterface,
+ ExportElementValidatedInterface
+{
+ /**
+ *
+ * @var
+ */
+ protected $translator;
+
+ public function __construct($translator)
+ {
+ $this->translator = $translator;
+ }
+
+
+ public function addRole()
+ {
+ return null;
+ }
+
+ public function alterQuery(QueryBuilder $qb, $data)
+ {
+ $qb->addSelect('DATE_DIFF(:date_age_calculation, person.birthdate)/365 as person_age');
+ $qb->setParameter('date_age_calculation', $data['date_age_calculation']);
+ $qb->addGroupBy('person_age');
+ }
+
+ public function applyOn()
+ {
+ return 'person';
+ }
+
+ public function buildForm(\Symfony\Component\Form\FormBuilderInterface $builder)
+ {
+ $builder->add('date_age_calculation', DateType::class, array(
+ 'label' => "Calculate age in relation to this date",
+ 'data' => new \DateTime(),
+ 'attr' => array('class' => 'datepicker'),
+ 'widget'=> 'single_text',
+ 'format' => 'dd-MM-yyyy'
+ ));
+ }
+
+ public function validateForm($data, ExecutionContextInterface $context)
+ {
+ if ($data['date_age_calculation'] === null) {
+ $context->buildViolation("The date should not be empty")
+ ->addViolation();
+ }
+ }
+
+ public function getLabels($key, array $values, $data)
+ {
+ return function($value) {
+ if ($value === '_header') {
+ return "Age";
+ }
+
+ if ($value === NULL) {
+ return $this->translator->trans("without data");
+ }
+
+ return $value;
+ };
+ }
+
+ public function getQueryKeys($data)
+ {
+ return array(
+ 'person_age'
+ );
+ }
+
+ public function getTitle()
+ {
+ return "Aggregate by age";
+ }
+
+}
diff --git a/src/Bundle/ChillPerson/Export/Aggregator/CountryOfBirthAggregator.php b/src/Bundle/ChillPerson/Export/Aggregator/CountryOfBirthAggregator.php
new file mode 100644
index 000000000..5bb08af60
--- /dev/null
+++ b/src/Bundle/ChillPerson/Export/Aggregator/CountryOfBirthAggregator.php
@@ -0,0 +1,202 @@
+
+ *
+ * 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\PersonBundle\Export\Aggregator;
+
+use Chill\MainBundle\Export\AggregatorInterface;
+use Symfony\Component\Form\FormBuilderInterface;
+use Doctrine\ORM\QueryBuilder;
+use Doctrine\ORM\EntityRepository;
+use Chill\MainBundle\Templating\TranslatableStringHelper;
+use Symfony\Component\Translation\TranslatorInterface;
+use Chill\MainBundle\Util\CountriesInfo;
+use Symfony\Component\Security\Core\Role\Role;
+use Chill\PersonBundle\Security\Authorization\PersonVoter;
+use Chill\MainBundle\Export\ExportElementValidatedInterface;
+use Symfony\Component\Validator\Context\ExecutionContextInterface;
+use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
+
+
+/**
+ *
+ *
+ * @author Julien Fastré
+ */
+class CountryOfBirthAggregator implements AggregatorInterface,
+ ExportElementValidatedInterface
+{
+ /**
+ *
+ * @var EntityRepository
+ */
+ protected $countriesRepository;
+
+ /**
+ *
+ * @var TranslatableStringHelper
+ */
+ protected $translatableStringHelper;
+
+ /**
+ *
+ * @var TranslatorInterface
+ */
+ protected $translator;
+
+ public function __construct(EntityRepository $countriesRepository,
+ TranslatableStringHelper $translatableStringHelper,
+ TranslatorInterface $translator)
+ {
+ $this->countriesRepository = $countriesRepository;
+ $this->translatableStringHelper = $translatableStringHelper;
+ $this->translator = $translator;
+ }
+
+ public function applyOn()
+ {
+ return 'person';
+ }
+
+
+ public function buildForm(FormBuilderInterface $builder)
+ {
+ $builder->add('group_by_level', ChoiceType::class, array(
+ 'choices' => array(
+ 'Group by continents' => 'continent',
+ 'Group by country' => 'country'
+ ),
+ 'expanded' => true,
+ 'multiple' => false
+ ));
+
+ }
+
+ public function validateForm($data, ExecutionContextInterface $context)
+ {
+ if ($data['group_by_level'] === null) {
+ $context->buildViolation("You should select an option")
+ ->addViolation();
+ }
+ }
+
+ public function alterQuery(QueryBuilder $qb, $data)
+ {
+ // add a clause in select part
+ if ($data['group_by_level'] === 'country') {
+ $qb->addSelect('countryOfBirth.countryCode as country_of_birth_aggregator');
+ } elseif ($data['group_by_level'] === 'continent') {
+ $clause = 'CASE '
+ . 'WHEN countryOfBirth.countryCode IN(:cob_africa_codes) THEN \'AF\' '
+ . 'WHEN countryOfBirth.countryCode IN(:cob_asia_codes) THEN \'AS\' '
+ . 'WHEN countryOfBirth.countryCode IN(:cob_europe_codes) THEN \'EU\' '
+ . 'WHEN countryOfBirth.countryCode IN(:cob_north_america_codes) THEN \'NA\' '
+ . 'WHEN countryOfBirth.countryCode IN(:cob_south_america_codes) THEN \'SA\' '
+ . 'WHEN countryOfBirth.countryCode IN(:cob_oceania_codes) THEN \'OC\' '
+ . 'WHEN countryOfBirth.countryCode IN(:cob_antartica_codes) THEN \'AN\' '
+ . 'ELSE \'\' '
+ . 'END as country_of_birth_aggregator ';
+ $qb->addSelect($clause);
+ $params =
+ array(
+ 'cob_africa_codes' => CountriesInfo::getCountriesCodeByContinent('AF'),
+ 'cob_asia_codes' => CountriesInfo::getCountriesCodeByContinent('AS'),
+ 'cob_europe_codes' => CountriesInfo::getCountriesCodeByContinent('EU'),
+ 'cob_north_america_codes' => CountriesInfo::getCountriesCodeByContinent('NA'),
+ 'cob_south_america_codes' => CountriesInfo::getCountriesCodeByContinent('SA'),
+ 'cob_oceania_codes' => CountriesInfo::getCountriesCodeByContinent('OC'),
+ 'cob_antartica_codes' => CountriesInfo::getCountriesCodeByContinent('AN')
+ );
+ foreach ($params as $k => $v) {
+ $qb->setParameter($k, $v);
+ }
+ } else {
+ throw new \LogicException("The group_by_level '".$data['group_by_level']
+ ." is not known.");
+ }
+
+
+ $qb->leftJoin('person.countryOfBirth', 'countryOfBirth');
+
+ // add group by
+ $groupBy = $qb->getDQLPart('groupBy');
+
+ if (!empty($groupBy)) {
+ $qb->addGroupBy('country_of_birth_aggregator');
+ } else {
+ $qb->groupBy('country_of_birth_aggregator');
+ }
+
+ }
+
+ public function getTitle()
+ {
+ return "Group people by country of birth";
+ }
+
+ public function getQueryKeys($data)
+ {
+ return array('country_of_birth_aggregator');
+ }
+
+ public function addRole()
+ {
+ return NULL;
+ }
+
+ public function getLabels($key, array $values, $data)
+ {
+ if ($data['group_by_level'] === 'country') {
+ $qb = $this->countriesRepository->createQueryBuilder('c');
+
+ $countries = $qb
+ ->andWhere($qb->expr()->in('c.countryCode', ':countries'))
+ ->setParameter('countries', $values)
+ ->getQuery()
+ ->getResult(\Doctrine\ORM\Query::HYDRATE_SCALAR);
+
+ // initialize array and add blank key for null values
+ $labels[''] = $this->translator->trans('without data');
+ $labels['_header'] = $this->translator->trans('Country of birth');
+ foreach($countries as $row) {
+ $labels[$row['c_countryCode']] = $this->translatableStringHelper->localize($row['c_name']);
+ }
+
+
+ } elseif ($data['group_by_level'] === 'continent') {
+
+ $labels = array(
+ 'EU' => $this->translator->trans('Europe'),
+ 'AS' => $this->translator->trans('Asia'),
+ 'AN' => $this->translator->trans('Antartica'),
+ 'AF' => $this->translator->trans('Africa'),
+ 'SA' => $this->translator->trans('South America'),
+ 'NA' => $this->translator->trans('North America'),
+ 'OC' => $this->translator->trans('Oceania'),
+ '' => $this->translator->trans('without data'),
+ '_header' => $this->translator->trans('Continent of birth')
+ );
+ }
+
+
+ return function($value) use ($labels) {
+ return $labels[$value];
+ };
+
+ }
+}
diff --git a/src/Bundle/ChillPerson/Export/Aggregator/GenderAggregator.php b/src/Bundle/ChillPerson/Export/Aggregator/GenderAggregator.php
new file mode 100644
index 000000000..6f4314a76
--- /dev/null
+++ b/src/Bundle/ChillPerson/Export/Aggregator/GenderAggregator.php
@@ -0,0 +1,103 @@
+
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+namespace Chill\PersonBundle\Export\Aggregator;
+
+use Chill\MainBundle\Export\AggregatorInterface;
+use Symfony\Component\Form\FormBuilderInterface;
+use Doctrine\ORM\QueryBuilder;
+use Symfony\Component\Translation\TranslatorInterface;
+use Chill\PersonBundle\Entity\Person;
+use Chill\PersonBundle\Export\Declarations;
+
+/**
+ *
+ *
+ * @author Julien Fastré
+ */
+class GenderAggregator implements AggregatorInterface
+{
+
+ /**
+ *
+ * @var TranslatorInterface
+ */
+ protected $translator;
+
+ public function __construct(TranslatorInterface $translator)
+ {
+ $this->translator = $translator;
+ }
+
+ public function applyOn()
+ {
+ return Declarations::PERSON_TYPE;
+ }
+
+
+ public function buildForm(FormBuilderInterface $builder)
+ {
+
+ }
+
+ public function alterQuery(QueryBuilder $qb, $data)
+ {
+
+ $qb->addSelect('person.gender as gender');
+
+ $qb->addGroupBy('gender');
+
+ }
+
+ public function getTitle()
+ {
+ return "Group people by gender";
+ }
+
+ public function getQueryKeys($data)
+ {
+ return array('gender');
+ }
+
+ public function getLabels($key, array $values, $data)
+ {
+ return function($value) {
+ switch ($value) {
+ case Person::FEMALE_GENDER :
+ return $this->translator->trans('woman');
+ case Person::MALE_GENDER :
+ return $this->translator->trans('man');
+ case Person::BOTH_GENDER:
+ return $this->translator->trans('both');
+ case null:
+ return $this->translator->trans('Not given');
+ case '_header' :
+ return $this->translator->trans('Gender');
+ default:
+ throw new \LogicException(sprintf("The value %s is not valid", $value));
+ }
+ };
+ }
+
+ public function addRole()
+ {
+ return NULL;
+ }
+
+}
diff --git a/src/Bundle/ChillPerson/Export/Aggregator/NationalityAggregator.php b/src/Bundle/ChillPerson/Export/Aggregator/NationalityAggregator.php
new file mode 100644
index 000000000..d708957f7
--- /dev/null
+++ b/src/Bundle/ChillPerson/Export/Aggregator/NationalityAggregator.php
@@ -0,0 +1,201 @@
+
+ *
+ * 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\PersonBundle\Export\Aggregator;
+
+use Chill\MainBundle\Export\AggregatorInterface;
+use Symfony\Component\Form\FormBuilderInterface;
+use Doctrine\ORM\QueryBuilder;
+use Doctrine\ORM\EntityRepository;
+use Chill\MainBundle\Templating\TranslatableStringHelper;
+use Symfony\Component\Translation\TranslatorInterface;
+use Chill\MainBundle\Util\CountriesInfo;
+use Symfony\Component\Security\Core\Role\Role;
+use Chill\PersonBundle\Security\Authorization\PersonVoter;
+use Chill\MainBundle\Export\ExportElementValidatedInterface;
+use Symfony\Component\Validator\Context\ExecutionContextInterface;
+use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
+
+/**
+ *
+ *
+ * @author Julien Fastré
+ */
+class NationalityAggregator implements AggregatorInterface,
+ ExportElementValidatedInterface
+{
+ /**
+ *
+ * @var EntityRepository
+ */
+ protected $countriesRepository;
+
+ /**
+ *
+ * @var TranslatableStringHelper
+ */
+ protected $translatableStringHelper;
+
+ /**
+ *
+ * @var TranslatorInterface
+ */
+ protected $translator;
+
+ public function __construct(EntityRepository $countriesRepository,
+ TranslatableStringHelper $translatableStringHelper,
+ TranslatorInterface $translator)
+ {
+ $this->countriesRepository = $countriesRepository;
+ $this->translatableStringHelper = $translatableStringHelper;
+ $this->translator = $translator;
+ }
+
+ public function applyOn()
+ {
+ return 'person';
+ }
+
+
+ public function buildForm(FormBuilderInterface $builder)
+ {
+ $builder->add('group_by_level', ChoiceType::class, array(
+ 'choices' => array(
+ 'Group by continents' => 'continent',
+ 'Group by country' => 'country'
+ ),
+ 'expanded' => true,
+ 'multiple' => false
+ ));
+
+ }
+
+ public function validateForm($data, ExecutionContextInterface $context)
+ {
+ if ($data['group_by_level'] === null) {
+ $context->buildViolation("You should select an option")
+ ->addViolation();
+ }
+ }
+
+ public function alterQuery(QueryBuilder $qb, $data)
+ {
+ // add a clause in select part
+ if ($data['group_by_level'] === 'country') {
+ $qb->addSelect('nationality.countryCode as nationality_aggregator');
+ } elseif ($data['group_by_level'] === 'continent') {
+ $clause = 'CASE '
+ . 'WHEN nationality.countryCode IN(:africa_codes) THEN \'AF\' '
+ . 'WHEN nationality.countryCode IN(:asia_codes) THEN \'AS\' '
+ . 'WHEN nationality.countryCode IN(:europe_codes) THEN \'EU\' '
+ . 'WHEN nationality.countryCode IN(:north_america_codes) THEN \'NA\' '
+ . 'WHEN nationality.countryCode IN(:south_america_codes) THEN \'SA\' '
+ . 'WHEN nationality.countryCode IN(:oceania_codes) THEN \'OC\' '
+ . 'WHEN nationality.countryCode IN(:antartica_codes) THEN \'AN\' '
+ . 'ELSE \'\' '
+ . 'END as nationality_aggregator ';
+ $qb->addSelect($clause);
+ $params =
+ array(
+ 'africa_codes' => CountriesInfo::getCountriesCodeByContinent('AF'),
+ 'asia_codes' => CountriesInfo::getCountriesCodeByContinent('AS'),
+ 'europe_codes' => CountriesInfo::getCountriesCodeByContinent('EU'),
+ 'north_america_codes' => CountriesInfo::getCountriesCodeByContinent('NA'),
+ 'south_america_codes' => CountriesInfo::getCountriesCodeByContinent('SA'),
+ 'oceania_codes' => CountriesInfo::getCountriesCodeByContinent('OC'),
+ 'antartica_codes' => CountriesInfo::getCountriesCodeByContinent('AN')
+ );
+ foreach ($params as $k => $v) {
+ $qb->setParameter($k, $v);
+ }
+ } else {
+ throw new \LogicException("The group_by_level '".$data['group_by_level']
+ ." is not known.");
+ }
+
+
+ $qb->leftJoin('person.nationality', 'nationality');
+
+ // add group by
+ $groupBy = $qb->getDQLPart('groupBy');
+
+ if (!empty($groupBy)) {
+ $qb->addGroupBy('nationality_aggregator');
+ } else {
+ $qb->groupBy('nationality_aggregator');
+ }
+
+ }
+
+ public function getTitle()
+ {
+ return "Group people by nationality";
+ }
+
+ public function getQueryKeys($data)
+ {
+ return array('nationality_aggregator');
+ }
+
+ public function addRole()
+ {
+ return NULL;
+ }
+
+ public function getLabels($key, array $values, $data)
+ {
+ if ($data['group_by_level'] === 'country') {
+ $qb = $this->countriesRepository->createQueryBuilder('c');
+
+ $countries = $qb
+ ->andWhere($qb->expr()->in('c.countryCode', ':countries'))
+ ->setParameter('countries', $values)
+ ->getQuery()
+ ->getResult(\Doctrine\ORM\Query::HYDRATE_SCALAR);
+
+ // initialize array and add blank key for null values
+ $labels[''] = $this->translator->trans('without data');
+ $labels['_header'] = $this->translator->trans('Nationality');
+ foreach($countries as $row) {
+ $labels[$row['c_countryCode']] = $this->translatableStringHelper->localize($row['c_name']);
+ }
+
+
+ } elseif ($data['group_by_level'] === 'continent') {
+
+ $labels = array(
+ 'EU' => $this->translator->trans('Europe'),
+ 'AS' => $this->translator->trans('Asia'),
+ 'AN' => $this->translator->trans('Antartica'),
+ 'AF' => $this->translator->trans('Africa'),
+ 'SA' => $this->translator->trans('South America'),
+ 'NA' => $this->translator->trans('North America'),
+ 'OC' => $this->translator->trans('Oceania'),
+ '' => $this->translator->trans('without data'),
+ '_header' => $this->translator->trans('Continent')
+ );
+ }
+
+
+ return function($value) use ($labels) {
+ return $labels[$value];
+ };
+
+ }
+}
diff --git a/src/Bundle/ChillPerson/Export/Declarations.php b/src/Bundle/ChillPerson/Export/Declarations.php
new file mode 100644
index 000000000..bbdf3e8e4
--- /dev/null
+++ b/src/Bundle/ChillPerson/Export/Declarations.php
@@ -0,0 +1,32 @@
+
+ *
+ * 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\PersonBundle\Export;
+
+/**
+ * This class declare constants used for the export framework.
+ *
+ *
+ * @author Julien Fastré
+ */
+abstract class Declarations
+{
+ CONST PERSON_TYPE = 'person';
+ CONST PERSON_IMPLIED_IN = 'person_implied_in';
+}
diff --git a/src/Bundle/ChillPerson/Export/Export/CountPerson.php b/src/Bundle/ChillPerson/Export/Export/CountPerson.php
new file mode 100644
index 000000000..0431095ec
--- /dev/null
+++ b/src/Bundle/ChillPerson/Export/Export/CountPerson.php
@@ -0,0 +1,136 @@
+
+ *
+ * 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\PersonBundle\Export\Export;
+
+use Chill\MainBundle\Export\ExportInterface;
+use Doctrine\ORM\QueryBuilder;
+use Symfony\Component\Form\FormBuilderInterface;
+use Doctrine\ORM\Query;
+use Chill\PersonBundle\Security\Authorization\PersonVoter;
+use Symfony\Component\Security\Core\Role\Role;
+use Chill\PersonBundle\Export\Declarations;
+use Chill\MainBundle\Export\FormatterInterface;
+use Doctrine\ORM\EntityManagerInterface;
+
+/**
+ *
+ *
+ * @author Julien Fastré
+ */
+class CountPerson implements ExportInterface
+{
+ /**
+ *
+ * @var EntityManagerInterface
+ */
+ protected $entityManager;
+
+ public function __construct(
+ EntityManagerInterface $em
+ )
+ {
+ $this->entityManager = $em;
+ }
+
+ /**
+ *
+ */
+ public function getType()
+ {
+ return Declarations::PERSON_TYPE;
+ }
+
+ public function getDescription()
+ {
+ return "Count peoples by various parameters.";
+ }
+
+ public function getTitle()
+ {
+ return "Count peoples";
+ }
+
+ public function requiredRole()
+ {
+ return new Role(PersonVoter::STATS);
+ }
+
+ /**
+ * Initiate the query
+ *
+ * @param QueryBuilder $qb
+ * @return QueryBuilder
+ */
+ public function initiateQuery(array $requiredModifiers, array $acl, array $data = array())
+ {
+ $centers = array_map(function($el) { return $el['center']; }, $acl);
+
+ $qb = $this->entityManager->createQueryBuilder();
+
+ $qb->select('COUNT(person.id) AS export_result')
+ ->from('ChillPersonBundle:Person', 'person')
+ ->join('person.center', 'center')
+ ->andWhere('center IN (:authorized_centers)')
+ ->setParameter('authorized_centers', $centers);
+ ;
+
+
+ return $qb;
+ }
+
+ public function getResult($qb, $data)
+ {
+ return $qb->getQuery()->getResult(Query::HYDRATE_SCALAR);
+ }
+
+ public function getQueryKeys($data)
+ {
+ return array('export_result');
+ }
+
+ public function getLabels($key, array $values, $data)
+ {
+ if ($key !== 'export_result') {
+ throw new \LogicException("the key $key is not used by this export");
+ }
+
+ $labels = array_combine($values, $values);
+ $labels['_header'] = $this->getTitle();
+
+ return function($value) use ($labels) {
+ return $labels[$value];
+ };
+ }
+
+ public function getAllowedFormattersTypes()
+ {
+ return array(FormatterInterface::TYPE_TABULAR);
+ }
+
+ public function buildForm(FormBuilderInterface $builder) {
+
+ }
+
+ public function supportsModifiers()
+ {
+ return array(Declarations::PERSON_TYPE, Declarations::PERSON_IMPLIED_IN);
+ }
+
+}
diff --git a/src/Bundle/ChillPerson/Export/Export/ListPerson.php b/src/Bundle/ChillPerson/Export/Export/ListPerson.php
new file mode 100644
index 000000000..dc36c8c57
--- /dev/null
+++ b/src/Bundle/ChillPerson/Export/Export/ListPerson.php
@@ -0,0 +1,503 @@
+entityManager = $em;
+ $this->translator = $translator;
+ $this->translatableStringHelper = $translatableStringHelper;
+ $this->customFieldProvider = $customFieldProvider;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @param FormBuilderInterface $builder
+ */
+ public function buildForm(FormBuilderInterface $builder)
+ {
+ $choices = array_combine($this->fields, $this->fields);
+
+ foreach ($this->getCustomFields() as $cf) {
+ $choices
+ [$this->translatableStringHelper->localize($cf->getName())]
+ =
+ $cf->getSlug();
+ }
+
+ // Add a checkbox to select fields
+ $builder->add('fields', ChoiceType::class, array(
+ 'multiple' => true,
+ 'expanded' => true,
+ 'choices' => $choices,
+ 'label' => 'Fields to include in export',
+ 'choice_attr' => function($val, $key, $index) {
+ // add a 'data-display-target' for address fields
+ if (substr($val, 0, 8) === 'address_') {
+ return ['data-display-target' => 'address_date'];
+ } else {
+ return [];
+ }
+ },
+ 'constraints' => [new Callback(array(
+ 'callback' => function($selected, ExecutionContextInterface $context) {
+ if (count($selected) === 0) {
+ $context->buildViolation('You must select at least one element')
+ ->atPath('fields')
+ ->addViolation();
+ }
+ }
+ ))]
+ ));
+
+ // add a date field for addresses
+ $builder->add('address_date', DateType::class, array(
+ 'label' => "Address valid at this date",
+ 'data' => new \DateTime(),
+ 'attr' => array( 'class' => 'datepicker'),
+ 'widget'=> 'single_text',
+ 'format' => 'dd-MM-yyyy',
+ 'required' => false,
+ 'block_name' => 'list_export_form_address_date'
+ ));
+ }
+
+ public function validateForm($data, ExecutionContextInterface $context)
+ {
+ // get the field starting with address_
+ $addressFields = array_filter(function($el) {
+ return substr($el, 0, 8) === 'address_';
+ }, $this->fields);
+
+ // check if there is one field starting with address in data
+ if (count(array_intersect($data['fields'], $addressFields)) > 0) {
+ // if a field address is checked, the date must not be empty
+ if (empty($data['address_date'])) {
+ $context
+ ->buildViolation("You must set this date if an address is checked")
+ ->atPath('address_date')
+ ->addViolation();
+ }
+ }
+ }
+
+ /**
+ * Get custom fields associated with person
+ *
+ * @return CustomField[]
+ */
+ private function getCustomFields()
+ {
+ return $this->entityManager
+ ->createQuery("SELECT cf "
+ . "FROM ChillCustomFieldsBundle:CustomField cf "
+ . "JOIN cf.customFieldGroup g "
+ . "WHERE cf.type != :title AND g.entity LIKE :entity")
+ ->setParameters(array(
+ 'title' => 'title',
+ 'entity' => \addcslashes(Person::class, "\\")
+ ))
+ ->getResult();
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @return type
+ */
+ public function getAllowedFormattersTypes()
+ {
+ return array(FormatterInterface::TYPE_LIST);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @return string
+ */
+ public function getDescription()
+ {
+ return "Create a list of people according to various filters.";
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @param type $key
+ * @param array $values
+ * @param type $data
+ * @return type
+ */
+ public function getLabels($key, array $values, $data)
+ {
+ switch ($key) {
+ case 'birthdate':
+ // for birthdate, we have to transform the string into a date
+ // to format the date correctly.
+ return function($value) {
+ if ($value === '_header') { return 'birthdate'; }
+
+ if (empty($value))
+ {
+ return "";
+ }
+
+ $date = \DateTime::createFromFormat('Y-m-d', $value);
+ // check that the creation could occurs.
+ if ($date === false) {
+ throw new \Exception(sprintf("The value %s could "
+ . "not be converted to %s", $value, \DateTime::class));
+ }
+
+ return $date->format('d-m-Y');
+ };
+ case 'gender' :
+ // for gender, we have to translate men/women statement
+ return function($value) {
+ if ($value === '_header') { return 'gender'; }
+
+ return $this->translator->trans($value);
+ };
+ case 'countryOfBirth':
+ case 'nationality':
+ $countryRepository = $this->entityManager
+ ->getRepository('ChillMainBundle:Country');
+
+ // load all countries in a single query
+ $countryRepository->findBy(array('countryCode' => $values));
+
+ return function($value) use ($key, $countryRepository) {
+ if ($value === '_header') { return \strtolower($key); }
+
+ if ($value === NULL) {
+ return $this->translator->trans('no data');
+ }
+
+ $country = $countryRepository->find($value);
+
+ return $this->translatableStringHelper->localize(
+ $country->getName());
+ };
+ case 'address_country_name':
+ return function($value) use ($key) {
+ if ($value === '_header') { return \strtolower($key); }
+
+ if ($value === NULL) {
+ return '';
+ }
+
+ return $this->translatableStringHelper->localize(json_decode($value, true));
+ };
+ case 'address_isnoaddress':
+ return function($value) use ($key) {
+ if ($value === '_header') { return 'address.address_homeless'; }
+
+ if ($value) {
+ return 'X';
+ } else {
+ return '';
+ }
+ };
+ default:
+ // for fields which are associated with person
+ if (in_array($key, $this->fields)) {
+ return function($value) use ($key) {
+ if ($value === '_header') { return \strtolower($key); }
+
+ return $value;
+
+ };
+ } else {
+ return $this->getLabelForCustomField($key, $values, $data);
+ }
+ }
+
+ }
+
+ private function getLabelForCustomField($key, array $values, $data)
+ {
+ // for fields which are custom fields
+ /* @var $cf CustomField */
+ $cf = $this->entityManager
+ ->getRepository(CustomField::class)
+ ->findOneBy(array('slug' => $this->DQLToSlug($key)));
+ $cfType = $this->customFieldProvider->getCustomFieldByType($cf->getType());
+ $defaultFunction = function($value) use ($cf) {
+ if ($value === '_header') {
+ return $this->translatableStringHelper->localize($cf->getName());
+ }
+
+ return $this->customFieldProvider
+ ->getCustomFieldByType($cf->getType())
+ ->render(json_decode($value, true), $cf, 'csv');
+ };
+
+ if ($cfType instanceof CustomFieldChoice and $cfType->isMultiple($cf)) {
+ return function($value) use ($cf, $cfType, $key) {
+ $slugChoice = $this->extractInfosFromSlug($key)['additionnalInfos']['choiceSlug'];
+ $decoded = \json_decode($value, true);
+
+ if ($value === '_header') {
+
+ $label = $cfType->getChoices($cf)[$slugChoice];
+
+ return $this->translatableStringHelper->localize($cf->getName())
+ .' | '.$label;
+ }
+
+ if ($slugChoice === '_other' and $cfType->isChecked($cf, $choiceSlug, $decoded)) {
+ return $cfType->extractOtherValue($cf, $decoded);
+ } else {
+ return $cfType->isChecked($cf, $slugChoice, $decoded);
+ }
+ };
+
+ } else {
+ return $defaultFunction;
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @param type $data
+ * @return type
+ */
+ public function getQueryKeys($data)
+ {
+ $fields = array();
+
+ foreach ($data['fields'] as $key) {
+ if (in_array($key, $this->fields)) {
+ $fields[] = $key;
+ }
+ }
+
+ // add the key from slugs and return
+ return \array_merge($fields, \array_keys($this->slugs));
+ }
+
+ /**
+ * clean a slug to be usable by DQL
+ *
+ * @param string $slugsanitize
+ * @param string $type the type of the customfield, if required (currently only for choices)
+ * @return string
+ */
+ private function slugToDQL($slug, $type = "default", array $additionalInfos = [])
+ {
+ $uid = 'slug_'.\uniqid();
+
+ $this->slugs[$uid] = [
+ 'slug' => $slug,
+ 'type' => $type,
+ 'additionnalInfos' => $additionalInfos
+ ];
+
+ return $uid;
+ }
+
+ private function DQLToSlug($cleanedSlug)
+ {
+ return $this->slugs[$cleanedSlug]['slug'];
+ }
+
+ /**
+ *
+ * @param type $cleanedSlug
+ * @return an array with keys = 'slug', 'type', 'additionnalInfo'
+ */
+ private function extractInfosFromSlug($slug)
+ {
+ return $this->slugs[$slug];
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ */
+ public function getResult($query, $data)
+ {
+ return $query->getQuery()->getResult(Query::HYDRATE_SCALAR);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @return string
+ */
+ public function getTitle()
+ {
+ return "List peoples";
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ */
+ public function getType()
+ {
+ return Declarations::PERSON_TYPE;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ */
+ public function initiateQuery(array $requiredModifiers, array $acl, array $data = array())
+ {
+ $centers = array_map(function($el) { return $el['center']; }, $acl);
+
+ // throw an error if any fields are present
+ if (!\array_key_exists('fields', $data)) {
+ throw new \Doctrine\DBAL\Exception\InvalidArgumentException("any fields "
+ . "have been checked");
+ }
+
+ $qb = $this->entityManager->createQueryBuilder();
+
+ foreach ($this->fields as $f) {
+ if (in_array($f, $data['fields'])) {
+ switch ($f) {
+ case 'countryOfBirth':
+ case 'nationality':
+ $qb->addSelect(sprintf('IDENTITY(person.%s) as %s', $f, $f));
+ break;
+ case 'address_street_address_1':
+ case 'address_street_address_2':
+ case 'address_valid_from':
+ case 'address_postcode_label':
+ case 'address_postcode_code':
+ case 'address_country_name':
+ case 'address_country_code':
+ case 'address_isnoaddress':
+
+ $qb->addSelect(sprintf(
+ 'GET_PERSON_ADDRESS_%s(person.id, :address_date) AS %s',
+ // get the part after address_
+ strtoupper(substr($f, 8)),
+ $f));
+ $qb->setParameter('address_date', $data['address_date']);
+ break;
+ default:
+ $qb->addSelect(sprintf('person.%s as %s', $f, $f));
+ }
+ }
+ }
+
+ foreach ($this->getCustomFields() as $cf) {
+ $cfType = $this->customFieldProvider->getCustomFieldByType($cf->getType());
+ if ($cfType instanceof CustomFieldChoice and $cfType->isMultiple($cf)) {
+ foreach($cfType->getChoices($cf) as $choiceSlug => $label) {
+ $slug = $this->slugToDQL($cf->getSlug(), 'choice', [ 'choiceSlug' => $choiceSlug ]);
+ $qb->addSelect(
+ sprintf('GET_JSON_FIELD_BY_KEY(person.cFData, :slug%s) AS %s',
+ $slug, $slug));
+ $qb->setParameter(sprintf('slug%s', $slug), $cf->getSlug());
+ }
+ } else {
+ $slug = $this->slugToDQL($cf->getSlug());
+ $qb->addSelect(
+ sprintf('GET_JSON_FIELD_BY_KEY(person.cFData, :slug%s) AS %s',
+ $slug, $slug));
+ $qb->setParameter(sprintf('slug%s', $slug), $cf->getSlug());
+ }
+ }
+
+ $qb
+ ->from('ChillPersonBundle:Person', 'person')
+ ->join('person.center', 'center')
+ ->andWhere('center IN (:authorized_centers)')
+ ->setParameter('authorized_centers', $centers);
+ ;
+
+
+ return $qb;
+ }
+
+ /**
+ *
+ * {@inheritDoc}
+ */
+ public function requiredRole()
+ {
+ return new Role(PersonVoter::LISTS);
+ }
+
+ /**
+ *
+ * {@inheritDoc}
+ */
+ public function supportsModifiers()
+ {
+ return array(Declarations::PERSON_TYPE, Declarations::PERSON_IMPLIED_IN);
+ }
+}
diff --git a/src/Bundle/ChillPerson/Export/Filter/AccompanyingPeriodClosingFilter.php b/src/Bundle/ChillPerson/Export/Filter/AccompanyingPeriodClosingFilter.php
new file mode 100644
index 000000000..7179de4d3
--- /dev/null
+++ b/src/Bundle/ChillPerson/Export/Filter/AccompanyingPeriodClosingFilter.php
@@ -0,0 +1,85 @@
+
+ *
+ * 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\PersonBundle\Export\Filter;
+
+use Chill\MainBundle\Export\FilterInterface;
+use Symfony\Component\Form\FormBuilderInterface;
+use Doctrine\ORM\QueryBuilder;
+use Chill\MainBundle\Form\Type\ChillDateType;
+use Doctrine\DBAL\Types\Type;
+use Chill\PersonBundle\Export\AbstractAccompanyingPeriodExportElement;
+
+/**
+ *
+ *
+ */
+class AccompanyingPeriodClosingFilter extends AbstractAccompanyingPeriodExportElement implements FilterInterface
+{
+ public function addRole()
+ {
+ return null;
+ }
+
+ public function alterQuery(QueryBuilder $qb, $data)
+ {
+ $this->addJoinAccompanyingPeriod($qb);
+
+ $clause = $qb->expr()->andX(
+ $qb->expr()->lte('accompanying_period.closingDate', ':date_to'),
+ $qb->expr()->gte('accompanying_period.closingDate', ':date_from'));
+
+ $qb->andWhere($clause);
+ $qb->setParameter('date_from', $data['date_from'], Type::DATE);
+ $qb->setParameter('date_to', $data['date_to'], Type::DATE);
+ }
+
+ public function applyOn(): string
+ {
+ return 'person';
+ }
+
+ public function buildForm(FormBuilderInterface $builder)
+ {
+ $builder->add('date_from', ChillDateType::class, array(
+ 'label' => "Having an accompanying period closed after this date",
+ 'data' => new \DateTime("-1 month"),
+ ));
+
+ $builder->add('date_to', ChillDateType::class, array(
+ 'label' => "Having an accompanying period closed before this date",
+ 'data' => new \DateTime(),
+ ));
+ }
+
+ public function describeAction($data, $format = 'string')
+ {
+ return [
+ "Filtered by accompanying period: persons having an accompanying period"
+ . " closed between the %date_from% and %date_to%",
+ [
+ '%date_from%' => $data['date_from']->format('d-m-Y'),
+ '%date_to%' => $data['date_to']->format('d-m-Y')
+ ]
+ ];
+ }
+
+ public function getTitle(): string
+ {
+ return "Filter by accompanying period: closed between two dates";
+ }
+}
diff --git a/src/Bundle/ChillPerson/Export/Filter/AccompanyingPeriodFilter.php b/src/Bundle/ChillPerson/Export/Filter/AccompanyingPeriodFilter.php
new file mode 100644
index 000000000..40f7dd501
--- /dev/null
+++ b/src/Bundle/ChillPerson/Export/Filter/AccompanyingPeriodFilter.php
@@ -0,0 +1,95 @@
+
+ *
+ * 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\PersonBundle\Export\Filter;
+
+use Chill\MainBundle\Export\FilterInterface;
+use Symfony\Component\Form\FormBuilderInterface;
+use Doctrine\ORM\QueryBuilder;
+use Chill\MainBundle\Form\Type\ChillDateType;
+use Doctrine\DBAL\Types\Type;
+use Chill\PersonBundle\Export\AbstractAccompanyingPeriodExportElement;
+
+/**
+ *
+ *
+ */
+class AccompanyingPeriodFilter extends AbstractAccompanyingPeriodExportElement implements FilterInterface
+{
+ public function addRole()
+ {
+ return null;
+ }
+
+ public function alterQuery(QueryBuilder $qb, $data)
+ {
+ $this->addJoinAccompanyingPeriod($qb);
+
+ $clause = $qb->expr()->andX();
+
+ $clause->add(
+ $qb->expr()->lte('accompanying_period.openingDate', ':date_to')
+ );
+ $clause->add(
+ $qb->expr()->orX(
+ $qb->expr()->gte('accompanying_period.closingDate', ':date_from'),
+ $qb->expr()->isNull('accompanying_period.closingDate')
+ )
+ );
+
+ $qb->andWhere($clause);
+ $qb->setParameter('date_from', $data['date_from'], Type::DATE);
+ $qb->setParameter('date_to', $data['date_to'], Type::DATE);
+ }
+
+ public function applyOn(): string
+ {
+ return 'person';
+ }
+
+ public function buildForm(FormBuilderInterface $builder)
+ {
+ $builder->add('date_from', ChillDateType::class, array(
+ 'label' => "Having an accompanying period opened after this date",
+ 'data' => new \DateTime("-1 month"),
+ ));
+
+ $builder->add('date_to', ChillDateType::class, array(
+ 'label' => "Having an accompanying period ending before this date, or "
+ . "still opened at this date",
+ 'data' => new \DateTime(),
+ ));
+ }
+
+ public function describeAction($data, $format = 'string')
+ {
+ return [
+ "Filtered by accompanying period: persons having an accompanying period"
+ . " opened after the %date_from% and closed before the %date_to% (or still opened "
+ . "at the %date_to%)",
+ [
+ '%date_from%' => $data['date_from']->format('d-m-Y'),
+ '%date_to%' => $data['date_to']->format('d-m-Y')
+ ]
+ ];
+ }
+
+ public function getTitle(): string
+ {
+ return "Filter by accompanying period: active period";
+ }
+}
diff --git a/src/Bundle/ChillPerson/Export/Filter/AccompanyingPeriodOpeningFilter.php b/src/Bundle/ChillPerson/Export/Filter/AccompanyingPeriodOpeningFilter.php
new file mode 100644
index 000000000..26a8818df
--- /dev/null
+++ b/src/Bundle/ChillPerson/Export/Filter/AccompanyingPeriodOpeningFilter.php
@@ -0,0 +1,85 @@
+
+ *
+ * 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\PersonBundle\Export\Filter;
+
+use Chill\MainBundle\Export\FilterInterface;
+use Symfony\Component\Form\FormBuilderInterface;
+use Doctrine\ORM\QueryBuilder;
+use Chill\MainBundle\Form\Type\ChillDateType;
+use Doctrine\DBAL\Types\Type;
+use Chill\PersonBundle\Export\AbstractAccompanyingPeriodExportElement;
+
+/**
+ *
+ *
+ */
+class AccompanyingPeriodOpeningFilter extends AbstractAccompanyingPeriodExportElement implements FilterInterface
+{
+ public function addRole()
+ {
+ return null;
+ }
+
+ public function alterQuery(QueryBuilder $qb, $data)
+ {
+ $this->addJoinAccompanyingPeriod($qb);
+
+ $clause = $qb->expr()->andX(
+ $qb->expr()->lte('accompanying_period.openingDate', ':date_to'),
+ $qb->expr()->gte('accompanying_period.openingDate', ':date_from'));
+
+ $qb->andWhere($clause);
+ $qb->setParameter('date_from', $data['date_from'], Type::DATE);
+ $qb->setParameter('date_to', $data['date_to'], Type::DATE);
+ }
+
+ public function applyOn(): string
+ {
+ return 'person';
+ }
+
+ public function buildForm(FormBuilderInterface $builder)
+ {
+ $builder->add('date_from', ChillDateType::class, array(
+ 'label' => "Having an accompanying period opened after this date",
+ 'data' => new \DateTime("-1 month"),
+ ));
+
+ $builder->add('date_to', ChillDateType::class, array(
+ 'label' => "Having an accompanying period opened before this date",
+ 'data' => new \DateTime(),
+ ));
+ }
+
+ public function describeAction($data, $format = 'string')
+ {
+ return [
+ "Filtered by accompanying period: persons having an accompanying period"
+ . " opened between the %date_from% and %date_to%",
+ [
+ '%date_from%' => $data['date_from']->format('d-m-Y'),
+ '%date_to%' => $data['date_to']->format('d-m-Y')
+ ]
+ ];
+ }
+
+ public function getTitle(): string
+ {
+ return "Filter by accompanying period: starting between two dates";
+ }
+}
diff --git a/src/Bundle/ChillPerson/Export/Filter/BirthdateFilter.php b/src/Bundle/ChillPerson/Export/Filter/BirthdateFilter.php
new file mode 100644
index 000000000..234d6bd3e
--- /dev/null
+++ b/src/Bundle/ChillPerson/Export/Filter/BirthdateFilter.php
@@ -0,0 +1,130 @@
+
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+namespace Chill\PersonBundle\Export\Filter;
+
+use Chill\MainBundle\Export\FilterInterface;
+use Symfony\Component\Form\Extension\Core\Type\DateType;
+use Symfony\Component\Validator\Context\ExecutionContextInterface;
+use Symfony\Component\Validator\Constraints;
+use Symfony\Component\Form\FormEvent;
+use Symfony\Component\Form\FormEvents;
+use Doctrine\ORM\Query\Expr;
+use Chill\MainBundle\Form\Type\Export\FilterType;
+use Symfony\Component\Form\FormError;
+use Chill\MainBundle\Export\ExportElementValidatedInterface;
+
+/**
+ *
+ *
+ * @author Julien Fastré
+ */
+class BirthdateFilter implements FilterInterface, ExportElementValidatedInterface
+{
+
+ public function addRole()
+ {
+ return null;
+ }
+
+ public function alterQuery(\Doctrine\ORM\QueryBuilder $qb, $data)
+ {
+ $where = $qb->getDQLPart('where');
+ $clause = $qb->expr()->between('person.birthdate', ':date_from',
+ ':date_to');
+
+ if ($where instanceof Expr\Andx) {
+ $where->add($clause);
+ } else {
+ $where = $qb->expr()->andX($clause);
+ }
+
+ $qb->add('where', $where);
+ $qb->setParameter('date_from', $data['date_from']);
+ $qb->setParameter('date_to', $data['date_to']);
+ }
+
+ public function applyOn()
+ {
+ return 'person';
+ }
+
+ public function buildForm(\Symfony\Component\Form\FormBuilderInterface $builder)
+ {
+ $builder->add('date_from', DateType::class, array(
+ 'label' => "Born after this date",
+ 'data' => new \DateTime(),
+ 'attr' => array('class' => 'datepicker'),
+ 'widget'=> 'single_text',
+ 'format' => 'dd-MM-yyyy',
+ ));
+
+ $builder->add('date_to', DateType::class, array(
+ 'label' => "Born before this date",
+ 'data' => new \DateTime(),
+ 'attr' => array('class' => 'datepicker'),
+ 'widget'=> 'single_text',
+ 'format' => 'dd-MM-yyyy',
+ ));
+
+ }
+
+ public function validateForm($data, ExecutionContextInterface $context)
+ {
+ $date_from = $data['date_from'];
+ $date_to = $data['date_to'];
+
+ if ($date_from === null) {
+ $context->buildViolation('The "date from" should not be empty')
+ //->atPath('date_from')
+ ->addViolation();
+ }
+
+ if ($date_to === null) {
+ $context->buildViolation('The "date to" should not be empty')
+ //->atPath('date_to')
+ ->addViolation();
+ }
+
+ if (
+ ($date_from !== null && $date_to !== null)
+ &&
+ $date_from >= $date_to
+ ) {
+ $context->buildViolation('The date "date to" should be after the '
+ . 'date given in "date from" field')
+ ->addViolation();
+ }
+ }
+
+ public function describeAction($data, $format = 'string')
+ {
+ return array('Filtered by person\'s birtdate: '
+ . 'between %date_from% and %date_to%', array(
+ '%date_from%' => $data['date_from']->format('d-m-Y'),
+ '%date_to%' => $data['date_to']->format('d-m-Y')
+ ));
+ }
+
+ public function getTitle()
+ {
+ return 'Filter by person\'s birthdate';
+ }
+
+}
diff --git a/src/Bundle/ChillPerson/Export/Filter/GenderFilter.php b/src/Bundle/ChillPerson/Export/Filter/GenderFilter.php
new file mode 100644
index 000000000..7db76f6a7
--- /dev/null
+++ b/src/Bundle/ChillPerson/Export/Filter/GenderFilter.php
@@ -0,0 +1,139 @@
+
+ *
+ * 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\PersonBundle\Export\Filter;
+
+use Chill\MainBundle\Export\FilterInterface;
+use Symfony\Component\Form\FormBuilderInterface;
+use Chill\PersonBundle\Entity\Person;
+use Doctrine\ORM\QueryBuilder;
+use Doctrine\ORM\Query\Expr;
+use Symfony\Component\Security\Core\Role\Role;
+use Chill\MainBundle\Export\ExportElementValidatedInterface;
+use Symfony\Component\Validator\Context\ExecutionContextInterface;
+use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
+use Symfony\Component\Translation\TranslatorInterface;
+
+/**
+ *
+ *
+ * @author Julien Fastré
+ */
+class GenderFilter implements FilterInterface,
+ ExportElementValidatedInterface
+{
+ /**
+ *
+ * @var TranslatorInterface
+ */
+ protected $translator;
+
+ function __construct(TranslatorInterface $translator)
+ {
+ $this->translator = $translator;
+ }
+
+ public function applyOn()
+ {
+ return 'person';
+ }
+
+ /**
+ *
+ */
+ public function buildForm(FormBuilderInterface $builder)
+ {
+ $builder->add('accepted_genders', ChoiceType::class, array(
+ 'choices' => array(
+ 'Woman' => Person::FEMALE_GENDER,
+ 'Man' => Person::MALE_GENDER,
+ 'Both' => Person::BOTH_GENDER,
+ 'Not given' => 'null'
+ ),
+ 'multiple' => true,
+ 'expanded' => true
+ ));
+ }
+
+ public function validateForm($data, ExecutionContextInterface $context)
+ {
+ if (!is_array($data['accepted_genders']) || count($data['accepted_genders']) === 0 ) {
+ $context->buildViolation("You should select an option")
+ ->addViolation();
+ }
+ }
+
+ public function alterQuery(QueryBuilder $qb, $data)
+ {
+ $where = $qb->getDQLPart('where');
+ $isIn = $qb->expr()->in('person.gender', ':person_gender');
+
+ if (!\in_array('null', $data['accepted_genders'])) {
+ $clause = $isIn;
+ } else {
+ $clause = $qb->expr()->orX($isIn, $qb->expr()->isNull('person.gender'));
+ }
+
+ if ($where instanceof Expr\Andx) {
+ $where->add($clause);
+ } else {
+ $where = $qb->expr()->andX($clause);
+ }
+
+ $qb->add('where', $where);
+ $qb->setParameter('person_gender', \array_filter(
+ $data['accepted_genders'],
+ function($el) {
+ return $el !== 'null';
+ }));
+ }
+
+ /**
+ * A title which will be used in the label for the form
+ *
+ * @return string
+ */
+ public function getTitle()
+ {
+ return 'Filter by person gender';
+ }
+
+ public function addRole()
+ {
+ return NULL;
+ }
+
+ public function describeAction($data, $format = 'string')
+ {
+ $genders = [];
+
+ foreach ($data['accepted_genders'] as $g) {
+ if ('null' === $g) {
+ $genders[] = $this->translator->trans('Not given');
+ } else {
+ $genders[] = $this->translator->trans($g);
+ }
+ }
+
+ return [
+ "Filtering by genders: only %genders%",
+ [ "%genders%" => \implode(", ", $genders)]
+ ];
+ }
+}
diff --git a/src/Bundle/ChillPerson/Export/Filter/NationalityFilter.php b/src/Bundle/ChillPerson/Export/Filter/NationalityFilter.php
new file mode 100644
index 000000000..d6dc52af8
--- /dev/null
+++ b/src/Bundle/ChillPerson/Export/Filter/NationalityFilter.php
@@ -0,0 +1,111 @@
+
+ *
+ * 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\PersonBundle\Export\Filter;
+
+use Symfony\Component\Form\FormBuilderInterface;
+use Doctrine\ORM\QueryBuilder;
+use Chill\MainBundle\Export\FilterInterface;
+use Doctrine\ORM\Query\Expr;
+use Chill\MainBundle\Templating\TranslatableStringHelper;
+use Doctrine\ORM\EntityRepository;
+use Chill\MainBundle\Entity\Country;
+use Chill\MainBundle\Export\ExportElementValidatedInterface;
+use Chill\MainBundle\Form\Type\Select2CountryType;
+use Symfony\Component\Validator\Context\ExecutionContextInterface;
+
+/**
+ *
+ *
+ * @author Julien Fastré
+ */
+class NationalityFilter implements FilterInterface,
+ ExportElementValidatedInterface
+{
+ /**
+ *
+ * @var TranslatableStringHelper
+ */
+ private $translatableStringHelper;
+
+ public function __construct(TranslatableStringHelper $helper)
+ {
+ $this->translatableStringHelper = $helper;
+ }
+
+ public function applyOn()
+ {
+ return 'person';
+ }
+
+ public function buildForm(FormBuilderInterface $builder)
+ {
+ $builder->add('nationalities', Select2CountryType::class, array(
+ 'placeholder' => 'Choose countries'
+ ));
+
+ }
+
+ public function validateForm($data, ExecutionContextInterface $context)
+ {
+ if ($data['nationalities'] === null) {
+ $context->buildViolation("A nationality must be selected")
+ ->addViolation();
+ }
+ }
+
+ public function alterQuery(QueryBuilder $qb, $data)
+ {
+ $where = $qb->getDQLPart('where');
+ $clause = $qb->expr()->in('person.nationality', ':person_nationality');
+
+ if ($where instanceof Expr\Andx) {
+ $where->add($clause);
+ } else {
+ $where = $qb->expr()->andX($clause);
+ }
+
+ $qb->add('where', $where);
+ $qb->setParameter('person_nationality', array($data['nationalities']));
+ }
+
+ public function getTitle()
+ {
+ return "Filter by person's nationality";
+ }
+
+ public function addRole()
+ {
+ return NULL;
+ }
+
+ public function describeAction($data, $format = 'string')
+ {
+ $countries = $data['nationalities'];
+
+ $names = array_map(function(Country $c) {
+ return $this->translatableStringHelper->localize($c->getName());
+ }, array($countries));
+
+ return array(
+ "Filtered by nationality : %nationalities%",
+ array('%nationalities%' => implode(", ", $names))
+ );
+ }
+}
diff --git a/src/Bundle/ChillPerson/Form/AccompanyingPeriodType.php b/src/Bundle/ChillPerson/Form/AccompanyingPeriodType.php
new file mode 100644
index 000000000..2de2011a1
--- /dev/null
+++ b/src/Bundle/ChillPerson/Form/AccompanyingPeriodType.php
@@ -0,0 +1,132 @@
+config = $config;
+ }
+
+ /**
+ * @param FormBuilderInterface $builder
+ * @param array $options
+ */
+ public function buildForm(FormBuilderInterface $builder, array $options)
+ {
+ //if the period_action is close, date opening should not be seen
+ if ($options['period_action'] !== 'close') {
+ $builder
+ ->add('openingDate', DateType::class, [
+ "required" => true,
+ 'widget' => 'single_text',
+ 'format' => 'dd-MM-yyyy'
+ ])
+ ;
+ }
+
+ // closingDate should be seen only if
+ // period_action = close
+ // OR ( period_action = update AND accompanying period is already closed )
+ $accompanyingPeriod = $options['data'];
+
+ if (
+ ($options['period_action'] === 'close')
+ OR
+ ($options['period_action'] === 'create')
+ OR
+ ($options['period_action'] === 'update' AND !$accompanyingPeriod->isOpen())
+ ) {
+
+ $builder->add('closingDate', DateType::class, [
+ 'required' => true,
+ 'widget' => 'single_text',
+ 'format' => 'dd-MM-yyyy'
+ ]);
+
+ $builder->add('closingMotive', ClosingMotivePickerType::class);
+ }
+
+ if ($this->config['user'] === 'visible') {
+ $builder->add('user', UserPickerType::class, [
+ 'center' => $options['center'],
+ 'role' => new Role(PersonVoter::SEE),
+ ]);
+ }
+
+ $builder->add('remark', TextareaType::class, [
+ 'required' => false
+ ]);
+ }
+
+ /**
+ * @param OptionsResolver $resolver
+ */
+ public function configureOptions(OptionsResolver $resolver)
+ {
+ $resolver->setDefaults([
+ 'data_class' => 'Chill\PersonBundle\Entity\AccompanyingPeriod'
+ ]);
+
+ $resolver
+ ->setRequired(['period_action'])
+ ->addAllowedTypes('period_action', 'string')
+ ->addAllowedValues('period_action', ['update', 'open', 'close', 'create'])
+ ->setRequired('center')
+ ->setAllowedTypes('center', Center::class)
+ ;
+ }
+
+ /**
+ * @param FormView $view
+ * @param FormInterface $form
+ * @param array $options
+ */
+ public function buildView(FormView $view, FormInterface $form, array $options)
+ {
+ $view->vars['action'] = $options['period_action'];
+ }
+
+ /**
+ * @return string
+ */
+ public function getBlockPrefix()
+ {
+ return 'chill_personbundle_accompanyingperiod';
+ }
+}
diff --git a/src/Bundle/ChillPerson/Form/ChoiceLoader/PersonChoiceLoader.php b/src/Bundle/ChillPerson/Form/ChoiceLoader/PersonChoiceLoader.php
new file mode 100644
index 000000000..b1cdfee7a
--- /dev/null
+++ b/src/Bundle/ChillPerson/Form/ChoiceLoader/PersonChoiceLoader.php
@@ -0,0 +1,138 @@
+
+ *
+ * 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\PersonBundle\Form\ChoiceLoader;
+
+use Symfony\Component\Form\ChoiceList\Loader\ChoiceLoaderInterface;
+use Symfony\Component\Form\ChoiceList\ChoiceListInterface;
+use Doctrine\ORM\EntityRepository;
+use Chill\PersonBundle\Entity\Person;
+
+/**
+ * Class PersonChoiceLoader
+ *
+ * @package Chill\PersonBundle\Form\ChoiceLoader
+ * @author Julien Fastré
+ */
+class PersonChoiceLoader implements ChoiceLoaderInterface
+{
+ /**
+ * @var EntityRepository
+ */
+ protected $personRepository;
+
+ /**
+ * @var array
+ */
+ protected $lazyLoadedPersons = [];
+
+ /**
+ * @var array
+ */
+ protected $centers = [];
+
+ /**
+ * PersonChoiceLoader constructor.
+ *
+ * @param EntityRepository $personRepository
+ * @param array|null $centers
+ */
+ public function __construct(
+ EntityRepository $personRepository,
+ array $centers = null
+ ) {
+ $this->personRepository = $personRepository;
+ if (NULL !== $centers) {
+ $this->centers = $centers;
+ }
+ }
+
+ /**
+ * @return bool
+ */
+ protected function hasCenterFilter()
+ {
+ return count($this->centers) > 0;
+ }
+
+ /**
+ * @param null $value
+ * @return ChoiceListInterface
+ */
+ public function loadChoiceList($value = null): ChoiceListInterface
+ {
+ $list = new \Symfony\Component\Form\ChoiceList\ArrayChoiceList(
+ $this->lazyLoadedPersons,
+ function(Person $p) use ($value) {
+ return \call_user_func($value, $p);
+ });
+
+ return $list;
+ }
+
+ /**
+ * @param array $values
+ * @param null $value
+ * @return array
+ */
+ public function loadChoicesForValues(array $values, $value = null)
+ {
+ $choices = [];
+
+ foreach($values as $value) {
+ if (empty($value)) {
+ continue;
+ }
+
+ $person = $this->personRepository->find($value);
+
+ if ($this->hasCenterFilter() &&
+ !\in_array($person->getCenter(), $this->centers)) {
+ throw new \RuntimeException("chosen a person not in correct center");
+ }
+
+ $choices[] = $person;
+ }
+
+ return $choices;
+ }
+
+ /**
+ * @param array $choices
+ * @param null $value
+ * @return array|string[]
+ */
+ public function loadValuesForChoices(array $choices, $value = null)
+ {
+ $values = [];
+
+ foreach ($choices as $choice) {
+ if (NULL === $choice) {
+ $values[] = null;
+ continue;
+ }
+
+ $id = \call_user_func($value, $choice);
+ $values[] = $id;
+ $this->lazyLoadedPersons[$id] = $choice;
+ }
+
+ return $values;
+ }
+}
diff --git a/src/Bundle/ChillPerson/Form/ClosingMotiveType.php b/src/Bundle/ChillPerson/Form/ClosingMotiveType.php
new file mode 100644
index 000000000..672fc4f19
--- /dev/null
+++ b/src/Bundle/ChillPerson/Form/ClosingMotiveType.php
@@ -0,0 +1,76 @@
+
+ *
+ * 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\PersonBundle\Form;
+
+use Symfony\Component\Form\AbstractType;
+use Symfony\Component\Form\FormBuilderInterface;
+use Symfony\Component\OptionsResolver\OptionsResolver;
+use Chill\PersonBundle\Entity\AccompanyingPeriod\ClosingMotive;
+use Chill\PersonBundle\Form\Type\ClosingMotivePickerType;
+use Chill\MainBundle\Form\Type\TranslatableStringFormType;
+use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
+use Symfony\Component\Form\Extension\Core\Type\NumberType;
+
+/**
+ * Class ClosingMotiveType
+ *
+ * @package Chill\PersonBundle\Form
+ */
+class ClosingMotiveType extends AbstractType
+{
+ /**
+ * @param FormBuilderInterface $builder
+ * @param array $options
+ */
+ public function buildForm(FormBuilderInterface $builder, array $options)
+ {
+ $builder
+ ->add('name', TranslatableStringFormType::class, [
+ 'label' => 'Nom'
+ ])
+ ->add('active', CheckboxType::class, [
+ 'label' => 'Actif ?',
+ 'required' => false
+ ])
+ ->add('ordering', NumberType::class, [
+ 'label' => 'Ordre d\'apparition',
+ 'required' => true,
+ 'scale' => 5
+ ])
+ ->add('parent', ClosingMotivePickerType::class, [
+ 'label' => 'Parent',
+ 'required' => false,
+ 'placeholder' => 'closing_motive.any parent',
+ 'multiple' => false,
+ 'only_leaf' => false
+ ])
+ ;
+ }
+
+ /**
+ * @param OptionsResolver $resolver
+ */
+ public function configureOptions(OptionsResolver $resolver)
+ {
+ $resolver
+ ->setDefault('class', ClosingMotive::class)
+ ;
+ }
+}
+
\ No newline at end of file
diff --git a/src/Bundle/ChillPerson/Form/CreationPersonType.php b/src/Bundle/ChillPerson/Form/CreationPersonType.php
new file mode 100644
index 000000000..ec3ddbf82
--- /dev/null
+++ b/src/Bundle/ChillPerson/Form/CreationPersonType.php
@@ -0,0 +1,159 @@
+
+ *
+ * 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\PersonBundle\Form;
+
+use Symfony\Component\Form\AbstractType;
+use Symfony\Component\Form\FormBuilderInterface;
+use Symfony\Component\OptionsResolver\OptionsResolver;
+use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToStringTransformer;
+use Symfony\Component\Form\Extension\Core\Type\HiddenType;
+use Symfony\Component\Form\Extension\Core\Type\DateType;
+use Chill\MainBundle\Form\Type\CenterType;
+use Chill\PersonBundle\Form\Type\GenderType;
+use Chill\MainBundle\Form\Type\DataTransformer\CenterTransformer;
+use Chill\PersonBundle\Config\ConfigPersonAltNamesHelper;
+use Chill\PersonBundle\Form\Type\PersonAltNameType;
+
+class CreationPersonType extends AbstractType
+{
+
+ const NAME = 'chill_personbundle_person_creation';
+
+ const FORM_NOT_REVIEWED = 'not_reviewed';
+ const FORM_REVIEWED = 'reviewed' ;
+ const FORM_BEING_REVIEWED = 'being_reviewed';
+
+ /**
+ *
+ * @var CenterTransformer
+ */
+ private $centerTransformer;
+
+ /**
+ *
+ * @var ConfigPersonAltNamesHelper
+ */
+ protected $configPersonAltNamesHelper;
+
+ public function __construct(
+ CenterTransformer $centerTransformer,
+ ConfigPersonAltNamesHelper $configPersonAltNamesHelper
+ ) {
+ $this->centerTransformer = $centerTransformer;
+ $this->configPersonAltNamesHelper = $configPersonAltNamesHelper;
+ }
+
+ /**
+ * @param FormBuilderInterface $builder
+ * @param array $options
+ */
+ public function buildForm(FormBuilderInterface $builder, array $options)
+ {
+ if ($options['form_status'] === self::FORM_BEING_REVIEWED) {
+
+ $dateToStringTransformer = new DateTimeToStringTransformer(
+ null, null, 'd-m-Y', false);
+
+ $builder->add('firstName', HiddenType::class)
+ ->add('lastName', HiddenType::class)
+ ->add('birthdate', HiddenType::class, array(
+ 'property_path' => 'birthdate'
+ ))
+ ->add('gender', HiddenType::class)
+ ->add('creation_date', HiddenType::class, array(
+ 'mapped' => false
+ ))
+ ->add('form_status', HiddenType::class, array(
+ 'mapped' => false,
+ 'data' => $options['form_status']
+ ))
+ ->add('center', HiddenType::class)
+ ;
+
+ if ($this->configPersonAltNamesHelper->hasAltNames()) {
+ $builder->add('altNames', PersonAltNameType::class, [
+ 'by_reference' => false,
+ 'force_hidden' => true
+ ]);
+ }
+
+ $builder->get('birthdate')
+ ->addModelTransformer($dateToStringTransformer);
+ $builder->get('creation_date')
+ ->addModelTransformer($dateToStringTransformer);
+ $builder->get('center')
+ ->addModelTransformer($this->centerTransformer);
+ } else {
+ $builder
+ ->add('firstName')
+ ->add('lastName')
+ ->add('birthdate', DateType::class, array('required' => false,
+ 'widget' => 'single_text', 'format' => 'dd-MM-yyyy'))
+ ->add('gender', GenderType::class, array(
+ 'required' => true, 'placeholder' => null
+ ))
+ ->add('creation_date', DateType::class, array(
+ 'required' => true,
+ 'widget' => 'single_text',
+ 'format' => 'dd-MM-yyyy',
+ 'mapped' => false,
+ 'data' => new \DateTime()))
+ ->add('form_status', HiddenType::class, array(
+ 'data' => $options['form_status'],
+ 'mapped' => false
+ ))
+ ->add('center', CenterType::class)
+ ;
+
+ if ($this->configPersonAltNamesHelper->hasAltNames()) {
+ $builder->add('altNames', PersonAltNameType::class, [
+ 'by_reference' => false
+ ]);
+ }
+ }
+ }
+
+ /**
+ * @param OptionsResolver $resolver
+ */
+ public function configureOptions(OptionsResolver $resolver)
+ {
+ $resolver->setDefaults(array(
+ 'data_class' => 'Chill\PersonBundle\Entity\Person'
+ ));
+
+ $resolver->setRequired('form_status')
+ ->setAllowedValues('form_status', array(
+ self::FORM_BEING_REVIEWED,
+ self::FORM_NOT_REVIEWED,
+ self::FORM_REVIEWED
+ ));
+ }
+
+ /**
+ * @return string
+ */
+ public function getBlockPrefix()
+ {
+ return self::NAME;
+ }
+}
diff --git a/src/Bundle/ChillPerson/Form/DataMapper/PersonAltNameDataMapper.php b/src/Bundle/ChillPerson/Form/DataMapper/PersonAltNameDataMapper.php
new file mode 100644
index 000000000..d1f50404b
--- /dev/null
+++ b/src/Bundle/ChillPerson/Form/DataMapper/PersonAltNameDataMapper.php
@@ -0,0 +1,87 @@
+getIterator() as $key => $altName) {
+ /** @var PersonAltName $altName */
+ $mapIndexToKey[$altName->getKey()] = $key;
+ }
+
+ foreach ($forms as $key => $form) {
+ if (\array_key_exists($key, $mapIndexToKey)) {
+ $form->setData($viewData->get($mapIndexToKey[$key])->getLabel());
+ }
+ }
+ }
+
+ /**
+ *
+ * @param FormInterface[] $forms
+ * @param Collection $viewData
+ */
+ public function mapFormsToData($forms, &$viewData)
+ {
+ $mapIndexToKey = [];
+
+ if (is_array($viewData)) {
+ $dataIterator = $viewData;
+ } else {
+ $dataIterator = $viewData instanceof ArrayCollection ?
+ $viewData->toArray() : $viewData->getIterator();
+ }
+
+ foreach ($dataIterator as $key => $altName) {
+ /** @var PersonAltName $altName */
+ $mapIndexToKey[$altName->getKey()] = $key;
+ }
+
+ foreach ($forms as $key => $form) {
+ $isEmpty = empty($form->getData());
+
+ if (\array_key_exists($key, $mapIndexToKey)) {
+ if ($isEmpty) {
+ $viewData->remove($mapIndexToKey[$key]);
+ } else {
+ $viewData->get($mapIndexToKey[$key])->setLabel($form->getData());
+ }
+ } else {
+ if (!$isEmpty) {
+ $altName = (new PersonAltName())
+ ->setKey($key)
+ ->setLabel($form->getData())
+ ;
+
+ if (is_array($viewData)) {
+ $viewData[] = $altName;
+ } else {
+ $viewData->add($altName);
+ }
+ }
+ }
+ }
+
+ }
+}
diff --git a/src/Bundle/ChillPerson/Form/DataTransformer/PersonToIdTransformer.php b/src/Bundle/ChillPerson/Form/DataTransformer/PersonToIdTransformer.php
new file mode 100644
index 000000000..2561019d5
--- /dev/null
+++ b/src/Bundle/ChillPerson/Form/DataTransformer/PersonToIdTransformer.php
@@ -0,0 +1,70 @@
+om = $om;
+ }
+
+ /**
+ * Transforms an object (issue) to a string (id).
+ *
+ * @param Person|null $issue
+ * @return string
+ */
+ public function transform($issue)
+ {
+ if (null === $issue) {
+ return "";
+ }
+
+ return $issue->getId();
+ }
+
+ /**
+ * Transforms a string (id) to an object (issue).
+ *
+ * @param string $id
+ *
+ * @return Person|null
+ *
+ * @throws TransformationFailedException if object (issue) is not found.
+ */
+ public function reverseTransform($id)
+ {
+ if (!$id) {
+ return null;
+ }
+
+ $issue = $this->om
+ ->getRepository('ChillPersonBundle:Person')
+ ->findOneBy(array('id' => $id))
+ ;
+
+ if (null === $issue) {
+ throw new TransformationFailedException(sprintf(
+ 'An issue with id "%s" does not exist!',
+ $id
+ ));
+ }
+
+ return $issue;
+ }
+}
+?>
\ No newline at end of file
diff --git a/src/Bundle/ChillPerson/Form/MaritalStatusType.php b/src/Bundle/ChillPerson/Form/MaritalStatusType.php
new file mode 100644
index 000000000..1897c7dc4
--- /dev/null
+++ b/src/Bundle/ChillPerson/Form/MaritalStatusType.php
@@ -0,0 +1,45 @@
+add('id', TextType::class, [
+ 'label' => 'Identifiant'
+ ])
+ ->add('name', TranslatableStringFormType::class, [
+ 'label' => 'Nom'
+ ])
+ ;
+ }
+
+ /**
+ * @param OptionsResolver $resolver
+ */
+ public function configureOptions(OptionsResolver $resolver)
+ {
+ $resolver
+ ->setDefault('class', MaritalStatus::class)
+ ;
+ }
+}
\ No newline at end of file
diff --git a/src/Bundle/ChillPerson/Form/PersonType.php b/src/Bundle/ChillPerson/Form/PersonType.php
new file mode 100644
index 000000000..7cf24a836
--- /dev/null
+++ b/src/Bundle/ChillPerson/Form/PersonType.php
@@ -0,0 +1,175 @@
+
+ *
+ * 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\PersonBundle\Form;
+
+use Symfony\Component\Form\AbstractType;
+use Symfony\Component\Form\FormBuilderInterface;
+use Symfony\Component\OptionsResolver\OptionsResolver;
+use Symfony\Component\Form\Extension\Core\Type\TextareaType;
+use Symfony\Component\Form\Extension\Core\Type\TextType;
+use Symfony\Component\Form\Extension\Core\Type\DateType;
+use Symfony\Component\Form\Extension\Core\Type\EmailType;
+use Symfony\Component\Form\Extension\Core\Type\TelType;
+use Chill\PersonBundle\Form\Type\GenderType;
+use Chill\MainBundle\Form\Type\Select2CountryType;
+use Chill\MainBundle\Form\Type\Select2LanguageType;
+use Chill\CustomFieldsBundle\Form\Type\CustomFieldType;
+use Chill\PersonBundle\Form\Type\Select2MaritalStatusType;
+use Chill\PersonBundle\Config\ConfigPersonAltNamesHelper;
+use Chill\PersonBundle\Form\Type\PersonAltNameType;
+
+class PersonType extends AbstractType
+{
+ /**
+ * array of configuration for person_fields.
+ *
+ * Contains whether we should add fields some optional fields (optional per
+ * instance)
+ *
+ * @var string[]
+ */
+ protected $config = array();
+
+ /**
+ *
+ * @var ConfigPersonAltNamesHelper
+ */
+ protected $configAltNamesHelper;
+
+ /**
+ *
+ * @param string[] $personFieldsConfiguration configuration of visibility of some fields
+ */
+ public function __construct(
+ array $personFieldsConfiguration,
+ ConfigPersonAltNamesHelper $configAltNamesHelper
+ ) {
+ $this->config = $personFieldsConfiguration;
+ $this->configAltNamesHelper = $configAltNamesHelper;
+ }
+
+ /**
+ * @param FormBuilderInterface $builder
+ * @param array $options
+ */
+ public function buildForm(FormBuilderInterface $builder, array $options)
+ {
+ $builder
+ ->add('firstName')
+ ->add('lastName')
+ ->add('birthdate', DateType::class, array('required' => false, 'widget' => 'single_text', 'format' => 'dd-MM-yyyy'))
+ ->add('gender', GenderType::class, array(
+ 'required' => true
+ ));
+
+ if ($this->configAltNamesHelper->hasAltNames()) {
+ $builder->add('altNames', PersonAltNameType::class, [
+ 'by_reference' => false
+ ]);
+ }
+
+ if ($this->config['memo'] === 'visible') {
+ $builder
+ ->add('memo', TextareaType::class, array('required' => false))
+ ;
+ }
+
+ if ($this->config['place_of_birth'] === 'visible') {
+ $builder->add('placeOfBirth', TextType::class, array('required' => false));
+ }
+
+ if ($this->config['contact_info'] === 'visible') {
+ $builder->add('contactInfo', TextareaType::class, array('required' => false));
+ }
+
+ if ($this->config['phonenumber'] === 'visible') {
+ $builder->add('phonenumber', TelType::class, array('required' => false));
+ }
+
+ if ($this->config['mobilenumber'] === 'visible') {
+ $builder->add('mobilenumber', TelType::class, array('required' => false));
+ }
+
+ if ($this->config['email'] === 'visible') {
+ $builder->add('email', EmailType::class, array('required' => false));
+ }
+
+ if ($this->config['country_of_birth'] === 'visible') {
+ $builder->add('countryOfBirth', Select2CountryType::class, array(
+ 'required' => false
+ ));
+ }
+
+ if ($this->config['nationality'] === 'visible') {
+ $builder->add('nationality', Select2CountryType::class, array(
+ 'required' => false
+ ));
+ }
+
+ if ($this->config['spoken_languages'] === 'visible') {
+ $builder->add('spokenLanguages', Select2LanguageType::class, array(
+ 'required' => false,
+ 'multiple' => true
+ ));
+ }
+
+ if ($this->config['marital_status'] === 'visible'){
+ $builder->add('maritalStatus', Select2MaritalStatusType::class, array(
+ 'required' => false
+ ));
+ }
+
+ if($options['cFGroup']) {
+ $builder
+ ->add('cFData', CustomFieldType::class,
+ array('attr' => array('class' => 'cf-fields'), 'group' => $options['cFGroup']))
+ ;
+ }
+ }
+
+ /**
+ * @param OptionsResolverInterface $resolver
+ */
+ public function configureOptions(OptionsResolver $resolver)
+ {
+ $resolver->setDefaults(array(
+ 'data_class' => 'Chill\PersonBundle\Entity\Person',
+ 'validation_groups' => array('general', 'creation')
+ ));
+
+ $resolver->setRequired(array(
+ 'cFGroup'
+ ));
+
+ $resolver->setAllowedTypes(
+ 'cFGroup', array('null', 'Chill\CustomFieldsBundle\Entity\CustomFieldsGroup')
+ );
+ }
+
+ /**
+ * @return string
+ */
+ public function getBlockPrefix()
+ {
+ return 'chill_personbundle_person';
+ }
+}
diff --git a/src/Bundle/ChillPerson/Form/Type/ClosingMotivePickerType.php b/src/Bundle/ChillPerson/Form/Type/ClosingMotivePickerType.php
new file mode 100644
index 000000000..aa7e47583
--- /dev/null
+++ b/src/Bundle/ChillPerson/Form/Type/ClosingMotivePickerType.php
@@ -0,0 +1,98 @@
+translatableStringHelper = $translatableStringHelper;
+ $this->entityRenderExtension = $chillEntityRenderExtension;
+ $this->repository = $closingMotiveRepository;
+ }
+
+ /**
+ * @return string
+ */
+ public function getBlockPrefix()
+ {
+ return 'closing_motive';
+ }
+
+ /**
+ * @return null|string
+ */
+ public function getParent()
+ {
+ return EntityType::class;
+ }
+
+ /**
+ * @param OptionsResolver $resolver
+ */
+ public function configureOptions(OptionsResolver $resolver)
+ {
+
+ $resolver->setDefaults([
+ 'class' => ClosingMotive::class,
+ 'empty_data' => null,
+ 'placeholder' => 'Choose a motive',
+ 'choice_label' => function(ClosingMotive $cm) {
+ return $this->entityRenderExtension->renderString($cm);
+ },
+ 'only_leaf' => true
+ ]);
+
+ $resolver
+ ->setAllowedTypes('only_leaf', 'bool')
+ ->setNormalizer('choices', function (Options $options) {
+ return $this->repository
+ ->getActiveClosingMotive($options['only_leaf']);
+ })
+ ;
+ }
+
+}
diff --git a/src/Bundle/ChillPerson/Form/Type/GenderType.php b/src/Bundle/ChillPerson/Form/Type/GenderType.php
new file mode 100644
index 000000000..bdd31e899
--- /dev/null
+++ b/src/Bundle/ChillPerson/Form/Type/GenderType.php
@@ -0,0 +1,38 @@
+ Person::MALE_GENDER,
+ Person::FEMALE_GENDER => Person::FEMALE_GENDER,
+ Person::BOTH_GENDER => Person::BOTH_GENDER
+ );
+
+ $resolver->setDefaults(array(
+ 'choices' => $a,
+ 'expanded' => true,
+ 'multiple' => false,
+ 'placeholder' => null
+ ));
+ }
+
+}
diff --git a/src/Bundle/ChillPerson/Form/Type/PersonAltNameType.php b/src/Bundle/ChillPerson/Form/Type/PersonAltNameType.php
new file mode 100644
index 000000000..1ba58e86e
--- /dev/null
+++ b/src/Bundle/ChillPerson/Form/Type/PersonAltNameType.php
@@ -0,0 +1,76 @@
+configHelper = $configHelper;
+ $this->translatableStringHelper = $translatableStringHelper;
+ }
+
+ public function buildForm(FormBuilderInterface $builder, array $options)
+ {
+ foreach ($this->getKeyChoices() as $label => $key) {
+ $builder->add(
+ $key,
+ $options['force_hidden'] ? HiddenType::class : TextType::class, [
+ 'label' => $label,
+ 'required' => false
+ ]);
+ }
+
+ $builder->setDataMapper(new \Chill\PersonBundle\Form\DataMapper\PersonAltNameDataMapper());
+ }
+
+ protected function getKeyChoices()
+ {
+ $choices = $this->configHelper->getChoices();
+ $translatedChoices = [];
+
+ foreach ($choices as $key => $labels) {
+ $label = $this->translatableStringHelper->localize($labels);
+ $translatedChoices[$label] = $key;
+ }
+
+ return $translatedChoices;
+ }
+
+ public function configureOptions(OptionsResolver $resolver)
+ {
+ $resolver
+ ->setDefault('class', \Chill\PersonBundle\Entity\PersonAltName::class)
+ ->setDefined('force_hidden')
+ ->setAllowedTypes('force_hidden', 'bool')
+ ->setDefault('force_hidden', false)
+ ;
+ }
+
+}
diff --git a/src/Bundle/ChillPerson/Form/Type/PickPersonType.php b/src/Bundle/ChillPerson/Form/Type/PickPersonType.php
new file mode 100644
index 000000000..839c84b69
--- /dev/null
+++ b/src/Bundle/ChillPerson/Form/Type/PickPersonType.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\PersonBundle\Form\Type;
+
+use Symfony\Component\Form\AbstractType;
+use Symfony\Component\Form\FormBuilderInterface;
+use Symfony\Component\OptionsResolver\OptionsResolver;
+use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
+use Symfony\Component\Security\Core\Exception\AccessDeniedException;
+use Symfony\Component\Security\Core\Role\Role;
+use Symfony\Bridge\Doctrine\Form\Type\EntityType;
+use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
+use Chill\MainBundle\Entity\GroupCenter;
+use Chill\PersonBundle\Entity\Person;
+use Chill\MainBundle\Security\Authorization\AuthorizationHelper;
+use Chill\MainBundle\Entity\Center;
+use Chill\PersonBundle\Repository\PersonRepository;
+use Chill\PersonBundle\Search\PersonSearch;
+use Symfony\Component\Translation\TranslatorInterface;
+use Chill\PersonBundle\Form\ChoiceLoader\PersonChoiceLoader;
+use Symfony\Component\OptionsResolver\Options;
+
+/**
+ * This type allow to pick a person.
+ *
+ * The form is embedded in a select2 input.
+ *
+ * The people may be filtered :
+ *
+ * - with the `centers` option, only the people associated with the given center(s)
+ * are seen. May be an instance of `Chill\MainBundle\Entity\Center`, or an array of
+ * `Chill\MainBundle\Entity\Center`. By default, all the reachable centers as selected.
+ * - with the `role` option, only the people belonging to the reachable center for the
+ * given role are displayed.
+ *
+ *
+ * @author Julien Fastré
+ */
+class PickPersonType extends AbstractType
+{
+ /**
+ * @var PersonRepository
+ */
+ protected $personRepository;
+
+ /**
+ *
+ * @var \Chill\MainBundle\Entity\User
+ */
+ protected $user;
+
+ /**
+ *
+ * @var AuthorizationHelper
+ */
+ protected $authorizationHelper;
+
+ /**
+ *
+ * @var UrlGeneratorInterface
+ */
+ protected $urlGenerator;
+
+ /**
+ *
+ * @var TranslatorInterface
+ */
+ protected $translator;
+
+ public function __construct(
+ PersonRepository $personRepository,
+ TokenStorageInterface $tokenStorage,
+ AuthorizationHelper $authorizationHelper,
+ UrlGeneratorInterface $urlGenerator,
+ TranslatorInterface $translator
+ )
+ {
+ $this->personRepository = $personRepository;
+ $this->user = $tokenStorage->getToken()->getUser();
+ $this->authorizationHelper = $authorizationHelper;
+ $this->urlGenerator = $urlGenerator;
+ $this->translator = $translator;
+ }
+
+ protected function filterCentersfom(Options $options)
+ {
+ if ($options['role'] === NULL) {
+ $centers = array_map(function (GroupCenter $g) {
+
+ return $g->getCenter();
+ }, $this->user->getGroupCenters()->toArray());
+ } else {
+ $centers = $this->authorizationHelper
+ ->getReachableCenters($this->user, $options['role']);
+ }
+
+ if ($options['centers'] === NULL) {
+ // we select all selected centers
+ $selectedCenters = $centers;
+ } else {
+ $selectedCenters = array();
+ $optionsCenters = is_array($options['centers']) ?
+ $options['centers'] : array($options['centers']);
+
+ foreach ($optionsCenters as $c) {
+ // check that every member of the array is a center
+ if (!$c instanceof Center) {
+ throw new \RuntimeException('Every member of the "centers" '
+ . 'option must be an instance of '.Center::class);
+ }
+ if (!in_array($c->getId(), array_map(
+ function(Center $c) { return $c->getId();},
+ $centers))) {
+ throw new AccessDeniedException('The given center is not reachable');
+ }
+ $selectedCenters[] = $c;
+ }
+ }
+
+ return $selectedCenters;
+ }
+
+ public function configureOptions(OptionsResolver $resolver)
+ {
+ parent::configureOptions($resolver);
+
+ // add the possibles options for this type
+ $resolver->setDefined('centers')
+ ->addAllowedTypes('centers', array('array', Center::class, 'null'))
+ ->setDefault('centers', null)
+ ->setDefined('role')
+ ->addAllowedTypes('role', array(Role::class, 'null'))
+ ->setDefault('role', null)
+ ;
+
+ // add the default options
+ $resolver->setDefaults(array(
+ 'class' => Person::class,
+ 'choice_label' => function(Person $p) {
+ return $p->getFirstname().' '.$p->getLastname();
+ },
+ 'placeholder' => 'Pick a person',
+ 'choice_attr' => function(Person $p) {
+ return array(
+ 'data-center' => $p->getCenter()->getId()
+ );
+ },
+ 'attr' => array('class' => 'select2 '),
+ 'choice_loader' => function(Options $options) {
+ $centers = $this->filterCentersfom($options);
+
+ return new PersonChoiceLoader($this->personRepository, $centers);
+ }
+ ));
+ }
+
+ public function getParent()
+ {
+ return EntityType::class;
+ }
+
+ public function buildView(\Symfony\Component\Form\FormView $view, \Symfony\Component\Form\FormInterface $form, array $options)
+ {
+ $view->vars['attr']['data-person-picker'] = true;
+ $view->vars['attr']['data-select-interactive-loading'] = true;
+ $view->vars['attr']['data-search-url'] = $this->urlGenerator
+ ->generate('chill_main_search', [ 'name' => PersonSearch::NAME, '_format' => 'json' ]);
+ $view->vars['attr']['data-placeholder'] = $this->translator->trans($options['placeholder']);
+ $view->vars['attr']['data-no-results-label'] = $this->translator->trans('select2.no_results');
+ $view->vars['attr']['data-error-load-label'] = $this->translator->trans('select2.error_loading');
+ $view->vars['attr']['data-searching-label'] = $this->translator->trans('select2.searching');
+ }
+
+}
diff --git a/src/Bundle/ChillPerson/Form/Type/Select2MaritalStatusType.php b/src/Bundle/ChillPerson/Form/Type/Select2MaritalStatusType.php
new file mode 100644
index 000000000..0cff8ee51
--- /dev/null
+++ b/src/Bundle/ChillPerson/Form/Type/Select2MaritalStatusType.php
@@ -0,0 +1,81 @@
+
+ *
+ * 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\PersonBundle\Form\Type;
+
+use Symfony\Component\Form\AbstractType;
+use Symfony\Component\OptionsResolver\OptionsResolver;
+use Chill\MainBundle\Form\Type\DataTransformer\ObjectToIdTransformer;
+use Symfony\Component\Form\FormBuilderInterface;
+use Symfony\Component\HttpFoundation\RequestStack;
+use Doctrine\Persistence\ObjectManager;
+use Chill\MainBundle\Form\Type\Select2ChoiceType;
+
+/**
+ * A type to select the marital status
+ *
+ * @author Champs-Libres COOP
+ */
+class Select2MaritalStatusType extends AbstractType
+{
+ /** @var RequestStack */
+ private $requestStack;
+
+ /** @var ObjectManager */
+ private $em;
+
+ public function __construct(RequestStack $requestStack,ObjectManager $em)
+ {
+ $this->requestStack = $requestStack;
+ $this->em = $em;
+ }
+
+ public function getBlockPrefix() {
+ return 'select2_chill_marital_status';
+ }
+
+ public function getParent() {
+ return Select2ChoiceType::class;
+ }
+
+ public function buildForm(FormBuilderInterface $builder, array $options)
+ {
+ $transformer = new ObjectToIdTransformer($this->em,'Chill\PersonBundle\Entity\MaritalStatus');
+ $builder->addModelTransformer($transformer);
+ }
+
+ public function configureOptions(OptionsResolver $resolver)
+ {
+ $locale = $this->requestStack->getCurrentRequest()->getLocale();
+ $maritalStatuses = $this->em->getRepository('Chill\PersonBundle\Entity\MaritalStatus')->findAll();
+ $choices = array();
+
+ foreach ($maritalStatuses as $ms) {
+ $choices[$ms->getId()] = $ms->getName()[$locale];
+ }
+
+ asort($choices, SORT_STRING | SORT_FLAG_CASE);
+
+ $resolver->setDefaults(array(
+ 'class' => 'Chill\PersonBundle\Entity\MaritalStatus',
+ 'choices' => array_combine(array_values($choices),array_keys($choices))
+ ));
+ }
+}
diff --git a/src/Bundle/ChillPerson/LICENSE b/src/Bundle/ChillPerson/LICENSE
new file mode 100644
index 000000000..2def0e883
--- /dev/null
+++ b/src/Bundle/ChillPerson/LICENSE
@@ -0,0 +1,661 @@
+ GNU AFFERO GENERAL PUBLIC LICENSE
+ Version 3, 19 November 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc.
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The GNU Affero General Public License is a free, copyleft license for
+software and other kinds of works, specifically designed to ensure
+cooperation with the community in the case of network server software.
+
+ The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+our General Public Licenses are intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+ Developers that use our General Public Licenses protect your rights
+with two steps: (1) assert copyright on the software, and (2) offer
+you this License which gives you legal permission to copy, distribute
+and/or modify the software.
+
+ A secondary benefit of defending all users' freedom is that
+improvements made in alternate versions of the program, if they
+receive widespread use, become available for other developers to
+incorporate. Many developers of free software are heartened and
+encouraged by the resulting cooperation. However, in the case of
+software used on network servers, this result may fail to come about.
+The GNU General Public License permits making a modified version and
+letting the public access it on a server without ever releasing its
+source code to the public.
+
+ The GNU Affero General Public License is designed specifically to
+ensure that, in such cases, the modified source code becomes available
+to the community. It requires the operator of a network server to
+provide the source code of the modified version running there to the
+users of that server. Therefore, public use of a modified version, on
+a publicly accessible server, gives the public access to the source
+code of the modified version.
+
+ An older license, called the Affero General Public License and
+published by Affero, was designed to accomplish similar goals. This is
+a different license, not a version of the Affero GPL, but Affero has
+released a new version of the Affero GPL which permits relicensing under
+this license.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ TERMS AND CONDITIONS
+
+ 0. Definitions.
+
+ "This License" refers to version 3 of the GNU Affero General Public License.
+
+ "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+ "The Program" refers to any copyrightable work licensed under this
+License. Each licensee is addressed as "you". "Licensees" and
+"recipients" may be individuals or organizations.
+
+ To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy. The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+ A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+ To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy. Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+ To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies. Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+ An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License. If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+ 1. Source Code.
+
+ The "source code" for a work means the preferred form of the work
+for making modifications to it. "Object code" means any non-source
+form of a work.
+
+ A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+ The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form. A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+ The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities. However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work. For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+ The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+ The Corresponding Source for a work in source code form is that
+same work.
+
+ 2. Basic Permissions.
+
+ All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met. This License explicitly affirms your unlimited
+permission to run the unmodified Program. The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work. This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+ You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force. You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright. Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+ Conveying under any other circumstances is permitted solely under
+the conditions stated below. Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+ No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+ When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+ 4. Conveying Verbatim Copies.
+
+ You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+ You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+ 5. Conveying Modified Source Versions.
+
+ You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+ a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.
+
+ b) The work must carry prominent notices stating that it is
+ released under this License and any conditions added under section
+ 7. This requirement modifies the requirement in section 4 to
+ "keep intact all notices".
+
+ c) You must license the entire work, as a whole, under this
+ License to anyone who comes into possession of a copy. This
+ License will therefore apply, along with any applicable section 7
+ additional terms, to the whole of the work, and all its parts,
+ regardless of how they are packaged. This License gives no
+ permission to license the work in any other way, but it does not
+ invalidate such permission if you have separately received it.
+
+ d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your
+ work need not make them do so.
+
+ A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit. Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+ 6. Conveying Non-Source Forms.
+
+ You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+ a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium
+ customarily used for software interchange.
+
+ b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a
+ written offer, valid for at least three years and valid for as
+ long as you offer spare parts or customer support for that product
+ model, to give anyone who possesses the object code either (1) a
+ copy of the Corresponding Source for all the software in the
+ product that is covered by this License, on a durable physical
+ medium customarily used for software interchange, for a price no
+ more than your reasonable cost of physically performing this
+ conveying of source, or (2) access to copy the
+ Corresponding Source from a network server at no charge.
+
+ c) Convey individual copies of the object code with a copy of the
+ written offer to provide the Corresponding Source. This
+ alternative is allowed only occasionally and noncommercially, and
+ only if you received the object code with such an offer, in accord
+ with subsection 6b.
+
+ d) Convey the object code by offering access from a designated
+ place (gratis or for a charge), and offer equivalent access to the
+ Corresponding Source in the same way through the same place at no
+ further charge. You need not require recipients to copy the
+ Corresponding Source along with the object code. If the place to
+ copy the object code is a network server, the Corresponding Source
+ may be on a different server (operated by you or a third party)
+ that supports equivalent copying facilities, provided you maintain
+ clear directions next to the object code saying where to find the
+ Corresponding Source. Regardless of what server hosts the
+ Corresponding Source, you remain obligated to ensure that it is
+ available for as long as needed to satisfy these requirements.
+
+ e) Convey the object code using peer-to-peer transmission, provided
+ you inform other peers where the object code and Corresponding
+ Source of the work are being offered to the general public at no
+ charge under subsection 6d.
+
+ A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+ A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling. In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage. For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product. A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+ "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source. The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+ If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information. But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+ The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed. Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+ Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+ 7. Additional Terms.
+
+ "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law. If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+ When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it. (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.) You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+ Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+ a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or
+
+ b) Requiring preservation of specified reasonable legal notices or
+ author attributions in that material or in the Appropriate Legal
+ Notices displayed by works containing it; or
+
+ c) Prohibiting misrepresentation of the origin of that material, or
+ requiring that modified versions of such material be marked in
+ reasonable ways as different from the original version; or
+
+ d) Limiting the use for publicity purposes of names of licensors or
+ authors of the material; or
+
+ e) Declining to grant rights under trademark law for use of some
+ trade names, trademarks, or service marks; or
+
+ f) Requiring indemnification of licensors and authors of that
+ material by anyone who conveys the material (or modified versions of
+ it) with contractual assumptions of liability to the recipient, for
+ any liability that these contractual assumptions directly impose on
+ those licensors and authors.
+
+ All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10. If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term. If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+ If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+ Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+ 8. Termination.
+
+ You may not propagate or modify a covered work except as expressly
+provided under this License. Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+ However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+ Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+ Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License. If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+ 9. Acceptance Not Required for Having Copies.
+
+ You are not required to accept this License in order to receive or
+run a copy of the Program. Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance. However,
+nothing other than this License grants you permission to propagate or
+modify any covered work. These actions infringe copyright if you do
+not accept this License. Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+ 10. Automatic Licensing of Downstream Recipients.
+
+ Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License. You are not responsible
+for enforcing compliance by third parties with this License.
+
+ An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations. If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+ You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License. For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+ 11. Patents.
+
+ A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based. The
+work thus licensed is called the contributor's "contributor version".
+
+ A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version. For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+ In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement). To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+ If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients. "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+ If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+ A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License. You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+ Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+ 12. No Surrender of Others' Freedom.
+
+ If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all. For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+ 13. Remote Network Interaction; Use with the GNU General Public License.
+
+ Notwithstanding any other provision of this License, if you modify the
+Program, your modified version must prominently offer all users
+interacting with it remotely through a computer network (if your version
+supports such interaction) an opportunity to receive the Corresponding
+Source of your version by providing access to the Corresponding Source
+from a network server at no charge, through some standard or customary
+means of facilitating copying of software. This Corresponding Source
+shall include the Corresponding Source for any work covered by version 3
+of the GNU General Public License that is incorporated pursuant to the
+following paragraph.
+
+ Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU General Public License into a single
+combined work, and to convey the resulting work. The terms of this
+License will continue to apply to the part which is the covered work,
+but the work with which it is combined will remain governed by version
+3 of the GNU General Public License.
+
+ 14. Revised Versions of this License.
+
+ The Free Software Foundation may publish revised and/or new versions of
+the GNU Affero General Public License from time to time. Such new versions
+will be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Program specifies that a certain numbered version of the GNU Affero General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation. If the Program does not specify a version number of the
+GNU Affero General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+ If the Program specifies that a proxy can decide which future
+versions of the GNU Affero General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+ Later license versions may give you additional or different
+permissions. However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+ 15. Disclaimer of Warranty.
+
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. Limitation of Liability.
+
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+ 17. Interpretation of Sections 15 and 16.
+
+ If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+
+ Copyright (C)
+
+ 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 .
+
+Also add information on how to contact you by electronic and paper mail.
+
+ If your software can interact with users remotely through a computer
+network, you should also make sure that it provides a way for users to
+get its source. For example, if your program is a web application, its
+interface could display a "Source" link that leads users to an archive
+of the code. There are many ways you could offer source, and different
+solutions will be better for different programs; see section 13 for the
+specific requirements.
+
+ You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU AGPL, see
+.
\ No newline at end of file
diff --git a/src/Bundle/ChillPerson/Menu/AdminMenuBuilder.php b/src/Bundle/ChillPerson/Menu/AdminMenuBuilder.php
new file mode 100644
index 000000000..e8a73d2ab
--- /dev/null
+++ b/src/Bundle/ChillPerson/Menu/AdminMenuBuilder.php
@@ -0,0 +1,61 @@
+
+ *
+ * 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\PersonBundle\Menu;
+
+use Chill\MainBundle\Routing\LocalMenuBuilderInterface;
+use Knp\Menu\MenuItem;
+use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
+use Chill\PersonBundle\Security\Authorization\PersonVoter;
+
+/**
+ *
+ *
+ */
+class AdminMenuBuilder implements LocalMenuBuilderInterface
+{
+ /**
+ *
+ * @var AuthorizationCheckerInterface
+ */
+ protected $authorizationChecker;
+
+ public function __construct(AuthorizationCheckerInterface $authorizationChecker)
+ {
+ $this->authorizationChecker = $authorizationChecker;
+ }
+
+
+ public function buildMenu($menuId, MenuItem $menu, array $parameters)
+ {
+ if (!$this->authorizationChecker->isGranted('ROLE_ADMIN')) {
+ return;
+ }
+
+ $menu->addChild('Person', [
+ 'route' => 'chill_person_admin'
+ ])
+ ->setExtras([
+ 'order' => 20
+ ]);
+ }
+
+ public static function getMenuIds(): array
+ {
+ return [ 'admin_section' ];
+ }
+}
diff --git a/src/Bundle/ChillPerson/Menu/PersonMenuBuilder.php b/src/Bundle/ChillPerson/Menu/PersonMenuBuilder.php
new file mode 100644
index 000000000..3a757d898
--- /dev/null
+++ b/src/Bundle/ChillPerson/Menu/PersonMenuBuilder.php
@@ -0,0 +1,84 @@
+
+ *
+ * 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\PersonBundle\Menu;
+
+use Chill\MainBundle\Routing\LocalMenuBuilderInterface;
+use Knp\Menu\MenuItem;
+use Symfony\Contracts\Translation\TranslatorInterface;
+
+/**
+ * Add menu entrie to person menu.
+ *
+ * Menu entries added :
+ *
+ * - person details ;
+ * - accompanying period (if `visible`)
+ *
+ */
+class PersonMenuBuilder implements LocalMenuBuilderInterface
+{
+ /**
+ *
+ * @var string 'visible' or 'hidden'
+ */
+ protected $showAccompanyingPeriod;
+
+ /**
+ *
+ * @var TranslatorInterface
+ */
+ protected $translator;
+
+ public function __construct(
+ $showAccompanyingPeriod,
+ TranslatorInterface $translator
+ ) {
+ $this->showAccompanyingPeriod = $showAccompanyingPeriod;
+ $this->translator = $translator;
+ }
+
+ public function buildMenu($menuId, MenuItem $menu, array $parameters)
+ {
+ $menu->addChild($this->translator->trans('Person details'), [
+ 'route' => 'chill_person_view',
+ 'routeParameters' => [
+ 'person_id' => $parameters['person']->getId()
+ ]
+ ])
+ ->setExtras([
+ 'order' => 50
+ ]);
+
+ if ($this->showAccompanyingPeriod === 'visible') {
+ $menu->addChild($this->translator->trans('Accompanying period list'), [
+ 'route' => 'chill_person_accompanying_period_list',
+ 'routeParameters' => [
+ 'person_id' => $parameters['person']->getId()
+ ]
+ ])
+ ->setExtras([
+ 'order' => 100
+ ]);
+ }
+ }
+
+ public static function getMenuIds(): array
+ {
+ return [ 'person' ];
+ }
+}
diff --git a/src/Bundle/ChillPerson/Menu/SectionMenuBuilder.php b/src/Bundle/ChillPerson/Menu/SectionMenuBuilder.php
new file mode 100644
index 000000000..ea6a1d060
--- /dev/null
+++ b/src/Bundle/ChillPerson/Menu/SectionMenuBuilder.php
@@ -0,0 +1,83 @@
+
+ *
+ * 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\PersonBundle\Menu;
+
+use Chill\MainBundle\Routing\LocalMenuBuilderInterface;
+use Knp\Menu\MenuItem;
+use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
+use Chill\PersonBundle\Security\Authorization\PersonVoter;
+use Symfony\Component\Translation\TranslatorInterface;
+
+/**
+ * Class SectionMenuBuilder
+ *
+ * @package Chill\PersonBundle\Menu
+ * @author Julien Fastré
+ */
+class SectionMenuBuilder implements LocalMenuBuilderInterface
+{
+ /**
+ * @var AuthorizationCheckerInterface
+ */
+ protected $authorizationChecker;
+
+ /**
+ * @var TranslatorInterface
+ */
+ protected $translator;
+
+ /**
+ * SectionMenuBuilder constructor.
+ *
+ * @param AuthorizationCheckerInterface $authorizationChecker
+ * @param TranslatorInterface $translator
+ */
+ public function __construct(AuthorizationCheckerInterface $authorizationChecker, TranslatorInterface $translator)
+ {
+ $this->authorizationChecker = $authorizationChecker;
+ $this->translator = $translator;
+ }
+
+ /**
+ * @param $menuId
+ * @param MenuItem $menu
+ * @param array $parameters
+ */
+ public function buildMenu($menuId, MenuItem $menu, array $parameters)
+ {
+ if ($this->authorizationChecker->isGranted(PersonVoter::CREATE)) {
+ $menu->addChild($this->translator->trans('Add a person'), [
+ 'route' => 'chill_person_new'
+ ])
+ ->setExtras([
+ 'order' => 10,
+ 'icons' => [ 'plus' ]
+ ]);
+ }
+ }
+
+ /**
+ * @return array
+ */
+ public static function getMenuIds(): array
+ {
+ return [ 'section' ];
+ }
+}
diff --git a/src/Bundle/ChillPerson/Privacy/PrivacyEvent.php b/src/Bundle/ChillPerson/Privacy/PrivacyEvent.php
new file mode 100644
index 000000000..65304f96a
--- /dev/null
+++ b/src/Bundle/ChillPerson/Privacy/PrivacyEvent.php
@@ -0,0 +1,110 @@
+,
+ *
+ * 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 .
+ */
+
+use Symfony\Component\EventDispatcher\Event;
+use Chill\PersonBundle\Entity\Person;
+
+/**
+ * Class PrivacyEvent
+ *
+ * Array $args expects arguments with the following keys: 'element_class', 'element_id', 'action'
+ * By default, action is set to 'show'
+ *
+ * @package Chill\PersonBundle\Privacy
+ */
+class PrivacyEvent extends Event
+{
+ const PERSON_PRIVACY_EVENT = 'chill_person.privacy_event';
+
+ /**
+ * @var Person
+ */
+ private $person;
+
+ /**
+ * @var array
+ */
+ private $args;
+
+ /**
+ * @var array
+ */
+ private $persons;
+
+ /**
+ * PrivacyEvent constructor.
+ *
+ * @param Person $person
+ * @param array $args
+ */
+ public function __construct(Person $person, array $args = array('action' => 'show'))
+ {
+ $this->person = $person;
+ $this->args = $args;
+ $this->persons = array();
+ }
+
+ /**
+ * @return Person
+ */
+ public function getPerson()
+ {
+ return $this->person;
+ }
+
+ /**
+ * @param Person $person
+ */
+ public function addPerson(Person $person)
+ {
+ $this->persons[] = $person;
+
+ return $this;
+ }
+
+ /**
+ * @return array $persons
+ */
+ public function getPersons()
+ {
+ return $this->persons;
+ }
+
+ /**
+ * @return bool
+ */
+ public function hasPersons()
+ {
+ return count($this->persons) >= 1;
+ }
+
+ /**
+ * @return array
+ */
+ public function getArgs()
+ {
+ return $this->args;
+ }
+
+}
\ No newline at end of file
diff --git a/src/Bundle/ChillPerson/Privacy/PrivacyEventSubscriber.php b/src/Bundle/ChillPerson/Privacy/PrivacyEventSubscriber.php
new file mode 100644
index 000000000..b6cc50e54
--- /dev/null
+++ b/src/Bundle/ChillPerson/Privacy/PrivacyEventSubscriber.php
@@ -0,0 +1,89 @@
+,
+ *
+ * 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 .
+ */
+
+use Psr\Log\LoggerInterface;
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
+use Chill\PersonBundle\Entity\Person;
+
+class PrivacyEventSubscriber implements EventSubscriberInterface
+{
+
+ /**
+ * @var LoggerInterface
+ */
+ protected $logger;
+
+ /**
+ * @var TokenStorageInterface
+ */
+ protected $token;
+
+ /**
+ * PrivacyEventSubscriber constructor.
+ *
+ * @param LoggerInterface $logger
+ */
+ public function __construct(LoggerInterface $logger, TokenStorageInterface $token)
+ {
+ $this->logger = $logger;
+ $this->token = $token;
+ }
+
+ public static function getSubscribedEvents()
+ {
+ return array(PrivacyEvent::PERSON_PRIVACY_EVENT => array(
+ array('onPrivacyEvent')
+ ));
+ }
+
+ public function onPrivacyEvent(PrivacyEvent $event)
+ {
+ $persons = array();
+
+ if ($event->hasPersons() === true) {
+ foreach ($event->getPersons() as $person) {
+ $persons[] = $person->getId();
+ }
+ }
+
+ $involved = array(
+ 'by_user' => $this->token->getToken()->getUser()->getUsername(),
+ 'by_user_id' => $this->token->getToken()->getUser()->getId(),
+ 'person_id' => $event->getPerson()->getId(),
+ );
+
+ if ($event->hasPersons()) {
+ $involved['persons'] = \array_map(
+ function(Person $p) { return $p->getId(); },
+ $event->getPersons()
+ );
+ }
+
+ $this->logger->notice(
+ "[Privacy Event] A Person Folder has been viewed",
+ array_merge($involved, $event->getArgs())
+ );
+ }
+}
\ No newline at end of file
diff --git a/src/Bundle/ChillPerson/README.md b/src/Bundle/ChillPerson/README.md
new file mode 100644
index 000000000..4412c2d2b
--- /dev/null
+++ b/src/Bundle/ChillPerson/README.md
@@ -0,0 +1,9 @@
+ChillPersonBundle
+=================
+
+The chill bundle for dealing with persons
+
+Documentation & installation
+----------------------------
+
+Read documentation here : http://chill.readthedocs.org
diff --git a/src/Bundle/ChillPerson/Repository/ClosingMotiveRepository.php b/src/Bundle/ChillPerson/Repository/ClosingMotiveRepository.php
new file mode 100644
index 000000000..c3519796d
--- /dev/null
+++ b/src/Bundle/ChillPerson/Repository/ClosingMotiveRepository.php
@@ -0,0 +1,59 @@
+
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+namespace Chill\PersonBundle\Repository;
+
+use Doctrine\ORM\EntityRepository;
+use Doctrine\ORM\QueryBuilder;
+use Doctrine\ORM\Query\ResultSetMappingBuilder;
+
+/**
+ * Class ClosingMotiveRepository
+ * Entity repository for closing motives
+ *
+ * @package Chill\PersonBundle\Repository
+ */
+class ClosingMotiveRepository extends EntityRepository
+{
+ /**
+ * @param bool $onlyLeaf
+ * @return mixed
+ */
+ public function getActiveClosingMotive(bool $onlyLeaf = true)
+ {
+ $rsm = new ResultSetMappingBuilder($this->getEntityManager());
+ $rsm->addRootEntityFromClassMetadata($this->getClassName(), 'cm');
+
+ $sql = "SELECT ".(string) $rsm."
+ FROM chill_person_closingmotive AS cm
+ WHERE
+ active IS TRUE ";
+
+ if ($onlyLeaf) {
+ $sql .= "AND cm.id NOT IN (
+ SELECT DISTINCT parent_id FROM chill_person_closingmotive WHERE parent_id IS NOT NULL
+ )";
+ }
+
+ $sql .= " ORDER BY cm.ordering ASC";
+
+ return $this->_em
+ ->createNativeQuery($sql, $rsm)
+ ->getResult()
+ ;
+ }
+}
diff --git a/src/Bundle/ChillPerson/Repository/PersonAltNameRepository.php b/src/Bundle/ChillPerson/Repository/PersonAltNameRepository.php
new file mode 100644
index 000000000..315cee94f
--- /dev/null
+++ b/src/Bundle/ChillPerson/Repository/PersonAltNameRepository.php
@@ -0,0 +1,13 @@
+
+ *
+ * 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\PersonBundle\Repository;
+
+use Doctrine\ORM\EntityRepository;
+use Doctrine\ORM\QueryBuilder;
+
+/**
+ * Class PersonRepository
+ *
+ * @package Chill\PersonBundle\Repository
+ */
+class PersonRepository extends EntityRepository
+{
+ /**
+ * @param string $phonenumber
+ * @param $centers
+ * @param $firstResult
+ * @param $maxResults
+ * @param array $only
+ * @return mixed
+ * @throws \Exception
+ */
+ public function findByPhone(
+ string $phonenumber,
+ $centers,
+ $firstResult,
+ $maxResults,
+ array $only = ['mobile', 'phone']
+ ) {
+ $qb = $this->createQueryBuilder('p');
+ $qb->select('p');
+
+ $this->addByCenters($qb, $centers);
+ $this->addPhoneNumber($qb, $phonenumber, $only);
+
+ $qb->setFirstResult($firstResult)
+ ->setMaxResults($maxResults)
+ ;
+
+ return $qb->getQuery()->getResult();
+ }
+
+ /**
+ * @param string $phonenumber
+ * @param $centers
+ * @param array $only
+ * @return int
+ * @throws \Doctrine\ORM\NoResultException
+ * @throws \Doctrine\ORM\NonUniqueResultException
+ */
+ public function countByPhone(
+ string $phonenumber,
+ $centers,
+ array $only = ['mobile', 'phone']
+ ): int
+ {
+ $qb = $this->createQueryBuilder('p');
+ $qb->select('COUNT(p)');
+
+ $this->addByCenters($qb, $centers);
+ $this->addPhoneNumber($qb, $phonenumber, $only);
+
+ return $qb->getQuery()->getSingleScalarResult();
+ }
+
+ /**
+ * @param QueryBuilder $qb
+ * @param string $phonenumber
+ * @param array $only
+ * @throws \Exception
+ */
+ protected function addPhoneNumber(QueryBuilder $qb, string $phonenumber, array $only)
+ {
+ if (count($only) === 0) {
+ throw new \Exception("No array field to search");
+ }
+
+ $phonenumber = $this->parsePhoneNumber($phonenumber);
+
+ $orX = $qb->expr()->orX();
+
+ if (\in_array('mobile', $only)) {
+ $orX->add($qb->expr()->like("REPLACE(p.mobilenumber, ' ', '')", ':phonenumber'));
+ }
+ if (\in_array('phone', $only)) {
+ $orX->add($qb->expr()->like("REPLACE(p.phonenumber, ' ', '')", ':phonenumber'));
+ }
+
+ $qb->andWhere($orX);
+
+ $qb->setParameter('phonenumber', '%'.$phonenumber.'%');
+ }
+
+ /**
+ * @param $phonenumber
+ * @return string
+ */
+ protected function parsePhoneNumber($phonenumber): string
+ {
+ return \str_replace(' ', '', $phonenumber);
+ }
+
+ /**
+ * @param QueryBuilder $qb
+ * @param array $centers
+ */
+ protected function addByCenters(QueryBuilder $qb, array $centers)
+ {
+ if (count($centers) > 0) {
+ $qb->andWhere($qb->expr()->in('p.center', ':centers'));
+ $qb->setParameter('centers', $centers);
+ }
+ }
+}
diff --git a/src/Bundle/ChillPerson/Resources/Gruntfile.js b/src/Bundle/ChillPerson/Resources/Gruntfile.js
new file mode 100644
index 000000000..1e3fea92f
--- /dev/null
+++ b/src/Bundle/ChillPerson/Resources/Gruntfile.js
@@ -0,0 +1,46 @@
+module.exports = function(grunt) {
+ grunt.initConfig({
+ pkg: grunt.file.readJSON('package.json'),
+
+ chillperson: {
+ folders: {
+ pub: './public',
+ css: '<%= chillperson.folders.pub %>/css/',
+ sass: '<%= chillperson.folders.pub %>/sass/',
+ }
+ },
+ sass: {
+ dist: {
+ options: {
+ debugInfo: false,
+ },
+ files: [{
+ expand: true,
+ cwd: '<%= chillperson.folders.sass.src %>',
+ src: ['*.scss'],
+ dest: '<%= chillperson.folders.css %>',
+ ext: '.css'
+ }]
+ }
+ },
+ watch: {
+ css: {
+ files: [ '<%= chillperson.folders.sass %>/*.scss', '<%= chillperson.folders.sass %>/**/*.scss' ],
+ tasks: ['generatecss'],
+ /*
+ options: {
+ spawn: false,
+ interrupt: true,
+ }
+ */
+ }
+ },
+ });
+
+ grunt.loadNpmTasks('grunt-contrib-sass');
+ grunt.loadNpmTasks('grunt-contrib-watch');
+
+ grunt.registerTask('generatecss', 'sass');
+
+ grunt.registerTask('default', ['generatecss']);
+};
\ No newline at end of file
diff --git a/src/Bundle/ChillPerson/Resources/doc/index.rst b/src/Bundle/ChillPerson/Resources/doc/index.rst
new file mode 100644
index 000000000..e69de29bb
diff --git a/src/Bundle/ChillPerson/Resources/package.json b/src/Bundle/ChillPerson/Resources/package.json
new file mode 100644
index 000000000..3db5780e7
--- /dev/null
+++ b/src/Bundle/ChillPerson/Resources/package.json
@@ -0,0 +1,16 @@
+{
+ "name": "chill-person-js-css",
+ "version": "0.0.0",
+ "description": "JS-CSS files for ChillPerson Bundle, a software for social workers",
+ "directories": {
+ },
+ "author": "Champs-Libres ",
+ "devDependencies": {
+ "grunt": "^0.4.5",
+ "grunt-contrib-copy": "^0.7.0",
+ "grunt-contrib-sass": "^0.8.1",
+ "grunt-contrib-watch": "^0.6.1",
+ "grunt-contrib-clean": "^0.6.0"
+ },
+ "dependencies": {}
+}
diff --git a/src/Bundle/ChillPerson/Resources/public/css/person.css b/src/Bundle/ChillPerson/Resources/public/css/person.css
new file mode 100644
index 000000000..61fca71e5
--- /dev/null
+++ b/src/Bundle/ChillPerson/Resources/public/css/person.css
@@ -0,0 +1,78 @@
+.chill-blue, div.person-view div.custom-fields figure.person-details div.cf_title_box:nth-child(4n+4) h2 {
+ color: #334d5c; }
+
+.chill-green, div.person-view div.custom-fields figure.person-details div.cf_title_box:nth-child(4n+2) h2 {
+ color: #43b29d; }
+
+.chill-green-dark {
+ color: #328474; }
+
+.chill-yellow {
+ color: #eec84a; }
+
+.chill-orange, div.person-view div.custom-fields figure.person-details div.cf_title_box:nth-child(4n+3) h2 {
+ color: #e2793d; }
+
+.chill-red, div.person-view div.custom-fields figure.person-details div.cf_title_box:nth-child(4n+1) h2 {
+ color: #df4949; }
+
+.chill-gray {
+ color: #ececec; }
+
+.chill-beige {
+ color: #cabb9f; }
+
+.chill-pink {
+ color: #dd506d; }
+
+.chill-dark-gray {
+ color: #333333; }
+
+.chill-light-gray {
+ color: #b2b2b2; }
+
+div#header-person-name {
+ background: none repeat scroll 0 0 #328474;
+ color: #FFF;
+ padding-top: 1em;
+ padding-bottom: 1em; }
+
+div#header-person-details {
+ background: none repeat scroll 0 0 #43b29d;
+ color: #FFF;
+ padding-top: 1em;
+ padding-bottom: 1em; }
+
+div#person_details_container {
+ padding-top: 20px;
+ padding-bottom: 20px; }
+
+div.person-view {
+ /* custom fields on the home page */ }
+ div.person-view figure.person-details {
+ /* background-color: $black;
+ padding-top: 0.2em;
+ padding-bottom: 0.2em;
+ }*/ }
+ div.person-view figure.person-details h2 {
+ font-family: 'Open Sans';
+ font-weight: 600;
+ margin-bottom: 0.3em;
+ font-variant: small-caps; }
+ div.person-view figure.person-details dl {
+ margin-top: 0.3em; }
+ div.person-view figure.person-details dt {
+ font-family: 'Open Sans';
+ font-weight: 600; }
+ div.person-view figure.person-details dd {
+ margin-left: 0; }
+ div.person-view div.custom-fields figure.person-details {
+ display: flex;
+ flex-flow: row wrap; }
+ div.person-view div.custom-fields figure.person-details div.cf_title_box:nth-child(2n+1) {
+ width: 50%;
+ margin-right: 40px; }
+ div.person-view div.custom-fields figure.person-details iv.cf_title_box:nth-child(2n+2) {
+ width: calc(50% - 40px); }
+
+/*# sourceMappingURL=person.css.map */
diff --git a/src/Bundle/ChillPerson/Resources/public/css/person.css.map b/src/Bundle/ChillPerson/Resources/public/css/person.css.map
new file mode 100644
index 000000000..6593711e5
--- /dev/null
+++ b/src/Bundle/ChillPerson/Resources/public/css/person.css.map
@@ -0,0 +1,7 @@
+{
+"version": 3,
+"mappings": "AAkBE,wGAA+B;EAC7B,KAAK,EAJI,OAAO;;AAGlB,yGAA+B;EAC7B,KAAK,EAJY,OAAO;;AAG1B,iBAA+B;EAC7B,KAAK,EAJoB,OAAO;;AAGlC,aAA+B;EAC7B,KAAK,EAJ4B,OAAO;;AAG1C,0GAA+B;EAC7B,KAAK,EAJoC,OAAO;;AAGlD,uGAA+B;EAC7B,KAAK,EAJ4C,OAAO;;AAG1D,WAA+B;EAC7B,KAAK,EAJoD,OAAO;;AAGlE,YAA+B;EAC7B,KAAK,EAJ4D,OAAO;;AAG1E,WAA+B;EAC7B,KAAK,EAJoE,OAAO;;AAGlF,gBAA+B;EAC7B,KAAK,EAJ4E,OAAO;;AAG1F,iBAA+B;EAC7B,KAAK,EAJoF,OAAO;;ACZpG,sBAAuB;EACnB,UAAU,EAAE,8BAAwC;EACpD,KAAK,EAAE,IAAI;EACX,WAAW,EAAE,GAAG;EAChB,cAAc,EAAE,GAAG;;AAGvB,yBAA0B;EACtB,UAAU,EAAE,8BAAmC;EAC/C,KAAK,EAAE,IAAI;EACX,WAAW,EAAE,GAAG;EAChB,cAAc,EAAE,GAAG;;AAGvB,4BAA6B;EAC5B,WAAW,EAAE,IAAI;EACjB,cAAc,EAAE,IAAI;;AAGrB,eAAgB;EA6BZ,oCAAoC;EA5BpC,qCAAsB;IAsB1B;;;mBAGe;IAxBH,wCAAG;MACC,WAAW,EAAE,WAAW;MACxB,WAAW,EAAE,GAAG;MAChB,aAAa,EAAE,KAAK;MACpB,YAAY,EAAE,UAAU;IAG5B,wCAAG;MACK,UAAU,EAAE,KAAK;IAGzB,wCAAG;MACH,WAAW,EAAE,WAAW;MACxB,WAAW,EAAE,GAAG;IAGhB,wCAAG;MACK,WAAW,EAAE,CAAC;EAY1B,uDAAsB;IAClB,OAAO,EAAE,IAAI;IACb,SAAS,EAAE,QAAQ;IAkBnB,wFAAgC;MAC5B,KAAK,EAAE,GAAG;MACV,YAAY,EAAE,IAAI;IAGtB,uFAAgC;MAC5B,KAAK,EAAE,gBAAgB",
+"sources": ["../../../../main/Resources/public/sass/custom/config/_colors.scss","../sass/person.scss"],
+"names": [],
+"file": "person.css"
+}
diff --git a/src/Bundle/ChillPerson/Resources/public/index.js b/src/Bundle/ChillPerson/Resources/public/index.js
new file mode 100644
index 000000000..f4bcba7fb
--- /dev/null
+++ b/src/Bundle/ChillPerson/Resources/public/index.js
@@ -0,0 +1 @@
+require('./sass/person.scss');
\ No newline at end of file
diff --git a/src/Bundle/ChillPerson/Resources/public/sass/index.js b/src/Bundle/ChillPerson/Resources/public/sass/index.js
new file mode 100644
index 000000000..35945c7ff
--- /dev/null
+++ b/src/Bundle/ChillPerson/Resources/public/sass/index.js
@@ -0,0 +1,5 @@
+require('./phone-alt-solid.svg');
+require('./mobile-alt-solid.svg');
+require('./person_by_phonenumber.scss');
+
+
diff --git a/src/Bundle/ChillPerson/Resources/public/sass/mobile-alt-solid.svg b/src/Bundle/ChillPerson/Resources/public/sass/mobile-alt-solid.svg
new file mode 100644
index 000000000..ae8b81bb1
--- /dev/null
+++ b/src/Bundle/ChillPerson/Resources/public/sass/mobile-alt-solid.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/Bundle/ChillPerson/Resources/public/sass/person.scss b/src/Bundle/ChillPerson/Resources/public/sass/person.scss
new file mode 100644
index 000000000..78e3d5b40
--- /dev/null
+++ b/src/Bundle/ChillPerson/Resources/public/sass/person.scss
@@ -0,0 +1,88 @@
+
+@import '../../../../main/Resources/public/sass/custom/config/colors';
+
+div#header-person-name {
+ background: none repeat scroll 0 0 $chill-green-dark;
+ color: #FFF;
+ padding-top: 1em;
+ padding-bottom: 1em;
+}
+
+div#header-person-details {
+ background: none repeat scroll 0 0 $chill-green;
+ color: #FFF;
+ padding-top: 1em;
+ padding-bottom: 1em;
+}
+
+div#person_details_container {
+ padding-top: 20px;
+ padding-bottom: 20px;
+}
+
+div.person-view {
+ figure.person-details {
+ h2 {
+ font-family: 'Open Sans';
+ font-weight: 600;
+ margin-bottom: 0.3em;
+ font-variant: small-caps;
+ }
+
+ dl {
+ margin-top: 0.3em;
+ }
+
+ dt {
+ font-family: 'Open Sans';
+ font-weight: 600;
+ }
+
+ dd {
+ margin-left: 0;
+ }
+
+// a.sc-button {
+/* background-color: $black;
+ padding-top: 0.2em;
+ padding-bottom: 0.2em;
+ }*/
+ }
+
+ /* custom fields on the home page */
+ div.custom-fields {
+ figure.person-details {
+ display: flex;
+ flex-flow: row wrap;
+
+ div.cf_title_box:nth-child(4n+1) h2 {
+ @extend .chill-red;
+ }
+
+ div.cf_title_box:nth-child(4n+2) h2 {
+ @extend .chill-green;
+ }
+
+ div.cf_title_box:nth-child(4n+3) h2 {
+ @extend .chill-orange;
+ }
+
+ div.cf_title_box:nth-child(4n+4) h2 {
+ @extend .chill-blue;
+ }
+
+ div.cf_title_box:nth-child(2n+1){
+ width: 50%;
+ margin-right: 40px;
+ }
+
+ iv.cf_title_box:nth-child(2n+2) {
+ width: calc(50% - 40px);
+ }
+
+ }
+
+
+ }
+
+}
diff --git a/src/Bundle/ChillPerson/Resources/public/sass/person_by_phonenumber.scss b/src/Bundle/ChillPerson/Resources/public/sass/person_by_phonenumber.scss
new file mode 100644
index 000000000..a1066c08f
--- /dev/null
+++ b/src/Bundle/ChillPerson/Resources/public/sass/person_by_phonenumber.scss
@@ -0,0 +1,25 @@
+.person-list__--by-phonenumber {
+ .person-list__--by-phonenumber__phones {
+ ul {
+ list-style: none inside;
+ padding: 0;
+ margin: 0;
+
+ li {
+ margin: 0.80rem;
+
+ img {
+ vertical-align: baseline;
+ height: 0.90rem;
+ margin-right: 0.20rem;
+ }
+ pre {
+ display: inline;
+ }
+ }
+ }
+
+
+ }
+}
+;
diff --git a/src/Bundle/ChillPerson/Resources/public/sass/phone-alt-solid.svg b/src/Bundle/ChillPerson/Resources/public/sass/phone-alt-solid.svg
new file mode 100644
index 000000000..6460d2d9e
--- /dev/null
+++ b/src/Bundle/ChillPerson/Resources/public/sass/phone-alt-solid.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/Bundle/ChillPerson/Resources/test/Fixtures/App/app/AppKernel.php b/src/Bundle/ChillPerson/Resources/test/Fixtures/App/app/AppKernel.php
new file mode 100644
index 000000000..29c257406
--- /dev/null
+++ b/src/Bundle/ChillPerson/Resources/test/Fixtures/App/app/AppKernel.php
@@ -0,0 +1,47 @@
+load(__DIR__.'/config/config_'.$this->getEnvironment().'.yml');
+ }
+
+ /**
+ * @return string
+ */
+ public function getCacheDir()
+ {
+ return sys_get_temp_dir().'/PersonBundle/cache';
+ }
+
+ /**
+ * @return string
+ */
+ public function getLogDir()
+ {
+ return sys_get_temp_dir().'/PersonBundle/logs';
+ }
+}
+
diff --git a/src/Bundle/ChillPerson/Resources/test/Fixtures/App/app/DoctrineMigrations/.gitignore b/src/Bundle/ChillPerson/Resources/test/Fixtures/App/app/DoctrineMigrations/.gitignore
new file mode 100644
index 000000000..d6b7ef32c
--- /dev/null
+++ b/src/Bundle/ChillPerson/Resources/test/Fixtures/App/app/DoctrineMigrations/.gitignore
@@ -0,0 +1,2 @@
+*
+!.gitignore
diff --git a/src/Bundle/ChillPerson/Resources/test/Fixtures/App/app/Resources/views/base.html.twig b/src/Bundle/ChillPerson/Resources/test/Fixtures/App/app/Resources/views/base.html.twig
new file mode 100644
index 000000000..bafd28d3b
--- /dev/null
+++ b/src/Bundle/ChillPerson/Resources/test/Fixtures/App/app/Resources/views/base.html.twig
@@ -0,0 +1,13 @@
+
+
+
+
+ {% block title %}Welcome!{% endblock %}
+ {% block stylesheets %}{% endblock %}
+
+
+
+ {% block body %}{% endblock %}
+ {% block javascripts %}{% endblock %}
+
+
diff --git a/src/Bundle/ChillPerson/Resources/test/Fixtures/App/app/autoload.php b/src/Bundle/ChillPerson/Resources/test/Fixtures/App/app/autoload.php
new file mode 100644
index 000000000..39dc0e2ba
--- /dev/null
+++ b/src/Bundle/ChillPerson/Resources/test/Fixtures/App/app/autoload.php
@@ -0,0 +1,11 @@
+getParameterOption(array('--env', '-e'), getenv('SYMFONY_ENV') ?: 'dev');
+$debug = getenv('SYMFONY_DEBUG') !== '0' && !$input->hasParameterOption(array('--no-debug', '')) && $env !== 'prod';
+
+if ($debug) {
+ Debug::enable();
+}
+
+$kernel = new AppKernel($env, $debug);
+$application = new Application($kernel);
+$application->run($input);
diff --git a/src/Bundle/ChillPerson/Resources/test/Fixtures/App/logs/empty b/src/Bundle/ChillPerson/Resources/test/Fixtures/App/logs/empty
new file mode 100644
index 000000000..e69de29bb
diff --git a/src/Bundle/ChillPerson/Resources/test/Fixtures/App/web/app_dev.php b/src/Bundle/ChillPerson/Resources/test/Fixtures/App/web/app_dev.php
new file mode 100644
index 000000000..e0279c2ae
--- /dev/null
+++ b/src/Bundle/ChillPerson/Resources/test/Fixtures/App/web/app_dev.php
@@ -0,0 +1,30 @@
+loadClassCache();
+$request = Request::createFromGlobals();
+$response = $kernel->handle($request);
+$response->send();
+$kernel->terminate($request, $response);
diff --git a/src/Bundle/ChillPerson/Resources/test/Fixtures/App/web/bundles/chillmain/CONTRIBUTING.md b/src/Bundle/ChillPerson/Resources/test/Fixtures/App/web/bundles/chillmain/CONTRIBUTING.md
new file mode 100644
index 000000000..bca4baf61
--- /dev/null
+++ b/src/Bundle/ChillPerson/Resources/test/Fixtures/App/web/bundles/chillmain/CONTRIBUTING.md
@@ -0,0 +1,107 @@
+Contributing to Select2
+=======================
+Looking to contribute something to Select2? **Here's how you can help.**
+
+Please take a moment to review this document in order to make the contribution
+process easy and effective for everyone involved.
+
+Following these guidelines helps to communicate that you respect the time of
+the developers managing and developing this open source project. In return,
+they should reciprocate that respect in addressing your issue or assessing
+patches and features.
+
+Using the issue tracker
+-----------------------
+When [reporting bugs][reporting-bugs] or
+[requesting features][requesting-features], the
+[issue tracker on GitHub][issue-tracker] is the recommended channel to use.
+
+The issue tracker **is not** a place for support requests. The
+[mailing list][mailing-list] or [IRC channel][irc-channel] are better places to
+get help.
+
+Reporting bugs with Select2
+---------------------------
+We really appreciate clear bug reports that _consistently_ show an issue
+_within Select2_.
+
+The ideal bug report follows these guidelines:
+
+1. **Use the [GitHub issue search][issue-search]** — Check if the issue
+ has already been reported.
+2. **Check if the issue has been fixed** — Try to reproduce the problem
+ using the code in the `master` branch.
+3. **Isolate the problem** — Try to create an
+ [isolated test case][isolated-case] that consistently reproduces the problem.
+
+Please try to be as detailed as possible in your bug report, especially if an
+isolated test case cannot be made. Some useful questions to include the answer
+to are:
+
+- What steps can be used to reproduce the issue?
+- What is the bug and what is the expected outcome?
+- What browser(s) and Operating System have you tested with?
+- Does the bug happen consistently across all tested browsers?
+- What version of jQuery are you using? And what version of Select2?
+- Are you using Select2 with other plugins?
+
+All of these questions will help people fix and identify any potential bugs.
+
+Requesting features in Select2
+------------------------------
+Select2 is a large library that carries with it a lot of functionality. Because
+of this, many feature requests will not be implemented in the core library.
+
+Before starting work on a major feature for Select2, **contact the
+[community][community] first** or you may risk spending a considerable amount of
+time on something which the project developers are not interested in bringing
+into the project.
+
+### Select2 4.0
+
+Many feature requests will be closed off until 4.0, where Select2 plans to adopt
+a more flexible API. If you are interested in helping with the development of
+the next major Select2 release, please send a message to the
+[mailing list][mailing-list] or [irc channel][irc-channel] for more information.
+
+Triaging issues and pull requests
+---------------------------------
+Anyone can help the project maintainers triage issues and review pull requests.
+
+### Handling new issues
+
+Select2 regularly receives new issues which need to be tested and organized.
+
+When a new issue that comes in that is similar to another existing issue, it
+should be checked to make sure it is not a duplicate. Duplicates issues should
+be marked by replying to the issue with "Duplicate of #[issue number]" where
+`[issue number]` is the url or issue number for the existing issue. This will
+allow the project maintainers to quickly close off additional issues and keep
+the discussion focused within a single issue.
+
+If you can test issues that are reported to Select2 that contain test cases and
+confirm under what conditions bugs happen, that will allow others to identify
+what causes a bug quicker.
+
+### Reviewing pull requests
+
+It is very common for pull requests to be opened for issues that contain a clear
+solution to the problem. These pull requests should be rigorously reviewed by
+the community before being accepted. If you are not sure about a piece of
+submitted code, or know of a better way to do something, do not hesitate to make
+a comment on the pull request.
+
+It should also be made clear that **all code contributed to Select** must be
+licensable under the [Apache 2 or GPL 2 licenses][licensing]. Code that cannot
+be released under either of these licenses **cannot be accepted** into the
+project.
+
+[community]: https://github.com/ivaynberg/select2#community
+[reporting-bugs]: #reporting-bugs-with-select2
+[requesting-features]: #requesting-features-in-select2
+[issue-tracker]: https://github.com/ivaynberg/select2/issues
+[mailing-list]: https://github.com/ivaynberg/select2#mailing-list
+[irc-channel]: https://github.com/ivaynberg/select2#irc-channel
+[issue-search]: https://github.com/ivaynberg/select2/search?q=&type=Issues
+[isolated-case]: http://css-tricks.com/6263-reduced-test-cases/
+[licensing]: https://github.com/ivaynberg/select2#copyright-and-license
diff --git a/src/Bundle/ChillPerson/Resources/test/Fixtures/App/web/bundles/chillmain/LICENSE b/src/Bundle/ChillPerson/Resources/test/Fixtures/App/web/bundles/chillmain/LICENSE
new file mode 100644
index 000000000..0247cc762
--- /dev/null
+++ b/src/Bundle/ChillPerson/Resources/test/Fixtures/App/web/bundles/chillmain/LICENSE
@@ -0,0 +1,18 @@
+Copyright 2014 Igor Vaynberg
+
+Version: @@ver@@ Timestamp: @@timestamp@@
+
+This software is licensed under the Apache License, Version 2.0 (the "Apache License") or the GNU
+General Public License version 2 (the "GPL License"). You may choose either license to govern your
+use of this software only upon the condition that you accept all of the terms of either the Apache
+License or the GPL License.
+
+You may obtain a copy of the Apache License and the GPL License at:
+
+http://www.apache.org/licenses/LICENSE-2.0
+http://www.gnu.org/licenses/gpl-2.0.html
+
+Unless required by applicable law or agreed to in writing, software distributed under the Apache License
+or the GPL Licesnse is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+either express or implied. See the Apache License and the GPL License for the specific language governing
+permissions and limitations under the Apache License and the GPL License.
diff --git a/src/Bundle/ChillPerson/Resources/test/Fixtures/App/web/bundles/chillmain/README.md b/src/Bundle/ChillPerson/Resources/test/Fixtures/App/web/bundles/chillmain/README.md
new file mode 100644
index 000000000..64380c451
--- /dev/null
+++ b/src/Bundle/ChillPerson/Resources/test/Fixtures/App/web/bundles/chillmain/README.md
@@ -0,0 +1,114 @@
+Select2
+=======
+
+Select2 is a jQuery-based replacement for select boxes. It supports searching, remote data sets, and infinite scrolling of results.
+
+To get started, checkout examples and documentation at http://ivaynberg.github.com/select2
+
+Use cases
+---------
+
+* Enhancing native selects with search.
+* Enhancing native selects with a better multi-select interface.
+* Loading data from JavaScript: easily load items via ajax and have them searchable.
+* Nesting optgroups: native selects only support one level of nested. Select2 does not have this restriction.
+* Tagging: ability to add new items on the fly.
+* Working with large, remote datasets: ability to partially load a dataset based on the search term.
+* Paging of large datasets: easy support for loading more pages when the results are scrolled to the end.
+* Templating: support for custom rendering of results and selections.
+
+Browser compatibility
+---------------------
+* IE 8+
+* Chrome 8+
+* Firefox 10+
+* Safari 3+
+* Opera 10.6+
+
+Usage
+-----
+You can source Select2 directly from a CDN like [JSDliver](http://www.jsdelivr.com/#!select2) or [CDNJS](http://www.cdnjs.com/libraries/select2), [download it from this GitHub repo](https://github.com/ivaynberg/select2/tags), or use one of the integrations below.
+
+Integrations
+------------
+
+* [Wicket-Select2](https://github.com/ivaynberg/wicket-select2) (Java / [Apache Wicket](http://wicket.apache.org))
+* [select2-rails](https://github.com/argerim/select2-rails) (Ruby on Rails)
+* [AngularUI](http://angular-ui.github.io/#ui-select) ([AngularJS](https://angularjs.org/))
+* [Django](https://github.com/applegrew/django-select2)
+* [Symfony](https://github.com/19Gerhard85/sfSelect2WidgetsPlugin)
+* [Symfony2](https://github.com/avocode/FormExtensions)
+* [Bootstrap 2](https://github.com/t0m/select2-bootstrap-css) and [Bootstrap 3](https://github.com/t0m/select2-bootstrap-css/tree/bootstrap3) (CSS skins)
+* [Meteor](https://github.com/nate-strauser/meteor-select2) (modern reactive JavaScript framework; + [Bootstrap 3 skin](https://github.com/esperadomedia/meteor-select2-bootstrap3-css/))
+* [Meteor](https://jquery-select2.meteor.com)
+* [Yii 2.x](http://demos.krajee.com/widgets#select2)
+* [Yii 1.x](https://github.com/tonybolzan/yii-select2)
+* [AtmosphereJS](https://atmospherejs.com/package/jquery-select2)
+
+### Example Integrations
+
+* [Knockout.js](https://github.com/ivaynberg/select2/wiki/Knockout.js-Integration)
+* [Socket.IO](https://github.com/ivaynberg/select2/wiki/Socket.IO-Integration)
+* [PHP](https://github.com/ivaynberg/select2/wiki/PHP-Example)
+* [.Net MVC] (https://github.com/ivaynberg/select2/wiki/.Net-MVC-Example)
+
+Internationalization (i18n)
+---------------------------
+
+Select2 supports multiple languages by simply including the right language JS
+file (`select2_locale_it.js`, `select2_locale_nl.js`, etc.) after `select2.js`.
+
+Missing a language? Just copy `select2_locale_en.js.template`, translate
+it, and make a pull request back to Select2 here on GitHub.
+
+Documentation
+-------------
+
+The documentation for Select2 is available [through GitHub Pages](https://ivaynberg.github.io/select2/) and is located within this repository in the [`gh-pages` branch](https://github.com/ivaynberg/select2/tree/gh-pages).
+
+Community
+---------
+
+### Bug tracker
+
+Have a bug? Please create an issue here on GitHub!
+
+https://github.com/ivaynberg/select2/issues
+
+### Mailing list
+
+Have a question? Ask on our mailing list!
+
+select2@googlegroups.com
+
+https://groups.google.com/d/forum/select2
+
+### IRC channel
+
+Need help implementing Select2 in your project? Ask in our IRC channel!
+
+**Network:** [Freenode](https://freenode.net/) (`chat.freenode.net`)
+
+**Channel:** `#select2`
+
+**Web access:** https://webchat.freenode.net/?channels=select2
+
+Copyright and license
+---------------------
+
+Copyright 2012 Igor Vaynberg
+
+This software is licensed under the Apache License, Version 2.0 (the "Apache License") or the GNU
+General Public License version 2 (the "GPL License"). You may choose either license to govern your
+use of this software only upon the condition that you accept all of the terms of either the Apache
+License or the GPL License.
+
+You may obtain a copy of the Apache License and the GPL License in the LICENSE file, or at:
+
+http://www.apache.org/licenses/LICENSE-2.0
+http://www.gnu.org/licenses/gpl-2.0.html
+
+Unless required by applicable law or agreed to in writing, software distributed under the Apache License
+or the GPL License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+either express or implied. See the Apache License and the GPL License for the specific language governing
+permissions and limitations under the Apache License and the GPL License.
diff --git a/src/Bundle/ChillPerson/Resources/test/Fixtures/App/web/bundles/chillmain/bower.json b/src/Bundle/ChillPerson/Resources/test/Fixtures/App/web/bundles/chillmain/bower.json
new file mode 100644
index 000000000..54d44c459
--- /dev/null
+++ b/src/Bundle/ChillPerson/Resources/test/Fixtures/App/web/bundles/chillmain/bower.json
@@ -0,0 +1,8 @@
+{
+ "name": "select2",
+ "version": "3.5.2",
+ "main": ["select2.js", "select2.css", "select2.png", "select2x2.png", "select2-spinner.gif"],
+ "dependencies": {
+ "jquery": ">= 1.7.1"
+ }
+}
diff --git a/src/Bundle/ChillPerson/Resources/test/Fixtures/App/web/bundles/chillmain/component.json b/src/Bundle/ChillPerson/Resources/test/Fixtures/App/web/bundles/chillmain/component.json
new file mode 100644
index 000000000..8bd3c020a
--- /dev/null
+++ b/src/Bundle/ChillPerson/Resources/test/Fixtures/App/web/bundles/chillmain/component.json
@@ -0,0 +1,66 @@
+{
+ "name": "select2",
+ "repo": "ivaynberg/select2",
+ "description": "Select2 is a jQuery based replacement for select boxes. It supports searching, remote data sets, and infinite scrolling of results.",
+ "version": "3.5.2",
+ "demo": "http://ivaynberg.github.io/select2/",
+ "keywords": [
+ "jquery"
+ ],
+ "main": "select2.js",
+ "styles": [
+ "select2.css",
+ "select2-bootstrap.css"
+ ],
+ "scripts": [
+ "select2.js",
+ "select2_locale_ar.js",
+ "select2_locale_bg.js",
+ "select2_locale_ca.js",
+ "select2_locale_cs.js",
+ "select2_locale_da.js",
+ "select2_locale_de.js",
+ "select2_locale_el.js",
+ "select2_locale_es.js",
+ "select2_locale_et.js",
+ "select2_locale_eu.js",
+ "select2_locale_fa.js",
+ "select2_locale_fi.js",
+ "select2_locale_fr.js",
+ "select2_locale_gl.js",
+ "select2_locale_he.js",
+ "select2_locale_hr.js",
+ "select2_locale_hu.js",
+ "select2_locale_id.js",
+ "select2_locale_is.js",
+ "select2_locale_it.js",
+ "select2_locale_ja.js",
+ "select2_locale_ka.js",
+ "select2_locale_ko.js",
+ "select2_locale_lt.js",
+ "select2_locale_lv.js",
+ "select2_locale_mk.js",
+ "select2_locale_ms.js",
+ "select2_locale_nl.js",
+ "select2_locale_no.js",
+ "select2_locale_pl.js",
+ "select2_locale_pt-BR.js",
+ "select2_locale_pt-PT.js",
+ "select2_locale_ro.js",
+ "select2_locale_ru.js",
+ "select2_locale_sk.js",
+ "select2_locale_sv.js",
+ "select2_locale_th.js",
+ "select2_locale_tr.js",
+ "select2_locale_uk.js",
+ "select2_locale_vi.js",
+ "select2_locale_zh-CN.js",
+ "select2_locale_zh-TW.js"
+ ],
+ "images": [
+ "select2-spinner.gif",
+ "select2.png",
+ "select2x2.png"
+ ],
+ "license": "MIT"
+}
diff --git a/src/Bundle/ChillPerson/Resources/test/Fixtures/App/web/bundles/chillmain/composer.json b/src/Bundle/ChillPerson/Resources/test/Fixtures/App/web/bundles/chillmain/composer.json
new file mode 100644
index 000000000..cd2d26a2a
--- /dev/null
+++ b/src/Bundle/ChillPerson/Resources/test/Fixtures/App/web/bundles/chillmain/composer.json
@@ -0,0 +1,29 @@
+{
+ "name":
+ "ivaynberg/select2",
+ "description": "Select2 is a jQuery based replacement for select boxes.",
+ "version": "3.5.2",
+ "type": "component",
+ "homepage": "http://ivaynberg.github.io/select2/",
+ "license": "Apache-2.0",
+ "require": {
+ "robloach/component-installer": "*",
+ "components/jquery": ">=1.7.1"
+ },
+ "extra": {
+ "component": {
+ "scripts": [
+ "select2.js"
+ ],
+ "files": [
+ "select2.js",
+ "select2_locale_*.js",
+ "select2.css",
+ "select2-bootstrap.css",
+ "select2-spinner.gif",
+ "select2.png",
+ "select2x2.png"
+ ]
+ }
+ }
+}
diff --git a/src/Bundle/ChillPerson/Resources/test/Fixtures/App/web/bundles/chillmain/css/admin.css b/src/Bundle/ChillPerson/Resources/test/Fixtures/App/web/bundles/chillmain/css/admin.css
new file mode 100644
index 000000000..e4ef36d42
--- /dev/null
+++ b/src/Bundle/ChillPerson/Resources/test/Fixtures/App/web/bundles/chillmain/css/admin.css
@@ -0,0 +1,2 @@
+ul.admin_tiles li { background: none repeat scroll 0 0 #E9E9E9; border: 1px solid #C9C9C9; padding: 1.5em; margin-left: 1.5em; margin-bottom: 1.5em; }
+ul.admin_tiles li p, ul.admin_tiles li h1, ul.admin_tiles li h2, ul.admin_tiles li h3 { width: 350px; }
diff --git a/src/Bundle/ChillPerson/Resources/test/Fixtures/App/web/bundles/chillmain/css/chillmain.css b/src/Bundle/ChillPerson/Resources/test/Fixtures/App/web/bundles/chillmain/css/chillmain.css
new file mode 100644
index 000000000..85c247173
--- /dev/null
+++ b/src/Bundle/ChillPerson/Resources/test/Fixtures/App/web/bundles/chillmain/css/chillmain.css
@@ -0,0 +1,25 @@
+div#usefulbar { background-color: #fbba3a; z-index: 1000; padding-right: 15px; }
+div#usefulbar form { margin: 0; }
+div#usefulbar i.menu { font-size: 2em; }
+div#usefulbar ul { display: flex; justify-content: flex-end; margin: 0; padding-top: 5px; padding-right: 10px; }
+div#usefulbar li { color: white; margin-left: 10px; }
+div#usefulbar li a { color: white; text-shadow: 0px 0px 1px #555; }
+div#usefulbar li i.icon-user-add:before { vertical-align: -5px; }
+div#usefulbar li#search_element { text-align: right; }
+div#usefulbar li#search_element div#search_form { margin: 0; padding: 0; }
+div#usefulbar li#search_element div#search_form div { margin: 0; }
+div#usefulbar li#search_element div#search_form .field { margin: 0; }
+div#usefulbar li#search_element div#search_form button { color: white; border: none; bottom: -2px; height: 35px; }
+
+div#flashMessages { margin-top: 20px; }
+div#flashMessages .flash-notice { margin-top: 10px; margin-bottom: 10px; }
+
+.personName { font-variant: small-caps; text-transform: capitalize; }
+
+.personName { text-transform: capitalize; }
+
+input.belgian_national_number_inversed_date { width: 7em; margin-right: 1em; }
+
+input.belgian_national_number_daily_counter { width: 4em; margin-right: 1em; }
+
+input.belgian_national_number_control_digit { width: 3em; }
\ No newline at end of file
diff --git a/src/Bundle/ChillPerson/Resources/test/Fixtures/App/web/bundles/chillmain/css/gumby.css b/src/Bundle/ChillPerson/Resources/test/Fixtures/App/web/bundles/chillmain/css/gumby.css
new file mode 100644
index 000000000..24a87ec7a
--- /dev/null
+++ b/src/Bundle/ChillPerson/Resources/test/Fixtures/App/web/bundles/chillmain/css/gumby.css
@@ -0,0 +1,2617 @@
+@charset "UTF-8";
+/**
+* Gumby Framework
+* ---------------
+*
+* Follow @gumbycss on twitter and spread the love.
+* We worked super hard on making this awesome and released it to the web.
+* All we ask is you leave this intact. #gumbyisawesome
+*
+* Gumby Framework
+* http://gumbyframework.com
+*
+* Built with love by your friends @digitalsurgeons
+* http://www.digitalsurgeons.com
+*
+* Free to use under the MIT license.
+* http://www.opensource.org/licenses/mit-license.php
+*/
+html, body, div, span, applet, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, a, abbr, acronym, address, big, cite, code, del, dfn, em, img, ins, kbd, q, s, samp, small, strike, strong, sub, sup, tt, var, b, u, i, center, dl, dt, dd, ol, ul, li, fieldset, form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td, article, aside, canvas, details, embed, figure, figcaption, footer, header, hgroup, menu, nav, output, ruby, section, summary, time, mark, audio, video { margin: 0; padding: 0; border: 0; font: inherit; font-size: 100%; vertical-align: baseline; }
+
+html { line-height: 1; }
+
+ol, ul { list-style: none; }
+
+table { border-collapse: collapse; border-spacing: 0; }
+
+caption, th, td { text-align: left; font-weight: normal; vertical-align: middle; }
+
+q, blockquote { quotes: none; }
+q:before, q:after, blockquote:before, blockquote:after { content: ""; content: none; }
+
+a img { border: none; }
+
+article, aside, details, figcaption, figure, footer, header, hgroup, menu, nav, section, summary { display: block; }
+
+.pull_right { float: right; }
+
+.pull_left { float: left; }
+
+* html { font-size: 100%; }
+
+html { font-size: 16px; line-height: 1.625em; }
+
+* { -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; }
+
+body { background: white; font-family: "Oxygen"; font-weight: 400; color: #555555; position: relative; -webkit-font-smoothing: antialiased; }
+@media only screen and (max-width: 767px) { body { -webkit-text-size-adjust: none; -ms-text-size-adjust: none; width: 100%; min-width: 0; } }
+
+html, body { height: 100%; }
+
+.hide { display: none; }
+
+.hide.active, .show { display: block; }
+
+.icon-note.icon-left a:before, .icon-note.icon-right a:after { content: "\266a"; height: inherit; }
+
+i.icon-note:before { content: "\266a"; height: inherit; }
+
+.icon-note-beamed.icon-left a:before, .icon-note-beamed.icon-right a:after { content: "\266b"; height: inherit; }
+
+i.icon-note-beamed:before { content: "\266b"; height: inherit; }
+
+.icon-music.icon-left a:before, .icon-music.icon-right a:after { content: "🎵"; height: inherit; }
+
+i.icon-music:before { content: "🎵"; height: inherit; }
+
+.icon-search.icon-left a:before, .icon-search.icon-right a:after { content: "🔍"; height: inherit; }
+
+i.icon-search:before { content: "🔍"; height: inherit; }
+
+.icon-flashlight.icon-left a:before, .icon-flashlight.icon-right a:after { content: "🔦"; height: inherit; }
+
+i.icon-flashlight:before { content: "🔦"; height: inherit; }
+
+.icon-mail.icon-left a:before, .icon-mail.icon-right a:after { content: "\2709"; height: inherit; }
+
+i.icon-mail:before { content: "\2709"; height: inherit; }
+
+.icon-heart.icon-left a:before, .icon-heart.icon-right a:after { content: "\2665"; height: inherit; }
+
+i.icon-heart:before { content: "\2665"; height: inherit; }
+
+.icon-heart-empty.icon-left a:before, .icon-heart-empty.icon-right a:after { content: "\2661"; height: inherit; }
+
+i.icon-heart-empty:before { content: "\2661"; height: inherit; }
+
+.icon-star.icon-left a:before, .icon-star.icon-right a:after { content: "\2605"; height: inherit; }
+
+i.icon-star:before { content: "\2605"; height: inherit; }
+
+.icon-star-empty.icon-left a:before, .icon-star-empty.icon-right a:after { content: "\2606"; height: inherit; }
+
+i.icon-star-empty:before { content: "\2606"; height: inherit; }
+
+.icon-user.icon-left a:before, .icon-user.icon-right a:after { content: "👤"; height: inherit; }
+
+i.icon-user:before { content: "👤"; height: inherit; }
+
+.icon-users.icon-left a:before, .icon-users.icon-right a:after { content: "👥"; height: inherit; }
+
+i.icon-users:before { content: "👥"; height: inherit; }
+
+.icon-user-add.icon-left a:before, .icon-user-add.icon-right a:after { content: "\e700"; height: inherit; }
+
+i.icon-user-add:before { content: "\e700"; height: inherit; }
+
+.icon-video.icon-left a:before, .icon-video.icon-right a:after { content: "🎬"; height: inherit; }
+
+i.icon-video:before { content: "🎬"; height: inherit; }
+
+.icon-picture.icon-left a:before, .icon-picture.icon-right a:after { content: "🌄"; height: inherit; }
+
+i.icon-picture:before { content: "🌄"; height: inherit; }
+
+.icon-camera.icon-left a:before, .icon-camera.icon-right a:after { content: "📷"; height: inherit; }
+
+i.icon-camera:before { content: "📷"; height: inherit; }
+
+.icon-layout.icon-left a:before, .icon-layout.icon-right a:after { content: "\268f"; height: inherit; }
+
+i.icon-layout:before { content: "\268f"; height: inherit; }
+
+.icon-menu.icon-left a:before, .icon-menu.icon-right a:after { content: "\2630"; height: inherit; }
+
+i.icon-menu:before { content: "\2630"; height: inherit; }
+
+.icon-check.icon-left a:before, .icon-check.icon-right a:after { content: "\2713"; height: inherit; }
+
+i.icon-check:before { content: "\2713"; height: inherit; }
+
+.icon-cancel.icon-left a:before, .icon-cancel.icon-right a:after { content: "\2715"; height: inherit; }
+
+i.icon-cancel:before { content: "\2715"; height: inherit; }
+
+.icon-cancel-circled.icon-left a:before, .icon-cancel-circled.icon-right a:after { content: "\2716"; height: inherit; }
+
+i.icon-cancel-circled:before { content: "\2716"; height: inherit; }
+
+.icon-cancel-squared.icon-left a:before, .icon-cancel-squared.icon-right a:after { content: "\274e"; height: inherit; }
+
+i.icon-cancel-squared:before { content: "\274e"; height: inherit; }
+
+.icon-plus.icon-left a:before, .icon-plus.icon-right a:after { content: "\2b"; height: inherit; }
+
+i.icon-plus:before { content: "\2b"; height: inherit; }
+
+.icon-plus-circled.icon-left a:before, .icon-plus-circled.icon-right a:after { content: "\2795"; height: inherit; }
+
+i.icon-plus-circled:before { content: "\2795"; height: inherit; }
+
+.icon-plus-squared.icon-left a:before, .icon-plus-squared.icon-right a:after { content: "\229e"; height: inherit; }
+
+i.icon-plus-squared:before { content: "\229e"; height: inherit; }
+
+.icon-minus.icon-left a:before, .icon-minus.icon-right a:after { content: "\2d"; height: inherit; }
+
+i.icon-minus:before { content: "\2d"; height: inherit; }
+
+.icon-minus-circled.icon-left a:before, .icon-minus-circled.icon-right a:after { content: "\2796"; height: inherit; }
+
+i.icon-minus-circled:before { content: "\2796"; height: inherit; }
+
+.icon-minus-squared.icon-left a:before, .icon-minus-squared.icon-right a:after { content: "\229f"; height: inherit; }
+
+i.icon-minus-squared:before { content: "\229f"; height: inherit; }
+
+.icon-help.icon-left a:before, .icon-help.icon-right a:after { content: "\2753"; height: inherit; }
+
+i.icon-help:before { content: "\2753"; height: inherit; }
+
+.icon-help-circled.icon-left a:before, .icon-help-circled.icon-right a:after { content: "\e704"; height: inherit; }
+
+i.icon-help-circled:before { content: "\e704"; height: inherit; }
+
+.icon-info.icon-left a:before, .icon-info.icon-right a:after { content: "\2139"; height: inherit; }
+
+i.icon-info:before { content: "\2139"; height: inherit; }
+
+.icon-info-circled.icon-left a:before, .icon-info-circled.icon-right a:after { content: "\e705"; height: inherit; }
+
+i.icon-info-circled:before { content: "\e705"; height: inherit; }
+
+.icon-back.icon-left a:before, .icon-back.icon-right a:after { content: "🔙"; height: inherit; }
+
+i.icon-back:before { content: "🔙"; height: inherit; }
+
+.icon-home.icon-left a:before, .icon-home.icon-right a:after { content: "\2302"; height: inherit; }
+
+i.icon-home:before { content: "\2302"; height: inherit; }
+
+.icon-link.icon-left a:before, .icon-link.icon-right a:after { content: "🔗"; height: inherit; }
+
+i.icon-link:before { content: "🔗"; height: inherit; }
+
+.icon-attach.icon-left a:before, .icon-attach.icon-right a:after { content: "📎"; height: inherit; }
+
+i.icon-attach:before { content: "📎"; height: inherit; }
+
+.icon-lock.icon-left a:before, .icon-lock.icon-right a:after { content: "🔒"; height: inherit; }
+
+i.icon-lock:before { content: "🔒"; height: inherit; }
+
+.icon-lock-open.icon-left a:before, .icon-lock-open.icon-right a:after { content: "🔓"; height: inherit; }
+
+i.icon-lock-open:before { content: "🔓"; height: inherit; }
+
+.icon-eye.icon-left a:before, .icon-eye.icon-right a:after { content: "\e70a"; height: inherit; }
+
+i.icon-eye:before { content: "\e70a"; height: inherit; }
+
+.icon-tag.icon-left a:before, .icon-tag.icon-right a:after { content: "\e70c"; height: inherit; }
+
+i.icon-tag:before { content: "\e70c"; height: inherit; }
+
+.icon-bookmark.icon-left a:before, .icon-bookmark.icon-right a:after { content: "🔖"; height: inherit; }
+
+i.icon-bookmark:before { content: "🔖"; height: inherit; }
+
+.icon-bookmarks.icon-left a:before, .icon-bookmarks.icon-right a:after { content: "📑"; height: inherit; }
+
+i.icon-bookmarks:before { content: "📑"; height: inherit; }
+
+.icon-flag.icon-left a:before, .icon-flag.icon-right a:after { content: "\2691"; height: inherit; }
+
+i.icon-flag:before { content: "\2691"; height: inherit; }
+
+.icon-thumbs-up.icon-left a:before, .icon-thumbs-up.icon-right a:after { content: "👍"; height: inherit; }
+
+i.icon-thumbs-up:before { content: "👍"; height: inherit; }
+
+.icon-thumbs-down.icon-left a:before, .icon-thumbs-down.icon-right a:after { content: "👎"; height: inherit; }
+
+i.icon-thumbs-down:before { content: "👎"; height: inherit; }
+
+.icon-download.icon-left a:before, .icon-download.icon-right a:after { content: "📥"; height: inherit; }
+
+i.icon-download:before { content: "📥"; height: inherit; }
+
+.icon-upload.icon-left a:before, .icon-upload.icon-right a:after { content: "📤"; height: inherit; }
+
+i.icon-upload:before { content: "📤"; height: inherit; }
+
+.icon-upload-cloud.icon-left a:before, .icon-upload-cloud.icon-right a:after { content: "\e711"; height: inherit; }
+
+i.icon-upload-cloud:before { content: "\e711"; height: inherit; }
+
+.icon-reply.icon-left a:before, .icon-reply.icon-right a:after { content: "\e712"; height: inherit; }
+
+i.icon-reply:before { content: "\e712"; height: inherit; }
+
+.icon-reply-all.icon-left a:before, .icon-reply-all.icon-right a:after { content: "\e713"; height: inherit; }
+
+i.icon-reply-all:before { content: "\e713"; height: inherit; }
+
+.icon-forward.icon-left a:before, .icon-forward.icon-right a:after { content: "\27a6"; height: inherit; }
+
+i.icon-forward:before { content: "\27a6"; height: inherit; }
+
+.icon-quote.icon-left a:before, .icon-quote.icon-right a:after { content: "\275e"; height: inherit; }
+
+i.icon-quote:before { content: "\275e"; height: inherit; }
+
+.icon-code.icon-left a:before, .icon-code.icon-right a:after { content: "\e714"; height: inherit; }
+
+i.icon-code:before { content: "\e714"; height: inherit; }
+
+.icon-export.icon-left a:before, .icon-export.icon-right a:after { content: "\e715"; height: inherit; }
+
+i.icon-export:before { content: "\e715"; height: inherit; }
+
+.icon-pencil.icon-left a:before, .icon-pencil.icon-right a:after { content: "\270e"; height: inherit; }
+
+i.icon-pencil:before { content: "\270e"; height: inherit; }
+
+.icon-feather.icon-left a:before, .icon-feather.icon-right a:after { content: "\2712"; height: inherit; }
+
+i.icon-feather:before { content: "\2712"; height: inherit; }
+
+.icon-print.icon-left a:before, .icon-print.icon-right a:after { content: "\e716"; height: inherit; }
+
+i.icon-print:before { content: "\e716"; height: inherit; }
+
+.icon-retweet.icon-left a:before, .icon-retweet.icon-right a:after { content: "\e717"; height: inherit; }
+
+i.icon-retweet:before { content: "\e717"; height: inherit; }
+
+.icon-keyboard.icon-left a:before, .icon-keyboard.icon-right a:after { content: "\2328"; height: inherit; }
+
+i.icon-keyboard:before { content: "\2328"; height: inherit; }
+
+.icon-comment.icon-left a:before, .icon-comment.icon-right a:after { content: "\e718"; height: inherit; }
+
+i.icon-comment:before { content: "\e718"; height: inherit; }
+
+.icon-chat.icon-left a:before, .icon-chat.icon-right a:after { content: "\e720"; height: inherit; }
+
+i.icon-chat:before { content: "\e720"; height: inherit; }
+
+.icon-bell.icon-left a:before, .icon-bell.icon-right a:after { content: "🔔"; height: inherit; }
+
+i.icon-bell:before { content: "🔔"; height: inherit; }
+
+.icon-attention.icon-left a:before, .icon-attention.icon-right a:after { content: "\26a0"; height: inherit; }
+
+i.icon-attention:before { content: "\26a0"; height: inherit; }
+
+.icon-alert.icon-left a:before, .icon-alert.icon-right a:after { content: "💥"; height: inherit; }
+
+i.icon-alert:before { content: "💥"; height: inherit; }
+
+.icon-vcard.icon-left a:before, .icon-vcard.icon-right a:after { content: "\e722"; height: inherit; }
+
+i.icon-vcard:before { content: "\e722"; height: inherit; }
+
+.icon-address.icon-left a:before, .icon-address.icon-right a:after { content: "\e723"; height: inherit; }
+
+i.icon-address:before { content: "\e723"; height: inherit; }
+
+.icon-location.icon-left a:before, .icon-location.icon-right a:after { content: "\e724"; height: inherit; }
+
+i.icon-location:before { content: "\e724"; height: inherit; }
+
+.icon-map.icon-left a:before, .icon-map.icon-right a:after { content: "\e727"; height: inherit; }
+
+i.icon-map:before { content: "\e727"; height: inherit; }
+
+.icon-direction.icon-left a:before, .icon-direction.icon-right a:after { content: "\27a2"; height: inherit; }
+
+i.icon-direction:before { content: "\27a2"; height: inherit; }
+
+.icon-compass.icon-left a:before, .icon-compass.icon-right a:after { content: "\e728"; height: inherit; }
+
+i.icon-compass:before { content: "\e728"; height: inherit; }
+
+.icon-cup.icon-left a:before, .icon-cup.icon-right a:after { content: "\2615"; height: inherit; }
+
+i.icon-cup:before { content: "\2615"; height: inherit; }
+
+.icon-trash.icon-left a:before, .icon-trash.icon-right a:after { content: "\e729"; height: inherit; }
+
+i.icon-trash:before { content: "\e729"; height: inherit; }
+
+.icon-doc.icon-left a:before, .icon-doc.icon-right a:after { content: "\e730"; height: inherit; }
+
+i.icon-doc:before { content: "\e730"; height: inherit; }
+
+.icon-docs.icon-left a:before, .icon-docs.icon-right a:after { content: "\e736"; height: inherit; }
+
+i.icon-docs:before { content: "\e736"; height: inherit; }
+
+.icon-doc-landscape.icon-left a:before, .icon-doc-landscape.icon-right a:after { content: "\e737"; height: inherit; }
+
+i.icon-doc-landscape:before { content: "\e737"; height: inherit; }
+
+.icon-doc-text.icon-left a:before, .icon-doc-text.icon-right a:after { content: "📄"; height: inherit; }
+
+i.icon-doc-text:before { content: "📄"; height: inherit; }
+
+.icon-doc-text-inv.icon-left a:before, .icon-doc-text-inv.icon-right a:after { content: "\e731"; height: inherit; }
+
+i.icon-doc-text-inv:before { content: "\e731"; height: inherit; }
+
+.icon-newspaper.icon-left a:before, .icon-newspaper.icon-right a:after { content: "📰"; height: inherit; }
+
+i.icon-newspaper:before { content: "📰"; height: inherit; }
+
+.icon-book-open.icon-left a:before, .icon-book-open.icon-right a:after { content: "📖"; height: inherit; }
+
+i.icon-book-open:before { content: "📖"; height: inherit; }
+
+.icon-book.icon-left a:before, .icon-book.icon-right a:after { content: "📕"; height: inherit; }
+
+i.icon-book:before { content: "📕"; height: inherit; }
+
+.icon-folder.icon-left a:before, .icon-folder.icon-right a:after { content: "📁"; height: inherit; }
+
+i.icon-folder:before { content: "📁"; height: inherit; }
+
+.icon-archive.icon-left a:before, .icon-archive.icon-right a:after { content: "\e738"; height: inherit; }
+
+i.icon-archive:before { content: "\e738"; height: inherit; }
+
+.icon-box.icon-left a:before, .icon-box.icon-right a:after { content: "📦"; height: inherit; }
+
+i.icon-box:before { content: "📦"; height: inherit; }
+
+.icon-rss.icon-left a:before, .icon-rss.icon-right a:after { content: "\e73a"; height: inherit; }
+
+i.icon-rss:before { content: "\e73a"; height: inherit; }
+
+.icon-phone.icon-left a:before, .icon-phone.icon-right a:after { content: "📞"; height: inherit; }
+
+i.icon-phone:before { content: "📞"; height: inherit; }
+
+.icon-cog.icon-left a:before, .icon-cog.icon-right a:after { content: "\2699"; height: inherit; }
+
+i.icon-cog:before { content: "\2699"; height: inherit; }
+
+.icon-tools.icon-left a:before, .icon-tools.icon-right a:after { content: "\2692"; height: inherit; }
+
+i.icon-tools:before { content: "\2692"; height: inherit; }
+
+.icon-share.icon-left a:before, .icon-share.icon-right a:after { content: "\e73c"; height: inherit; }
+
+i.icon-share:before { content: "\e73c"; height: inherit; }
+
+.icon-shareable.icon-left a:before, .icon-shareable.icon-right a:after { content: "\e73e"; height: inherit; }
+
+i.icon-shareable:before { content: "\e73e"; height: inherit; }
+
+.icon-basket.icon-left a:before, .icon-basket.icon-right a:after { content: "\e73d"; height: inherit; }
+
+i.icon-basket:before { content: "\e73d"; height: inherit; }
+
+.icon-bag.icon-left a:before, .icon-bag.icon-right a:after { content: "👜"; height: inherit; }
+
+i.icon-bag:before { content: "👜"; height: inherit; }
+
+.icon-calendar.icon-left a:before, .icon-calendar.icon-right a:after { content: "📅"; height: inherit; }
+
+i.icon-calendar:before { content: "📅"; height: inherit; }
+
+.icon-login.icon-left a:before, .icon-login.icon-right a:after { content: "\e740"; height: inherit; }
+
+i.icon-login:before { content: "\e740"; height: inherit; }
+
+.icon-logout.icon-left a:before, .icon-logout.icon-right a:after { content: "\e741"; height: inherit; }
+
+i.icon-logout:before { content: "\e741"; height: inherit; }
+
+.icon-mic.icon-left a:before, .icon-mic.icon-right a:after { content: "🎤"; height: inherit; }
+
+i.icon-mic:before { content: "🎤"; height: inherit; }
+
+.icon-mute.icon-left a:before, .icon-mute.icon-right a:after { content: "🔇"; height: inherit; }
+
+i.icon-mute:before { content: "🔇"; height: inherit; }
+
+.icon-sound.icon-left a:before, .icon-sound.icon-right a:after { content: "🔊"; height: inherit; }
+
+i.icon-sound:before { content: "🔊"; height: inherit; }
+
+.icon-volume.icon-left a:before, .icon-volume.icon-right a:after { content: "\e742"; height: inherit; }
+
+i.icon-volume:before { content: "\e742"; height: inherit; }
+
+.icon-clock.icon-left a:before, .icon-clock.icon-right a:after { content: "🕔"; height: inherit; }
+
+i.icon-clock:before { content: "🕔"; height: inherit; }
+
+.icon-hourglass.icon-left a:before, .icon-hourglass.icon-right a:after { content: "\23f3"; height: inherit; }
+
+i.icon-hourglass:before { content: "\23f3"; height: inherit; }
+
+.icon-lamp.icon-left a:before, .icon-lamp.icon-right a:after { content: "💡"; height: inherit; }
+
+i.icon-lamp:before { content: "💡"; height: inherit; }
+
+.icon-light-down.icon-left a:before, .icon-light-down.icon-right a:after { content: "🔅"; height: inherit; }
+
+i.icon-light-down:before { content: "🔅"; height: inherit; }
+
+.icon-light-up.icon-left a:before, .icon-light-up.icon-right a:after { content: "🔆"; height: inherit; }
+
+i.icon-light-up:before { content: "🔆"; height: inherit; }
+
+.icon-adjust.icon-left a:before, .icon-adjust.icon-right a:after { content: "\25d1"; height: inherit; }
+
+i.icon-adjust:before { content: "\25d1"; height: inherit; }
+
+.icon-block.icon-left a:before, .icon-block.icon-right a:after { content: "🚫"; height: inherit; }
+
+i.icon-block:before { content: "🚫"; height: inherit; }
+
+.icon-resize-full.icon-left a:before, .icon-resize-full.icon-right a:after { content: "\e744"; height: inherit; }
+
+i.icon-resize-full:before { content: "\e744"; height: inherit; }
+
+.icon-resize-small.icon-left a:before, .icon-resize-small.icon-right a:after { content: "\e746"; height: inherit; }
+
+i.icon-resize-small:before { content: "\e746"; height: inherit; }
+
+.icon-popup.icon-left a:before, .icon-popup.icon-right a:after { content: "\e74c"; height: inherit; }
+
+i.icon-popup:before { content: "\e74c"; height: inherit; }
+
+.icon-publish.icon-left a:before, .icon-publish.icon-right a:after { content: "\e74d"; height: inherit; }
+
+i.icon-publish:before { content: "\e74d"; height: inherit; }
+
+.icon-window.icon-left a:before, .icon-window.icon-right a:after { content: "\e74e"; height: inherit; }
+
+i.icon-window:before { content: "\e74e"; height: inherit; }
+
+.icon-arrow-combo.icon-left a:before, .icon-arrow-combo.icon-right a:after { content: "\e74f"; height: inherit; }
+
+i.icon-arrow-combo:before { content: "\e74f"; height: inherit; }
+
+.icon-down-circled.icon-left a:before, .icon-down-circled.icon-right a:after { content: "\e758"; height: inherit; }
+
+i.icon-down-circled:before { content: "\e758"; height: inherit; }
+
+.icon-left-circled.icon-left a:before, .icon-left-circled.icon-right a:after { content: "\e759"; height: inherit; }
+
+i.icon-left-circled:before { content: "\e759"; height: inherit; }
+
+.icon-right-circled.icon-left a:before, .icon-right-circled.icon-right a:after { content: "\e75a"; height: inherit; }
+
+i.icon-right-circled:before { content: "\e75a"; height: inherit; }
+
+.icon-up-circled.icon-left a:before, .icon-up-circled.icon-right a:after { content: "\e75b"; height: inherit; }
+
+i.icon-up-circled:before { content: "\e75b"; height: inherit; }
+
+.icon-down-open.icon-left a:before, .icon-down-open.icon-right a:after { content: "\e75c"; height: inherit; }
+
+i.icon-down-open:before { content: "\e75c"; height: inherit; }
+
+.icon-left-open.icon-left a:before, .icon-left-open.icon-right a:after { content: "\e75d"; height: inherit; }
+
+i.icon-left-open:before { content: "\e75d"; height: inherit; }
+
+.icon-right-open.icon-left a:before, .icon-right-open.icon-right a:after { content: "\e75e"; height: inherit; }
+
+i.icon-right-open:before { content: "\e75e"; height: inherit; }
+
+.icon-up-open.icon-left a:before, .icon-up-open.icon-right a:after { content: "\e75f"; height: inherit; }
+
+i.icon-up-open:before { content: "\e75f"; height: inherit; }
+
+.icon-down-open-mini.icon-left a:before, .icon-down-open-mini.icon-right a:after { content: "\e760"; height: inherit; }
+
+i.icon-down-open-mini:before { content: "\e760"; height: inherit; }
+
+.icon-left-open-mini.icon-left a:before, .icon-left-open-mini.icon-right a:after { content: "\e761"; height: inherit; }
+
+i.icon-left-open-mini:before { content: "\e761"; height: inherit; }
+
+.icon-right-open-mini.icon-left a:before, .icon-right-open-mini.icon-right a:after { content: "\e762"; height: inherit; }
+
+i.icon-right-open-mini:before { content: "\e762"; height: inherit; }
+
+.icon-up-open-mini.icon-left a:before, .icon-up-open-mini.icon-right a:after { content: "\e763"; height: inherit; }
+
+i.icon-up-open-mini:before { content: "\e763"; height: inherit; }
+
+.icon-down-open-big.icon-left a:before, .icon-down-open-big.icon-right a:after { content: "\e764"; height: inherit; }
+
+i.icon-down-open-big:before { content: "\e764"; height: inherit; }
+
+.icon-left-open-big.icon-left a:before, .icon-left-open-big.icon-right a:after { content: "\e765"; height: inherit; }
+
+i.icon-left-open-big:before { content: "\e765"; height: inherit; }
+
+.icon-right-open-big.icon-left a:before, .icon-right-open-big.icon-right a:after { content: "\e766"; height: inherit; }
+
+i.icon-right-open-big:before { content: "\e766"; height: inherit; }
+
+.icon-up-open-big.icon-left a:before, .icon-up-open-big.icon-right a:after { content: "\e767"; height: inherit; }
+
+i.icon-up-open-big:before { content: "\e767"; height: inherit; }
+
+.icon-down.icon-left a:before, .icon-down.icon-right a:after { content: "\2b07"; height: inherit; }
+
+i.icon-down:before { content: "\2b07"; height: inherit; }
+
+.icon-arrow-left.icon-left a:before, .icon-arrow-left.icon-right a:after { content: "\2b05"; height: inherit; }
+
+i.icon-arrow-left:before { content: "\2b05"; height: inherit; }
+
+.icon-arrow-right.icon-left a:before, .icon-arrow-right.icon-right a:after { content: "\27a1"; height: inherit; }
+
+i.icon-arrow-right:before { content: "\27a1"; height: inherit; }
+
+.icon-up.icon-left a:before, .icon-up.icon-right a:after { content: "\2b06"; height: inherit; }
+
+i.icon-up:before { content: "\2b06"; height: inherit; }
+
+.icon-down-dir.icon-left a:before, .icon-down-dir.icon-right a:after { content: "\25be"; height: inherit; }
+
+i.icon-down-dir:before { content: "\25be"; height: inherit; }
+
+.icon-left-dir.icon-left a:before, .icon-left-dir.icon-right a:after { content: "\25c2"; height: inherit; }
+
+i.icon-left-dir:before { content: "\25c2"; height: inherit; }
+
+.icon-right-dir.icon-left a:before, .icon-right-dir.icon-right a:after { content: "\25b8"; height: inherit; }
+
+i.icon-right-dir:before { content: "\25b8"; height: inherit; }
+
+.icon-up-dir.icon-left a:before, .icon-up-dir.icon-right a:after { content: "\25b4"; height: inherit; }
+
+i.icon-up-dir:before { content: "\25b4"; height: inherit; }
+
+.icon-down-bold.icon-left a:before, .icon-down-bold.icon-right a:after { content: "\e4b0"; height: inherit; }
+
+i.icon-down-bold:before { content: "\e4b0"; height: inherit; }
+
+.icon-left-bold.icon-left a:before, .icon-left-bold.icon-right a:after { content: "\e4ad"; height: inherit; }
+
+i.icon-left-bold:before { content: "\e4ad"; height: inherit; }
+
+.icon-right-bold.icon-left a:before, .icon-right-bold.icon-right a:after { content: "\e4ae"; height: inherit; }
+
+i.icon-right-bold:before { content: "\e4ae"; height: inherit; }
+
+.icon-up-bold.icon-left a:before, .icon-up-bold.icon-right a:after { content: "\e4af"; height: inherit; }
+
+i.icon-up-bold:before { content: "\e4af"; height: inherit; }
+
+.icon-down-thin.icon-left a:before, .icon-down-thin.icon-right a:after { content: "\2193"; height: inherit; }
+
+i.icon-down-thin:before { content: "\2193"; height: inherit; }
+
+.icon-left-thin.icon-left a:before, .icon-left-thin.icon-right a:after { content: "\2190"; height: inherit; }
+
+i.icon-left-thin:before { content: "\2190"; height: inherit; }
+
+.icon-right-thin.icon-left a:before, .icon-right-thin.icon-right a:after { content: "\2192"; height: inherit; }
+
+i.icon-right-thin:before { content: "\2192"; height: inherit; }
+
+.icon-up-thin.icon-left a:before, .icon-up-thin.icon-right a:after { content: "\2191"; height: inherit; }
+
+i.icon-up-thin:before { content: "\2191"; height: inherit; }
+
+.icon-ccw.icon-left a:before, .icon-ccw.icon-right a:after { content: "\27f2"; height: inherit; }
+
+i.icon-ccw:before { content: "\27f2"; height: inherit; }
+
+.icon-cw.icon-left a:before, .icon-cw.icon-right a:after { content: "\27f3"; height: inherit; }
+
+i.icon-cw:before { content: "\27f3"; height: inherit; }
+
+.icon-arrows-ccw.icon-left a:before, .icon-arrows-ccw.icon-right a:after { content: "🔄"; height: inherit; }
+
+i.icon-arrows-ccw:before { content: "🔄"; height: inherit; }
+
+.icon-level-down.icon-left a:before, .icon-level-down.icon-right a:after { content: "\21b3"; height: inherit; }
+
+i.icon-level-down:before { content: "\21b3"; height: inherit; }
+
+.icon-level-up.icon-left a:before, .icon-level-up.icon-right a:after { content: "\21b0"; height: inherit; }
+
+i.icon-level-up:before { content: "\21b0"; height: inherit; }
+
+.icon-shuffle.icon-left a:before, .icon-shuffle.icon-right a:after { content: "🔀"; height: inherit; }
+
+i.icon-shuffle:before { content: "🔀"; height: inherit; }
+
+.icon-loop.icon-left a:before, .icon-loop.icon-right a:after { content: "🔁"; height: inherit; }
+
+i.icon-loop:before { content: "🔁"; height: inherit; }
+
+.icon-switch.icon-left a:before, .icon-switch.icon-right a:after { content: "\21c6"; height: inherit; }
+
+i.icon-switch:before { content: "\21c6"; height: inherit; }
+
+.icon-play.icon-left a:before, .icon-play.icon-right a:after { content: "\25b6"; height: inherit; }
+
+i.icon-play:before { content: "\25b6"; height: inherit; }
+
+.icon-stop.icon-left a:before, .icon-stop.icon-right a:after { content: "\25a0"; height: inherit; }
+
+i.icon-stop:before { content: "\25a0"; height: inherit; }
+
+.icon-pause.icon-left a:before, .icon-pause.icon-right a:after { content: "\2389"; height: inherit; }
+
+i.icon-pause:before { content: "\2389"; height: inherit; }
+
+.icon-record.icon-left a:before, .icon-record.icon-right a:after { content: "\26ab"; height: inherit; }
+
+i.icon-record:before { content: "\26ab"; height: inherit; }
+
+.icon-to-end.icon-left a:before, .icon-to-end.icon-right a:after { content: "\23ed"; height: inherit; }
+
+i.icon-to-end:before { content: "\23ed"; height: inherit; }
+
+.icon-to-start.icon-left a:before, .icon-to-start.icon-right a:after { content: "\23ee"; height: inherit; }
+
+i.icon-to-start:before { content: "\23ee"; height: inherit; }
+
+.icon-fast-forward.icon-left a:before, .icon-fast-forward.icon-right a:after { content: "\23e9"; height: inherit; }
+
+i.icon-fast-forward:before { content: "\23e9"; height: inherit; }
+
+.icon-fast-backward.icon-left a:before, .icon-fast-backward.icon-right a:after { content: "\23ea"; height: inherit; }
+
+i.icon-fast-backward:before { content: "\23ea"; height: inherit; }
+
+.icon-progress-0.icon-left a:before, .icon-progress-0.icon-right a:after { content: "\e768"; height: inherit; }
+
+i.icon-progress-0:before { content: "\e768"; height: inherit; }
+
+.icon-progress-1.icon-left a:before, .icon-progress-1.icon-right a:after { content: "\e769"; height: inherit; }
+
+i.icon-progress-1:before { content: "\e769"; height: inherit; }
+
+.icon-progress-2.icon-left a:before, .icon-progress-2.icon-right a:after { content: "\e76a"; height: inherit; }
+
+i.icon-progress-2:before { content: "\e76a"; height: inherit; }
+
+.icon-progress-3.icon-left a:before, .icon-progress-3.icon-right a:after { content: "\e76b"; height: inherit; }
+
+i.icon-progress-3:before { content: "\e76b"; height: inherit; }
+
+.icon-target.icon-left a:before, .icon-target.icon-right a:after { content: "🎯"; height: inherit; }
+
+i.icon-target:before { content: "🎯"; height: inherit; }
+
+.icon-palette.icon-left a:before, .icon-palette.icon-right a:after { content: "🎨"; height: inherit; }
+
+i.icon-palette:before { content: "🎨"; height: inherit; }
+
+.icon-list.icon-left a:before, .icon-list.icon-right a:after { content: "\e005"; height: inherit; }
+
+i.icon-list:before { content: "\e005"; height: inherit; }
+
+.icon-list-add.icon-left a:before, .icon-list-add.icon-right a:after { content: "\e003"; height: inherit; }
+
+i.icon-list-add:before { content: "\e003"; height: inherit; }
+
+.icon-signal.icon-left a:before, .icon-signal.icon-right a:after { content: "📶"; height: inherit; }
+
+i.icon-signal:before { content: "📶"; height: inherit; }
+
+.icon-trophy.icon-left a:before, .icon-trophy.icon-right a:after { content: "🏆"; height: inherit; }
+
+i.icon-trophy:before { content: "🏆"; height: inherit; }
+
+.icon-battery.icon-left a:before, .icon-battery.icon-right a:after { content: "🔋"; height: inherit; }
+
+i.icon-battery:before { content: "🔋"; height: inherit; }
+
+.icon-back-in-time.icon-left a:before, .icon-back-in-time.icon-right a:after { content: "\e771"; height: inherit; }
+
+i.icon-back-in-time:before { content: "\e771"; height: inherit; }
+
+.icon-monitor.icon-left a:before, .icon-monitor.icon-right a:after { content: "💻"; height: inherit; }
+
+i.icon-monitor:before { content: "💻"; height: inherit; }
+
+.icon-mobile.icon-left a:before, .icon-mobile.icon-right a:after { content: "📱"; height: inherit; }
+
+i.icon-mobile:before { content: "📱"; height: inherit; }
+
+.icon-network.icon-left a:before, .icon-network.icon-right a:after { content: "\e776"; height: inherit; }
+
+i.icon-network:before { content: "\e776"; height: inherit; }
+
+.icon-cd.icon-left a:before, .icon-cd.icon-right a:after { content: "💿"; height: inherit; }
+
+i.icon-cd:before { content: "💿"; height: inherit; }
+
+.icon-inbox.icon-left a:before, .icon-inbox.icon-right a:after { content: "\e777"; height: inherit; }
+
+i.icon-inbox:before { content: "\e777"; height: inherit; }
+
+.icon-install.icon-left a:before, .icon-install.icon-right a:after { content: "\e778"; height: inherit; }
+
+i.icon-install:before { content: "\e778"; height: inherit; }
+
+.icon-globe.icon-left a:before, .icon-globe.icon-right a:after { content: "🌎"; height: inherit; }
+
+i.icon-globe:before { content: "🌎"; height: inherit; }
+
+.icon-cloud.icon-left a:before, .icon-cloud.icon-right a:after { content: "\2601"; height: inherit; }
+
+i.icon-cloud:before { content: "\2601"; height: inherit; }
+
+.icon-cloud-thunder.icon-left a:before, .icon-cloud-thunder.icon-right a:after { content: "\26c8"; height: inherit; }
+
+i.icon-cloud-thunder:before { content: "\26c8"; height: inherit; }
+
+.icon-flash.icon-left a:before, .icon-flash.icon-right a:after { content: "\26a1"; height: inherit; }
+
+i.icon-flash:before { content: "\26a1"; height: inherit; }
+
+.icon-moon.icon-left a:before, .icon-moon.icon-right a:after { content: "\263d"; height: inherit; }
+
+i.icon-moon:before { content: "\263d"; height: inherit; }
+
+.icon-flight.icon-left a:before, .icon-flight.icon-right a:after { content: "\2708"; height: inherit; }
+
+i.icon-flight:before { content: "\2708"; height: inherit; }
+
+.icon-paper-plane.icon-left a:before, .icon-paper-plane.icon-right a:after { content: "\e79b"; height: inherit; }
+
+i.icon-paper-plane:before { content: "\e79b"; height: inherit; }
+
+.icon-leaf.icon-left a:before, .icon-leaf.icon-right a:after { content: "🍂"; height: inherit; }
+
+i.icon-leaf:before { content: "🍂"; height: inherit; }
+
+.icon-lifebuoy.icon-left a:before, .icon-lifebuoy.icon-right a:after { content: "\e788"; height: inherit; }
+
+i.icon-lifebuoy:before { content: "\e788"; height: inherit; }
+
+.icon-mouse.icon-left a:before, .icon-mouse.icon-right a:after { content: "\e789"; height: inherit; }
+
+i.icon-mouse:before { content: "\e789"; height: inherit; }
+
+.icon-briefcase.icon-left a:before, .icon-briefcase.icon-right a:after { content: "💼"; height: inherit; }
+
+i.icon-briefcase:before { content: "💼"; height: inherit; }
+
+.icon-suitcase.icon-left a:before, .icon-suitcase.icon-right a:after { content: "\e78e"; height: inherit; }
+
+i.icon-suitcase:before { content: "\e78e"; height: inherit; }
+
+.icon-dot.icon-left a:before, .icon-dot.icon-right a:after { content: "\e78b"; height: inherit; }
+
+i.icon-dot:before { content: "\e78b"; height: inherit; }
+
+.icon-dot-2.icon-left a:before, .icon-dot-2.icon-right a:after { content: "\e78c"; height: inherit; }
+
+i.icon-dot-2:before { content: "\e78c"; height: inherit; }
+
+.icon-dot-3.icon-left a:before, .icon-dot-3.icon-right a:after { content: "\e78d"; height: inherit; }
+
+i.icon-dot-3:before { content: "\e78d"; height: inherit; }
+
+.icon-brush.icon-left a:before, .icon-brush.icon-right a:after { content: "\e79a"; height: inherit; }
+
+i.icon-brush:before { content: "\e79a"; height: inherit; }
+
+.icon-magnet.icon-left a:before, .icon-magnet.icon-right a:after { content: "\e7a1"; height: inherit; }
+
+i.icon-magnet:before { content: "\e7a1"; height: inherit; }
+
+.icon-infinity.icon-left a:before, .icon-infinity.icon-right a:after { content: "\221e"; height: inherit; }
+
+i.icon-infinity:before { content: "\221e"; height: inherit; }
+
+.icon-erase.icon-left a:before, .icon-erase.icon-right a:after { content: "\232b"; height: inherit; }
+
+i.icon-erase:before { content: "\232b"; height: inherit; }
+
+.icon-chart-pie.icon-left a:before, .icon-chart-pie.icon-right a:after { content: "\e751"; height: inherit; }
+
+i.icon-chart-pie:before { content: "\e751"; height: inherit; }
+
+.icon-chart-line.icon-left a:before, .icon-chart-line.icon-right a:after { content: "📈"; height: inherit; }
+
+i.icon-chart-line:before { content: "📈"; height: inherit; }
+
+.icon-chart-bar.icon-left a:before, .icon-chart-bar.icon-right a:after { content: "📊"; height: inherit; }
+
+i.icon-chart-bar:before { content: "📊"; height: inherit; }
+
+.icon-chart-area.icon-left a:before, .icon-chart-area.icon-right a:after { content: "🔾"; height: inherit; }
+
+i.icon-chart-area:before { content: "🔾"; height: inherit; }
+
+.icon-tape.icon-left a:before, .icon-tape.icon-right a:after { content: "\2707"; height: inherit; }
+
+i.icon-tape:before { content: "\2707"; height: inherit; }
+
+.icon-graduation-cap.icon-left a:before, .icon-graduation-cap.icon-right a:after { content: "🎓"; height: inherit; }
+
+i.icon-graduation-cap:before { content: "🎓"; height: inherit; }
+
+.icon-language.icon-left a:before, .icon-language.icon-right a:after { content: "\e752"; height: inherit; }
+
+i.icon-language:before { content: "\e752"; height: inherit; }
+
+.icon-ticket.icon-left a:before, .icon-ticket.icon-right a:after { content: "🎫"; height: inherit; }
+
+i.icon-ticket:before { content: "🎫"; height: inherit; }
+
+.icon-water.icon-left a:before, .icon-water.icon-right a:after { content: "💦"; height: inherit; }
+
+i.icon-water:before { content: "💦"; height: inherit; }
+
+.icon-droplet.icon-left a:before, .icon-droplet.icon-right a:after { content: "💧"; height: inherit; }
+
+i.icon-droplet:before { content: "💧"; height: inherit; }
+
+.icon-air.icon-left a:before, .icon-air.icon-right a:after { content: "\e753"; height: inherit; }
+
+i.icon-air:before { content: "\e753"; height: inherit; }
+
+.icon-credit-card.icon-left a:before, .icon-credit-card.icon-right a:after { content: "💳"; height: inherit; }
+
+i.icon-credit-card:before { content: "💳"; height: inherit; }
+
+.icon-floppy.icon-left a:before, .icon-floppy.icon-right a:after { content: "💾"; height: inherit; }
+
+i.icon-floppy:before { content: "💾"; height: inherit; }
+
+.icon-clipboard.icon-left a:before, .icon-clipboard.icon-right a:after { content: "📋"; height: inherit; }
+
+i.icon-clipboard:before { content: "📋"; height: inherit; }
+
+.icon-megaphone.icon-left a:before, .icon-megaphone.icon-right a:after { content: "📣"; height: inherit; }
+
+i.icon-megaphone:before { content: "📣"; height: inherit; }
+
+.icon-database.icon-left a:before, .icon-database.icon-right a:after { content: "\e754"; height: inherit; }
+
+i.icon-database:before { content: "\e754"; height: inherit; }
+
+.icon-drive.icon-left a:before, .icon-drive.icon-right a:after { content: "\e755"; height: inherit; }
+
+i.icon-drive:before { content: "\e755"; height: inherit; }
+
+.icon-bucket.icon-left a:before, .icon-bucket.icon-right a:after { content: "\e756"; height: inherit; }
+
+i.icon-bucket:before { content: "\e756"; height: inherit; }
+
+.icon-thermometer.icon-left a:before, .icon-thermometer.icon-right a:after { content: "\e757"; height: inherit; }
+
+i.icon-thermometer:before { content: "\e757"; height: inherit; }
+
+.icon-key.icon-left a:before, .icon-key.icon-right a:after { content: "🔑"; height: inherit; }
+
+i.icon-key:before { content: "🔑"; height: inherit; }
+
+.icon-flow-cascade.icon-left a:before, .icon-flow-cascade.icon-right a:after { content: "\e790"; height: inherit; }
+
+i.icon-flow-cascade:before { content: "\e790"; height: inherit; }
+
+.icon-flow-branch.icon-left a:before, .icon-flow-branch.icon-right a:after { content: "\e791"; height: inherit; }
+
+i.icon-flow-branch:before { content: "\e791"; height: inherit; }
+
+.icon-flow-tree.icon-left a:before, .icon-flow-tree.icon-right a:after { content: "\e792"; height: inherit; }
+
+i.icon-flow-tree:before { content: "\e792"; height: inherit; }
+
+.icon-flow-line.icon-left a:before, .icon-flow-line.icon-right a:after { content: "\e793"; height: inherit; }
+
+i.icon-flow-line:before { content: "\e793"; height: inherit; }
+
+.icon-flow-parallel.icon-left a:before, .icon-flow-parallel.icon-right a:after { content: "\e794"; height: inherit; }
+
+i.icon-flow-parallel:before { content: "\e794"; height: inherit; }
+
+.icon-rocket.icon-left a:before, .icon-rocket.icon-right a:after { content: "🚀"; height: inherit; }
+
+i.icon-rocket:before { content: "🚀"; height: inherit; }
+
+.icon-gauge.icon-left a:before, .icon-gauge.icon-right a:after { content: "\e7a2"; height: inherit; }
+
+i.icon-gauge:before { content: "\e7a2"; height: inherit; }
+
+.icon-traffic-cone.icon-left a:before, .icon-traffic-cone.icon-right a:after { content: "\e7a3"; height: inherit; }
+
+i.icon-traffic-cone:before { content: "\e7a3"; height: inherit; }
+
+.icon-cc.icon-left a:before, .icon-cc.icon-right a:after { content: "\e7a5"; height: inherit; }
+
+i.icon-cc:before { content: "\e7a5"; height: inherit; }
+
+.icon-cc-by.icon-left a:before, .icon-cc-by.icon-right a:after { content: "\e7a6"; height: inherit; }
+
+i.icon-cc-by:before { content: "\e7a6"; height: inherit; }
+
+.icon-cc-nc.icon-left a:before, .icon-cc-nc.icon-right a:after { content: "\e7a7"; height: inherit; }
+
+i.icon-cc-nc:before { content: "\e7a7"; height: inherit; }
+
+.icon-cc-nc-eu.icon-left a:before, .icon-cc-nc-eu.icon-right a:after { content: "\e7a8"; height: inherit; }
+
+i.icon-cc-nc-eu:before { content: "\e7a8"; height: inherit; }
+
+.icon-cc-nc-jp.icon-left a:before, .icon-cc-nc-jp.icon-right a:after { content: "\e7a9"; height: inherit; }
+
+i.icon-cc-nc-jp:before { content: "\e7a9"; height: inherit; }
+
+.icon-cc-sa.icon-left a:before, .icon-cc-sa.icon-right a:after { content: "\e7aa"; height: inherit; }
+
+i.icon-cc-sa:before { content: "\e7aa"; height: inherit; }
+
+.icon-cc-nd.icon-left a:before, .icon-cc-nd.icon-right a:after { content: "\e7ab"; height: inherit; }
+
+i.icon-cc-nd:before { content: "\e7ab"; height: inherit; }
+
+.icon-cc-pd.icon-left a:before, .icon-cc-pd.icon-right a:after { content: "\e7ac"; height: inherit; }
+
+i.icon-cc-pd:before { content: "\e7ac"; height: inherit; }
+
+.icon-cc-zero.icon-left a:before, .icon-cc-zero.icon-right a:after { content: "\e7ad"; height: inherit; }
+
+i.icon-cc-zero:before { content: "\e7ad"; height: inherit; }
+
+.icon-cc-share.icon-left a:before, .icon-cc-share.icon-right a:after { content: "\e7ae"; height: inherit; }
+
+i.icon-cc-share:before { content: "\e7ae"; height: inherit; }
+
+.icon-cc-remix.icon-left a:before, .icon-cc-remix.icon-right a:after { content: "\e7af"; height: inherit; }
+
+i.icon-cc-remix:before { content: "\e7af"; height: inherit; }
+
+.icon-github.icon-left a:before, .icon-github.icon-right a:after { content: "\f300"; height: inherit; }
+
+i.icon-github:before { content: "\f300"; height: inherit; }
+
+.icon-github-circled.icon-left a:before, .icon-github-circled.icon-right a:after { content: "\f301"; height: inherit; }
+
+i.icon-github-circled:before { content: "\f301"; height: inherit; }
+
+.icon-flickr.icon-left a:before, .icon-flickr.icon-right a:after { content: "\f303"; height: inherit; }
+
+i.icon-flickr:before { content: "\f303"; height: inherit; }
+
+.icon-flickr-circled.icon-left a:before, .icon-flickr-circled.icon-right a:after { content: "\f304"; height: inherit; }
+
+i.icon-flickr-circled:before { content: "\f304"; height: inherit; }
+
+.icon-vimeo.icon-left a:before, .icon-vimeo.icon-right a:after { content: "\f306"; height: inherit; }
+
+i.icon-vimeo:before { content: "\f306"; height: inherit; }
+
+.icon-vimeo-circled.icon-left a:before, .icon-vimeo-circled.icon-right a:after { content: "\f307"; height: inherit; }
+
+i.icon-vimeo-circled:before { content: "\f307"; height: inherit; }
+
+.icon-twitter.icon-left a:before, .icon-twitter.icon-right a:after { content: "\f309"; height: inherit; }
+
+i.icon-twitter:before { content: "\f309"; height: inherit; }
+
+.icon-twitter-circled.icon-left a:before, .icon-twitter-circled.icon-right a:after { content: "\f30a"; height: inherit; }
+
+i.icon-twitter-circled:before { content: "\f30a"; height: inherit; }
+
+.icon-facebook.icon-left a:before, .icon-facebook.icon-right a:after { content: "\f30c"; height: inherit; }
+
+i.icon-facebook:before { content: "\f30c"; height: inherit; }
+
+.icon-facebook-circled.icon-left a:before, .icon-facebook-circled.icon-right a:after { content: "\f30d"; height: inherit; }
+
+i.icon-facebook-circled:before { content: "\f30d"; height: inherit; }
+
+.icon-facebook-squared.icon-left a:before, .icon-facebook-squared.icon-right a:after { content: "\f30e"; height: inherit; }
+
+i.icon-facebook-squared:before { content: "\f30e"; height: inherit; }
+
+.icon-gplus.icon-left a:before, .icon-gplus.icon-right a:after { content: "\f30f"; height: inherit; }
+
+i.icon-gplus:before { content: "\f30f"; height: inherit; }
+
+.icon-gplus-circled.icon-left a:before, .icon-gplus-circled.icon-right a:after { content: "\f310"; height: inherit; }
+
+i.icon-gplus-circled:before { content: "\f310"; height: inherit; }
+
+.icon-pinterest.icon-left a:before, .icon-pinterest.icon-right a:after { content: "\f312"; height: inherit; }
+
+i.icon-pinterest:before { content: "\f312"; height: inherit; }
+
+.icon-pinterest-circled.icon-left a:before, .icon-pinterest-circled.icon-right a:after { content: "\f313"; height: inherit; }
+
+i.icon-pinterest-circled:before { content: "\f313"; height: inherit; }
+
+.icon-tumblr.icon-left a:before, .icon-tumblr.icon-right a:after { content: "\f315"; height: inherit; }
+
+i.icon-tumblr:before { content: "\f315"; height: inherit; }
+
+.icon-tumblr-circled.icon-left a:before, .icon-tumblr-circled.icon-right a:after { content: "\f316"; height: inherit; }
+
+i.icon-tumblr-circled:before { content: "\f316"; height: inherit; }
+
+.icon-linkedin.icon-left a:before, .icon-linkedin.icon-right a:after { content: "\f318"; height: inherit; }
+
+i.icon-linkedin:before { content: "\f318"; height: inherit; }
+
+.icon-linkedin-circled.icon-left a:before, .icon-linkedin-circled.icon-right a:after { content: "\f319"; height: inherit; }
+
+i.icon-linkedin-circled:before { content: "\f319"; height: inherit; }
+
+.icon-dribbble.icon-left a:before, .icon-dribbble.icon-right a:after { content: "\f31b"; height: inherit; }
+
+i.icon-dribbble:before { content: "\f31b"; height: inherit; }
+
+.icon-dribbble-circled.icon-left a:before, .icon-dribbble-circled.icon-right a:after { content: "\f31c"; height: inherit; }
+
+i.icon-dribbble-circled:before { content: "\f31c"; height: inherit; }
+
+.icon-stumbleupon.icon-left a:before, .icon-stumbleupon.icon-right a:after { content: "\f31e"; height: inherit; }
+
+i.icon-stumbleupon:before { content: "\f31e"; height: inherit; }
+
+.icon-stumbleupon-circled.icon-left a:before, .icon-stumbleupon-circled.icon-right a:after { content: "\f31f"; height: inherit; }
+
+i.icon-stumbleupon-circled:before { content: "\f31f"; height: inherit; }
+
+.icon-lastfm.icon-left a:before, .icon-lastfm.icon-right a:after { content: "\f321"; height: inherit; }
+
+i.icon-lastfm:before { content: "\f321"; height: inherit; }
+
+.icon-lastfm-circled.icon-left a:before, .icon-lastfm-circled.icon-right a:after { content: "\f322"; height: inherit; }
+
+i.icon-lastfm-circled:before { content: "\f322"; height: inherit; }
+
+.icon-rdio.icon-left a:before, .icon-rdio.icon-right a:after { content: "\f324"; height: inherit; }
+
+i.icon-rdio:before { content: "\f324"; height: inherit; }
+
+.icon-rdio-circled.icon-left a:before, .icon-rdio-circled.icon-right a:after { content: "\f325"; height: inherit; }
+
+i.icon-rdio-circled:before { content: "\f325"; height: inherit; }
+
+.icon-spotify.icon-left a:before, .icon-spotify.icon-right a:after { content: "\f327"; height: inherit; }
+
+i.icon-spotify:before { content: "\f327"; height: inherit; }
+
+.icon-spotify-circled.icon-left a:before, .icon-spotify-circled.icon-right a:after { content: "\f328"; height: inherit; }
+
+i.icon-spotify-circled:before { content: "\f328"; height: inherit; }
+
+.icon-qq.icon-left a:before, .icon-qq.icon-right a:after { content: "\f32a"; height: inherit; }
+
+i.icon-qq:before { content: "\f32a"; height: inherit; }
+
+.icon-instagram.icon-left a:before, .icon-instagram.icon-right a:after { content: "\f32d"; height: inherit; }
+
+i.icon-instagram:before { content: "\f32d"; height: inherit; }
+
+.icon-dropbox.icon-left a:before, .icon-dropbox.icon-right a:after { content: "\f330"; height: inherit; }
+
+i.icon-dropbox:before { content: "\f330"; height: inherit; }
+
+.icon-evernote.icon-left a:before, .icon-evernote.icon-right a:after { content: "\f333"; height: inherit; }
+
+i.icon-evernote:before { content: "\f333"; height: inherit; }
+
+.icon-flattr.icon-left a:before, .icon-flattr.icon-right a:after { content: "\f336"; height: inherit; }
+
+i.icon-flattr:before { content: "\f336"; height: inherit; }
+
+.icon-skype.icon-left a:before, .icon-skype.icon-right a:after { content: "\f339"; height: inherit; }
+
+i.icon-skype:before { content: "\f339"; height: inherit; }
+
+.icon-skype-circled.icon-left a:before, .icon-skype-circled.icon-right a:after { content: "\f33a"; height: inherit; }
+
+i.icon-skype-circled:before { content: "\f33a"; height: inherit; }
+
+.icon-renren.icon-left a:before, .icon-renren.icon-right a:after { content: "\f33c"; height: inherit; }
+
+i.icon-renren:before { content: "\f33c"; height: inherit; }
+
+.icon-sina-weibo.icon-left a:before, .icon-sina-weibo.icon-right a:after { content: "\f33f"; height: inherit; }
+
+i.icon-sina-weibo:before { content: "\f33f"; height: inherit; }
+
+.icon-paypal.icon-left a:before, .icon-paypal.icon-right a:after { content: "\f342"; height: inherit; }
+
+i.icon-paypal:before { content: "\f342"; height: inherit; }
+
+.icon-picasa.icon-left a:before, .icon-picasa.icon-right a:after { content: "\f345"; height: inherit; }
+
+i.icon-picasa:before { content: "\f345"; height: inherit; }
+
+.icon-soundcloud.icon-left a:before, .icon-soundcloud.icon-right a:after { content: "\f348"; height: inherit; }
+
+i.icon-soundcloud:before { content: "\f348"; height: inherit; }
+
+.icon-mixi.icon-left a:before, .icon-mixi.icon-right a:after { content: "\f34b"; height: inherit; }
+
+i.icon-mixi:before { content: "\f34b"; height: inherit; }
+
+.icon-behance.icon-left a:before, .icon-behance.icon-right a:after { content: "\f34e"; height: inherit; }
+
+i.icon-behance:before { content: "\f34e"; height: inherit; }
+
+.icon-google-circles.icon-left a:before, .icon-google-circles.icon-right a:after { content: "\f351"; height: inherit; }
+
+i.icon-google-circles:before { content: "\f351"; height: inherit; }
+
+.icon-vkontakte.icon-left a:before, .icon-vkontakte.icon-right a:after { content: "\f354"; height: inherit; }
+
+i.icon-vkontakte:before { content: "\f354"; height: inherit; }
+
+.icon-smashing.icon-left a:before, .icon-smashing.icon-right a:after { content: "\f357"; height: inherit; }
+
+i.icon-smashing:before { content: "\f357"; height: inherit; }
+
+.icon-sweden.icon-left a:before, .icon-sweden.icon-right a:after { content: "\f601"; height: inherit; }
+
+i.icon-sweden:before { content: "\f601"; height: inherit; }
+
+.icon-db-shape.icon-left a:before, .icon-db-shape.icon-right a:after { content: "\f600"; height: inherit; }
+
+i.icon-db-shape:before { content: "\f600"; height: inherit; }
+
+.icon-logo-db.icon-left a:before, .icon-logo-db.icon-right a:after { content: "\f603"; height: inherit; }
+
+i.icon-logo-db:before { content: "\f603"; height: inherit; }
+
+.fixed { position: fixed; }
+.fixed.pinned { position: absolute; }
+@media only screen and (max-width: 768px) { .fixed { position: relative !important; top: auto !important; left: auto !important; } }
+
+.unfixed { position: relative !important; top: auto !important; left: auto !important; }
+
+.text-center { text-align: center; }
+
+.text-left { text-align: left; }
+
+.text-right { text-align: right; }
+
+/* Fonts */
+@font-face { font-family: "entypo"; font-style: normal; font-weight: 400; src: url(../fonts/icons/entypo.eot); src: url("../fonts/icons/entypo.eot?#iefix") format("ie9-skip-eot"), url("../fonts/icons/entypo.woff") format("woff"), url("../fonts/icons/entypo.ttf") format("truetype"); }
+
+@font-face { font-family: "entypo"; font-style: normal; font-weight: 400; src: url("./../../fonts/icons/entypo.eot"); src: url("./../../fonts/icons/entypo.eot?#iefix") format("ie9-skip-eot"), url("./../../fonts/icons/entypo.woff") format("woff"), url("./../../fonts/icons/entypo.ttf") format("truetype"); }
+
+@font-face { font-family: "Merriweather Sans"; font-style: normal; font-weight: 400; src: url("./../../fonts/font_1_pathRegular.ttf") format("truetype"); }
+
+@font-face { font-family: "Merriweather Sans"; font-style: italic; font-weight: 400; src: url("./../../fonts/Merriweather_Sans/MerriweatherSans-Italic.ttf") format("truetype"); }
+
+@font-face { font-family: "Merriweather Sans"; font-style: normal; font-weight: 300; src: url("./../../fonts/Merriweather_Sans/MerriweatherSans-Light.ttf") format("truetype"); }
+
+@font-face { font-family: "Merriweather Sans"; font-style: italic; font-weight: 300; src: url("./../../fonts/Merriweather_Sans/MerriweatherSans-LigthItalic.ttf") format("truetype"); }
+
+@font-face { font-family: "Merriweather Sans"; font-style: normal; font-weight: 700; src: url("./../../fonts/Merriweather_Sans/MerriweatherSans-Bold.ttf") format("truetype"); }
+
+@font-face { font-family: "Merriweather Sans"; font-style: italic; font-weight: 700; src: url("./../../fonts/Merriweather_Sans/MerriweatherSans-BoldItalic.ttf") format("truetype"); }
+
+@font-face { font-family: "Merriweather Sans"; font-style: normal; font-weight: 800; src: url("./../../fonts/Merriweather_Sans/MerriweatherSans-ExtraBold.ttf") format("truetype"); }
+
+@font-face { font-family: "Merriweather Sans"; font-style: italic; font-weight: 800; src: url("./../../fonts/Merriweather_Sans/MerriweatherSans-ExtraBoldItalic.ttf") format("truetype"); }
+
+@font-face { font-family: "Oxygen"; font-style: normal; font-weight: 300; src: url("./../../fonts/Oxygen/Oxygen-Light.ttf") format("truetype"); }
+
+@font-face { font-family: "Oxygen"; font-style: normal; font-weight: 400; src: url("./../../fonts/Oxygen/Oxygen-Regular.ttf") format("truetype"); }
+
+@font-face { font-family: "Oxygen"; font-style: normal; font-weight: 700; src: url("./../../fonts/Oxygen/Oxygen-Bold.ttf") format("truetype"); }
+
+h1, h2, h3, h4, h5, h6 { font-family: "Oxygen"; font-weight: 300; color: #444444; text-rendering: optimizeLegibility; padding-top: 0.273em; line-height: 1.15538em; padding-bottom: 0.273em; }
+h1 a, h2 a, h3 a, h4 a, h5 a, h6 a { color: #d04526; }
+
+@media only screen and (max-width: 767px) { h1, h2, h3, h4, h5, h6 { word-wrap: break-word; } }
+h1 { font-size: 68px; font-size: 4.25rem; }
+h1.xlarge { font-size: 110px; font-size: 6.875rem; }
+h1.xxlarge { font-size: 126px; font-size: 7.875rem; }
+h1.absurd { font-size: 177px; font-size: 11.0625rem; }
+
+h2 { font-size: 42px; font-size: 2.625rem; }
+
+h3 { font-size: 30px; font-size: 1.875rem; }
+
+h4 { font-size: 26px; font-size: 1.625rem; }
+
+h5 { font-size: 18px; font-size: 1.125rem; }
+
+h6 { font-size: 16px; font-size: 1rem; }
+
+@media only screen and (max-width: 767px) { h1 { font-size: 42px; font-size: 2.625rem; }
+ h2 { font-size: 36px; font-size: 2.25rem; } }
+.subhead { color: #777; font-weight: normal; margin-bottom: 20px; }
+
+/*===================================================== Links & Paragraph styles ======================================================*/
+p { font-family: "Oxygen"; font-weight: 400; font-size: 16px; font-size: 1rem; margin-bottom: 13px; line-height: 1.625em; }
+p.lead { font-size: 20px; font-size: 1.25rem; margin-bottom: 18px; }
+@media only screen and (max-width: 768px) { p { font-size: 17.6px; font-size: 1.1rem; line-height: 1.625em; } }
+
+a { color: #d04526; text-decoration: none; outline: 0; line-height: inherit; }
+a:hover { color: #c03d20; }
+
+/*=====================================================
+ Lists ======================================================*/
+ul, ol { margin-bottom: 0.273em; }
+
+ul { list-style: none outside; }
+
+ol { list-style: decimal; margin-left: 30px; }
+
+ul.square, ul.circle, ul.disc { margin-left: 25px; }
+ul.square { list-style: square outside; }
+ul.circle { list-style: circle outside; }
+ul.disc { list-style: disc outside; }
+ul ul { margin: 4px 0 5px 25px; }
+
+ol ol { margin: 4px 0 5px 30px; }
+
+li { padding-bottom: 0.273em; }
+
+ul.large li { line-height: 21px; }
+
+dl dt { font-weight: bold; font-size: 16px; font-size: 1rem; }
+
+@media only screen and (max-width: 768px) { ul, ol, dl, p { text-align: left; } }
+/* Mobile */
+em { font-style: italic; line-height: inherit; }
+
+strong { font-weight: 700; line-height: inherit; }
+
+small { font-size: 56.4%; line-height: inherit; }
+
+h1 small, h2 small, h3 small, h4 small, h5 small { color: #777; }
+
+/* Blockquotes */
+blockquote { line-height: 20px; color: #777; margin: 0 0 18px; padding: 9px 20px 0 19px; border-left: 5px solid #cccccc; }
+blockquote p { line-height: 20px; color: #777; }
+blockquote cite { display: block; font-size: 12px; font-size: 1.2rem; color: #555555; }
+blockquote cite:before { content: "\2014 \0020"; }
+blockquote cite a { color: #555555; }
+blockquote cite a:visited { color: #555555; }
+
+hr { border: 1px solid #cccccc; clear: both; margin: 16px 0 18px; height: 0; }
+
+abbr, acronym { text-transform: uppercase; font-size: 90%; color: #222; border-bottom: 1px solid #cccccc; cursor: help; }
+
+abbr { text-transform: none; }
+
+/** Print styles. Inlined to avoid required HTTP connection: www.phpied.com/delay-loading-your-print-css/ Credit to Paul Irish and HTML5 Boilerplate (html5boilerplate.com) */
+@media print { * { background: transparent !important; color: black !important; text-shadow: none !important; filter: none !important; -ms-filter: none !important; }
+ /* Black prints faster: sanbeiji.com/archives/953 */
+ p a { color: #555555 !important; text-decoration: underline; }
+ p a:visited { color: #555555 !important; text-decoration: underline; }
+ p a[href]:after { content: " (" attr(href) ")"; }
+ abbr[title]:after { content: " (" attr(title) ")"; }
+ .ir a:after { content: ""; }
+ a[href^="javascript:"]:after, a[href^="#"]:after { content: ""; }
+ /* Don't show links for images, or javascript/internal links */
+ pre, blockquote { border: 1px solid #999; page-break-inside: avoid; }
+ thead { display: table-header-group; }
+ /* css-discuss.incutio.com/wiki/Printing_Tables */
+ tr, img { page-break-inside: avoid; }
+ @page { margin: 0.5cm; }
+ p, h2, h3 { orphans: 3; widows: 3; }
+ h2, h3 { page-break-after: avoid; } }
+/*=================================================
+ +++ LE GRID +++ A Responsive Grid -- Gumby defaults to a standard 960 grid, but you can change it to whatever you'd like.
+ ==================================================*/
+/*.container { padding: 0 $gutter-in-px;
+}*/
+.row { width: 100%; max-width: 980px; min-width: 320px; margin: 0 auto; padding-left: 20px; padding-right: 20px; }
+.row .row { min-width: 0; padding-left: 0; padding-right: 0; }
+
+/* To fix the grid into a different size, set max-width to your desired width */
+.column, .columns { margin-left: 2.12766%; float: left; min-height: 1px; -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; }
+
+.column:first-child, .columns:first-child, .alpha { margin-left: 0; }
+
+.column.omega, .columns.omega { float: right; }
+
+/* Column Classes */
+.row .one.column { width: 6.38298%; }
+.row .one.columns { width: 6.38298%; }
+.row .two.columns { width: 14.89362%; }
+.row .three.columns { width: 23.40426%; }
+.row .four.columns { width: 31.91489%; }
+.row .five.columns { width: 40.42553%; }
+.row .six.columns { width: 48.93617%; }
+.row .seven.columns { width: 57.44681%; }
+.row .eight.columns { width: 65.95745%; }
+.row .nine.columns { width: 74.46809%; }
+.row .ten.columns { width: 82.97872%; }
+.row .eleven.columns { width: 91.48936%; }
+.row .twelve.columns { width: 100%; }
+
+/* Push and Pull Classes */
+.row .push_one { margin-left: 10.6383%; }
+.row .push_one:first-child { margin-left: 8.51064%; }
+.row .pull_one.one.column { margin-left: -14.89362%; }
+.row .pull_one.one.column:first-child { margin-left: 0; }
+.row .pull_one.two.columns { margin-left: -23.40426%; }
+.row .pull_one.two.columns:first-child { margin-left: 0; }
+.row .pull_one.three.columns { margin-left: -31.91489%; }
+.row .pull_one.three.columns:first-child { margin-left: 0; }
+.row .pull_one.four.columns { margin-left: -40.42553%; }
+.row .pull_one.four.columns:first-child { margin-left: 0; }
+.row .pull_one.five.columns { margin-left: -48.93617%; }
+.row .pull_one.five.columns:first-child { margin-left: 0; }
+.row .pull_one.six.columns { margin-left: -57.44681%; }
+.row .pull_one.six.columns:first-child { margin-left: 0; }
+.row .pull_one.seven.columns { margin-left: -65.95745%; }
+.row .pull_one.seven.columns:first-child { margin-left: 0; }
+.row .pull_one.eight.columns { margin-left: -74.46809%; }
+.row .pull_one.eight.columns:first-child { margin-left: 0; }
+.row .pull_one.nine.columns { margin-left: -82.97872%; }
+.row .pull_one.nine.columns:first-child { margin-left: 0; }
+.row .pull_one.ten.columns { margin-left: -91.48936%; }
+.row .pull_one.ten.columns:first-child { margin-left: 0; }
+.row .pull_one.eleven.columns { margin-left: -100.0%; }
+.row .pull_one.eleven.columns:first-child { margin-left: 0; }
+.row .push_two { margin-left: 19.14894%; }
+.row .push_two:first-child { margin-left: 17.02128%; }
+.row .pull_two.one.column { margin-left: -23.40426%; }
+.row .pull_two.one.column:first-child { margin-left: 0; }
+.row .pull_two.two.columns { margin-left: -31.91489%; }
+.row .pull_two.two.columns:first-child { margin-left: 0; }
+.row .pull_two.three.columns { margin-left: -40.42553%; }
+.row .pull_two.three.columns:first-child { margin-left: 0; }
+.row .pull_two.four.columns { margin-left: -48.93617%; }
+.row .pull_two.four.columns:first-child { margin-left: 0; }
+.row .pull_two.five.columns { margin-left: -57.44681%; }
+.row .pull_two.five.columns:first-child { margin-left: 0; }
+.row .pull_two.six.columns { margin-left: -65.95745%; }
+.row .pull_two.six.columns:first-child { margin-left: 0; }
+.row .pull_two.seven.columns { margin-left: -74.46809%; }
+.row .pull_two.seven.columns:first-child { margin-left: 0; }
+.row .pull_two.eight.columns { margin-left: -82.97872%; }
+.row .pull_two.eight.columns:first-child { margin-left: 0; }
+.row .pull_two.nine.columns { margin-left: -91.48936%; }
+.row .pull_two.nine.columns:first-child { margin-left: 0; }
+.row .pull_two.ten.columns { margin-left: -100.0%; }
+.row .pull_two.ten.columns:first-child { margin-left: 0; }
+.row .pull_two.eleven.columns { margin-left: -108.51064%; }
+.row .pull_two.eleven.columns:first-child { margin-left: 0; }
+.row .push_three { margin-left: 27.65957%; }
+.row .push_three:first-child { margin-left: 25.53191%; }
+.row .pull_three.one.column { margin-left: -31.91489%; }
+.row .pull_three.one.column:first-child { margin-left: 0; }
+.row .pull_three.two.columns { margin-left: -40.42553%; }
+.row .pull_three.two.columns:first-child { margin-left: 0; }
+.row .pull_three.three.columns { margin-left: -48.93617%; }
+.row .pull_three.three.columns:first-child { margin-left: 0; }
+.row .pull_three.four.columns { margin-left: -57.44681%; }
+.row .pull_three.four.columns:first-child { margin-left: 0; }
+.row .pull_three.five.columns { margin-left: -65.95745%; }
+.row .pull_three.five.columns:first-child { margin-left: 0; }
+.row .pull_three.six.columns { margin-left: -74.46809%; }
+.row .pull_three.six.columns:first-child { margin-left: 0; }
+.row .pull_three.seven.columns { margin-left: -82.97872%; }
+.row .pull_three.seven.columns:first-child { margin-left: 0; }
+.row .pull_three.eight.columns { margin-left: -91.48936%; }
+.row .pull_three.eight.columns:first-child { margin-left: 0; }
+.row .pull_three.nine.columns { margin-left: -100.0%; }
+.row .pull_three.nine.columns:first-child { margin-left: 0; }
+.row .pull_three.ten.columns { margin-left: -108.51064%; }
+.row .pull_three.ten.columns:first-child { margin-left: 0; }
+.row .pull_three.eleven.columns { margin-left: -117.02128%; }
+.row .pull_three.eleven.columns:first-child { margin-left: 0; }
+.row .push_four { margin-left: 36.17021%; }
+.row .push_four:first-child { margin-left: 34.04255%; }
+.row .pull_four.one.column { margin-left: -40.42553%; }
+.row .pull_four.one.column:first-child { margin-left: 0; }
+.row .pull_four.two.columns { margin-left: -48.93617%; }
+.row .pull_four.two.columns:first-child { margin-left: 0; }
+.row .pull_four.three.columns { margin-left: -57.44681%; }
+.row .pull_four.three.columns:first-child { margin-left: 0; }
+.row .pull_four.four.columns { margin-left: -65.95745%; }
+.row .pull_four.four.columns:first-child { margin-left: 0; }
+.row .pull_four.five.columns { margin-left: -74.46809%; }
+.row .pull_four.five.columns:first-child { margin-left: 0; }
+.row .pull_four.six.columns { margin-left: -82.97872%; }
+.row .pull_four.six.columns:first-child { margin-left: 0; }
+.row .pull_four.seven.columns { margin-left: -91.48936%; }
+.row .pull_four.seven.columns:first-child { margin-left: 0; }
+.row .pull_four.eight.columns { margin-left: -100.0%; }
+.row .pull_four.eight.columns:first-child { margin-left: 0; }
+.row .pull_four.nine.columns { margin-left: -108.51064%; }
+.row .pull_four.nine.columns:first-child { margin-left: 0; }
+.row .pull_four.ten.columns { margin-left: -117.02128%; }
+.row .pull_four.ten.columns:first-child { margin-left: 0; }
+.row .pull_four.eleven.columns { margin-left: -125.53191%; }
+.row .pull_four.eleven.columns:first-child { margin-left: 0; }
+.row .push_five { margin-left: 44.68085%; }
+.row .push_five:first-child { margin-left: 42.55319%; }
+.row .pull_five.one.column { margin-left: -48.93617%; }
+.row .pull_five.one.column:first-child { margin-left: 0; }
+.row .pull_five.two.columns { margin-left: -57.44681%; }
+.row .pull_five.two.columns:first-child { margin-left: 0; }
+.row .pull_five.three.columns { margin-left: -65.95745%; }
+.row .pull_five.three.columns:first-child { margin-left: 0; }
+.row .pull_five.four.columns { margin-left: -74.46809%; }
+.row .pull_five.four.columns:first-child { margin-left: 0; }
+.row .pull_five.five.columns { margin-left: -82.97872%; }
+.row .pull_five.five.columns:first-child { margin-left: 0; }
+.row .pull_five.six.columns { margin-left: -91.48936%; }
+.row .pull_five.six.columns:first-child { margin-left: 0; }
+.row .pull_five.seven.columns { margin-left: -100.0%; }
+.row .pull_five.seven.columns:first-child { margin-left: 0; }
+.row .pull_five.eight.columns { margin-left: -108.51064%; }
+.row .pull_five.eight.columns:first-child { margin-left: 0; }
+.row .pull_five.nine.columns { margin-left: -117.02128%; }
+.row .pull_five.nine.columns:first-child { margin-left: 0; }
+.row .pull_five.ten.columns { margin-left: -125.53191%; }
+.row .pull_five.ten.columns:first-child { margin-left: 0; }
+.row .pull_five.eleven.columns { margin-left: -134.04255%; }
+.row .pull_five.eleven.columns:first-child { margin-left: 0; }
+.row .push_six { margin-left: 53.19149%; }
+.row .push_six:first-child { margin-left: 51.06383%; }
+.row .pull_six.one.column { margin-left: -57.44681%; }
+.row .pull_six.one.column:first-child { margin-left: 0; }
+.row .pull_six.two.columns { margin-left: -65.95745%; }
+.row .pull_six.two.columns:first-child { margin-left: 0; }
+.row .pull_six.three.columns { margin-left: -74.46809%; }
+.row .pull_six.three.columns:first-child { margin-left: 0; }
+.row .pull_six.four.columns { margin-left: -82.97872%; }
+.row .pull_six.four.columns:first-child { margin-left: 0; }
+.row .pull_six.five.columns { margin-left: -91.48936%; }
+.row .pull_six.five.columns:first-child { margin-left: 0; }
+.row .pull_six.six.columns { margin-left: -100.0%; }
+.row .pull_six.six.columns:first-child { margin-left: 0; }
+.row .pull_six.seven.columns { margin-left: -108.51064%; }
+.row .pull_six.seven.columns:first-child { margin-left: 0; }
+.row .pull_six.eight.columns { margin-left: -117.02128%; }
+.row .pull_six.eight.columns:first-child { margin-left: 0; }
+.row .pull_six.nine.columns { margin-left: -125.53191%; }
+.row .pull_six.nine.columns:first-child { margin-left: 0; }
+.row .pull_six.ten.columns { margin-left: -134.04255%; }
+.row .pull_six.ten.columns:first-child { margin-left: 0; }
+.row .pull_six.eleven.columns { margin-left: -142.55319%; }
+.row .pull_six.eleven.columns:first-child { margin-left: 0; }
+.row .push_seven { margin-left: 61.70213%; }
+.row .push_seven:first-child { margin-left: 59.57447%; }
+.row .pull_seven.one.column { margin-left: -65.95745%; }
+.row .pull_seven.one.column:first-child { margin-left: 0; }
+.row .pull_seven.two.columns { margin-left: -74.46809%; }
+.row .pull_seven.two.columns:first-child { margin-left: 0; }
+.row .pull_seven.three.columns { margin-left: -82.97872%; }
+.row .pull_seven.three.columns:first-child { margin-left: 0; }
+.row .pull_seven.four.columns { margin-left: -91.48936%; }
+.row .pull_seven.four.columns:first-child { margin-left: 0; }
+.row .pull_seven.five.columns { margin-left: -100.0%; }
+.row .pull_seven.five.columns:first-child { margin-left: 0; }
+.row .pull_seven.six.columns { margin-left: -108.51064%; }
+.row .pull_seven.six.columns:first-child { margin-left: 0; }
+.row .pull_seven.seven.columns { margin-left: -117.02128%; }
+.row .pull_seven.seven.columns:first-child { margin-left: 0; }
+.row .pull_seven.eight.columns { margin-left: -125.53191%; }
+.row .pull_seven.eight.columns:first-child { margin-left: 0; }
+.row .pull_seven.nine.columns { margin-left: -134.04255%; }
+.row .pull_seven.nine.columns:first-child { margin-left: 0; }
+.row .pull_seven.ten.columns { margin-left: -142.55319%; }
+.row .pull_seven.ten.columns:first-child { margin-left: 0; }
+.row .pull_seven.eleven.columns { margin-left: -151.06383%; }
+.row .pull_seven.eleven.columns:first-child { margin-left: 0; }
+.row .push_eight { margin-left: 70.21277%; }
+.row .push_eight:first-child { margin-left: 68.08511%; }
+.row .pull_eight.one.column { margin-left: -74.46809%; }
+.row .pull_eight.one.column:first-child { margin-left: 0; }
+.row .pull_eight.two.columns { margin-left: -82.97872%; }
+.row .pull_eight.two.columns:first-child { margin-left: 0; }
+.row .pull_eight.three.columns { margin-left: -91.48936%; }
+.row .pull_eight.three.columns:first-child { margin-left: 0; }
+.row .pull_eight.four.columns { margin-left: -100.0%; }
+.row .pull_eight.four.columns:first-child { margin-left: 0; }
+.row .pull_eight.five.columns { margin-left: -108.51064%; }
+.row .pull_eight.five.columns:first-child { margin-left: 0; }
+.row .pull_eight.six.columns { margin-left: -117.02128%; }
+.row .pull_eight.six.columns:first-child { margin-left: 0; }
+.row .pull_eight.seven.columns { margin-left: -125.53191%; }
+.row .pull_eight.seven.columns:first-child { margin-left: 0; }
+.row .pull_eight.eight.columns { margin-left: -134.04255%; }
+.row .pull_eight.eight.columns:first-child { margin-left: 0; }
+.row .pull_eight.nine.columns { margin-left: -142.55319%; }
+.row .pull_eight.nine.columns:first-child { margin-left: 0; }
+.row .pull_eight.ten.columns { margin-left: -151.06383%; }
+.row .pull_eight.ten.columns:first-child { margin-left: 0; }
+.row .pull_eight.eleven.columns { margin-left: -159.57447%; }
+.row .pull_eight.eleven.columns:first-child { margin-left: 0; }
+.row .push_nine { margin-left: 78.7234%; }
+.row .push_nine:first-child { margin-left: 76.59574%; }
+.row .pull_nine.one.column { margin-left: -82.97872%; }
+.row .pull_nine.one.column:first-child { margin-left: 0; }
+.row .pull_nine.two.columns { margin-left: -91.48936%; }
+.row .pull_nine.two.columns:first-child { margin-left: 0; }
+.row .pull_nine.three.columns { margin-left: -100.0%; }
+.row .pull_nine.three.columns:first-child { margin-left: 0; }
+.row .pull_nine.four.columns { margin-left: -108.51064%; }
+.row .pull_nine.four.columns:first-child { margin-left: 0; }
+.row .pull_nine.five.columns { margin-left: -117.02128%; }
+.row .pull_nine.five.columns:first-child { margin-left: 0; }
+.row .pull_nine.six.columns { margin-left: -125.53191%; }
+.row .pull_nine.six.columns:first-child { margin-left: 0; }
+.row .pull_nine.seven.columns { margin-left: -134.04255%; }
+.row .pull_nine.seven.columns:first-child { margin-left: 0; }
+.row .pull_nine.eight.columns { margin-left: -142.55319%; }
+.row .pull_nine.eight.columns:first-child { margin-left: 0; }
+.row .pull_nine.nine.columns { margin-left: -151.06383%; }
+.row .pull_nine.nine.columns:first-child { margin-left: 0; }
+.row .pull_nine.ten.columns { margin-left: -159.57447%; }
+.row .pull_nine.ten.columns:first-child { margin-left: 0; }
+.row .pull_nine.eleven.columns { margin-left: -168.08511%; }
+.row .pull_nine.eleven.columns:first-child { margin-left: 0; }
+.row .push_ten { margin-left: 87.23404%; }
+.row .push_ten:first-child { margin-left: 85.10638%; }
+.row .pull_ten.one.column { margin-left: -91.48936%; }
+.row .pull_ten.one.column:first-child { margin-left: 0; }
+.row .pull_ten.two.columns { margin-left: -100.0%; }
+.row .pull_ten.two.columns:first-child { margin-left: 0; }
+.row .pull_ten.three.columns { margin-left: -108.51064%; }
+.row .pull_ten.three.columns:first-child { margin-left: 0; }
+.row .pull_ten.four.columns { margin-left: -117.02128%; }
+.row .pull_ten.four.columns:first-child { margin-left: 0; }
+.row .pull_ten.five.columns { margin-left: -125.53191%; }
+.row .pull_ten.five.columns:first-child { margin-left: 0; }
+.row .pull_ten.six.columns { margin-left: -134.04255%; }
+.row .pull_ten.six.columns:first-child { margin-left: 0; }
+.row .pull_ten.seven.columns { margin-left: -142.55319%; }
+.row .pull_ten.seven.columns:first-child { margin-left: 0; }
+.row .pull_ten.eight.columns { margin-left: -151.06383%; }
+.row .pull_ten.eight.columns:first-child { margin-left: 0; }
+.row .pull_ten.nine.columns { margin-left: -159.57447%; }
+.row .pull_ten.nine.columns:first-child { margin-left: 0; }
+.row .pull_ten.ten.columns { margin-left: -168.08511%; }
+.row .pull_ten.ten.columns:first-child { margin-left: 0; }
+.row .pull_ten.eleven.columns { margin-left: -176.59574%; }
+.row .pull_ten.eleven.columns:first-child { margin-left: 0; }
+.row .push_eleven { margin-left: 95.74468%; }
+.row .push_eleven:first-child { margin-left: 93.61702%; }
+.row .pull_eleven.one.column { margin-left: -100.0%; }
+.row .pull_eleven.one.column:first-child { margin-left: 0; }
+.row .pull_eleven.two.columns { margin-left: -108.51064%; }
+.row .pull_eleven.two.columns:first-child { margin-left: 0; }
+.row .pull_eleven.three.columns { margin-left: -117.02128%; }
+.row .pull_eleven.three.columns:first-child { margin-left: 0; }
+.row .pull_eleven.four.columns { margin-left: -125.53191%; }
+.row .pull_eleven.four.columns:first-child { margin-left: 0; }
+.row .pull_eleven.five.columns { margin-left: -134.04255%; }
+.row .pull_eleven.five.columns:first-child { margin-left: 0; }
+.row .pull_eleven.six.columns { margin-left: -142.55319%; }
+.row .pull_eleven.six.columns:first-child { margin-left: 0; }
+.row .pull_eleven.seven.columns { margin-left: -151.06383%; }
+.row .pull_eleven.seven.columns:first-child { margin-left: 0; }
+.row .pull_eleven.eight.columns { margin-left: -159.57447%; }
+.row .pull_eleven.eight.columns:first-child { margin-left: 0; }
+.row .pull_eleven.nine.columns { margin-left: -168.08511%; }
+.row .pull_eleven.nine.columns:first-child { margin-left: 0; }
+.row .pull_eleven.ten.columns { margin-left: -176.59574%; }
+.row .pull_eleven.ten.columns:first-child { margin-left: 0; }
+.row .pull_eleven.eleven.columns { margin-left: -185.10638%; }
+.row .pull_eleven.eleven.columns:first-child { margin-left: 0; }
+
+/* Centered Classes */
+.row .one.centered { margin-left: 46.80851%; }
+.row .two.centered { margin-left: 42.55319%; }
+.row .three.centered { margin-left: 38.29787%; }
+.row .four.centered { margin-left: 34.04255%; }
+.row .five.centered { margin-left: 29.78723%; }
+.row .six.centered { margin-left: 25.53191%; }
+.row .seven.centered { margin-left: 21.2766%; }
+.row .eight.centered { margin-left: 17.02128%; }
+.row .nine.centered { margin-left: 12.76596%; }
+.row .ten.centered { margin-left: 8.51064%; }
+.row .eleven.centered { margin-left: 4.25532%; }
+
+/* Hybrid Grid Columns */
+.sixteen.colgrid .row .one.column { width: 4.25532%; }
+.sixteen.colgrid .row .one.columns { width: 4.25532%; }
+.sixteen.colgrid .row .two.columns { width: 10.6383%; }
+.sixteen.colgrid .row .three.columns { width: 17.02128%; }
+.sixteen.colgrid .row .four.columns { width: 23.40426%; }
+.sixteen.colgrid .row .five.columns { width: 29.78723%; }
+.sixteen.colgrid .row .six.columns { width: 36.17021%; }
+.sixteen.colgrid .row .seven.columns { width: 42.55319%; }
+.sixteen.colgrid .row .eight.columns { width: 48.93617%; }
+.sixteen.colgrid .row .nine.columns { width: 55.31915%; }
+.sixteen.colgrid .row .ten.columns { width: 61.70213%; }
+.sixteen.colgrid .row .eleven.columns { width: 68.08511%; }
+.sixteen.colgrid .row .twelve.columns { width: 74.46809%; }
+.sixteen.colgrid .row .thirteen.columns { width: 80.85106%; }
+.sixteen.colgrid .row .fourteen.columns { width: 87.23404%; }
+.sixteen.colgrid .row .fifteen.columns { width: 93.61702%; }
+.sixteen.colgrid .row .sixteen.columns { width: 100%; }
+
+/* Hybrid Push and Pull Classes */
+.sixteen.colgrid .row .push_one { margin-left: 8.51064%; }
+.sixteen.colgrid .row .push_one:first-child { margin-left: 6.38298%; }
+.sixteen.colgrid .row .pull_one.one.column { margin-left: -10.6383%; }
+.sixteen.colgrid .row .pull_one.one.column:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_one.two.columns { margin-left: -17.02128%; }
+.sixteen.colgrid .row .pull_one.two.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_one.three.columns { margin-left: -23.40426%; }
+.sixteen.colgrid .row .pull_one.three.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_one.four.columns { margin-left: -29.78723%; }
+.sixteen.colgrid .row .pull_one.four.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_one.five.columns { margin-left: -36.17021%; }
+.sixteen.colgrid .row .pull_one.five.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_one.six.columns { margin-left: -42.55319%; }
+.sixteen.colgrid .row .pull_one.six.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_one.seven.columns { margin-left: -48.93617%; }
+.sixteen.colgrid .row .pull_one.seven.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_one.eight.columns { margin-left: -55.31915%; }
+.sixteen.colgrid .row .pull_one.eight.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_one.nine.columns { margin-left: -61.70213%; }
+.sixteen.colgrid .row .pull_one.nine.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_one.ten.columns { margin-left: -68.08511%; }
+.sixteen.colgrid .row .pull_one.ten.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_one.eleven.columns { margin-left: -74.46809%; }
+.sixteen.colgrid .row .pull_one.eleven.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_one.twelve.columns { margin-left: -80.85106%; }
+.sixteen.colgrid .row .pull_one.twelve.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_one.thirteen.columns { margin-left: -87.23404%; }
+.sixteen.colgrid .row .pull_one.thirteen.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_one.fourteen.columns { margin-left: -93.61702%; }
+.sixteen.colgrid .row .pull_one.fourteen.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_one.fifteen.columns { margin-left: -100%; }
+.sixteen.colgrid .row .pull_one.fifteen.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .push_two { margin-left: 14.89362%; }
+.sixteen.colgrid .row .push_two:first-child { margin-left: 12.76596%; }
+.sixteen.colgrid .row .pull_two.one.column { margin-left: -17.02128%; }
+.sixteen.colgrid .row .pull_two.one.column:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_two.two.columns { margin-left: -23.40426%; }
+.sixteen.colgrid .row .pull_two.two.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_two.three.columns { margin-left: -29.78723%; }
+.sixteen.colgrid .row .pull_two.three.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_two.four.columns { margin-left: -36.17021%; }
+.sixteen.colgrid .row .pull_two.four.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_two.five.columns { margin-left: -42.55319%; }
+.sixteen.colgrid .row .pull_two.five.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_two.six.columns { margin-left: -48.93617%; }
+.sixteen.colgrid .row .pull_two.six.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_two.seven.columns { margin-left: -55.31915%; }
+.sixteen.colgrid .row .pull_two.seven.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_two.eight.columns { margin-left: -61.70213%; }
+.sixteen.colgrid .row .pull_two.eight.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_two.nine.columns { margin-left: -68.08511%; }
+.sixteen.colgrid .row .pull_two.nine.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_two.ten.columns { margin-left: -74.46809%; }
+.sixteen.colgrid .row .pull_two.ten.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_two.eleven.columns { margin-left: -80.85106%; }
+.sixteen.colgrid .row .pull_two.eleven.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_two.twelve.columns { margin-left: -87.23404%; }
+.sixteen.colgrid .row .pull_two.twelve.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_two.thirteen.columns { margin-left: -93.61702%; }
+.sixteen.colgrid .row .pull_two.thirteen.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_two.fourteen.columns { margin-left: -100%; }
+.sixteen.colgrid .row .pull_two.fourteen.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_two.fifteen.columns { margin-left: -106.38298%; }
+.sixteen.colgrid .row .pull_two.fifteen.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .push_three { margin-left: 21.2766%; }
+.sixteen.colgrid .row .push_three:first-child { margin-left: 19.14894%; }
+.sixteen.colgrid .row .pull_three.one.column { margin-left: -23.40426%; }
+.sixteen.colgrid .row .pull_three.one.column:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_three.two.columns { margin-left: -29.78723%; }
+.sixteen.colgrid .row .pull_three.two.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_three.three.columns { margin-left: -36.17021%; }
+.sixteen.colgrid .row .pull_three.three.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_three.four.columns { margin-left: -42.55319%; }
+.sixteen.colgrid .row .pull_three.four.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_three.five.columns { margin-left: -48.93617%; }
+.sixteen.colgrid .row .pull_three.five.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_three.six.columns { margin-left: -55.31915%; }
+.sixteen.colgrid .row .pull_three.six.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_three.seven.columns { margin-left: -61.70213%; }
+.sixteen.colgrid .row .pull_three.seven.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_three.eight.columns { margin-left: -68.08511%; }
+.sixteen.colgrid .row .pull_three.eight.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_three.nine.columns { margin-left: -74.46809%; }
+.sixteen.colgrid .row .pull_three.nine.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_three.ten.columns { margin-left: -80.85106%; }
+.sixteen.colgrid .row .pull_three.ten.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_three.eleven.columns { margin-left: -87.23404%; }
+.sixteen.colgrid .row .pull_three.eleven.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_three.twelve.columns { margin-left: -93.61702%; }
+.sixteen.colgrid .row .pull_three.twelve.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_three.thirteen.columns { margin-left: -100%; }
+.sixteen.colgrid .row .pull_three.thirteen.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_three.fourteen.columns { margin-left: -106.38298%; }
+.sixteen.colgrid .row .pull_three.fourteen.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_three.fifteen.columns { margin-left: -112.76596%; }
+.sixteen.colgrid .row .pull_three.fifteen.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .push_four { margin-left: 27.65957%; }
+.sixteen.colgrid .row .push_four:first-child { margin-left: 25.53191%; }
+.sixteen.colgrid .row .pull_four.one.column { margin-left: -29.78723%; }
+.sixteen.colgrid .row .pull_four.one.column:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_four.two.columns { margin-left: -36.17021%; }
+.sixteen.colgrid .row .pull_four.two.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_four.three.columns { margin-left: -42.55319%; }
+.sixteen.colgrid .row .pull_four.three.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_four.four.columns { margin-left: -48.93617%; }
+.sixteen.colgrid .row .pull_four.four.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_four.five.columns { margin-left: -55.31915%; }
+.sixteen.colgrid .row .pull_four.five.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_four.six.columns { margin-left: -61.70213%; }
+.sixteen.colgrid .row .pull_four.six.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_four.seven.columns { margin-left: -68.08511%; }
+.sixteen.colgrid .row .pull_four.seven.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_four.eight.columns { margin-left: -74.46809%; }
+.sixteen.colgrid .row .pull_four.eight.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_four.nine.columns { margin-left: -80.85106%; }
+.sixteen.colgrid .row .pull_four.nine.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_four.ten.columns { margin-left: -87.23404%; }
+.sixteen.colgrid .row .pull_four.ten.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_four.eleven.columns { margin-left: -93.61702%; }
+.sixteen.colgrid .row .pull_four.eleven.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_four.twelve.columns { margin-left: -100%; }
+.sixteen.colgrid .row .pull_four.twelve.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_four.thirteen.columns { margin-left: -106.38298%; }
+.sixteen.colgrid .row .pull_four.thirteen.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_four.fourteen.columns { margin-left: -112.76596%; }
+.sixteen.colgrid .row .pull_four.fourteen.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_four.fifteen.columns { margin-left: -119.14894%; }
+.sixteen.colgrid .row .pull_four.fifteen.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .push_five { margin-left: 34.04255%; }
+.sixteen.colgrid .row .push_five:first-child { margin-left: 31.91489%; }
+.sixteen.colgrid .row .pull_five.one.column { margin-left: -36.17021%; }
+.sixteen.colgrid .row .pull_five.one.column:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_five.two.columns { margin-left: -42.55319%; }
+.sixteen.colgrid .row .pull_five.two.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_five.three.columns { margin-left: -48.93617%; }
+.sixteen.colgrid .row .pull_five.three.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_five.four.columns { margin-left: -55.31915%; }
+.sixteen.colgrid .row .pull_five.four.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_five.five.columns { margin-left: -61.70213%; }
+.sixteen.colgrid .row .pull_five.five.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_five.six.columns { margin-left: -68.08511%; }
+.sixteen.colgrid .row .pull_five.six.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_five.seven.columns { margin-left: -74.46809%; }
+.sixteen.colgrid .row .pull_five.seven.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_five.eight.columns { margin-left: -80.85106%; }
+.sixteen.colgrid .row .pull_five.eight.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_five.nine.columns { margin-left: -87.23404%; }
+.sixteen.colgrid .row .pull_five.nine.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_five.ten.columns { margin-left: -93.61702%; }
+.sixteen.colgrid .row .pull_five.ten.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_five.eleven.columns { margin-left: -100%; }
+.sixteen.colgrid .row .pull_five.eleven.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_five.twelve.columns { margin-left: -106.38298%; }
+.sixteen.colgrid .row .pull_five.twelve.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_five.thirteen.columns { margin-left: -112.76596%; }
+.sixteen.colgrid .row .pull_five.thirteen.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_five.fourteen.columns { margin-left: -119.14894%; }
+.sixteen.colgrid .row .pull_five.fourteen.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_five.fifteen.columns { margin-left: -125.53191%; }
+.sixteen.colgrid .row .pull_five.fifteen.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .push_six { margin-left: 40.42553%; }
+.sixteen.colgrid .row .push_six:first-child { margin-left: 38.29787%; }
+.sixteen.colgrid .row .pull_six.one.column { margin-left: -42.55319%; }
+.sixteen.colgrid .row .pull_six.one.column:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_six.two.columns { margin-left: -48.93617%; }
+.sixteen.colgrid .row .pull_six.two.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_six.three.columns { margin-left: -55.31915%; }
+.sixteen.colgrid .row .pull_six.three.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_six.four.columns { margin-left: -61.70213%; }
+.sixteen.colgrid .row .pull_six.four.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_six.five.columns { margin-left: -68.08511%; }
+.sixteen.colgrid .row .pull_six.five.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_six.six.columns { margin-left: -74.46809%; }
+.sixteen.colgrid .row .pull_six.six.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_six.seven.columns { margin-left: -80.85106%; }
+.sixteen.colgrid .row .pull_six.seven.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_six.eight.columns { margin-left: -87.23404%; }
+.sixteen.colgrid .row .pull_six.eight.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_six.nine.columns { margin-left: -93.61702%; }
+.sixteen.colgrid .row .pull_six.nine.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_six.ten.columns { margin-left: -100.0%; }
+.sixteen.colgrid .row .pull_six.ten.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_six.eleven.columns { margin-left: -106.38298%; }
+.sixteen.colgrid .row .pull_six.eleven.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_six.twelve.columns { margin-left: -112.76596%; }
+.sixteen.colgrid .row .pull_six.twelve.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_six.thirteen.columns { margin-left: -119.14894%; }
+.sixteen.colgrid .row .pull_six.thirteen.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_six.fourteen.columns { margin-left: -125.53191%; }
+.sixteen.colgrid .row .pull_six.fourteen.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_six.fifteen.columns { margin-left: -131.91489%; }
+.sixteen.colgrid .row .pull_six.fifteen.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .push_seven { margin-left: 46.80851%; }
+.sixteen.colgrid .row .push_seven:first-child { margin-left: 44.68085%; }
+.sixteen.colgrid .row .pull_seven.one.column { margin-left: -48.93617%; }
+.sixteen.colgrid .row .pull_seven.one.column:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_seven.two.columns { margin-left: -55.31915%; }
+.sixteen.colgrid .row .pull_seven.two.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_seven.three.columns { margin-left: -61.70213%; }
+.sixteen.colgrid .row .pull_seven.three.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_seven.four.columns { margin-left: -68.08511%; }
+.sixteen.colgrid .row .pull_seven.four.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_seven.five.columns { margin-left: -74.46809%; }
+.sixteen.colgrid .row .pull_seven.five.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_seven.six.columns { margin-left: -80.85106%; }
+.sixteen.colgrid .row .pull_seven.six.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_seven.seven.columns { margin-left: -87.23404%; }
+.sixteen.colgrid .row .pull_seven.seven.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_seven.eight.columns { margin-left: -93.61702%; }
+.sixteen.colgrid .row .pull_seven.eight.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_seven.nine.columns { margin-left: -100.0%; }
+.sixteen.colgrid .row .pull_seven.nine.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_seven.ten.columns { margin-left: -106.38298%; }
+.sixteen.colgrid .row .pull_seven.ten.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_seven.eleven.columns { margin-left: -112.76596%; }
+.sixteen.colgrid .row .pull_seven.eleven.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_seven.twelve.columns { margin-left: -119.14894%; }
+.sixteen.colgrid .row .pull_seven.twelve.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_seven.thirteen.columns { margin-left: -125.53191%; }
+.sixteen.colgrid .row .pull_seven.thirteen.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_seven.fourteen.columns { margin-left: -131.91489%; }
+.sixteen.colgrid .row .pull_seven.fourteen.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_seven.fifteen.columns { margin-left: -138.29787%; }
+.sixteen.colgrid .row .pull_seven.fifteen.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .push_eight { margin-left: 53.19149%; }
+.sixteen.colgrid .row .push_eight:first-child { margin-left: 51.06383%; }
+.sixteen.colgrid .row .pull_eight.one.column { margin-left: -55.31915%; }
+.sixteen.colgrid .row .pull_eight.one.column:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_eight.two.columns { margin-left: -61.70213%; }
+.sixteen.colgrid .row .pull_eight.two.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_eight.three.columns { margin-left: -68.08511%; }
+.sixteen.colgrid .row .pull_eight.three.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_eight.four.columns { margin-left: -74.46809%; }
+.sixteen.colgrid .row .pull_eight.four.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_eight.five.columns { margin-left: -80.85106%; }
+.sixteen.colgrid .row .pull_eight.five.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_eight.six.columns { margin-left: -87.23404%; }
+.sixteen.colgrid .row .pull_eight.six.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_eight.seven.columns { margin-left: -93.61702%; }
+.sixteen.colgrid .row .pull_eight.seven.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_eight.eight.columns { margin-left: -100%; }
+.sixteen.colgrid .row .pull_eight.eight.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_eight.nine.columns { margin-left: -106.38298%; }
+.sixteen.colgrid .row .pull_eight.nine.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_eight.ten.columns { margin-left: -112.76596%; }
+.sixteen.colgrid .row .pull_eight.ten.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_eight.eleven.columns { margin-left: -119.14894%; }
+.sixteen.colgrid .row .pull_eight.eleven.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_eight.twelve.columns { margin-left: -125.53191%; }
+.sixteen.colgrid .row .pull_eight.twelve.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_eight.thirteen.columns { margin-left: -131.91489%; }
+.sixteen.colgrid .row .pull_eight.thirteen.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_eight.fourteen.columns { margin-left: -138.29787%; }
+.sixteen.colgrid .row .pull_eight.fourteen.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_eight.fifteen.columns { margin-left: -144.68085%; }
+.sixteen.colgrid .row .pull_eight.fifteen.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .push_nine { margin-left: 59.57447%; }
+.sixteen.colgrid .row .push_nine:first-child { margin-left: 57.44681%; }
+.sixteen.colgrid .row .pull_nine.one.column { margin-left: -61.70213%; }
+.sixteen.colgrid .row .pull_nine.one.column:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_nine.two.columns { margin-left: -68.08511%; }
+.sixteen.colgrid .row .pull_nine.two.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_nine.three.columns { margin-left: -74.46809%; }
+.sixteen.colgrid .row .pull_nine.three.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_nine.four.columns { margin-left: -80.85106%; }
+.sixteen.colgrid .row .pull_nine.four.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_nine.five.columns { margin-left: -87.23404%; }
+.sixteen.colgrid .row .pull_nine.five.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_nine.six.columns { margin-left: -93.61702%; }
+.sixteen.colgrid .row .pull_nine.six.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_nine.seven.columns { margin-left: -100%; }
+.sixteen.colgrid .row .pull_nine.seven.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_nine.eight.columns { margin-left: -106.38298%; }
+.sixteen.colgrid .row .pull_nine.eight.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_nine.nine.columns { margin-left: -112.76596%; }
+.sixteen.colgrid .row .pull_nine.nine.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_nine.ten.columns { margin-left: -119.14894%; }
+.sixteen.colgrid .row .pull_nine.ten.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_nine.eleven.columns { margin-left: -125.53191%; }
+.sixteen.colgrid .row .pull_nine.eleven.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_nine.twelve.columns { margin-left: -131.91489%; }
+.sixteen.colgrid .row .pull_nine.twelve.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_nine.thirteen.columns { margin-left: -138.29787%; }
+.sixteen.colgrid .row .pull_nine.thirteen.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_nine.fourteen.columns { margin-left: -144.68085%; }
+.sixteen.colgrid .row .pull_nine.fourteen.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_nine.fifteen.columns { margin-left: -151.06383%; }
+.sixteen.colgrid .row .pull_nine.fifteen.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .push_ten { margin-left: 65.95745%; }
+.sixteen.colgrid .row .push_ten:first-child { margin-left: 63.82979%; }
+.sixteen.colgrid .row .pull_ten.one.column { margin-left: -68.08511%; }
+.sixteen.colgrid .row .pull_ten.one.column:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_ten.two.columns { margin-left: -74.46809%; }
+.sixteen.colgrid .row .pull_ten.two.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_ten.three.columns { margin-left: -80.85106%; }
+.sixteen.colgrid .row .pull_ten.three.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_ten.four.columns { margin-left: -87.23404%; }
+.sixteen.colgrid .row .pull_ten.four.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_ten.five.columns { margin-left: -93.61702%; }
+.sixteen.colgrid .row .pull_ten.five.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_ten.six.columns { margin-left: -100%; }
+.sixteen.colgrid .row .pull_ten.six.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_ten.seven.columns { margin-left: -106.38298%; }
+.sixteen.colgrid .row .pull_ten.seven.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_ten.eight.columns { margin-left: -112.76596%; }
+.sixteen.colgrid .row .pull_ten.eight.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_ten.nine.columns { margin-left: -119.14894%; }
+.sixteen.colgrid .row .pull_ten.nine.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_ten.ten.columns { margin-left: -125.53191%; }
+.sixteen.colgrid .row .pull_ten.ten.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_ten.eleven.columns { margin-left: -131.91489%; }
+.sixteen.colgrid .row .pull_ten.eleven.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_ten.twelve.columns { margin-left: -138.29787%; }
+.sixteen.colgrid .row .pull_ten.twelve.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_ten.thirteen.columns { margin-left: -144.68085%; }
+.sixteen.colgrid .row .pull_ten.thirteen.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_ten.fourteen.columns { margin-left: -151.06383%; }
+.sixteen.colgrid .row .pull_ten.fourteen.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_ten.fifteen.columns { margin-left: -157.44681%; }
+.sixteen.colgrid .row .pull_ten.fifteen.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .push_eleven { margin-left: 72.34043%; }
+.sixteen.colgrid .row .push_eleven:first-child { margin-left: 70.21277%; }
+.sixteen.colgrid .row .pull_eleven.one.column { margin-left: -74.46809%; }
+.sixteen.colgrid .row .pull_eleven.one.column:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_eleven.two.columns { margin-left: -80.85106%; }
+.sixteen.colgrid .row .pull_eleven.two.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_eleven.three.columns { margin-left: -87.23404%; }
+.sixteen.colgrid .row .pull_eleven.three.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_eleven.four.columns { margin-left: -93.61702%; }
+.sixteen.colgrid .row .pull_eleven.four.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_eleven.five.columns { margin-left: -100%; }
+.sixteen.colgrid .row .pull_eleven.five.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_eleven.six.columns { margin-left: -106.38298%; }
+.sixteen.colgrid .row .pull_eleven.six.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_eleven.seven.columns { margin-left: -112.76596%; }
+.sixteen.colgrid .row .pull_eleven.seven.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_eleven.eight.columns { margin-left: -119.14894%; }
+.sixteen.colgrid .row .pull_eleven.eight.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_eleven.nine.columns { margin-left: -125.53191%; }
+.sixteen.colgrid .row .pull_eleven.nine.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_eleven.ten.columns { margin-left: -131.91489%; }
+.sixteen.colgrid .row .pull_eleven.ten.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_eleven.eleven.columns { margin-left: -138.29787%; }
+.sixteen.colgrid .row .pull_eleven.eleven.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_eleven.twelve.columns { margin-left: -144.68085%; }
+.sixteen.colgrid .row .pull_eleven.twelve.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_eleven.thirteen.columns { margin-left: -151.06383%; }
+.sixteen.colgrid .row .pull_eleven.thirteen.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_eleven.fourteen.columns { margin-left: -157.44681%; }
+.sixteen.colgrid .row .pull_eleven.fourteen.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_eleven.fifteen.columns { margin-left: -163.82979%; }
+.sixteen.colgrid .row .pull_eleven.fifteen.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .push_twelve { margin-left: 78.7234%; }
+.sixteen.colgrid .row .push_twelve:first-child { margin-left: 76.59574%; }
+.sixteen.colgrid .row .pull_twelve.one.column { margin-left: -80.85106%; }
+.sixteen.colgrid .row .pull_twelve.one.column:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_twelve.two.columns { margin-left: -87.23404%; }
+.sixteen.colgrid .row .pull_twelve.two.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_twelve.three.columns { margin-left: -93.61702%; }
+.sixteen.colgrid .row .pull_twelve.three.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_twelve.four.columns { margin-left: -100.0%; }
+.sixteen.colgrid .row .pull_twelve.four.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_twelve.five.columns { margin-left: -106.38298%; }
+.sixteen.colgrid .row .pull_twelve.five.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_twelve.six.columns { margin-left: -112.76596%; }
+.sixteen.colgrid .row .pull_twelve.six.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_twelve.seven.columns { margin-left: -119.14894%; }
+.sixteen.colgrid .row .pull_twelve.seven.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_twelve.eight.columns { margin-left: -125.53191%; }
+.sixteen.colgrid .row .pull_twelve.eight.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_twelve.nine.columns { margin-left: -131.91489%; }
+.sixteen.colgrid .row .pull_twelve.nine.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_twelve.ten.columns { margin-left: -138.29787%; }
+.sixteen.colgrid .row .pull_twelve.ten.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_twelve.eleven.columns { margin-left: -144.68085%; }
+.sixteen.colgrid .row .pull_twelve.eleven.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_twelve.twelve.columns { margin-left: -151.06383%; }
+.sixteen.colgrid .row .pull_twelve.twelve.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_twelve.thirteen.columns { margin-left: -157.44681%; }
+.sixteen.colgrid .row .pull_twelve.thirteen.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_twelve.fourteen.columns { margin-left: -163.82979%; }
+.sixteen.colgrid .row .pull_twelve.fourteen.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_twelve.fifteen.columns { margin-left: -170.21277%; }
+.sixteen.colgrid .row .pull_twelve.fifteen.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .push_thirteen { margin-left: 85.10638%; }
+.sixteen.colgrid .row .push_thirteen:first-child { margin-left: 82.97872%; }
+.sixteen.colgrid .row .pull_thirteen.one.column { margin-left: -87.23404%; }
+.sixteen.colgrid .row .pull_thirteen.one.column:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_thirteen.two.columns { margin-left: -93.61702%; }
+.sixteen.colgrid .row .pull_thirteen.two.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_thirteen.three.columns { margin-left: -100.0%; }
+.sixteen.colgrid .row .pull_thirteen.three.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_thirteen.four.columns { margin-left: -106.38298%; }
+.sixteen.colgrid .row .pull_thirteen.four.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_thirteen.five.columns { margin-left: -112.76596%; }
+.sixteen.colgrid .row .pull_thirteen.five.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_thirteen.six.columns { margin-left: -119.14894%; }
+.sixteen.colgrid .row .pull_thirteen.six.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_thirteen.seven.columns { margin-left: -125.53191%; }
+.sixteen.colgrid .row .pull_thirteen.seven.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_thirteen.eight.columns { margin-left: -131.91489%; }
+.sixteen.colgrid .row .pull_thirteen.eight.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_thirteen.nine.columns { margin-left: -138.29787%; }
+.sixteen.colgrid .row .pull_thirteen.nine.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_thirteen.ten.columns { margin-left: -144.68085%; }
+.sixteen.colgrid .row .pull_thirteen.ten.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_thirteen.eleven.columns { margin-left: -151.06383%; }
+.sixteen.colgrid .row .pull_thirteen.eleven.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_thirteen.twelve.columns { margin-left: -157.44681%; }
+.sixteen.colgrid .row .pull_thirteen.twelve.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_thirteen.thirteen.columns { margin-left: -163.82979%; }
+.sixteen.colgrid .row .pull_thirteen.thirteen.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_thirteen.fourteen.columns { margin-left: -170.21277%; }
+.sixteen.colgrid .row .pull_thirteen.fourteen.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_thirteen.fifteen.columns { margin-left: -176.59574%; }
+.sixteen.colgrid .row .pull_thirteen.fifteen.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .push_fourteen { margin-left: 91.48936%; }
+.sixteen.colgrid .row .push_fourteen:first-child { margin-left: 89.3617%; }
+.sixteen.colgrid .row .pull_fourteen.one.column { margin-left: -93.61702%; }
+.sixteen.colgrid .row .pull_fourteen.one.column:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_fourteen.two.columns { margin-left: -100%; }
+.sixteen.colgrid .row .pull_fourteen.two.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_fourteen.three.columns { margin-left: -106.38298%; }
+.sixteen.colgrid .row .pull_fourteen.three.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_fourteen.four.columns { margin-left: -112.76596%; }
+.sixteen.colgrid .row .pull_fourteen.four.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_fourteen.five.columns { margin-left: -119.14894%; }
+.sixteen.colgrid .row .pull_fourteen.five.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_fourteen.six.columns { margin-left: -125.53191%; }
+.sixteen.colgrid .row .pull_fourteen.six.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_fourteen.seven.columns { margin-left: -131.91489%; }
+.sixteen.colgrid .row .pull_fourteen.seven.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_fourteen.eight.columns { margin-left: -138.29787%; }
+.sixteen.colgrid .row .pull_fourteen.eight.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_fourteen.nine.columns { margin-left: -144.68085%; }
+.sixteen.colgrid .row .pull_fourteen.nine.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_fourteen.ten.columns { margin-left: -151.06383%; }
+.sixteen.colgrid .row .pull_fourteen.ten.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_fourteen.eleven.columns { margin-left: -157.44681%; }
+.sixteen.colgrid .row .pull_fourteen.eleven.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_fourteen.twelve.columns { margin-left: -163.82979%; }
+.sixteen.colgrid .row .pull_fourteen.twelve.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_fourteen.thirteen.columns { margin-left: -170.21277%; }
+.sixteen.colgrid .row .pull_fourteen.thirteen.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_fourteen.fourteen.columns { margin-left: -176.59574%; }
+.sixteen.colgrid .row .pull_fourteen.fourteen.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_fourteen.fifteen.columns { margin-left: -182.97872%; }
+.sixteen.colgrid .row .pull_fourteen.fifteen.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .push_fifteen { margin-left: 97.87234%; }
+.sixteen.colgrid .row .push_fifteen:first-child { margin-left: 95.74468%; }
+.sixteen.colgrid .row .pull_fifteen.one.column { margin-left: -100%; }
+.sixteen.colgrid .row .pull_fifteen.one.column:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_fifteen.two.columns { margin-left: -106.38298%; }
+.sixteen.colgrid .row .pull_fifteen.two.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_fifteen.three.columns { margin-left: -112.76596%; }
+.sixteen.colgrid .row .pull_fifteen.three.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_fifteen.four.columns { margin-left: -119.14894%; }
+.sixteen.colgrid .row .pull_fifteen.four.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_fifteen.five.columns { margin-left: -125.53191%; }
+.sixteen.colgrid .row .pull_fifteen.five.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_fifteen.six.columns { margin-left: -131.91489%; }
+.sixteen.colgrid .row .pull_fifteen.six.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_fifteen.seven.columns { margin-left: -138.29787%; }
+.sixteen.colgrid .row .pull_fifteen.seven.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_fifteen.eight.columns { margin-left: -144.68085%; }
+.sixteen.colgrid .row .pull_fifteen.eight.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_fifteen.nine.columns { margin-left: -151.06383%; }
+.sixteen.colgrid .row .pull_fifteen.nine.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_fifteen.ten.columns { margin-left: -157.44681%; }
+.sixteen.colgrid .row .pull_fifteen.ten.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_fifteen.eleven.columns { margin-left: -163.82979%; }
+.sixteen.colgrid .row .pull_fifteen.eleven.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_fifteen.twelve.columns { margin-left: -170.21277%; }
+.sixteen.colgrid .row .pull_fifteen.twelve.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_fifteen.thirteen.columns { margin-left: -176.59574%; }
+.sixteen.colgrid .row .pull_fifteen.thirteen.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_fifteen.fourteen.columns { margin-left: -182.97872%; }
+.sixteen.colgrid .row .pull_fifteen.fourteen.columns:first-child { margin-left: 0; }
+.sixteen.colgrid .row .pull_fifteen.fifteen.columns { margin-left: -189.3617%; }
+.sixteen.colgrid .row .pull_fifteen.fifteen.columns:first-child { margin-left: 0; }
+
+/* Hybrid Centered Classes */
+.sixteen.colgrid .row .one.centered { margin-left: 47.87234%; }
+.sixteen.colgrid .row .two.centered { margin-left: 44.68085%; }
+.sixteen.colgrid .row .three.centered { margin-left: 41.48936%; }
+.sixteen.colgrid .row .four.centered { margin-left: 38.29787%; }
+.sixteen.colgrid .row .five.centered { margin-left: 35.10638%; }
+.sixteen.colgrid .row .six.centered { margin-left: 31.91489%; }
+.sixteen.colgrid .row .seven.centered { margin-left: 28.7234%; }
+.sixteen.colgrid .row .eight.centered { margin-left: 25.53191%; }
+.sixteen.colgrid .row .nine.centered { margin-left: 22.34043%; }
+.sixteen.colgrid .row .ten.centered { margin-left: 19.14894%; }
+.sixteen.colgrid .row .eleven.centered { margin-left: 15.95745%; }
+.sixteen.colgrid .row .twelve.centered { margin-left: 12.76596%; }
+.sixteen.colgrid .row .thirteen.centered { margin-left: 9.57447%; }
+.sixteen.colgrid .row .fourteen.centered { margin-left: 6.38298%; }
+.sixteen.colgrid .row .fifteen.centered { margin-left: 3.19149%; }
+
+img, object, embed { max-width: 100%; height: auto; }
+
+img { -ms-interpolation-mode: bicubic; }
+
+#map_canvas img, .map_canvas img { max-width: none !important; }
+
+/* Tile Grid */
+.tiles { display: block; overflow: hidden; }
+.tiles > li { display: block; height: auto; float: left; padding-bottom: 0; }
+.tiles.two_up { margin-left: -4%; }
+.tiles.two_up > li { margin-left: 4%; width: 46%; }
+.tiles.three_up, .tiles.four_up { margin-left: -2%; }
+.tiles.three_up > li { margin-left: 2%; width: 31.3%; }
+.tiles.four_up > li { margin-left: 2%; width: 23%; }
+.tiles.five_up { margin-left: -1.5%; }
+.tiles.five_up > li { margin-left: 1.5%; width: 18.5%; }
+
+/* Nicolas Gallagher's micro clearfix */
+.clearfix { *zoom: 1; }
+.clearfix:before, .clearfix:after { content: ""; display: table; }
+.clearfix:after { clear: both; }
+
+.row { *zoom: 1; }
+.row:before, .row:after { content: ""; display: table; }
+.row:after { clear: both; }
+
+.valign { display: table; width: 100%; }
+.valign > div, .valign > article, .valign > section, .valign > figure { display: table-cell; vertical-align: middle; }
+
+/* Mobile */
+@media only screen and (max-width: 767px) { body { -webkit-text-size-adjust: none; -ms-text-size-adjust: none; width: 100%; min-width: 0; }
+ .container { min-width: 0; margin-left: 0; margin-right: 0; }
+ .row { width: 100%; min-width: 0; margin-left: 0; margin-right: 0; }
+ .row .row .column, .row .row .columns { padding: 0; }
+ .row .centered { margin-left: 0 !important; }
+ .column, .columns { width: auto !important; float: none; margin-left: 0; margin-right: 0; }
+ .column:last-child, .columns:last-child { margin-right: 0; float: none; }
+ [class*="column"] + [class*="column"]:last-child { float: none; }
+ [class*="column"]:before { display: table; }
+ [class*="column"]:after { display: table; clear: both; }
+ [class^="push_"], [class*="push_"], [class^="pull_"], [class*="pull_"] { margin-left: 0 !important; } }
+/*=====================================================
+ Navigation (with dropdowns)
+ ======================================================*/
+.navbar { width: 100%; min-height: 60px; display: block; margin-bottom: 20px; background: #4a4d50; position: relative; }
+@media only screen and (max-width: 767px) { .navbar { border: none; }
+ .navbar .column, .navbar .columns { min-height: 0; } }
+.navbar.fixed { position: fixed; z-index: 99999; }
+.navbar.pinned { position: absolute; }
+.navbar a.toggle { display: none; }
+@media only screen and (max-width: 767px) { .navbar a.toggle { top: 18%; right: 4%; width: 46px; position: absolute; text-align: center; display: inline-block; color: white; background: #4a4d50; height: 40px; line-height: 38px; -webkit-border-radius: 4px; -moz-border-radius: 4px; -ms-border-radius: 4px; -o-border-radius: 4px; border-radius: 4px; font-size: 30px; font-size: 1.875rem; }
+ .navbar a.toggle:hover { background: #565a5d; }
+ .navbar a.toggle:active, .navbar a.toggle.active { background: #3e4043; } }
+
+.navbar .logo { display: inline-block; margin: 0 2.12766% 0 0; padding: 0; height: 60px; line-height: 58px; }
+.navbar .logo a { display: block; padding: 0; overflow: hidden; height: 60px; line-height: 58px; }
+.navbar .logo a img { max-height: 95%; }
+@media only screen and (max-width: 767px) { .navbar .logo { float: left; display: inline; }
+ .navbar .logo a { padding: 0; }
+ .navbar .logo a img { width: auto; height: auto; max-width: 100%; } }
+
+.navbar ul { display: table; vertical-align: middle; margin: 0; float: none; }
+@media only screen and (max-width: 767px) { .navbar ul { position: absolute; display: block; width: 100% !important; height: 0; max-height: 0; top: 60px; left: 0; overflow: hidden; text-align: center; background: #3e4043; }
+ .navbar ul.active { height: auto; max-height: 600px; z-index: 999998; -webkit-transition-duration: 0.5s; -moz-transition-duration: 0.5s; -o-transition-duration: 0.5s; transition-duration: 0.5s; -webkit-box-shadow: 0 2px 2px #252728; -moz-box-shadow: 0 2px 2px #252728; box-shadow: 0 2px 2px #252728; } }
+.navbar ul li { display: table-cell; text-align: center; padding-bottom: 0; margin: 0; height: 60px; line-height: 58px; }
+@media only screen and (max-width: 767px) { .navbar ul li { display: block; position: relative; min-height: 50px; max-height: 320px; height: auto; width: 100%; border-right: 0 !important; -webkit-box-shadow: none; -moz-box-shadow: none; box-shadow: none; -webkit-transition-duration: 0.5s; -moz-transition-duration: 0.5s; -o-transition-duration: 0.5s; transition-duration: 0.5s; } }
+.navbar ul li > a { display: block; padding: 0 16px; white-space: nowrap; color: white; text-shadow: 0 1px 2px #191a1b, 0 1px 0 #191a1b; height: 60px; line-height: 58px; font-size: 16px; font-size: 1rem; }
+.navbar ul li > a i.icon-popup { position: absolute; }
+.navbar ul li .btn { border-color: #000101 !important; }
+.navbar ul li.field { margin-bottom: 0 !important; margin-right: 0; }
+@media only screen and (max-width: 767px) { .navbar ul li.field { padding: 0 20px; } }
+.navbar ul li.field input.search { background: #191a1b; border: none; color: #f2f2f2; }
+.navbar ul li .dropdown { width: auto; min-width: 0; max-width: 320px; height: 0; position: absolute; background: #fafafa; overflow: hidden; z-index: 999; }
+@media only screen and (max-width: 767px) { .navbar ul li .dropdown { width: 100%; max-width: 100%; position: relative; -webkit-box-shadow: none !important; -moz-box-shadow: none !important; box-shadow: none !important; }
+ .navbar ul li.active .dropdown { border-bottom: 1px solid #313436; }
+ .navbar ul li.active .dropdown ul { position: relative; top: 0; background: #36393b; min-height: 50px; max-height: 250px; height: auto; overflow: auto; -webkit-box-shadow: none !important; -moz-box-shadow: none !important; box-shadow: none !important; }
+ .navbar ul li.active .dropdown ul li { min-height: 50px; border-bottom: #3e4043; }
+ .navbar ul li.active .dropdown ul li a { color: white; border-bottom: 1px solid #313436; }
+ .navbar ul li.active .dropdown ul li a:hover { color: #d04526; } }
+
+@media only screen and (min-width: 768px) and (max-width: 939px) { .navbar > ul > li > .btn a { padding: 0 10px 0 10px !important; }
+ .navbar ul > li .dropdown ul li.active .dropdown { left: -320px; } }
+
+.navcontain { height: 80px; }
+@media only screen and (max-width: 768px) { .navcontain { height: auto; } }
+
+.pretty.navbar { background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #7b8085), color-stop(100%, #313436)); background-image: -webkit-linear-gradient(#7b8085, #313436); background-image: -moz-linear-gradient(#7b8085, #313436); background-image: -o-linear-gradient(#7b8085, #313436); background-image: linear-gradient(#7b8085, #313436); -webkit-box-shadow: inset 0 1px 1px #7b8085, 0 1px 2px rgba(0, 0, 0, 0.8) !important; -moz-box-shadow: inset 0 1px 1px #7b8085, 0 1px 2px rgba(0, 0, 0, 0.8) !important; box-shadow: inset 0 1px 1px #7b8085, 0 1px 2px rgba(0, 0, 0, 0.8) !important; /* Remove this line if you dont want a dropshadow on your navigation*/ }
+@media only screen and (max-width: 767px) { .pretty.navbar a.toggle { border: 1px solid #3e4043; background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #7b8085), color-stop(100%, #4a4d50)); background-image: -webkit-linear-gradient(#7b8085, #4a4d50); background-image: -moz-linear-gradient(#7b8085, #4a4d50); background-image: -o-linear-gradient(#7b8085, #4a4d50); background-image: linear-gradient(#7b8085, #4a4d50); -webkit-box-shadow: inset 0 1px 2px #888d91, inset 0 -1px 1px #565a5d, inset 1px 0 1px #565a5d, inset -1px 0 1px #565a5d, 0 1px 1px #63676a; -moz-box-shadow: inset 0 1px 2px #888d91, inset 0 -1px 1px #565a5d, inset 1px 0 1px #565a5d, inset -1px 0 1px #565a5d, 0 1px 1px #63676a; box-shadow: inset 0 1px 2px #888d91, inset 0 -1px 1px #565a5d, inset 1px 0 1px #565a5d, inset -1px 0 1px #565a5d, 0 1px 1px #63676a; }
+ .pretty.navbar a.toggle i { text-shadow: 0 1px 1px #191a1b; }
+ .pretty.navbar a.toggle:hover { background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #888d91), color-stop(100%, #565a5d)); background-image: -webkit-linear-gradient(#888d91, #565a5d); background-image: -moz-linear-gradient(#888d91, #565a5d); background-image: -o-linear-gradient(#888d91, #565a5d); background-image: linear-gradient(#888d91, #565a5d); }
+ .pretty.navbar a.toggle:active, .pretty.navbar a.toggle.active { background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #3e4043), color-stop(100%, #4a4d50)); background-image: -webkit-linear-gradient(#3e4043, #4a4d50); background-image: -moz-linear-gradient(#3e4043, #4a4d50); background-image: -o-linear-gradient(#3e4043, #4a4d50); background-image: linear-gradient(#3e4043, #4a4d50); -webkit-box-shadow: 0 1px 1px #63676a; -moz-box-shadow: 0 1px 1px #63676a; box-shadow: 0 1px 1px #63676a; } }
+.pretty.navbar.row { -webkit-border-radius: 4px; -moz-border-radius: 4px; -ms-border-radius: 4px; -o-border-radius: 4px; border-radius: 4px; }
+@media only screen and (max-width: 767px) { .pretty.navbar.row { -webkit-border-radius: 0; -moz-border-radius: 0; -ms-border-radius: 0; -o-border-radius: 0; border-radius: 0; } }
+.pretty.navbar ul li.field input.search { background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #191a1b), color-stop(100%, #4f5255)); background-image: -webkit-linear-gradient(#191a1b, #4f5255); background-image: -moz-linear-gradient(#191a1b, #4f5255); background-image: -o-linear-gradient(#191a1b, #4f5255); background-image: linear-gradient(#191a1b, #4f5255); border: none; -webkit-box-shadow: 0 1px 2px #888d91 !important; -moz-box-shadow: 0 1px 2px #888d91 !important; box-shadow: 0 1px 2px #888d91 !important; /* Remove this line if you dont want a dropshadow on your navigation*/ }
+.pretty.navbar > ul > li:first-child, .pretty.navbar .pretty.navbar > ul > li:first-child a:hover { -webkit-box-shadow: none; -moz-box-shadow: none; box-shadow: none; }
+
+.navbar li .dropdown { width: auto; min-width: 0; max-width: 320px; height: 0; position: absolute; background: #fafafa; overflow: hidden; z-index: 999; }
+@media only screen and (max-width: 767px) { .navbar li .dropdown .dropdown { width: 100%; max-width: 100%; position: relative; -webkit-box-shadow: none !important; -moz-box-shadow: none !important; box-shadow: none !important; }
+ .navbar li .dropdown.active .dropdown { border-bottom: 1px solid #313436; }
+ .navbar li .dropdown.active .dropdown ul { position: relative; top: 0; background: #36393b; min-height: 50px; max-height: 250px; height: auto; overflow: auto; -webkit-box-shadow: none !important; -moz-box-shadow: none !important; box-shadow: none !important; }
+ .navbar li .dropdown.active .dropdown ul li { min-height: 50px; border-bottom: #3e4043; }
+ .navbar li .dropdown.active .dropdown ul li a { color: white; border-bottom: 1px solid #313436; }
+ .navbar li .dropdown.active .dropdown ul li a:hover { color: #d04526; } }
+
+.navbar li .dropdown ul { margin: 0; display: block; }
+.navbar li .dropdown ul > li { position: relative; display: block; width: 100%; float: left; text-align: left; height: auto; -webkit-border-radius: none; -moz-border-radius: none; -ms-border-radius: none; -o-border-radius: none; border-radius: none; }
+@media only screen and (min-width: 768px) and (max-width: 939px) { .navbar li .dropdown ul > li { max-width: 320px; word-wrap: break-word; } }
+.navbar li .dropdown ul > li a { display: block; padding: 0 20px; color: #d04526; border-bottom: 1px solid #cccccc; text-shadow: none; height: 51px; line-height: 49px; }
+@media only screen and (max-width: 767px) { .navbar li .dropdown ul > li a { padding: 0 20px; } }
+.navbar li .dropdown ul > li .dropdown { display: none; background: white; }
+.navbar li .dropdown ul li:first-child a { -webkit-border-radius: 0; -moz-border-radius: 0; -ms-border-radius: 0; -o-border-radius: 0; border-radius: 0; }
+
+.gumby-no-touch .navbar ul li:hover > a, .gumby-touch .navbar ul li.active > a { position: relative; background: #868d92; z-index: 1000; }
+
+.gumby-no-touch .navbar ul li:hover .dropdown, .gumby-touch .navbar ul li.active .dropdown { min-height: 50px; max-height: 561px; overflow: visible; height: auto; width: 100%; padding: 0; border-top: 1px solid #3e4043; -webkit-box-shadow: 0px 3px 4px rgba(0, 0, 0, 0.3); -moz-box-shadow: 0px 3px 4px rgba(0, 0, 0, 0.3); box-shadow: 0px 3px 4px rgba(0, 0, 0, 0.3); }
+
+.gumby-no-touch .navbar ul li:hover .dropdown ul { position: relative; top: 0; min-height: 50px; max-height: 250px; height: auto; -webkit-box-shadow: none !important; -moz-box-shadow: none !important; box-shadow: none !important; -webkit-transition-duration: 0.5s; -moz-transition-duration: 0.5s; -o-transition-duration: 0.5s; transition-duration: 0.5s; }
+@media only screen and (max-width: 767px) { .gumby-no-touch .navbar ul li:hover .dropdown ul { overflow: auto; background: #36393b; }
+ .gumby-no-touch .navbar ul li:hover .dropdown ul li { border-bottom: #3e4043; }
+ .gumby-no-touch .navbar ul li:hover .dropdown ul li a { color: white; border-bottom: 1px solid #313436; }
+ .gumby-no-touch .navbar ul li:hover .dropdown ul li a:hover { color: #d04526; } }
+
+.gumby-no-touch .navbar li .dropdown ul > li:hover .dropdown, .gumby-touch .navbar li .dropdown ul > li.active .dropdown { border-top: none; display: block; position: absolute; z-index: 9999; left: 100%; top: 0; margin-top: 0; }
+@media only screen and (max-width: 767px) { .gumby-no-touch .navbar li .dropdown ul > li:hover .dropdown, .gumby-touch .navbar li .dropdown ul > li.active .dropdown { position: relative; left: 0; }
+ .gumby-no-touch .navbar li .dropdown ul > li:hover .dropdown ul, .gumby-touch .navbar li .dropdown ul > li.active .dropdown ul { background: #252728 !important; } }
+
+.gumby-no-touch .navbar li .dropdown ul li a:hover { background: #f2f2f2; }
+
+.gumby-touch .navbar a:hover { color: white !important; }
+
+.subnav { display: block; width: auto; overflow: hidden; margin: 0 0 18px 0; padding-top: 4px; }
+.subnav li, .subnav dt, .subnav dd { float: left; display: inline; margin-left: 9px; margin-bottom: 4px; }
+.subnav li:first-child, .subnav dt:first-child, .subnav dd:first-child { margin-left: 0; }
+.subnav dt { color: #f2f2f2; font-weight: normal; }
+.subnav li a, .subnav dd a { color: white; font-size: 15px; text-decoration: none; -webkit-border-radius: 4px; -moz-border-radius: 4px; -ms-border-radius: 4px; -o-border-radius: 4px; border-radius: 4px; }
+.subnav li.active a, .subnav dd.active a { background: #4a4d50; padding: 5px 9px; text-shadow: 0 1px 1px #4a4d50; }
+
+/* Buttons */
+.btn, .skiplink { display: inline-block; width: auto; background: #f2f2f2; -webkit-appearance: none; font-family: "Oxygen"; font-weight: 600; padding: 0 !important; text-align: center; }
+.btn > a, .btn input, .btn button, .skiplink > a, .skiplink input, .skiplink button { display: block; padding: 0 18px; color: white; height: 100%; }
+.btn input, .btn button, .skiplink input, .skiplink button { background: none; border: none; width: 100%; font-size: 100%; cursor: pointer; font-weight: 400; -webkit-appearance: none; -moz-appearance: none; appearance: none; }
+
+.btn.xlarge, .skiplink.xlarge { font-size: 30px; font-size: 1.875rem; height: 66px; line-height: 64px; }
+.btn.xlarge a, .skiplink.xlarge a { position: relative; padding: 0 30px; }
+.btn.xlarge.icon-left a, .skiplink.xlarge.icon-left a { padding-left: 66px; }
+.btn.xlarge.icon-left a:before, .skiplink.xlarge.icon-left a:before { left: 20px; }
+.btn.xlarge.icon-right a, .skiplink.xlarge.icon-right a { padding-right: 66px; }
+.btn.xlarge.icon-right a:after, .skiplink.xlarge.icon-right a:after { right: 20px; }
+.btn.large, .skiplink.large { font-size: 26px; font-size: 1.625rem; height: 58px; line-height: 56px; }
+.btn.large a, .skiplink.large a { position: relative; padding: 0 26px; }
+.btn.large.icon-left a, .skiplink.large.icon-left a { padding-left: 58px; }
+.btn.large.icon-left a:before, .skiplink.large.icon-left a:before { left: 17.33333px; }
+.btn.large.icon-right a, .skiplink.large.icon-right a { padding-right: 58px; }
+.btn.large.icon-right a:after, .skiplink.large.icon-right a:after { right: 17.33333px; }
+.btn.medium, .skiplink.medium { font-size: 16px; font-size: 1rem; height: 36px; line-height: 34px; }
+.btn.medium a, .skiplink.medium a { position: relative; padding: 0 16px; }
+.btn.medium.icon-left a, .skiplink.medium.icon-left a { padding-left: 36px; }
+.btn.medium.icon-left a:before, .skiplink.medium.icon-left a:before { left: 10.66667px; }
+.btn.medium.icon-right a, .skiplink.medium.icon-right a { padding-right: 36px; }
+.btn.medium.icon-right a:after, .skiplink.medium.icon-right a:after { right: 10.66667px; }
+.btn.medium a, .skiplink.medium a { padding: 0 18px; }
+.btn.small, .skiplink.small { font-size: 10px; font-size: 0.625rem; height: 23px; line-height: 21px; }
+.btn.small a, .skiplink.small a { position: relative; padding: 0 10px; }
+.btn.small.icon-left a, .skiplink.small.icon-left a { padding-left: 23px; }
+.btn.small.icon-left a:before, .skiplink.small.icon-left a:before { left: 6.66667px; }
+.btn.small.icon-right a, .skiplink.small.icon-right a { padding-right: 23px; }
+.btn.small.icon-right a:after, .skiplink.small.icon-right a:after { right: 6.66667px; }
+.btn.small a, .skiplink.small a { padding: 0 10px; }
+.btn.oval, .skiplink.oval { -webkit-border-radius: 1000px; -moz-border-radius: 1000px; -ms-border-radius: 1000px; -o-border-radius: 1000px; border-radius: 1000px; }
+.btn.pill-left, .skiplink.pill-left { -webkit-border-radius: 500px 0 0 500px; -moz-border-radius: 500px 0 0 500px; -ms-border-radius: 500px 0 0 500px; -o-border-radius: 500px 0 0 500px; border-radius: 500px 0 0 500px; }
+.btn.pill-right, .skiplink.pill-right { -webkit-border-radius: 0 500px 500px 0; -moz-border-radius: 0 500px 500px 0; -ms-border-radius: 0 500px 500px 0; -o-border-radius: 0 500px 500px 0; border-radius: 0 500px 500px 0; }
+
+.btn.primary, .skiplink.primary { background: #3085d6; border: 1px solid #3085d6; }
+.btn.primary:hover, .skiplink.primary:hover { background: #5b9ede; }
+.btn.primary:active, .skiplink.primary:active { background: #236bb0; }
+.btn.secondary, .skiplink.secondary { background: #42a35a; border: 1px solid #42a35a; }
+.btn.secondary:hover, .skiplink.secondary:hover { background: #5bbd73; }
+.btn.secondary:active, .skiplink.secondary:active { background: #337f46; }
+.btn.default, .skiplink.default { background: #f2f2f2; border: 1px solid #f2f2f2; color: #555555; border: 1px solid #f2f2f2; }
+.btn.default:hover, .skiplink.default:hover { background: white; }
+.btn.default:active, .skiplink.default:active { background: #d8d8d8; }
+.btn.default:hover, .skiplink.default:hover { border: 1px solid #e5e5e5; }
+.btn.default a, .btn.default input, .btn.default button, .skiplink.default a, .skiplink.default input, .skiplink.default button { color: #555555; }
+.btn.info, .skiplink.info { background: #4a4d50; border: 1px solid #4a4d50; }
+.btn.info:hover, .skiplink.info:hover { background: #63676a; }
+.btn.info:active, .skiplink.info:active { background: #313436; }
+.btn.danger, .skiplink.danger { background: #ca3838; border: 1px solid #ca3838; }
+.btn.danger:hover, .skiplink.danger:hover { background: #d56060; }
+.btn.danger:active, .skiplink.danger:active { background: #a32c2c; }
+.btn.warning, .skiplink.warning { background: #f6b83f; border: 1px solid #f6b83f; color: #644405; }
+.btn.warning:hover, .skiplink.warning:hover { background: #f8ca70; }
+.btn.warning:active, .skiplink.warning:active { background: #f4a60e; }
+.btn.warning a, .btn.warning input, .btn.warning button, .skiplink.warning a, .skiplink.warning input, .skiplink.warning button { color: #644405; }
+.btn.success, .skiplink.success { background: #58c026; border: 1px solid #58c026; }
+.btn.success:hover, .skiplink.success:hover { background: #72d940; }
+.btn.success:active, .skiplink.success:active { background: #44951e; }
+.btn.metro, .metro .btn, .metro .skiplink, .btn.metro:hover, .metro .btn:hover, .metro .skiplink:hover, .skiplink.metro:hover, .btn.metro:active, .metro .btn:active, .metro .skiplink:active, .skiplink.metro:active, .skiplink.metro { -webkit-border-radius: 0; -moz-border-radius: 0; -ms-border-radius: 0; -o-border-radius: 0; border-radius: 0; }
+.btn.metro.rounded, .metro .rounded.btn, .metro .rounded.skiplink, .rounded.skiplink.metro:hover, .rounded.skiplink.metro:active, .skiplink.metro.rounded { -webkit-border-radius: 4px; -moz-border-radius: 4px; -ms-border-radius: 4px; -o-border-radius: 4px; border-radius: 4px; }
+.btn.pretty, .pretty .btn, .pretty .skiplink, .btn.pretty:hover, .pretty .btn:hover, .pretty .skiplink:hover, .skiplink.pretty:hover, .btn.pretty:active, .pretty .btn:active, .pretty .skiplink:active, .skiplink.pretty:active, .skiplink.pretty { -webkit-border-radius: 4px; -moz-border-radius: 4px; -ms-border-radius: 4px; -o-border-radius: 4px; border-radius: 4px; }
+.btn.pretty.squared, .pretty .squared.btn, .pretty .squared.skiplink, .squared.skiplink.pretty:hover, .squared.skiplink.pretty:active, .skiplink.pretty.squared { -webkit-border-radius: 0; -moz-border-radius: 0; -ms-border-radius: 0; -o-border-radius: 0; border-radius: 0; }
+
+.btn.pretty.primary, .pretty .primary.btn, .pretty .primary.skiplink, .primary.skiplink.pretty:hover, .primary.skiplink.pretty:active, .skiplink.pretty.primary { background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #85b7e7), color-stop(100%, #2a85dc)); background-image: -webkit-linear-gradient(#85b7e7, #2a85dc); background-image: -moz-linear-gradient(#85b7e7, #2a85dc); background-image: -o-linear-gradient(#85b7e7, #2a85dc); background-image: linear-gradient(#85b7e7, #2a85dc); -webkit-box-shadow: inset 0 0 3px #f0f6fc; -moz-box-shadow: inset 0 0 3px #f0f6fc; box-shadow: inset 0 0 3px #f0f6fc; border: 1px solid #1f5e9b; }
+.pretty .primary.btn:hover, .pretty .primary.skiplink:hover, .primary.btn.pretty:hover, .primary.skiplink.pretty:hover, .skiplink.pretty.primary:hover { background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #a2d4fc), color-stop(100%, #54b2fe)); background-image: -webkit-linear-gradient(#a2d4fc, #54b2fe); background-image: -moz-linear-gradient(#a2d4fc, #54b2fe); background-image: -o-linear-gradient(#a2d4fc, #54b2fe); background-image: linear-gradient(#a2d4fc, #54b2fe); -webkit-box-shadow: inset 0 0 3px white; -moz-box-shadow: inset 0 0 3px white; box-shadow: inset 0 0 3px white; border: 1px solid #0e90f8; }
+.pretty .primary.btn:active, .pretty .primary.skiplink:active, .primary.btn.pretty:active, .primary.skiplink.pretty:active, .skiplink.pretty.primary:active { background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #2a85dc), color-stop(100%, #85b7e7)); background-image: -webkit-linear-gradient(#2a85dc, #85b7e7); background-image: -moz-linear-gradient(#2a85dc, #85b7e7); background-image: -o-linear-gradient(#2a85dc, #85b7e7); background-image: linear-gradient(#2a85dc, #85b7e7); -webkit-box-shadow: inset 0 0 3px white; -moz-box-shadow: inset 0 0 3px white; box-shadow: inset 0 0 3px white; }
+.btn.pretty.primary a, .pretty .primary.btn a, .pretty .primary.skiplink a, .primary.skiplink.pretty:hover a, .primary.skiplink.pretty:active a, .btn.pretty.primary input, .pretty .primary.btn input, .pretty .primary.skiplink input, .primary.skiplink.pretty:hover input, .primary.skiplink.pretty:active input, .btn.pretty.primary button, .pretty .primary.btn button, .pretty .primary.skiplink button, .primary.skiplink.pretty:hover button, .primary.skiplink.pretty:active button, .skiplink.pretty.primary a, .skiplink.pretty.primary input, .skiplink.pretty.primary button { text-shadow: 0 1px 1px #1a5186; }
+.btn.pretty.secondary, .pretty .secondary.btn, .pretty .secondary.skiplink, .secondary.skiplink.pretty:hover, .secondary.skiplink.pretty:active, .skiplink.pretty.secondary { background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #80cb92), color-stop(100%, #3ca957)); background-image: -webkit-linear-gradient(#80cb92, #3ca957); background-image: -moz-linear-gradient(#80cb92, #3ca957); background-image: -o-linear-gradient(#80cb92, #3ca957); background-image: linear-gradient(#80cb92, #3ca957); -webkit-box-shadow: inset 0 0 3px #daf0e0; -moz-box-shadow: inset 0 0 3px #daf0e0; box-shadow: inset 0 0 3px #daf0e0; border: 1px solid #2c6d3c; }
+.pretty .secondary.btn:hover, .pretty .secondary.skiplink:hover, .secondary.btn.pretty:hover, .secondary.skiplink.pretty:hover, .skiplink.pretty.secondary:hover { background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #a1d3ad), color-stop(100%, #68c07d)); background-image: -webkit-linear-gradient(#a1d3ad, #68c07d); background-image: -moz-linear-gradient(#a1d3ad, #68c07d); background-image: -o-linear-gradient(#a1d3ad, #68c07d); background-image: linear-gradient(#a1d3ad, #68c07d); -webkit-box-shadow: inset 0 0 3px #f8fcf9; -moz-box-shadow: inset 0 0 3px #f8fcf9; box-shadow: inset 0 0 3px #f8fcf9; border: 1px solid #469659; }
+.pretty .secondary.btn:active, .pretty .secondary.skiplink:active, .secondary.btn.pretty:active, .secondary.skiplink.pretty:active, .skiplink.pretty.secondary:active { background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #3ca957), color-stop(100%, #80cb92)); background-image: -webkit-linear-gradient(#3ca957, #80cb92); background-image: -moz-linear-gradient(#3ca957, #80cb92); background-image: -o-linear-gradient(#3ca957, #80cb92); background-image: linear-gradient(#3ca957, #80cb92); -webkit-box-shadow: inset 0 0 3px #ecf8ef; -moz-box-shadow: inset 0 0 3px #ecf8ef; box-shadow: inset 0 0 3px #ecf8ef; }
+.btn.pretty.secondary a, .pretty .secondary.btn a, .pretty .secondary.skiplink a, .secondary.skiplink.pretty:hover a, .secondary.skiplink.pretty:active a, .btn.pretty.secondary input, .pretty .secondary.btn input, .pretty .secondary.skiplink input, .secondary.skiplink.pretty:hover input, .secondary.skiplink.pretty:active input, .btn.pretty.secondary button, .pretty .secondary.btn button, .pretty .secondary.skiplink button, .secondary.skiplink.pretty:hover button, .secondary.skiplink.pretty:active button, .skiplink.pretty.secondary a, .skiplink.pretty.secondary input, .skiplink.pretty.secondary button { text-shadow: 0 1px 1px #255a32; }
+.btn.pretty.default, .pretty .default.btn, .pretty .default.skiplink, .default.skiplink.pretty:hover, .default.skiplink.pretty:active, .skiplink.pretty.default { background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #ffffff), color-stop(100%, #f3f1f1)); background-image: -webkit-linear-gradient(#ffffff, #f3f1f1); background-image: -moz-linear-gradient(#ffffff, #f3f1f1); background-image: -o-linear-gradient(#ffffff, #f3f1f1); background-image: linear-gradient(#ffffff, #f3f1f1); -webkit-box-shadow: inset 0 0 3px white; -moz-box-shadow: inset 0 0 3px white; box-shadow: inset 0 0 3px white; border: 1px solid #cccccc; }
+.pretty .default.btn:hover, .pretty .default.skiplink:hover, .default.btn.pretty:hover, .default.skiplink.pretty:hover, .skiplink.pretty.default:hover { background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #ffffff), color-stop(100%, #ffffff)); background-image: -webkit-linear-gradient(#ffffff, #ffffff); background-image: -moz-linear-gradient(#ffffff, #ffffff); background-image: -o-linear-gradient(#ffffff, #ffffff); background-image: linear-gradient(#ffffff, #ffffff); -webkit-box-shadow: inset 0 0 3px white; -moz-box-shadow: inset 0 0 3px white; box-shadow: inset 0 0 3px white; border: 1px solid #d9d9d9; }
+.pretty .default.btn:active, .pretty .default.skiplink:active, .default.btn.pretty:active, .default.skiplink.pretty:active, .skiplink.pretty.default:active { background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #f3f1f1), color-stop(100%, #ffffff)); background-image: -webkit-linear-gradient(#f3f1f1, #ffffff); background-image: -moz-linear-gradient(#f3f1f1, #ffffff); background-image: -o-linear-gradient(#f3f1f1, #ffffff); background-image: linear-gradient(#f3f1f1, #ffffff); -webkit-box-shadow: inset 0 0 3px white; -moz-box-shadow: inset 0 0 3px white; box-shadow: inset 0 0 3px white; }
+.btn.pretty.default a, .pretty .default.btn a, .pretty .default.skiplink a, .default.skiplink.pretty:hover a, .default.skiplink.pretty:active a, .btn.pretty.default input, .pretty .default.btn input, .pretty .default.skiplink input, .default.skiplink.pretty:hover input, .default.skiplink.pretty:active input, .btn.pretty.default button, .pretty .default.btn button, .pretty .default.skiplink button, .default.skiplink.pretty:hover button, .default.skiplink.pretty:active button, .skiplink.pretty.default a, .skiplink.pretty.default input, .skiplink.pretty.default button { text-shadow: 0 1px 1px white; }
+.btn.pretty.info, .pretty .info.btn, .pretty .info.skiplink, .info.skiplink.pretty:hover, .info.skiplink.pretty:active, .skiplink.pretty.info { background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #7b8085), color-stop(100%, #464d54)); background-image: -webkit-linear-gradient(#7b8085, #464d54); background-image: -moz-linear-gradient(#7b8085, #464d54); background-image: -o-linear-gradient(#7b8085, #464d54); background-image: linear-gradient(#7b8085, #464d54); -webkit-box-shadow: inset 0 0 3px #bdc0c2; -moz-box-shadow: inset 0 0 3px #bdc0c2; box-shadow: inset 0 0 3px #bdc0c2; border: 1px solid #252728; }
+.pretty .info.btn:hover, .pretty .info.skiplink:hover, .info.btn.pretty:hover, .info.skiplink.pretty:hover, .skiplink.pretty.info:hover { background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #aeb3b6), color-stop(100%, #808e98)); background-image: -webkit-linear-gradient(#aeb3b6, #808e98); background-image: -moz-linear-gradient(#aeb3b6, #808e98); background-image: -o-linear-gradient(#aeb3b6, #808e98); background-image: linear-gradient(#aeb3b6, #808e98); -webkit-box-shadow: inset 0 0 3px #f1f2f3; -moz-box-shadow: inset 0 0 3px #f1f2f3; box-shadow: inset 0 0 3px #f1f2f3; border: 1px solid #60676b; }
+.pretty .info.btn:active, .pretty .info.skiplink:active, .info.btn.pretty:active, .info.skiplink.pretty:active, .skiplink.pretty.info:active { background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #464d54), color-stop(100%, #7b8085)); background-image: -webkit-linear-gradient(#464d54, #7b8085); background-image: -moz-linear-gradient(#464d54, #7b8085); background-image: -o-linear-gradient(#464d54, #7b8085); background-image: linear-gradient(#464d54, #7b8085); -webkit-box-shadow: inset 0 0 3px #cbcdce; -moz-box-shadow: inset 0 0 3px #cbcdce; box-shadow: inset 0 0 3px #cbcdce; }
+.btn.pretty.info a, .pretty .info.btn a, .pretty .info.skiplink a, .info.skiplink.pretty:hover a, .info.skiplink.pretty:active a, .btn.pretty.info input, .pretty .info.btn input, .pretty .info.skiplink input, .info.skiplink.pretty:hover input, .info.skiplink.pretty:active input, .btn.pretty.info button, .pretty .info.btn button, .pretty .info.skiplink button, .info.skiplink.pretty:hover button, .info.skiplink.pretty:active button, .skiplink.pretty.info a, .skiplink.pretty.info input, .skiplink.pretty.info button { text-shadow: 0 1px 1px #191a1b; }
+.btn.pretty.danger, .pretty .danger.btn, .pretty .danger.skiplink, .danger.skiplink.pretty:hover, .danger.skiplink.pretty:active, .skiplink.pretty.danger { background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #df8989), color-stop(100%, #d03232)); background-image: -webkit-linear-gradient(#df8989, #d03232); background-image: -moz-linear-gradient(#df8989, #d03232); background-image: -o-linear-gradient(#df8989, #d03232); background-image: linear-gradient(#df8989, #d03232); -webkit-box-shadow: inset 0 0 3px #faeded; -moz-box-shadow: inset 0 0 3px #faeded; box-shadow: inset 0 0 3px #faeded; border: 1px solid #8f2626; }
+.pretty .danger.btn:hover, .pretty .danger.skiplink:hover, .danger.btn.pretty:hover, .danger.skiplink.pretty:hover, .skiplink.pretty.danger:hover { background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #f79696), color-stop(100%, #f64a4a)); background-image: -webkit-linear-gradient(#f79696, #f64a4a); background-image: -moz-linear-gradient(#f79696, #f64a4a); background-image: -o-linear-gradient(#f79696, #f64a4a); background-image: linear-gradient(#f79696, #f64a4a); -webkit-box-shadow: inset 0 0 3px white; -moz-box-shadow: inset 0 0 3px white; box-shadow: inset 0 0 3px white; border: 1px solid #e21212; }
+.pretty .danger.btn:active, .pretty .danger.skiplink:active, .danger.btn.pretty:active, .danger.skiplink.pretty:active, .skiplink.pretty.danger:active { background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #d03232), color-stop(100%, #df8989)); background-image: -webkit-linear-gradient(#d03232, #df8989); background-image: -moz-linear-gradient(#d03232, #df8989); background-image: -o-linear-gradient(#d03232, #df8989); background-image: linear-gradient(#d03232, #df8989); -webkit-box-shadow: inset 0 0 3px white; -moz-box-shadow: inset 0 0 3px white; box-shadow: inset 0 0 3px white; }
+.btn.pretty.danger a, .pretty .danger.btn a, .pretty .danger.skiplink a, .danger.skiplink.pretty:hover a, .danger.skiplink.pretty:active a, .btn.pretty.danger input, .pretty .danger.btn input, .pretty .danger.skiplink input, .danger.skiplink.pretty:hover input, .danger.skiplink.pretty:active input, .btn.pretty.danger button, .pretty .danger.btn button, .pretty .danger.skiplink button, .danger.skiplink.pretty:hover button, .danger.skiplink.pretty:active button, .skiplink.pretty.danger a, .skiplink.pretty.danger input, .skiplink.pretty.danger button { text-shadow: 0 1px 1px #7b2121; }
+.btn.pretty.warning, .pretty .warning.btn, .pretty .warning.skiplink, .warning.skiplink.pretty:hover, .warning.skiplink.pretty:active, .skiplink.pretty.warning { background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #fbdca0), color-stop(100%, #fbba3a)); background-image: -webkit-linear-gradient(#fbdca0, #fbba3a); background-image: -moz-linear-gradient(#fbdca0, #fbba3a); background-image: -o-linear-gradient(#fbdca0, #fbba3a); background-image: linear-gradient(#fbdca0, #fbba3a); -webkit-box-shadow: inset 0 0 3px white; -moz-box-shadow: inset 0 0 3px white; box-shadow: inset 0 0 3px white; border: 1px solid #de960a; color: #644405; }
+.pretty .warning.btn:hover, .pretty .warning.skiplink:hover, .warning.btn.pretty:hover, .warning.skiplink.pretty:hover, .skiplink.pretty.warning:hover { background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #feecca), color-stop(100%, #ffd37d)); background-image: -webkit-linear-gradient(#feecca, #ffd37d); background-image: -moz-linear-gradient(#feecca, #ffd37d); background-image: -o-linear-gradient(#feecca, #ffd37d); background-image: linear-gradient(#feecca, #ffd37d); -webkit-box-shadow: inset 0 0 3px white; -moz-box-shadow: inset 0 0 3px white; box-shadow: inset 0 0 3px white; border: 1px solid #fcb834; }
+.pretty .warning.btn:active, .pretty .warning.skiplink:active, .warning.btn.pretty:active, .warning.skiplink.pretty:active, .skiplink.pretty.warning:active { background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #fbba3a), color-stop(100%, #fbdca0)); background-image: -webkit-linear-gradient(#fbba3a, #fbdca0); background-image: -moz-linear-gradient(#fbba3a, #fbdca0); background-image: -o-linear-gradient(#fbba3a, #fbdca0); background-image: linear-gradient(#fbba3a, #fbdca0); -webkit-box-shadow: inset 0 0 3px white; -moz-box-shadow: inset 0 0 3px white; box-shadow: inset 0 0 3px white; }
+.btn.pretty.warning a, .pretty .warning.btn a, .pretty .warning.skiplink a, .warning.skiplink.pretty:hover a, .warning.skiplink.pretty:active a, .btn.pretty.warning input, .pretty .warning.btn input, .pretty .warning.skiplink input, .warning.skiplink.pretty:hover input, .warning.skiplink.pretty:active input, .btn.pretty.warning button, .pretty .warning.btn button, .pretty .warning.skiplink button, .warning.skiplink.pretty:hover button, .warning.skiplink.pretty:active button, .skiplink.pretty.warning a, .skiplink.pretty.warning input, .skiplink.pretty.warning button { text-shadow: 0 1px 1px #fbdca0; }
+.btn.pretty.success, .pretty .success.btn, .pretty .success.skiplink, .success.skiplink.pretty:hover, .success.skiplink.pretty:active, .skiplink.pretty.success { background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #91e26a), color-stop(100%, #56c620)); background-image: -webkit-linear-gradient(#91e26a, #56c620); background-image: -moz-linear-gradient(#91e26a, #56c620); background-image: -o-linear-gradient(#91e26a, #56c620); background-image: linear-gradient(#91e26a, #56c620); -webkit-box-shadow: inset 0 0 3px #e0f7d5; -moz-box-shadow: inset 0 0 3px #e0f7d5; box-shadow: inset 0 0 3px #e0f7d5; border: 1px solid #3b8019; }
+.pretty .success.btn:hover, .pretty .success.skiplink:hover, .success.btn.pretty:hover, .success.skiplink.pretty:hover, .skiplink.pretty.success:hover { background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #96e570), color-stop(100%, #64df29)); background-image: -webkit-linear-gradient(#96e570, #64df29); background-image: -moz-linear-gradient(#96e570, #64df29); background-image: -o-linear-gradient(#96e570, #64df29); background-image: linear-gradient(#96e570, #64df29); -webkit-box-shadow: inset 0 0 3px #e5f9db; -moz-box-shadow: inset 0 0 3px #e5f9db; box-shadow: inset 0 0 3px #e5f9db; border: 1px solid #479f1d; }
+.pretty .success.btn:active, .pretty .success.skiplink:active, .success.btn.pretty:active, .success.skiplink.pretty:active, .skiplink.pretty.success:active { background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #56c620), color-stop(100%, #91e26a)); background-image: -webkit-linear-gradient(#56c620, #91e26a); background-image: -moz-linear-gradient(#56c620, #91e26a); background-image: -o-linear-gradient(#56c620, #91e26a); background-image: linear-gradient(#56c620, #91e26a); -webkit-box-shadow: inset 0 0 3px #f0fbea; -moz-box-shadow: inset 0 0 3px #f0fbea; box-shadow: inset 0 0 3px #f0fbea; }
+.btn.pretty.success a, .pretty .success.btn a, .pretty .success.skiplink a, .success.skiplink.pretty:hover a, .success.skiplink.pretty:active a, .btn.pretty.success input, .pretty .success.btn input, .pretty .success.skiplink input, .success.skiplink.pretty:hover input, .success.skiplink.pretty:active input, .btn.pretty.success button, .pretty .success.btn button, .pretty .success.skiplink button, .success.skiplink.pretty:hover button, .success.skiplink.pretty:active button, .skiplink.pretty.success a, .skiplink.pretty.success input, .skiplink.pretty.success button { text-shadow: 0 1px 1px #316b15; }
+
+/* Icons */
+[class^="icon-"] a:before, [class*=" icon-"] a:before, [class^="icon-"] a:after, [class*=" icon-"] a:after, i[class^="icon-"], i[class*=" icon-"] { font-family: "entypo"; position: absolute; text-decoration: none; zoom: 1; }
+
+i[class^="icon-"], i[class*=" icon-"] { display: inline-block; position: static; min-width: 20px; margin: 0 5px; text-align: center; }
+
+/* Form Styles */
+form { margin: 0 0 18px; }
+form label { display: block; font-size: 16px; font-size: 1rem; line-height: 1.625em; cursor: pointer; margin-bottom: 9px; }
+form label.inline { display: inline-block; padding-right: 20px; }
+form dt { margin: 0; }
+form textarea { height: 150px; }
+form ul, form ul li { margin-left: 0; list-style-type: none; }
+form fieldset { border-style: solid; border-width: 0.0625em; padding: 1.5625em; border-color: #d8d8d8; margin: 18px 0; }
+form fieldset legend { padding: 5px 10px; }
+
+.field { position: relative; max-width: 100%; margin-bottom: 10px; vertical-align: middle; font-size: 16px; overflow: hidden; /* remove inline-block white-space — A 0px font-size = 0px of white space */ }
+.field.metro, .field .metro { -webkit-border-radius: 0; -moz-border-radius: 0; -ms-border-radius: 0; -o-border-radius: 0; border-radius: 0; }
+.field input, .field input[type="*"], .field textarea { max-width: 100%; width: 100%; padding: 0; margin: 0; border: none; outline: none; resize: none; -webkit-appearance: none; font-family: "Oxygen"; font-weight: 300; font-size: 16px; font-size: 1rem; -webkit-box-shadow: none; -moz-box-shadow: none; box-shadow: none; }
+.field .input { position: relative; padding: 0 10px; background: #fff; border: 1px solid #d8d8d8; height: 36px; line-height: 34px; font-size: 16px; font-size: 1rem; -webkit-border-radius: 4px; -moz-border-radius: 4px; -ms-border-radius: 4px; -o-border-radius: 4px; border-radius: 4px; }
+.field .input.search { height: 36px; line-height: 34px; -webkit-border-radius: 1000px; -moz-border-radius: 1000px; -ms-border-radius: 1000px; -o-border-radius: 1000px; border-radius: 1000px; padding-right: 0; }
+.field .input.textarea { height: auto; }
+.field .xnarrow { width: 13.33333%; }
+.field .narrow { width: 30.66667%; }
+.field .normal { width: 48%; }
+.field .wide { width: 65.33333%; }
+.field .xwide { width: 82.66667%; }
+.field .xxwide { width: 100%; }
+.field .xnarrow, .field .narrow, .field .normal, .field .wide, .field .xwide, .field .xxwide { margin: 0; }
+.field .xnarrow:last-child, .field .narrow:last-child, .field .normal:last-child, .field .wide:last-child, .field .xwide:last-child, .field .xxwide:last-child { margin-left: -4px; }
+.field .xnarrow:first-child, .field .narrow:first-child, .field .normal:first-child, .field .wide:first-child, .field .xwide:first-child, .field .xxwide:first-child { margin-right: 3.94%; margin-left: 0; }
+.field .xnarrow:first-child:last-child, .field .narrow:first-child:last-child, .field .normal:first-child:last-child, .field .wide:first-child:last-child, .field .xwide:first-child:last-child, .field .xxwide:first-child:last-child { margin: 0; }
+.field label + .xnarrow:last-child, .field label + .narrow:last-child, .field label + .normal:last-child, .field label + .wide:last-child, .field label + .xwide:last-child, .field label + .xxwide:last-child { margin-left: 0; }
+@media only screen and (max-width: 960px) { .field .xxwide:first-child, .field .xxwide:last-child { margin-right: 0%; } }
+.field.prepend, .field.append { font-size: 0; white-space: nowrap; padding-bottom: 3.5px; }
+.field.prepend input, .field.prepend .input, .field.append input, .field.append .input { display: inline-block; max-width: 100%; }
+.field.prepend input, .field.prepend .input { -webkit-border-radius: 0px 4px 4px 0; -moz-border-radius: 0px 4px 4px 0; -ms-border-radius: 0px 4px 4px 0; -o-border-radius: 0px 4px 4px 0; border-radius: 0px 4px 4px 0; }
+.field.append input, .field.append .input { -webkit-border-radius: 4px 0 0 4px; -moz-border-radius: 4px 0 0 4px; -ms-border-radius: 4px 0 0 4px; -o-border-radius: 4px 0 0 4px; border-radius: 4px 0 0 4px; }
+.field.prepend.append input { -webkit-border-radius: 0; -moz-border-radius: 0; -ms-border-radius: 0; -o-border-radius: 0; border-radius: 0; }
+.field.prepend.append input:first-child { -webkit-border-radius: 4px 0 0 4px; -moz-border-radius: 4px 0 0 4px; -ms-border-radius: 4px 0 0 4px; -o-border-radius: 4px 0 0 4px; border-radius: 4px 0 0 4px; }
+.field.prepend.append input:last-child { margin-left: -1px; -webkit-border-radius: 0px 4px 4px 0; -moz-border-radius: 0px 4px 4px 0; -ms-border-radius: 0px 4px 4px 0; -o-border-radius: 0px 4px 4px 0; border-radius: 0px 4px 4px 0; }
+.field.prepend .adjoined, .field.append .adjoined, .field.prepend .btn, .field.append .btn { position: relative; display: inline-block; margin-bottom: 0; z-index: 99; }
+.field.prepend .btn a, .field.prepend .btn input, .field.prepend .btn button, .field.append .btn a, .field.append .btn input, .field.append .btn button { padding: 0 12px; }
+.field.prepend .adjoined, .field.append .adjoined { padding: 0 10px 0 10px; background: #f2f2f2; border: 1px solid #d8d8d8; font-family: "Oxygen"; font-weight: 600; color: #555555; font-size: 16px; font-size: 1rem; height: 36px; line-height: 34px; }
+.field.prepend *:first-child { -webkit-border-radius: 4px 0 0 4px; -moz-border-radius: 4px 0 0 4px; -ms-border-radius: 4px 0 0 4px; -o-border-radius: 4px 0 0 4px; border-radius: 4px 0 0 4px; }
+.field.prepend input:first-child { margin-right: 0; }
+.field.prepend .adjoined, .field.prepend .btn { margin-right: -1px; }
+.field .adjoined:first-child { margin-left: 0 !important; }
+.field.append .adjoined, .field.append .btn { margin-left: -1px; }
+.field.append *:last-child { -webkit-border-radius: 0px 4px 4px 0; -moz-border-radius: 0px 4px 4px 0; -ms-border-radius: 0px 4px 4px 0; -o-border-radius: 0px 4px 4px 0; border-radius: 0px 4px 4px 0; }
+.field.append button, .field.prepend button { display: inline-block; }
+.field.append input:first-child { margin-right: 0; }
+.field.double input, .field.double .input { width: 50% !important; }
+.field.double input:last-child, .field.double .input:last-child { margin-left: -1px; }
+.field.danger:after { font-family: "entypo"; content: "\2716"; font-size: 16px; position: absolute; top: 14%; right: 15px; z-index: 999; color: #ca3838; }
+.field.danger.no-icon:after { display: none; }
+.field.danger.append:after, .field.danger.prepend:after { content: ""; }
+.field.danger input, .field.danger .input, .field.danger textarea, .field.danger .textarea, .field.danger .radio span, .field.danger .checkbox span, .field.danger .picker { border-color: #ca3838; color: #ca3838; background: #f0c5c5; -webkit-transition-duration: 0.2s; -moz-transition-duration: 0.2s; -o-transition-duration: 0.2s; transition-duration: 0.2s; }
+.field.danger textarea { color: #ca3838; }
+.field.danger input::-webkit-input-placeholder, .field.danger textarea::-webkit-input-placeholder { color: #ca3838; }
+.field.danger input:-moz-placeholder, .field.danger textarea:-moz-placeholder { color: #ca3838; }
+.field.warning:after { font-family: "entypo"; content: "\26a0"; font-size: 16px; position: absolute; top: 14%; right: 15px; z-index: 999; color: #f6b83f; }
+.field.warning.no-icon:after { display: none; }
+.field.warning.append:after, .field.warning.prepend:after { content: ""; }
+.field.warning input, .field.warning .input, .field.warning textarea, .field.warning .textarea, .field.warning .radio span, .field.warning .checkbox span, .field.warning .picker { border-color: #f6b83f; color: #f6b83f; background: #fef7ea; -webkit-transition-duration: 0.2s; -moz-transition-duration: 0.2s; -o-transition-duration: 0.2s; transition-duration: 0.2s; }
+.field.warning textarea { color: #f6b83f; }
+.field.warning input::-webkit-input-placeholder, .field.warning textarea::-webkit-input-placeholder { color: #f6b83f; }
+.field.warning input:-moz-placeholder, .field.warning textarea:-moz-placeholder { color: #f6b83f; }
+.field.success:after { font-family: "entypo"; content: "\2713"; font-size: 16px; position: absolute; top: 14%; right: 15px; z-index: 999; color: #58c026; }
+.field.success.no-icon:after { display: none; }
+.field.success.append:after, .field.success.prepend:after { content: ""; }
+.field.success input, .field.success .input, .field.success textarea, .field.success .textarea, .field.success .radio span, .field.success .checkbox span, .field.success .picker { border-color: #58c026; color: #58c026; background: #c0eeaa; -webkit-transition-duration: 0.2s; -moz-transition-duration: 0.2s; -o-transition-duration: 0.2s; transition-duration: 0.2s; }
+.field.success textarea { color: #58c026; }
+.field.success input::-webkit-input-placeholder, .field.success textarea::-webkit-input-placeholder { color: #58c026; }
+.field.success input:-moz-placeholder, .field.success textarea:-moz-placeholder { color: #58c026; }
+.field .picker.danger { border-color: #ca3838; color: #ca3838; background: #f0c5c5; -webkit-transition-duration: 0.2s; -moz-transition-duration: 0.2s; -o-transition-duration: 0.2s; transition-duration: 0.2s; }
+.field .picker.danger select, .field .picker.danger:after { color: #ca3838; }
+.field .picker.warning { border-color: #f6b83f; color: #f6b83f; background: #fef7ea; -webkit-transition-duration: 0.2s; -moz-transition-duration: 0.2s; -o-transition-duration: 0.2s; transition-duration: 0.2s; }
+.field .picker.warning select, .field .picker.warning:after { color: #f6b83f; }
+.field .picker.success { border-color: #58c026; color: #58c026; background: #c0eeaa; -webkit-transition-duration: 0.2s; -moz-transition-duration: 0.2s; -o-transition-duration: 0.2s; transition-duration: 0.2s; }
+.field .picker.success select, .field .picker.success:after { color: #58c026; }
+
+.field .text input[type="search"] { -webkit-appearance: textfield; }
+
+.no-js .radio input { -webkit-appearance: radio; margin-left: 1px; }
+.no-js .checkbox input { -webkit-appearance: checkbox; }
+.no-js .radio input, .no-js .checkbox input { display: inline-block; width: 16px; }
+
+.js .field .radio, .js .field .checkbox { position: relative; }
+.js .field .radio.danger, .js .field .checkbox.danger { color: #ca3838; }
+.js .field .radio.danger span, .js .field .checkbox.danger span { border-color: #ca3838; color: #ca3838; background: #f0c5c5; -webkit-transition-duration: 0.2s; -moz-transition-duration: 0.2s; -o-transition-duration: 0.2s; transition-duration: 0.2s; }
+.js .field .radio.warning, .js .field .checkbox.warning { color: #f6b83f; }
+.js .field .radio.warning span, .js .field .checkbox.warning span { border-color: #f6b83f; color: #f6b83f; background: #fef7ea; -webkit-transition-duration: 0.2s; -moz-transition-duration: 0.2s; -o-transition-duration: 0.2s; transition-duration: 0.2s; }
+.js .field .radio.success, .js .field .checkbox.success { color: #58c026; color: #555555; }
+.js .field .radio.success i, .js .field .checkbox.success i { color: #58c026; }
+.js .field .radio.success span, .js .field .checkbox.success span { border-color: #58c026; color: #58c026; background: #c0eeaa; -webkit-transition-duration: 0.2s; -moz-transition-duration: 0.2s; -o-transition-duration: 0.2s; transition-duration: 0.2s; }
+.js .field .radio.checked i, .js .field .checkbox.checked i { position: absolute; top: -1px; left: -8px; line-height: 16px; }
+.js .field .radio span, .js .field .checkbox span { display: inline-block; width: 16px; height: 16px; position: relative; top: 2px; border: solid 1px #ccc; background: #fefefe; }
+.js .field .radio input[type="radio"], .js .field .radio input[type="checkbox"], .js .field .checkbox input[type="radio"], .js .field .checkbox input[type="checkbox"] { display: none; }
+.js .field .radio span { -webkit-border-radius: 8px; -moz-border-radius: 8px; -ms-border-radius: 8px; -o-border-radius: 8px; border-radius: 8px; }
+.js .field .checkbox span { -webkit-border-radius: 3px; -moz-border-radius: 3px; -ms-border-radius: 3px; -o-border-radius: 3px; border-radius: 3px; }
+
+.field .text input[type="search"] { -webkit-appearance: textfield; }
+
+/* Form Picker Element (