* * 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\Controller; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Doctrine\ORM\QueryBuilder; use Chill\MainBundle\Pagination\PaginatorFactory; use Symfony\Component\Form\FormInterface; use Symfony\Component\Translation\TranslatorInterface; use Symfony\Component\Form\Extension\Core\Type\SubmitType; use Chill\MainBundle\Security\Authorization\AuthorizationHelper; use Symfony\Component\Security\Core\Role\Role; use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Chill\MainBundle\CRUD\Resolver\Resolver; use Chill\MainBundle\Pagination\PaginatorInterface; /** * * */ class CRUDController extends AbstractController { /** * The crud configuration * * This configuration si defined by `chill_main['crud']`. * * @var array */ protected $crudConfig; public function setCrudConfig(array $config) { $this->crudConfig = $config; } protected function processTemplateParameters($action): array { throw new Exception('is this method used ?'); $configured = $this->getTemplateParameters($action); switch($action) { case 'index': $default = [ 'columns' => $this->getDoctrine()->getManager() ->getClassMetadata($this->getEntity()) ->getIdentifierFieldNames(), 'actions' => ['edit', 'delete'] ]; break; default: throw new \LogicException("this action is not supported: $action"); } $result = \array_merge($default, $configured); // add constants $result['class'] = $this->getEntity(); return $result; } protected function orderQuery($action, QueryBuilder $query, Request $request, PaginatorInterface $paginator): QueryBuilder { return $query; } public function index(Request $request) { return $this->indexEntityAction('index', $request); } protected function indexEntityAction($action, Request $request) { $totalItems = $this->countEntities($action, $request); $paginator = $this->getPaginatorFactory()->create($totalItems); $query = $this->queryEntities($action, $request, $paginator); $entities = $query->getQuery()->getResult(); $defaultTemplateParameters = [ 'entities' => $entities, 'crud_name' => $this->getCrudName(), 'paginator' => $paginator ]; return $this->render( $this->getTemplateFor($action, $entities, $request), $this->generateTemplateParameter($action, $entities, $request, $defaultTemplateParameters) ); } protected function queryEntities($action, Request $request, PaginatorInterface $paginator) { $query = $this->getDoctrine()->getManager() ->createQueryBuilder() ->select('e') ->from($this->getEntityClass(), 'e') ->setFirstResult($paginator->getCurrentPage()->getFirstItemNumber()) ->setMaxResults($paginator->getItemsPerPage()) ; $this->orderQuery($action, $query, $request, $paginator); return $query; } protected function countEntities($action, Request $request) { return $this->getDoctrine()->getManager() ->createQuery("SELECT COUNT(e) FROM ".$this->getEntityClass()." e") ->getSingleScalarResult() ; } public function edit(Request $request, $id): Response { return $this->formEditAction('edit', $request, $id); } public function new(Request $request): Response { return $this->formCreateAction('new', $request); } public function view(Request $request, $id): Response { return $this->viewAction('view', $request, $id); } protected function viewAction($action, Request $request, $id) { $entity = $this->getEntity($action, $id, $request); $postFetch = $this->onPostFetchEntity($action, $request, $entity); if ($postFetch instanceof Response) { return $postFetch; } $this->checkACL($action, $entity); $postCheckACL = $this->onPostCheckACL($action, $request, $entity); if ($postCheckACL instanceof Response) { return $postCheckACL; } $defaultTemplateParameters = [ 'entity' => $entity, 'crud_name' => $this->getCrudName() ]; return $this->render( $this->getTemplateFor($action, $entity, $request), $this->generateTemplateParameter($action, $entity, $request, $defaultTemplateParameters) ); } protected function formEditAction($action, Request $request, $id, $formClass = null, $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); } $this->checkACL($action, $entity); $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->generateTemplateParameter($action, $entity, $request, $defaultTemplateParameters) ); } protected function formCreateAction($action, Request $request, $formClass = null): Response { if ($request->query->has('duplicate')) { $entity = $this->duplicateEntity($action, $request); } else { $entity = $this->createEntity($action, $request); } $this->checkACL($action, $entity); $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->generateTemplateParameter($action, $entity, $request, $defaultTemplateParameters) ); } /** * get the instance of the entity with the given id * * @param string $id * @return object */ protected function getEntity($action, $id, Request $request): ?object { return $this->getDoctrine() ->getRepository($this->getEntityClass()) ->find($id); } protected function duplicateEntity(string $action, Request $request) { $id = $request->query->get('duplicate_id', 0); $originalEntity = $this->getEntity($action, $id, $request); $this->getDoctrine()->getManager() ->detach($originalEntity); return $originalEntity; } protected function getEntityClass(): string { return $this->crudConfig['class']; } protected function getCrudName(): string { return $this->crudConfig['name']; } protected function checkACL($action, $entity) { $this->denyAccessUnlessGranted($this->getRoleFor($action), $entity); } protected function getRoleFor($action) { if (NULL !== ($this->getActionConfig($action)['role'])) { return $this->getActionConfig($action)['role']; } return $this->buildDefaultRole($action); } protected function buildDefaultRole($action) { return $this->getCrudResolver()->buildDefaultRole($this->getCrudName(), $action); } protected function getFormClassFor($action) { return $this->crudConfig[$action]['form_class'] ?? $this->crudConfig['form_class']; } protected function createFormFor($action, $entity, $formClass = null, $formOptions = []) { $formClass = $formClass ?? $this->getFormClassFor($action); $form = $this->createForm($formClass, $entity, $formOptions); $this->customizeForm($action, $form); return $form; } protected function customizeForm($action, FormInterface $form) { } protected function generateLabelForButton($action, $formName, $form) { return sprintf("crud.%s.button_action_form", $action); } protected function generateFormErrorMessage($action, FormInterface $form): string { $msg = 'This form contains errors'; return $this->getTranslator()->trans($msg); } protected function generateFormSuccessMessage($action, $entity): string { switch ($action) { case 'edit': $msg = "crud.edit.success"; break; case 'new': $msg = "crud.new.success"; break; default: $msg = "crud.default.success"; } return $this->getTranslator()->trans($msg); } protected function generateTemplateParameter( $action, $entity, Request $request, array $defaultTemplateParameters = [] ) { return $defaultTemplateParameters; } protected function createEntity($action, Request $request): object { $type = $this->getEntityClass(); return new $type; } /** * * @param string $action * @param mixed $entity the entity for the current request, or an array of entities * @param Request $request * @return string * @throws \LogicException */ protected function getTemplateFor($action, $entity, Request $request) { if ($this->hasCustomTemplate($action, $entity, $request)) { return $this->getActionConfig($action)['template']; } switch ($action) { case 'new': return '@ChillMain/CRUD/new.html.twig'; case 'edit': return '@ChillMain/CRUD/edit.html.twig'; case 'index': return '@ChillMain/CRUD/index.html.twig'; case 'view': return '@ChillMain/CRUD/view.html.twig'; default: throw new \LogicException("the view for action $action is not " . "defined. You should override ".__METHOD__." to add this " . "action"); } } protected function hasCustomTemplate($action, $entity, Request $request): bool { return !empty($this->getActionConfig($action)['template']); } protected function onPreFlush(string $action, $entity, FormInterface $form, Request $request) { } protected function onPostFlush(string $action, $entity, FormInterface $form, Request $request) { } protected function onPrePersist(string $action, $entity, FormInterface $form, Request $request) { } protected function onPostPersist(string $action, $entity, FormInterface $form, Request $request) { } protected function onPostFetchEntity($action, Request $request, $entity): ?Response { return null; } protected function onPostCheckACL($action, Request $request, $entity): ?Response { return null; } protected function onFormValid(object $entity, FormInterface $form, Request $request) { } protected function onBeforeRedirectAfterSubmission(string $action, $entity, FormInterface $form, Request $request) { $next = $request->request->get("submit", "save-and-close"); switch ($next) { case "save-and-close": return $this->redirectToRoute('chill_crud_'.$this->getCrudName().'_index'); case "save-and-new": return $this->redirectToRoute('chill_crud_'.$this->getCrudName().'_new'); default: return $this->redirectToRoute('chill_crud_'.$this->getCrudName().'_view', [ 'id' => $entity->getId() ]); } } protected function getActionConfig(string $action) { return $this->crudConfig['actions'][$action]; } protected function getPaginatorFactory(): PaginatorFactory { return $this->get(PaginatorFactory::class); } protected function getTranslator(): TranslatorInterface { return $this->container->get('translator'); } protected function getAuthorizationHelper(): AuthorizationHelper { return $this->container->get(AuthorizationHelper::class); } protected function getReachableCenters(Role $role, Scope $scope = null) { return $this->getAuthorizationHelper() ->getReachableCenters($this->getUser(), $role, $scope) ; } protected function getEventDispatcher(): EventDispatcherInterface { return $this->get(EventDispatcherInterface::class); } protected function getCrudResolver(): Resolver { return $this->get(Resolver::class); } public static function getSubscribedServices() { return \array_merge( parent::getSubscribedServices(), [ PaginatorFactory::class => PaginatorFactory::class, 'translator' => TranslatorInterface::class, AuthorizationHelper::class => AuthorizationHelper::class, EventDispatcherInterface::class => EventDispatcherInterface::class, Resolver::class => Resolver::class, ] ); } }