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

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