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

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