vendor/sonata-project/admin-bundle/src/Admin/AbstractAdmin.php line 1861

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\Admin;
  12. use Knp\Menu\ItemInterface;
  13. use Sonata\AdminBundle\BCLayer\BCHelper;
  14. use Sonata\AdminBundle\Datagrid\DatagridInterface;
  15. use Sonata\AdminBundle\Datagrid\DatagridMapper;
  16. use Sonata\AdminBundle\Datagrid\ListMapper;
  17. use Sonata\AdminBundle\Datagrid\ProxyQueryInterface;
  18. use Sonata\AdminBundle\DependencyInjection\Admin\AbstractTaggedAdmin;
  19. use Sonata\AdminBundle\Exception\AdminClassNotFoundException;
  20. use Sonata\AdminBundle\FieldDescription\FieldDescriptionCollection;
  21. use Sonata\AdminBundle\FieldDescription\FieldDescriptionInterface;
  22. use Sonata\AdminBundle\Form\FormMapper;
  23. use Sonata\AdminBundle\Form\Type\ModelHiddenType;
  24. use Sonata\AdminBundle\Manipulator\ObjectManipulator;
  25. use Sonata\AdminBundle\Model\ProxyResolverInterface;
  26. use Sonata\AdminBundle\Object\Metadata;
  27. use Sonata\AdminBundle\Object\MetadataInterface;
  28. use Sonata\AdminBundle\Route\RouteCollection;
  29. use Sonata\AdminBundle\Route\RouteCollectionInterface;
  30. use Sonata\AdminBundle\Security\Acl\Permission\AdminPermissionMap;
  31. use Sonata\AdminBundle\Security\Handler\AclSecurityHandlerInterface;
  32. use Sonata\AdminBundle\Show\ShowMapper;
  33. use Sonata\AdminBundle\Util\Instantiator;
  34. use Sonata\AdminBundle\Util\ParametersManipulator;
  35. use Symfony\Component\Form\Extension\Core\Type\HiddenType;
  36. use Symfony\Component\Form\FormBuilderInterface;
  37. use Symfony\Component\Form\FormEvent;
  38. use Symfony\Component\Form\FormEvents;
  39. use Symfony\Component\Form\FormInterface;
  40. use Symfony\Component\HttpFoundation\Request;
  41. use Symfony\Component\PropertyAccess\Exception\UninitializedPropertyException;
  42. use Symfony\Component\PropertyAccess\PropertyAccess;
  43. use Symfony\Component\Routing\Generator\UrlGeneratorInterface as RoutingUrlGeneratorInterface;
  44. use Symfony\Component\Security\Acl\Model\DomainObjectInterface;
  45. use Symfony\Component\Security\Core\Exception\AccessDeniedException;
  46. /**
  47.  * @author Thomas Rabaix <thomas.rabaix@sonata-project.org>
  48.  *
  49.  * @phpstan-template T of object
  50.  * @phpstan-extends AbstractTaggedAdmin<T>
  51.  * @phpstan-implements AdminInterface<T>
  52.  */
  53. abstract class AbstractAdmin extends AbstractTaggedAdmin implements AdminInterfaceDomainObjectInterfaceAdminTreeInterface
  54. {
  55.     // NEXT_MAJOR: Remove the CONTEXT constants.
  56.     /** @deprecated */
  57.     public const CONTEXT_MENU 'menu';
  58.     /** @deprecated */
  59.     public const CONTEXT_DASHBOARD 'dashboard';
  60.     public const CLASS_REGEX =
  61.         '@
  62.         (?:([A-Za-z0-9]*)\\\)?        # vendor name / app name
  63.         (Bundle\\\)?                  # optional bundle directory
  64.         ([A-Za-z0-9]+?)(?:Bundle)?\\\ # bundle name, with optional suffix
  65.         (
  66.             Entity|Document|Model|PHPCR|CouchDocument|Phpcr|
  67.             Doctrine\\\Orm|Doctrine\\\Phpcr|Doctrine\\\MongoDB|Doctrine\\\CouchDB
  68.         )\\\(.*)@x';
  69.     private const ACTION_TREE 1;
  70.     private const ACTION_SHOW 2;
  71.     private const ACTION_EDIT 4;
  72.     private const ACTION_DELETE 8;
  73.     private const ACTION_ACL 16;
  74.     private const ACTION_HISTORY 32;
  75.     private const ACTION_LIST 64;
  76.     private const ACTION_BATCH 128;
  77.     private const INTERNAL_ACTIONS = [
  78.         'tree' => self::ACTION_TREE,
  79.         'show' => self::ACTION_SHOW,
  80.         'edit' => self::ACTION_EDIT,
  81.         'delete' => self::ACTION_DELETE,
  82.         'acl' => self::ACTION_ACL,
  83.         'history' => self::ACTION_HISTORY,
  84.         'list' => self::ACTION_LIST,
  85.         'batch' => self::ACTION_BATCH,
  86.     ];
  87.     private const MASK_OF_ACTION_CREATE self::ACTION_TREE self::ACTION_SHOW self::ACTION_EDIT self::ACTION_DELETE self::ACTION_LIST self::ACTION_BATCH;
  88.     private const MASK_OF_ACTION_SHOW self::ACTION_EDIT self::ACTION_HISTORY self::ACTION_ACL;
  89.     private const MASK_OF_ACTION_EDIT self::ACTION_SHOW self::ACTION_DELETE self::ACTION_ACL self::ACTION_HISTORY;
  90.     private const MASK_OF_ACTION_HISTORY self::ACTION_SHOW self::ACTION_EDIT self::ACTION_ACL;
  91.     private const MASK_OF_ACTION_ACL self::ACTION_EDIT self::ACTION_HISTORY;
  92.     private const MASK_OF_ACTION_LIST self::ACTION_SHOW self::ACTION_EDIT self::ACTION_DELETE self::ACTION_ACL self::ACTION_BATCH;
  93.     private const MASK_OF_ACTIONS_USING_OBJECT self::MASK_OF_ACTION_SHOW self::MASK_OF_ACTION_EDIT self::MASK_OF_ACTION_HISTORY self::MASK_OF_ACTION_ACL;
  94.     private const DEFAULT_LIST_PER_PAGE_RESULTS 25;
  95.     private const DEFAULT_LIST_PER_PAGE_OPTIONS = [102550100250];
  96.     /**
  97.      * @deprecated since sonata-project/admin-bundle 4.15, will be removed in 5.0.
  98.      *
  99.      * The base route name used to generate the routing information.
  100.      *
  101.      * @var string|null
  102.      */
  103.     protected $baseRouteName;
  104.     /**
  105.      * @deprecated since sonata-project/admin-bundle 4.15, will be removed in 5.0.
  106.      *
  107.      * The base route pattern used to generate the routing information.
  108.      *
  109.      * @var string|null
  110.      */
  111.     protected $baseRoutePattern;
  112.     /**
  113.      * The label class name  (used in the title/breadcrumb ...).
  114.      *
  115.      * @var string|null
  116.      */
  117.     protected $classnameLabel;
  118.     /**
  119.      * Setting to true will enable preview mode for
  120.      * the entity and show a preview button in the
  121.      * edit/create forms.
  122.      *
  123.      * @var bool
  124.      */
  125.     protected $supportsPreviewMode false;
  126.     /**
  127.      * The list FieldDescription constructed from the configureListField method.
  128.      *
  129.      * @var array<string, FieldDescriptionInterface>
  130.      */
  131.     private array $listFieldDescriptions = [];
  132.     /**
  133.      * The show FieldDescription constructed from the configureShowFields method.
  134.      *
  135.      * @var FieldDescriptionInterface[]
  136.      */
  137.     private array $showFieldDescriptions = [];
  138.     /**
  139.      * The list FieldDescription constructed from the configureFormField method.
  140.      *
  141.      * @var FieldDescriptionInterface[]
  142.      */
  143.     private array $formFieldDescriptions = [];
  144.     /**
  145.      * The filter FieldDescription constructed from the configureFilterField method.
  146.      *
  147.      * @var FieldDescriptionInterface[]
  148.      */
  149.     private array $filterFieldDescriptions = [];
  150.     /**
  151.      * The maximum number of page numbers to display in the list.
  152.      */
  153.     private int $maxPageLinks 25;
  154.     /**
  155.      * The translation domain to be used to translate messages.
  156.      */
  157.     private string $translationDomain 'messages';
  158.     /**
  159.      * Array of routes related to this admin.
  160.      */
  161.     private ?RouteCollectionInterface $routes null;
  162.     /**
  163.      * The subject only set in edit/update/create mode.
  164.      *
  165.      * @phpstan-var T|null
  166.      */
  167.     private ?object $subject null;
  168.     /**
  169.      * Define a Collection of child admin, ie /admin/order/{id}/order-element/{childId}.
  170.      *
  171.      * @var array<string, AdminInterface<object>>
  172.      */
  173.     private array $children = [];
  174.     /**
  175.      * Reference the parent admin.
  176.      *
  177.      * @var AdminInterface<object>|null
  178.      */
  179.     private ?AdminInterface $parent null;
  180.     /**
  181.      * Reference the parent FieldDescription related to this admin
  182.      * only set for FieldDescription which is associated to an Sub Admin instance.
  183.      */
  184.     private ?FieldDescriptionInterface $parentFieldDescription null;
  185.     /**
  186.      * If true then the current admin is part of the nested admin set (from the url).
  187.      */
  188.     private bool $currentChild false;
  189.     /**
  190.      * The uniqId is used to avoid clashing with 2 admin related to the code
  191.      * ie: a Block linked to a Block.
  192.      */
  193.     private ?string $uniqId null;
  194.     /**
  195.      * The current request object.
  196.      */
  197.     private ?Request $request null;
  198.     /**
  199.      * @phpstan-var DatagridInterface<ProxyQueryInterface<T>>|null
  200.      */
  201.     private ?DatagridInterface $datagrid null;
  202.     private ?ItemInterface $menu null;
  203.     /**
  204.      * @var string[]
  205.      */
  206.     private array $formTheme = [];
  207.     /**
  208.      * @var string[]
  209.      */
  210.     private array $filterTheme = [];
  211.     /**
  212.      * @var AdminExtensionInterface[]
  213.      *
  214.      * @phpstan-var array<AdminExtensionInterface<T>>
  215.      */
  216.     private array $extensions = [];
  217.     /**
  218.      * @var array<string, bool>
  219.      */
  220.     private array $cacheIsGranted = [];
  221.     /**
  222.      * @var array<string, string|null>
  223.      */
  224.     private array $parentAssociationMapping = [];
  225.     /**
  226.      * The subclasses supported by the admin class.
  227.      *
  228.      * @var string[]
  229.      *
  230.      * @phpstan-var array<string, class-string<T>>
  231.      */
  232.     private array $subClasses = [];
  233.     /**
  234.      * The list collection.
  235.      *
  236.      * @var FieldDescriptionCollection<FieldDescriptionInterface>|null
  237.      */
  238.     private ?FieldDescriptionCollection $list null;
  239.     /**
  240.      * @var FieldDescriptionCollection<FieldDescriptionInterface>|null
  241.      */
  242.     private ?FieldDescriptionCollection $show null;
  243.     private ?FormInterface $form null;
  244.     /**
  245.      * The cached base route name.
  246.      */
  247.     private ?string $cachedBaseRouteName null;
  248.     /**
  249.      * The cached base route pattern.
  250.      */
  251.     private ?string $cachedBaseRoutePattern null;
  252.     /**
  253.      * The form group disposition.
  254.      *
  255.      * @var array<string, array<string, mixed>>
  256.      */
  257.     private array $formGroups = [];
  258.     /**
  259.      * The form tabs disposition.
  260.      *
  261.      * @var array<string, array<string, mixed>>
  262.      */
  263.     private array $formTabs = [];
  264.     /**
  265.      * The view group disposition.
  266.      *
  267.      * @var array<string, array<string, mixed>>
  268.      */
  269.     private array $showGroups = [];
  270.     /**
  271.      * The view tab disposition.
  272.      *
  273.      * @var array<string, array<string, mixed>>
  274.      */
  275.     private array $showTabs = [];
  276.     /**
  277.      * @var array<string, bool>
  278.      */
  279.     private array $loaded = [
  280.         'routes' => false,
  281.         'tab_menu' => false,
  282.         'show' => false,
  283.         'list' => false,
  284.         'form' => false,
  285.         'datagrid' => false,
  286.     ];
  287.     public function getExportFormats(): array
  288.     {
  289.         return [];
  290.     }
  291.     final public function getExportFields(): array
  292.     {
  293.         $fields $this->configureExportFields();
  294.         foreach ($this->getExtensions() as $extension) {
  295.             $fields $extension->configureExportFields($this$fields);
  296.         }
  297.         return $fields;
  298.     }
  299.     final public function getDataSourceIterator(): \Iterator
  300.     {
  301.         $datagrid $this->getDatagrid();
  302.         $datagrid->buildPager();
  303.         $fields = [];
  304.         foreach ($this->getExportFields() as $key => $field) {
  305.             if (!\is_string($key)) {
  306.                 $label $this->getTranslationLabel($field'export''label');
  307.                 $key $this->getTranslator()->trans($label, [], $this->getTranslationDomain());
  308.             }
  309.             $fields[$key] = $field;
  310.         }
  311.         $query $datagrid->getQuery();
  312.         return $this->getDataSource()->createIterator($query$fields);
  313.     }
  314.     final public function initialize(): void
  315.     {
  316.         if (null === $this->classnameLabel) {
  317.             $namespaceSeparatorPos strrpos($this->getClass(), '\\');
  318.             $this->classnameLabel false !== $namespaceSeparatorPos
  319.                 substr($this->getClass(), $namespaceSeparatorPos 1)
  320.                 : $this->getClass();
  321.         }
  322.         $this->configure();
  323.         foreach ($this->getExtensions() as $extension) {
  324.             $extension->configure($this);
  325.         }
  326.     }
  327.     final public function update(object $object): object
  328.     {
  329.         $this->preUpdate($object);
  330.         foreach ($this->getExtensions() as $extension) {
  331.             $extension->preUpdate($this$object);
  332.         }
  333.         $this->getModelManager()->update($object);
  334.         $this->postUpdate($object);
  335.         foreach ($this->getExtensions() as $extension) {
  336.             $extension->postUpdate($this$object);
  337.         }
  338.         return $object;
  339.     }
  340.     final public function create(object $object): object
  341.     {
  342.         $this->prePersist($object);
  343.         foreach ($this->getExtensions() as $extension) {
  344.             $extension->prePersist($this$object);
  345.         }
  346.         $this->getModelManager()->create($object);
  347.         $this->postPersist($object);
  348.         foreach ($this->getExtensions() as $extension) {
  349.             $extension->postPersist($this$object);
  350.         }
  351.         $this->createObjectSecurity($object);
  352.         return $object;
  353.     }
  354.     final public function delete(object $object): void
  355.     {
  356.         $this->preRemove($object);
  357.         foreach ($this->getExtensions() as $extension) {
  358.             $extension->preRemove($this$object);
  359.         }
  360.         $this->getSecurityHandler()->deleteObjectSecurity($this$object);
  361.         $this->getModelManager()->delete($object);
  362.         $this->postRemove($object);
  363.         foreach ($this->getExtensions() as $extension) {
  364.             $extension->postRemove($this$object);
  365.         }
  366.     }
  367.     public function preBatchAction(string $actionNameProxyQueryInterface $query, array &$idxbool $allElements false): void
  368.     {
  369.     }
  370.     final public function getDefaultFilterParameters(): array
  371.     {
  372.         return array_merge(
  373.             $this->getDefaultSortValues(),
  374.             $this->getDefaultFilterValues()
  375.         );
  376.     }
  377.     final public function getFilterParameters(): array
  378.     {
  379.         $parameters $this->getDefaultFilterParameters();
  380.         // build the values array
  381.         if ($this->hasRequest()) {
  382.             $bag $this->getRequest()->query;
  383.             $filters $bag->all('filter');
  384.             if (isset($filters[DatagridInterface::PAGE])) {
  385.                 $filters[DatagridInterface::PAGE] = (int) $filters[DatagridInterface::PAGE];
  386.             }
  387.             if (isset($filters[DatagridInterface::PER_PAGE])) {
  388.                 $filters[DatagridInterface::PER_PAGE] = (int) $filters[DatagridInterface::PER_PAGE];
  389.             }
  390.             // if filter persistence is configured
  391.             if ($this->hasFilterPersister()) {
  392.                 // if reset filters is asked, remove from storage
  393.                 if ('reset' === $this->getRequest()->query->get('filters')) {
  394.                     $this->getFilterPersister()->reset($this->getCode());
  395.                 }
  396.                 // if no filters, fetch from storage
  397.                 // otherwise save to storage
  398.                 if ([] === $filters) {
  399.                     $filters $this->getFilterPersister()->get($this->getCode());
  400.                 } else {
  401.                     $this->getFilterPersister()->set($this->getCode(), $filters);
  402.                 }
  403.             }
  404.             $parameters ParametersManipulator::merge($parameters$filters);
  405.             // always force the parent value
  406.             if ($this->isChild()) {
  407.                 $parentAssociationMapping $this->getParentAssociationMapping();
  408.                 if (null !== $parentAssociationMapping) {
  409.                     $name str_replace('.''__'$parentAssociationMapping);
  410.                     $parameters[$name] = ['value' => $this->getRequest()->get($this->getParent()->getIdParameter())];
  411.                 }
  412.             }
  413.         }
  414.         if (
  415.             !isset($parameters[DatagridInterface::PER_PAGE])
  416.             || !\is_int($parameters[DatagridInterface::PER_PAGE])
  417.             || !$this->determinedPerPageValue($parameters[DatagridInterface::PER_PAGE])
  418.         ) {
  419.             $parameters[DatagridInterface::PER_PAGE] = $this->getMaxPerPage();
  420.         }
  421.         $parameters $this->configureFilterParameters($parameters);
  422.         foreach ($this->getExtensions() as $extension) {
  423.             $parameters $extension->configureFilterParameters($this$parameters);
  424.         }
  425.         return $parameters;
  426.     }
  427.     /**
  428.      * Returns the name of the parent related field, so the field can be use to set the default
  429.      * value (ie the parent object) or to filter the object.
  430.      *
  431.      * @throws \LogicException
  432.      */
  433.     final public function getParentAssociationMapping(): ?string
  434.     {
  435.         if (!$this->isChild()) {
  436.             throw new \LogicException(sprintf(
  437.                 'Admin "%s" has no parent.',
  438.                 static::class
  439.             ));
  440.         }
  441.         $parent $this->getParent()->getCode();
  442.         return $this->parentAssociationMapping[$parent];
  443.     }
  444.     final public function getBaseRoutePattern(): string
  445.     {
  446.         if (null !== $this->cachedBaseRoutePattern) {
  447.             return $this->cachedBaseRoutePattern;
  448.         }
  449.         if ($this->isChild()) { // the admin class is a child, prefix it with the parent route pattern
  450.             $this->cachedBaseRoutePattern sprintf(
  451.                 '%s/%s/%s',
  452.                 $this->getParent()->getBaseRoutePattern(),
  453.                 $this->getParent()->getRouterIdParameter(),
  454.                 $this->generateBaseRoutePattern(true)
  455.             );
  456.         } else {
  457.             $this->cachedBaseRoutePattern $this->generateBaseRoutePattern();
  458.         }
  459.         return $this->cachedBaseRoutePattern;
  460.     }
  461.     /**
  462.      * Returns the baseRouteName used to generate the routing information.
  463.      *
  464.      * @return string the baseRouteName used to generate the routing information
  465.      */
  466.     final public function getBaseRouteName(): string
  467.     {
  468.         if (null !== $this->cachedBaseRouteName) {
  469.             return $this->cachedBaseRouteName;
  470.         }
  471.         if ($this->isChild()) { // the admin class is a child, prefix it with the parent route name
  472.             $this->cachedBaseRouteName sprintf(
  473.                 '%s_%s',
  474.                 $this->getParent()->getBaseRouteName(),
  475.                 $this->generateBaseRouteName(true)
  476.             );
  477.         } else {
  478.             $this->cachedBaseRouteName $this->generateBaseRouteName();
  479.         }
  480.         return $this->cachedBaseRouteName;
  481.     }
  482.     final public function getClass(): string
  483.     {
  484.         if ($this->hasActiveSubClass()) {
  485.             if ($this->hasParentFieldDescription()) {
  486.                 throw new \LogicException('Feature not implemented: an embedded admin cannot have subclass');
  487.             }
  488.             $subClass $this->getRequest()->query->get('subclass');
  489.             \assert(\is_string($subClass));
  490.             if (!$this->hasSubClass($subClass)) {
  491.                 throw new \LogicException(sprintf('Subclass "%s" is not defined.'$subClass));
  492.             }
  493.             return $this->getSubClass($subClass);
  494.         }
  495.         // Do not use `$this->hasSubject()` and `$this->getSubject()` here to avoid infinite loop.
  496.         // `getSubject` use `hasSubject()` which use `getObject()` which use `getClass()`.
  497.         if (null !== $this->subject) {
  498.             $modelManager $this->getModelManager();
  499.             /** @phpstan-var class-string<T> $class */
  500.             $class $modelManager instanceof ProxyResolverInterface
  501.                 $modelManager->getRealClass($this->subject)
  502.                 // NEXT_MAJOR: Change to `\get_class($this->subject)` instead
  503.                 BCHelper::getClass($this->subject);
  504.             return $class;
  505.         }
  506.         return $this->getModelClass();
  507.     }
  508.     final public function getSubClasses(): array
  509.     {
  510.         return $this->subClasses;
  511.     }
  512.     final public function setSubClasses(array $subClasses): void
  513.     {
  514.         $this->subClasses $subClasses;
  515.     }
  516.     final public function hasSubClass(string $name): bool
  517.     {
  518.         return isset($this->subClasses[$name]);
  519.     }
  520.     final public function hasActiveSubClass(): bool
  521.     {
  522.         if (\count($this->subClasses) > && $this->hasRequest()) {
  523.             return \is_string($this->getRequest()->query->get('subclass'));
  524.         }
  525.         return false;
  526.     }
  527.     final public function getActiveSubClass(): string
  528.     {
  529.         if (!$this->hasActiveSubClass()) {
  530.             throw new \LogicException(sprintf(
  531.                 'Admin "%s" has no active subclass.',
  532.                 static::class
  533.             ));
  534.         }
  535.         return $this->getSubClass($this->getActiveSubclassCode());
  536.     }
  537.     final public function getActiveSubclassCode(): string
  538.     {
  539.         if (!$this->hasActiveSubClass()) {
  540.             throw new \LogicException(sprintf(
  541.                 'Admin "%s" has no active subclass.',
  542.                 static::class
  543.             ));
  544.         }
  545.         $subClass = (string) $this->getRequest()->query->get('subclass');
  546.         if (!$this->hasSubClass($subClass)) {
  547.             throw new \LogicException(sprintf(
  548.                 'Admin "%s" has no active subclass.',
  549.                 static::class
  550.             ));
  551.         }
  552.         return $subClass;
  553.     }
  554.     final public function getBatchActions(): array
  555.     {
  556.         if (!$this->hasRoute('batch')) {
  557.             return [];
  558.         }
  559.         $actions = [];
  560.         if ($this->hasRoute('delete') && $this->hasAccess('delete')) {
  561.             $actions['delete'] = [
  562.                 'label' => 'action_delete',
  563.                 'translation_domain' => 'SonataAdminBundle',
  564.                 'ask_confirmation' => true// by default always true
  565.             ];
  566.         }
  567.         $actions $this->configureBatchActions($actions);
  568.         foreach ($this->getExtensions() as $extension) {
  569.             $actions $extension->configureBatchActions($this$actions);
  570.         }
  571.         foreach ($actions as $name => &$action) {
  572.             if (!\array_key_exists('label'$action)) {
  573.                 $action['label'] = $this->getTranslationLabel($name'batch''label');
  574.             }
  575.             if (!\array_key_exists('translation_domain'$action)) {
  576.                 $action['translation_domain'] = $this->getTranslationDomain();
  577.             }
  578.         }
  579.         return $actions;
  580.     }
  581.     final public function getRoutes(): RouteCollectionInterface
  582.     {
  583.         $routes $this->buildRoutes();
  584.         if (null === $routes) {
  585.             throw new \LogicException('Cannot access routes during the building process.');
  586.         }
  587.         return $routes;
  588.     }
  589.     public function getRouterIdParameter(): string
  590.     {
  591.         return sprintf('{%s}'$this->getIdParameter());
  592.     }
  593.     public function getIdParameter(): string
  594.     {
  595.         $parameter 'id';
  596.         for ($i 0$i $this->getChildDepth(); ++$i) {
  597.             $parameter sprintf('child%s'ucfirst($parameter));
  598.         }
  599.         return $parameter;
  600.     }
  601.     final public function hasRoute(string $name): bool
  602.     {
  603.         return $this->getRouteGenerator()->hasAdminRoute($this$name);
  604.     }
  605.     final public function isCurrentRoute(string $name, ?string $adminCode null): bool
  606.     {
  607.         if (!$this->hasRequest()) {
  608.             return false;
  609.         }
  610.         $request $this->getRequest();
  611.         $route $request->get('_route');
  612.         if (null !== $adminCode) {
  613.             $pool $this->getConfigurationPool();
  614.             if ($pool->hasAdminByAdminCode($adminCode)) {
  615.                 $admin $pool->getAdminByAdminCode($adminCode);
  616.             } else {
  617.                 return false;
  618.             }
  619.         } else {
  620.             $admin $this;
  621.         }
  622.         return $admin->getRoutes()->getRouteName($name) === $route;
  623.     }
  624.     final public function generateObjectUrl(string $nameobject $object, array $parameters = [], int $referenceType RoutingUrlGeneratorInterface::ABSOLUTE_PATH): string
  625.     {
  626.         $parameters[$this->getIdParameter()] = $this->getUrlSafeIdentifier($object);
  627.         return $this->generateUrl($name$parameters$referenceType);
  628.     }
  629.     final public function generateUrl(string $name, array $parameters = [], int $referenceType RoutingUrlGeneratorInterface::ABSOLUTE_PATH): string
  630.     {
  631.         return $this->getRouteGenerator()->generateUrl($this$name$parameters$referenceType);
  632.     }
  633.     final public function generateMenuUrl(string $name, array $parameters = [], int $referenceType RoutingUrlGeneratorInterface::ABSOLUTE_PATH): array
  634.     {
  635.         return $this->getRouteGenerator()->generateMenuUrl($this$name$parameters$referenceType);
  636.     }
  637.     final public function getNewInstance(): object
  638.     {
  639.         $object $this->createNewInstance();
  640.         $this->alterNewInstance($object);
  641.         foreach ($this->getExtensions() as $extension) {
  642.             $extension->alterNewInstance($this$object);
  643.         }
  644.         return $object;
  645.     }
  646.     final public function getFormBuilder(): FormBuilderInterface
  647.     {
  648.         $formBuilder $this->getFormContractor()->getFormBuilder(
  649.             $this->getUniqId(),
  650.             ['data_class' => $this->getClass()] + $this->getFormOptions(),
  651.         );
  652.         $this->defineFormBuilder($formBuilder);
  653.         return $formBuilder;
  654.     }
  655.     /**
  656.      * This method is being called by the main admin class and the child class,
  657.      * the getFormBuilder is only call by the main admin class.
  658.      */
  659.     final public function defineFormBuilder(FormBuilderInterface $formBuilder): void
  660.     {
  661.         if (!$this->hasSubject()) {
  662.             throw new \LogicException(sprintf(
  663.                 'Admin "%s" has no subject.',
  664.                 static::class
  665.             ));
  666.         }
  667.         $mapper = new FormMapper($this->getFormContractor(), $formBuilder$this);
  668.         $this->configureFormFields($mapper);
  669.         foreach ($this->getExtensions() as $extension) {
  670.             $extension->configureFormFields($mapper);
  671.         }
  672.     }
  673.     final public function attachAdminClass(FieldDescriptionInterface $fieldDescription): void
  674.     {
  675.         $pool $this->getConfigurationPool();
  676.         try {
  677.             $admin $pool->getAdminByFieldDescription($fieldDescription);
  678.         } catch (AdminClassNotFoundException) {
  679.             // Using a fieldDescription with no admin class for the target model is a valid case.
  680.             // Since there is no easy way to check for this case, we catch the exception instead.
  681.             return;
  682.         }
  683.         if ($this->hasRequest()) {
  684.             $admin->setRequest($this->getRequest());
  685.         }
  686.         $fieldDescription->setAssociationAdmin($admin);
  687.     }
  688.     /**
  689.      * @param string|int|null $id
  690.      *
  691.      * @phpstan-return T|null
  692.      */
  693.     final public function getObject($id): ?object
  694.     {
  695.         if (null === $id) {
  696.             return null;
  697.         }
  698.         $object $this->getModelManager()->find($this->getClass(), $id);
  699.         if (null === $object) {
  700.             return null;
  701.         }
  702.         $this->alterObject($object);
  703.         foreach ($this->getExtensions() as $extension) {
  704.             $extension->alterObject($this$object);
  705.         }
  706.         return $object;
  707.     }
  708.     final public function getForm(): FormInterface
  709.     {
  710.         $form $this->buildForm();
  711.         if (null === $form) {
  712.             throw new \LogicException('Cannot access form during the building process.');
  713.         }
  714.         return $form;
  715.     }
  716.     final public function getList(): FieldDescriptionCollection
  717.     {
  718.         $list $this->buildList();
  719.         if (null === $list) {
  720.             throw new \LogicException('Cannot access list during the building process.');
  721.         }
  722.         return $list;
  723.     }
  724.     final public function createQuery(): ProxyQueryInterface
  725.     {
  726.         $query $this->getModelManager()->createQuery($this->getClass());
  727.         $query $this->configureQuery($query);
  728.         foreach ($this->getExtensions() as $extension) {
  729.             $extension->configureQuery($this$query);
  730.         }
  731.         return $query;
  732.     }
  733.     final public function getDatagrid(): DatagridInterface
  734.     {
  735.         $datagrid $this->buildDatagrid();
  736.         if (null === $datagrid) {
  737.             throw new \LogicException('Cannot access datagrid during the building process.');
  738.         }
  739.         return $datagrid;
  740.     }
  741.     final public function getSideMenu(string $action, ?AdminInterface $childAdmin null): ItemInterface
  742.     {
  743.         if ($this->isChild()) {
  744.             return $this->getParent()->getSideMenu($action$this);
  745.         }
  746.         $menu $this->buildTabMenu($action$childAdmin);
  747.         if (null === $menu) {
  748.             throw new \LogicException('Cannot access menu during the building process.');
  749.         }
  750.         return $menu;
  751.     }
  752.     final public function getRootCode(): string
  753.     {
  754.         return $this->getRoot()->getCode();
  755.     }
  756.     final public function getRoot(): AdminInterface
  757.     {
  758.         if (!$this->hasParentFieldDescription()) {
  759.             return $this;
  760.         }
  761.         return $this->getParentFieldDescription()->getAdmin()->getRoot();
  762.     }
  763.     final public function getMaxPerPage(): int
  764.     {
  765.         $sortValues $this->getDefaultSortValues();
  766.         return $sortValues[DatagridInterface::PER_PAGE] ?? self::DEFAULT_LIST_PER_PAGE_RESULTS;
  767.     }
  768.     final public function setMaxPageLinks(int $maxPageLinks): void
  769.     {
  770.         $this->maxPageLinks $maxPageLinks;
  771.     }
  772.     final public function getMaxPageLinks(): int
  773.     {
  774.         return $this->maxPageLinks;
  775.     }
  776.     final public function getFormGroups(): array
  777.     {
  778.         return $this->formGroups;
  779.     }
  780.     final public function setFormGroups(array $formGroups): void
  781.     {
  782.         $this->formGroups $formGroups;
  783.     }
  784.     final public function removeFieldFromFormGroup(string $key): void
  785.     {
  786.         foreach ($this->formGroups as $name => $_formGroup) {
  787.             unset($this->formGroups[$name]['fields'][$key]);
  788.             if ([] === $this->formGroups[$name]['fields']) {
  789.                 unset($this->formGroups[$name]);
  790.             }
  791.         }
  792.     }
  793.     final public function reorderFormGroup(string $group, array $keys): void
  794.     {
  795.         $formGroups $this->getFormGroups();
  796.         $formGroups[$group]['fields'] = array_merge(array_flip($keys), $formGroups[$group]['fields']);
  797.         $this->setFormGroups($formGroups);
  798.     }
  799.     final public function getFormTabs(): array
  800.     {
  801.         return $this->formTabs;
  802.     }
  803.     final public function setFormTabs(array $formTabs): void
  804.     {
  805.         $this->formTabs $formTabs;
  806.     }
  807.     final public function getShowTabs(): array
  808.     {
  809.         return $this->showTabs;
  810.     }
  811.     final public function setShowTabs(array $showTabs): void
  812.     {
  813.         $this->showTabs $showTabs;
  814.     }
  815.     final public function getShowGroups(): array
  816.     {
  817.         return $this->showGroups;
  818.     }
  819.     final public function setShowGroups(array $showGroups): void
  820.     {
  821.         $this->showGroups $showGroups;
  822.     }
  823.     final public function removeFieldFromShowGroup(string $key): void
  824.     {
  825.         foreach ($this->showGroups as $name => $_showGroup) {
  826.             unset($this->showGroups[$name]['fields'][$key]);
  827.             if ([] === $this->showGroups[$name]['fields']) {
  828.                 unset($this->showGroups[$name]);
  829.             }
  830.         }
  831.     }
  832.     final public function reorderShowGroup(string $group, array $keys): void
  833.     {
  834.         $showGroups $this->getShowGroups();
  835.         $showGroups[$group]['fields'] = array_merge(array_flip($keys), $showGroups[$group]['fields']);
  836.         $this->setShowGroups($showGroups);
  837.     }
  838.     final public function setParentFieldDescription(FieldDescriptionInterface $parentFieldDescription): void
  839.     {
  840.         $this->parentFieldDescription $parentFieldDescription;
  841.     }
  842.     final public function getParentFieldDescription(): FieldDescriptionInterface
  843.     {
  844.         if (!$this->hasParentFieldDescription()) {
  845.             throw new \LogicException(sprintf(
  846.                 'Admin "%s" has no parent field description.',
  847.                 static::class
  848.             ));
  849.         }
  850.         return $this->parentFieldDescription;
  851.     }
  852.     /**
  853.      * @phpstan-assert-if-true !null $this->parentFieldDescription
  854.      */
  855.     final public function hasParentFieldDescription(): bool
  856.     {
  857.         return null !== $this->parentFieldDescription;
  858.     }
  859.     final public function setSubject(?object $subject): void
  860.     {
  861.         if (null !== $subject && !is_a($subject$this->getModelClass(), true)) {
  862.             throw new \LogicException(sprintf(
  863.                 'Admin "%s" does not allow this subject: %s, use the one register with this admin class %s',
  864.                 static::class,
  865.                 $subject::class,
  866.                 $this->getModelClass()
  867.             ));
  868.         }
  869.         $this->subject $subject;
  870.     }
  871.     final public function getSubject(): object
  872.     {
  873.         if (!$this->hasSubject()) {
  874.             throw new \LogicException(sprintf(
  875.                 'Admin "%s" has no subject.',
  876.                 static::class
  877.             ));
  878.         }
  879.         return $this->subject;
  880.     }
  881.     /**
  882.      * @phpstan-assert-if-true !null $this->subject
  883.      */
  884.     final public function hasSubject(): bool
  885.     {
  886.         if (null === $this->subject && $this->hasRequest() && !$this->hasParentFieldDescription()) {
  887.             $id $this->getRequest()->get($this->getIdParameter());
  888.             if (null !== $id) {
  889.                 $this->subject $this->getObject($id);
  890.             }
  891.         }
  892.         return null !== $this->subject;
  893.     }
  894.     final public function getFormFieldDescriptions(): array
  895.     {
  896.         $this->buildForm();
  897.         return $this->formFieldDescriptions;
  898.     }
  899.     final public function getFormFieldDescription(string $name): FieldDescriptionInterface
  900.     {
  901.         $this->buildForm();
  902.         if (!$this->hasFormFieldDescription($name)) {
  903.             throw new \LogicException(sprintf(
  904.                 'Admin "%s" has no form field description for the field %s.',
  905.                 static::class,
  906.                 $name
  907.             ));
  908.         }
  909.         return $this->formFieldDescriptions[$name];
  910.     }
  911.     /**
  912.      * Returns true if the admin has a FieldDescription with the given $name.
  913.      */
  914.     final public function hasFormFieldDescription(string $name): bool
  915.     {
  916.         $this->buildForm();
  917.         return \array_key_exists($name$this->formFieldDescriptions);
  918.     }
  919.     final public function addFormFieldDescription(string $nameFieldDescriptionInterface $fieldDescription): void
  920.     {
  921.         $this->formFieldDescriptions[$name] = $fieldDescription;
  922.     }
  923.     /**
  924.      * remove a FieldDescription.
  925.      */
  926.     final public function removeFormFieldDescription(string $name): void
  927.     {
  928.         unset($this->formFieldDescriptions[$name]);
  929.     }
  930.     /**
  931.      * build and return the collection of form FieldDescription.
  932.      *
  933.      * @return FieldDescriptionInterface[] collection of form FieldDescription
  934.      */
  935.     final public function getShowFieldDescriptions(): array
  936.     {
  937.         $this->buildShow();
  938.         return $this->showFieldDescriptions;
  939.     }
  940.     /**
  941.      * Returns the form FieldDescription with the given $name.
  942.      */
  943.     final public function getShowFieldDescription(string $name): FieldDescriptionInterface
  944.     {
  945.         $this->buildShow();
  946.         if (!$this->hasShowFieldDescription($name)) {
  947.             throw new \LogicException(sprintf(
  948.                 'Admin "%s" has no show field description for the field %s.',
  949.                 static::class,
  950.                 $name
  951.             ));
  952.         }
  953.         return $this->showFieldDescriptions[$name];
  954.     }
  955.     final public function hasShowFieldDescription(string $name): bool
  956.     {
  957.         $this->buildShow();
  958.         return \array_key_exists($name$this->showFieldDescriptions);
  959.     }
  960.     final public function addShowFieldDescription(string $nameFieldDescriptionInterface $fieldDescription): void
  961.     {
  962.         $this->showFieldDescriptions[$name] = $fieldDescription;
  963.     }
  964.     final public function removeShowFieldDescription(string $name): void
  965.     {
  966.         unset($this->showFieldDescriptions[$name]);
  967.     }
  968.     final public function getListFieldDescriptions(): array
  969.     {
  970.         $this->buildList();
  971.         return $this->listFieldDescriptions;
  972.     }
  973.     final public function getListFieldDescription(string $name): FieldDescriptionInterface
  974.     {
  975.         $this->buildList();
  976.         if (!$this->hasListFieldDescription($name)) {
  977.             throw new \LogicException(sprintf(
  978.                 'Admin "%s" has no list field description for %s.',
  979.                 static::class,
  980.                 $name
  981.             ));
  982.         }
  983.         return $this->listFieldDescriptions[$name];
  984.     }
  985.     final public function hasListFieldDescription(string $name): bool
  986.     {
  987.         $this->buildList();
  988.         return \array_key_exists($name$this->listFieldDescriptions);
  989.     }
  990.     final public function addListFieldDescription(string $nameFieldDescriptionInterface $fieldDescription): void
  991.     {
  992.         $this->listFieldDescriptions[$name] = $fieldDescription;
  993.     }
  994.     final public function removeListFieldDescription(string $name): void
  995.     {
  996.         unset($this->listFieldDescriptions[$name]);
  997.     }
  998.     final public function getFilterFieldDescription(string $name): FieldDescriptionInterface
  999.     {
  1000.         $this->buildDatagrid();
  1001.         if (!$this->hasFilterFieldDescription($name)) {
  1002.             throw new \LogicException(sprintf(
  1003.                 'Admin "%s" has no filter field description for the field %s.',
  1004.                 static::class,
  1005.                 $name
  1006.             ));
  1007.         }
  1008.         return $this->filterFieldDescriptions[$name];
  1009.     }
  1010.     final public function hasFilterFieldDescription(string $name): bool
  1011.     {
  1012.         $this->buildDatagrid();
  1013.         return \array_key_exists($name$this->filterFieldDescriptions);
  1014.     }
  1015.     final public function addFilterFieldDescription(string $nameFieldDescriptionInterface $fieldDescription): void
  1016.     {
  1017.         $this->filterFieldDescriptions[$name] = $fieldDescription;
  1018.     }
  1019.     final public function removeFilterFieldDescription(string $name): void
  1020.     {
  1021.         unset($this->filterFieldDescriptions[$name]);
  1022.     }
  1023.     final public function getFilterFieldDescriptions(): array
  1024.     {
  1025.         $this->buildDatagrid();
  1026.         return $this->filterFieldDescriptions;
  1027.     }
  1028.     /**
  1029.      * @psalm-suppress PossiblyNullArgument Will be solved in NEXT_MAJOR
  1030.      */
  1031.     final public function addChild(AdminInterface $child, ?string $field null): void
  1032.     {
  1033.         $parentAdmin $this;
  1034.         while ($parentAdmin->isChild() && $parentAdmin->getCode() !== $child->getCode()) {
  1035.             $parentAdmin $parentAdmin->getParent();
  1036.         }
  1037.         if ($parentAdmin->getCode() === $child->getCode()) {
  1038.             throw new \LogicException(sprintf(
  1039.                 'Circular reference detected! The child admin `%s` is already in the parent tree of the `%s` admin.',
  1040.                 $child->getCode(),
  1041.                 $this->getCode()
  1042.             ));
  1043.         }
  1044.         $this->children[$child->getCode()] = $child;
  1045.         // @phpstan-ignore-next-line Will be solved in NEXT_MAJOR
  1046.         $child->setParent($this$field);
  1047.     }
  1048.     final public function hasChild(string $code): bool
  1049.     {
  1050.         return isset($this->children[$code]);
  1051.     }
  1052.     final public function getChildren(): array
  1053.     {
  1054.         return $this->children;
  1055.     }
  1056.     final public function getChild(string $code): AdminInterface
  1057.     {
  1058.         if (!$this->hasChild($code)) {
  1059.             throw new \LogicException(sprintf(
  1060.                 'Admin "%s" has no child for the code %s.',
  1061.                 static::class,
  1062.                 $code
  1063.             ));
  1064.         }
  1065.         return $this->getChildren()[$code];
  1066.     }
  1067.     final public function setParent(AdminInterface $parent, ?string $parentAssociationMapping null): void
  1068.     {
  1069.         $this->parent $parent;
  1070.         $this->parentAssociationMapping[$parent->getCode()] = $parentAssociationMapping;
  1071.     }
  1072.     final public function getParent(): AdminInterface
  1073.     {
  1074.         if (null === $this->parent) {
  1075.             throw new \LogicException(sprintf(
  1076.                 'Admin "%s" has no parent.',
  1077.                 static::class
  1078.             ));
  1079.         }
  1080.         return $this->parent;
  1081.     }
  1082.     final public function getRootAncestor(): AdminInterface
  1083.     {
  1084.         $parent $this;
  1085.         while ($parent->isChild()) {
  1086.             $parent $parent->getParent();
  1087.         }
  1088.         return $parent;
  1089.     }
  1090.     final public function getChildDepth(): int
  1091.     {
  1092.         $parent $this;
  1093.         $depth 0;
  1094.         while ($parent->isChild()) {
  1095.             $parent $parent->getParent();
  1096.             ++$depth;
  1097.         }
  1098.         return $depth;
  1099.     }
  1100.     final public function getCurrentLeafChildAdmin(): ?AdminInterface
  1101.     {
  1102.         $child $this->getCurrentChildAdmin();
  1103.         if (null === $child) {
  1104.             return null;
  1105.         }
  1106.         for ($c $childnull !== $c$c $child->getCurrentChildAdmin()) {
  1107.             $child $c;
  1108.         }
  1109.         return $child;
  1110.     }
  1111.     final public function isChild(): bool
  1112.     {
  1113.         return $this->parent instanceof AdminInterface;
  1114.     }
  1115.     /**
  1116.      * Returns true if the admin has children, false otherwise.
  1117.      *
  1118.      * @phpstan-assert-if-true non-empty-array $this->children
  1119.      */
  1120.     final public function hasChildren(): bool
  1121.     {
  1122.         return \count($this->children) > 0;
  1123.     }
  1124.     final public function setUniqId(string $uniqId): void
  1125.     {
  1126.         $this->uniqId $uniqId;
  1127.     }
  1128.     final public function getUniqId(): string
  1129.     {
  1130.         if (null === $this->uniqId) {
  1131.             $this->uniqId sprintf('s%s'uniqid());
  1132.         }
  1133.         return $this->uniqId;
  1134.     }
  1135.     final public function getClassnameLabel(): string
  1136.     {
  1137.         if (null === $this->classnameLabel) {
  1138.             throw new \LogicException(sprintf(
  1139.                 'Admin "%s" has no classname label. Did you forgot to initialize the admin ?',
  1140.                 static::class
  1141.             ));
  1142.         }
  1143.         return $this->classnameLabel;
  1144.     }
  1145.     final public function getPersistentParameters(): array
  1146.     {
  1147.         $parameters $this->configurePersistentParameters();
  1148.         foreach ($this->getExtensions() as $extension) {
  1149.             $parameters $extension->configurePersistentParameters($this$parameters);
  1150.         }
  1151.         return $parameters;
  1152.     }
  1153.     final public function getPersistentParameter(string $name$default null)
  1154.     {
  1155.         $parameters $this->getPersistentParameters();
  1156.         return $parameters[$name] ?? $default;
  1157.     }
  1158.     final public function setCurrentChild(bool $currentChild): void
  1159.     {
  1160.         $this->currentChild $currentChild;
  1161.     }
  1162.     final public function isCurrentChild(): bool
  1163.     {
  1164.         return $this->currentChild;
  1165.     }
  1166.     final public function getCurrentChildAdmin(): ?AdminInterface
  1167.     {
  1168.         foreach ($this->getChildren() as $child) {
  1169.             if ($child->isCurrentChild()) {
  1170.                 return $child;
  1171.             }
  1172.         }
  1173.         return null;
  1174.     }
  1175.     final public function setTranslationDomain(string $translationDomain): void
  1176.     {
  1177.         $this->translationDomain $translationDomain;
  1178.     }
  1179.     final public function getTranslationDomain(): string
  1180.     {
  1181.         return $this->translationDomain;
  1182.     }
  1183.     final public function getTranslationLabel(string $labelstring $context ''string $type ''): string
  1184.     {
  1185.         return $this->getLabelTranslatorStrategy()->getLabel($label$context$type);
  1186.     }
  1187.     final public function setRequest(Request $request): void
  1188.     {
  1189.         $this->request $request;
  1190.         foreach ($this->getChildren() as $children) {
  1191.             $children->setRequest($request);
  1192.         }
  1193.     }
  1194.     final public function getRequest(): Request
  1195.     {
  1196.         if (!$this->hasRequest()) {
  1197.             throw new \LogicException('The Request object has not been set');
  1198.         }
  1199.         return $this->request;
  1200.     }
  1201.     /**
  1202.      * @phpstan-assert-if-true !null $this->request
  1203.      */
  1204.     final public function hasRequest(): bool
  1205.     {
  1206.         return null !== $this->request;
  1207.     }
  1208.     final public function getBaseCodeRoute(): string
  1209.     {
  1210.         if ($this->isChild()) {
  1211.             return $this->getParent()->getBaseCodeRoute().'|'.$this->getCode();
  1212.         }
  1213.         return $this->getCode();
  1214.     }
  1215.     /**
  1216.      * @return string
  1217.      */
  1218.     public function getObjectIdentifier()
  1219.     {
  1220.         return $this->getCode();
  1221.     }
  1222.     public function showInDashboard(): bool
  1223.     {
  1224.         // NEXT_MAJOR: Remove those lines and uncomment the last one.
  1225.         $permissionShow $this->getPermissionsShow(self::CONTEXT_DASHBOARD'sonata_deprecation_mute');
  1226.         $permission === \count($permissionShow) ? reset($permissionShow) : $permissionShow;
  1227.         return $this->isGranted($permission);
  1228.         // return $this->isGranted('LIST');
  1229.     }
  1230.     /**
  1231.      * NEXT_MAJOR: Remove this method.
  1232.      *
  1233.      * @deprecated since sonata-project/admin-bundle version 4.7 use showInDashboard instead
  1234.      */
  1235.     final public function showIn(string $context): bool
  1236.     {
  1237.         if ('sonata_deprecation_mute' !== (\func_get_args()[1] ?? null)) {
  1238.             @trigger_error(sprintf(
  1239.                 'The "%s()" method is deprecated since sonata-project/admin-bundle version 4.7 and will be'
  1240.                 .' removed in 5.0 version. Use showInDashboard() instead.',
  1241.                 __METHOD__
  1242.             ), \E_USER_DEPRECATED);
  1243.         }
  1244.         $permissionShow $this->getPermissionsShow($context'sonata_deprecation_mute');
  1245.         // Avoid isGranted deprecation if there is only one permission show.
  1246.         $permission === \count($permissionShow) ? reset($permissionShow) : $permissionShow;
  1247.         return $this->isGranted($permission);
  1248.     }
  1249.     final public function createObjectSecurity(object $object): void
  1250.     {
  1251.         $this->getSecurityHandler()->createObjectSecurity($this$object);
  1252.     }
  1253.     final public function isGranted($name, ?object $object null): bool
  1254.     {
  1255.         if (\is_array($name)) {
  1256.             @trigger_error(
  1257.                 sprintf(
  1258.                     'Passing an array as argument 1 of "%s()" is deprecated since sonata-project/admin-bundle 4.6'
  1259.                     .' and will throw an error in 5.0. You MUST pass a string instead.',
  1260.                     __METHOD__
  1261.                 ),
  1262.                 \E_USER_DEPRECATED
  1263.             );
  1264.         }
  1265.         $objectRef null !== $object sprintf('/%s#%s'spl_object_hash($object), $this->id($object) ?? '') : '';
  1266.         $key md5(json_encode($name\JSON_THROW_ON_ERROR).$objectRef);
  1267.         if (!\array_key_exists($key$this->cacheIsGranted)) {
  1268.             $this->cacheIsGranted[$key] = $this->getSecurityHandler()->isGranted($this$name$object ?? $this);
  1269.         }
  1270.         return $this->cacheIsGranted[$key];
  1271.     }
  1272.     final public function getUrlSafeIdentifier(object $model): ?string
  1273.     {
  1274.         return $this->getModelManager()->getUrlSafeIdentifier($model);
  1275.     }
  1276.     final public function getNormalizedIdentifier(object $model): ?string
  1277.     {
  1278.         return $this->getModelManager()->getNormalizedIdentifier($model);
  1279.     }
  1280.     public function id(object $model): ?string
  1281.     {
  1282.         return $this->getNormalizedIdentifier($model);
  1283.     }
  1284.     final public function getShow(): FieldDescriptionCollection
  1285.     {
  1286.         $show $this->buildShow();
  1287.         if (null === $show) {
  1288.             throw new \LogicException('Cannot access show during the building process.');
  1289.         }
  1290.         return $show;
  1291.     }
  1292.     final public function setFormTheme(array $formTheme): void
  1293.     {
  1294.         $this->formTheme $formTheme;
  1295.     }
  1296.     final public function getFormTheme(): array
  1297.     {
  1298.         return $this->formTheme;
  1299.     }
  1300.     final public function setFilterTheme(array $filterTheme): void
  1301.     {
  1302.         $this->filterTheme $filterTheme;
  1303.     }
  1304.     final public function getFilterTheme(): array
  1305.     {
  1306.         return $this->filterTheme;
  1307.     }
  1308.     final public function addExtension(AdminExtensionInterface $extension): void
  1309.     {
  1310.         $this->extensions[] = $extension;
  1311.     }
  1312.     /**
  1313.      * @phpstan-param AdminExtensionInterface<T> $extension
  1314.      */
  1315.     final public function removeExtension(AdminExtensionInterface $extension): void
  1316.     {
  1317.         $key array_search($extension$this->extensionstrue);
  1318.         if (false === $key) {
  1319.             throw new \InvalidArgumentException(
  1320.                 sprintf('The extension "%s" was not set to the "%s" admin.'$extension::class, self::class)
  1321.             );
  1322.         }
  1323.         unset($this->extensions[$key]);
  1324.     }
  1325.     final public function getExtensions(): array
  1326.     {
  1327.         return $this->extensions;
  1328.     }
  1329.     public function toString(object $object): string
  1330.     {
  1331.         if (method_exists($object'__toString') && null !== $object->__toString()) {
  1332.             return $object->__toString();
  1333.         }
  1334.         $modelManager $this->getModelManager();
  1335.         if ($modelManager instanceof ProxyResolverInterface) {
  1336.             $class $modelManager->getRealClass($object);
  1337.         } else {
  1338.             // NEXT_MAJOR: Change to `\get_class($object)`
  1339.             $class BCHelper::getClass($object);
  1340.         }
  1341.         return sprintf('%s:%s'$classspl_object_hash($object));
  1342.     }
  1343.     final public function supportsPreviewMode(): bool
  1344.     {
  1345.         return $this->supportsPreviewMode;
  1346.     }
  1347.     /**
  1348.      * Returns predefined per page options.
  1349.      *
  1350.      * @return array<int>
  1351.      */
  1352.     public function getPerPageOptions(): array
  1353.     {
  1354.         $perPageOptions self::DEFAULT_LIST_PER_PAGE_OPTIONS;
  1355.         $perPageOptions[] = $this->getMaxPerPage();
  1356.         $perPageOptions array_unique($perPageOptions);
  1357.         sort($perPageOptions);
  1358.         return $perPageOptions;
  1359.     }
  1360.     /**
  1361.      * Returns true if the per page value is allowed, false otherwise.
  1362.      */
  1363.     final public function determinedPerPageValue(int $perPage): bool
  1364.     {
  1365.         return \in_array($perPage$this->getPerPageOptions(), true);
  1366.     }
  1367.     final public function isAclEnabled(): bool
  1368.     {
  1369.         return $this->getSecurityHandler() instanceof AclSecurityHandlerInterface;
  1370.     }
  1371.     public function getObjectMetadata(object $object): MetadataInterface
  1372.     {
  1373.         return new Metadata($this->toString($object));
  1374.     }
  1375.     final public function setListMode(string $mode): void
  1376.     {
  1377.         $this->getRequest()->getSession()->set(sprintf('%s.list_mode'$this->getCode()), $mode);
  1378.     }
  1379.     final public function getListMode(): string
  1380.     {
  1381.         $defaultListMode array_keys($this->getListModes())[0];
  1382.         if (!$this->hasRequest() || !$this->getRequest()->hasSession()) {
  1383.             return $defaultListMode;
  1384.         }
  1385.         return $this->getRequest()->getSession()->get(sprintf('%s.list_mode'$this->getCode()), $defaultListMode);
  1386.     }
  1387.     final public function checkAccess(string $action, ?object $object null): void
  1388.     {
  1389.         $access $this->getAccess();
  1390.         if (!\array_key_exists($action$access)) {
  1391.             throw new \InvalidArgumentException(sprintf(
  1392.                 'Action "%s" could not be found in access mapping.'
  1393.                 .' Please make sure your action is defined into your admin class accessMapping property.',
  1394.                 $action
  1395.             ));
  1396.         }
  1397.         if (!\is_array($access[$action])) {
  1398.             $access[$action] = [$access[$action]];
  1399.         }
  1400.         foreach ($access[$action] as $role) {
  1401.             if (false === $this->isGranted($role$object)) {
  1402.                 throw new AccessDeniedException(sprintf('Access Denied to the action %s and role %s'$action$role));
  1403.             }
  1404.         }
  1405.     }
  1406.     final public function hasAccess(string $action, ?object $object null): bool
  1407.     {
  1408.         $access $this->getAccess();
  1409.         if (!\array_key_exists($action$access)) {
  1410.             return false;
  1411.         }
  1412.         if (!\is_array($access[$action])) {
  1413.             $access[$action] = [$access[$action]];
  1414.         }
  1415.         foreach ($access[$action] as $role) {
  1416.             if (false === $this->isGranted($role$object)) {
  1417.                 return false;
  1418.             }
  1419.         }
  1420.         return true;
  1421.     }
  1422.     /**
  1423.      * @return array<string, array<string, mixed>>
  1424.      *
  1425.      * @phpstan-param T|null $object
  1426.      */
  1427.     final public function getActionButtons(string $action, ?object $object null): array
  1428.     {
  1429.         $defaultButtonList $this->getDefaultActionButtons($action$object);
  1430.         $buttonList $this->configureActionButtons($defaultButtonList$action$object);
  1431.         foreach ($this->getExtensions() as $extension) {
  1432.             $buttonList $extension->configureActionButtons($this$buttonList$action$object);
  1433.         }
  1434.         return $buttonList;
  1435.     }
  1436.     /**
  1437.      * Get the list of actions that can be accessed directly from the dashboard.
  1438.      *
  1439.      * @return array<string, array<string, mixed>>
  1440.      */
  1441.     final public function getDashboardActions(): array
  1442.     {
  1443.         $actions = [];
  1444.         if ($this->hasRoute('create') && $this->hasAccess('create')) {
  1445.             $actions['create'] = [
  1446.                 'label' => 'link_add',
  1447.                 'translation_domain' => 'SonataAdminBundle',
  1448.                 'template' => $this->getTemplateRegistry()->getTemplate('action_create'),
  1449.                 'url' => $this->generateUrl('create'),
  1450.                 'icon' => 'fas fa-plus-circle',
  1451.             ];
  1452.         }
  1453.         if ($this->hasRoute('list') && $this->hasAccess('list')) {
  1454.             $actions['list'] = [
  1455.                 'label' => 'link_list',
  1456.                 'translation_domain' => 'SonataAdminBundle',
  1457.                 'url' => $this->generateUrl('list'),
  1458.                 'icon' => 'fas fa-list',
  1459.             ];
  1460.         }
  1461.         $actions $this->configureDashboardActions($actions);
  1462.         foreach ($this->getExtensions() as $extension) {
  1463.             $actions $extension->configureDashboardActions($this$actions);
  1464.         }
  1465.         return $actions;
  1466.     }
  1467.     final public function createFieldDescription(string $propertyName, array $options = []): FieldDescriptionInterface
  1468.     {
  1469.         $fieldDescriptionFactory $this->getFieldDescriptionFactory();
  1470.         $fieldDescription $fieldDescriptionFactory->create($this->getClass(), $propertyName$options);
  1471.         $fieldDescription->setAdmin($this);
  1472.         return $fieldDescription;
  1473.     }
  1474.     /**
  1475.      * Hook to run after initialization.
  1476.      */
  1477.     protected function configure(): void
  1478.     {
  1479.     }
  1480.     protected function generateBaseRoutePattern(bool $isChildAdmin false): string
  1481.     {
  1482.         // NEXT_MAJOR: Remove this code
  1483.         if (null !== $this->baseRoutePattern) {
  1484.             @trigger_error(sprintf(
  1485.                 'Overriding the baseRoutePattern property is deprecated since sonata-project/admin-bundle 4.15.'
  1486.                 .' You MUST override the method %s() instead.',
  1487.                 __METHOD__
  1488.             ), \E_USER_DEPRECATED);
  1489.             return $this->baseRoutePattern;
  1490.         }
  1491.         preg_match(self::CLASS_REGEX$this->getModelClass(), $matches);
  1492.         if ([] === $matches) {
  1493.             throw new \LogicException(sprintf(
  1494.                 'Please define a default `baseRoutePattern` value for the admin class `%s`',
  1495.                 static::class
  1496.             ));
  1497.         }
  1498.         if ($isChildAdmin) {
  1499.             return $this->urlize($matches[5], '-');
  1500.         }
  1501.         return sprintf(
  1502.             '/%s%s/%s',
  1503.             '' === $matches[1] ? '' $this->urlize($matches[1], '-').'/',
  1504.             $this->urlize($matches[3], '-'),
  1505.             $this->urlize($matches[5], '-')
  1506.         );
  1507.     }
  1508.     protected function generateBaseRouteName(bool $isChildAdmin false): string
  1509.     {
  1510.         // NEXT_MAJOR: Remove this code
  1511.         if (null !== $this->baseRouteName) {
  1512.             @trigger_error(sprintf(
  1513.                 'Overriding the baseRouteName property is deprecated since sonata-project/admin-bundle 4.15.'
  1514.                 .' You MUST override the method %s() instead.',
  1515.                 __METHOD__
  1516.             ), \E_USER_DEPRECATED);
  1517.             return $this->baseRouteName;
  1518.         }
  1519.         preg_match(self::CLASS_REGEX$this->getModelClass(), $matches);
  1520.         if ([] === $matches) {
  1521.             throw new \LogicException(sprintf(
  1522.                 'Cannot automatically determine base route name,'
  1523.                 .' please define a default `baseRouteName` value for the admin class `%s`',
  1524.                 static::class
  1525.             ));
  1526.         }
  1527.         if ($isChildAdmin) {
  1528.             return $this->urlize($matches[5]);
  1529.         }
  1530.         return sprintf(
  1531.             'admin_%s%s_%s',
  1532.             '' === $matches[1] ? '' $this->urlize($matches[1]).'_',
  1533.             $this->urlize($matches[3]),
  1534.             $this->urlize($matches[5])
  1535.         );
  1536.     }
  1537.     /**
  1538.      * @phpstan-return T
  1539.      */
  1540.     protected function createNewInstance(): object
  1541.     {
  1542.         $object Instantiator::instantiate($this->getClass());
  1543.         $this->appendParentObject($object);
  1544.         return $object;
  1545.     }
  1546.     /**
  1547.      * @phpstan-param T $object
  1548.      */
  1549.     protected function alterNewInstance(object $object): void
  1550.     {
  1551.     }
  1552.     /**
  1553.      * @phpstan-param T $object
  1554.      */
  1555.     protected function alterObject(object $object): void
  1556.     {
  1557.     }
  1558.     /**
  1559.      * @phpstan-param T $object
  1560.      */
  1561.     protected function preValidate(object $object): void
  1562.     {
  1563.     }
  1564.     /**
  1565.      * @phpstan-param T $object
  1566.      */
  1567.     protected function preUpdate(object $object): void
  1568.     {
  1569.     }
  1570.     /**
  1571.      * @phpstan-param T $object
  1572.      */
  1573.     protected function postUpdate(object $object): void
  1574.     {
  1575.     }
  1576.     /**
  1577.      * @phpstan-param T $object
  1578.      */
  1579.     protected function prePersist(object $object): void
  1580.     {
  1581.     }
  1582.     /**
  1583.      * @phpstan-param T $object
  1584.      */
  1585.     protected function postPersist(object $object): void
  1586.     {
  1587.     }
  1588.     /**
  1589.      * @phpstan-param T $object
  1590.      */
  1591.     protected function preRemove(object $object): void
  1592.     {
  1593.     }
  1594.     /**
  1595.      * @phpstan-param T $object
  1596.      */
  1597.     protected function postRemove(object $object): void
  1598.     {
  1599.     }
  1600.     /**
  1601.      * @return array<string, mixed>
  1602.      */
  1603.     protected function configurePersistentParameters(): array
  1604.     {
  1605.         return [];
  1606.     }
  1607.     /**
  1608.      * @return string[]
  1609.      */
  1610.     protected function configureExportFields(): array
  1611.     {
  1612.         return $this->getModelManager()->getExportFields($this->getClass());
  1613.     }
  1614.     /**
  1615.      * @param ProxyQueryInterface<T> $query
  1616.      *
  1617.      * @return ProxyQueryInterface<T>
  1618.      */
  1619.     protected function configureQuery(ProxyQueryInterface $query): ProxyQueryInterface
  1620.     {
  1621.         return $query;
  1622.     }
  1623.     /**
  1624.      * urlize the given word.
  1625.      *
  1626.      * @param string $sep the separator
  1627.      */
  1628.     final protected function urlize(string $wordstring $sep '_'): string
  1629.     {
  1630.         return strtolower(preg_replace('/[^a-z0-9_]/i'$sep.'$1'$word) ?? '');
  1631.     }
  1632.     /**
  1633.      * @param array<string, mixed> $parameters
  1634.      *
  1635.      * @return array<string, mixed>
  1636.      */
  1637.     protected function configureFilterParameters(array $parameters): array
  1638.     {
  1639.         return $parameters;
  1640.     }
  1641.     /**
  1642.      * Returns a list of default sort values.
  1643.      *
  1644.      * @phpstan-return array{
  1645.      *     _page?: int,
  1646.      *     _per_page?: int,
  1647.      *     _sort_by?: string,
  1648.      *     _sort_order?: string
  1649.      * }
  1650.      */
  1651.     final protected function getDefaultSortValues(): array
  1652.     {
  1653.         $defaultSortValues = [DatagridInterface::PAGE => 1DatagridInterface::PER_PAGE => self::DEFAULT_LIST_PER_PAGE_RESULTS];
  1654.         $this->configureDefaultSortValues($defaultSortValues);
  1655.         foreach ($this->getExtensions() as $extension) {
  1656.             $extension->configureDefaultSortValues($this$defaultSortValues);
  1657.         }
  1658.         return $defaultSortValues;
  1659.     }
  1660.     /**
  1661.      * Returns a list of default filters.
  1662.      *
  1663.      * @return array<string, array<string, mixed>>
  1664.      */
  1665.     final protected function getDefaultFilterValues(): array
  1666.     {
  1667.         $defaultFilterValues = [];
  1668.         $this->configureDefaultFilterValues($defaultFilterValues);
  1669.         foreach ($this->getExtensions() as $extension) {
  1670.             $extension->configureDefaultFilterValues($this$defaultFilterValues);
  1671.         }
  1672.         return $defaultFilterValues;
  1673.     }
  1674.     /**
  1675.      * @return array<string, mixed>
  1676.      */
  1677.     final protected function getFormOptions(): array
  1678.     {
  1679.         $formOptions = [];
  1680.         $this->configureFormOptions($formOptions);
  1681.         foreach ($this->getExtensions() as $extension) {
  1682.             $extension->configureFormOptions($this$formOptions);
  1683.         }
  1684.         return $formOptions;
  1685.     }
  1686.     /**
  1687.      * @phpstan-param FormMapper<T> $form
  1688.      */
  1689.     protected function configureFormFields(FormMapper $form): void
  1690.     {
  1691.     }
  1692.     /**
  1693.      * @phpstan-param ListMapper<T> $list
  1694.      */
  1695.     protected function configureListFields(ListMapper $list): void
  1696.     {
  1697.     }
  1698.     /**
  1699.      * @phpstan-param DatagridMapper<T> $filter
  1700.      */
  1701.     protected function configureDatagridFilters(DatagridMapper $filter): void
  1702.     {
  1703.     }
  1704.     /**
  1705.      * @phpstan-param ShowMapper<T> $show
  1706.      */
  1707.     protected function configureShowFields(ShowMapper $show): void
  1708.     {
  1709.     }
  1710.     protected function configureRoutes(RouteCollectionInterface $collection): void
  1711.     {
  1712.     }
  1713.     /**
  1714.      * @param array<string, array<string, mixed>> $buttonList
  1715.      *
  1716.      * @return array<string, array<string, mixed>>
  1717.      *
  1718.      * @phpstan-param T|null $object
  1719.      */
  1720.     protected function configureActionButtons(array $buttonListstring $action, ?object $object null): array
  1721.     {
  1722.         return $buttonList;
  1723.     }
  1724.     /**
  1725.      * @param array<string, array<string, mixed>> $actions
  1726.      *
  1727.      * @return array<string, array<string, mixed>>
  1728.      */
  1729.     protected function configureDashboardActions(array $actions): array
  1730.     {
  1731.         return $actions;
  1732.     }
  1733.     /**
  1734.      * Allows you to customize batch actions.
  1735.      *
  1736.      * @param array<string, array<string, mixed>> $actions
  1737.      *
  1738.      * @return array<string, array<string, mixed>>
  1739.      */
  1740.     protected function configureBatchActions(array $actions): array
  1741.     {
  1742.         return $actions;
  1743.     }
  1744.     /**
  1745.      * Configures the tab menu in your admin.
  1746.      *
  1747.      * @phpstan-template TChild of object
  1748.      * @phpstan-param AdminInterface<TChild>|null $childAdmin
  1749.      */
  1750.     protected function configureTabMenu(ItemInterface $menustring $action, ?AdminInterface $childAdmin null): void
  1751.     {
  1752.     }
  1753.     /**
  1754.      * Gets the subclass corresponding to the given name.
  1755.      *
  1756.      * @phpstan-return class-string<T>
  1757.      */
  1758.     protected function getSubClass(string $name): string
  1759.     {
  1760.         if ($this->hasSubClass($name)) {
  1761.             return $this->subClasses[$name];
  1762.         }
  1763.         throw new \LogicException(sprintf('Unable to find the subclass `%s` for admin `%s`'$name, static::class));
  1764.     }
  1765.     /**
  1766.      * Return list routes with permissions name.
  1767.      *
  1768.      * @return array<string, string|string[]>
  1769.      */
  1770.     final protected function getAccess(): array
  1771.     {
  1772.         $access array_merge([
  1773.             'acl' => AdminPermissionMap::PERMISSION_MASTER,
  1774.             'export' => AdminPermissionMap::PERMISSION_EXPORT,
  1775.             'historyCompareRevisions' => AdminPermissionMap::PERMISSION_HISTORY,
  1776.             'historyViewRevision' => AdminPermissionMap::PERMISSION_HISTORY,
  1777.             'history' => AdminPermissionMap::PERMISSION_HISTORY,
  1778.             'edit' => AdminPermissionMap::PERMISSION_EDIT,
  1779.             'show' => AdminPermissionMap::PERMISSION_VIEW,
  1780.             'create' => AdminPermissionMap::PERMISSION_CREATE,
  1781.             'delete' => AdminPermissionMap::PERMISSION_DELETE,
  1782.             'batchDelete' => AdminPermissionMap::PERMISSION_DELETE,
  1783.             'list' => AdminPermissionMap::PERMISSION_LIST,
  1784.         ], $this->getAccessMapping());
  1785.         foreach ($this->getExtensions() as $extension) {
  1786.             $access array_merge($access$extension->getAccessMapping($this));
  1787.         }
  1788.         return $access;
  1789.     }
  1790.     /**
  1791.      * @return array<string, string|string[]> [action1 => requiredRole1, action2 => [requiredRole2, requiredRole3]]
  1792.      */
  1793.     protected function getAccessMapping(): array
  1794.     {
  1795.         return [];
  1796.     }
  1797.     /**
  1798.      * Return the list of permissions the user should have in order to display the admin.
  1799.      *
  1800.      * NEXT_MAJOR: Remove this method.
  1801.      *
  1802.      * @deprecated since sonata-project/admin-bundle version 4.7
  1803.      *
  1804.      * @return string[]
  1805.      */
  1806.     protected function getPermissionsShow(string $context): array
  1807.     {
  1808.         if ('sonata_deprecation_mute' !== (\func_get_args()[1] ?? null)) {
  1809.             @trigger_error(sprintf(
  1810.                 'The "%s()" method is deprecated since sonata-project/admin-bundle version 4.7 and will be'
  1811.                 .' removed in 5.0 version.',
  1812.                 __METHOD__
  1813.             ), \E_USER_DEPRECATED);
  1814.         }
  1815.         return ['LIST'];
  1816.     }
  1817.     /**
  1818.      * Configures a list of default filters.
  1819.      *
  1820.      * @param array<string, array<string, mixed>> $filterValues
  1821.      */
  1822.     protected function configureDefaultFilterValues(array &$filterValues): void
  1823.     {
  1824.     }
  1825.     /**
  1826.      * Configures a list of form options.
  1827.      *
  1828.      * @param array<string, mixed> $formOptions
  1829.      */
  1830.     protected function configureFormOptions(array &$formOptions): void
  1831.     {
  1832.     }
  1833.     /**
  1834.      * Configures a list of default sort values.
  1835.      *
  1836.      * Example:
  1837.      *   $sortValues[DatagridInterface::SORT_BY] = 'foo'
  1838.      *   $sortValues[DatagridInterface::SORT_ORDER] = 'DESC'
  1839.      *
  1840.      * @param array<string, string|int> $sortValues
  1841.      *
  1842.      * @phpstan-param array{
  1843.      *     _page?: int,
  1844.      *     _per_page?: int,
  1845.      *     _sort_by?: string,
  1846.      *     _sort_order?: string
  1847.      * } $sortValues
  1848.      */
  1849.     protected function configureDefaultSortValues(array &$sortValues): void
  1850.     {
  1851.     }
  1852.     /**
  1853.      * Set the parent object, if any, to the provided object.
  1854.      *
  1855.      * @phpstan-param T $object
  1856.      */
  1857.     final protected function appendParentObject(object $object): void
  1858.     {
  1859.         if ($this->isChild()) {
  1860.             $parentAssociationMapping $this->getParentAssociationMapping();
  1861.             if (null !== $parentAssociationMapping) {
  1862.                 $parentAdmin $this->getParent();
  1863.                 $parentObject $parentAdmin->getObject($this->getRequest()->get($parentAdmin->getIdParameter()));
  1864.                 if (null !== $parentObject) {
  1865.                     $propertyAccessor PropertyAccess::createPropertyAccessor();
  1866.                     try {
  1867.                         $value $propertyAccessor->getValue($object$parentAssociationMapping);
  1868.                     } catch (UninitializedPropertyException) {
  1869.                         $value null;
  1870.                     }
  1871.                     if (\is_array($value) || $value instanceof \ArrayAccess) {
  1872.                         $value[] = $parentObject;
  1873.                         $propertyAccessor->setValue($object$parentAssociationMapping$value);
  1874.                     } else {
  1875.                         $propertyAccessor->setValue($object$parentAssociationMapping$parentObject);
  1876.                     }
  1877.                 }
  1878.                 return;
  1879.             }
  1880.         }
  1881.         if ($this->hasParentFieldDescription()) {
  1882.             $parentAdmin $this->getParentFieldDescription()->getAdmin();
  1883.             $parentObject $parentAdmin->getObject($this->getRequest()->get($parentAdmin->getIdParameter()));
  1884.             if (null !== $parentObject) {
  1885.                 ObjectManipulator::setObject($object$parentObject$this->getParentFieldDescription());
  1886.             }
  1887.         }
  1888.     }
  1889.     /**
  1890.      * @return array<string, array<string, mixed>>
  1891.      *
  1892.      * @phpstan-param T|null $object
  1893.      */
  1894.     private function getDefaultActionButtons(string $action, ?object $object null): array
  1895.     {
  1896.         // nothing to do for non-internal actions
  1897.         if (!isset(self::INTERNAL_ACTIONS[$action])) {
  1898.             return [];
  1899.         }
  1900.         $buttonList = [];
  1901.         $actionBit self::INTERNAL_ACTIONS[$action];
  1902.         if (!== (self::MASK_OF_ACTION_CREATE $actionBit)
  1903.             && $this->hasRoute('create')
  1904.             && $this->hasAccess('create')
  1905.         ) {
  1906.             $buttonList['create'] = [
  1907.                 'template' => $this->getTemplateRegistry()->getTemplate('button_create'),
  1908.             ];
  1909.         }
  1910.         $canAccessObject !== (self::MASK_OF_ACTIONS_USING_OBJECT $actionBit)
  1911.             && null !== $object
  1912.             && null !== $this->id($object);
  1913.         if ($canAccessObject
  1914.             && !== (self::MASK_OF_ACTION_EDIT $actionBit)
  1915.             && $this->hasRoute('edit')
  1916.             && $this->hasAccess('edit'$object)
  1917.         ) {
  1918.             $buttonList['edit'] = [
  1919.                 'template' => $this->getTemplateRegistry()->getTemplate('button_edit'),
  1920.             ];
  1921.         }
  1922.         if ($canAccessObject
  1923.             && !== (self::MASK_OF_ACTION_HISTORY $actionBit)
  1924.             && $this->hasRoute('history')
  1925.             && $this->hasAccess('history'$object)
  1926.         ) {
  1927.             $buttonList['history'] = [
  1928.                 'template' => $this->getTemplateRegistry()->getTemplate('button_history'),
  1929.             ];
  1930.         }
  1931.         if ($canAccessObject
  1932.             && !== (self::MASK_OF_ACTION_ACL $actionBit)
  1933.             && $this->isAclEnabled()
  1934.             && $this->hasRoute('acl')
  1935.             && $this->hasAccess('acl'$object)
  1936.         ) {
  1937.             $buttonList['acl'] = [
  1938.                 'template' => $this->getTemplateRegistry()->getTemplate('button_acl'),
  1939.             ];
  1940.         }
  1941.         if ($canAccessObject
  1942.             && !== (self::MASK_OF_ACTION_SHOW $actionBit)
  1943.             && $this->hasRoute('show')
  1944.             && $this->hasAccess('show'$object)
  1945.             && \count($this->getShow()) > 0
  1946.         ) {
  1947.             $buttonList['show'] = [
  1948.                 'template' => $this->getTemplateRegistry()->getTemplate('button_show'),
  1949.             ];
  1950.         }
  1951.         if (!== (self::MASK_OF_ACTION_LIST $actionBit)
  1952.             && $this->hasRoute('list')
  1953.             && $this->hasAccess('list')
  1954.         ) {
  1955.             $buttonList['list'] = [
  1956.                 'template' => $this->getTemplateRegistry()->getTemplate('button_list'),
  1957.             ];
  1958.         }
  1959.         return $buttonList;
  1960.     }
  1961.     /**
  1962.      * @return DatagridInterface<ProxyQueryInterface<T>>|null
  1963.      */
  1964.     private function buildDatagrid(): ?DatagridInterface
  1965.     {
  1966.         if ($this->loaded['datagrid']) {
  1967.             return $this->datagrid;
  1968.         }
  1969.         $this->loaded['datagrid'] = true;
  1970.         $filterParameters $this->getFilterParameters();
  1971.         // transform DatagridInterface::SORT_BY filter parameter from a string to a FieldDescriptionInterface for the datagrid.
  1972.         if (isset($filterParameters[DatagridInterface::SORT_BY]) && \is_string($filterParameters[DatagridInterface::SORT_BY])) {
  1973.             if ($this->hasListFieldDescription($filterParameters[DatagridInterface::SORT_BY])) {
  1974.                 $filterParameters[DatagridInterface::SORT_BY] = $this->getListFieldDescription($filterParameters[DatagridInterface::SORT_BY]);
  1975.             } else {
  1976.                 $filterParameters[DatagridInterface::SORT_BY] = $this->createFieldDescription(
  1977.                     $filterParameters[DatagridInterface::SORT_BY]
  1978.                 );
  1979.                 $this->getListBuilder()->buildField(null$filterParameters[DatagridInterface::SORT_BY]);
  1980.             }
  1981.         }
  1982.         // initialize the datagrid
  1983.         $this->datagrid $this->getDatagridBuilder()->getBaseDatagrid($this$filterParameters);
  1984.         $this->datagrid->getPager()->setMaxPageLinks($this->getMaxPageLinks());
  1985.         /** @psalm-suppress InvalidArgument https://github.com/vimeo/psalm/issues/8423 */
  1986.         $mapper = new DatagridMapper($this->getDatagridBuilder(), $this->datagrid$this);
  1987.         // build the datagrid filter
  1988.         $this->configureDatagridFilters($mapper);
  1989.         // ok, try to limit to add parent filter
  1990.         if ($this->isChild()) {
  1991.             $parentAssociationMapping $this->getParentAssociationMapping();
  1992.             if (null !== $parentAssociationMapping && !$mapper->has($parentAssociationMapping)) {
  1993.                 $mapper->add($parentAssociationMappingnull, [
  1994.                     'show_filter' => false,
  1995.                     'label' => false,
  1996.                     'field_type' => ModelHiddenType::class,
  1997.                     'field_options' => [
  1998.                         'model_manager' => $this->getParent()->getModelManager(),
  1999.                         'class' => $this->getParent()->getClass(),
  2000.                     ],
  2001.                     'operator_type' => HiddenType::class,
  2002.                 ], [
  2003.                     'admin_code' => $this->getParent()->getCode(),
  2004.                 ]);
  2005.             }
  2006.         }
  2007.         foreach ($this->getExtensions() as $extension) {
  2008.             $extension->configureDatagridFilters($mapper);
  2009.         }
  2010.         return $this->datagrid;
  2011.     }
  2012.     /**
  2013.      * @return FieldDescriptionCollection<FieldDescriptionInterface>|null
  2014.      */
  2015.     private function buildShow(): ?FieldDescriptionCollection
  2016.     {
  2017.         if ($this->loaded['show']) {
  2018.             return $this->show;
  2019.         }
  2020.         $this->loaded['show'] = true;
  2021.         $this->show $this->getShowBuilder()->getBaseList();
  2022.         $mapper = new ShowMapper($this->getShowBuilder(), $this->show$this);
  2023.         $this->configureShowFields($mapper);
  2024.         foreach ($this->getExtensions() as $extension) {
  2025.             $extension->configureShowFields($mapper);
  2026.         }
  2027.         return $this->show;
  2028.     }
  2029.     /**
  2030.      * @return FieldDescriptionCollection<FieldDescriptionInterface>|null
  2031.      */
  2032.     private function buildList(): ?FieldDescriptionCollection
  2033.     {
  2034.         if ($this->loaded['list']) {
  2035.             return $this->list;
  2036.         }
  2037.         $this->loaded['list'] = true;
  2038.         $this->list $this->getListBuilder()->getBaseList();
  2039.         $mapper = new ListMapper($this->getListBuilder(), $this->list$this);
  2040.         if (\count($this->getBatchActions()) > && $this->hasRequest() && !$this->getRequest()->isXmlHttpRequest()) {
  2041.             $mapper->add(ListMapper::NAME_BATCHListMapper::TYPE_BATCH, [
  2042.                 'label' => 'batch',
  2043.                 'sortable' => false,
  2044.                 'virtual_field' => true,
  2045.                 'template' => $this->getTemplateRegistry()->getTemplate('batch'),
  2046.             ]);
  2047.         }
  2048.         $this->configureListFields($mapper);
  2049.         foreach ($this->getExtensions() as $extension) {
  2050.             $extension->configureListFields($mapper);
  2051.         }
  2052.         if ($this->hasRequest()
  2053.             && $this->getRequest()->isXmlHttpRequest()
  2054.             && $this->getRequest()->query->getBoolean('select'true// NEXT_MAJOR: Change the default value to `false` in version 5
  2055.         ) {
  2056.             $mapper->add(ListMapper::NAME_SELECTListMapper::TYPE_SELECT, [
  2057.                 'label' => false,
  2058.                 'sortable' => false,
  2059.                 'virtual_field' => false,
  2060.                 'template' => $this->getTemplateRegistry()->getTemplate('select'),
  2061.             ]);
  2062.         }
  2063.         return $this->list;
  2064.     }
  2065.     private function buildForm(): ?FormInterface
  2066.     {
  2067.         if ($this->loaded['form']) {
  2068.             return $this->form;
  2069.         }
  2070.         $this->loaded['form'] = true;
  2071.         $formBuilder $this->getFormBuilder();
  2072.         $formBuilder->addEventListener(FormEvents::POST_SUBMIT, function (FormEvent $event): void {
  2073.             /** @phpstan-var T $data */
  2074.             $data $event->getData();
  2075.             $this->preValidate($data);
  2076.         }, 100);
  2077.         $this->form $formBuilder->getForm();
  2078.         return $this->form;
  2079.     }
  2080.     private function buildRoutes(): ?RouteCollectionInterface
  2081.     {
  2082.         if ($this->loaded['routes']) {
  2083.             return $this->routes;
  2084.         }
  2085.         $this->loaded['routes'] = true;
  2086.         $routes = new RouteCollection(
  2087.             $this->getBaseCodeRoute(),
  2088.             $this->getBaseRouteName(),
  2089.             $this->getBaseRoutePattern(),
  2090.             $this->getBaseControllerName()
  2091.         );
  2092.         $this->getRouteBuilder()->build($this$routes);
  2093.         $this->configureRoutes($routes);
  2094.         foreach ($this->getExtensions() as $extension) {
  2095.             $extension->configureRoutes($this$routes);
  2096.         }
  2097.         $this->routes $routes;
  2098.         return $this->routes;
  2099.     }
  2100.     /**
  2101.      * @phpstan-template TChild of object
  2102.      * @phpstan-param AdminInterface<TChild>|null $childAdmin
  2103.      */
  2104.     private function buildTabMenu(string $action, ?AdminInterface $childAdmin null): ?ItemInterface
  2105.     {
  2106.         if ($this->loaded['tab_menu']) {
  2107.             return $this->menu;
  2108.         }
  2109.         $this->loaded['tab_menu'] = true;
  2110.         $menu $this->getMenuFactory()->createItem('root');
  2111.         $menu->setChildrenAttribute('class''nav navbar-nav');
  2112.         $menu->setExtra('translation_domain'$this->getTranslationDomain());
  2113.         $this->configureTabMenu($menu$action$childAdmin);
  2114.         foreach ($this->getExtensions() as $extension) {
  2115.             $extension->configureTabMenu($this$menu$action$childAdmin);
  2116.         }
  2117.         $this->menu $menu;
  2118.         return $this->menu;
  2119.     }
  2120. }