diff --git a/.gitignore b/.gitignore index 34dec8d5d..c497e28d7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,8 @@ .composer/* composer.phar composer.lock - docs/build/ +.php_cs.cache ###> symfony/framework-bundle ### /.env.local @@ -19,3 +19,4 @@ docs/build/ /phpunit.xml .phpunit.result.cache ###< phpunit/phpunit ### + diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 2172dbd03..4b6da4216 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -18,7 +18,7 @@ before_script: # Bring in any services we need http://docs.gitlab.com/ee/ci/docker/using_docker_images.html#what-is-a-service # See http://docs.gitlab.com/ee/ci/services/README.html for examples. services: - - name: postgres:12 + - name: postgis/postgis:12-3.1-alpine alias: db - name: redis alias: redis diff --git a/docs/source/development/api.rst b/docs/source/development/api.rst new file mode 100644 index 000000000..86eb6ff65 --- /dev/null +++ b/docs/source/development/api.rst @@ -0,0 +1,453 @@ +.. Copyright (C) 2014 Champs Libres Cooperative SCRLFS + Permission is granted to copy, distribute and/or modify this document + under the terms of the GNU Free Documentation License, Version 1.3 + or any later version published by the Free Software Foundation; + with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. + A copy of the license is included in the section entitled "GNU + Free Documentation License". + +.. _api: + +API +### + +Chill provides a basic framework to build REST api. + +Configure a route +================= + +Follow those steps to build a REST api: + +1. Create your model; +2. Configure the API; + +You can also: + +* hook into the controller to customize some steps; +* add more route and steps + +.. note:: + + Useful links: + + * `How to use annotation to configure serialization `_ + * `How to create your custom normalizer `_ + +Auto-loading the routes +*********************** + +Ensure that those lines are present in your file `app/config/routing.yml`: + + +.. code-block:: yaml + + chill_cruds: + resource: 'chill_main_crud_route_loader:load' + type: service + + +Create your model +***************** + +Create your model on the usual way: + +.. code-block:: php + + namespace Chill\PersonBundle\Entity\AccompanyingPeriod; + + use Chill\PersonBundle\Entity\AccompanyingPeriod\OriginRepository; + use Doctrine\ORM\Mapping as ORM; + + /** + * @ORM\Entity(repositoryClass=OriginRepository::class) + * @ORM\Table(name="chill_person_accompanying_period_origin") + */ + class Origin + { + /** + * @ORM\Id + * @ORM\GeneratedValue + * @ORM\Column(type="integer") + */ + private $id; + + /** + * @ORM\Column(type="json") + */ + private $label; + + /** + * @ORM\Column(type="date_immutable", nullable=true) + */ + private $noActiveAfter; + + // .. getters and setters + + } + + +Configure api +************* + +Configure the api using Yaml (see the full configuration: :ref:`api_full_configuration`): + +.. code-block:: yaml + + # config/packages/chill_main.yaml + chill_main: + apis: + accompanying_period_origin: + base_path: '/api/1.0/person/accompanying-period/origin' + class: 'Chill\PersonBundle\Entity\AccompanyingPeriod\Origin' + name: accompanying_period_origin + base_role: 'ROLE_USER' + actions: + _index: + methods: + GET: true + HEAD: true + _entity: + methods: + GET: true + HEAD: true + +.. note:: + + If you are working on a shared bundle (aka "The chill bundles"), you should define your configuration inside the class :code:`ChillXXXXBundleExtension`, using the "prependConfig" feature: + + .. code-block:: php + + namespace Chill\PersonBundle\DependencyInjection; + + use Symfony\Component\DependencyInjection\ContainerBuilder; + use Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface; + use Symfony\Component\HttpFoundation\Request; + + /** + * 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 + { + public function prepend(ContainerBuilder $container) + { + $this->prependCruds($container); + } + + /** + * @param ContainerBuilder $container + */ + protected function prependCruds(ContainerBuilder $container) + { + $container->prependExtensionConfig('chill_main', [ + 'apis' => [ + [ + 'class' => \Chill\PersonBundle\Entity\AccompanyingPeriod\Origin::class, + 'name' => 'accompanying_period_origin', + 'base_path' => '/api/1.0/person/accompanying-period/origin', + 'controller' => \Chill\PersonBundle\Controller\OpeningApiController::class, + 'base_role' => 'ROLE_USER', + 'actions' => [ + '_index' => [ + 'methods' => [ + Request::METHOD_GET => true, + Request::METHOD_HEAD => true + ], + ], + '_entity' => [ + 'methods' => [ + Request::METHOD_GET => true, + Request::METHOD_HEAD => true + ] + ], + ] + ] + ] + ]); + } + } + +The :code:`_index` and :code:`_entity` action +============================================= + +The :code:`_index` and :code:`_entity` action are default actions: + +* they will call a specific method in the default controller; +* they will generate defined routes: + +Index: + Name: :code:`chill_api_single_accompanying_period_origin__index` + + Path: :code:`/api/1.0/person/accompanying-period/origin.{_format}` + +Entity: + Name: :code:`chill_api_single_accompanying_period_origin__entity` + + Path: :code:`/api/1.0/person/accompanying-period/origin/{id}.{_format}` + +Role +==== + +By default, the key `base_role` is used to check ACL. Take care of creating the :code:`Voter` required to take that into account. + +For index action, the role will be called with :code:`NULL` as :code:`$subject`. The retrieved entity will be the subject for single queries. + +You can also define a role for each method. In this case, this role is used for the given method, and, if any, the base role is taken into account. + +.. code-block:: yaml + + # config/packages/chill_main.yaml + chill_main: + apis: + accompanying_period_origin: + base_path: '/api/1.0/person/bla/bla' + class: 'Chill\PersonBundle\Entity\Blah' + name: bla + actions: + _entity: + methods: + GET: true + HEAD: true + roles: + GET: MY_ROLE_SEE + HEAD: MY ROLE_SEE + +Customize the controller +======================== + +You can customize the controller by hooking into the default actions. Take care of extending :code:`Chill\MainBundle\CRUD\Controller\ApiController`. + + +.. code-block:: php + + + namespace Chill\PersonBundle\Controller; + + use Chill\MainBundle\CRUD\Controller\ApiController; + use Symfony\Component\HttpFoundation\Request; + use Symfony\Component\HttpFoundation\Response; + + class OpeningApiController extends ApiController + { + protected function customizeQuery(string $action, Request $request, $qb): void + { + $qb->where($qb->expr()->gt('e.noActiveAfter', ':now')) + ->orWhere($qb->expr()->isNull('e.noActiveAfter')); + $qb->setParameter('now', new \DateTime('now')); + } + } + +And set your controller in configuration: + +.. code-block:: yaml + + chill_main: + apis: + accompanying_period_origin: + base_path: '/api/1.0/person/accompanying-period/origin' + class: 'Chill\PersonBundle\Entity\AccompanyingPeriod\Origin' + name: accompanying_period_origin + # add a controller + controller: 'Chill\PersonBundle\Controller\OpeningApiController' + base_role: 'ROLE_USER' + actions: + _index: + methods: + GET: true + HEAD: true + _entity: + methods: + GET: true + HEAD: true + +Create your own actions +======================= + +You can add your own actions: + +.. code-block:: yaml + + chill_main: + apis: + - + class: Chill\PersonBundle\Entity\AccompanyingPeriod + name: accompanying_course + base_path: /api/1.0/person/accompanying-course + controller: Chill\PersonBundle\Controller\AccompanyingCourseApiController + actions: + # add a custom participation: + participation: + methods: + POST: true + DELETE: true + GET: false + HEAD: false + PUT: false + roles: + POST: CHILL_PERSON_ACCOMPANYING_PERIOD_SEE + DELETE: CHILL_PERSON_ACCOMPANYING_PERIOD_SEE + GET: null + HEAD: null + PUT: null + single-collection: single + +The key :code:`single-collection` with value :code:`single` will add a :code:`/{id}/ + "action name"` (in this example, :code:`/{id}/participation`) into the path, after the base path. If the value is :code:`collection`, no id will be set, but the action name will be append to the path. + +Then, create the corresponding action into your controller: + +.. code-block:: php + + namespace Chill\PersonBundle\Controller; + + use Chill\MainBundle\CRUD\Controller\ApiController; + use Symfony\Component\HttpFoundation\Request; + use Symfony\Component\HttpFoundation\Response; + use Chill\PersonBundle\Entity\AccompanyingPeriod; + use Symfony\Component\HttpFoundation\Exception\BadRequestException; + use Symfony\Component\EventDispatcher\EventDispatcherInterface; + use Symfony\Component\Validator\Validator\ValidatorInterface; + use Chill\PersonBundle\Privacy\AccompanyingPeriodPrivacyEvent; + use Chill\PersonBundle\Entity\Person; + + class AccompanyingCourseApiController extends ApiController + { + protected EventDispatcherInterface $eventDispatcher; + + protected ValidatorInterface $validator; + + public function __construct(EventDispatcherInterface $eventDispatcher, $validator) + { + $this->eventDispatcher = $eventDispatcher; + $this->validator = $validator; + } + + public function participationApi($id, Request $request, $_format) + { + /** @var AccompanyingPeriod $accompanyingPeriod */ + $accompanyingPeriod = $this->getEntity('participation', $id, $request); + $person = $this->getSerializer() + ->deserialize($request->getContent(), Person::class, $_format, []); + + if (NULL === $person) { + throw new BadRequestException('person id not found'); + } + + $this->onPostCheckACL('participation', $request, $accompanyingPeriod, $_format); + + switch ($request->getMethod()) { + case Request::METHOD_POST: + $participation = $accompanyingPeriod->addPerson($person); + break; + case Request::METHOD_DELETE: + $participation = $accompanyingPeriod->removePerson($person); + break; + default: + throw new BadRequestException("This method is not supported"); + } + + $errors = $this->validator->validate($accompanyingPeriod); + + if ($errors->count() > 0) { + // only format accepted + return $this->json($errors); + } + + $this->getDoctrine()->getManager()->flush(); + + return $this->json($participation); + } + } + +Serialization for collection +============================ + +A specific model has been defined for returning collection: + +.. code-block:: json + + { + "count": 49, + "results": [ + ], + "pagination": { + "more": true, + "next": "/api/1.0/search.json&q=xxxx......&page=2", + "previous": null, + "first": 0, + "items_per_page": 1 + } + } + + +This can be achieved quickly by assembling results into a :code:`Chill\MainBundle\Serializer\Model\Collection`. The pagination information is given by using :code:`Paginator` (see :ref:`Pagination `). + +.. code-block:: php + + use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; + use Chill\MainBundle\Pagination\PaginatorInterface; + + class MyController extends AbstractController + { + + protected function serializeCollection(PaginatorInterface $paginator, $entities): Response + { + $model = new Collection($entities, $paginator); + + return $this->json($model, Response::HTTP_OK, [], $context); + } + } + +.. _api_full_configuration: + +Full configuration example +========================== + +.. code-block:: yaml + + apis: + - + class: Chill\PersonBundle\Entity\AccompanyingPeriod + name: accompanying_course + base_path: /api/1.0/person/accompanying-course + controller: Chill\PersonBundle\Controller\AccompanyingCourseApiController + actions: + _entity: + roles: + GET: CHILL_PERSON_ACCOMPANYING_PERIOD_SEE + HEAD: null + POST: null + DELETE: null + PUT: null + controller_action: null + path: null + single-collection: single + methods: + GET: true + HEAD: true + POST: false + DELETE: false + PUT: false + participation: + methods: + POST: true + DELETE: true + GET: false + HEAD: false + PUT: false + roles: + POST: CHILL_PERSON_ACCOMPANYING_PERIOD_SEE + DELETE: CHILL_PERSON_ACCOMPANYING_PERIOD_SEE + GET: null + HEAD: null + PUT: null + controller_action: null + # the requirements for the route. Will be set to `[ 'id' => '\d+' ]` if left empty. + requirements: [] + path: null + single-collection: single + base_role: null + + diff --git a/docs/source/development/index.rst b/docs/source/development/index.rst index af2e3f948..f35bc12db 100644 --- a/docs/source/development/index.rst +++ b/docs/source/development/index.rst @@ -16,6 +16,7 @@ As Chill rely on the `symfony `_ framework, reading the fram Instructions to create a new bundle CRUD (Create - Update - Delete) for one entity + Helpers for building a REST API Routing Menus Forms diff --git a/docs/source/development/pagination.rst b/docs/source/development/pagination.rst index 44c06b308..c0cec9639 100644 --- a/docs/source/development/pagination.rst +++ b/docs/source/development/pagination.rst @@ -7,6 +7,8 @@ Free Documentation License". +.. _pagination-ref: + Pagination ########## @@ -15,7 +17,7 @@ The Bundle :code:`Chill\MainBundle` provides a **Pagination** api which allow yo A simple example **************** -In the controller, get the :class:`Chill\Main\Pagination\PaginatorFactory` from the `Container` and use this :code:`PaginatorFactory` to create a :code:`Paginator` instance. +In the controller, get the :code:`Chill\Main\Pagination\PaginatorFactory` from the `Container` and use this :code:`PaginatorFactory` to create a :code:`Paginator` instance. .. literalinclude:: pagination/example.php diff --git a/src/Bundle/ChillMainBundle/CRUD/CompilerPass/CRUDControllerCompilerPass.php b/src/Bundle/ChillMainBundle/CRUD/CompilerPass/CRUDControllerCompilerPass.php new file mode 100644 index 000000000..d8a46837c --- /dev/null +++ b/src/Bundle/ChillMainBundle/CRUD/CompilerPass/CRUDControllerCompilerPass.php @@ -0,0 +1,77 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +namespace Chill\MainBundle\CRUD\CompilerPass; + +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\Reference; +use Chill\MainBundle\Routing\MenuComposer; +use Symfony\Component\DependencyInjection\Definition; + +/** + * + * + */ +class CRUDControllerCompilerPass implements CompilerPassInterface +{ + public function process(ContainerBuilder $container) + { + $crudConfig = $container->getParameter('chill_main_crud_route_loader_config'); + $apiConfig = $container->getParameter('chill_main_api_route_loader_config'); + + foreach ($crudConfig as $crudEntry) { + $this->configureCrudController($container, $crudEntry, 'crud'); + } + + foreach ($apiConfig as $crudEntry) { + $this->configureCrudController($container, $crudEntry, 'api'); + } + } + + /** + * Add a controller for each definition, and add a methodCall to inject crud configuration to controller + */ + private function configureCrudController(ContainerBuilder $container, array $crudEntry, string $apiOrCrud): void + { + $controllerClass = $crudEntry['controller']; + + $controllerServiceName = 'cs'.$apiOrCrud.'_'.$crudEntry['name'].'_controller'; + + if ($container->hasDefinition($controllerClass)) { + $controller = $container->getDefinition($controllerClass); + $container->removeDefinition($controllerClass); + $alreadyDefined = true; + } else { + $controller = new Definition($controllerClass); + $alreadyDefined = false; + } + + $controller->addTag('controller.service_arguments'); + if (FALSE === $alreadyDefined) { + $controller->setAutoconfigured(true); + $controller->setPublic(true); + } + + $param = 'chill_main_'.$apiOrCrud.'_config_'.$crudEntry['name']; + $container->setParameter($param, $crudEntry); + $controller->addMethodCall('setCrudConfig', ['%'.$param.'%']); + + $container->setDefinition($controllerServiceName, $controller); + } + +} diff --git a/src/Bundle/ChillMainBundle/CRUD/Controller/AbstractCRUDController.php b/src/Bundle/ChillMainBundle/CRUD/Controller/AbstractCRUDController.php new file mode 100644 index 000000000..5cc055f26 --- /dev/null +++ b/src/Bundle/ChillMainBundle/CRUD/Controller/AbstractCRUDController.php @@ -0,0 +1,225 @@ +getDoctrine() + ->getRepository($this->getEntityClass()) + ->find($id); + } + + /** + * Count the number of entities + * + * By default, count all entities. You can customize the query by + * using the method `customizeQuery`. + * + * @param string $action + * @param Request $request + * @return int + */ + protected function countEntities(string $action, Request $request, $_format): int + { + return $this->buildQueryEntities($action, $request) + ->select('COUNT(e)') + ->getQuery() + ->getSingleScalarResult() + ; + } + + /** + * Query the entity. + * + * By default, get all entities. You can customize the query by using the + * method `customizeQuery`. + * + * The method `orderEntity` is called internally to order entities. + * + * It returns, by default, a query builder. + * + */ + protected function queryEntities(string $action, Request $request, string $_format, PaginatorInterface $paginator) + { + $query = $this->buildQueryEntities($action, $request) + ->setFirstResult($paginator->getCurrentPage()->getFirstItemNumber()) + ->setMaxResults($paginator->getItemsPerPage()); + + // allow to order queries and return the new query + return $this->orderQuery($action, $query, $request, $paginator, $_format); + } + + /** + * Add ordering fields in the query build by self::queryEntities + * + */ + protected function orderQuery(string $action, $query, Request $request, PaginatorInterface $paginator, $_format) + { + return $query; + } + + /** + * Build the base query for listing all entities. + * + * This method is used internally by `countEntities` `queryEntities` + * + * This base query does not contains any `WHERE` or `SELECT` clauses. You + * can add some by using the method `customizeQuery`. + * + * The alias for the entity is "e". + * + * @param string $action + * @param Request $request + * @return QueryBuilder + */ + protected function buildQueryEntities(string $action, Request $request) + { + $qb = $this->getDoctrine()->getManager() + ->createQueryBuilder() + ->select('e') + ->from($this->getEntityClass(), 'e') + ; + + $this->customizeQuery($action, $request, $qb); + + return $qb; + } + + protected function customizeQuery(string $action, Request $request, $query): void {} + + /** + * Get the result of the query + */ + protected function getQueryResult(string $action, Request $request, string $_format, int $totalItems, PaginatorInterface $paginator, $query) + { + return $query->getQuery()->getResult(); + } + + protected function onPreIndex(string $action, Request $request, string $_format): ?Response + { + return null; + } + + /** + * method used by indexAction + */ + protected function onPreIndexBuildQuery(string $action, Request $request, string $_format, int $totalItems, PaginatorInterface $paginator): ?Response + { + return null; + } + + /** + * method used by indexAction + */ + protected function onPostIndexBuildQuery(string $action, Request $request, string $_format, int $totalItems, PaginatorInterface $paginator, $query): ?Response + { + return null; + } + + /** + * method used by indexAction + */ + protected function onPostIndexFetchQuery(string $action, Request $request, string $_format, int $totalItems, PaginatorInterface $paginator, $entities): ?Response + { + return null; + } + + + /** + * Get the complete FQDN of the class + * + * @return string the complete fqdn of the class + */ + protected function getEntityClass(): string + { + return $this->crudConfig['class']; + } + + /** + * called on post fetch entity + */ + protected function onPostFetchEntity(string $action, Request $request, $entity, $_format): ?Response + { + return null; + } + + /** + * Called on post check ACL + */ + protected function onPostCheckACL(string $action, Request $request, string $_format, $entity): ?Response + { + return null; + } + + /** + * check the acl. Called by every action. + * + * By default, check the role given by `getRoleFor` for the value given in + * $entity. + * + * Throw an \Symfony\Component\Security\Core\Exception\AccessDeniedHttpException + * if not accessible. + * + * @throws \Symfony\Component\Security\Core\Exception\AccessDeniedHttpException + */ + protected function checkACL(string $action, Request $request, string $_format, $entity = null) + { + $this->denyAccessUnlessGranted($this->getRoleFor($action, $request, $entity, $_format), $entity); + } + + /** + * + * @return string the crud name + */ + protected function getCrudName(): string + { + return $this->crudConfig['name']; + } + + protected function getActionConfig(string $action) + { + return $this->crudConfig['actions'][$action]; + } + + /** + * Set the crud configuration + * + * Used by the container to inject configuration for this crud. + */ + public function setCrudConfig(array $config): void + { + $this->crudConfig = $config; + } + + /** + * @return PaginatorFactory + */ + protected function getPaginatorFactory(): PaginatorFactory + { + return $this->container->get('chill_main.paginator_factory'); + } +} diff --git a/src/Bundle/ChillMainBundle/CRUD/Controller/ApiController.php b/src/Bundle/ChillMainBundle/CRUD/Controller/ApiController.php new file mode 100644 index 000000000..7643db0e9 --- /dev/null +++ b/src/Bundle/ChillMainBundle/CRUD/Controller/ApiController.php @@ -0,0 +1,220 @@ +getEntity($action, $id, $request, $_format); + + $postFetch = $this->onPostFetchEntity($action, $request, $entity, $_format); + + if ($postFetch instanceof Response) { + return $postFetch; + } + + if (NULL === $entity) { + throw $this->createNotFoundException(sprintf("The %s with id %s " + . "is not found", $this->getCrudName(), $id)); + } + + $response = $this->checkACL($action, $request, $_format, $entity); + if ($response instanceof Response) { + return $response; + } + + $response = $this->onPostCheckACL($action, $request, $_format, $entity); + if ($response instanceof Response) { + return $response; + } + + $response = $this->onBeforeSerialize($action, $request, $_format, $entity); + if ($response instanceof Response) { + return $response; + } + + if ($_format === 'json') { + $context = $this->getContextForSerialization($action, $request, $_format, $entity); + + return $this->json($entity, Response::HTTP_OK, [], $context); + } else { + throw new \Symfony\Component\HttpFoundation\Exception\BadRequestException("This format is not implemented"); + } + } + + public function onBeforeSerialize(string $action, Request $request, $_format, $entity): ?Response + { + return null; + } + + /** + * Base method for handling api action + * + * @return void + */ + public function entityApi(Request $request, $id, $_format): Response + { + switch ($request->getMethod()) { + case Request::METHOD_GET: + case REQUEST::METHOD_HEAD: + return $this->entityGet('_entity', $request, $id, $_format); + default: + throw new \Symfony\Component\HttpFoundation\Exception\BadRequestException("This method is not implemented"); + } + } + + /** + * Base action for indexing entities + */ + public function indexApi(Request $request, string $_format) + { + switch ($request->getMethod()) { + case Request::METHOD_GET: + case REQUEST::METHOD_HEAD: + return $this->indexApiAction('_index', $request, $_format); + default: + throw $this->createNotFoundException("This method is not supported"); + } + } + + /** + * Build an index page. + * + * Some steps may be overriden during this process of rendering. + * + * This method: + * + * 1. Launch `onPreIndex` + * x. check acl. If it does return a response instance, return it + * x. launch `onPostCheckACL`. If it does return a response instance, return it + * 1. count the items, using `countEntities` + * 2. build a paginator element from the the number of entities ; + * 3. Launch `onPreIndexQuery`. If it does return a response instance, return it + * 3. build a query, using `queryEntities` + * x. fetch the results, using `getQueryResult` + * x. Launch `onPostIndexFetchQuery`. If it does return a response instance, return it + * 4. Serialize the entities in a Collection, using `SerializeCollection` + * + * @param string $action + * @param Request $request + * @return type + */ + protected function indexApiAction($action, Request $request, $_format) + { + $this->onPreIndex($action, $request, $_format); + + $response = $this->checkACL($action, $request, $_format); + if ($response instanceof Response) { + return $response; + } + + if (!isset($entity)) { + $entity = ''; + } + + $response = $this->onPostCheckACL($action, $request, $_format, $entity); + if ($response instanceof Response) { + return $response; + } + + $totalItems = $this->countEntities($action, $request, $_format); + $paginator = $this->getPaginatorFactory()->create($totalItems); + + $response = $this->onPreIndexBuildQuery($action, $request, $_format, $totalItems, + $paginator); + + if ($response instanceof Response) { + return $response; + } + + $query = $this->queryEntities($action, $request, $_format, $paginator); + + $response = $this->onPostIndexBuildQuery($action, $request, $_format, $totalItems, + $paginator, $query); + + if ($response instanceof Response) { + return $response; + } + + $entities = $this->getQueryResult($action, $request, $_format, $totalItems, $paginator, $query); + + $response = $this->onPostIndexFetchQuery($action, $request, $_format, $totalItems, + $paginator, $entities); + + if ($response instanceof Response) { + return $response; + } + + return $this->serializeCollection($action, $request, $_format, $paginator, $entities); + } + + /** + * Serialize collections + * + */ + protected function serializeCollection(string $action, Request $request, string $_format, PaginatorInterface $paginator, $entities): Response + { + $model = new Collection($entities, $paginator); + + $context = $this->getContextForSerialization($action, $request, $_format, $entities); + + return $this->json($model, Response::HTTP_OK, [], $context); + } + + + protected function getContextForSerialization(string $action, Request $request, string $_format, $entity): array + { + return []; + } + + /** + * get the role given from the config. + */ + protected function getRoleFor(string $action, Request $request, $entity, $_format): string + { + $actionConfig = $this->getActionConfig($action); + + if (NULL !== $actionConfig['roles'][$request->getMethod()]) { + return $actionConfig['roles'][$request->getMethod()]; + } + + if ($this->crudConfig['base_role']) { + return $this->crudConfig['base_role']; + } + + throw new \RuntimeException(sprintf("the config does not have any role for the ". + "method %s nor a global role for the whole action. Add those to your ". + "configuration or override the required method", $request->getMethod())); + + } + + protected function getSerializer(): SerializerInterface + { + return $this->get('serializer'); + } +} diff --git a/src/Bundle/ChillMainBundle/CRUD/Controller/CRUDController.php b/src/Bundle/ChillMainBundle/CRUD/Controller/CRUDController.php index 8a8d32932..25b7e5860 100644 --- a/src/Bundle/ChillMainBundle/CRUD/Controller/CRUDController.php +++ b/src/Bundle/ChillMainBundle/CRUD/Controller/CRUDController.php @@ -34,6 +34,7 @@ use Chill\MainBundle\CRUD\Form\CRUDDeleteEntityForm; use Chill\MainBundle\Pagination\PaginatorFactory; use Chill\MainBundle\Pagination\PaginatorInterface; use Chill\MainBundle\Security\Authorization\AuthorizationHelper; +use Symfony\Component\Serializer\SerializerInterface; /** * Class CRUDController @@ -42,16 +43,16 @@ use Chill\MainBundle\Security\Authorization\AuthorizationHelper; */ class CRUDController extends AbstractController { - + /** - * The crud configuration + * The crud configuration * * This configuration si defined by `chill_main['crud']`. * * @var array */ protected $crudConfig; - + /** * @param array $config */ @@ -59,7 +60,7 @@ class CRUDController extends AbstractController { $this->crudConfig = $config; } - + /** * @param $parameter * @return Response @@ -68,7 +69,7 @@ class CRUDController extends AbstractController { return new Response($parameter); } - + /** * @param Request $request * @param $id @@ -78,7 +79,7 @@ class CRUDController extends AbstractController { return $this->deleteAction('delete', $request, $id); } - + /** * @param string $action * @param Request $request @@ -89,78 +90,78 @@ class CRUDController extends AbstractController protected function deleteAction(string $action, Request $request, $id, $formClass = null) { $this->onPreDelete($action, $request, $id); - + $entity = $this->getEntity($action, $id, $request); - + $postFetch = $this->onPostFetchEntity($action, $request, $entity); - + if ($postFetch instanceof Response) { return $postFetch; } - + if (NULL === $entity) { throw $this->createNotFoundException(sprintf("The %s with id %s " . "is not found"), $this->getCrudName(), $id); } - + $response = $this->checkACL($action, $entity); if ($response instanceof Response) { return $response; } - + $response = $this->onPostCheckACL($action, $request, $entity); if ($response instanceof Response) { return $response; } - + $form = $this->createFormFor($action, $entity, $formClass); - + $form->handleRequest($request); - + if ($form->isSubmitted() && $form->isValid()) { $this->onFormValid($entity, $form, $request); $em = $this->getDoctrine()->getManager(); - + $this->onPreRemove($action, $entity, $form, $request); $this->removeEntity($action, $entity, $form, $request); $this->onPostRemove($action, $entity, $form, $request); - + $this->onPreFlush($action, $entity, $form, $request); $em->flush(); $this->onPostFlush($action, $entity, $form, $request); - + $this->addFlash('success', $this->generateFormSuccessMessage($action, $entity)); - + $result = $this->onBeforeRedirectAfterSubmission($action, $entity, $form, $request); - + if ($result instanceof Response) { return $result; } - + return $this->redirectToRoute('chill_crud_'.$this->getCrudName().'_view', ['id' => $entity->getId()]); - + } elseif ($form->isSubmitted()) { $this->addFlash('error', $this->generateFormErrorMessage($action, $form)); } - + $defaultTemplateParameters = [ 'form' => $form->createView(), 'entity' => $entity, 'crud_name' => $this->getCrudName() ]; - + return $this->render( - $this->getTemplateFor($action, $entity, $request), + $this->getTemplateFor($action, $entity, $request), $this->generateTemplateParameter($action, $entity, $request, $defaultTemplateParameters) ); } - + /** * @param string $action * @param Request $request */ protected function onPreDelete(string $action, Request $request) {} - + /** * @param string $action * @param $entity @@ -168,7 +169,7 @@ class CRUDController extends AbstractController * @param Request $request */ protected function onPreRemove(string $action, $entity, FormInterface $form, Request $request) {} - + /** * @param string $action * @param $entity @@ -176,7 +177,7 @@ class CRUDController extends AbstractController * @param Request $request */ protected function onPostRemove(string $action, $entity, FormInterface $form, Request $request) {} - + /** * @param string $action * @param $entity @@ -189,10 +190,10 @@ class CRUDController extends AbstractController ->getManager() ->remove($entity); } - + /** * Base method called by index action. - * + * * @param Request $request * @return type */ @@ -200,14 +201,14 @@ class CRUDController extends AbstractController { return $this->indexEntityAction('index', $request); } - + /** * Build an index page. - * + * * Some steps may be overriden during this process of rendering. - * + * * This method: - * + * * 1. Launch `onPreIndex` * x. check acl. If it does return a response instance, return it * x. launch `onPostCheckACL`. If it does return a response instance, return it @@ -218,15 +219,15 @@ class CRUDController extends AbstractController * x. fetch the results, using `getQueryResult` * x. Launch `onPostIndexFetchQuery`. If it does return a response instance, return it * 4. create default parameters: - * - * The default parameters are: - * + * + * The default parameters are: + * * * entities: the list en entities ; * * crud_name: the name of the crud ; * * paginator: a paginator element ; - * 5. Launch rendering, the parameter is fetch using `getTemplateFor` + * 5. Launch rendering, the parameter is fetch using `getTemplateFor` * The parameters may be personnalized using `generateTemplateParameter`. - * + * * @param string $action * @param Request $request * @return type @@ -234,80 +235,80 @@ class CRUDController extends AbstractController protected function indexEntityAction($action, Request $request) { $this->onPreIndex($action, $request); - + $response = $this->checkACL($action, null); if ($response instanceof Response) { return $response; } - + if (!isset($entity)) { $entity = ''; } - + $response = $this->onPostCheckACL($action, $request, $entity); if ($response instanceof Response) { return $response; } - + $totalItems = $this->countEntities($action, $request); $paginator = $this->getPaginatorFactory()->create($totalItems); - - $response = $this->onPreIndexBuildQuery($action, $request, $totalItems, + + $response = $this->onPreIndexBuildQuery($action, $request, $totalItems, $paginator); - + if ($response instanceof Response) { return $response; } - + $query = $this->queryEntities($action, $request, $paginator); - - $response = $this->onPostIndexBuildQuery($action, $request, $totalItems, + + $response = $this->onPostIndexBuildQuery($action, $request, $totalItems, $paginator, $query); - + if ($response instanceof Response) { return $response; } - + $entities = $this->getQueryResult($action, $request, $totalItems, $paginator, $query); - - $response = $this->onPostIndexFetchQuery($action, $request, $totalItems, + + $response = $this->onPostIndexFetchQuery($action, $request, $totalItems, $paginator, $entities); - + if ($response instanceof Response) { return $response; } - + $defaultTemplateParameters = [ 'entities' => $entities, 'crud_name' => $this->getCrudName(), 'paginator' => $paginator ]; - + return $this->render( - $this->getTemplateFor($action, $entities, $request), + $this->getTemplateFor($action, $entities, $request), $this->generateTemplateParameter($action, $entities, $request, $defaultTemplateParameters) ); } - + /** * @param string $action * @param Request $request */ protected function onPreIndex(string $action, Request $request) { } - + /** * method used by indexAction - * + * * @param string $action * @param Request $request * @param int $totalItems * @param PaginatorInterface $paginator */ protected function onPreIndexBuildQuery(string $action, Request $request, int $totalItems, PaginatorInterface $paginator) { } - + /** * method used by indexAction - * + * * @param string $action * @param Request $request * @param int $totalItems @@ -315,10 +316,10 @@ class CRUDController extends AbstractController * @param mixed $query */ protected function onPostIndexBuildQuery(string $action, Request $request, int $totalItems, PaginatorInterface $paginator, $query) { } - + /** * method used by indexAction - * + * * @param string $action * @param Request $request * @param int $totalItems @@ -326,14 +327,14 @@ class CRUDController extends AbstractController * @param mixed $entities */ protected function onPostIndexFetchQuery(string $action, Request $request, int $totalItems, PaginatorInterface $paginator, $entities) { } - + /** * Build the base query for listing all entities, normally use in a listing * page. - * + * * This base query does not contains any `WHERE` or `SELECT` clauses. Those * are added by other methods, like `queryEntities` and `countQueries`. - * + * * @param string $action * @param Request $request * @return QueryBuilder @@ -346,16 +347,16 @@ class CRUDController extends AbstractController ->from($this->getEntityClass(), 'e') ; } - + /** * Query the entity. - * + * * By default, get all entities. - * + * * The method `orderEntity` is called internally to order entities. - * + * * It returns, by default, a query builder. - * + * * @param string $action * @param Request $request * @param PaginatorInterface $paginator @@ -366,15 +367,15 @@ class CRUDController extends AbstractController $query = $this->buildQueryEntities($action, $request) ->setFirstResult($paginator->getCurrentPage()->getFirstItemNumber()) ->setMaxResults($paginator->getItemsPerPage()); - + // allow to order queries and return the new query return $this->orderQuery($action, $query, $request, $paginator); } - - + + /** * Add ordering fields in the query build by self::queryEntities - * + * * @param string $action * @param QueryBuilder|mixed $query by default, an instance of QueryBuilder * @param Request $request @@ -385,10 +386,10 @@ class CRUDController extends AbstractController { return $query; } - + /** * Get the result of the query - * + * * @param string $action * @param Request $request * @param int $totalItems @@ -396,14 +397,14 @@ class CRUDController extends AbstractController * @param mixed $query * @return mixed */ - protected function getQueryResult(string $action, Request $request, int $totalItems, PaginatorInterface $paginator, $query) + protected function getQueryResult(string $action, Request $request, int $totalItems, PaginatorInterface $paginator, $query) { return $query->getQuery()->getResult(); } - + /** * Count the number of entities - * + * * @param string $action * @param Request $request * @return int @@ -416,12 +417,12 @@ class CRUDController extends AbstractController ->getSingleScalarResult() ; } - + /** * BAse method for edit action - * + * * IMplemented by the method formEditAction, with action as 'edit' - * + * * @param Request $request * @param mixed $id * @return Response @@ -430,12 +431,12 @@ class CRUDController extends AbstractController { return $this->formEditAction('edit', $request, $id); } - + /** * Base method for new action - * + * * Implemented by the method formNewAction, with action as 'new' - * + * * @param Request $request * @return Response */ @@ -443,12 +444,12 @@ class CRUDController extends AbstractController { return $this->formCreateAction('new', $request); } - + /** * Base method for the view action. - * + * * Implemented by the method viewAction, with action as 'view' - * + * * @param Request $request * @param mixed $id * @return Response @@ -457,105 +458,124 @@ class CRUDController extends AbstractController { return $this->viewAction('view', $request, $id); } - + /** * The view action. - * + * * Some steps may be overriden during this process of rendering: - * + * * This method: - * + * * 1. fetch the entity, using `getEntity` * 2. launch `onPostFetchEntity`. If postfetch is an instance of Response, * this response is returned. * 2. throw an HttpNotFoundException if entity is null * 3. check ACL using `checkACL` ; - * 4. launch `onPostCheckACL`. If the result is an instance of Response, + * 4. launch `onPostCheckACL`. If the result is an instance of Response, * this response is returned ; * 5. generate default template parameters: - * + * * * `entity`: the fetched entity ; * * `crud_name`: the crud name - * 6. Launch rendering, the parameter is fetch using `getTemplateFor` + * 6. Launch rendering, the parameter is fetch using `getTemplateFor` * The parameters may be personnalized using `generateTemplateParameter`. - * + * * @param string $action * @param Request $request * @param mixed $id * @return Response */ - protected function viewAction(string $action, Request $request, $id) + protected function viewAction(string $action, Request $request, $id, $_format = 'html'): Response { $entity = $this->getEntity($action, $id, $request); - + $postFetch = $this->onPostFetchEntity($action, $request, $entity); - + if ($postFetch instanceof Response) { return $postFetch; } - + if (NULL === $entity) { throw $this->createNotFoundException(sprintf("The %s with id %s " - . "is not found"), $this->getCrudName(), $id); + . "is not found", $this->getCrudName(), $id)); } - + $response = $this->checkACL($action, $entity); if ($response instanceof Response) { return $response; } - + $response = $this->onPostCheckACL($action, $request, $entity); if ($response instanceof Response) { return $response; } - - $defaultTemplateParameters = [ - 'entity' => $entity, - 'crud_name' => $this->getCrudName() - ]; - - return $this->render( - $this->getTemplateFor($action, $entity, $request), - $this->generateTemplateParameter($action, $entity, $request, $defaultTemplateParameters) + + if ($_format === 'html') { + $defaultTemplateParameters = [ + 'entity' => $entity, + 'crud_name' => $this->getCrudName() + ]; + + return $this->render( + $this->getTemplateFor($action, $entity, $request), + $this->generateTemplateParameter($action, $entity, $request, $defaultTemplateParameters) ); + } elseif ($_format === 'json') { + $context = $this->getContextForSerialization($action, $request, $entity, $_format); + + return $this->json($entity, Response::HTTP_OK, [], $context); + } else { + throw new \Symfony\Component\HttpFoundation\Exception\BadRequestException("This format is not implemented"); + } + } - + + /** + * Get the context for the serialization + */ + public function getContextForSerialization(string $action, Request $request, $entity, string $_format): array + { + return []; + } + + + /** * The edit action. - * + * * Some steps may be overriden during this process of rendering: - * + * * This method: - * + * * 1. fetch the entity, using `getEntity` * 2. launch `onPostFetchEntity`. If postfetch is an instance of Response, * this response is returned. * 2. throw an HttpNotFoundException if entity is null * 3. check ACL using `checkACL` ; - * 4. launch `onPostCheckACL`. If the result is an instance of Response, + * 4. launch `onPostCheckACL`. If the result is an instance of Response, * this response is returned ; * 5. generate a form using `createFormFor`, and handle request on this form; - * + * * If the form is valid, the entity is stored and flushed, and a redirection * is returned. - * + * * In this case, those hooks are available: - * + * * * onFormValid * * onPreFlush * * onPostFlush - * * onBeforeRedirectAfterSubmission. If this method return an instance of + * * onBeforeRedirectAfterSubmission. If this method return an instance of * Response, this response is returned. - * + * * 5. generate default template parameters: - * + * * * `entity`: the fetched entity ; * * `crud_name`: the crud name ; * * `form`: the formview instance. - * - * 6. Launch rendering, the parameter is fetch using `getTemplateFor` + * + * 6. Launch rendering, the parameter is fetch using `getTemplateFor` * The parameters may be personnalized using `generateTemplateParameter`. - * + * * @param string $action * @param Request $request * @param mixed $id @@ -567,98 +587,98 @@ class CRUDController extends AbstractController protected function formEditAction(string $action, Request $request, $id, string $formClass = null, array $formOptions = []): Response { $entity = $this->getEntity($action, $id, $request); - + if (NULL === $entity) { throw $this->createNotFoundException(sprintf("The %s with id %s " - . "is not found"), $this->getCrudName(), $id); + . "is not found", $this->getCrudName(), $id)); } - + $response = $this->checkACL($action, $entity); if ($response instanceof Response) { return $response; } - + $response = $this->onPostCheckACL($action, $request, $entity); if ($response instanceof Response) { return $response; } - + $form = $this->createFormFor($action, $entity, $formClass, $formOptions); - + $form->handleRequest($request); - + if ($form->isSubmitted() && $form->isValid()) { $this->onFormValid($entity, $form, $request); $em = $this->getDoctrine()->getManager(); - + $this->onPreFlush($action, $entity, $form, $request); $em->flush(); $this->onPostFlush($action, $entity, $form, $request); - + $this->addFlash('success', $this->generateFormSuccessMessage($action, $entity)); - + $result = $this->onBeforeRedirectAfterSubmission($action, $entity, $form, $request); - + if ($result instanceof Response) { return $result; } - + return $this->redirectToRoute('chill_crud_'.$this->getCrudName().'_index'); - + } elseif ($form->isSubmitted()) { $this->addFlash('error', $this->generateFormErrorMessage($action, $form)); } - + $defaultTemplateParameters = [ 'form' => $form->createView(), 'entity' => $entity, 'crud_name' => $this->getCrudName() ]; - + return $this->render( - $this->getTemplateFor($action, $entity, $request), + $this->getTemplateFor($action, $entity, $request), $this->generateTemplateParameter($action, $entity, $request, $defaultTemplateParameters) ); } - + /** * The new (or creation) action. - * + * * Some steps may be overriden during this process of rendering: - * + * * This method: - * + * * 1. Create or duplicate an entity: - * - * If the `duplicate` parameter is present, the entity is duplicated + * + * If the `duplicate` parameter is present, the entity is duplicated * using the `duplicate` method. - * + * * If not, the entity is created using the `create` method. * 3. check ACL using `checkACL` ; - * 4. launch `onPostCheckACL`. If the result is an instance of Response, + * 4. launch `onPostCheckACL`. If the result is an instance of Response, * this response is returned ; * 5. generate a form using `createFormFor`, and handle request on this form; - * + * * If the form is valid, the entity is stored and flushed, and a redirection * is returned. - * + * * In this case, those hooks are available: - * + * * * onFormValid * * onPreFlush * * onPostFlush - * * onBeforeRedirectAfterSubmission. If this method return an instance of + * * onBeforeRedirectAfterSubmission. If this method return an instance of * Response, this response is returned. - * + * * 5. generate default template parameters: - * + * * * `entity`: the fetched entity ; * * `crud_name`: the crud name ; * * `form`: the formview instance. - * - * 6. Launch rendering, the parameter is fetch using `getTemplateFor` + * + * 6. Launch rendering, the parameter is fetch using `getTemplateFor` * The parameters may be personnalized using `generateTemplateParameter`. - * + * * @param string $action * @param Request $request * @param type $formClass @@ -671,62 +691,62 @@ class CRUDController extends AbstractController } else { $entity = $this->createEntity($action, $request); } - + $response = $this->checkACL($action, $entity); if ($response instanceof Response) { return $response; } - + $response = $this->onPostCheckACL($action, $request, $entity); if ($response instanceof Response) { return $response; } - + $form = $this->createFormFor($action, $entity, $formClass); - + $form->handleRequest($request); - + if ($form->isSubmitted() && $form->isValid()) { $this->onFormValid($entity, $form, $request); $em = $this->getDoctrine()->getManager(); - + $this->onPrePersist($action, $entity, $form, $request); $em->persist($entity); $this->onPostPersist($action, $entity, $form, $request); - + $this->onPreFlush($action, $entity, $form, $request); $em->flush(); $this->onPostFlush($action, $entity, $form, $request); $this->getPaginatorFactory(); $this->addFlash('success', $this->generateFormSuccessMessage($action, $entity)); - + $result = $this->onBeforeRedirectAfterSubmission($action, $entity, $form, $request); - + if ($result instanceof Response) { return $result; } - + return $this->redirectToRoute('chill_crud_'.$this->getCrudName().'_view', ['id' => $entity->getId()]); - + } elseif ($form->isSubmitted()) { $this->addFlash('error', $this->generateFormErrorMessage($action, $form)); } - + $defaultTemplateParameters = [ 'form' => $form->createView(), 'entity' => $entity, 'crud_name' => $this->getCrudName() ]; - + return $this->render( - $this->getTemplateFor($action, $entity, $request), + $this->getTemplateFor($action, $entity, $request), $this->generateTemplateParameter($action, $entity, $request, $defaultTemplateParameters) ); } - + /** * get the instance of the entity with the given id - * + * * @param string $id * @return object */ @@ -736,10 +756,10 @@ class CRUDController extends AbstractController ->getRepository($this->getEntityClass()) ->find($id); } - + /** * Duplicate an entity - * + * * @param string $action * @param Request $request * @return mixed @@ -748,24 +768,24 @@ class CRUDController extends AbstractController { $id = $request->query->get('duplicate_id', 0); $originalEntity = $this->getEntity($action, $id, $request); - + $this->getDoctrine()->getManager() ->detach($originalEntity); - + return $originalEntity; } - + /** - * + * * @return string the complete fqdn of the class */ protected function getEntityClass(): string { return $this->crudConfig['class']; } - + /** - * + * * @return string the crud name */ protected function getCrudName(): string @@ -775,13 +795,13 @@ class CRUDController extends AbstractController /** * check the acl. Called by every action. - * - * By default, check the role given by `getRoleFor` for the value given in + * + * By default, check the role given by `getRoleFor` for the value given in * $entity. - * + * * Throw an \Symfony\Component\Security\Core\Exception\AccessDeniedHttpException * if not accessible. - * + * * @param string $action * @param mixed $entity * @throws \Symfony\Component\Security\Core\Exception\AccessDeniedHttpException @@ -790,39 +810,39 @@ class CRUDController extends AbstractController { $this->denyAccessUnlessGranted($this->getRoleFor($action), $entity); } - + /** * get the role given from the config. - * + * * @param string $action * @return string */ protected function getRoleFor($action) { - if (NULL !== ($this->getActionConfig($action)['role'])) { + if (\array_key_exists('role', $this->getActionConfig($action))) { return $this->getActionConfig($action)['role']; } - + return $this->buildDefaultRole($action); } /** * build a default role name, using the crud resolver. - * + * * This method should not be overriden. Override `getRoleFor` instead. - * + * * @param string $action * @return string */ protected function buildDefaultRole($action) { - return $this->getCrudResolver()->buildDefaultRole($this->getCrudName(), + return $this->getCrudResolver()->buildDefaultRole($this->getCrudName(), $action); } - + /** * get the default form class from config - * + * * @param string $action * @return string the FQDN of the form class */ @@ -832,27 +852,27 @@ class CRUDController extends AbstractController return $this->crudConfig[$action]['form_class'] ?? $this->getDefaultDeleteFormClass($action); } - + return $this->crudConfig[$action]['form_class'] ?? $this->crudConfig['form_class']; } - + protected function getDefaultDeleteFormClass($action) { return CRUDDeleteEntityForm::class; } - + /** * Create a form - * + * * use the method `getFormClassFor` - * + * * A hook is available: `customizeForm` allow you to customize the form * if needed. - * + * * It is preferable to override customizeForm instead of overriding * this method. - * + * * @param string $action * @param mixed $entity * @param string $formClass @@ -862,17 +882,17 @@ class CRUDController extends AbstractController protected function createFormFor(string $action, $entity, string $formClass = null, array $formOptions = []): FormInterface { $formClass = $formClass ?? $this->getFormClassFor($action); - + $form = $this->createForm($formClass, $entity, $formOptions); - + $this->customizeForm($action, $form); - + return $form; } - + /** * Customize the form created by createFormFor. - * + * * @param string $action * @param FormInterface $form */ @@ -880,12 +900,12 @@ class CRUDController extends AbstractController { } - + /** * Generate a message which explains an error about the form. - * + * * Used in form actions - * + * * @param string $action * @param FormInterface $form * @return string @@ -893,13 +913,13 @@ class CRUDController extends AbstractController protected function generateFormErrorMessage(string $action, FormInterface $form): string { $msg = 'This form contains errors'; - + return $this->getTranslator()->trans($msg); } - + /** * Generate a success message when a form could be flushed successfully - * + * * @param string $action * @param mixed $entity * @return string @@ -919,13 +939,13 @@ class CRUDController extends AbstractController default: $msg = "crud.default.success"; } - + return $this->getTranslator()->trans($msg); } - + /** * Customize template parameters. - * + * * @param string $action * @param mixed $entity * @param Request $request @@ -933,9 +953,9 @@ class CRUDController extends AbstractController * @return array */ protected function generateTemplateParameter( - string $action, - $entity, - Request $request, + string $action, + $entity, + Request $request, array $defaultTemplateParameters = [] ) { return $defaultTemplateParameters; @@ -943,7 +963,7 @@ class CRUDController extends AbstractController /** * Create an entity. - * + * * @param string $action * @param Request $request * @return object @@ -951,17 +971,17 @@ class CRUDController extends AbstractController protected function createEntity(string $action, Request $request): object { $type = $this->getEntityClass(); - + return new $type; } - + /** * Get the template for the current crud. - * - * This template may be defined in configuration. If any template are - * defined, return the default template for the actions new, edit, index, + * + * This template may be defined in configuration. If any template are + * defined, return the default template for the actions new, edit, index, * and view. - * + * * @param string $action * @param mixed $entity the entity for the current request, or an array of entities * @param Request $request @@ -973,11 +993,11 @@ class CRUDController extends AbstractController if ($this->hasCustomTemplate($action, $entity, $request)) { return $this->getActionConfig($action)['template']; } - + switch ($action) { case 'new': return '@ChillMain/CRUD/new.html.twig'; - case 'edit': + case 'edit': return '@ChillMain/CRUD/edit.html.twig'; case 'index': return '@ChillMain/CRUD/index.html.twig'; @@ -991,7 +1011,7 @@ class CRUDController extends AbstractController . "action"); } } - + /** * @param $action * @param $entity @@ -1002,7 +1022,7 @@ class CRUDController extends AbstractController { return !empty($this->getActionConfig($action)['template']); } - + /** * @param string $action * @param $entity @@ -1012,7 +1032,7 @@ class CRUDController extends AbstractController protected function onPreFlush(string $action, $entity, FormInterface $form, Request $request) { } - + /** * @param string $action * @param $entity @@ -1022,7 +1042,7 @@ class CRUDController extends AbstractController protected function onPostFlush(string $action, $entity, FormInterface $form, Request $request) { } - + /** * @param string $action * @param $entity @@ -1032,7 +1052,7 @@ class CRUDController extends AbstractController protected function onPrePersist(string $action, $entity, FormInterface $form, Request $request) { } - + /** * @param string $action * @param $entity @@ -1042,7 +1062,7 @@ class CRUDController extends AbstractController protected function onPostPersist(string $action, $entity, FormInterface $form, Request $request) { } - + /** * @param $action * @param Request $request @@ -1053,7 +1073,7 @@ class CRUDController extends AbstractController { return null; } - + /** * @param $action * @param Request $request @@ -1064,7 +1084,7 @@ class CRUDController extends AbstractController { return null; } - + /** * @param object $entity * @param FormInterface $form @@ -1073,16 +1093,16 @@ class CRUDController extends AbstractController protected function onFormValid(object $entity, FormInterface $form, Request $request) { } - + /** * Return a redirect response depending on the value of submit button. - * + * * The handled values are : - * + * * * save-and-close: return to index of current crud ; * * save-and-new: return to new page of current crud ; * * save-and-view: return to view page of current crud ; - * + * * @param string $action * @param mixed $entity * @param FormInterface $form @@ -1104,7 +1124,7 @@ class CRUDController extends AbstractController ]); } } - + /** * Include services * @@ -1115,7 +1135,7 @@ class CRUDController extends AbstractController { return $this->crudConfig['actions'][$action]; } - + /** * @return PaginatorFactory */ @@ -1123,7 +1143,7 @@ class CRUDController extends AbstractController { return $this->container->get('chill_main.paginator_factory'); } - + /** * @return TranslatorInterface */ @@ -1131,7 +1151,7 @@ class CRUDController extends AbstractController { return $this->container->get('translator'); } - + /** * @return AuthorizationHelper */ @@ -1139,19 +1159,19 @@ class CRUDController extends AbstractController { return $this->container->get(AuthorizationHelper::class); } - + /** * @param Role $role * @param Scope|null $scope * @return \Chill\MainBundle\Entity\Center[] */ - protected function getReachableCenters(Role $role, Scope $scope = null) + protected function getReachableCenters(Role $role, Scope $scope = null) { return $this->getAuthorizationHelper() ->getReachableCenters($this->getUser(), $role, $scope) ; } - + /** * @return EventDispatcherInterface */ @@ -1159,7 +1179,7 @@ class CRUDController extends AbstractController { return $this->get(EventDispatcherInterface::class); } - + /** * @return Resolver */ @@ -1167,7 +1187,7 @@ class CRUDController extends AbstractController { return $this->get(Resolver::class); } - + /** * @return array */ @@ -1181,6 +1201,7 @@ class CRUDController extends AbstractController AuthorizationHelper::class => AuthorizationHelper::class, EventDispatcherInterface::class => EventDispatcherInterface::class, Resolver::class => Resolver::class, + SerializerInterface::class => SerializerInterface::class, ] ); } diff --git a/src/Bundle/ChillMainBundle/CRUD/Routing/CRUDRoutesLoader.php b/src/Bundle/ChillMainBundle/CRUD/Routing/CRUDRoutesLoader.php index 5a0eb405e..32068e518 100644 --- a/src/Bundle/ChillMainBundle/CRUD/Routing/CRUDRoutesLoader.php +++ b/src/Bundle/ChillMainBundle/CRUD/Routing/CRUDRoutesLoader.php @@ -23,6 +23,9 @@ namespace Chill\MainBundle\CRUD\Routing; use Symfony\Component\Config\Loader\Loader; use Symfony\Component\Routing\Route; use Symfony\Component\Routing\RouteCollection; +use Symfony\Component\HttpFoundation\Request; +use Chill\MainBundle\CRUD\Controller\ApiController; +use Chill\MainBundle\CRUD\Controller\CRUDController; /** * Class CRUDRoutesLoader @@ -32,24 +35,34 @@ use Symfony\Component\Routing\RouteCollection; */ class CRUDRoutesLoader extends Loader { - /** - * @var array - */ - protected $config = []; + protected array $crudConfig = []; + + protected array $apiCrudConfig = []; /** * @var bool */ private $isLoaded = false; + + private const ALL_SINGLE_METHODS = [ + Request::METHOD_GET, + Request::METHOD_POST, + Request::METHOD_PUT, + Request::METHOD_DELETE + ]; + + private const ALL_INDEX_METHODS = [ Request::METHOD_GET, Request::METHOD_HEAD ]; /** * CRUDRoutesLoader constructor. * - * @param $config + * @param $crudConfig the config from cruds + * @param $apicrudConfig the config from api_crud */ - public function __construct($config) + public function __construct(array $crudConfig, array $apiConfig) { - $this->config = $config; + $this->crudConfig = $crudConfig; + $this->apiConfig = $apiConfig; } /** @@ -63,53 +76,161 @@ class CRUDRoutesLoader extends Loader } /** - * @return RouteCollection + * Load routes for CRUD and CRUD Api */ - public function load($resource, $type = null) + public function load($resource, $type = null): RouteCollection { - if (true === $this->isLoaded) { throw new \RuntimeException('Do not add the "CRUD" loader twice'); } - + $collection = new RouteCollection(); - foreach ($this->config as $config) { - $collection->addCollection($this->loadConfig($config)); + foreach ($this->crudConfig as $crudConfig) { + $collection->addCollection($this->loadCrudConfig($crudConfig)); } - + foreach ($this->apiConfig as $crudConfig) { + $collection->addCollection($this->loadApi($crudConfig)); + } + return $collection; } /** - * @param $config + * Load routes for CRUD (without api) + * + * @param $crudConfig * @return RouteCollection */ - protected function loadConfig($config): RouteCollection + protected function loadCrudConfig($crudConfig): RouteCollection { $collection = new RouteCollection(); - foreach ($config['actions'] as $name => $action) { + $controller ='cscrud_'.$crudConfig['name'].'_controller'; + + foreach ($crudConfig['actions'] as $name => $action) { + // defaults (controller name) $defaults = [ - '_controller' => 'cscrud_'.$config['name'].'_controller'.':'.($action['controller_action'] ?? $name) + '_controller' => $controller.':'.($action['controller_action'] ?? $name) ]; if ($name === 'index') { - $path = "{_locale}".$config['base_path']; + $path = "{_locale}".$crudConfig['base_path']; $route = new Route($path, $defaults); } elseif ($name === 'new') { - $path = "{_locale}".$config['base_path'].'/'.$name; + $path = "{_locale}".$crudConfig['base_path'].'/'.$name; $route = new Route($path, $defaults); } else { - $path = "{_locale}".$config['base_path'].($action['path'] ?? '/{id}/'.$name); + $path = "{_locale}".$crudConfig['base_path'].($action['path'] ?? '/{id}/'.$name); $requirements = $action['requirements'] ?? [ '{id}' => '\d+' ]; $route = new Route($path, $defaults, $requirements); } - $collection->add('chill_crud_'.$config['name'].'_'.$name, $route); + $collection->add('chill_crud_'.$crudConfig['name'].'_'.$name, $route); } return $collection; } + + /** + * Load routes for api single + * + * @param $crudConfig + * @return RouteCollection + */ + protected function loadApi(array $crudConfig): RouteCollection + { + $collection = new RouteCollection(); + $controller ='csapi_'.$crudConfig['name'].'_controller'; + + foreach ($crudConfig['actions'] as $name => $action) { + // filter only on single actions + $singleCollection = $action['single-collection'] ?? $name === '_entity' ? 'single' : NULL; + if ('collection' === $singleCollection) { +// continue; + } + + // compute default action + switch ($name) { + case '_entity': + $controllerAction = 'entityApi'; + break; + case '_index': + $controllerAction = 'indexApi'; + break; + default: + $controllerAction = $name.'Api'; + break; + } + + $defaults = [ + '_controller' => $controller.':'.($action['controller_action'] ?? $controllerAction) + ]; + + // path are rewritten + // if name === 'default', we rewrite it to nothing :-) + $localName = \in_array($name, [ '_entity', '_index' ]) ? '' : '/'.$name; + if ('collection' === $action['single-collection'] || '_index' === $name) { + $localPath = $action['path'] ?? $localName.'.{_format}'; + } else { + $localPath = $action['path'] ?? '/{id}'.$localName.'.{_format}'; + } + $path = $crudConfig['base_path'].$localPath; + + $requirements = $action['requirements'] ?? [ '{id}' => '\d+' ]; + + $methods = \array_keys(\array_filter($action['methods'], function($value, $key) { return $value; }, + ARRAY_FILTER_USE_BOTH)); + + $route = new Route($path, $defaults, $requirements); + $route->setMethods($methods); + + $collection->add('chill_api_single_'.$crudConfig['name'].'_'.$name, $route); + } + + return $collection; + } + + /** + * Load routes for api multi + * + * @param $crudConfig + * @return RouteCollection + */ + protected function loadApiMultiConfig(array $crudConfig): RouteCollection + { + $collection = new RouteCollection(); + $controller ='csapi_'.$crudConfig['name'].'_controller'; + + foreach ($crudConfig['actions'] as $name => $action) { + // filter only on single actions + $singleCollection = $action['single-collection'] ?? $name === '_index' ? 'collection' : NULL; + if ('single' === $singleCollection) { + continue; + } + + $defaults = [ + '_controller' => $controller.':'.($action['controller_action'] ?? '_entity' === $name ? 'entityApi' : $name.'Api') + ]; + + // path are rewritten + // if name === 'default', we rewrite it to nothing :-) + $localName = '_entity' === $name ? '' : '/'.$name; + $localPath = $action['path'] ?? '/{id}'.$localName.'.{_format}'; + $path = $crudConfig['base_path'].$localPath; + + $requirements = $action['requirements'] ?? [ '{id}' => '\d+' ]; + + $methods = \array_keys(\array_filter($action['methods'], function($value, $key) { return $value; }, + ARRAY_FILTER_USE_BOTH)); + + $route = new Route($path, $defaults, $requirements); + $route->setMethods($methods); + + $collection->add('chill_api_single_'.$crudConfig['name'].'_'.$name, $route); + } + + return $collection; + } } diff --git a/src/Bundle/ChillMainBundle/ChillMainBundle.php b/src/Bundle/ChillMainBundle/ChillMainBundle.php index abfcdd6fd..51ef344f2 100644 --- a/src/Bundle/ChillMainBundle/ChillMainBundle.php +++ b/src/Bundle/ChillMainBundle/ChillMainBundle.php @@ -14,6 +14,7 @@ use Chill\MainBundle\DependencyInjection\CompilerPass\NotificationCounterCompile use Chill\MainBundle\DependencyInjection\CompilerPass\MenuCompilerPass; use Chill\MainBundle\DependencyInjection\CompilerPass\ACLFlagsCompilerPass; use Chill\MainBundle\DependencyInjection\CompilerPass\GroupingCenterCompilerPass; +use Chill\MainBundle\CRUD\CompilerPass\CRUDControllerCompilerPass; use Chill\MainBundle\Templating\Entity\CompilerPass as RenderEntityCompilerPass; @@ -33,5 +34,6 @@ class ChillMainBundle extends Bundle $container->addCompilerPass(new ACLFlagsCompilerPass()); $container->addCompilerPass(new GroupingCenterCompilerPass()); $container->addCompilerPass(new RenderEntityCompilerPass()); + $container->addCompilerPass(new CRUDControllerCompilerPass()); } } diff --git a/src/Bundle/ChillMainBundle/Controller/AddressController.php b/src/Bundle/ChillMainBundle/Controller/AddressController.php new file mode 100644 index 000000000..1aeb39062 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Controller/AddressController.php @@ -0,0 +1,63 @@ +json($address); + default: + throw new BadRequestException('Unsupported format'); + } + } + + + /** + * Get API Data for showing endpoint + * + * @Route( + * "/{_locale}/main/api/1.0/address-reference/{address_reference_id}/show.{_format}", + * name="chill_main_address_reference_api_show" + * ) + * @ParamConverter("addressReference", options={"id": "address_reference_id"}) + */ + public function showAddressReference(AddressReference $addressReference, $_format): Response + { + // TODO check ACL ? + switch ($_format) { + case 'json': + return $this->json($addressReference); + default: + throw new BadRequestException('Unsupported format'); + } + + } +} diff --git a/src/Bundle/ChillMainBundle/DataFixtures/ORM/LoadAddressReferences.php b/src/Bundle/ChillMainBundle/DataFixtures/ORM/LoadAddressReferences.php new file mode 100644 index 000000000..81bd1d909 --- /dev/null +++ b/src/Bundle/ChillMainBundle/DataFixtures/ORM/LoadAddressReferences.php @@ -0,0 +1,95 @@ +faker = \Faker\Factory::create('fr_FR'); + } + + /** + * + * @var ContainerInterface + */ + private $container; + + public function setContainer(ContainerInterface $container = null) + { + $this->container = $container; + } + + public function getOrder() { + return 51; + } + + + /** + * Create a random point + * + * @return Point + */ + private function getRandomPoint() + { + $lonBrussels = 4.35243; + $latBrussels = 50.84676; + $lon = $lonBrussels + 0.01 * rand(-5, 5); + $lat = $latBrussels + 0.01 * rand(-5, 5); + return Point::fromLonLat($lon, $lat); + } + + /** + * Create a random reference address + * + * @return AddressReference + */ + private function getRandomAddressReference() + { + $ar= new AddressReference(); + + $ar->setRefId($this->faker->numerify('ref-id-######')); + $ar->setStreet($this->faker->streetName); + $ar->setStreetNumber(rand(0,199)); + $ar ->setPoint($this->getRandomPoint()); + $ar->setPostcode($this->getReference( + LoadPostalCodes::$refs[array_rand(LoadPostalCodes::$refs)] + )); + + $ar->setMunicipalityCode($ar->getPostcode()->getCode()); + + return $ar + ; + } + + public function load(ObjectManager $manager) { + + echo "loading some reference address... \n"; + + for ($i=0; $i<10; $i++) { + $ar = $this->getRandomAddressReference(); + $manager->persist($ar); + } + + $manager->flush(); + } + + +} diff --git a/src/Bundle/ChillMainBundle/DependencyInjection/ChillMainExtension.php b/src/Bundle/ChillMainBundle/DependencyInjection/ChillMainExtension.php index 234e1203d..a40221263 100644 --- a/src/Bundle/ChillMainBundle/DependencyInjection/ChillMainExtension.php +++ b/src/Bundle/ChillMainBundle/DependencyInjection/ChillMainExtension.php @@ -133,7 +133,7 @@ class ChillMainExtension extends Extension implements PrependExtensionInterface, $loader->load('services/search.yaml'); $loader->load('services/serializer.yaml'); - $this->configureCruds($container, $config['cruds'], $loader); + $this->configureCruds($container, $config['cruds'], $config['apis'], $loader); } /** @@ -188,7 +188,12 @@ class ChillMainExtension extends Extension implements PrependExtensionInterface, $container->prependExtensionConfig('doctrine', array( 'dbal' => [ 'types' => [ - 'dateinterval' => \Chill\MainBundle\Doctrine\Type\NativeDateIntervalType::class + 'dateinterval' => [ + 'class' => \Chill\MainBundle\Doctrine\Type\NativeDateIntervalType::class + ], + 'point' => [ + 'class' => \Chill\MainBundle\Doctrine\Type\PointType::class + ] ] ] )); @@ -210,51 +215,24 @@ class ChillMainExtension extends Extension implements PrependExtensionInterface, } /** - * @param ContainerBuilder $container - * @param array $config the config under 'cruds' key - * @return null + * Load parameter for configuration and set parameters for api */ - protected function configureCruds(ContainerBuilder $container, $config, Loader\YamlFileLoader $loader) + protected function configureCruds( + ContainerBuilder $container, + array $crudConfig, + array $apiConfig, + Loader\YamlFileLoader $loader + ): void { - if (count($config) === 0) { + if (count($crudConfig) === 0) { return; } $loader->load('services/crud.yaml'); - $container->setParameter('chill_main_crud_route_loader_config', $config); + $container->setParameter('chill_main_crud_route_loader_config', $crudConfig); + $container->setParameter('chill_main_api_route_loader_config', $apiConfig); - $definition = new Definition(); - $definition - ->setClass(\Chill\MainBundle\CRUD\Routing\CRUDRoutesLoader::class) - ->addArgument('%chill_main_crud_route_loader_config%') - ; - - $container->setDefinition('chill_main_crud_route_loader', $definition); - - $alreadyExistingNames = []; - - foreach ($config as $crudEntry) { - $controller = $crudEntry['controller']; - $controllerServiceName = 'cscrud_'.$crudEntry['name'].'_controller'; - $name = $crudEntry['name']; - - // check for existing crud names - if (\in_array($name, $alreadyExistingNames)) { - throw new LogicException(sprintf("the name %s is defined twice in CRUD", $name)); - } - - if (!$container->has($controllerServiceName)) { - $controllerDefinition = new Definition($controller); - $controllerDefinition->addTag('controller.service_arguments'); - $controllerDefinition->setAutoconfigured(true); - $controllerDefinition->setClass($crudEntry['controller']); - $container->setDefinition($controllerServiceName, $controllerDefinition); - } - - $container->setParameter('chill_main_crud_config_'.$name, $crudEntry); - $container->getDefinition($controllerServiceName) - ->addMethodCall('setCrudConfig', ['%chill_main_crud_config_'.$name.'%']); - } + // Note: the controller are loaded inside compiler pass } } diff --git a/src/Bundle/ChillMainBundle/DependencyInjection/Configuration.php b/src/Bundle/ChillMainBundle/DependencyInjection/Configuration.php index d5024bf5d..c7e4c00ef 100644 --- a/src/Bundle/ChillMainBundle/DependencyInjection/Configuration.php +++ b/src/Bundle/ChillMainBundle/DependencyInjection/Configuration.php @@ -8,6 +8,7 @@ use Chill\MainBundle\DependencyInjection\Widget\Factory\WidgetFactoryInterface; use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition; use Chill\MainBundle\DependencyInjection\Widget\AddWidgetConfigurationTrait; use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\HttpFoundation\Request; /** @@ -140,7 +141,7 @@ class Configuration implements ConfigurationInterface ->scalarNode('controller_action') ->defaultNull() ->info('the method name to call in the route. Will be set to the action name if left empty.') - ->example("'action'") + ->example("action") ->end() ->scalarNode('path') ->defaultNull() @@ -168,6 +169,78 @@ class Configuration implements ConfigurationInterface ->end() ->end() + + + ->arrayNode('apis') + ->defaultValue([]) + ->arrayPrototype() + ->children() + ->scalarNode('class')->cannotBeEmpty()->isRequired()->end() + ->scalarNode('controller') + ->cannotBeEmpty() + ->defaultValue(\Chill\MainBundle\CRUD\Controller\ApiController::class) + ->end() + ->scalarNode('name')->cannotBeEmpty()->isRequired()->end() + ->scalarNode('base_path')->cannotBeEmpty()->isRequired()->end() + ->scalarNode('base_role')->defaultNull()->end() + ->arrayNode('actions') + ->useAttributeAsKey('name') + ->arrayPrototype() + ->children() + ->scalarNode('controller_action') + ->defaultNull() + ->info('the method name to call in the controller. Will be set to the concatenation '. + 'of action name + \'Api\' if left empty.') + ->example("showApi") + ->end() + ->scalarNode('path') + ->defaultNull() + ->info('the path that will be **appended** after the base path. Do not forget to add ' . + 'arguments for the method. By default, will set to the action name, including an `{id}` '. + 'parameter. A suffix of action name will be appended, except if the action name '. + 'is "_entity".') + ->example('/{id}/my-action') + ->end() + ->arrayNode('requirements') + ->ignoreExtraKeys(false) + ->info('the requirements for the route. Will be set to `[ \'id\' => \'\d+\' ]` if left empty.') + ->end() + ->enumNode('single-collection') + ->values(['single', 'collection']) + ->defaultValue('single') + ->info('indicates if the returned object is a single element or a collection. '. + 'If the action name is `_index`, this value will always be considered as '. + '`collection`') + ->end() + ->arrayNode('methods') + ->addDefaultsIfNotSet() + ->info('the allowed methods') + ->children() + ->booleanNode(Request::METHOD_GET)->defaultTrue()->end() + ->booleanNode(Request::METHOD_HEAD)->defaultTrue()->end() + ->booleanNode(Request::METHOD_POST)->defaultFalse()->end() + ->booleanNode(Request::METHOD_DELETE)->defaultFalse()->end() + ->booleanNode(Request::METHOD_PUT)->defaultFalse()->end() + ->end() + ->end() + ->arrayNode('roles') + ->addDefaultsIfNotSet() + ->info("The role require for each http method") + ->children() + ->scalarNode(Request::METHOD_GET)->defaultNull()->end() + ->scalarNode(Request::METHOD_HEAD)->defaultNull()->end() + ->scalarNode(Request::METHOD_POST)->defaultNull()->end() + ->scalarNode(Request::METHOD_DELETE)->defaultNull()->end() + ->scalarNode(Request::METHOD_PUT)->defaultNull()->end() + ->end() + ->end() + ->end() + ->end() + ->end() + ->end() + ->end() + + ->end() ->end() // end of root/children ->end() // end of root ; diff --git a/src/Bundle/ChillMainBundle/Doctrine/Model/Point.php b/src/Bundle/ChillMainBundle/Doctrine/Model/Point.php new file mode 100644 index 000000000..43c21ae59 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Doctrine/Model/Point.php @@ -0,0 +1,103 @@ +lat = $lat; + $this->lon = $lon; + } + + public function toGeoJson(): string + { + $array = $this->toArrayGeoJson(); + return \json_encode($array); + } + + public function jsonSerialize(): array + { + return $this->toArrayGeoJson(); + } + + public function toArrayGeoJson(): array + { + return [ + "type" => "Point", + "coordinates" => [ $this->lon, $this->lat ] + ]; + } + + /** + * + * @return string + */ + public function toWKT(): string + { + return 'SRID='.self::$SRID.';POINT('.$this->lon.' '.$this->lat.')'; + } + + /** + * + * @param type $geojson + * @return Point + */ + public static function fromGeoJson(string $geojson): Point + { + $a = json_decode($geojson); + //check if the geojson string is correct + if (NULL === $a or !isset($a->type) or !isset($a->coordinates)){ + throw PointException::badJsonString($geojson); + } + + if ($a->type != 'Point'){ + throw PointException::badGeoType(); + } + + $lat = $a->coordinates[1]; + $lon = $a->coordinates[0]; + + return Point::fromLonLat($lon, $lat); + } + + public static function fromLonLat(float $lon, float $lat): Point + { + if (($lon > -180 && $lon < 180) && ($lat > -90 && $lat < 90)) + { + return new Point($lon, $lat); + } else { + throw PointException::badCoordinates($lon, $lat); + } + } + + public static function fromArrayGeoJson(array $array): Point + { + if ($array['type'] == 'Point' && + isset($array['coordinates'])) + { + return self::fromLonLat($array['coordinates'][0], $array['coordinates'][1]); + } + } + + public function getLat(): float + { + return $this->lat; + } + + public function getLon(): float + { + return $this->lon; + } +} + + diff --git a/src/Bundle/ChillMainBundle/Doctrine/Model/PointException.php b/src/Bundle/ChillMainBundle/Doctrine/Model/PointException.php new file mode 100644 index 000000000..4e3101435 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Doctrine/Model/PointException.php @@ -0,0 +1,27 @@ +toWKT(); + } + } + + public function canRequireSQLConversion() + { + return true; + } + + public function convertToPHPValueSQL($sqlExpr, $platform) + { + return 'ST_AsGeoJSON('.$sqlExpr.') '; + } + + public function convertToDatabaseValueSQL($sqlExpr, AbstractPlatform $platform) + { + return $sqlExpr; + } +} + diff --git a/src/Bundle/ChillMainBundle/Entity/Address.php b/src/Bundle/ChillMainBundle/Entity/Address.php index 69c39c7b6..36eda09c2 100644 --- a/src/Bundle/ChillMainBundle/Entity/Address.php +++ b/src/Bundle/ChillMainBundle/Entity/Address.php @@ -4,6 +4,8 @@ namespace Chill\MainBundle\Entity; use Doctrine\ORM\Mapping as ORM; use Symfony\Component\Validator\Context\ExecutionContextInterface; +use Chill\MainBundle\Doctrine\Model\Point; +use Chill\ThirdPartyBundle\Entity\ThirdParty; /** * Address @@ -28,14 +30,14 @@ class Address * * @ORM\Column(type="string", length=255) */ - private $streetAddress1 = ''; + private $street = ''; /** * @var string * * @ORM\Column(type="string", length=255) */ - private $streetAddress2 = ''; + private $streetNumber = ''; /** * @var PostalCode @@ -43,7 +45,56 @@ class Address * @ORM\ManyToOne(targetEntity="Chill\MainBundle\Entity\PostalCode") */ private $postcode; - + + /** + * @var string|null + * + * @ORM\Column(type="string", length=16, nullable=true) + */ + private $floor; + + /** + * @var string|null + * + * @ORM\Column(type="string", length=16, nullable=true) + */ + private $corridor; + + /** + * @var string|null + * + * @ORM\Column(type="string", length=16, nullable=true) + */ + private $steps; + + /** + * @var string|null + * + * @ORM\Column(type="string", length=255, nullable=true) + */ + private $buildingName; + + /** + * @var string|null + * + * @ORM\Column(type="string", length=16, nullable=true) + */ + private $flat; + + /** + * @var string|null + * + * @ORM\Column(type="string", length=255, nullable=true) + */ + private $distribution; + + /** + * @var string|null + * + * @ORM\Column(type="string", length=255, nullable=true) + */ + private $extra; + /** * Indicates when the address starts validation. Used to build an history * of address. By default, the current date. @@ -53,27 +104,55 @@ class Address * @ORM\Column(type="date") */ private $validFrom; - + + /** + * Indicates when the address ends. Used to build an history + * of address. + * + * @var \DateTime|null + * + * @ORM\Column(type="date", nullable=true) + */ + private $validTo; + /** * True if the address is a "no address", aka homeless person, ... * * @var bool */ private $isNoAddress = false; - + + /** + * A geospatial field storing the coordinates of the Address + * + * @var Point|null + * + * @ORM\Column(type="point", nullable=true) + */ + private $point; + + /** + * A ThirdParty reference for person's addresses that are linked to a third party + * + * @var ThirdParty|null + * + * @ORM\ManyToOne(targetEntity="Chill\ThirdPartyBundle\Entity\ThirdParty") + * @ORM\JoinColumn(nullable=true) + */ + private $linkedToThirdParty; + /** * A list of metadata, added by customizable fields - * + * * @var array */ private $customs = []; - + public function __construct() { $this->validFrom = new \DateTime(); } - /** * Get id * @@ -85,7 +164,7 @@ class Address } /** - * Set streetAddress1 + * Set streetAddress1 (legacy function) * * @param string $streetAddress1 * @@ -93,23 +172,23 @@ class Address */ public function setStreetAddress1($streetAddress1) { - $this->streetAddress1 = $streetAddress1 === NULL ? '' : $streetAddress1; + $this->street = $streetAddress1 === NULL ? '' : $streetAddress1; return $this; } /** - * Get streetAddress1 + * Get streetAddress1 (legacy function) * * @return string */ public function getStreetAddress1() { - return $this->streetAddress1; + return $this->street; } /** - * Set streetAddress2 + * Set streetAddress2 (legacy function) * * @param string $streetAddress2 * @@ -117,19 +196,19 @@ class Address */ public function setStreetAddress2($streetAddress2) { - $this->streetAddress2 = $streetAddress2 === NULL ? '' : $streetAddress2; + $this->streetNumber = $streetAddress2 === NULL ? '' : $streetAddress2; return $this; } /** - * Get streetAddress2 + * Get streetAddress2 (legacy function) * * @return string */ public function getStreetAddress2() { - return $this->streetAddress2; + return $this->streetNumber; } /** @@ -155,7 +234,7 @@ class Address { return $this->postcode; } - + /** * @return \DateTime */ @@ -173,19 +252,19 @@ class Address $this->validFrom = $validFrom; return $this; } - + /** * Get IsNoAddress - * + * * Indicate true if the address is a fake address (homeless, ...) - * + * * @return bool */ public function getIsNoAddress(): bool { return $this->isNoAddress; } - + /** * @return bool */ @@ -196,9 +275,9 @@ class Address /** * Set IsNoAddress - * + * * Indicate true if the address is a fake address (homeless, ...) - * + * * @param bool $isNoAddress * @return $this */ @@ -207,10 +286,10 @@ class Address $this->isNoAddress = $isNoAddress; return $this; } - + /** * Get customs informations in the address - * + * * @return array */ public function getCustoms(): array @@ -220,27 +299,27 @@ class Address /** * Store custom informations in the address - * + * * @param array $customs * @return $this */ public function setCustoms(array $customs): self { $this->customs = $customs; - + return $this; } - + /** * Validate the address. - * + * * Check that: - * + * * * if the address is not home address: * * the postal code is present * * the valid from is not null * * the address street 1 is greater than 2 - * + * * @param ExecutionContextInterface $context * @param array $payload */ @@ -252,18 +331,18 @@ class Address ->atPath('validFrom') ->addViolation(); } - + if ($this->isNoAddress()) { return; } - + if (empty($this->getStreetAddress1())) { $context ->buildViolation("address.street1-should-be-set") ->atPath('streetAddress1') ->addViolation(); } - + if (!$this->getPostcode() instanceof PostalCode) { $context ->buildViolation("address.postcode-should-be-set") @@ -271,7 +350,7 @@ class Address ->addViolation(); } } - + /** * @param Address $original * @return Address @@ -286,5 +365,149 @@ class Address ; } + public function getStreet(): ?string + { + return $this->street; + } + + public function setStreet(string $street): self + { + $this->street = $street; + + return $this; + } + + public function getStreetNumber(): ?string + { + return $this->streetNumber; + } + + public function setStreetNumber(string $streetNumber): self + { + $this->streetNumber = $streetNumber; + + return $this; + } + + public function getFloor(): ?string + { + return $this->floor; + } + + public function setFloor(?string $floor): self + { + $this->floor = $floor; + + return $this; + } + + public function getCorridor(): ?string + { + return $this->corridor; + } + + public function setCorridor(?string $corridor): self + { + $this->corridor = $corridor; + + return $this; + } + + public function getSteps(): ?string + { + return $this->steps; + } + + public function setSteps(?string $steps): self + { + $this->steps = $steps; + + return $this; + } + + public function getBuildingName(): ?string + { + return $this->buildingName; + } + + public function setBuildingName(?string $buildingName): self + { + $this->buildingName = $buildingName; + + return $this; + } + + public function getFlat(): ?string + { + return $this->flat; + } + + public function setFlat(?string $flat): self + { + $this->flat = $flat; + + return $this; + } + + public function getDistribution(): ?string + { + return $this->distribution; + } + + public function setDistribution(?string $distribution): self + { + $this->distribution = $distribution; + + return $this; + } + + public function getExtra(): ?string + { + return $this->extra; + } + + public function setExtra(?string $extra): self + { + $this->extra = $extra; + + return $this; + } + + public function getValidTo(): ?\DateTimeInterface + { + return $this->validTo; + } + + public function setValidTo(\DateTimeInterface $validTo): self + { + $this->validTo = $validTo; + + return $this; + } + + public function getPoint(): ?Point + { + return $this->point; + } + + public function setPoint(?Point $point): self + { + $this->point = $point; + + return $this; + } + + public function getLinkedToThirdParty() + { + return $this->linkedToThirdParty; + } + + public function setLinkedToThirdParty($linkedToThirdParty): self + { + $this->linkedToThirdParty = $linkedToThirdParty; + + return $this; + } + } diff --git a/src/Bundle/ChillMainBundle/Entity/AddressReference.php b/src/Bundle/ChillMainBundle/Entity/AddressReference.php new file mode 100644 index 000000000..944cab81b --- /dev/null +++ b/src/Bundle/ChillMainBundle/Entity/AddressReference.php @@ -0,0 +1,165 @@ +id; + } + + public function getRefId(): ?string + { + return $this->refId; + } + + public function setRefId(string $refId): self + { + $this->refId = $refId; + + return $this; + } + + public function getStreet(): ?string + { + return $this->street; + } + + public function setStreet(?string $street): self + { + $this->street = $street; + + return $this; + } + + public function getStreetNumber(): ?string + { + return $this->streetNumber; + } + + public function setStreetNumber(?string $streetNumber): self + { + $this->streetNumber = $streetNumber; + + return $this; + } + + /** + * Set postcode + * + * @param PostalCode $postcode + * + * @return Address + */ + public function setPostcode(PostalCode $postcode = null) + { + $this->postcode = $postcode; + + return $this; + } + + /** + * Get postcode + * + * @return PostalCode + */ + public function getPostcode() + { + return $this->postcode; + } + + public function getMunicipalityCode(): ?string + { + return $this->municipalityCode; + } + + public function setMunicipalityCode(?string $municipalityCode): self + { + $this->municipalityCode = $municipalityCode; + + return $this; + } + + public function getSource(): ?string + { + return $this->source; + } + + public function setSource(?string $source): self + { + $this->source = $source; + + return $this; + } + + public function getPoint(): ?Point + { + return $this->point; + } + + public function setPoint(?Point $point): self + { + $this->point = $point; + + return $this; + } +} diff --git a/src/Bundle/ChillMainBundle/Form/Type/AddressType.php b/src/Bundle/ChillMainBundle/Form/Type/AddressType.php index 4a0e222b8..68cfdda09 100644 --- a/src/Bundle/ChillMainBundle/Form/Type/AddressType.php +++ b/src/Bundle/ChillMainBundle/Form/Type/AddressType.php @@ -33,8 +33,8 @@ use Symfony\Component\Form\Extension\Core\Type\ChoiceType; * A type to create/update Address entity * * Options: - * - * - `has_valid_from` (boolean): show if an entry "has valid from" must be + * + * - `has_valid_from` (boolean): show if an entry "has valid from" must be * shown. * - `null_if_empty` (boolean): replace the address type by null if the street * or the postCode is empty. This is useful when the address is not required and @@ -45,10 +45,10 @@ class AddressType extends AbstractType public function buildForm(FormBuilderInterface $builder, array $options) { $builder - ->add('streetAddress1', TextType::class, array( + ->add('street', TextType::class, array( 'required' => !$options['has_no_address'] // true if has no address is false )) - ->add('streetAddress2', TextType::class, array( + ->add('streetNumber', TextType::class, array( 'required' => false )) ->add('postCode', PostalCodeType::class, array( @@ -57,7 +57,7 @@ class AddressType extends AbstractType 'required' => !$options['has_no_address'] // true if has no address is false )) ; - + if ($options['has_valid_from']) { $builder ->add('validFrom', DateType::class, array( @@ -67,7 +67,7 @@ class AddressType extends AbstractType ) ); } - + if ($options['has_no_address']) { $builder ->add('isNoAddress', ChoiceType::class, [ @@ -79,12 +79,12 @@ class AddressType extends AbstractType 'label' => 'address.address_homeless' ]); } - + if ($options['null_if_empty'] === TRUE) { $builder->setDataMapper(new AddressDataMapper()); } } - + public function configureOptions(OptionsResolver $resolver) { $resolver diff --git a/src/Bundle/ChillMainBundle/Form/Type/Select2CountryType.php b/src/Bundle/ChillMainBundle/Form/Type/Select2CountryType.php index 3c9b83bf4..617d1afce 100644 --- a/src/Bundle/ChillMainBundle/Form/Type/Select2CountryType.php +++ b/src/Bundle/ChillMainBundle/Form/Type/Select2CountryType.php @@ -27,6 +27,7 @@ use Symfony\Component\Form\FormBuilderInterface; use Chill\MainBundle\Form\Type\DataTransformer\ObjectToIdTransformer; use Doctrine\Persistence\ObjectManager; use Chill\MainBundle\Form\Type\Select2ChoiceType; +use Chill\MainBundle\Templating\TranslatableStringHelper; /** * Extends choice to allow adding select2 library on widget @@ -41,15 +42,26 @@ class Select2CountryType extends AbstractType */ private $requestStack; + /** + * + * @var TranslatableStringHelper + */ + protected $translatableStringHelper; + /** * @var ObjectManager */ private $em; - public function __construct(RequestStack $requestStack,ObjectManager $em) + public function __construct( + RequestStack $requestStack, + ObjectManager $em, + TranslatableStringHelper $translatableStringHelper + ) { $this->requestStack = $requestStack; $this->em = $em; + $this->translatableStringHelper = $translatableStringHelper; } public function getBlockPrefix() @@ -75,7 +87,7 @@ class Select2CountryType extends AbstractType $choices = array(); foreach ($countries as $c) { - $choices[$c->getId()] = $c->getName()[$locale]; + $choices[$c->getId()] = $this->translatableStringHelper->localize($c->getName()); } asort($choices, SORT_STRING | SORT_FLAG_CASE); diff --git a/src/Bundle/ChillMainBundle/Form/Type/Select2LanguageType.php b/src/Bundle/ChillMainBundle/Form/Type/Select2LanguageType.php index 147e9797a..2c8dea857 100644 --- a/src/Bundle/ChillMainBundle/Form/Type/Select2LanguageType.php +++ b/src/Bundle/ChillMainBundle/Form/Type/Select2LanguageType.php @@ -27,6 +27,7 @@ use Symfony\Component\Form\FormBuilderInterface; use Chill\MainBundle\Form\Type\DataTransformer\MultipleObjectsToIdTransformer; use Doctrine\Persistence\ObjectManager; use Chill\MainBundle\Form\Type\Select2ChoiceType; +use Chill\MainBundle\Templating\TranslatableStringHelper; /** * Extends choice to allow adding select2 library on widget for languages (multiple) @@ -43,10 +44,21 @@ class Select2LanguageType extends AbstractType */ private $em; - public function __construct(RequestStack $requestStack,ObjectManager $em) + /** + * + * @var TranslatableStringHelper + */ + protected $translatableStringHelper; + + public function __construct( + RequestStack $requestStack, + ObjectManager $em, + TranslatableStringHelper $translatableStringHelper + ) { $this->requestStack = $requestStack; $this->em = $em; + $this->translatableStringHelper = $translatableStringHelper; } public function getBlockPrefix() @@ -72,7 +84,7 @@ class Select2LanguageType extends AbstractType $choices = array(); foreach ($languages as $l) { - $choices[$l->getId()] = $l->getName()[$locale]; + $choices[$l->getId()] = $this->translatableStringHelper->localize($l->getName()); } asort($choices, SORT_STRING | SORT_FLAG_CASE); diff --git a/src/Bundle/ChillMainBundle/Repository/AddressReferenceRepository.php b/src/Bundle/ChillMainBundle/Repository/AddressReferenceRepository.php new file mode 100644 index 000000000..208151420 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Repository/AddressReferenceRepository.php @@ -0,0 +1,50 @@ +createQueryBuilder('a') + ->andWhere('a.exampleField = :val') + ->setParameter('val', $value) + ->orderBy('a.id', 'ASC') + ->setMaxResults(10) + ->getQuery() + ->getResult() + ; + } + */ + + /* + public function findOneBySomeField($value): ?AddressReference + { + return $this->createQueryBuilder('a') + ->andWhere('a.exampleField = :val') + ->setParameter('val', $value) + ->getQuery() + ->getOneOrNullResult() + ; + } + */ +} diff --git a/src/Bundle/ChillMainBundle/Resources/public/modules/bootstrap/bootstrap.scss b/src/Bundle/ChillMainBundle/Resources/public/modules/bootstrap/bootstrap.scss index 4d3b86ed3..8a77ac48f 100644 --- a/src/Bundle/ChillMainBundle/Resources/public/modules/bootstrap/bootstrap.scss +++ b/src/Bundle/ChillMainBundle/Resources/public/modules/bootstrap/bootstrap.scss @@ -28,7 +28,7 @@ // @import "bootstrap/scss/card"; // @import "bootstrap/scss/breadcrumb"; // @import "bootstrap/scss/pagination"; -// @import "bootstrap/scss/badge"; +@import "bootstrap/scss/badge"; // @import "bootstrap/scss/jumbotron"; // @import "bootstrap/scss/alert"; // @import "bootstrap/scss/progress"; @@ -41,7 +41,7 @@ // @import "bootstrap/scss/popover"; // @import "bootstrap/scss/carousel"; // @import "bootstrap/scss/spinners"; -// @import "bootstrap/scss/utilities"; +@import "bootstrap/scss/utilities"; // @import "bootstrap/scss/print"; @import "custom"; diff --git a/src/Bundle/ChillMainBundle/Resources/public/modules/scratch/custom/modules/_buttons.scss b/src/Bundle/ChillMainBundle/Resources/public/modules/scratch/custom/modules/_buttons.scss index e94aa494e..0b7af263b 100644 --- a/src/Bundle/ChillMainBundle/Resources/public/modules/scratch/custom/modules/_buttons.scss +++ b/src/Bundle/ChillMainBundle/Resources/public/modules/scratch/custom/modules/_buttons.scss @@ -5,7 +5,7 @@ @include button($green, $white); } - &.bt-reset, &.bt-delete { + &.bt-reset, &.bt-delete, &.bt-remove { @include button($red, $white); } @@ -24,6 +24,7 @@ &.bt-save::before, &.bt-new::before, &.bt-delete::before, + &.bt-remove::before, &.bt-update::before, &.bt-edit::before, &.bt-cancel::before, @@ -56,7 +57,12 @@ // add a trash content: ""; } - + + &.bt-remove::before { + // add a times + content: ""; + } + &.bt-edit::before, &.bt-update::before { // add a pencil content: ""; @@ -94,6 +100,7 @@ &.bt-save::before, &.bt-new::before, &.bt-delete::before, + &.bt-remove::before, &.bt-update::before, &.bt-edit::before, &.bt-cancel::before, @@ -123,6 +130,7 @@ &.bt-save::before, &.bt-new::before, &.bt-delete::before, + &.bt-remove::before, &.bt-update::before, &.bt-edit::before, &.bt-cancel::before, diff --git a/src/Bundle/ChillMainBundle/Resources/public/scss/chillmain.scss b/src/Bundle/ChillMainBundle/Resources/public/scss/chillmain.scss index 4f9ecf46d..a27dcdc42 100644 --- a/src/Bundle/ChillMainBundle/Resources/public/scss/chillmain.scss +++ b/src/Bundle/ChillMainBundle/Resources/public/scss/chillmain.scss @@ -39,6 +39,8 @@ div.subheader { height: 130px; } +//// VUEJS //// + div.vue-component { padding: 1.5em; margin: 2em 0; @@ -55,3 +57,97 @@ div.vue-component { } dd { margin-left: 1em; } } + +//// MODAL //// +.modal-mask { + position: fixed; + z-index: 9998; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: rgba(0, 0, 0, 0.75); + display: table; + transition: opacity 0.3s ease; +} +.modal-header .close { // bootstrap classes, override sc-button 0 radius + border-top-right-radius: 0.3rem; +} + +/* +* The following styles are auto-applied to elements with +* transition="modal" when their visibility is toggled +* by Vue.js. +* +* You can easily play with the modal transition by editing +* these styles. +*/ +.modal-enter { + opacity: 0; +} +.modal-leave-active { + opacity: 0; +} +.modal-enter .modal-container, +.modal-leave-active .modal-container { + -webkit-transform: scale(1.1); + transform: scale(1.1); +} + +//// AddPersons modal +div.modal-body.up { + margin: auto 4em; + div.search { + position: relative; + input { + padding: 1.2em 1.5em 1.2em 2.5em; + margin: 1em 0; + } + i { + position: absolute; + top: 50%; + left: 0.5em; + padding: 0.65em 0; + opacity: 0.5; + } + + } +} +div.results { + div.count { + margin: -0.5em 0 0.7em; + display: flex; + justify-content: space-between; + } + div.list-item { + line-height: 26pt; + padding: 0.3em 0.8em; + display: flex; + flex-direction: row; + &.checked { + background-color: #ececec; + border-bottom: 1px dotted #8b8b8b; + } + div.container { + & > input { + margin-right: 0.8em; + } + } + div.right_actions { + margin: 0 0 0 auto; + & > * { + margin-left: 0.5em; + } + a.sc-button { + border: 1px solid lightgrey; + font-size: 70%; + padding: 4px; + } + } + } +} + +.discret { + color: grey; + margin-right: 1em; +} diff --git a/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/Modal.vue b/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/Modal.vue new file mode 100644 index 000000000..cbd2d0738 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/Modal.vue @@ -0,0 +1,45 @@ + + + diff --git a/src/Bundle/ChillMainBundle/Resources/public/vuejs/_js/i18n.js b/src/Bundle/ChillMainBundle/Resources/public/vuejs/_js/i18n.js new file mode 100644 index 000000000..4f5c64e30 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Resources/public/vuejs/_js/i18n.js @@ -0,0 +1,55 @@ +import { createI18n } from 'vue-i18n' + +const datetimeFormats = { + fr: { + short: { + year: "numeric", + month: "numeric", + day: "numeric" + }, + long: { + year: "numeric", + month: "short", + day: "numeric", + weekday: "short", + hour: "numeric", + minute: "numeric", + hour12: false + } + } +}; +const messages = { + fr: { + action: { + actions: "Actions", + show: "Voir", + edit: "Modifier", + create: "Créer", + remove: "Enlever", + delete: "Supprimer", + save: "Enregistrer", + add: "Ajouter", + show_modal: "Ouvrir une modale", + ok: "OK", + cancel: "Annuler", + close: "Fermer", + next: "Suivant", + previous: "Précédent", + back: "Retour", + check_all: "cocher tout", + reset: "réinitialiser" + }, + } +}; + +const _createI18n = (appMessages) => { + Object.assign(messages.fr, appMessages.fr); + return createI18n({ + locale: 'fr', + fallbackLocale: 'fr', + datetimeFormats, + messages, + }) +}; + +export { _createI18n } diff --git a/src/Bundle/ChillMainBundle/Resources/views/Address/macro.html.twig b/src/Bundle/ChillMainBundle/Resources/views/Address/macro.html.twig index 165d7c280..5de175169 100644 --- a/src/Bundle/ChillMainBundle/Resources/views/Address/macro.html.twig +++ b/src/Bundle/ChillMainBundle/Resources/views/Address/macro.html.twig @@ -6,8 +6,8 @@
{{ 'address.consider homeless'|trans }}
{% endif %}
- {% if address.streetAddress1 is not empty %}

{{ address.streetAddress1 }}

{% endif %} - {% if address.streetAddress2 is not empty %}

{{ address.streetAddress2 }}

{% endif %} + {% if address.street is not empty %}

{{ address.street }}

{% endif %} + {% if address.streetNumber is not empty %}

{{ address.streetNumber }}

{% endif %} {% if address.postCode is not empty %}

{{ address.postCode.code }} {{ address.postCode.name }}

{{ address.postCode.country.name|localize_translatable_string }}

diff --git a/src/Bundle/ChillMainBundle/Serializer/Model/Collection.php b/src/Bundle/ChillMainBundle/Serializer/Model/Collection.php new file mode 100644 index 000000000..9983d3595 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Serializer/Model/Collection.php @@ -0,0 +1,28 @@ +items = $items; + $this->paginator = $paginator; + } + + public function getPaginator(): PaginatorInterface + { + return $this->paginator; + } + + public function getItems() + { + return $this->items; + } +} diff --git a/src/Bundle/ChillMainBundle/Serializer/Normalizer/CollectionNormalizer.php b/src/Bundle/ChillMainBundle/Serializer/Normalizer/CollectionNormalizer.php new file mode 100644 index 000000000..1ba95d924 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Serializer/Normalizer/CollectionNormalizer.php @@ -0,0 +1,41 @@ +getPaginator(); + + $data['count'] = $paginator->getTotalItems(); + $pagination['first'] = $paginator->getCurrentPageFirstItemNumber(); + $pagination['items_per_page'] = $paginator->getItemsPerPage(); + $pagination['next'] = $paginator->hasNextPage() ? + $paginator->getNextPage()->generateUrl() : null; + $pagination['previous'] = $paginator->hasPreviousPage() ? + $paginator->getPreviousPage()->generateUrl() : null; + $pagination['more'] = $paginator->hasNextPage(); + $data['pagination'] = $pagination; + + // normalize results + $data['results'] = $this->normalizer->normalize($collection->getItems(), + $format, $context); + + return $data; + } +} diff --git a/src/Bundle/ChillMainBundle/Tests/Doctrine/Model/PointTest.php b/src/Bundle/ChillMainBundle/Tests/Doctrine/Model/PointTest.php new file mode 100644 index 000000000..5632bf96a --- /dev/null +++ b/src/Bundle/ChillMainBundle/Tests/Doctrine/Model/PointTest.php @@ -0,0 +1,119 @@ + + */ +class ExportControllerTest extends KernelTestCase +{ + + public function testToWKT() + { + $lon = 4.8634; + $lat = 50.47382; + $point = $this->preparePoint($lon, $lat); + + $this->assertEquals($point->toWKT(),'SRID=4326;POINT(4.8634 50.47382)'); + } + + public function testToGeojson() + { + $lon = 4.8634; + $lat = 50.47382; + $point = $this->preparePoint($lon, $lat); + + $this->assertEquals($point->toGeoJson(),'{"type":"Point","coordinates":[4.8634,50.47382]}'); + } + + public function testToArrayGeoJson() + { + $lon = 4.8634; + $lat = 50.47382; + $point = $this->preparePoint($lon, $lat); + + $this->assertEquals( + $point->toArrayGeoJson(), + [ + 'type' => 'Point', + 'coordinates' => [4.8634, 50.47382] + ] + ); + } + + public function testJsonSerialize() + { + $lon = 4.8634; + $lat = 50.47382; + $point = $this->preparePoint($lon, $lat); + + $this->assertEquals( + $point->jsonSerialize(), + [ + 'type' => 'Point', + 'coordinates' => [4.8634, 50.47382] + ] + ); + } + + public function testFromGeoJson() + { + $geojson = '{"type":"Point","coordinates":[4.8634,50.47382]}'; + $lon = 4.8634; + $lat = 50.47382; + $point = $this->preparePoint($lon, $lat);; + + $this->assertEquals($point, Point::fromGeoJson($geojson)); + } + + public function testFromLonLat() + { + $lon = 4.8634; + $lat = 50.47382; + $point = $this->preparePoint($lon, $lat);; + + $this->assertEquals($point, Point::fromLonLat($lon, $lat)); + } + + public function testFromArrayGeoJson() + { + $array = [ + 'type' => 'Point', + 'coordinates' => [4.8634, 50.47382] + ]; + $lon = 4.8634; + $lat = 50.47382; + $point = $this->preparePoint($lon, $lat);; + + $this->assertEquals($point, Point::fromArrayGeoJson($array)); + } + + public function testGetLat() + { + $lon = 4.8634; + $lat = 50.47382; + $point = $this->preparePoint($lon, $lat);; + + $this->assertEquals($lat, $point->getLat()); + } + + public function testGetLon() + { + $lon = 4.8634; + $lat = 50.47382; + $point = $this->preparePoint($lon, $lat);; + + $this->assertEquals($lon, $point->getLon()); + } + + private function preparePoint($lon, $lat) + { + return Point::fromLonLat($lon, $lat); + } + +} diff --git a/src/Bundle/ChillMainBundle/config/services/crud.yaml b/src/Bundle/ChillMainBundle/config/services/crud.yaml index 8723d6eb1..c5c05f344 100644 --- a/src/Bundle/ChillMainBundle/config/services/crud.yaml +++ b/src/Bundle/ChillMainBundle/config/services/crud.yaml @@ -1,7 +1,8 @@ services: Chill\MainBundle\CRUD\Routing\CRUDRoutesLoader: arguments: - $config: '%chill_main_crud_route_loader_config%' + $crudConfig: '%chill_main_crud_route_loader_config%' + $apiConfig: '%chill_main_api_route_loader_config%' tags: [ routing.loader ] Chill\MainBundle\CRUD\Resolver\Resolver: @@ -13,4 +14,4 @@ services: arguments: $resolver: '@Chill\MainBundle\CRUD\Resolver\Resolver' tags: - - { name: twig.extension } \ No newline at end of file + - { name: twig.extension } diff --git a/src/Bundle/ChillMainBundle/config/services/form.yaml b/src/Bundle/ChillMainBundle/config/services/form.yaml index 513553194..5a694a417 100644 --- a/src/Bundle/ChillMainBundle/config/services/form.yaml +++ b/src/Bundle/ChillMainBundle/config/services/form.yaml @@ -23,6 +23,7 @@ services: arguments: - "@request_stack" - "@doctrine.orm.entity_manager" + - "@chill.main.helper.translatable_string" tags: - { name: form.type, alias: select2_chill_country } @@ -31,6 +32,7 @@ services: arguments: - "@request_stack" - "@doctrine.orm.entity_manager" + - "@chill.main.helper.translatable_string" tags: - { name: form.type, alias: select2_chill_language } diff --git a/src/Bundle/ChillMainBundle/config/services/pagination.yaml b/src/Bundle/ChillMainBundle/config/services/pagination.yaml index c7c8a89a9..f6282a39f 100644 --- a/src/Bundle/ChillMainBundle/config/services/pagination.yaml +++ b/src/Bundle/ChillMainBundle/config/services/pagination.yaml @@ -6,6 +6,7 @@ services: - "@request_stack" - "@router" - "%chill_main.pagination.item_per_page%" + Chill\MainBundle\Pagination\PaginatorFactory: '@chill_main.paginator_factory' chill_main.paginator.twig_extensions: diff --git a/src/Bundle/ChillMainBundle/config/services/serializer.yaml b/src/Bundle/ChillMainBundle/config/services/serializer.yaml index 763576a5c..fb5f57b7e 100644 --- a/src/Bundle/ChillMainBundle/config/services/serializer.yaml +++ b/src/Bundle/ChillMainBundle/config/services/serializer.yaml @@ -11,3 +11,7 @@ services: Chill\MainBundle\Serializer\Normalizer\UserNormalizer: tags: - { name: 'serializer.normalizer', priority: 64 } + + Chill\MainBundle\Serializer\Normalizer\CollectionNormalizer: + tags: + - { name: 'serializer.normalizer', priority: 64 } diff --git a/src/Bundle/ChillMainBundle/migrations/Version20210414091001.php b/src/Bundle/ChillMainBundle/migrations/Version20210414091001.php new file mode 100644 index 000000000..db207c594 --- /dev/null +++ b/src/Bundle/ChillMainBundle/migrations/Version20210414091001.php @@ -0,0 +1,32 @@ +abortIf($this->connection->getDatabasePlatform()->getName() !== 'postgresql', 'Migration can only be executed safely on \'postgresql\'.'); + + $this->addSql('CREATE EXTENSION IF NOT EXISTS postgis;'); + + } + + public function down(Schema $schema) : void + { + $this->abortIf($this->connection->getDatabasePlatform()->getName() !== 'postgresql', 'Migration can only be executed safely on \'postgresql\'.'); + + $this->addSql('DROP EXTENSION IF NOT EXISTS postgis;'); + } + + public function getDescription(): string + { + return "Enable the postgis extension in public schema"; + } +} diff --git a/src/Bundle/ChillMainBundle/migrations/Version20210420115006.php b/src/Bundle/ChillMainBundle/migrations/Version20210420115006.php new file mode 100644 index 000000000..79dea4853 --- /dev/null +++ b/src/Bundle/ChillMainBundle/migrations/Version20210420115006.php @@ -0,0 +1,50 @@ +addSql('ALTER TABLE chill_main_address RENAME COLUMN streetaddress1 TO street;'); + $this->addSql('ALTER TABLE chill_main_address RENAME COLUMN streetaddress2 TO streetNumber;'); + $this->addSql('ALTER TABLE chill_main_address ADD floor VARCHAR(16) DEFAULT NULL'); + $this->addSql('ALTER TABLE chill_main_address ADD corridor VARCHAR(16) DEFAULT NULL'); + $this->addSql('ALTER TABLE chill_main_address ADD steps VARCHAR(16) DEFAULT NULL'); + $this->addSql('ALTER TABLE chill_main_address ADD buildingName VARCHAR(255) DEFAULT NULL'); + $this->addSql('ALTER TABLE chill_main_address ADD flat VARCHAR(16) DEFAULT NULL'); + $this->addSql('ALTER TABLE chill_main_address ADD distribution VARCHAR(255) DEFAULT NULL'); + $this->addSql('ALTER TABLE chill_main_address ADD extra VARCHAR(255) DEFAULT NULL'); + $this->addSql('ALTER TABLE chill_main_address ADD validTo DATE DEFAULT NULL'); + $this->addSql('ALTER TABLE chill_main_address ADD point geometry(POINT,4326) DEFAULT NULL'); + } + + + public function down(Schema $schema) : void + { + $this->addSql('ALTER TABLE chill_main_address RENAME COLUMN street TO streetaddress1;'); + $this->addSql('ALTER TABLE chill_main_address RENAME COLUMN streetNumber TO streetaddress2;'); + $this->addSql('ALTER TABLE chill_main_address DROP floor'); + $this->addSql('ALTER TABLE chill_main_address DROP corridor'); + $this->addSql('ALTER TABLE chill_main_address DROP steps'); + $this->addSql('ALTER TABLE chill_main_address DROP buildingName'); + $this->addSql('ALTER TABLE chill_main_address DROP flat'); + $this->addSql('ALTER TABLE chill_main_address DROP distribution'); + $this->addSql('ALTER TABLE chill_main_address DROP extra'); + $this->addSql('ALTER TABLE chill_main_address DROP validTo'); + $this->addSql('ALTER TABLE chill_main_address DROP point'); + } +} diff --git a/src/Bundle/ChillMainBundle/migrations/Version20210503085107.php b/src/Bundle/ChillMainBundle/migrations/Version20210503085107.php new file mode 100644 index 000000000..f693777a0 --- /dev/null +++ b/src/Bundle/ChillMainBundle/migrations/Version20210503085107.php @@ -0,0 +1,33 @@ +addSql('CREATE SEQUENCE chill_main_address_reference_id_seq INCREMENT BY 1 MINVALUE 1 START 1'); + $this->addSql('CREATE TABLE chill_main_address_reference (id INT NOT NULL, postcode_id INT DEFAULT NULL, refId VARCHAR(255) NOT NULL, street VARCHAR(255) DEFAULT NULL, streetNumber VARCHAR(255) DEFAULT NULL, municipalityCode VARCHAR(255) DEFAULT NULL, source VARCHAR(255) DEFAULT NULL, point geometry(POINT,4326) NOT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE INDEX IDX_CA6C1BD7EECBFDF1 ON chill_main_address_reference (postcode_id)'); + $this->addSql('ALTER TABLE chill_main_address_reference ADD CONSTRAINT FK_CA6C1BD7EECBFDF1 FOREIGN KEY (postcode_id) REFERENCES chill_main_postal_code (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + } + + public function down(Schema $schema): void + { + $this->addSql('DROP SEQUENCE chill_main_address_reference_id_seq CASCADE'); + $this->addSql('DROP TABLE chill_main_address_reference'); + } +} diff --git a/src/Bundle/ChillMainBundle/migrations/Version20210505153727.php b/src/Bundle/ChillMainBundle/migrations/Version20210505153727.php new file mode 100644 index 000000000..42161ba84 --- /dev/null +++ b/src/Bundle/ChillMainBundle/migrations/Version20210505153727.php @@ -0,0 +1,58 @@ +addSql('ALTER TABLE chill_main_address ADD linkedToThirdParty_id INT DEFAULT NULL'); + $this->addSql('ALTER TABLE chill_main_address ADD CONSTRAINT FK_165051F6114B8DD9 FOREIGN KEY (linkedToThirdParty_id) REFERENCES chill_3party.third_party (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('CREATE INDEX IDX_165051F6114B8DD9 ON chill_main_address (linkedToThirdParty_id)'); + $this->addSql(' + CREATE TABLE chill_main_address_legacy AS + TABLE chill_main_address; + '); + $this->addSql(' + WITH hydrated_addresses AS ( + SELECT *, rank() OVER (PARTITION BY pa_a.person_id ORDER BY validfrom) + FROM chill_main_address AS aa JOIN chill_person_persons_to_addresses AS pa_a ON aa.id = pa_a.address_id + ) + UPDATE chill_main_address AS b + SET validto = ( + SELECT validfrom - INTERVAL \'1 DAY\' + FROM hydrated_addresses + WHERE hydrated_addresses.id = ( + SELECT a1.id + FROM hydrated_addresses AS a1 JOIN hydrated_addresses AS a2 ON a2.person_id = a1.person_id AND a2.rank = (a1.rank-1) + WHERE a2.id = b.id + ) + ); + '); + } + + public function down(Schema $schema): void + { + $this->addSql('ALTER TABLE chill_main_address DROP CONSTRAINT FK_165051F6114B8DD9'); + $this->addSql('DROP INDEX IDX_165051F6114B8DD9'); + $this->addSql('ALTER TABLE chill_main_address DROP linkedToThirdParty_id'); + $this->addSql('DROP TABLE IF EXISTS chill_main_address_legacy'); + $this->addSql(' + UPDATE chill_main_address + SET validto = null; + '); + } +} diff --git a/src/Bundle/ChillPersonBundle/Controller/AccompanyingCourseApiController.php b/src/Bundle/ChillPersonBundle/Controller/AccompanyingCourseApiController.php new file mode 100644 index 000000000..bbf2f399a --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Controller/AccompanyingCourseApiController.php @@ -0,0 +1,79 @@ +eventDispatcher = $eventDispatcher; + $this->validator = $validator; + } + + public function participationApi($id, Request $request, $_format) + { + /** @var AccompanyingPeriod $accompanyingPeriod */ + $accompanyingPeriod = $this->getEntity('participation', $id, $request); + $person = $this->getSerializer() + ->deserialize($request->getContent(), Person::class, $_format, []); + + if (NULL === $person) { + throw new BadRequestException('person id not found'); + } + + // TODO add acl + // + $this->onPostCheckACL('participation', $request, $_format, $accompanyingPeriod); + + switch ($request->getMethod()) { + case Request::METHOD_POST: + $participation = $accompanyingPeriod->addPerson($person); + break; + case Request::METHOD_DELETE: + $participation = $accompanyingPeriod->removePerson($person); + $participation->setEndDate(new \DateTimeImmutable('now')); + break; + default: + throw new BadRequestException("This method is not supported"); + } + + $errors = $this->validator->validate($accompanyingPeriod); + + if ($errors->count() > 0) { + // only format accepted + return $this->json($errors); + } + + $this->getDoctrine()->getManager()->flush(); + + return $this->json($participation); + } + + protected function onPostCheckACL(string $action, Request $request, string $_format, $entity): ?Response + { + $this->eventDispatcher->dispatch( + AccompanyingPeriodPrivacyEvent::ACCOMPANYING_PERIOD_PRIVACY_EVENT, + new AccompanyingPeriodPrivacyEvent($entity, [ + 'action' => $action, + 'request' => $request->getMethod() + ]) + ); + + return null; + } +} diff --git a/src/Bundle/ChillPersonBundle/Controller/AccompanyingCourseController.php b/src/Bundle/ChillPersonBundle/Controller/AccompanyingCourseController.php index 9bc732d87..05a1934e6 100644 --- a/src/Bundle/ChillPersonBundle/Controller/AccompanyingCourseController.php +++ b/src/Bundle/ChillPersonBundle/Controller/AccompanyingCourseController.php @@ -121,7 +121,7 @@ class AccompanyingCourseController extends Controller * @Route( * "/{_locale}/person/api/1.0/accompanying-course/{accompanying_period_id}/participation.{_format}", * name="chill_person_accompanying_course_api_add_participation", - * methods={"POST"}, + * methods={"POST","DELETE"}, * format="json", * requirements={ * "_format": "json", @@ -129,7 +129,7 @@ class AccompanyingCourseController extends Controller * ) * @ParamConverter("accompanyingCourse", options={"id": "accompanying_period_id"}) */ - public function addParticipationAPI(Request $request, AccompanyingPeriod $accompanyingCourse, $_format): Response + public function participationAPI(Request $request, AccompanyingPeriod $accompanyingCourse, $_format): Response { switch ($_format) { case 'json': @@ -146,7 +146,9 @@ class AccompanyingCourseController extends Controller } // TODO add acl - $accompanyingCourse->addPerson($person); + $participation = ($request->getMethod() === 'POST') ? + $accompanyingCourse->addPerson($person) : $accompanyingCourse->removePerson($person); + $errors = $this->validator->validate($accompanyingCourse); if ($errors->count() > 0) { @@ -156,6 +158,6 @@ class AccompanyingCourseController extends Controller $this->getDoctrine()->getManager()->flush(); - return new JsonResponse(); + return $this->json($participation); } } diff --git a/src/Bundle/ChillPersonBundle/Controller/OpeningApiController.php b/src/Bundle/ChillPersonBundle/Controller/OpeningApiController.php new file mode 100644 index 000000000..3b44162c4 --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Controller/OpeningApiController.php @@ -0,0 +1,17 @@ +where($qb->expr()->gt('e.noActiveAfter', ':now')) + ->orWhere($qb->expr()->isNull('e.noActiveAfter')); + $qb->setParameter('now', new \DateTime('now')); + } +} diff --git a/src/Bundle/ChillPersonBundle/Controller/PersonController.php b/src/Bundle/ChillPersonBundle/Controller/PersonController.php index bfb32654e..02320aed3 100644 --- a/src/Bundle/ChillPersonBundle/Controller/PersonController.php +++ b/src/Bundle/ChillPersonBundle/Controller/PersonController.php @@ -38,6 +38,7 @@ use Symfony\Component\Translation\TranslatorInterface; use Chill\MainBundle\Search\SearchProvider; use Chill\PersonBundle\Repository\PersonRepository; use Chill\PersonBundle\Config\ConfigPersonAltNamesHelper; +use Chill\PersonBundle\Repository\PersonNotDuplicateRepository; use Symfony\Component\Validator\Validator\ValidatorInterface; use Doctrine\ORM\EntityManagerInterface; @@ -290,7 +291,7 @@ final class PersonController extends AbstractController return $errors; } - public function reviewAction(Request $request) + public function reviewAction(Request $request, PersonNotDuplicateRepository $personNotDuplicateRepository) { if ($request->getMethod() !== 'POST') { $r = new Response("You must send something to review the creation of a new Person"); @@ -299,7 +300,6 @@ final class PersonController extends AbstractController } $form = $this->createForm( - //CreationPersonType::NAME, CreationPersonType::class, new Person(), array( @@ -326,7 +326,7 @@ final class PersonController extends AbstractController } $form = $this->createForm( - CreationPersonType::NAME, + CreationPersonType::class, $person, array( 'action' => $this->generateUrl('chill_person_review'), @@ -342,8 +342,7 @@ final class PersonController extends AbstractController $this->em->persist($person); - $alternatePersons = $this->similarPersonMatcher - ->matchPerson($person); + $alternatePersons = $this->similarPersonMatcher->matchPerson($person, $personNotDuplicateRepository); if (count($alternatePersons) === 0) { return $this->forward('ChillPersonBundle:Person:create'); diff --git a/src/Bundle/ChillPersonBundle/Controller/PersonDuplicateController.php b/src/Bundle/ChillPersonBundle/Controller/PersonDuplicateController.php index aa2e2768a..d30ddebae 100644 --- a/src/Bundle/ChillPersonBundle/Controller/PersonDuplicateController.php +++ b/src/Bundle/ChillPersonBundle/Controller/PersonDuplicateController.php @@ -19,6 +19,7 @@ use Symfony\Component\Translation\TranslatorInterface; use Chill\ActivityBundle\Entity\Activity; use Chill\DocStoreBundle\Entity\PersonDocument; use Chill\EventBundle\Entity\Participation; +use Chill\PersonBundle\Repository\PersonNotDuplicateRepository; use Chill\TaskBundle\Entity\SingleTask; class PersonDuplicateController extends Controller @@ -62,7 +63,7 @@ class PersonDuplicateController extends Controller $this->eventDispatcher = $eventDispatcher; } - public function viewAction($person_id) + public function viewAction($person_id, PersonNotDuplicateRepository $personNotDuplicateRepository) { $person = $this->_getPerson($person_id); if ($person === null) { @@ -74,10 +75,9 @@ class PersonDuplicateController extends Controller "You are not allowed to see this person."); $duplicatePersons = $this->similarPersonMatcher-> - matchPerson($person, 0.5, SimilarPersonMatcher::SIMILAR_SEARCH_ORDER_BY_ALPHABETICAL); + matchPerson($person, $personNotDuplicateRepository, 0.5, SimilarPersonMatcher::SIMILAR_SEARCH_ORDER_BY_ALPHABETICAL); - $notDuplicatePersons = $this->getDoctrine()->getRepository(PersonNotDuplicate::class) - ->findNotDuplicatePerson($person); + $notDuplicatePersons = $personNotDuplicateRepository->findNotDuplicatePerson($person); return $this->render('ChillPersonBundle:PersonDuplicate:view.html.twig', [ 'person' => $person, @@ -97,7 +97,7 @@ class PersonDuplicateController extends Controller $person1->counters = $this->_getCounters($person1_id); $person2->counters = $this->_getCounters($person2_id); - + if ($person1 === null) { throw $this->createNotFoundException("Person with id $person1_id not" . " found on this server"); @@ -264,17 +264,17 @@ class PersonDuplicateController extends Controller return [$person1, $person2]; } - + private function _getCounters($id): ?array { $em = $this->getDoctrine()->getManager(); - + $nb_activity = $em->getRepository(Activity::class)->findBy(['person'=>$id]); $nb_document = $em->getRepository(PersonDocument::class)->findBy(['person'=>$id]); $nb_event = $em->getRepository(Participation::class)->findBy(['person'=>$id]); $nb_task = $em->getRepository(SingleTask::class)->countByParameters(['person'=>$id]); $person = $em->getRepository(Person::class)->findOneBy(['id'=>$id]); - + return [ 'nb_activity' => count($nb_activity), 'nb_document' => count($nb_document), diff --git a/src/Bundle/ChillPersonBundle/DataFixtures/ORM/LoadAccompanyingPeriod.php b/src/Bundle/ChillPersonBundle/DataFixtures/ORM/LoadAccompanyingPeriod.php new file mode 100644 index 000000000..a30943bcb --- /dev/null +++ b/src/Bundle/ChillPersonBundle/DataFixtures/ORM/LoadAccompanyingPeriod.php @@ -0,0 +1,88 @@ +, + * + * 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 Symfony\Component\DependencyInjection\ContainerAwareInterface; + +use Chill\PersonBundle\Entity\AccompanyingPeriod; +use Chill\PersonBundle\Entity\Person; + +/** + * Description of LoadAccompanyingPeriod + * + * @author Champs-Libres Coop + */ +class LoadAccompanyingPeriod extends AbstractFixture implements OrderedFixtureInterface, ContainerAwareInterface +{ + use \Symfony\Component\DependencyInjection\ContainerAwareTrait; + + + public const ACCOMPANYING_PERIOD = 'parcours 1'; + + public function getOrder() + { + return 10004; + } + + public static $references = array(); + + public function load(ObjectManager $manager) + { + + $centerA = $this->getReference('centerA'); + $centerAId = $centerA->getId(); + + $personIds = $this->container->get('doctrine.orm.entity_manager') + ->createQueryBuilder() + ->select('p.id') + ->from('ChillPersonBundle:Person', 'p') + ->where('p.center = :centerAId') + ->orderBy('p.id', 'ASC') + ->setParameter('centerAId', $centerAId) + ->getQuery() + ->getScalarResult(); + + $openingDate = new \DateTime('2020-04-01'); + + $person1 = $manager->getRepository(Person::class)->find($personIds[0]); + $person2 = $manager->getRepository(Person::class)->find($personIds[1]); + + $socialScope = $this->getReference('scope_social'); + + $a = new AccompanyingPeriod($openingDate); + $a->addPerson($person1); + $a->addPerson($person2); + $a->addScope($socialScope); + $a->setStep(AccompanyingPeriod::STEP_CONFIRMED); + + $manager->persist($a); + + $this->addReference(self::ACCOMPANYING_PERIOD, $a); + echo "Adding one AccompanyingPeriod\n"; + + $manager->flush(); + } +} diff --git a/src/Bundle/ChillPersonBundle/DataFixtures/ORM/LoadAccompanyingPeriodOrigin.php b/src/Bundle/ChillPersonBundle/DataFixtures/ORM/LoadAccompanyingPeriodOrigin.php new file mode 100644 index 000000000..cbec9c439 --- /dev/null +++ b/src/Bundle/ChillPersonBundle/DataFixtures/ORM/LoadAccompanyingPeriodOrigin.php @@ -0,0 +1,62 @@ +, + * + * 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\Origin; + +/** + * Description of LoadAccompanyingPeriodOrigin + * + * @author Champs-Libres Coop + */ +class LoadAccompanyingPeriodOrigin extends AbstractFixture implements OrderedFixtureInterface +{ + + public const ACCOMPANYING_PERIOD_ORIGIN = 'accompanying_period_origin'; + + public function getOrder() + { + return 10005; + } + + private $phoneCall = ['en' => 'phone call', 'fr' => 'appel téléphonique']; + + public static $references = array(); + + public function load(ObjectManager $manager) + { + $o = new Origin(); + $o->setLabel(json_encode($this->phoneCall)); + + $manager->persist($o); + + $this->addReference(self::ACCOMPANYING_PERIOD_ORIGIN, $o); + echo "Adding one AccompanyingPeriod Origin\n"; + + $manager->flush(); + } +} diff --git a/src/Bundle/ChillPersonBundle/DataFixtures/ORM/LoadPeople.php b/src/Bundle/ChillPersonBundle/DataFixtures/ORM/LoadPeople.php index fa20d3457..e8697ff28 100644 --- a/src/Bundle/ChillPersonBundle/DataFixtures/ORM/LoadPeople.php +++ b/src/Bundle/ChillPersonBundle/DataFixtures/ORM/LoadPeople.php @@ -29,6 +29,7 @@ use Chill\PersonBundle\Entity\Person; use Symfony\Component\DependencyInjection\ContainerAwareInterface; use Chill\MainBundle\DataFixtures\ORM\LoadPostalCodes; use Chill\MainBundle\Entity\Address; +use Chill\MainBundle\Doctrine\Model\Point; /** * Load people into database @@ -37,17 +38,17 @@ use Chill\MainBundle\Entity\Address; * @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 @@ -56,57 +57,57 @@ class LoadPeople extends AbstractFixture implements OrderedFixtureInterface, Con $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 = ''; @@ -117,13 +118,13 @@ class LoadPeople extends AbstractFixture implements OrderedFixtureInterface, Con } 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(); @@ -137,7 +138,7 @@ class LoadPeople extends AbstractFixture implements OrderedFixtureInterface, Con } else { $address = null; } - + $person = array( 'FirstName' => $firstName, 'LastName' => $lastName, @@ -147,15 +148,15 @@ class LoadPeople extends AbstractFixture implements OrderedFixtureInterface, Con '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) @@ -171,10 +172,10 @@ class LoadPeople extends AbstractFixture implements OrderedFixtureInterface, Con 'Address' => null ), $specific); } - + /** * create a new person from array data - * + * * @param array $person * @param ObjectManager $manager * @throws \Exception @@ -200,35 +201,51 @@ class LoadPeople extends AbstractFixture implements OrderedFixtureInterface, Con $this->addAccompanyingPeriods($p, $value, $manager); 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 + // 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 - * + * Create a random point + * + * @return Point + */ + private function getRandomPoint() + { + $lonBrussels = 4.35243; + $latBrussels = 50.84676; + $lon = $lonBrussels + 0.01 * rand(-5, 5); + $lat = $latBrussels + 0.01 * rand(-5, 5); + return Point::fromLonLat($lon, $lat); + } + + + /** + * Create a random address + * * @return Address */ private function getRandomAddress() @@ -238,13 +255,16 @@ class LoadPeople extends AbstractFixture implements OrderedFixtureInterface, Con ->setStreetAddress2( rand(0,9) > 5 ? $this->faker->streetAddress : '' ) + ->setPoint( + rand(0,9) > 5 ? $this->getRandomPoint() : NULL + ) ->setPostcode($this->getReference( LoadPostalCodes::$refs[array_rand(LoadPostalCodes::$refs)] )) ->setValidFrom($this->faker->dateTimeBetween('-5 years')) ; } - + private function getCountry($countryCode) { if ($countryCode === NULL) { @@ -257,30 +277,30 @@ class LoadPeople extends AbstractFixture implements OrderedFixtureInterface, Con private $maritalStatusRef = ['ms_single', 'ms_married', 'ms_widow', 'ms_separat', 'ms_divorce', 'ms_legalco', 'ms_unknown']; - + private $firstNamesMale = array("Jean", "Mohamed", "Alfred", "Robert", "Justin", "Brian", "Compère", "Jean-de-Dieu", "Charles", "Pierre", "Luc", "Mathieu", "Alain", "Etienne", "Eric", "Corentin", "Gaston", "Spirou", "Fantasio", "Mahmadou", "Mohamidou", "Vursuv", "Youssef" ); - + private $firstNamesFemale = array("Svedana", "Sevlatina", "Irène", "Marcelle", "Corentine", "Alfonsine", "Caroline", "Solange", "Gostine", "Fatoumata", "Nicole", "Groseille", "Chana", "Oxana", "Ivana", "Julie", "Tina", "Adèle" ); - + private $lastNames = array("Diallo", "Bah", "Gaillot", "Martin"); - + private $lastNamesTrigrams = array("fas", "tré", "hu", 'blart', 'van', 'der', 'lin', 'den', 'ta', 'mi', 'net', 'gna', 'bol', 'sac', 'ré', 'jo', 'du', 'pont', 'cas', 'tor', 'rob', 'al', 'ma', 'gone', 'car',"fu", "ka", "lot", "no", "va", "du", "bu", "su", "jau", "tte", 'sir', "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( 'LastName' => "Depardieu", @@ -362,21 +382,21 @@ class LoadPeople extends AbstractFixture implements OrderedFixtureInterface, Con 'maritalStatus' => 'ms_legalco' ), ); - - + + private function addAccompanyingPeriods(Person $person, array $periods, ObjectManager $manager) { foreach ($periods as $period) { - + echo "adding new past Accompanying Period..\n"; - + /** @var AccompanyingPeriod $accompanyingPeriod */ $accompanyingPeriod = new AccompanyingPeriod(new \DateTime($period['from'])); $accompanyingPeriod ->setClosingDate(new \DateTime($period['to'])) ->setRemark($period['remark']) ; - + $person->addAccompanyingPeriod($accompanyingPeriod); } } diff --git a/src/Bundle/ChillPersonBundle/DataFixtures/ORM/LoadPersonACL.php b/src/Bundle/ChillPersonBundle/DataFixtures/ORM/LoadPersonACL.php index a3f42ced9..c8593d1fd 100644 --- a/src/Bundle/ChillPersonBundle/DataFixtures/ORM/LoadPersonACL.php +++ b/src/Bundle/ChillPersonBundle/DataFixtures/ORM/LoadPersonACL.php @@ -25,6 +25,7 @@ use Doctrine\Persistence\ObjectManager; use Chill\MainBundle\DataFixtures\ORM\LoadPermissionsGroup; use Chill\MainBundle\Entity\RoleScope; use Chill\PersonBundle\Security\Authorization\PersonVoter; +use Chill\PersonBundle\Security\Authorization\AccompanyingPeriodVoter; /** * Add a role CHILL_PERSON_UPDATE & CHILL_PERSON_CREATE for all groups except administrative, @@ -44,6 +45,7 @@ class LoadPersonACL extends AbstractFixture implements OrderedFixtureInterface { foreach (LoadPermissionsGroup::$refs as $permissionsGroupRef) { $permissionsGroup = $this->getReference($permissionsGroupRef); + $scopeSocial = $this->getReference('scope_social'); //create permission group switch ($permissionsGroup->getName()) { @@ -51,6 +53,12 @@ class LoadPersonACL extends AbstractFixture implements OrderedFixtureInterface case 'direction': printf("Adding CHILL_PERSON_UPDATE & CHILL_PERSON_CREATE to %s permission group \n", $permissionsGroup->getName()); + $permissionsGroup->addRoleScope( + (new RoleScope()) + ->setRole(AccompanyingPeriodVoter::SEE) + ->setScope($scopeSocial) + ); + $roleScopeUpdate = (new RoleScope()) ->setRole('CHILL_PERSON_UPDATE') ->setScope(null); diff --git a/src/Bundle/ChillPersonBundle/DataFixtures/ORM/LoadSocialActions.php b/src/Bundle/ChillPersonBundle/DataFixtures/ORM/LoadSocialActions.php new file mode 100644 index 000000000..ec47c6e0b --- /dev/null +++ b/src/Bundle/ChillPersonBundle/DataFixtures/ORM/LoadSocialActions.php @@ -0,0 +1,82 @@ + + * + * 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\SocialWork\SocialAction; + +/** + * Create social actions + * + */ +class LoadSocialActions extends AbstractFixture implements OrderedFixtureInterface +{ + public function getOrder() + { + return 10020; + } + + public static $socialActions = [ + 'social_action_info_conseil' => [ + 'title' => [ + 'fr' => 'Informer, conseiller' + ], + 'issue' => 'social_issue_prev_prot' + ], + 'social_action_instruire' => [ + 'title' => [ + 'fr' => 'Instruire l\'imprime unique pour des impayés' + ], + 'issue' => 'social_issue_prev_prot' + ], + 'social_action_MASP' => [ + 'title' => [ + 'fr' => 'MASP' + ], + 'issue' => 'social_issue_diff_fin' + ], + 'social_action_protection_enfant' => [ + 'title' => [ + 'fr' => 'Protection Enfant confié dans le cadre judiciaire' + ], + 'issue' => 'social_issue_enfant_protection' + ], + ]; + + public function load(ObjectManager $manager) + { + foreach (static::$socialActions as $ref => $new) { + $socialAction = new SocialAction(); + $socialAction->setTitle($new['title']); + $socialAction->setIssue($this->getReference($new['issue'])); + $socialAction->setDefaultNotificationDelay(new \DateInterval('P5D')); + + $manager->persist($socialAction); + $this->addReference($ref, $socialAction); + print("Adding SocialAction '".$new['title']['fr']."'\n"); + } + + $manager->flush(); + } +} diff --git a/src/Bundle/ChillPersonBundle/DataFixtures/ORM/LoadSocialGoals.php b/src/Bundle/ChillPersonBundle/DataFixtures/ORM/LoadSocialGoals.php new file mode 100644 index 000000000..a6c2b0ef0 --- /dev/null +++ b/src/Bundle/ChillPersonBundle/DataFixtures/ORM/LoadSocialGoals.php @@ -0,0 +1,70 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace Chill\PersonBundle\DataFixtures\ORM; + +use Doctrine\Common\DataFixtures\AbstractFixture; +use Doctrine\Common\DataFixtures\OrderedFixtureInterface; +use Doctrine\Persistence\ObjectManager; + +use Chill\PersonBundle\Entity\SocialWork\Goal; + + +/** + * Create social goals + * + */ +class LoadSocialGoals extends AbstractFixture implements OrderedFixtureInterface +{ + public function getOrder() + { + return 10030; + } + + public static $socialGoals = [ + 'social_goal_instuire_dossier' => [ + 'title' => [ + 'fr' => 'Instruire le dossier de surendettement' + ], + 'action' => 'social_action_MASP' + ], + 'social_goal_proteger' => [ + 'title' => [ + 'fr' => 'Protéger via une assistance educative placement' + ], + 'action' => 'social_action_protection_enfant' + ], + ]; + + public function load(ObjectManager $manager) + { + foreach (static::$socialGoals as $ref => $new) { + $socialGoal = new Goal(); + $socialGoal->setTitle($new['title']); + $socialGoal->addSocialAction($this->getReference($new['action'])); + + $manager->persist($socialGoal); + $this->addReference($ref, $socialGoal); + print("Adding SocialGoal '".$new['title']['fr']."'\n"); + } + + $manager->flush(); + } +} diff --git a/src/Bundle/ChillPersonBundle/DataFixtures/ORM/LoadSocialIssues.php b/src/Bundle/ChillPersonBundle/DataFixtures/ORM/LoadSocialIssues.php new file mode 100644 index 000000000..eb9c303ed --- /dev/null +++ b/src/Bundle/ChillPersonBundle/DataFixtures/ORM/LoadSocialIssues.php @@ -0,0 +1,90 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace Chill\PersonBundle\DataFixtures\ORM; + +use Doctrine\Common\DataFixtures\AbstractFixture; +use Doctrine\Common\DataFixtures\OrderedFixtureInterface; +use Doctrine\Persistence\ObjectManager; + +use Chill\PersonBundle\Entity\SocialWork\SocialIssue; + +/** + * Create social issues + * + */ +class LoadSocialIssues extends AbstractFixture implements OrderedFixtureInterface +{ + public function getOrder() + { + return 10010; + } + + public static $socialIssues = [ + 'social_issue_diff_fin_or_admin' => [ + 'title' => [ + 'fr' => 'ADULTE - DIFFICULTES FINANCIERES ET/OU ADMINISTRATIVES' + ] + ], + 'social_issue_prev_prot' => [ + 'title' => [ + 'fr' => 'ADULTE PREVENTION/PROTECTION' + ], + 'parent' => 'social_issue_diff_fin_or_admin' + ], + 'social_issue_diff_fin' => [ + 'title' => [ + 'fr' => 'Difficulté financière' + ], + 'parent' => 'social_issue_diff_fin_or_admin' + ], + 'social_issue_enfant_famille' => [ + 'title' => [ + 'fr' => 'Enfant / famille' + ] + ], + 'social_issue_enfant_protection' => [ + 'title' => [ + 'fr' => 'enfant - protection' + ], + 'parent' => 'social_issue_enfant_famille' + ], + ]; + + public function load(ObjectManager $manager) + { + foreach (static::$socialIssues as $ref => $new) { + $socialIssue = new SocialIssue(); + $socialIssue->setTitle($new['title']); + + if ( array_key_exists('parent', $new)) { + $parentRef = $new['parent']; + $parent = $this->getReference($parentRef); + $socialIssue->setParent($parent); + } + + $manager->persist($socialIssue); + $this->addReference($ref, $socialIssue); + print("Adding SocialIssue '".$new['title']['fr']."'\n"); + } + + $manager->flush(); + } +} diff --git a/src/Bundle/ChillPersonBundle/DataFixtures/ORM/LoadSocialResults.php b/src/Bundle/ChillPersonBundle/DataFixtures/ORM/LoadSocialResults.php new file mode 100644 index 000000000..573313573 --- /dev/null +++ b/src/Bundle/ChillPersonBundle/DataFixtures/ORM/LoadSocialResults.php @@ -0,0 +1,94 @@ + + * + * 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\SocialWork\Result; + + +/** + * Create social results + * + */ +class LoadSocialResults extends AbstractFixture implements OrderedFixtureInterface +{ + public function getOrder() + { + return 10040; + } + + public static $socialResults = [ + 'social_result_FSL_acces' => [ + 'title' => [ + 'fr' => 'FSL - accès cautionnement' + ], + 'action' => 'social_action_instruire' + ], + 'social_result_FSL_maintien' => [ + 'title' => [ + 'fr' => 'FSL maintien - impayés de loyer' + ], + 'action' => 'social_action_MASP' + ], + 'social_result_soutien_parental' => [ + 'title' => [ + 'fr' => 'Soutien parental' + ], + // 'action' => 'social_action_protection_enfant', (via le goal) + 'goal' => 'social_goal_proteger' + ], + 'social_result_accompagnement_mineur' => [ + 'title' => [ + 'fr' => 'Accompagnement du mineur' + ], + // 'action' => 'social_action_protection_enfant', (via le goal) + 'goal' => 'social_goal_proteger', + ], + ]; + + public function load(ObjectManager $manager) + { + foreach (static::$socialResults as $ref => $new) { + $socialResult = new Result(); + $socialResult->setTitle($new['title']); + + if ( array_key_exists('action', $new)) { + $action = $this->getReference($new['action']); + $socialResult->addSocialAction($action); + } + + if ( array_key_exists('goal', $new)) { + $goal = $this->getReference($new['goal']); + $socialResult->addGoal($goal); + } + + + $manager->persist($socialResult); + $this->addReference($ref, $socialResult); + print("Adding SocialResult '".$new['title']['fr']."'\n"); + } + + $manager->flush(); + } +} diff --git a/src/Bundle/ChillPersonBundle/DependencyInjection/ChillPersonExtension.php b/src/Bundle/ChillPersonBundle/DependencyInjection/ChillPersonExtension.php index 9eaa5d17f..98e2d30e9 100644 --- a/src/Bundle/ChillPersonBundle/DependencyInjection/ChillPersonExtension.php +++ b/src/Bundle/ChillPersonBundle/DependencyInjection/ChillPersonExtension.php @@ -28,6 +28,7 @@ use Chill\MainBundle\DependencyInjection\MissingBundleException; use Chill\PersonBundle\Security\Authorization\PersonVoter; use Chill\MainBundle\Security\Authorization\ChillExportVoter; use Chill\PersonBundle\Doctrine\DQL\AddressPart; +use Symfony\Component\HttpFoundation\Request; /** * Class ChillPersonExtension @@ -72,10 +73,13 @@ class ChillPersonExtension extends Extension implements PrependExtensionInterfac $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'); + // We can get rid of this file when the service 'chill.person.repository.person' is no more used. + // We should use the PersonRepository service instead of a custom service name. + $loader->load('services/repository.yaml'); $loader->load('services/serializer.yaml'); + $loader->load('services/security.yaml'); // load service advanced search only if configure if ($config['search']['search_by_phone'] != 'never') { @@ -307,6 +311,55 @@ class ChillPersonExtension extends Extension implements PrependExtensionInterfac 'template' => '@ChillPerson/MaritalStatus/edit.html.twig', ] ] + ], + ], + 'apis' => [ + [ + 'class' => \Chill\PersonBundle\Entity\AccompanyingPeriod::class, + 'name' => 'accompanying_course', + 'base_path' => '/api/1.0/person/accompanying-course', + 'controller' => \Chill\PersonBundle\Controller\AccompanyingCourseApiController::class, + 'actions' => [ + '_entity' => [ + 'roles' => [ + Request::METHOD_GET => \Chill\PersonBundle\Security\Authorization\AccompanyingPeriodVoter::SEE + ] + ], + 'participation' => [ + 'methods' => [ + Request::METHOD_POST => true, + Request::METHOD_DELETE => true, + Request::METHOD_GET => false, + Request::METHOD_HEAD => false, + ], + 'roles' => [ + Request::METHOD_POST => \Chill\PersonBundle\Security\Authorization\AccompanyingPeriodVoter::SEE, + Request::METHOD_DELETE=> \Chill\PersonBundle\Security\Authorization\AccompanyingPeriodVoter::SEE + ] + ] + + ] + ], + [ + 'class' => \Chill\PersonBundle\Entity\AccompanyingPeriod\Origin::class, + 'name' => 'accompanying_period_origin', + 'base_path' => '/api/1.0/person/accompanying-period/origin', + 'controller' => \Chill\PersonBundle\Controller\OpeningApiController::class, + 'base_role' => 'ROLE_USER', + 'actions' => [ + '_index' => [ + 'methods' => [ + Request::METHOD_GET => true, + Request::METHOD_HEAD => true + ], + ], + '_entity' => [ + 'methods' => [ + Request::METHOD_GET => true, + Request::METHOD_HEAD => true + ] + ], + ] ] ] ]); diff --git a/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod.php b/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod.php index 18ee535b8..8ec017c07 100644 --- a/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod.php +++ b/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod.php @@ -37,7 +37,7 @@ use Chill\MainBundle\Entity\User; /** * AccompanyingPeriod Class * - * @ORM\Entity(repositoryClass="Chill\PersonBundle\Repository\AccompanyingPeriodRepository") + * @ORM\Entity * @ORM\Table(name="chill_person_accompanying_period") */ class AccompanyingPeriod @@ -118,7 +118,7 @@ class AccompanyingPeriod * * @ORM\OneToMany(targetEntity=AccompanyingPeriodParticipation::class, * mappedBy="accompanyingPeriod", - * cascade={"persist", "remove", "merge", "detach"}) + * cascade={"persist", "refresh", "remove", "merge", "detach"}) */ private $participations; @@ -344,50 +344,68 @@ class AccompanyingPeriod } /** - * This private function scan Participations Collection, - * searching for a given Person + * Get the participation containing a person */ - private function participationsContainsPerson(Person $person): ?AccompanyingPeriodParticipation + public function getParticipationsContainsPerson(Person $person): Collection { - foreach ($this->participations as $participation) { - /** @var AccompanyingPeriodParticipation $participation */ - if ($person === $participation->getPerson()) { - return $participation; - }} - - return null; + return $this->getParticipations($person)->filter( + function(AccompanyingPeriodParticipation $participation) use ($person) { + if ($person === $participation->getPerson()) { + return $participation; + } + }); } /** - * This public function is the same but return only true or false + * Get the opened participation containing a person + * + * "Open" means that the closed date is NULL + */ + public function getOpenParticipationContainsPerson(Person $person): ?AccompanyingPeriodParticipation + { + $collection = $this->getParticipationsContainsPerson($person)->filter( + function(AccompanyingPeriodParticipation $participation) use ($person) { + if (NULL === $participation->getEndDate()) { + return $participation; + } + }); + + return $collection->count() > 0 ? $collection->first() : NULL; + } + + /** + * Return true if the accompanying period contains a person. + * + * **Note**: this participation can be opened or not. */ public function containsPerson(Person $person): bool { - return ($this->participationsContainsPerson($person) === null) ? false : true; + return $this->getParticipationsContainsPerson($person)->count() > 0; } /** * Add Person */ - public function addPerson(Person $person = null): self + public function addPerson(Person $person = null): AccompanyingPeriodParticipation { $participation = new AccompanyingPeriodParticipation($this, $person); $this->participations[] = $participation; - return $this; + return $participation; } /** * Remove Person */ - public function removePerson(Person $person): void + public function removePerson(Person $person): ?AccompanyingPeriodParticipation { - $participation = $this->participationsContainsPerson($person); + $participation = $this->getOpenParticipationContainsPerson($person); - if (! null === $participation) { + if ($participation instanceof AccompanyingPeriodParticipation) { $participation->setEndDate(new \DateTimeImmutable('now')); - $this->participations->removeElement($participation); } + + return $participation; } @@ -415,7 +433,7 @@ class AccompanyingPeriod return false; } - $participation = $this->participationsContainsPerson($person); + $participation = $this->getParticipationsContainsPerson($person); if (!null === $participation) { $person = $participation->getPerson(); diff --git a/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/AccompanyingPeriodWork.php b/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/AccompanyingPeriodWork.php index 6983f6c0e..25fbcc342 100644 --- a/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/AccompanyingPeriodWork.php +++ b/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/AccompanyingPeriodWork.php @@ -4,7 +4,6 @@ namespace Chill\PersonBundle\Entity\AccompanyingPeriod; use Chill\PersonBundle\Entity\SocialWork\Result; use Chill\PersonBundle\Entity\SocialWork\SocialAction; -use Chill\PersonBundle\Repository\AccompanyingPeriod\AccompanyingPeriodWorkRepository; use Chill\MainBundle\Entity\User; use Chill\PersonBundle\Entity\AccompanyingPeriod; use Chill\ThirdPartyBundle\Entity\ThirdParty; @@ -13,7 +12,7 @@ use Doctrine\Common\Collections\Collection; use Doctrine\ORM\Mapping as ORM; /** - * @ORM\Entity(repositoryClass=AccompanyingPeriodWorkRepository::class) + * @ORM\Entity * @ORM\Table(name="chill_person_accompanying_period_work") */ class AccompanyingPeriodWork diff --git a/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/AccompanyingPeriodWorkGoal.php b/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/AccompanyingPeriodWorkGoal.php index ec371d6c2..955f9e3a1 100644 --- a/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/AccompanyingPeriodWorkGoal.php +++ b/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/AccompanyingPeriodWorkGoal.php @@ -4,13 +4,12 @@ namespace Chill\PersonBundle\Entity\AccompanyingPeriod; use Chill\PersonBundle\Entity\SocialWork\Goal; use Chill\PersonBundle\Entity\SocialWork\Result; -use Chill\PersonBundle\Repository\AccompanyingPeriod\AccompanyingPeriodWorkGoalRepository; use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Collection; use Doctrine\ORM\Mapping as ORM; /** - * @ORM\Entity(repositoryClass=AccompanyingPeriodWorkGoalRepository::class) + * @ORM\Entity * @ORM\Table(name="chill_person_accompanying_period_work_goal") */ class AccompanyingPeriodWorkGoal diff --git a/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/ClosingMotive.php b/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/ClosingMotive.php index 4e334b2aa..29b2a44aa 100644 --- a/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/ClosingMotive.php +++ b/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/ClosingMotive.php @@ -29,8 +29,7 @@ use Doctrine\Common\Collections\ArrayCollection; /** * ClosingMotive give an explanation why we closed the Accompanying period * - * @ORM\Entity( - * repositoryClass="Chill\PersonBundle\Repository\AccompanyingPeriod\ClosingMotiveRepository") + * @ORM\Entity * @ORM\Table(name="chill_person_accompanying_period_closingmotive") */ class ClosingMotive diff --git a/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/Comment.php b/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/Comment.php index e8ecf3248..60b3466cd 100644 --- a/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/Comment.php +++ b/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/Comment.php @@ -22,13 +22,12 @@ namespace Chill\PersonBundle\Entity\AccompanyingPeriod; -use Chill\PersonBundle\Repository\AccompanyingPeriod\CommentRepository; use Chill\MainBundle\Entity\User; use Chill\PersonBundle\Entity\AccompanyingPeriod; use Doctrine\ORM\Mapping as ORM; /** - * @ORM\Entity(repositoryClass=CommentRepository::class) + * @ORM\Entity * @ORM\Table(name="chill_person_accompanying_period_comment") */ class Comment diff --git a/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/Origin.php b/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/Origin.php index 42c75efca..a852e166e 100644 --- a/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/Origin.php +++ b/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/Origin.php @@ -22,11 +22,10 @@ namespace Chill\PersonBundle\Entity\AccompanyingPeriod; -use Chill\PersonBundle\Entity\AccompanyingPeriod\OriginRepository; use Doctrine\ORM\Mapping as ORM; /** - * @ORM\Entity(repositoryClass=OriginRepository::class) + * @ORM\Entity * @ORM\Table(name="chill_person_accompanying_period_origin") */ class Origin @@ -53,7 +52,7 @@ class Origin return $this->id; } - public function getLabel(): ?string + public function getLabel() { return $this->label; } diff --git a/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/Resource.php b/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/Resource.php index ec13fcad6..0cfefba87 100644 --- a/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/Resource.php +++ b/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/Resource.php @@ -22,7 +22,6 @@ namespace Chill\PersonBundle\Entity\AccompanyingPeriod; -use Chill\PersonBundle\Repository\AccompanyingPeriod\ResourceRepository; use Chill\PersonBundle\Entity\AccompanyingPeriod; use Chill\PersonBundle\Entity\AccompanyingPeriod\Comment; use Chill\PersonBundle\Entity\Person; @@ -30,7 +29,7 @@ use Chill\ThirdPartyBundle\Entity\ThirdParty; use Doctrine\ORM\Mapping as ORM; /** - * @ORM\Entity(repositoryClass=ResourceRepository::class) + * @ORM\Entity * @ORM\Table(name="chill_person_accompanying_period_resource") */ class Resource diff --git a/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriodParticipation.php b/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriodParticipation.php index c22e84e42..d57d00386 100644 --- a/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriodParticipation.php +++ b/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriodParticipation.php @@ -22,7 +22,6 @@ namespace Chill\PersonBundle\Entity; -use Chill\PersonBundle\Repository\AccompanyingPeriodParticipationRepository; use Chill\PersonBundle\Entity\AccompanyingPeriod; use Chill\PersonBundle\Entity\Person; use Doctrine\ORM\Mapping as ORM; @@ -31,7 +30,7 @@ use Doctrine\ORM\Mapping as ORM; * AccompanyingPeriodParticipation Class * * @package Chill\PersonBundle\Entity - * @ORM\Entity(repositoryClass=AccompanyingPeriodParticipationRepository::class) + * @ORM\Entity * @ORM\Table(name="chill_person_accompanying_period_participation") */ class AccompanyingPeriodParticipation diff --git a/src/Bundle/ChillPersonBundle/Entity/Household/Household.php b/src/Bundle/ChillPersonBundle/Entity/Household/Household.php new file mode 100644 index 000000000..4e8787a96 --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Entity/Household/Household.php @@ -0,0 +1,69 @@ +id; + } + + /** + * Addresses + * @var Collection + * + * @ORM\ManyToMany( + * targetEntity="Chill\MainBundle\Entity\Address", + * cascade={"persist", "remove", "merge", "detach"}) + * @ORM\JoinTable(name="chill_person_household_to_addresses") + * @ORM\OrderBy({"validFrom" = "DESC"}) + */ + private $addresses; + + + /** + * @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; + } + +} diff --git a/src/Bundle/ChillPersonBundle/Entity/Household/HouseholdMembers.php b/src/Bundle/ChillPersonBundle/Entity/Household/HouseholdMembers.php new file mode 100644 index 000000000..5d16649a8 --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Entity/Household/HouseholdMembers.php @@ -0,0 +1,152 @@ +id; + } + + public function getPosition(): ?string + { + return $this->position; + } + + public function setPosition(?string $position): self + { + $this->position = $position; + + return $this; + } + + public function getStartDate(): ?\DateTimeInterface + { + return $this->startDate; + } + + public function setStartDate(\DateTimeInterface $startDate): self + { + $this->startDate = $startDate; + + return $this; + } + + public function getEndDate(): ?\DateTimeInterface + { + return $this->endDate; + } + + public function setEndDate(\DateTimeInterface $endDate): self + { + $this->endDate = $endDate; + + return $this; + } + + public function getComment(): ?string + { + return $this->comment; + } + + public function setComment(?string $comment): self + { + $this->comment = $comment; + + return $this; + } + + public function getSharedHousehold(): ?bool + { + return $this->sharedHousehold; + } + + public function setSharedHousehold(bool $sharedHousehold): self + { + $this->sharedHousehold = $sharedHousehold; + + return $this; + } + + public function getPerson(): ?Person + { + return $this->person; + } + + public function setPerson(?Person $person): self + { + $this->person = $person; + + return $this; + } + + public function getHousehold(): ?Household + { + return $this->household; + } + + public function setHousehold(?Household $household): self + { + $this->household = $household; + + return $this; + } +} diff --git a/src/Bundle/ChillPersonBundle/Entity/MaritalStatus.php b/src/Bundle/ChillPersonBundle/Entity/MaritalStatus.php index f1addbb2c..43ae1b09f 100644 --- a/src/Bundle/ChillPersonBundle/Entity/MaritalStatus.php +++ b/src/Bundle/ChillPersonBundle/Entity/MaritalStatus.php @@ -25,7 +25,7 @@ use Doctrine\ORM\Mapping as ORM; /** * MaritalStatus * - * @ORM\Entity() + * @ORM\Entity * @ORM\Table(name="chill_person_marital_status") * @ORM\HasLifecycleCallbacks() */ diff --git a/src/Bundle/ChillPersonBundle/Entity/Person.php b/src/Bundle/ChillPersonBundle/Entity/Person.php index 94e0a0ee5..e88998205 100644 --- a/src/Bundle/ChillPersonBundle/Entity/Person.php +++ b/src/Bundle/ChillPersonBundle/Entity/Person.php @@ -38,7 +38,7 @@ use Symfony\Component\Validator\Context\ExecutionContextInterface; /** * Person Class * - * @ORM\Entity(repositoryClass="Chill\PersonBundle\Repository\PersonRepository") + * @ORM\Entity * @ORM\Table(name="chill_person_person", * indexes={@ORM\Index( * name="person_names", @@ -390,7 +390,7 @@ class Person implements HasCenterInterface * * @deprecated since 1.1 use `getOpenedAccompanyingPeriod instead */ - public function getCurrentAccompanyingPeriod() : AccompanyingPeriod + public function getCurrentAccompanyingPeriod() : ?AccompanyingPeriod { return $this->getOpenedAccompanyingPeriod(); } diff --git a/src/Bundle/ChillPersonBundle/Entity/PersonAltName.php b/src/Bundle/ChillPersonBundle/Entity/PersonAltName.php index c5295487e..95603d4ef 100644 --- a/src/Bundle/ChillPersonBundle/Entity/PersonAltName.php +++ b/src/Bundle/ChillPersonBundle/Entity/PersonAltName.php @@ -8,7 +8,7 @@ use Doctrine\ORM\Mapping as ORM; * PersonAltName * * @ORM\Table(name="chill_person_alt_name") - * @ORM\Entity(repositoryClass="Chill\PersonBundle\Repository\PersonAltNameRepository") + * @ORM\Entity */ class PersonAltName { diff --git a/src/Bundle/ChillPersonBundle/Entity/PersonNotDuplicate.php b/src/Bundle/ChillPersonBundle/Entity/PersonNotDuplicate.php index ce2a1c26f..9f116c501 100644 --- a/src/Bundle/ChillPersonBundle/Entity/PersonNotDuplicate.php +++ b/src/Bundle/ChillPersonBundle/Entity/PersonNotDuplicate.php @@ -9,7 +9,7 @@ use Chill\MainBundle\Entity\User; * PersonNotDuplicate * * @ORM\Table(name="chill_person_not_duplicate") - * @ORM\Entity(repositoryClass="Chill\PersonBundle\Repository\PersonNotDuplicateRepository") + * @ORM\Entity */ class PersonNotDuplicate { diff --git a/src/Bundle/ChillPersonBundle/Entity/PersonPhone.php b/src/Bundle/ChillPersonBundle/Entity/PersonPhone.php index 4a2ffaf28..222495e17 100644 --- a/src/Bundle/ChillPersonBundle/Entity/PersonPhone.php +++ b/src/Bundle/ChillPersonBundle/Entity/PersonPhone.php @@ -8,7 +8,7 @@ use Doctrine\ORM\Mapping as ORM; /** * Person Phones * - * @ORM\Entity() + * @ORM\Entity * @ORM\Table(name="chill_person_phone") */ class PersonPhone diff --git a/src/Bundle/ChillPersonBundle/Entity/SocialWork/Evaluation.php b/src/Bundle/ChillPersonBundle/Entity/SocialWork/Evaluation.php index 9e6dcbaa6..5a877e264 100644 --- a/src/Bundle/ChillPersonBundle/Entity/SocialWork/Evaluation.php +++ b/src/Bundle/ChillPersonBundle/Entity/SocialWork/Evaluation.php @@ -2,11 +2,10 @@ namespace Chill\PersonBundle\Entity\SocialWork; -use Chill\PersonBundle\Repository\SocialWork\EvaluationRepository; use Doctrine\ORM\Mapping as ORM; /** - * @ORM\Entity(repositoryClass=EvaluationRepository::class) + * @ORM\Entity * @ORM\Table(name="chill_person_social_work_evaluation") */ class Evaluation diff --git a/src/Bundle/ChillPersonBundle/Entity/SocialWork/Goal.php b/src/Bundle/ChillPersonBundle/Entity/SocialWork/Goal.php index c92e9a736..996fcf3eb 100644 --- a/src/Bundle/ChillPersonBundle/Entity/SocialWork/Goal.php +++ b/src/Bundle/ChillPersonBundle/Entity/SocialWork/Goal.php @@ -2,13 +2,12 @@ namespace Chill\PersonBundle\Entity\SocialWork; -use Chill\PersonBundle\Repository\SocialWork\GoalRepository; use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Collection; use Doctrine\ORM\Mapping as ORM; /** - * @ORM\Entity(repositoryClass=GoalRepository::class) + * @ORM\Entity * @ORM\Table(name="chill_person_social_work_goal") */ class Goal diff --git a/src/Bundle/ChillPersonBundle/Entity/SocialWork/Result.php b/src/Bundle/ChillPersonBundle/Entity/SocialWork/Result.php index be38b1757..37e5c2b18 100644 --- a/src/Bundle/ChillPersonBundle/Entity/SocialWork/Result.php +++ b/src/Bundle/ChillPersonBundle/Entity/SocialWork/Result.php @@ -4,13 +4,12 @@ namespace Chill\PersonBundle\Entity\SocialWork; use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWork; use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWorkGoal; -use Chill\PersonBundle\Repository\SocialWork\ResultRepository; use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Collection; use Doctrine\ORM\Mapping as ORM; /** - * @ORM\Entity(repositoryClass=ResultRepository::class) + * @ORM\Entity * @ORM\Table(name="chill_person_social_work_result") */ class Result diff --git a/src/Bundle/ChillPersonBundle/Entity/SocialWork/SocialAction.php b/src/Bundle/ChillPersonBundle/Entity/SocialWork/SocialAction.php index 64413d987..ff7291b45 100644 --- a/src/Bundle/ChillPersonBundle/Entity/SocialWork/SocialAction.php +++ b/src/Bundle/ChillPersonBundle/Entity/SocialWork/SocialAction.php @@ -2,13 +2,12 @@ namespace Chill\PersonBundle\Entity\SocialWork; -use Chill\PersonBundle\Repository\SocialWork\SocialActionRepository; use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Collection; use Doctrine\ORM\Mapping as ORM; /** - * @ORM\Entity(repositoryClass=SocialActionRepository::class) + * @ORM\Entity * @ORM\Table(name="chill_person_social_action") */ class SocialAction diff --git a/src/Bundle/ChillPersonBundle/Entity/SocialWork/SocialIssue.php b/src/Bundle/ChillPersonBundle/Entity/SocialWork/SocialIssue.php index cfec01751..2b6df9be2 100644 --- a/src/Bundle/ChillPersonBundle/Entity/SocialWork/SocialIssue.php +++ b/src/Bundle/ChillPersonBundle/Entity/SocialWork/SocialIssue.php @@ -1,14 +1,12 @@ repository = $entityManager->getRepository(AccompanyingPeriodWorkGoal::class); } } diff --git a/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriod/AccompanyingPeriodWorkRepository.php b/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriod/AccompanyingPeriodWorkRepository.php index f879e97d7..5d455b617 100644 --- a/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriod/AccompanyingPeriodWorkRepository.php +++ b/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriod/AccompanyingPeriodWorkRepository.php @@ -3,8 +3,8 @@ namespace Chill\PersonBundle\Repository\AccompanyingPeriod; use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWork; -use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; -use Doctrine\Persistence\ManagerRegistry; +use Doctrine\ORM\EntityManagerInterface; +use Doctrine\ORM\EntityRepository; /** * @method AccompanyingPeriodWork|null find($id, $lockMode = null, $lockVersion = null) @@ -12,10 +12,12 @@ use Doctrine\Persistence\ManagerRegistry; * @method AccompanyingPeriodWork[] findAll() * @method AccompanyingPeriodWork[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null) */ -class AccompanyingPeriodWorkRepository extends ServiceEntityRepository +final class AccompanyingPeriodWorkRepository { - public function __construct(ManagerRegistry $registry) + private EntityRepository $repository; + + public function __construct(EntityManagerInterface $entityManager) { - parent::__construct($registry, AccompanyingPeriodWork::class); + $this->repository = $entityManager->getRepository(AccompanyingPeriodWork::class); } } diff --git a/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriod/ClosingMotiveRepository.php b/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriod/ClosingMotiveRepository.php index ef1130b62..c99ca8efc 100644 --- a/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriod/ClosingMotiveRepository.php +++ b/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriod/ClosingMotiveRepository.php @@ -22,8 +22,9 @@ namespace Chill\PersonBundle\Repository\AccompanyingPeriod; +use Chill\PersonBundle\Entity\AccompanyingPeriod\ClosingMotive; +use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityRepository; -use Doctrine\ORM\QueryBuilder; use Doctrine\ORM\Query\ResultSetMappingBuilder; /** @@ -32,17 +33,24 @@ use Doctrine\ORM\Query\ResultSetMappingBuilder; * * @package Chill\PersonBundle\Repository */ -class ClosingMotiveRepository extends EntityRepository +final class ClosingMotiveRepository { + private EntityRepository $repository; + + public function __construct(EntityManagerInterface $entityManager) + { + $this->repository = $entityManager->getRepository(ClosingMotive::class); + } + /** * @param bool $onlyLeaf * @return mixed */ public function getActiveClosingMotive(bool $onlyLeaf = true) { - $rsm = new ResultSetMappingBuilder($this->getEntityManager()); - $rsm->addRootEntityFromClassMetadata($this->getClassName(), 'cm'); - + $rsm = new ResultSetMappingBuilder($this->repository->getEntityManager()); + $rsm->addRootEntityFromClassMetadata($this->repository->getClassName(), 'cm'); + $sql = "SELECT ".(string) $rsm." FROM chill_person_accompanying_period_closingmotive AS cm WHERE @@ -55,10 +63,11 @@ class ClosingMotiveRepository extends EntityRepository } $sql .= " ORDER BY cm.ordering ASC"; - - return $this->_em + + return $this + ->repository + ->getEntityManager() ->createNativeQuery($sql, $rsm) - ->getResult() - ; + ->getResult(); } } diff --git a/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriod/CommentRepository.php b/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriod/CommentRepository.php index b41e77591..aca7a9c68 100644 --- a/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriod/CommentRepository.php +++ b/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriod/CommentRepository.php @@ -23,8 +23,8 @@ namespace Chill\PersonBundle\Repository\AccompanyingPeriod; use Chill\PersonBundle\Entity\AccompanyingPeriod\Comment; -use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; -use Doctrine\Persistence\ManagerRegistry; +use Doctrine\ORM\EntityManagerInterface; +use Doctrine\ORM\EntityRepository; /** * @method Comment|null find($id, $lockMode = null, $lockVersion = null) @@ -32,11 +32,12 @@ use Doctrine\Persistence\ManagerRegistry; * @method Comment[] findAll() * @method Comment[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null) */ -class CommentRepository extends ServiceEntityRepository +final class CommentRepository { - public function __construct(ManagerRegistry $registry) + private EntityRepository $repository; + + public function __construct(EntityManagerInterface $entityManager) { - parent::__construct($registry, Comment::class); + $this->repository = $entityManager->getRepository(Comment::class); } - } diff --git a/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriod/OriginRepository.php b/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriod/OriginRepository.php index 6a5b28901..c2851b851 100644 --- a/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriod/OriginRepository.php +++ b/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriod/OriginRepository.php @@ -23,8 +23,8 @@ namespace Chill\PersonBundle\Repository\AccompanyingPeriod; use Chill\PersonBundle\Entity\AccompanyingPeriod\Origin; -use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; -use Doctrine\Persistence\ManagerRegistry; +use Doctrine\ORM\EntityManagerInterface; +use Doctrine\ORM\EntityRepository; /** * @method Origin|null find($id, $lockMode = null, $lockVersion = null) @@ -32,11 +32,12 @@ use Doctrine\Persistence\ManagerRegistry; * @method Origin[] findAll() * @method Origin[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null) */ -class OriginRepository extends ServiceEntityRepository +final class OriginRepository { - public function __construct(ManagerRegistry $registry) - { - parent::__construct($registry, Origin::class); - } + private EntityRepository $repository; + public function __construct(EntityManagerInterface $entityManager) + { + $this->repository = $entityManager->getRepository(Origin::class); + } } diff --git a/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriod/ResourceRepository.php b/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriod/ResourceRepository.php index 46eaaaaa0..4f625b59c 100644 --- a/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriod/ResourceRepository.php +++ b/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriod/ResourceRepository.php @@ -23,8 +23,8 @@ namespace Chill\PersonBundle\Repository\AccompanyingPeriod; use Chill\PersonBundle\Entity\AccompanyingPeriod\Resource; -use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; -use Doctrine\Persistence\ManagerRegistry; +use Doctrine\ORM\EntityManagerInterface; +use Doctrine\ORM\EntityRepository; /** * @method Resource|null find($id, $lockMode = null, $lockVersion = null) @@ -32,11 +32,12 @@ use Doctrine\Persistence\ManagerRegistry; * @method Resource[] findAll() * @method Resource[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null) */ -class ResourceRepository extends ServiceEntityRepository +final class ResourceRepository { - public function __construct(ManagerRegistry $registry) - { - parent::__construct($registry, Resource::class); - } + private EntityRepository $repository; + public function __construct(EntityManagerInterface $entityManager) + { + $this->repository = $entityManager->getRepository(Resource::class); + } } diff --git a/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriodParticipationRepository.php b/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriodParticipationRepository.php index fbe957ecf..0f157ed42 100644 --- a/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriodParticipationRepository.php +++ b/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriodParticipationRepository.php @@ -23,8 +23,8 @@ namespace Chill\PersonBundle\Repository; use Chill\PersonBundle\Entity\AccompanyingPeriodParticipation; -use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; -use Doctrine\Persistence\ManagerRegistry; +use Doctrine\ORM\EntityManagerInterface; +use Doctrine\ORM\EntityRepository; /** * @method AccompanyingPeriodParticipation|null find($id, $lockMode = null, $lockVersion = null) @@ -32,11 +32,12 @@ use Doctrine\Persistence\ManagerRegistry; * @method AccompanyingPeriodParticipation[] findAll() * @method AccompanyingPeriodParticipation[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null) */ -class AccompanyingPeriodParticipationRepository extends ServiceEntityRepository +final class AccompanyingPeriodParticipationRepository { - public function __construct(ManagerRegistry $registry) + private EntityRepository $repository; + + public function __construct(EntityManagerInterface $entityManager) { - parent::__construct($registry, AccompanyingPeriodParticipation::class); + $this->repository = $entityManager->getRepository(AccompanyingPeriodParticipation::class); } - } diff --git a/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriodRepository.php b/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriodRepository.php index fffb55ede..07aa0a55f 100644 --- a/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriodRepository.php +++ b/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriodRepository.php @@ -23,9 +23,8 @@ namespace Chill\PersonBundle\Repository; use Chill\PersonBundle\Entity\AccompanyingPeriod; -use Chill\PersonBundle\Entity\Person; -use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; -use Doctrine\Persistence\ManagerRegistry; +use Doctrine\ORM\EntityManagerInterface; +use Doctrine\ORM\EntityRepository; /** * @method AccompanyingPeriod|null find($id, $lockMode = null, $lockVersion = null) @@ -33,10 +32,12 @@ use Doctrine\Persistence\ManagerRegistry; * @method AccompanyingPeriod[] findAll() * @method AccompanyingPeriod[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null) */ -class AccompanyingPeriodRepository extends ServiceEntityRepository +final class AccompanyingPeriodRepository { - public function __construct(ManagerRegistry $registry) + private EntityRepository $repository; + + public function __construct(EntityManagerInterface $entityManager) { - parent::__construct($registry, AccompanyingPeriod::class); + $this->repository = $entityManager->getRepository(AccompanyingPeriod::class); } } diff --git a/src/Bundle/ChillPersonBundle/Repository/Household/HouseholdMembersRepository.php b/src/Bundle/ChillPersonBundle/Repository/Household/HouseholdMembersRepository.php new file mode 100644 index 000000000..b11d3d93a --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Repository/Household/HouseholdMembersRepository.php @@ -0,0 +1,52 @@ +repository = $entityManager->getRepository(HouseholdMembers::class); + } + + // /** + // * @return HouseholdMembers[] Returns an array of HouseholdMembers objects + // */ + /* + public function findByExampleField($value) + { + return $this->createQueryBuilder('h') + ->andWhere('h.exampleField = :val') + ->setParameter('val', $value) + ->orderBy('h.id', 'ASC') + ->setMaxResults(10) + ->getQuery() + ->getResult() + ; + } + */ + + /* + public function findOneBySomeField($value): ?HouseholdMembers + { + return $this->createQueryBuilder('h') + ->andWhere('h.exampleField = :val') + ->setParameter('val', $value) + ->getQuery() + ->getOneOrNullResult() + ; + } + */ +} diff --git a/src/Bundle/ChillPersonBundle/Repository/Household/HouseholdRepository.php b/src/Bundle/ChillPersonBundle/Repository/Household/HouseholdRepository.php new file mode 100644 index 000000000..78a68f56d --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Repository/Household/HouseholdRepository.php @@ -0,0 +1,52 @@ +repository = $entityManager->getRepository(Household::class); + } + + // /** + // * @return Household[] Returns an array of Household objects + // */ + /* + public function findByExampleField($value) + { + return $this->createQueryBuilder('h') + ->andWhere('h.exampleField = :val') + ->setParameter('val', $value) + ->orderBy('h.id', 'ASC') + ->setMaxResults(10) + ->getQuery() + ->getResult() + ; + } + */ + + /* + public function findOneBySomeField($value): ?Household + { + return $this->createQueryBuilder('h') + ->andWhere('h.exampleField = :val') + ->setParameter('val', $value) + ->getQuery() + ->getOneOrNullResult() + ; + } + */ +} diff --git a/src/Bundle/ChillPersonBundle/Repository/PersonAltNameRepository.php b/src/Bundle/ChillPersonBundle/Repository/PersonAltNameRepository.php index 315cee94f..c5dea689a 100644 --- a/src/Bundle/ChillPersonBundle/Repository/PersonAltNameRepository.php +++ b/src/Bundle/ChillPersonBundle/Repository/PersonAltNameRepository.php @@ -2,12 +2,22 @@ namespace Chill\PersonBundle\Repository; +use Chill\PersonBundle\Entity\PersonAltName; +use Doctrine\ORM\EntityManagerInterface; +use Doctrine\ORM\EntityRepository; + /** * PersonAltNameRepository * * This class was generated by the Doctrine ORM. Add your own custom * repository methods below. */ -class PersonAltNameRepository extends \Doctrine\ORM\EntityRepository +final class PersonAltNameRepository { + private EntityRepository $repository; + + public function __construct(EntityManagerInterface $entityManager) + { + $this->repository = $entityManager->getRepository(PersonAltName::class); + } } diff --git a/src/Bundle/ChillPersonBundle/Repository/PersonNotDuplicateRepository.php b/src/Bundle/ChillPersonBundle/Repository/PersonNotDuplicateRepository.php index 8ab711b15..989baaeec 100644 --- a/src/Bundle/ChillPersonBundle/Repository/PersonNotDuplicateRepository.php +++ b/src/Bundle/ChillPersonBundle/Repository/PersonNotDuplicateRepository.php @@ -4,6 +4,7 @@ namespace Chill\PersonBundle\Repository; use Chill\PersonBundle\Entity\Person; use Chill\PersonBundle\Entity\PersonNotDuplicate; +use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityRepository; /** @@ -11,8 +12,15 @@ use Doctrine\ORM\EntityRepository; * * @package Chill\PersonBundle\Repository */ -class PersonNotDuplicateRepository extends EntityRepository +final class PersonNotDuplicateRepository { + private EntityRepository $repository; + + public function __construct(EntityManagerInterface $entityManager) + { + $this->repository = $entityManager->getRepository(PersonNotDuplicate::class); + } + /** * @param \Chill\PersonBundle\Entity\Person $person * @@ -20,7 +28,7 @@ class PersonNotDuplicateRepository extends EntityRepository */ public function findNotDuplicatePerson(Person $person) { - $qb = $this->createQueryBuilder('pnd'); + $qb = $this->repository->createQueryBuilder('pnd'); $qb->select('pnd') ->where('pnd.person1 = :person OR pnd.person2 = :person') ; diff --git a/src/Bundle/ChillPersonBundle/Repository/PersonRepository.php b/src/Bundle/ChillPersonBundle/Repository/PersonRepository.php index ccc56b639..12733a668 100644 --- a/src/Bundle/ChillPersonBundle/Repository/PersonRepository.php +++ b/src/Bundle/ChillPersonBundle/Repository/PersonRepository.php @@ -18,16 +18,25 @@ namespace Chill\PersonBundle\Repository; +use Chill\PersonBundle\Entity\Person; +use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityRepository; use Doctrine\ORM\QueryBuilder; -/** - * Class PersonRepository - * - * @package Chill\PersonBundle\Repository - */ -class PersonRepository extends EntityRepository +final class PersonRepository { + private EntityRepository $repository; + + public function __construct(EntityManagerInterface $entityManager) + { + $this->repository = $entityManager->getRepository(Person::class); + } + + public function find($id, $lockMode = null, $lockVersion = null) + { + return $this->repository->find($id, $lockMode, $lockVersion); + } + /** * @param string $phonenumber * @param $centers @@ -44,7 +53,7 @@ class PersonRepository extends EntityRepository $maxResults, array $only = ['mobile', 'phone'] ) { - $qb = $this->createQueryBuilder('p'); + $qb = $this->repository->createQueryBuilder('p'); $qb->select('p'); $this->addByCenters($qb, $centers); @@ -71,7 +80,7 @@ class PersonRepository extends EntityRepository array $only = ['mobile', 'phone'] ): int { - $qb = $this->createQueryBuilder('p'); + $qb = $this->repository->createQueryBuilder('p'); $qb->select('COUNT(p)'); $this->addByCenters($qb, $centers); diff --git a/src/Bundle/ChillPersonBundle/Repository/SocialWork/EvaluationRepository.php b/src/Bundle/ChillPersonBundle/Repository/SocialWork/EvaluationRepository.php index b030e1617..c9ab3c931 100644 --- a/src/Bundle/ChillPersonBundle/Repository/SocialWork/EvaluationRepository.php +++ b/src/Bundle/ChillPersonBundle/Repository/SocialWork/EvaluationRepository.php @@ -3,8 +3,8 @@ namespace Chill\PersonBundle\Repository\SocialWork; use Chill\PersonBundle\Entity\SocialWork\Evaluation; -use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; -use Doctrine\Persistence\ManagerRegistry; +use Doctrine\ORM\EntityManagerInterface; +use Doctrine\ORM\EntityRepository; /** * @method Evaluation|null find($id, $lockMode = null, $lockVersion = null) @@ -12,10 +12,12 @@ use Doctrine\Persistence\ManagerRegistry; * @method Evaluation[] findAll() * @method Evaluation[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null) */ -class EvaluationRepository extends ServiceEntityRepository +final class EvaluationRepository { - public function __construct(ManagerRegistry $registry) + private EntityRepository $repository; + + public function __construct(EntityManagerInterface $entityManager) { - parent::__construct($registry, Evaluation::class); + $this->repository = $entityManager->getRepository(Evaluation::class); } } diff --git a/src/Bundle/ChillPersonBundle/Repository/SocialWork/GoalRepository.php b/src/Bundle/ChillPersonBundle/Repository/SocialWork/GoalRepository.php index 031c31764..30576b4ed 100644 --- a/src/Bundle/ChillPersonBundle/Repository/SocialWork/GoalRepository.php +++ b/src/Bundle/ChillPersonBundle/Repository/SocialWork/GoalRepository.php @@ -3,8 +3,8 @@ namespace Chill\PersonBundle\Repository\SocialWork; use Chill\PersonBundle\Entity\SocialWork\Goal; -use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; -use Doctrine\Persistence\ManagerRegistry; +use Doctrine\ORM\EntityManagerInterface; +use Doctrine\ORM\EntityRepository; /** * @method Goal|null find($id, $lockMode = null, $lockVersion = null) @@ -12,10 +12,12 @@ use Doctrine\Persistence\ManagerRegistry; * @method Goal[] findAll() * @method Goal[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null) */ -class GoalRepository extends ServiceEntityRepository +final class GoalRepository { - public function __construct(ManagerRegistry $registry) + private EntityRepository $repository; + + public function __construct(EntityManagerInterface $entityManager) { - parent::__construct($registry, Goal::class); + $this->repository = $entityManager->getRepository(Goal::class); } } diff --git a/src/Bundle/ChillPersonBundle/Repository/SocialWork/ResultRepository.php b/src/Bundle/ChillPersonBundle/Repository/SocialWork/ResultRepository.php index 511823d61..004a2f82c 100644 --- a/src/Bundle/ChillPersonBundle/Repository/SocialWork/ResultRepository.php +++ b/src/Bundle/ChillPersonBundle/Repository/SocialWork/ResultRepository.php @@ -3,8 +3,8 @@ namespace Chill\PersonBundle\Repository\SocialWork; use Chill\PersonBundle\Entity\SocialWork\Result; -use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; -use Doctrine\Persistence\ManagerRegistry; +use Doctrine\ORM\EntityManagerInterface; +use Doctrine\ORM\EntityRepository; /** * @method Result|null find($id, $lockMode = null, $lockVersion = null) @@ -12,10 +12,12 @@ use Doctrine\Persistence\ManagerRegistry; * @method Result[] findAll() * @method Result[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null) */ -class ResultRepository extends ServiceEntityRepository +final class ResultRepository { - public function __construct(ManagerRegistry $registry) + private EntityRepository $repository; + + public function __construct(EntityManagerInterface $entityManager) { - parent::__construct($registry, Result::class); + $this->repository = $entityManager->getRepository(Result::class); } } diff --git a/src/Bundle/ChillPersonBundle/Repository/SocialWork/SocialActionRepository.php b/src/Bundle/ChillPersonBundle/Repository/SocialWork/SocialActionRepository.php index f0f2e81b4..cfa9adbd7 100644 --- a/src/Bundle/ChillPersonBundle/Repository/SocialWork/SocialActionRepository.php +++ b/src/Bundle/ChillPersonBundle/Repository/SocialWork/SocialActionRepository.php @@ -3,8 +3,8 @@ namespace Chill\PersonBundle\Repository\SocialWork; use Chill\PersonBundle\Entity\SocialWork\SocialAction; -use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; -use Doctrine\Persistence\ManagerRegistry; +use Doctrine\ORM\EntityManagerInterface; +use Doctrine\ORM\EntityRepository; /** * @method SocialAction|null find($id, $lockMode = null, $lockVersion = null) @@ -12,10 +12,12 @@ use Doctrine\Persistence\ManagerRegistry; * @method SocialAction[] findAll() * @method SocialAction[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null) */ -class SocialActionRepository extends ServiceEntityRepository +final class SocialActionRepository { - public function __construct(ManagerRegistry $registry) + private EntityRepository $repository; + + public function __construct(EntityManagerInterface $entityManager) { - parent::__construct($registry, SocialAction::class); + $this->repository = $entityManager->getRepository(SocialAction::class); } } diff --git a/src/Bundle/ChillPersonBundle/Repository/SocialWork/SocialIssueRepository.php b/src/Bundle/ChillPersonBundle/Repository/SocialWork/SocialIssueRepository.php index e64b057f4..2f6eb3ba1 100644 --- a/src/Bundle/ChillPersonBundle/Repository/SocialWork/SocialIssueRepository.php +++ b/src/Bundle/ChillPersonBundle/Repository/SocialWork/SocialIssueRepository.php @@ -4,7 +4,8 @@ namespace Chill\PersonBundle\Repository\SocialWork; use Chill\PersonBundle\Entity\SocialWork\SocialIssue; use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; -use Doctrine\Persistence\ManagerRegistry; +use Doctrine\ORM\EntityManagerInterface; +use Doctrine\ORM\EntityRepository; /** * @method SocialIssue|null find($id, $lockMode = null, $lockVersion = null) @@ -12,10 +13,12 @@ use Doctrine\Persistence\ManagerRegistry; * @method SocialIssue[] findAll() * @method SocialIssue[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null) */ -class SocialIssueRepository extends ServiceEntityRepository +final class SocialIssueRepository { - public function __construct(ManagerRegistry $registry) + private EntityRepository $repository; + + public function __construct(EntityManagerInterface $entityManager) { - parent::__construct($registry, SocialIssue::class); + $this->repository = $entityManager->getRepository(SocialIssue::class); } } diff --git a/src/Bundle/ChillPersonBundle/Resources/public/js/AccompanyingCourse/App.vue b/src/Bundle/ChillPersonBundle/Resources/public/js/AccompanyingCourse/App.vue deleted file mode 100644 index 971eca1c7..000000000 --- a/src/Bundle/ChillPersonBundle/Resources/public/js/AccompanyingCourse/App.vue +++ /dev/null @@ -1,45 +0,0 @@ - - - - - diff --git a/src/Bundle/ChillPersonBundle/Resources/public/js/AccompanyingCourse/components/AccompanyingCourse.vue b/src/Bundle/ChillPersonBundle/Resources/public/js/AccompanyingCourse/components/AccompanyingCourse.vue deleted file mode 100644 index d08798c00..000000000 --- a/src/Bundle/ChillPersonBundle/Resources/public/js/AccompanyingCourse/components/AccompanyingCourse.vue +++ /dev/null @@ -1,26 +0,0 @@ - - - diff --git a/src/Bundle/ChillPersonBundle/Resources/public/js/AccompanyingCourse/components/PersonItem.vue b/src/Bundle/ChillPersonBundle/Resources/public/js/AccompanyingCourse/components/PersonItem.vue deleted file mode 100644 index f75f0779b..000000000 --- a/src/Bundle/ChillPersonBundle/Resources/public/js/AccompanyingCourse/components/PersonItem.vue +++ /dev/null @@ -1,25 +0,0 @@ - - - diff --git a/src/Bundle/ChillPersonBundle/Resources/public/js/AccompanyingCourse/components/PersonsAssociated.vue b/src/Bundle/ChillPersonBundle/Resources/public/js/AccompanyingCourse/components/PersonsAssociated.vue deleted file mode 100644 index f4ccf6964..000000000 --- a/src/Bundle/ChillPersonBundle/Resources/public/js/AccompanyingCourse/components/PersonsAssociated.vue +++ /dev/null @@ -1,69 +0,0 @@ - - - diff --git a/src/Bundle/ChillPersonBundle/Resources/public/js/AccompanyingCourse/components/Requestor.vue b/src/Bundle/ChillPersonBundle/Resources/public/js/AccompanyingCourse/components/Requestor.vue deleted file mode 100644 index 75a95e241..000000000 --- a/src/Bundle/ChillPersonBundle/Resources/public/js/AccompanyingCourse/components/Requestor.vue +++ /dev/null @@ -1,16 +0,0 @@ - - - diff --git a/src/Bundle/ChillPersonBundle/Resources/public/js/AccompanyingCourse/index.js b/src/Bundle/ChillPersonBundle/Resources/public/js/AccompanyingCourse/index.js deleted file mode 100644 index 4335113f7..000000000 --- a/src/Bundle/ChillPersonBundle/Resources/public/js/AccompanyingCourse/index.js +++ /dev/null @@ -1,8 +0,0 @@ -import App from './App.vue'; -import { createApp } from 'vue'; - -const app = createApp({ - template: `` -}) -.component('app', App) -.mount('#accompanying-course'); diff --git a/src/Bundle/ChillPersonBundle/Resources/public/js/AccompanyingCourse/store/.keep b/src/Bundle/ChillPersonBundle/Resources/public/js/AccompanyingCourse/store/.keep deleted file mode 100644 index e69de29bb..000000000 diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/App.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/App.vue new file mode 100644 index 000000000..480b882dc --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/App.vue @@ -0,0 +1,25 @@ + + + diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/api.js b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/api.js new file mode 100644 index 000000000..0f71f7170 --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/api.js @@ -0,0 +1,49 @@ +const + locale = 'fr', + format = 'json' + , accompanying_period_id = window.accompanyingCourseId //tmp +; + +/* +* Endpoint chill_person_accompanying_course_api_show +* method GET, get AccompanyingCourse Object +* +* @accompanying_period_id___ integer +* @TODO var is not used but necessary in method signature +*/ +let getAccompanyingCourse = (accompanying_period_id___) => { //tmp + const url = `/${locale}/person/api/1.0/accompanying-course/${accompanying_period_id}/show.${format}`; + return fetch(url) + .then(response => { + if (response.ok) { return response.json(); } + throw Error('Error with request resource response'); + }); +}; + +/* +* Endpoint chill_person_accompanying_course_api_add_participation, +* method POST/DELETE, add/close a participation to the accompanyingCourse +* +* @accompanying_period_id integer - id of accompanyingCourse +* @person_id integer - id of person +* @method string - POST or DELETE +*/ +let postParticipation = (accompanying_period_id, person_id, method) => { + const url = `/${locale}/person/api/1.0/accompanying-course/${accompanying_period_id}/participation.${format}` + return fetch(url, { + method: method, + headers: { + 'Content-Type': 'application/json;charset=utf-8' + }, + body: JSON.stringify({id: person_id}) + }) + .then(response => { + if (response.ok) { return response.json(); } + throw Error('Error with request resource response'); + }); +}; + +export { + getAccompanyingCourse, + postParticipation +}; diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/AccompanyingCourse.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/AccompanyingCourse.vue new file mode 100644 index 000000000..be3014ddb --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/AccompanyingCourse.vue @@ -0,0 +1,28 @@ + + + diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/PersonItem.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/PersonItem.vue new file mode 100644 index 000000000..0d81d61c4 --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/PersonItem.vue @@ -0,0 +1,56 @@ + + + diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/PersonsAssociated.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/PersonsAssociated.vue new file mode 100644 index 000000000..631534fcb --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/PersonsAssociated.vue @@ -0,0 +1,66 @@ + + + diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Requestor.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Requestor.vue new file mode 100644 index 000000000..6086ab515 --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Requestor.vue @@ -0,0 +1,86 @@ + + + diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/index.js b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/index.js new file mode 100644 index 000000000..66b6d6683 --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/index.js @@ -0,0 +1,23 @@ +import { createApp } from 'vue' +import { _createI18n } from 'ChillMainAssets/vuejs/_js/i18n' +import { appMessages } from './js/i18n' +import { initPromise } from './store' + +import App from './App.vue'; + +initPromise.then(store => { + + //console.log('store in create_store', store); + //console.log('store accompanyingCourse', store.state.accompanyingCourse); + + const i18n = _createI18n(appMessages); + + const app = createApp({ + template: ``, + }) + .use(store) + .use(i18n) + .component('app', App) + .mount('#accompanying-course'); + +}); diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/js/i18n.js b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/js/i18n.js new file mode 100644 index 000000000..045476686 --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/js/i18n.js @@ -0,0 +1,32 @@ +import { personMessages } from 'ChillPersonAssets/vuejs/_js/i18n' + +const appMessages = { + fr: { + course: { + id: "id", + title: "Parcours", + opening_date: "Date d'ouverture", + closing_date: "Date de clôture", + remark: "Commentaire", + closing_motive: "Motif de clôture", + }, + persons_associated: { + title: "Usagers concernés", + counter: "Pas d'usager | 1 usager | {count} usagers", + firstname: "Prénom", + lastname: "Nom", + startdate: "Date d'entrée", + enddate: "Date de sortie", + addPerson: "Ajouter un usager", + }, + requestor: { + title: "Demandeur", + }, + } +}; + +Object.assign(appMessages.fr, personMessages.fr); + +export { + appMessages +}; diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/store/index.js b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/store/index.js new file mode 100644 index 000000000..04785eca5 --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/store/index.js @@ -0,0 +1,76 @@ +import 'es6-promise/auto'; +import { createStore } from 'vuex'; +import addPersons from './modules/addPersons' +import { getAccompanyingCourse, postParticipation } from '../api'; + +const debug = process.env.NODE_ENV !== 'production'; + +const id = window.accompanyingCourseId; //tmp + +let initPromise = getAccompanyingCourse(id) + .then(accompanying_course => new Promise((resolve, reject) => { + + const store = createStore({ + strict: debug, + modules: { + addPersons + }, + state: { + accompanyingCourse: accompanying_course, + errorMsg: [] + }, + getters: { + }, + mutations: { + removeParticipation(state, item) { + //console.log('mutation: remove item', item.id); + state.accompanyingCourse.participations = state.accompanyingCourse.participations.filter(participation => participation !== item); + }, + closeParticipation(state, { participation, payload }) { + console.log('### mutation: close item', { participation, payload }); + // trouve dans le state le payload et le supprime du state + state.accompanyingCourse.participations = state.accompanyingCourse.participations.filter(participation => participation !== payload); + // pousse la participation + state.accompanyingCourse.participations.push(participation); + }, + addParticipation(state, participation) { + //console.log('### mutation: add participation', participation); + state.accompanyingCourse.participations.push(participation); + }, + }, + actions: { + removeParticipation({ commit }, payload) { + commit('removeParticipation', payload); + }, + closeParticipation({ commit }, payload) { + //console.log('## action: fetch delete participation: payload', payload.person.id); + postParticipation(id, payload.person.id, 'DELETE') + .then(participation => new Promise((resolve, reject) => { + //console.log('payload', payload); + commit('closeParticipation', { participation, payload }); + resolve(); + })) + .catch((error) => { + state.errorMsg.push(error.message); + }); + }, + addParticipation(addPersons, payload) { + //console.log('## action: fetch post participation: payload', payload.id); + postParticipation(id, payload.id, 'POST') + .then(participation => new Promise((resolve, reject) => { + //console.log(participation, payload); + addPersons.commit('addParticipation', participation); + addPersons.commit('resetState', payload); + resolve(); + })) + .catch((error) => { + state.errorMsg.push(error.message); + }); + }, + } + }); + //console.log('store object', store.state.accompanyingCourse.id); + resolve(store); + })); + +export { initPromise }; diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/store/modules/addPersons.js b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/store/modules/addPersons.js new file mode 100644 index 000000000..e65f94fa9 --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/store/modules/addPersons.js @@ -0,0 +1,76 @@ +import { searchPersons } from 'ChillPersonAssets/vuejs/_api/AddPersons' +import { postParticipation } from '../../api'; + + +// initial state +const state = { + query: "", + suggested: [], + selected: [] +} + +// getters +const getters = { + selectedAndSuggested: state => { + const uniqBy = (a, key) => [ + ...new Map( + a.map(x => [key(x), x]) + ).values() + ]; + let union = [...new Set([ + ...state.suggested.slice().reverse(), + ...state.selected.slice().reverse(), + ])]; + return uniqBy(union, k => k.id); + } +} + +// mutations +const mutations = { + setQuery(state, query) { + //console.log('q=', query); + state.query = query; + }, + loadSuggestions(state, suggested) { + state.suggested = suggested; + }, + updateSelected(state, value) { + state.selected = value; + }, + resetState(state, selected) { + //console.log('avant', state.selected); + state.selected = state.selected.filter(value => value !== selected); + //console.log('après', state.selected); + state.query = ""; + state.suggested = []; + } +} + +// actions +const actions = { + setQuery({ commit }, payload) { + //console.log('## action: setquery: payload', payload); + commit('setQuery', payload.query); + if (payload.query.length >= 3) { + searchPersons(payload.query) + .then(suggested => new Promise((resolve, reject) => { + commit('loadSuggestions', suggested.results); + resolve(); + })); + } else { + commit('loadSuggestions', []); + } + }, + updateSelected({ commit }, payload) { + //console.log('## action: update selected values: payload', payload); + commit('updateSelected', payload); + } +} + +export default { + //namespaced: true, + state, + getters, + actions, + mutations +} diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_api/AddPersons.js b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_api/AddPersons.js new file mode 100644 index 000000000..d5ac91ac5 --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_api/AddPersons.js @@ -0,0 +1,20 @@ +const + locale = 'fr', + format = 'json' +; + +/* +* Endpoint chill_person_search, method GET, get a list of persons +* +* @query string - the query to search for +*/ +let searchPersons = (query) => { + let url = `/${locale}/search.${format}?name=person_regular&q=${query}`; + return fetch(url) + .then(response => { + if (response.ok) { return response.json(); } + throw Error('Error with request resource response'); + }); +}; + +export { searchPersons }; diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AddPersons.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AddPersons.vue new file mode 100644 index 000000000..53600da1c --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AddPersons.vue @@ -0,0 +1,132 @@ + + + diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/PersonSuggestion.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/PersonSuggestion.vue new file mode 100644 index 000000000..f2561b0af --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/PersonSuggestion.vue @@ -0,0 +1,53 @@ + + + diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_js/i18n.js b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_js/i18n.js new file mode 100644 index 000000000..34044441e --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_js/i18n.js @@ -0,0 +1,21 @@ +const personMessages = { + fr: { + add_persons: { + search_add_others_persons: "Rechercher et ajouter d'autres usagers", + title: "Ajouter des usagers", + suggested_counter: "Pas de résultats | 1 résultat | {count} résultats", + selected_counter: " 1 sélectionné | {count} sélectionnés", + search_some_persons: "Rechercher des personnes..", + }, + item: { + type_person: "Usager", + type_tms: "TMS", + type_3rdparty: "Tiers", + type_menage: "Ménage" + } + } +}; + +export { + personMessages +}; diff --git a/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingCourse/show.html.twig b/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingCourse/show.html.twig index a33b65cff..427f5f0ac 100644 --- a/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingCourse/show.html.twig +++ b/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingCourse/show.html.twig @@ -5,15 +5,13 @@ {% endblock %} {% block content %} -

{{ block('title') }}

-
+{% endblock %} - {{ encore_entry_script_tags('accompanying_course') }} - +{% block js %} - + {{ encore_entry_script_tags('accompanying_course') }} {% endblock %} diff --git a/src/Bundle/ChillPersonBundle/Resources/views/Address/edit.html.twig b/src/Bundle/ChillPersonBundle/Resources/views/Address/edit.html.twig index eea76b5fe..b516dfb83 100644 --- a/src/Bundle/ChillPersonBundle/Resources/views/Address/edit.html.twig +++ b/src/Bundle/ChillPersonBundle/Resources/views/Address/edit.html.twig @@ -21,17 +21,17 @@ {% block title 'Update address for %name%'|trans({ '%name%': person.firstName ~ ' ' ~ person.lastName } ) %} {% block personcontent %} - +

{{ 'Update address for %name%'|trans({ '%name%': person.firstName ~ ' ' ~ person.lastName } ) }}

- + {{ form_start(form) }} - + {{ form_row(form.isNoAddress) }} - {{ form_row(form.streetAddress1) }} - {{ form_row(form.streetAddress2) }} + {{ form_row(form.street) }} + {{ form_row(form.streetNumber) }} {{ form_row(form.postCode) }} {{ form_row(form.validFrom) }} - + - + {{ form_end(form) }} - -{% endblock personcontent %} \ No newline at end of file + +{% endblock personcontent %} diff --git a/src/Bundle/ChillPersonBundle/Resources/views/Address/list.html.twig b/src/Bundle/ChillPersonBundle/Resources/views/Address/list.html.twig index 9e91e486c..8a2aeee42 100644 --- a/src/Bundle/ChillPersonBundle/Resources/views/Address/list.html.twig +++ b/src/Bundle/ChillPersonBundle/Resources/views/Address/list.html.twig @@ -23,9 +23,9 @@ {% block title %}{{ 'Addresses\'history for %name%'|trans({ '%name%': person.firstName ~ ' ' ~ person.lastName } ) }}{% endblock %} {% block personcontent %} - +

{{ 'Addresses\'history for %name%'|trans({ '%name%': person.firstName ~ ' ' ~ person.lastName } ) }}

- + @@ -48,11 +48,11 @@ {% for address in person.addresses %} - + - + + {% endfor %} {% endif %}
{{ 'Since %date%'|trans( { '%date%' : address.validFrom|format_date('long') } ) }} {{ address_macros._render(address, { 'with_valid_from' : false, 'has_no_address': true } ) }}
  • @@ -61,11 +61,17 @@
- + + + + + +
- -{% endblock personcontent %} \ No newline at end of file + +{% endblock personcontent %} diff --git a/src/Bundle/ChillPersonBundle/Resources/views/Address/new.html.twig b/src/Bundle/ChillPersonBundle/Resources/views/Address/new.html.twig index 9e94d7804..70cc51e53 100644 --- a/src/Bundle/ChillPersonBundle/Resources/views/Address/new.html.twig +++ b/src/Bundle/ChillPersonBundle/Resources/views/Address/new.html.twig @@ -21,21 +21,21 @@ {% block title %}{{ 'New address for %name%'|trans({ '%name%': person.firstName|capitalize ~ ' ' ~ person.lastName } )|capitalize }}{% endblock %} {% block personcontent %} - +

{{ 'New address for %name%'|trans({ '%name%': person.firstName ~ ' ' ~ person.lastName } ) }}

- + {{ form_start(form) }} - + {{ form_row(form.isNoAddress) }} - {{ form_row(form.streetAddress1) }} - {{ form_errors(form.streetAddress1) }} - {{ form_row(form.streetAddress2) }} - {{ form_errors(form.streetAddress2) }} + {{ form_row(form.street) }} + {{ form_errors(form.street) }} + {{ form_row(form.streetNumber) }} + {{ form_errors(form.streetNumber) }} {{ form_row(form.postCode) }} {{ form_errors(form.postCode) }} {{ form_row(form.validFrom) }} {{ form_errors(form.validFrom) }} - + - + {{ form_end(form) }} - -{% endblock personcontent %} \ No newline at end of file + +{% endblock personcontent %} diff --git a/src/Bundle/ChillPersonBundle/Search/SimilarPersonMatcher.php b/src/Bundle/ChillPersonBundle/Search/SimilarPersonMatcher.php index 956f9a4d2..1c564aa11 100644 --- a/src/Bundle/ChillPersonBundle/Search/SimilarPersonMatcher.php +++ b/src/Bundle/ChillPersonBundle/Search/SimilarPersonMatcher.php @@ -22,12 +22,13 @@ use Chill\PersonBundle\Entity\PersonNotDuplicate; use Doctrine\ORM\EntityManagerInterface; use Chill\PersonBundle\Entity\Person; use Chill\MainBundle\Security\Authorization\AuthorizationHelper; +use Chill\PersonBundle\Repository\PersonNotDuplicateRepository; use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; use Symfony\Component\Security\Core\Role\Role; use Chill\PersonBundle\Security\Authorization\PersonVoter; /** - * + * * * @author Julien Fastré */ @@ -41,20 +42,20 @@ class SimilarPersonMatcher * @var EntityManagerInterface */ protected $em; - + /** * @var AuthorizationHelper */ protected $authorizationHelper; - + /** - * @var TokenStorageInterface + * @var TokenStorageInterface */ protected $tokenStorage; - + public function __construct( - EntityManagerInterface $em, - AuthorizationHelper $authorizationHelper, + EntityManagerInterface $em, + AuthorizationHelper $authorizationHelper, TokenStorageInterface $tokenStorage ) { $this->em = $em; @@ -62,7 +63,7 @@ class SimilarPersonMatcher $this->tokenStorage = $tokenStorage; } - public function matchPerson(Person $person, $precision = 0.15, $orderBy = self::SIMILAR_SEARCH_ORDER_BY_SIMILARITY) + public function matchPerson(Person $person, PersonNotDuplicateRepository $personNotDuplicateRepository, $precision = 0.15, $orderBy = self::SIMILAR_SEARCH_ORDER_BY_SIMILARITY) { $centers = $this->authorizationHelper->getReachableCenters( $this->tokenStorage->getToken()->getUser(), @@ -77,8 +78,7 @@ class SimilarPersonMatcher . ' AND p.id != :personId ' ; - $notDuplicatePersons = $this->em->getRepository(PersonNotDuplicate::class) - ->findNotDuplicatePerson($person); + $notDuplicatePersons = $personNotDuplicateRepository->findNotDuplicatePerson($person); if (count($notDuplicatePersons)) { $dql .= ' AND p.id not in (:notDuplicatePersons)'; diff --git a/src/Bundle/ChillPersonBundle/Security/Authorization/AccompanyingPeriodVoter.php b/src/Bundle/ChillPersonBundle/Security/Authorization/AccompanyingPeriodVoter.php new file mode 100644 index 000000000..ca92c6d7c --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Security/Authorization/AccompanyingPeriodVoter.php @@ -0,0 +1,73 @@ +helper = $helper; + } + + protected function supports($attribute, $subject) + { + return $subject instanceof AccompanyingPeriod; + } + + protected function voteOnAttribute($attribute, $subject, TokenInterface $token) + { + if (!$token->getUser() instanceof User) { + return false; + } + + // TODO take scopes into account + foreach ($subject->getPersons() as $person) { + // give access as soon as on center is reachable + if ($this->helper->userHasAccess($token->getUser(), $person->getCenter(), $attribute)) { + return true; + } + + return false; + } + } + + private function getAttributes() + { + return [ + self::SEE + ]; + } + + public function getRoles() + { + return $this->getAttributes(); + } + + public function getRolesWithoutScope() + { + return []; + } + + public function getRolesWithHierarchy() + { + return [ 'Person' => $this->getRoles() ]; + } + +} diff --git a/src/Bundle/ChillPersonBundle/Serializer/Normalizer/PersonNormalizer.php b/src/Bundle/ChillPersonBundle/Serializer/Normalizer/PersonNormalizer.php index d6bcc4e96..d88d27ddc 100644 --- a/src/Bundle/ChillPersonBundle/Serializer/Normalizer/PersonNormalizer.php +++ b/src/Bundle/ChillPersonBundle/Serializer/Normalizer/PersonNormalizer.php @@ -55,7 +55,7 @@ class PersonNormalizer implements 'id' => $person->getId(), 'firstName' => $person->getFirstName(), 'lastName' => $person->getLastName(), - 'birthdate' => $person->getBirthdate() ? $this->normalizer->normalize($person->getBirthdate()) : null, + 'birthdate' => $this->normalizer->normalize($person->getBirthdate()), 'center' => $this->normalizer->normalize($person->getCenter()) ]; } diff --git a/src/Bundle/ChillPersonBundle/Tests/Controller/AccompanyingCourseControllerTest.php b/src/Bundle/ChillPersonBundle/Tests/Controller/AccompanyingCourseApiControllerTest.php similarity index 71% rename from src/Bundle/ChillPersonBundle/Tests/Controller/AccompanyingCourseControllerTest.php rename to src/Bundle/ChillPersonBundle/Tests/Controller/AccompanyingCourseApiControllerTest.php index c45cb93d9..61b5307a4 100644 --- a/src/Bundle/ChillPersonBundle/Tests/Controller/AccompanyingCourseControllerTest.php +++ b/src/Bundle/ChillPersonBundle/Tests/Controller/AccompanyingCourseApiControllerTest.php @@ -36,7 +36,7 @@ use Symfony\Component\HttpFoundation\Request; /** * Test api for AccompanyingCourseControllerTest */ -class AccompanyingCourseControllerTest extends WebTestCase +class AccompanyingCourseApiControllerTest extends WebTestCase { protected static EntityManagerInterface $em; @@ -65,7 +65,7 @@ class AccompanyingCourseControllerTest extends WebTestCase */ public function testAccompanyingCourseShow(int $personId, AccompanyingPeriod $period) { - $this->client->request(Request::METHOD_GET, sprintf('/fr/person/api/1.0/accompanying-course/%d/show.json', $period->getId())); + $c = $this->client->request(Request::METHOD_GET, sprintf('/api/1.0/person/accompanying-course/%d.json', $period->getId())); $response = $this->client->getResponse(); $this->assertEquals(200, $response->getStatusCode(), "Test that the response of rest api has a status code ok (200)"); @@ -77,6 +77,14 @@ class AccompanyingCourseControllerTest extends WebTestCase $this->assertGreaterThan(0, $data->participations); } + public function testShow404() + { + $this->client->request(Request::METHOD_GET, sprintf('/api/1.0/person/accompanying-course/%d.json', 99999)); + $response = $this->client->getResponse(); + + $this->assertEquals(404, $response->getStatusCode(), "Test that the response of rest api has a status code 'not found' (404)"); + } + /** * * @dataProvider dataGenerateRandomAccompanyingCourse @@ -85,26 +93,55 @@ class AccompanyingCourseControllerTest extends WebTestCase { $this->client->request( Request::METHOD_POST, - sprintf('/fr/person/api/1.0/accompanying-course/%d/participation.json', $period->getId()), + sprintf('/api/1.0/person/accompanying-course/%d/participation.json', $period->getId()), [], // parameters [], // files [], // server parameters \json_encode([ 'id' => $personId ]) ); $response = $this->client->getResponse(); + $data = \json_decode($response->getContent(), true); $this->assertEquals(200, $response->getStatusCode(), "Test that the response of rest api has a status code ok (200)"); - $this->client->request(Request::METHOD_GET, sprintf('/fr/person/api/1.0/accompanying-course/%d/show.json', $period->getId())); + $this->assertArrayHasKey('id', $data); + $this->assertArrayHasKey('startDate', $data); + $this->assertNotNull($data['startDate']); + + // check by deownloading the accompanying cours + + $this->client->request(Request::METHOD_GET, sprintf('/api/1.0/person/accompanying-course/%d.json', $period->getId())); $response = $this->client->getResponse(); $data = \json_decode($response->getContent()); + // check that the person id is contained $participationsPersonsIds = \array_map( function($participation) { return $participation->person->id; }, $data->participations); $this->assertContains($personId, $participationsPersonsIds); + // check removing the participation + $this->client->request( + Request::METHOD_DELETE, + sprintf('/api/1.0/person/accompanying-course/%d/participation.json', $period->getId()), + [], // parameters + [], // files + [], // server parameters + \json_encode([ 'id' => $personId ]) + ); + $response = $this->client->getResponse(); + $data = \json_decode($response->getContent(), true); + + $this->assertEquals(200, $response->getStatusCode(), "Test that the response of rest api has a status code ok (200)"); + $this->assertArrayHasKey('id', $data); + $this->assertArrayHasKey('startDate', $data); + $this->assertNotNull($data['startDate']); + $this->assertArrayHasKey('endDate', $data); + $this->assertNotNull($data['endDate']); + + + // set to variable for tear down $this->personId = $personId; $this->period = $period; } @@ -112,6 +149,7 @@ class AccompanyingCourseControllerTest extends WebTestCase protected function tearDown() { // remove participation created during test 'testAccompanyingCourseAddParticipation' + // and if the test could not remove it $testAddParticipationName = 'testAccompanyingCourseAddParticipation'; @@ -126,8 +164,10 @@ class AccompanyingCourseControllerTest extends WebTestCase ->findOneBy(['person' => $this->personId, 'accompanyingPeriod' => $this->period]) ; - $em->remove($participation); - $em->flush(); + if (NULL !== $participation) { + $em->remove($participation); + $em->flush(); + } } public function dataGenerateRandomAccompanyingCourse() @@ -139,9 +179,9 @@ class AccompanyingCourseControllerTest extends WebTestCase // * one for getting the person, which will in turn provide his accompanying period; // * one for getting the personId to populate to the data manager // - // Ensure to keep always $maxGenerated to the double of $maxResults - $maxGenerated = 1; - $maxResults = 15 * 8; + // Ensure to keep always $maxGenerated to the double of $maxResults. x8 is a good compromize :) + $maxGenerated = 3; + $maxResults = $maxGenerated * 8; static::bootKernel(); $em = static::$container->get(EntityManagerInterface::class); diff --git a/src/Bundle/ChillPersonBundle/Tests/Controller/PersonControllerCreateTest.php b/src/Bundle/ChillPersonBundle/Tests/Controller/PersonControllerCreateTest.php index c49c0ea97..d1381c241 100644 --- a/src/Bundle/ChillPersonBundle/Tests/Controller/PersonControllerCreateTest.php +++ b/src/Bundle/ChillPersonBundle/Tests/Controller/PersonControllerCreateTest.php @@ -25,6 +25,7 @@ namespace Chill\PersonBundle\Tests\Controller; use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; use Symfony\Component\DomCrawler\Form; use Chill\MainBundle\Test\PrepareClientTrait; +use \Symfony\Component\BrowserKit\Client; /** * Test creation and deletion for persons @@ -32,7 +33,9 @@ use Chill\MainBundle\Test\PrepareClientTrait; class PersonControllerCreateTest extends WebTestCase { use PrepareClientTrait; - + + private Client $client; + const FIRSTNAME_INPUT = 'chill_personbundle_person_creation[firstName]'; const LASTNAME_INPUT = "chill_personbundle_person_creation[lastName]"; const GENDER_INPUT = "chill_personbundle_person_creation[gender]"; @@ -42,18 +45,20 @@ class PersonControllerCreateTest extends WebTestCase const LONG_TEXT = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. Sed cursus ante dapibus diam. Sed nisi. Nulla quis sem at nibh elementum imperdiet. Duis sagittis ipsum. Praesent mauris. Fusce nec tellus sed augue semper porta. Mauris massa. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosq. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. Sed cursus ante dapibus diam. Sed nisi. Nulla quis sem at nibh elementum imperdiet. Duis sagittis ipsum. Praesent mauris. Fusce nec tellus sed augue semper porta.Mauris massa. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosq."; - public function setUp() + public function setUp(): void { - $this->client = $this::getClientAuthenticated(); + $this->client = $this->getClientAuthenticated(); } /** * * @param Form $creationForm */ - private function fillAValidCreationForm(Form &$creationForm, - $firstname = 'God', $lastname = 'Jesus') - { + private function fillAValidCreationForm( + Form &$creationForm, + string $firstname = 'God', + string $lastname = 'Jesus' + ) { $creationForm->get(self::FIRSTNAME_INPUT)->setValue($firstname); $creationForm->get(self::LASTNAME_INPUT)->setValue($lastname); $creationForm->get(self::GENDER_INPUT)->select("man"); diff --git a/src/Bundle/ChillPersonBundle/Tests/Entity/AccompanyingPeriodTest.php b/src/Bundle/ChillPersonBundle/Tests/Entity/AccompanyingPeriodTest.php index a31055a0d..03fddab41 100644 --- a/src/Bundle/ChillPersonBundle/Tests/Entity/AccompanyingPeriodTest.php +++ b/src/Bundle/ChillPersonBundle/Tests/Entity/AccompanyingPeriodTest.php @@ -27,7 +27,6 @@ use Chill\PersonBundle\Entity\Person; class AccompanyingPeriodTest extends \PHPUnit\Framework\TestCase { - public function testClosingIsAfterOpeningConsistency() { $datetime1 = new \DateTime('now'); @@ -77,22 +76,32 @@ class AccompanyingPeriodTest extends \PHPUnit\Framework\TestCase $this->assertFalse($period->isOpen()); } - public function testCanBeReOpened() + public function testPersonPeriod() { - $person = new Person(\DateTime::createFromFormat('Y-m-d', '2010-01-01')); - $person->close($person->getAccompanyingPeriods()[0] - ->setClosingDate(\DateTime::createFromFormat('Y-m-d', '2010-12-31'))); - - $firstAccompanygingPeriod = $person->getAccompanyingPeriodsOrdered()[0]; - - $this->assertTrue($firstAccompanygingPeriod->canBeReOpened()); - - $lastAccompanyingPeriod = (new AccompanyingPeriod(\DateTime::createFromFormat('Y-m-d', '2011-01-01'))) - ->setClosingDate(\DateTime::createFromFormat('Y-m-d', '2011-12-31')) - ; - $person->addAccompanyingPeriod($lastAccompanyingPeriod); - - $this->assertFalse($firstAccompanygingPeriod->canBeReOpened()); - } + $person = new Person(); + $person2 = new Person(); + $person3 = new Person(); + $period = new AccompanyingPeriod(new \DateTime()); + $period->addPerson($person); + $period->addPerson($person2); + $period->addPerson($person3); + + $this->assertEquals(3, $period->getParticipations()->count()); + $this->assertTrue($period->containsPerson($person)); + $this->assertFalse($period->containsPerson(new Person())); + + $participation = $period->getOpenParticipationContainsPerson($person); + $participations = $period->getParticipationsContainsPerson($person); + $this->assertNotNull($participation); + $this->assertSame($person, $participation->getPerson()); + $this->assertEquals(1, $participations->count()); + + $participationL = $period->removePerson($person); + $this->assertSame($participationL, $participation); + $this->assertTrue($participation->getEndDate() instanceof \DateTimeInterface); + + $participation = $period->getOpenParticipationContainsPerson($person); + $this->assertNull($participation); + } } diff --git a/src/Bundle/ChillPersonBundle/Widget/PersonListWidget.php b/src/Bundle/ChillPersonBundle/Widget/PersonListWidget.php index e885e0a26..2dcbb8230 100644 --- a/src/Bundle/ChillPersonBundle/Widget/PersonListWidget.php +++ b/src/Bundle/ChillPersonBundle/Widget/PersonListWidget.php @@ -45,7 +45,7 @@ class PersonListWidget implements WidgetInterface /** * Repository for persons * - * @var EntityRepository + * @var PersonRepository */ protected $personRepository; @@ -76,7 +76,7 @@ class PersonListWidget implements WidgetInterface protected $user; public function __construct( - EntityRepository $personRepostory, + PersonRepository $personRepostory, EntityManager $em, AuthorizationHelper $authorizationHelper, TokenStorage $tokenStorage diff --git a/src/Bundle/ChillPersonBundle/chill.webpack.config.js b/src/Bundle/ChillPersonBundle/chill.webpack.config.js index 9d7a33d02..53aed5d91 100644 --- a/src/Bundle/ChillPersonBundle/chill.webpack.config.js +++ b/src/Bundle/ChillPersonBundle/chill.webpack.config.js @@ -8,5 +8,5 @@ module.exports = function(encore, entries) ChillPersonAssets: __dirname + '/Resources/public' }); - encore.addEntry('accompanying_course', __dirname + '/Resources/public/js/AccompanyingCourse/index.js'); + encore.addEntry('accompanying_course', __dirname + '/Resources/public/vuejs/AccompanyingCourse/index.js'); }; diff --git a/src/Bundle/ChillPersonBundle/config/services.yaml b/src/Bundle/ChillPersonBundle/config/services.yaml index bccb69b0d..23aca6f35 100644 --- a/src/Bundle/ChillPersonBundle/config/services.yaml +++ b/src/Bundle/ChillPersonBundle/config/services.yaml @@ -1,8 +1,10 @@ parameters: # cl_chill_person.example.class: Chill\PersonBundle\Example -services: - +services: + _defaults: + autowire: true + autoconfigure: true chill.person.form.type.select2maritalstatus: class: Chill\PersonBundle\Form\Type\Select2MaritalStatusType @@ -28,17 +30,13 @@ services: tags: - { name: chill.timeline, context: 'person' } - chill.person.security.authorization.person: - class: Chill\PersonBundle\Security\Authorization\PersonVoter - arguments: - - "@chill.main.security.authorization.helper" - tags: - - { name: security.voter } - - { name: chill.role } - chill.person.birthdate_validation: class: Chill\PersonBundle\Validator\Constraints\BirthdateValidator arguments: - "%chill_person.validation.birtdate_not_before%" tags: - { name: validator.constraint_validator, alias: birthdate_not_before } + + Chill\PersonBundle\Repository\: + resource: '../Repository/' + tags: ['doctrine.repository_service'] diff --git a/src/Bundle/ChillPersonBundle/config/services/controller.yaml b/src/Bundle/ChillPersonBundle/config/services/controller.yaml index dc575f320..41a5ac719 100644 --- a/src/Bundle/ChillPersonBundle/config/services/controller.yaml +++ b/src/Bundle/ChillPersonBundle/config/services/controller.yaml @@ -46,3 +46,9 @@ services: $dispatcher: '@Symfony\Contracts\EventDispatcher\EventDispatcherInterface' $validator: '@Symfony\Component\Validator\Validator\ValidatorInterface' tags: ['controller.service_arguments'] + + Chill\PersonBundle\Controller\AccompanyingCourseApiController: + arguments: + $eventDispatcher: '@Symfony\Contracts\EventDispatcher\EventDispatcherInterface' + $validator: '@Symfony\Component\Validator\Validator\ValidatorInterface' + tags: ['controller.service_arguments'] diff --git a/src/Bundle/ChillPersonBundle/config/services/repository.yaml b/src/Bundle/ChillPersonBundle/config/services/repository.yaml index e899ba9e1..e5ce06149 100644 --- a/src/Bundle/ChillPersonBundle/config/services/repository.yaml +++ b/src/Bundle/ChillPersonBundle/config/services/repository.yaml @@ -2,24 +2,6 @@ services: chill.person.repository.person: class: Chill\PersonBundle\Repository\PersonRepository - factory: ['@doctrine.orm.entity_manager', getRepository] - arguments: - - 'Chill\PersonBundle\Entity\Person' + autowire: true + autoconfigure: true Chill\PersonBundle\Repository\PersonRepository: '@chill.person.repository.person' - - Chill\PersonBundle\Repository\AccompanyingPeriod\ClosingMotiveRepository: - class: Chill\PersonBundle\Repository\AccompanyingPeriod\ClosingMotiveRepository - factory: ['@doctrine.orm.entity_manager', getRepository] - arguments: - - 'Chill\PersonBundle\Entity\AccompanyingPeriod\ClosingMotive' - - Chill\PersonBundle\Repository\AccompanyingPeriodRepository: - class: Chill\PersonBundle\Repository\AccompanyingPeriodRepository - tags: [ doctrine.repository_service ] - arguments: - - '@Doctrine\Persistence\ManagerRegistry' - - Chill\PersonBundle\Repository\AccompanyingPeriodParticipationRepository: - arguments: - - '@Doctrine\Persistence\ManagerRegistry' - tags: [ doctrine.repository_service ] diff --git a/src/Bundle/ChillPersonBundle/config/services/security.yaml b/src/Bundle/ChillPersonBundle/config/services/security.yaml new file mode 100644 index 000000000..21590fcda --- /dev/null +++ b/src/Bundle/ChillPersonBundle/config/services/security.yaml @@ -0,0 +1,16 @@ +services: + chill.person.security.authorization.person: + class: Chill\PersonBundle\Security\Authorization\PersonVoter + arguments: + - "@chill.main.security.authorization.helper" + tags: + - { name: security.voter } + - { name: chill.role } + + Chill\PersonBundle\Security\Authorization\AccompanyingPeriodVoter: + arguments: + - "@chill.main.security.authorization.helper" + tags: + - { name: security.voter } + - { name: chill.role } + diff --git a/src/Bundle/ChillPersonBundle/migrations/Version20210505093408.php b/src/Bundle/ChillPersonBundle/migrations/Version20210505093408.php new file mode 100644 index 000000000..cf5a93164 --- /dev/null +++ b/src/Bundle/ChillPersonBundle/migrations/Version20210505093408.php @@ -0,0 +1,41 @@ +addSql('CREATE SEQUENCE Household_id_seq INCREMENT BY 1 MINVALUE 1 START 1'); + $this->addSql('CREATE SEQUENCE HouseholdMembers_id_seq INCREMENT BY 1 MINVALUE 1 START 1'); + $this->addSql('CREATE TABLE Household (id INT NOT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE TABLE HouseholdMembers (id INT NOT NULL, person_id INT DEFAULT NULL, household_id INT DEFAULT NULL, position VARCHAR(255) DEFAULT NULL, startDate DATE NOT NULL, endDate DATE NOT NULL, comment VARCHAR(255) DEFAULT NULL, sharedHousehold BOOLEAN NOT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE INDEX IDX_4D1FB288217BBB47 ON HouseholdMembers (person_id)'); + $this->addSql('CREATE INDEX IDX_4D1FB288E79FF843 ON HouseholdMembers (household_id)'); + $this->addSql('ALTER TABLE HouseholdMembers ADD CONSTRAINT FK_4D1FB288217BBB47 FOREIGN KEY (person_id) REFERENCES chill_person_person (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE HouseholdMembers ADD CONSTRAINT FK_4D1FB288E79FF843 FOREIGN KEY (household_id) REFERENCES Household (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + } + + public function down(Schema $schema): void + { + $this->addSql('ALTER TABLE HouseholdMembers DROP CONSTRAINT FK_4D1FB288E79FF843'); + $this->addSql('ALTER TABLE HouseholdMembers DROP CONSTRAINT FK_4D1FB288217BBB47'); + $this->addSql('DROP SEQUENCE Household_id_seq CASCADE'); + $this->addSql('DROP SEQUENCE HouseholdMembers_id_seq CASCADE'); + $this->addSql('DROP TABLE Household'); + $this->addSql('DROP TABLE HouseholdMembers'); + } +} diff --git a/src/Bundle/ChillPersonBundle/migrations/Version20210505154316.php b/src/Bundle/ChillPersonBundle/migrations/Version20210505154316.php new file mode 100644 index 000000000..bb3a56337 --- /dev/null +++ b/src/Bundle/ChillPersonBundle/migrations/Version20210505154316.php @@ -0,0 +1,33 @@ +addSql('CREATE TABLE chill_person_household_to_addresses (household_id INT NOT NULL, address_id INT NOT NULL, PRIMARY KEY(household_id, address_id))'); + $this->addSql('CREATE INDEX IDX_7109483E79FF843 ON chill_person_household_to_addresses (household_id)'); + $this->addSql('CREATE INDEX IDX_7109483F5B7AF75 ON chill_person_household_to_addresses (address_id)'); + $this->addSql('ALTER TABLE chill_person_household_to_addresses ADD CONSTRAINT FK_7109483E79FF843 FOREIGN KEY (household_id) REFERENCES Household (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE chill_person_household_to_addresses ADD CONSTRAINT FK_7109483F5B7AF75 FOREIGN KEY (address_id) REFERENCES chill_main_address (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE'); + } + + public function down(Schema $schema): void + { + $this->addSql('DROP TABLE chill_person_household_to_addresses'); + } +} diff --git a/src/Bundle/ChillPersonBundle/translations/messages.fr.yml b/src/Bundle/ChillPersonBundle/translations/messages.fr.yml index 1fe6e3956..ab3a5bfe6 100644 --- a/src/Bundle/ChillPersonBundle/translations/messages.fr.yml +++ b/src/Bundle/ChillPersonBundle/translations/messages.fr.yml @@ -190,6 +190,7 @@ CHILL_PERSON_CREATE: Ajouter des personnes CHILL_PERSON_STATS: Statistiques sur les personnes CHILL_PERSON_LISTS: Liste des personnes CHILL_PERSON_DUPLICATE: Gérer les doublons de personnes +CHILL_PERSON_ACCOMPANYING_PERIOD_SEE: Voir les périodes d'accompagnement #period Period closed!: Période clôturée!