vendor/sonata-project/admin-bundle/src/Controller/CRUDController.php line 311

Open in your IDE?
  1. <?php
  2. declare(strict_types=1);
  3. /*
  4.  * This file is part of the Sonata Project package.
  5.  *
  6.  * (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
  7.  *
  8.  * For the full copyright and license information, please view the LICENSE
  9.  * file that was distributed with this source code.
  10.  */
  11. namespace Sonata\AdminBundle\Controller;
  12. use Doctrine\Common\Inflector\Inflector;
  13. use Psr\Log\LoggerInterface;
  14. use Psr\Log\NullLogger;
  15. use Sonata\AdminBundle\Admin\AdminInterface;
  16. use Sonata\AdminBundle\Admin\FieldDescriptionCollection;
  17. use Sonata\AdminBundle\Datagrid\ProxyQueryInterface;
  18. use Sonata\AdminBundle\Exception\LockException;
  19. use Sonata\AdminBundle\Exception\ModelManagerException;
  20. use Sonata\AdminBundle\Templating\TemplateRegistryInterface;
  21. use Sonata\AdminBundle\Util\AdminObjectAclData;
  22. use Sonata\AdminBundle\Util\AdminObjectAclManipulator;
  23. use Symfony\Bridge\Twig\AppVariable;
  24. use Symfony\Bridge\Twig\Command\DebugCommand;
  25. use Symfony\Bridge\Twig\Extension\FormExtension;
  26. use Symfony\Bridge\Twig\Form\TwigRenderer;
  27. use Symfony\Bundle\FrameworkBundle\Controller\ControllerTrait;
  28. use Symfony\Component\DependencyInjection\ContainerAwareInterface;
  29. use Symfony\Component\DependencyInjection\ContainerAwareTrait;
  30. use Symfony\Component\DependencyInjection\ContainerInterface;
  31. use Symfony\Component\Form\FormInterface;
  32. use Symfony\Component\Form\FormRenderer;
  33. use Symfony\Component\Form\FormView;
  34. use Symfony\Component\HttpFoundation\JsonResponse;
  35. use Symfony\Component\HttpFoundation\RedirectResponse;
  36. use Symfony\Component\HttpFoundation\Request;
  37. use Symfony\Component\HttpFoundation\Response;
  38. use Symfony\Component\HttpKernel\Exception\HttpException;
  39. use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
  40. use Symfony\Component\PropertyAccess\PropertyAccess;
  41. use Symfony\Component\PropertyAccess\PropertyPath;
  42. use Symfony\Component\Security\Core\Exception\AccessDeniedException;
  43. use Symfony\Component\Security\Csrf\CsrfToken;
  44. // BC for Symfony < 3.3 where this trait does not exist
  45. // NEXT_MAJOR: Remove the polyfill and inherit from \Symfony\Bundle\FrameworkBundle\Controller\Controller again
  46. if (!trait_exists(ControllerTrait::class)) {
  47.     require_once __DIR__.'/PolyfillControllerTrait.php';
  48. }
  49. /**
  50.  * @author Thomas Rabaix <thomas.rabaix@sonata-project.org>
  51.  */
  52. class CRUDController implements ContainerAwareInterface
  53. {
  54.     // NEXT_MAJOR: Don't use these traits anymore (inherit from Controller instead)
  55.     use ControllerTraitContainerAwareTrait {
  56.         ControllerTrait::render as originalRender;
  57.     }
  58.     /**
  59.      * The related Admin class.
  60.      *
  61.      * @var AdminInterface
  62.      */
  63.     protected $admin;
  64.     /**
  65.      * The template registry of the related Admin class.
  66.      *
  67.      * @var TemplateRegistryInterface
  68.      */
  69.     private $templateRegistry;
  70.     // BC for Symfony 3.3 where ControllerTrait exists but does not contain get() and has() methods.
  71.     public function __call($method$arguments)
  72.     {
  73.         if (\in_array($method, ['get''has'], true)) {
  74.             return $this->container->{$method}(...$arguments);
  75.         }
  76.         if (method_exists($this'proxyToControllerClass')) {
  77.             return $this->proxyToControllerClass($method$arguments);
  78.         }
  79.         throw new \LogicException('Call to undefined method '.__CLASS__.'::'.$method);
  80.     }
  81.     public function setContainer(ContainerInterface $container null)
  82.     {
  83.         $this->container $container;
  84.         $this->configure();
  85.     }
  86.     /**
  87.      * NEXT_MAJOR: Remove this method.
  88.      *
  89.      * @see renderWithExtraParams()
  90.      *
  91.      * @param string $view       The view name
  92.      * @param array  $parameters An array of parameters to pass to the view
  93.      *
  94.      * @return Response A Response instance
  95.      *
  96.      * @deprecated since version 3.27, to be removed in 4.0. Use Sonata\AdminBundle\Controller\CRUDController::renderWithExtraParams() instead.
  97.      */
  98.     public function render($view, array $parameters = [], Response $response null)
  99.     {
  100.         @trigger_error(
  101.             'Method '.__CLASS__.'::render has been renamed to '.__CLASS__.'::renderWithExtraParams.',
  102.             E_USER_DEPRECATED
  103.         );
  104.         return $this->renderWithExtraParams($view$parameters$response);
  105.     }
  106.     /**
  107.      * Renders a view while passing mandatory parameters on to the template.
  108.      *
  109.      * @param string $view The view name
  110.      *
  111.      * @return Response A Response instance
  112.      */
  113.     public function renderWithExtraParams($view, array $parameters = [], Response $response null)
  114.     {
  115.         if (!$this->isXmlHttpRequest()) {
  116.             $parameters['breadcrumbs_builder'] = $this->get('sonata.admin.breadcrumbs_builder');
  117.         }
  118.         $parameters['admin'] = $parameters['admin'] ??
  119.             $this->admin;
  120.         $parameters['base_template'] = $parameters['base_template'] ??
  121.             $this->getBaseTemplate();
  122.         $parameters['admin_pool'] = $this->get('sonata.admin.pool');
  123.         //NEXT_MAJOR: Remove method alias and use $this->render() directly.
  124.         return $this->originalRender($view$parameters$response);
  125.     }
  126.     /**
  127.      * List action.
  128.      *
  129.      * @throws AccessDeniedException If access is not granted
  130.      *
  131.      * @return Response
  132.      */
  133.     public function listAction()
  134.     {
  135.         $request $this->getRequest();
  136.         $this->admin->checkAccess('list');
  137.         $preResponse $this->preList($request);
  138.         if (null !== $preResponse) {
  139.             return $preResponse;
  140.         }
  141.         if ($listMode $request->get('_list_mode')) {
  142.             $this->admin->setListMode($listMode);
  143.         }
  144.         $datagrid $this->admin->getDatagrid();
  145.         $formView $datagrid->getForm()->createView();
  146.         // set the theme for the current Admin Form
  147.         $this->setFormTheme($formView$this->admin->getFilterTheme());
  148.         // NEXT_MAJOR: Remove this line and use commented line below it instead
  149.         $template $this->admin->getTemplate('list');
  150.         // $template = $this->templateRegistry->getTemplate('list');
  151.         return $this->renderWithExtraParams($template, [
  152.             'action' => 'list',
  153.             'form' => $formView,
  154.             'datagrid' => $datagrid,
  155.             'csrf_token' => $this->getCsrfToken('sonata.batch'),
  156.             'export_formats' => $this->has('sonata.admin.admin_exporter') ?
  157.                 $this->get('sonata.admin.admin_exporter')->getAvailableFormats($this->admin) :
  158.                 $this->admin->getExportFormats(),
  159.         ], null);
  160.     }
  161.     /**
  162.      * Execute a batch delete.
  163.      *
  164.      * @throws AccessDeniedException If access is not granted
  165.      *
  166.      * @return RedirectResponse
  167.      */
  168.     public function batchActionDelete(ProxyQueryInterface $query)
  169.     {
  170.         $this->admin->checkAccess('batchDelete');
  171.         $modelManager $this->admin->getModelManager();
  172.         try {
  173.             $modelManager->batchDelete($this->admin->getClass(), $query);
  174.             $this->addFlash(
  175.                 'sonata_flash_success',
  176.                 $this->trans('flash_batch_delete_success', [], 'SonataAdminBundle')
  177.             );
  178.         } catch (ModelManagerException $e) {
  179.             $this->handleModelManagerException($e);
  180.             $this->addFlash(
  181.                 'sonata_flash_error',
  182.                 $this->trans('flash_batch_delete_error', [], 'SonataAdminBundle')
  183.             );
  184.         }
  185.         return $this->redirectToList();
  186.     }
  187.     /**
  188.      * Delete action.
  189.      *
  190.      * @param int|string|null $id
  191.      *
  192.      * @throws NotFoundHttpException If the object does not exist
  193.      * @throws AccessDeniedException If access is not granted
  194.      *
  195.      * @return Response|RedirectResponse
  196.      */
  197.     public function deleteAction($id)
  198.     {
  199.         $request $this->getRequest();
  200.         $id $request->get($this->admin->getIdParameter());
  201.         $object $this->admin->getObject($id);
  202.         if (!$object) {
  203.             throw $this->createNotFoundException(sprintf('unable to find the object with id: %s'$id));
  204.         }
  205.         $this->checkParentChildAssociation($request$object);
  206.         $this->admin->checkAccess('delete'$object);
  207.         $preResponse $this->preDelete($request$object);
  208.         if (null !== $preResponse) {
  209.             return $preResponse;
  210.         }
  211.         if ('DELETE' === $this->getRestMethod()) {
  212.             // check the csrf token
  213.             $this->validateCsrfToken('sonata.delete');
  214.             $objectName $this->admin->toString($object);
  215.             try {
  216.                 $this->admin->delete($object);
  217.                 if ($this->isXmlHttpRequest()) {
  218.                     return $this->renderJson(['result' => 'ok'], 200, []);
  219.                 }
  220.                 $this->addFlash(
  221.                     'sonata_flash_success',
  222.                     $this->trans(
  223.                         'flash_delete_success',
  224.                         ['%name%' => $this->escapeHtml($objectName)],
  225.                         'SonataAdminBundle'
  226.                     )
  227.                 );
  228.             } catch (ModelManagerException $e) {
  229.                 $this->handleModelManagerException($e);
  230.                 if ($this->isXmlHttpRequest()) {
  231.                     return $this->renderJson(['result' => 'error'], 200, []);
  232.                 }
  233.                 $this->addFlash(
  234.                     'sonata_flash_error',
  235.                     $this->trans(
  236.                         'flash_delete_error',
  237.                         ['%name%' => $this->escapeHtml($objectName)],
  238.                         'SonataAdminBundle'
  239.                     )
  240.                 );
  241.             }
  242.             return $this->redirectTo($object);
  243.         }
  244.         // NEXT_MAJOR: Remove this line and use commented line below it instead
  245.         $template $this->admin->getTemplate('delete');
  246.         // $template = $this->templateRegistry->getTemplate('delete');
  247.         return $this->renderWithExtraParams($template, [
  248.             'object' => $object,
  249.             'action' => 'delete',
  250.             'csrf_token' => $this->getCsrfToken('sonata.delete'),
  251.         ], null);
  252.     }
  253.     /**
  254.      * Edit action.
  255.      *
  256.      * @param int|string|null $id
  257.      *
  258.      * @throws NotFoundHttpException If the object does not exist
  259.      * @throws \RuntimeException     If no editable field is defined
  260.      * @throws AccessDeniedException If access is not granted
  261.      *
  262.      * @return Response|RedirectResponse
  263.      */
  264.     public function editAction($id null)
  265.     {
  266.         $request $this->getRequest();
  267.         // the key used to lookup the template
  268.         $templateKey 'edit';
  269.         $id $request->get($this->admin->getIdParameter());
  270.         $existingObject $this->admin->getObject($id);
  271.         if (!$existingObject) {
  272.             throw $this->createNotFoundException(sprintf('unable to find the object with id: %s'$id));
  273.         }
  274.         $this->checkParentChildAssociation($request$existingObject);
  275.         $this->admin->checkAccess('edit'$existingObject);
  276.         $preResponse $this->preEdit($request$existingObject);
  277.         if (null !== $preResponse) {
  278.             return $preResponse;
  279.         }
  280.         $this->admin->setSubject($existingObject);
  281.         $objectId $this->admin->getNormalizedIdentifier($existingObject);
  282.         $form $this->admin->getForm();
  283.         if (!\is_array($fields $form->all()) || === \count($fields)) {
  284.             throw new \RuntimeException(
  285.                 'No editable field defined. Did you forget to implement the "configureFormFields" method?'
  286.             );
  287.         }
  288.         $form->setData($existingObject);
  289.         $form->handleRequest($request);
  290.         if ($form->isSubmitted()) {
  291.             $isFormValid $form->isValid();
  292.             // persist if the form was valid and if in preview mode the preview was approved
  293.             if ($isFormValid && (!$this->isInPreviewMode() || $this->isPreviewApproved())) {
  294.                 $submittedObject $form->getData();
  295.                 $this->admin->setSubject($submittedObject);
  296.                 try {
  297.                     $existingObject $this->admin->update($submittedObject);
  298.                     if ($this->isXmlHttpRequest()) {
  299.                         return $this->handleXmlHttpRequestSuccessResponse($request$existingObject);
  300.                     }
  301.                     $this->addFlash(
  302.                         'sonata_flash_success',
  303.                         $this->trans(
  304.                             'flash_edit_success',
  305.                             ['%name%' => $this->escapeHtml($this->admin->toString($existingObject))],
  306.                             'SonataAdminBundle'
  307.                         )
  308.                     );
  309.                     // redirect to edit mode
  310.                     return $this->redirectTo($existingObject);
  311.                 } catch (ModelManagerException $e) {
  312.                     $this->handleModelManagerException($e);
  313.                     $isFormValid false;
  314.                 } catch (LockException $e) {
  315.                     $this->addFlash('sonata_flash_error'$this->trans('flash_lock_error', [
  316.                         '%name%' => $this->escapeHtml($this->admin->toString($existingObject)),
  317.                         '%link_start%' => '<a href="'.$this->admin->generateObjectUrl('edit'$existingObject).'">',
  318.                         '%link_end%' => '</a>',
  319.                     ], 'SonataAdminBundle'));
  320.                 }
  321.             }
  322.             // show an error message if the form failed validation
  323.             if (!$isFormValid) {
  324.                 if ($this->isXmlHttpRequest() && null !== ($response $this->handleXmlHttpRequestErrorResponse($request$form))) {
  325.                     return $response;
  326.                 }
  327.                 $this->addFlash(
  328.                     'sonata_flash_error',
  329.                     $this->trans(
  330.                         'flash_edit_error',
  331.                         ['%name%' => $this->escapeHtml($this->admin->toString($existingObject))],
  332.                         'SonataAdminBundle'
  333.                     )
  334.                 );
  335.             } elseif ($this->isPreviewRequested()) {
  336.                 // enable the preview template if the form was valid and preview was requested
  337.                 $templateKey 'preview';
  338.                 $this->admin->getShow();
  339.             }
  340.         }
  341.         $formView $form->createView();
  342.         // set the theme for the current Admin Form
  343.         $this->setFormTheme($formView$this->admin->getFormTheme());
  344.         // NEXT_MAJOR: Remove this line and use commented line below it instead
  345.         $template $this->admin->getTemplate($templateKey);
  346.         // $template = $this->templateRegistry->getTemplate($templateKey);
  347.         return $this->renderWithExtraParams($template, [
  348.             'action' => 'edit',
  349.             'form' => $formView,
  350.             'object' => $existingObject,
  351.             'objectId' => $objectId,
  352.         ], null);
  353.     }
  354.     /**
  355.      * Batch action.
  356.      *
  357.      * @throws NotFoundHttpException If the HTTP method is not POST
  358.      * @throws \RuntimeException     If the batch action is not defined
  359.      *
  360.      * @return Response|RedirectResponse
  361.      */
  362.     public function batchAction()
  363.     {
  364.         $request $this->getRequest();
  365.         $restMethod $this->getRestMethod();
  366.         if ('POST' !== $restMethod) {
  367.             throw $this->createNotFoundException(sprintf('Invalid request type "%s", POST expected'$restMethod));
  368.         }
  369.         // check the csrf token
  370.         $this->validateCsrfToken('sonata.batch');
  371.         $confirmation $request->get('confirmation'false);
  372.         if ($data json_decode((string) $request->get('data'), true)) {
  373.             $action $data['action'];
  374.             $idx $data['idx'];
  375.             $allElements $data['all_elements'];
  376.             $request->request->replace(array_merge($request->request->all(), $data));
  377.         } else {
  378.             $request->request->set('idx'$request->get('idx', []));
  379.             $request->request->set('all_elements'$request->get('all_elements'false));
  380.             $action $request->get('action');
  381.             $idx $request->get('idx');
  382.             $allElements $request->get('all_elements');
  383.             $data $request->request->all();
  384.             unset($data['_sonata_csrf_token']);
  385.         }
  386.         // NEXT_MAJOR: Remove reflection check.
  387.         $reflector = new \ReflectionMethod($this->admin'getBatchActions');
  388.         if ($reflector->getDeclaringClass()->getName() === \get_class($this->admin)) {
  389.             @trigger_error('Override Sonata\AdminBundle\Admin\AbstractAdmin::getBatchActions method'
  390.                 .' is deprecated since version 3.2.'
  391.                 .' Use Sonata\AdminBundle\Admin\AbstractAdmin::configureBatchActions instead.'
  392.                 .' The method will be final in 4.0.'E_USER_DEPRECATED
  393.             );
  394.         }
  395.         $batchActions $this->admin->getBatchActions();
  396.         if (!\array_key_exists($action$batchActions)) {
  397.             throw new \RuntimeException(sprintf('The `%s` batch action is not defined'$action));
  398.         }
  399.         $camelizedAction Inflector::classify($action);
  400.         $isRelevantAction sprintf('batchAction%sIsRelevant'$camelizedAction);
  401.         if (method_exists($this$isRelevantAction)) {
  402.             $nonRelevantMessage $this->{$isRelevantAction}($idx$allElements$request);
  403.         } else {
  404.             $nonRelevantMessage !== \count($idx) || $allElements// at least one item is selected
  405.         }
  406.         if (!$nonRelevantMessage) { // default non relevant message (if false of null)
  407.             $nonRelevantMessage 'flash_batch_empty';
  408.         }
  409.         $datagrid $this->admin->getDatagrid();
  410.         $datagrid->buildPager();
  411.         if (true !== $nonRelevantMessage) {
  412.             $this->addFlash(
  413.                 'sonata_flash_info',
  414.                 $this->trans($nonRelevantMessage, [], 'SonataAdminBundle')
  415.             );
  416.             return $this->redirectToList();
  417.         }
  418.         $askConfirmation $batchActions[$action]['ask_confirmation'] ??
  419.             true;
  420.         if ($askConfirmation && 'ok' !== $confirmation) {
  421.             $actionLabel $batchActions[$action]['label'];
  422.             $batchTranslationDomain $batchActions[$action]['translation_domain'] ??
  423.                 $this->admin->getTranslationDomain();
  424.             $formView $datagrid->getForm()->createView();
  425.             $this->setFormTheme($formView$this->admin->getFilterTheme());
  426.             // NEXT_MAJOR: Remove these lines and use commented lines below them instead
  427.             $template = !empty($batchActions[$action]['template']) ?
  428.                 $batchActions[$action]['template'] :
  429.                 $this->admin->getTemplate('batch_confirmation');
  430.             // $template = !empty($batchActions[$action]['template']) ?
  431.             //     $batchActions[$action]['template'] :
  432.             //     $this->templateRegistry->getTemplate('batch_confirmation');
  433.             return $this->renderWithExtraParams($template, [
  434.                 'action' => 'list',
  435.                 'action_label' => $actionLabel,
  436.                 'batch_translation_domain' => $batchTranslationDomain,
  437.                 'datagrid' => $datagrid,
  438.                 'form' => $formView,
  439.                 'data' => $data,
  440.                 'csrf_token' => $this->getCsrfToken('sonata.batch'),
  441.             ], null);
  442.         }
  443.         // execute the action, batchActionXxxxx
  444.         $finalAction sprintf('batchAction%s'$camelizedAction);
  445.         if (!method_exists($this$finalAction)) {
  446.             throw new \RuntimeException(sprintf('A `%s::%s` method must be callable', static::class, $finalAction));
  447.         }
  448.         $query $datagrid->getQuery();
  449.         $query->setFirstResult(null);
  450.         $query->setMaxResults(null);
  451.         $this->admin->preBatchAction($action$query$idx$allElements);
  452.         if (\count($idx) > 0) {
  453.             $this->admin->getModelManager()->addIdentifiersToQuery($this->admin->getClass(), $query$idx);
  454.         } elseif (!$allElements) {
  455.             $this->addFlash(
  456.                 'sonata_flash_info',
  457.                 $this->trans('flash_batch_no_elements_processed', [], 'SonataAdminBundle')
  458.             );
  459.             return $this->redirectToList();
  460.         }
  461.         return $this->{$finalAction}($query$request);
  462.     }
  463.     /**
  464.      * Create action.
  465.      *
  466.      * @throws AccessDeniedException If access is not granted
  467.      * @throws \RuntimeException     If no editable field is defined
  468.      *
  469.      * @return Response
  470.      */
  471.     public function createAction()
  472.     {
  473.         $request $this->getRequest();
  474.         // the key used to lookup the template
  475.         $templateKey 'edit';
  476.         $this->admin->checkAccess('create');
  477.         $class = new \ReflectionClass($this->admin->hasActiveSubClass() ? $this->admin->getActiveSubClass() : $this->admin->getClass());
  478.         if ($class->isAbstract()) {
  479.             return $this->renderWithExtraParams(
  480.                 '@SonataAdmin/CRUD/select_subclass.html.twig',
  481.                 [
  482.                     'base_template' => $this->getBaseTemplate(),
  483.                     'admin' => $this->admin,
  484.                     'action' => 'create',
  485.                 ],
  486.                 null
  487.             );
  488.         }
  489.         $newObject $this->admin->getNewInstance();
  490.         $preResponse $this->preCreate($request$newObject);
  491.         if (null !== $preResponse) {
  492.             return $preResponse;
  493.         }
  494.         $this->admin->setSubject($newObject);
  495.         $form $this->admin->getForm();
  496.         if (!\is_array($fields $form->all()) || === \count($fields)) {
  497.             throw new \RuntimeException(
  498.                 'No editable field defined. Did you forget to implement the "configureFormFields" method?'
  499.             );
  500.         }
  501.         $form->setData($newObject);
  502.         $form->handleRequest($request);
  503.         if ($form->isSubmitted()) {
  504.             $isFormValid $form->isValid();
  505.             // persist if the form was valid and if in preview mode the preview was approved
  506.             if ($isFormValid && (!$this->isInPreviewMode() || $this->isPreviewApproved())) {
  507.                 $submittedObject $form->getData();
  508.                 $this->admin->setSubject($submittedObject);
  509.                 $this->admin->checkAccess('create'$submittedObject);
  510.                 try {
  511.                     $newObject $this->admin->create($submittedObject);
  512.                     if ($this->isXmlHttpRequest()) {
  513.                         return $this->handleXmlHttpRequestSuccessResponse($request$newObject);
  514.                     }
  515.                     $this->addFlash(
  516.                         'sonata_flash_success',
  517.                         $this->trans(
  518.                             'flash_create_success',
  519.                             ['%name%' => $this->escapeHtml($this->admin->toString($newObject))],
  520.                             'SonataAdminBundle'
  521.                         )
  522.                     );
  523.                     // redirect to edit mode
  524.                     return $this->redirectTo($newObject);
  525.                 } catch (ModelManagerException $e) {
  526.                     $this->handleModelManagerException($e);
  527.                     $isFormValid false;
  528.                 }
  529.             }
  530.             // show an error message if the form failed validation
  531.             if (!$isFormValid) {
  532.                 if ($this->isXmlHttpRequest() && null !== ($response $this->handleXmlHttpRequestErrorResponse($request$form))) {
  533.                     return $response;
  534.                 }
  535.                 $this->addFlash(
  536.                     'sonata_flash_error',
  537.                     $this->trans(
  538.                         'flash_create_error',
  539.                         ['%name%' => $this->escapeHtml($this->admin->toString($newObject))],
  540.                         'SonataAdminBundle'
  541.                     )
  542.                 );
  543.             } elseif ($this->isPreviewRequested()) {
  544.                 // pick the preview template if the form was valid and preview was requested
  545.                 $templateKey 'preview';
  546.                 $this->admin->getShow();
  547.             }
  548.         }
  549.         $formView $form->createView();
  550.         // set the theme for the current Admin Form
  551.         $this->setFormTheme($formView$this->admin->getFormTheme());
  552.         // NEXT_MAJOR: Remove this line and use commented line below it instead
  553.         $template $this->admin->getTemplate($templateKey);
  554.         // $template = $this->templateRegistry->getTemplate($templateKey);
  555.         return $this->renderWithExtraParams($template, [
  556.             'action' => 'create',
  557.             'form' => $formView,
  558.             'object' => $newObject,
  559.             'objectId' => null,
  560.         ], null);
  561.     }
  562.     /**
  563.      * Show action.
  564.      *
  565.      * @param int|string|null $id
  566.      *
  567.      * @throws NotFoundHttpException If the object does not exist
  568.      * @throws AccessDeniedException If access is not granted
  569.      *
  570.      * @return Response
  571.      */
  572.     public function showAction($id null)
  573.     {
  574.         $request $this->getRequest();
  575.         $id $request->get($this->admin->getIdParameter());
  576.         $object $this->admin->getObject($id);
  577.         if (!$object) {
  578.             throw $this->createNotFoundException(sprintf('unable to find the object with id: %s'$id));
  579.         }
  580.         $this->checkParentChildAssociation($request$object);
  581.         $this->admin->checkAccess('show'$object);
  582.         $preResponse $this->preShow($request$object);
  583.         if (null !== $preResponse) {
  584.             return $preResponse;
  585.         }
  586.         $this->admin->setSubject($object);
  587.         $fields $this->admin->getShow();
  588.         \assert($fields instanceof FieldDescriptionCollection);
  589.         // NEXT_MAJOR: replace deprecation with exception
  590.         if (!\is_array($fields->getElements()) || === $fields->count()) {
  591.             @trigger_error(
  592.                 'Calling this method without implementing "configureShowFields"'
  593.                 .' is not supported since 3.40.0'
  594.                 .' and will no longer be possible in 4.0',
  595.                 E_USER_DEPRECATED
  596.             );
  597.         }
  598.         // NEXT_MAJOR: Remove this line and use commented line below it instead
  599.         $template $this->admin->getTemplate('show');
  600.         //$template = $this->templateRegistry->getTemplate('show');
  601.         return $this->renderWithExtraParams($template, [
  602.             'action' => 'show',
  603.             'object' => $object,
  604.             'elements' => $fields,
  605.         ], null);
  606.     }
  607.     /**
  608.      * Show history revisions for object.
  609.      *
  610.      * @param int|string|null $id
  611.      *
  612.      * @throws AccessDeniedException If access is not granted
  613.      * @throws NotFoundHttpException If the object does not exist or the audit reader is not available
  614.      *
  615.      * @return Response
  616.      */
  617.     public function historyAction($id null)
  618.     {
  619.         $request $this->getRequest();
  620.         $id $request->get($this->admin->getIdParameter());
  621.         $object $this->admin->getObject($id);
  622.         if (!$object) {
  623.             throw $this->createNotFoundException(sprintf('unable to find the object with id: %s'$id));
  624.         }
  625.         $this->admin->checkAccess('history'$object);
  626.         $manager $this->get('sonata.admin.audit.manager');
  627.         if (!$manager->hasReader($this->admin->getClass())) {
  628.             throw $this->createNotFoundException(
  629.                 sprintf(
  630.                     'unable to find the audit reader for class : %s',
  631.                     $this->admin->getClass()
  632.                 )
  633.             );
  634.         }
  635.         $reader $manager->getReader($this->admin->getClass());
  636.         $revisions $reader->findRevisions($this->admin->getClass(), $id);
  637.         // NEXT_MAJOR: Remove this line and use commented line below it instead
  638.         $template $this->admin->getTemplate('history');
  639.         // $template = $this->templateRegistry->getTemplate('history');
  640.         return $this->renderWithExtraParams($template, [
  641.             'action' => 'history',
  642.             'object' => $object,
  643.             'revisions' => $revisions,
  644.             'currentRevision' => $revisions current($revisions) : false,
  645.         ], null);
  646.     }
  647.     /**
  648.      * View history revision of object.
  649.      *
  650.      * @param int|string|null $id
  651.      * @param string|null     $revision
  652.      *
  653.      * @throws AccessDeniedException If access is not granted
  654.      * @throws NotFoundHttpException If the object or revision does not exist or the audit reader is not available
  655.      *
  656.      * @return Response
  657.      */
  658.     public function historyViewRevisionAction($id null$revision null)
  659.     {
  660.         $request $this->getRequest();
  661.         $id $request->get($this->admin->getIdParameter());
  662.         $object $this->admin->getObject($id);
  663.         if (!$object) {
  664.             throw $this->createNotFoundException(sprintf('unable to find the object with id: %s'$id));
  665.         }
  666.         $this->admin->checkAccess('historyViewRevision'$object);
  667.         $manager $this->get('sonata.admin.audit.manager');
  668.         if (!$manager->hasReader($this->admin->getClass())) {
  669.             throw $this->createNotFoundException(
  670.                 sprintf(
  671.                     'unable to find the audit reader for class : %s',
  672.                     $this->admin->getClass()
  673.                 )
  674.             );
  675.         }
  676.         $reader $manager->getReader($this->admin->getClass());
  677.         // retrieve the revisioned object
  678.         $object $reader->find($this->admin->getClass(), $id$revision);
  679.         if (!$object) {
  680.             throw $this->createNotFoundException(
  681.                 sprintf(
  682.                     'unable to find the targeted object `%s` from the revision `%s` with classname : `%s`',
  683.                     $id,
  684.                     $revision,
  685.                     $this->admin->getClass()
  686.                 )
  687.             );
  688.         }
  689.         $this->admin->setSubject($object);
  690.         // NEXT_MAJOR: Remove this line and use commented line below it instead
  691.         $template $this->admin->getTemplate('show');
  692.         // $template = $this->templateRegistry->getTemplate('show');
  693.         return $this->renderWithExtraParams($template, [
  694.             'action' => 'show',
  695.             'object' => $object,
  696.             'elements' => $this->admin->getShow(),
  697.         ], null);
  698.     }
  699.     /**
  700.      * Compare history revisions of object.
  701.      *
  702.      * @param int|string|null $id
  703.      * @param int|string|null $base_revision
  704.      * @param int|string|null $compare_revision
  705.      *
  706.      * @throws AccessDeniedException If access is not granted
  707.      * @throws NotFoundHttpException If the object or revision does not exist or the audit reader is not available
  708.      *
  709.      * @return Response
  710.      */
  711.     public function historyCompareRevisionsAction($id null$base_revision null$compare_revision null)
  712.     {
  713.         $request $this->getRequest();
  714.         $this->admin->checkAccess('historyCompareRevisions');
  715.         $id $request->get($this->admin->getIdParameter());
  716.         $object $this->admin->getObject($id);
  717.         if (!$object) {
  718.             throw $this->createNotFoundException(sprintf('unable to find the object with id: %s'$id));
  719.         }
  720.         $manager $this->get('sonata.admin.audit.manager');
  721.         if (!$manager->hasReader($this->admin->getClass())) {
  722.             throw $this->createNotFoundException(
  723.                 sprintf(
  724.                     'unable to find the audit reader for class : %s',
  725.                     $this->admin->getClass()
  726.                 )
  727.             );
  728.         }
  729.         $reader $manager->getReader($this->admin->getClass());
  730.         // retrieve the base revision
  731.         $base_object $reader->find($this->admin->getClass(), $id$base_revision);
  732.         if (!$base_object) {
  733.             throw $this->createNotFoundException(
  734.                 sprintf(
  735.                     'unable to find the targeted object `%s` from the revision `%s` with classname : `%s`',
  736.                     $id,
  737.                     $base_revision,
  738.                     $this->admin->getClass()
  739.                 )
  740.             );
  741.         }
  742.         // retrieve the compare revision
  743.         $compare_object $reader->find($this->admin->getClass(), $id$compare_revision);
  744.         if (!$compare_object) {
  745.             throw $this->createNotFoundException(
  746.                 sprintf(
  747.                     'unable to find the targeted object `%s` from the revision `%s` with classname : `%s`',
  748.                     $id,
  749.                     $compare_revision,
  750.                     $this->admin->getClass()
  751.                 )
  752.             );
  753.         }
  754.         $this->admin->setSubject($base_object);
  755.         // NEXT_MAJOR: Remove this line and use commented line below it instead
  756.         $template $this->admin->getTemplate('show_compare');
  757.         // $template = $this->templateRegistry->getTemplate('show_compare');
  758.         return $this->renderWithExtraParams($template, [
  759.             'action' => 'show',
  760.             'object' => $base_object,
  761.             'object_compare' => $compare_object,
  762.             'elements' => $this->admin->getShow(),
  763.         ], null);
  764.     }
  765.     /**
  766.      * Export data to specified format.
  767.      *
  768.      * @throws AccessDeniedException If access is not granted
  769.      * @throws \RuntimeException     If the export format is invalid
  770.      *
  771.      * @return Response
  772.      */
  773.     public function exportAction(Request $request)
  774.     {
  775.         $this->admin->checkAccess('export');
  776.         $format $request->get('format');
  777.         // NEXT_MAJOR: remove the check
  778.         if (!$this->has('sonata.admin.admin_exporter')) {
  779.             @trigger_error(
  780.                 'Not registering the exporter bundle is deprecated since version 3.14.'
  781.                 .' You must register it to be able to use the export action in 4.0.',
  782.                 E_USER_DEPRECATED
  783.             );
  784.             $allowedExportFormats = (array) $this->admin->getExportFormats();
  785.             $class = (string) $this->admin->getClass();
  786.             $filename sprintf(
  787.                 'export_%s_%s.%s',
  788.                 strtolower((string) substr($classstrripos($class'\\') + 1)),
  789.                 date('Y_m_d_H_i_s'strtotime('now')),
  790.                 $format
  791.             );
  792.             $exporter $this->get('sonata.admin.exporter');
  793.         } else {
  794.             $adminExporter $this->get('sonata.admin.admin_exporter');
  795.             $allowedExportFormats $adminExporter->getAvailableFormats($this->admin);
  796.             $filename $adminExporter->getExportFilename($this->admin$format);
  797.             $exporter $this->get('sonata.exporter.exporter');
  798.         }
  799.         if (!\in_array($format$allowedExportFormatstrue)) {
  800.             throw new \RuntimeException(
  801.                 sprintf(
  802.                     'Export in format `%s` is not allowed for class: `%s`. Allowed formats are: `%s`',
  803.                     $format,
  804.                     $this->admin->getClass(),
  805.                     implode(', '$allowedExportFormats)
  806.                 )
  807.             );
  808.         }
  809.         return $exporter->getResponse(
  810.             $format,
  811.             $filename,
  812.             $this->admin->getDataSourceIterator()
  813.         );
  814.     }
  815.     /**
  816.      * Returns the Response object associated to the acl action.
  817.      *
  818.      * @param int|string|null $id
  819.      *
  820.      * @throws AccessDeniedException If access is not granted
  821.      * @throws NotFoundHttpException If the object does not exist or the ACL is not enabled
  822.      *
  823.      * @return Response|RedirectResponse
  824.      */
  825.     public function aclAction($id null)
  826.     {
  827.         $request $this->getRequest();
  828.         if (!$this->admin->isAclEnabled()) {
  829.             throw $this->createNotFoundException('ACL are not enabled for this admin');
  830.         }
  831.         $id $request->get($this->admin->getIdParameter());
  832.         $object $this->admin->getObject($id);
  833.         if (!$object) {
  834.             throw $this->createNotFoundException(sprintf('unable to find the object with id: %s'$id));
  835.         }
  836.         $this->admin->checkAccess('acl'$object);
  837.         $this->admin->setSubject($object);
  838.         $aclUsers $this->getAclUsers();
  839.         $aclRoles $this->getAclRoles();
  840.         $adminObjectAclManipulator $this->get('sonata.admin.object.manipulator.acl.admin');
  841.         $adminObjectAclData = new AdminObjectAclData(
  842.             $this->admin,
  843.             $object,
  844.             $aclUsers,
  845.             $adminObjectAclManipulator->getMaskBuilderClass(),
  846.             $aclRoles
  847.         );
  848.         $aclUsersForm $adminObjectAclManipulator->createAclUsersForm($adminObjectAclData);
  849.         $aclRolesForm $adminObjectAclManipulator->createAclRolesForm($adminObjectAclData);
  850.         if ('POST' === $request->getMethod()) {
  851.             if ($request->request->has(AdminObjectAclManipulator::ACL_USERS_FORM_NAME)) {
  852.                 $form $aclUsersForm;
  853.                 $updateMethod 'updateAclUsers';
  854.             } elseif ($request->request->has(AdminObjectAclManipulator::ACL_ROLES_FORM_NAME)) {
  855.                 $form $aclRolesForm;
  856.                 $updateMethod 'updateAclRoles';
  857.             }
  858.             if (isset($form)) {
  859.                 $form->handleRequest($request);
  860.                 if ($form->isValid()) {
  861.                     $adminObjectAclManipulator->$updateMethod($adminObjectAclData);
  862.                     $this->addFlash(
  863.                         'sonata_flash_success',
  864.                         $this->trans('flash_acl_edit_success', [], 'SonataAdminBundle')
  865.                     );
  866.                     return new RedirectResponse($this->admin->generateObjectUrl('acl'$object));
  867.                 }
  868.             }
  869.         }
  870.         // NEXT_MAJOR: Remove this line and use commented line below it instead
  871.         $template $this->admin->getTemplate('acl');
  872.         // $template = $this->templateRegistry->getTemplate('acl');
  873.         return $this->renderWithExtraParams($template, [
  874.             'action' => 'acl',
  875.             'permissions' => $adminObjectAclData->getUserPermissions(),
  876.             'object' => $object,
  877.             'users' => $aclUsers,
  878.             'roles' => $aclRoles,
  879.             'aclUsersForm' => $aclUsersForm->createView(),
  880.             'aclRolesForm' => $aclRolesForm->createView(),
  881.         ], null);
  882.     }
  883.     /**
  884.      * @return Request
  885.      */
  886.     public function getRequest()
  887.     {
  888.         return $this->container->get('request_stack')->getCurrentRequest();
  889.     }
  890.     /**
  891.      * Gets a container configuration parameter by its name.
  892.      *
  893.      * @param string $name The parameter name
  894.      *
  895.      * @return mixed
  896.      */
  897.     protected function getParameter($name)
  898.     {
  899.         return $this->container->getParameter($name);
  900.     }
  901.     /**
  902.      * Render JSON.
  903.      *
  904.      * @param mixed $data
  905.      * @param int   $status
  906.      * @param array $headers
  907.      *
  908.      * @return JsonResponse with json encoded data
  909.      */
  910.     protected function renderJson($data$status 200$headers = [])
  911.     {
  912.         return new JsonResponse($data$status$headers);
  913.     }
  914.     /**
  915.      * Returns true if the request is a XMLHttpRequest.
  916.      *
  917.      * @return bool True if the request is an XMLHttpRequest, false otherwise
  918.      */
  919.     protected function isXmlHttpRequest()
  920.     {
  921.         $request $this->getRequest();
  922.         return $request->isXmlHttpRequest() || $request->get('_xml_http_request');
  923.     }
  924.     /**
  925.      * Returns the correct RESTful verb, given either by the request itself or
  926.      * via the "_method" parameter.
  927.      *
  928.      * @return string HTTP method, either
  929.      */
  930.     protected function getRestMethod()
  931.     {
  932.         $request $this->getRequest();
  933.         if (Request::getHttpMethodParameterOverride() || !$request->request->has('_method')) {
  934.             return $request->getMethod();
  935.         }
  936.         return $request->request->get('_method');
  937.     }
  938.     /**
  939.      * Contextualize the admin class depends on the current request.
  940.      *
  941.      * @throws \RuntimeException
  942.      */
  943.     protected function configure()
  944.     {
  945.         $request $this->getRequest();
  946.         $adminCode $request->get('_sonata_admin');
  947.         if (!$adminCode) {
  948.             throw new \RuntimeException(sprintf(
  949.                 'There is no `_sonata_admin` defined for the controller `%s` and the current route `%s`',
  950.                 static::class,
  951.                 $request->get('_route')
  952.             ));
  953.         }
  954.         $this->admin $this->container->get('sonata.admin.pool')->getAdminByAdminCode($adminCode);
  955.         if (!$this->admin) {
  956.             throw new \RuntimeException(sprintf(
  957.                 'Unable to find the admin class related to the current controller (%s)',
  958.                 static::class
  959.             ));
  960.         }
  961.         $this->templateRegistry $this->container->get($this->admin->getCode().'.template_registry');
  962.         if (!$this->templateRegistry instanceof TemplateRegistryInterface) {
  963.             throw new \RuntimeException(sprintf(
  964.                 'Unable to find the template registry related to the current admin (%s)',
  965.                 $this->admin->getCode()
  966.             ));
  967.         }
  968.         $rootAdmin $this->admin;
  969.         while ($rootAdmin->isChild()) {
  970.             $rootAdmin->setCurrentChild(true);
  971.             $rootAdmin $rootAdmin->getParent();
  972.         }
  973.         $rootAdmin->setRequest($request);
  974.         if ($request->get('uniqid')) {
  975.             $this->admin->setUniqid($request->get('uniqid'));
  976.         }
  977.     }
  978.     /**
  979.      * Proxy for the logger service of the container.
  980.      * If no such service is found, a NullLogger is returned.
  981.      *
  982.      * @return LoggerInterface
  983.      */
  984.     protected function getLogger()
  985.     {
  986.         if ($this->container->has('logger')) {
  987.             $logger $this->container->get('logger');
  988.             \assert($logger instanceof LoggerInterface);
  989.             return $logger;
  990.         }
  991.         return new NullLogger();
  992.     }
  993.     /**
  994.      * Returns the base template name.
  995.      *
  996.      * @return string The template name
  997.      */
  998.     protected function getBaseTemplate()
  999.     {
  1000.         if ($this->isXmlHttpRequest()) {
  1001.             // NEXT_MAJOR: Remove this line and use commented line below it instead
  1002.             return $this->admin->getTemplate('ajax');
  1003.             // return $this->templateRegistry->getTemplate('ajax');
  1004.         }
  1005.         // NEXT_MAJOR: Remove this line and use commented line below it instead
  1006.         return $this->admin->getTemplate('layout');
  1007.         // return $this->templateRegistry->getTemplate('layout');
  1008.     }
  1009.     /**
  1010.      * @throws \Exception
  1011.      */
  1012.     protected function handleModelManagerException(\Exception $e)
  1013.     {
  1014.         if ($this->get('kernel')->isDebug()) {
  1015.             throw $e;
  1016.         }
  1017.         $context = ['exception' => $e];
  1018.         if ($e->getPrevious()) {
  1019.             $context['previous_exception_message'] = $e->getPrevious()->getMessage();
  1020.         }
  1021.         $this->getLogger()->error($e->getMessage(), $context);
  1022.     }
  1023.     /**
  1024.      * Redirect the user depend on this choice.
  1025.      *
  1026.      * @param object $object
  1027.      *
  1028.      * @return RedirectResponse
  1029.      */
  1030.     protected function redirectTo($object)
  1031.     {
  1032.         $request $this->getRequest();
  1033.         $url false;
  1034.         if (null !== $request->get('btn_update_and_list')) {
  1035.             return $this->redirectToList();
  1036.         }
  1037.         if (null !== $request->get('btn_create_and_list')) {
  1038.             return $this->redirectToList();
  1039.         }
  1040.         if (null !== $request->get('btn_create_and_create')) {
  1041.             $params = [];
  1042.             if ($this->admin->hasActiveSubClass()) {
  1043.                 $params['subclass'] = $request->get('subclass');
  1044.             }
  1045.             $url $this->admin->generateUrl('create'$params);
  1046.         }
  1047.         if ('DELETE' === $this->getRestMethod()) {
  1048.             return $this->redirectToList();
  1049.         }
  1050.         if (!$url) {
  1051.             foreach (['edit''show'] as $route) {
  1052.                 if ($this->admin->hasRoute($route) && $this->admin->hasAccess($route$object)) {
  1053.                     $url $this->admin->generateObjectUrl(
  1054.                         $route,
  1055.                         $object,
  1056.                         $this->getSelectedTab($request)
  1057.                     );
  1058.                     break;
  1059.                 }
  1060.             }
  1061.         }
  1062.         if (!$url) {
  1063.             return $this->redirectToList();
  1064.         }
  1065.         return new RedirectResponse($url);
  1066.     }
  1067.     /**
  1068.      * Redirects the user to the list view.
  1069.      *
  1070.      * @return RedirectResponse
  1071.      */
  1072.     final protected function redirectToList()
  1073.     {
  1074.         $parameters = [];
  1075.         if ($filter $this->admin->getFilterParameters()) {
  1076.             $parameters['filter'] = $filter;
  1077.         }
  1078.         return $this->redirect($this->admin->generateUrl('list'$parameters));
  1079.     }
  1080.     /**
  1081.      * Returns true if the preview is requested to be shown.
  1082.      *
  1083.      * @return bool
  1084.      */
  1085.     protected function isPreviewRequested()
  1086.     {
  1087.         $request $this->getRequest();
  1088.         return null !== $request->get('btn_preview');
  1089.     }
  1090.     /**
  1091.      * Returns true if the preview has been approved.
  1092.      *
  1093.      * @return bool
  1094.      */
  1095.     protected function isPreviewApproved()
  1096.     {
  1097.         $request $this->getRequest();
  1098.         return null !== $request->get('btn_preview_approve');
  1099.     }
  1100.     /**
  1101.      * Returns true if the request is in the preview workflow.
  1102.      *
  1103.      * That means either a preview is requested or the preview has already been shown
  1104.      * and it got approved/declined.
  1105.      *
  1106.      * @return bool
  1107.      */
  1108.     protected function isInPreviewMode()
  1109.     {
  1110.         return $this->admin->supportsPreviewMode()
  1111.         && ($this->isPreviewRequested()
  1112.             || $this->isPreviewApproved()
  1113.             || $this->isPreviewDeclined());
  1114.     }
  1115.     /**
  1116.      * Returns true if the preview has been declined.
  1117.      *
  1118.      * @return bool
  1119.      */
  1120.     protected function isPreviewDeclined()
  1121.     {
  1122.         $request $this->getRequest();
  1123.         return null !== $request->get('btn_preview_decline');
  1124.     }
  1125.     /**
  1126.      * Gets ACL users.
  1127.      *
  1128.      * @return \Traversable
  1129.      */
  1130.     protected function getAclUsers()
  1131.     {
  1132.         $aclUsers = [];
  1133.         $userManagerServiceName $this->container->getParameter('sonata.admin.security.acl_user_manager');
  1134.         if (null !== $userManagerServiceName && $this->has($userManagerServiceName)) {
  1135.             $userManager $this->get($userManagerServiceName);
  1136.             if (method_exists($userManager'findUsers')) {
  1137.                 $aclUsers $userManager->findUsers();
  1138.             }
  1139.         }
  1140.         return \is_array($aclUsers) ? new \ArrayIterator($aclUsers) : $aclUsers;
  1141.     }
  1142.     /**
  1143.      * Gets ACL roles.
  1144.      *
  1145.      * @return \Traversable
  1146.      */
  1147.     protected function getAclRoles()
  1148.     {
  1149.         $aclRoles = [];
  1150.         $roleHierarchy $this->container->getParameter('security.role_hierarchy.roles');
  1151.         $pool $this->container->get('sonata.admin.pool');
  1152.         foreach ($pool->getAdminServiceIds() as $id) {
  1153.             try {
  1154.                 $admin $pool->getInstance($id);
  1155.             } catch (\Exception $e) {
  1156.                 continue;
  1157.             }
  1158.             $baseRole $admin->getSecurityHandler()->getBaseRole($admin);
  1159.             foreach ($admin->getSecurityInformation() as $role => $permissions) {
  1160.                 $role sprintf($baseRole$role);
  1161.                 $aclRoles[] = $role;
  1162.             }
  1163.         }
  1164.         foreach ($roleHierarchy as $name => $roles) {
  1165.             $aclRoles[] = $name;
  1166.             $aclRoles array_merge($aclRoles$roles);
  1167.         }
  1168.         $aclRoles array_unique($aclRoles);
  1169.         return \is_array($aclRoles) ? new \ArrayIterator($aclRoles) : $aclRoles;
  1170.     }
  1171.     /**
  1172.      * Validate CSRF token for action without form.
  1173.      *
  1174.      * @param string $intention
  1175.      *
  1176.      * @throws HttpException
  1177.      */
  1178.     protected function validateCsrfToken($intention)
  1179.     {
  1180.         if (false === $this->admin->getFormBuilder()->getOption('csrf_protection')) {
  1181.             return;
  1182.         }
  1183.         $request $this->getRequest();
  1184.         $token $request->get('_sonata_csrf_token');
  1185.         if ($this->container->has('security.csrf.token_manager')) {
  1186.             $valid $this->container->get('security.csrf.token_manager')->isTokenValid(new CsrfToken($intention$token));
  1187.         } else {
  1188.             return;
  1189.         }
  1190.         if (!$valid) {
  1191.             throw new HttpException(400'The csrf token is not valid, CSRF attack?');
  1192.         }
  1193.     }
  1194.     /**
  1195.      * Escape string for html output.
  1196.      *
  1197.      * @param string $s
  1198.      *
  1199.      * @return string
  1200.      */
  1201.     protected function escapeHtml($s)
  1202.     {
  1203.         return htmlspecialchars((string) $sENT_QUOTES ENT_SUBSTITUTE'UTF-8');
  1204.     }
  1205.     /**
  1206.      * Get CSRF token.
  1207.      *
  1208.      * @param string $intention
  1209.      *
  1210.      * @return string|false
  1211.      */
  1212.     protected function getCsrfToken($intention)
  1213.     {
  1214.         if ($this->container->has('security.csrf.token_manager')) {
  1215.             return $this->container->get('security.csrf.token_manager')->getToken($intention)->getValue();
  1216.         }
  1217.         return false;
  1218.     }
  1219.     /**
  1220.      * This method can be overloaded in your custom CRUD controller.
  1221.      * It's called from createAction.
  1222.      *
  1223.      * @param object $object
  1224.      *
  1225.      * @return Response|null
  1226.      */
  1227.     protected function preCreate(Request $request$object)
  1228.     {
  1229.         return null;
  1230.     }
  1231.     /**
  1232.      * This method can be overloaded in your custom CRUD controller.
  1233.      * It's called from editAction.
  1234.      *
  1235.      * @param object $object
  1236.      *
  1237.      * @return Response|null
  1238.      */
  1239.     protected function preEdit(Request $request$object)
  1240.     {
  1241.         return null;
  1242.     }
  1243.     /**
  1244.      * This method can be overloaded in your custom CRUD controller.
  1245.      * It's called from deleteAction.
  1246.      *
  1247.      * @param object $object
  1248.      *
  1249.      * @return Response|null
  1250.      */
  1251.     protected function preDelete(Request $request$object)
  1252.     {
  1253.         return null;
  1254.     }
  1255.     /**
  1256.      * This method can be overloaded in your custom CRUD controller.
  1257.      * It's called from showAction.
  1258.      *
  1259.      * @param object $object
  1260.      *
  1261.      * @return Response|null
  1262.      */
  1263.     protected function preShow(Request $request$object)
  1264.     {
  1265.         return null;
  1266.     }
  1267.     /**
  1268.      * This method can be overloaded in your custom CRUD controller.
  1269.      * It's called from listAction.
  1270.      *
  1271.      * @return Response|null
  1272.      */
  1273.     protected function preList(Request $request)
  1274.     {
  1275.         return null;
  1276.     }
  1277.     /**
  1278.      * Translate a message id.
  1279.      *
  1280.      * @param string $id
  1281.      * @param string $domain
  1282.      * @param string $locale
  1283.      *
  1284.      * @return string translated string
  1285.      */
  1286.     final protected function trans($id, array $parameters = [], $domain null$locale null)
  1287.     {
  1288.         $domain $domain ?: $this->admin->getTranslationDomain();
  1289.         return $this->get('translator')->trans($id$parameters$domain$locale);
  1290.     }
  1291.     private function getSelectedTab(Request $request)
  1292.     {
  1293.         return array_filter(['_tab' => $request->request->get('_tab')]);
  1294.     }
  1295.     private function checkParentChildAssociation(Request $request$object): void
  1296.     {
  1297.         if (!($parentAdmin $this->admin->getParent())) {
  1298.             return;
  1299.         }
  1300.         // NEXT_MAJOR: remove this check
  1301.         if (!$this->admin->getParentAssociationMapping()) {
  1302.             return;
  1303.         }
  1304.         $parentId $request->get($parentAdmin->getIdParameter());
  1305.         $propertyAccessor PropertyAccess::createPropertyAccessor();
  1306.         $propertyPath = new PropertyPath($this->admin->getParentAssociationMapping());
  1307.         if ($parentAdmin->getObject($parentId) !== $propertyAccessor->getValue($object$propertyPath)) {
  1308.             // NEXT_MAJOR: make this exception
  1309.             @trigger_error("Accessing a child that isn't connected to a given parent is deprecated since 3.34"
  1310.                 ." and won't be allowed in 4.0.",
  1311.                 E_USER_DEPRECATED
  1312.             );
  1313.         }
  1314.     }
  1315.     /**
  1316.      * Sets the admin form theme to form view. Used for compatibility between Symfony versions.
  1317.      */
  1318.     private function setFormTheme(FormView $formView, array $theme null): void
  1319.     {
  1320.         $twig $this->get('twig');
  1321.         // BC for Symfony < 3.2 where this runtime does not exists
  1322.         if (!method_exists(AppVariable::class, 'getToken')) {
  1323.             $twig->getExtension(FormExtension::class)->renderer->setTheme($formView$theme);
  1324.             return;
  1325.         }
  1326.         // BC for Symfony < 3.4 where runtime should be TwigRenderer
  1327.         if (!method_exists(DebugCommand::class, 'getLoaderPaths')) {
  1328.             $twig->getRuntime(TwigRenderer::class)->setTheme($formView$theme);
  1329.             return;
  1330.         }
  1331.         $twig->getRuntime(FormRenderer::class)->setTheme($formView$theme);
  1332.     }
  1333.     private function handleXmlHttpRequestErrorResponse(Request $requestFormInterface $form): ?JsonResponse
  1334.     {
  1335.         if ('application/json' !== $request->headers->get('Accept')) {
  1336.             @trigger_error('In next major version response will return 406 NOT ACCEPTABLE without `Accept: application/json`'E_USER_DEPRECATED);
  1337.             return null;
  1338.         }
  1339.         $errors = [];
  1340.         foreach ($form->getErrors(true) as $error) {
  1341.             $errors[] = $error->getMessage();
  1342.         }
  1343.         return $this->renderJson([
  1344.             'result' => 'error',
  1345.             'errors' => $errors,
  1346.         ], 400);
  1347.     }
  1348.     /**
  1349.      * @param object $object
  1350.      */
  1351.     private function handleXmlHttpRequestSuccessResponse(Request $request$object): JsonResponse
  1352.     {
  1353.         if ('application/json' !== $request->headers->get('Accept')) {
  1354.             @trigger_error('In next major version response will return 406 NOT ACCEPTABLE without `Accept: application/json`'E_USER_DEPRECATED);
  1355.         }
  1356.         return $this->renderJson([
  1357.             'result' => 'ok',
  1358.             'objectId' => $this->admin->getNormalizedIdentifier($object),
  1359.             'objectName' => $this->escapeHtml($this->admin->toString($object)),
  1360.         ], 200);
  1361.     }
  1362. }