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

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