<?php 
 
namespace EasyCorp\Bundle\EasyAdminBundle\Controller; 
 
use Doctrine\DBAL\Exception\ForeignKeyConstraintViolationException; 
use Doctrine\ORM\EntityManagerInterface; 
use Doctrine\ORM\QueryBuilder; 
use EasyCorp\Bundle\EasyAdminBundle\Collection\FieldCollection; 
use EasyCorp\Bundle\EasyAdminBundle\Collection\FilterCollection; 
use EasyCorp\Bundle\EasyAdminBundle\Config\Action; 
use EasyCorp\Bundle\EasyAdminBundle\Config\Actions; 
use EasyCorp\Bundle\EasyAdminBundle\Config\Assets; 
use EasyCorp\Bundle\EasyAdminBundle\Config\Crud; 
use EasyCorp\Bundle\EasyAdminBundle\Config\Filters; 
use EasyCorp\Bundle\EasyAdminBundle\Config\KeyValueStore; 
use EasyCorp\Bundle\EasyAdminBundle\Config\Option\EA; 
use EasyCorp\Bundle\EasyAdminBundle\Context\AdminContext; 
use EasyCorp\Bundle\EasyAdminBundle\Contracts\Controller\CrudControllerInterface; 
use EasyCorp\Bundle\EasyAdminBundle\Dto\BatchActionDto; 
use EasyCorp\Bundle\EasyAdminBundle\Dto\EntityDto; 
use EasyCorp\Bundle\EasyAdminBundle\Dto\FieldDto; 
use EasyCorp\Bundle\EasyAdminBundle\Dto\SearchDto; 
use EasyCorp\Bundle\EasyAdminBundle\Event\AfterCrudActionEvent; 
use EasyCorp\Bundle\EasyAdminBundle\Event\AfterEntityDeletedEvent; 
use EasyCorp\Bundle\EasyAdminBundle\Event\AfterEntityPersistedEvent; 
use EasyCorp\Bundle\EasyAdminBundle\Event\AfterEntityUpdatedEvent; 
use EasyCorp\Bundle\EasyAdminBundle\Event\BeforeCrudActionEvent; 
use EasyCorp\Bundle\EasyAdminBundle\Event\BeforeEntityDeletedEvent; 
use EasyCorp\Bundle\EasyAdminBundle\Event\BeforeEntityPersistedEvent; 
use EasyCorp\Bundle\EasyAdminBundle\Event\BeforeEntityUpdatedEvent; 
use EasyCorp\Bundle\EasyAdminBundle\Exception\EntityRemoveException; 
use EasyCorp\Bundle\EasyAdminBundle\Exception\ForbiddenActionException; 
use EasyCorp\Bundle\EasyAdminBundle\Exception\InsufficientEntityPermissionException; 
use EasyCorp\Bundle\EasyAdminBundle\Factory\ActionFactory; 
use EasyCorp\Bundle\EasyAdminBundle\Factory\ControllerFactory; 
use EasyCorp\Bundle\EasyAdminBundle\Factory\EntityFactory; 
use EasyCorp\Bundle\EasyAdminBundle\Factory\FilterFactory; 
use EasyCorp\Bundle\EasyAdminBundle\Factory\FormFactory; 
use EasyCorp\Bundle\EasyAdminBundle\Factory\PaginatorFactory; 
use EasyCorp\Bundle\EasyAdminBundle\Field\AssociationField; 
use EasyCorp\Bundle\EasyAdminBundle\Form\Type\FileUploadType; 
use EasyCorp\Bundle\EasyAdminBundle\Form\Type\FiltersFormType; 
use EasyCorp\Bundle\EasyAdminBundle\Form\Type\Model\FileUploadState; 
use EasyCorp\Bundle\EasyAdminBundle\Orm\EntityRepository; 
use EasyCorp\Bundle\EasyAdminBundle\Orm\EntityUpdater; 
use EasyCorp\Bundle\EasyAdminBundle\Provider\AdminContextProvider; 
use EasyCorp\Bundle\EasyAdminBundle\Provider\FieldProvider; 
use EasyCorp\Bundle\EasyAdminBundle\Router\AdminUrlGenerator; 
use EasyCorp\Bundle\EasyAdminBundle\Router\CrudUrlGenerator; 
use EasyCorp\Bundle\EasyAdminBundle\Security\Permission; 
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; 
use Symfony\Component\EventDispatcher\EventDispatcherInterface; 
use Symfony\Component\Form\FormBuilderInterface; 
use Symfony\Component\Form\FormInterface; 
use Symfony\Component\HttpFoundation\JsonResponse; 
use Symfony\Component\HttpFoundation\RedirectResponse; 
use Symfony\Component\HttpFoundation\Response; 
use function Symfony\Component\String\u; 
 
/** 
 * @author Javier Eguiluz <javier.eguiluz@gmail.com> 
 */ 
abstract class AbstractCrudController extends AbstractController implements CrudControllerInterface 
{ 
    abstract public static function getEntityFqcn(): string; 
 
    public function configureCrud(Crud $crud): Crud 
    { 
        return $crud; 
    } 
 
    public function configureAssets(Assets $assets): Assets 
    { 
        return $assets; 
    } 
 
    public function configureActions(Actions $actions): Actions 
    { 
        return $actions; 
    } 
 
    public function configureFilters(Filters $filters): Filters 
    { 
        return $filters; 
    } 
 
    public function configureFields(string $pageName): iterable 
    { 
        return $this->container->get(FieldProvider::class)->getDefaultFields($pageName); 
    } 
 
    public static function getSubscribedServices(): array 
    { 
        return array_merge(parent::getSubscribedServices(), [ 
            'event_dispatcher' => '?'.EventDispatcherInterface::class, 
            ActionFactory::class => '?'.ActionFactory::class, 
            AdminContextProvider::class => '?'.AdminContextProvider::class, 
            AdminUrlGenerator::class => '?'.AdminUrlGenerator::class, 
            ControllerFactory::class => '?'.ControllerFactory::class, 
            CrudUrlGenerator::class => '?'.CrudUrlGenerator::class, 
            EntityFactory::class => '?'.EntityFactory::class, 
            EntityRepository::class => '?'.EntityRepository::class, 
            EntityUpdater::class => '?'.EntityUpdater::class, 
            FieldProvider::class => '?'.FieldProvider::class, 
            FilterFactory::class => '?'.FilterFactory::class, 
            FormFactory::class => '?'.FormFactory::class, 
            PaginatorFactory::class => '?'.PaginatorFactory::class, 
        ]); 
    } 
 
    public function index(AdminContext $context) 
    { 
        $event = new BeforeCrudActionEvent($context); 
        $this->container->get('event_dispatcher')->dispatch($event); 
        if ($event->isPropagationStopped()) { 
            return $event->getResponse(); 
        } 
 
        if (!$this->isGranted(Permission::EA_EXECUTE_ACTION, ['action' => Action::INDEX, 'entity' => null])) { 
            throw new ForbiddenActionException($context); 
        } 
 
        $fields = FieldCollection::new($this->configureFields(Crud::PAGE_INDEX)); 
        $filters = $this->container->get(FilterFactory::class)->create($context->getCrud()->getFiltersConfig(), $fields, $context->getEntity()); 
        $queryBuilder = $this->createIndexQueryBuilder($context->getSearch(), $context->getEntity(), $fields, $filters); 
        $paginator = $this->container->get(PaginatorFactory::class)->create($queryBuilder); 
 
        // this can happen after deleting some items and trying to return 
        // to a 'index' page that no longer exists. Redirect to the last page instead 
        if ($paginator->isOutOfRange()) { 
            return $this->redirect($this->container->get(AdminUrlGenerator::class) 
                ->set(EA::PAGE, $paginator->getLastPage()) 
                ->generateUrl()); 
        } 
 
        $entities = $this->container->get(EntityFactory::class)->createCollection($context->getEntity(), $paginator->getResults()); 
        $this->container->get(EntityFactory::class)->processFieldsForAll($entities, $fields); 
        $actions = $this->container->get(EntityFactory::class)->processActionsForAll($entities, $context->getCrud()->getActionsConfig()); 
 
        $responseParameters = $this->configureResponseParameters(KeyValueStore::new([ 
            'pageName' => Crud::PAGE_INDEX, 
            'templateName' => 'crud/index', 
            'entities' => $entities, 
            'paginator' => $paginator, 
            'global_actions' => $actions->getGlobalActions(), 
            'batch_actions' => $actions->getBatchActions(), 
            'filters' => $filters, 
        ])); 
 
        $event = new AfterCrudActionEvent($context, $responseParameters); 
        $this->container->get('event_dispatcher')->dispatch($event); 
        if ($event->isPropagationStopped()) { 
            return $event->getResponse(); 
        } 
 
        return $responseParameters; 
    } 
 
    public function detail(AdminContext $context) 
    { 
        $event = new BeforeCrudActionEvent($context); 
        $this->container->get('event_dispatcher')->dispatch($event); 
        if ($event->isPropagationStopped()) { 
            return $event->getResponse(); 
        } 
 
        if (!$this->isGranted(Permission::EA_EXECUTE_ACTION, ['action' => Action::DETAIL, 'entity' => $context->getEntity()])) { 
            throw new ForbiddenActionException($context); 
        } 
 
        if (!$context->getEntity()->isAccessible()) { 
            throw new InsufficientEntityPermissionException($context); 
        } 
 
        $this->container->get(EntityFactory::class)->processFields($context->getEntity(), FieldCollection::new($this->configureFields(Crud::PAGE_DETAIL))); 
        $this->container->get(EntityFactory::class)->processActions($context->getEntity(), $context->getCrud()->getActionsConfig()); 
 
        $responseParameters = $this->configureResponseParameters(KeyValueStore::new([ 
            'pageName' => Crud::PAGE_DETAIL, 
            'templateName' => 'crud/detail', 
            'entity' => $context->getEntity(), 
        ])); 
 
        $event = new AfterCrudActionEvent($context, $responseParameters); 
        $this->container->get('event_dispatcher')->dispatch($event); 
        if ($event->isPropagationStopped()) { 
            return $event->getResponse(); 
        } 
 
        return $responseParameters; 
    } 
 
    public function edit(AdminContext $context) 
    { 
        $event = new BeforeCrudActionEvent($context); 
        $this->container->get('event_dispatcher')->dispatch($event); 
        if ($event->isPropagationStopped()) { 
            return $event->getResponse(); 
        } 
 
        if (!$this->isGranted(Permission::EA_EXECUTE_ACTION, ['action' => Action::EDIT, 'entity' => $context->getEntity()])) { 
            throw new ForbiddenActionException($context); 
        } 
 
        if (!$context->getEntity()->isAccessible()) { 
            throw new InsufficientEntityPermissionException($context); 
        } 
 
        $this->container->get(EntityFactory::class)->processFields($context->getEntity(), FieldCollection::new($this->configureFields(Crud::PAGE_EDIT))); 
        $this->container->get(EntityFactory::class)->processActions($context->getEntity(), $context->getCrud()->getActionsConfig()); 
        $entityInstance = $context->getEntity()->getInstance(); 
 
        if ($context->getRequest()->isXmlHttpRequest()) { 
            $fieldName = $context->getRequest()->query->get('fieldName'); 
            $newValue = 'true' === mb_strtolower($context->getRequest()->query->get('newValue')); 
 
            $event = $this->ajaxEdit($context->getEntity(), $fieldName, $newValue); 
            if ($event->isPropagationStopped()) { 
                return $event->getResponse(); 
            } 
 
            // cast to integer instead of string to avoid sending empty responses for 'false' 
            return new Response((int) $newValue); 
        } 
 
        $editForm = $this->createEditForm($context->getEntity(), $context->getCrud()->getEditFormOptions(), $context); 
        $editForm->handleRequest($context->getRequest()); 
        if ($editForm->isSubmitted() && $editForm->isValid()) { 
            $this->processUploadedFiles($editForm); 
 
            $event = new BeforeEntityUpdatedEvent($entityInstance); 
            $this->container->get('event_dispatcher')->dispatch($event); 
            $entityInstance = $event->getEntityInstance(); 
 
            $this->updateEntity($this->container->get('doctrine')->getManagerForClass($context->getEntity()->getFqcn()), $entityInstance); 
 
            $this->container->get('event_dispatcher')->dispatch(new AfterEntityUpdatedEvent($entityInstance)); 
 
            return $this->getRedirectResponseAfterSave($context, Action::EDIT); 
        } 
 
        $responseParameters = $this->configureResponseParameters(KeyValueStore::new([ 
            'pageName' => Crud::PAGE_EDIT, 
            'templateName' => 'crud/edit', 
            'edit_form' => $editForm, 
            'entity' => $context->getEntity(), 
        ])); 
 
        $event = new AfterCrudActionEvent($context, $responseParameters); 
        $this->container->get('event_dispatcher')->dispatch($event); 
        if ($event->isPropagationStopped()) { 
            return $event->getResponse(); 
        } 
 
        return $responseParameters; 
    } 
 
    public function new(AdminContext $context) 
    { 
        $event = new BeforeCrudActionEvent($context); 
        $this->container->get('event_dispatcher')->dispatch($event); 
        if ($event->isPropagationStopped()) { 
            return $event->getResponse(); 
        } 
 
        if (!$this->isGranted(Permission::EA_EXECUTE_ACTION, ['action' => Action::NEW, 'entity' => null])) { 
            throw new ForbiddenActionException($context); 
        } 
 
        if (!$context->getEntity()->isAccessible()) { 
            throw new InsufficientEntityPermissionException($context); 
        } 
 
        $context->getEntity()->setInstance($this->createEntity($context->getEntity()->getFqcn())); 
        $this->container->get(EntityFactory::class)->processFields($context->getEntity(), FieldCollection::new($this->configureFields(Crud::PAGE_NEW))); 
        $this->container->get(EntityFactory::class)->processActions($context->getEntity(), $context->getCrud()->getActionsConfig()); 
 
        $newForm = $this->createNewForm($context->getEntity(), $context->getCrud()->getNewFormOptions(), $context); 
        $newForm->handleRequest($context->getRequest()); 
 
        $entityInstance = $newForm->getData(); 
        $context->getEntity()->setInstance($entityInstance); 
 
        if ($newForm->isSubmitted() && $newForm->isValid()) { 
            $this->processUploadedFiles($newForm); 
 
            $event = new BeforeEntityPersistedEvent($entityInstance); 
            $this->container->get('event_dispatcher')->dispatch($event); 
            $entityInstance = $event->getEntityInstance(); 
 
            $this->persistEntity($this->container->get('doctrine')->getManagerForClass($context->getEntity()->getFqcn()), $entityInstance); 
 
            $this->container->get('event_dispatcher')->dispatch(new AfterEntityPersistedEvent($entityInstance)); 
            $context->getEntity()->setInstance($entityInstance); 
 
            return $this->getRedirectResponseAfterSave($context, Action::NEW); 
        } 
 
        $responseParameters = $this->configureResponseParameters(KeyValueStore::new([ 
            'pageName' => Crud::PAGE_NEW, 
            'templateName' => 'crud/new', 
            'entity' => $context->getEntity(), 
            'new_form' => $newForm, 
        ])); 
 
        $event = new AfterCrudActionEvent($context, $responseParameters); 
        $this->container->get('event_dispatcher')->dispatch($event); 
        if ($event->isPropagationStopped()) { 
            return $event->getResponse(); 
        } 
 
        return $responseParameters; 
    } 
 
    public function delete(AdminContext $context) 
    { 
        $event = new BeforeCrudActionEvent($context); 
        $this->container->get('event_dispatcher')->dispatch($event); 
        if ($event->isPropagationStopped()) { 
            return $event->getResponse(); 
        } 
 
        if (!$this->isGranted(Permission::EA_EXECUTE_ACTION, ['action' => Action::DELETE, 'entity' => $context->getEntity()])) { 
            throw new ForbiddenActionException($context); 
        } 
 
        if (!$context->getEntity()->isAccessible()) { 
            throw new InsufficientEntityPermissionException($context); 
        } 
 
        $csrfToken = $context->getRequest()->request->get('token'); 
        if (!$this->isCsrfTokenValid('ea-delete', $csrfToken)) { 
            return $this->redirectToRoute($context->getDashboardRouteName()); 
        } 
 
        $entityInstance = $context->getEntity()->getInstance(); 
 
        $event = new BeforeEntityDeletedEvent($entityInstance); 
        $this->container->get('event_dispatcher')->dispatch($event); 
        if ($event->isPropagationStopped()) { 
            return $event->getResponse(); 
        } 
        $entityInstance = $event->getEntityInstance(); 
 
        try { 
            $this->deleteEntity($this->container->get('doctrine')->getManagerForClass($context->getEntity()->getFqcn()), $entityInstance); 
        } catch (ForeignKeyConstraintViolationException $e) { 
            throw new EntityRemoveException(['entity_name' => $context->getEntity()->getName(), 'message' => $e->getMessage()]); 
        } 
 
        $this->container->get('event_dispatcher')->dispatch(new AfterEntityDeletedEvent($entityInstance)); 
 
        $responseParameters = $this->configureResponseParameters(KeyValueStore::new([ 
            'entity' => $context->getEntity(), 
        ])); 
 
        $event = new AfterCrudActionEvent($context, $responseParameters); 
        $this->container->get('event_dispatcher')->dispatch($event); 
        if ($event->isPropagationStopped()) { 
            return $event->getResponse(); 
        } 
 
        if (null !== $referrer = $context->getReferrer()) { 
            return $this->redirect($referrer); 
        } 
 
        return $this->redirect($this->container->get(AdminUrlGenerator::class)->setAction(Action::INDEX)->unset(EA::ENTITY_ID)->generateUrl()); 
    } 
 
    public function batchDelete(AdminContext $context, BatchActionDto $batchActionDto): Response 
    { 
        $event = new BeforeCrudActionEvent($context); 
        $this->container->get('event_dispatcher')->dispatch($event); 
        if ($event->isPropagationStopped()) { 
            return $event->getResponse(); 
        } 
 
        if (!$this->isCsrfTokenValid('ea-batch-action-'.Action::BATCH_DELETE, $batchActionDto->getCsrfToken())) { 
            return $this->redirectToRoute($context->getDashboardRouteName()); 
        } 
 
        $entityManager = $this->container->get('doctrine')->getManagerForClass($batchActionDto->getEntityFqcn()); 
        $repository = $entityManager->getRepository($batchActionDto->getEntityFqcn()); 
        foreach ($batchActionDto->getEntityIds() as $entityId) { 
            $entityInstance = $repository->find($entityId); 
            if (!$entityInstance) { 
                continue; 
            } 
 
            $entityDto = $context->getEntity()->newWithInstance($entityInstance); 
            if (!$this->isGranted(Permission::EA_EXECUTE_ACTION, ['action' => Action::DELETE, 'entity' => $entityDto])) { 
                throw new ForbiddenActionException($context); 
            } 
 
            if (!$entityDto->isAccessible()) { 
                throw new InsufficientEntityPermissionException($context); 
            } 
 
            $event = new BeforeEntityDeletedEvent($entityInstance); 
            $this->container->get('event_dispatcher')->dispatch($event); 
            $entityInstance = $event->getEntityInstance(); 
 
            try { 
                $this->deleteEntity($entityManager, $entityInstance); 
            } catch (ForeignKeyConstraintViolationException $e) { 
                throw new EntityRemoveException(['entity_name' => $entityDto->toString(), 'message' => $e->getMessage()]); 
            } 
 
            $this->container->get('event_dispatcher')->dispatch(new AfterEntityDeletedEvent($entityInstance)); 
        } 
 
        $responseParameters = $this->configureResponseParameters(KeyValueStore::new([ 
            'entity' => $context->getEntity(), 
            'batchActionDto' => $batchActionDto, 
        ])); 
 
        $event = new AfterCrudActionEvent($context, $responseParameters); 
        $this->container->get('event_dispatcher')->dispatch($event); 
        if ($event->isPropagationStopped()) { 
            return $event->getResponse(); 
        } 
 
        return $this->redirect($batchActionDto->getReferrerUrl()); 
    } 
 
    public function autocomplete(AdminContext $context): JsonResponse 
    { 
        $queryBuilder = $this->createIndexQueryBuilder($context->getSearch(), $context->getEntity(), FieldCollection::new([]), FilterCollection::new()); 
 
        $autocompleteContext = $context->getRequest()->get(AssociationField::PARAM_AUTOCOMPLETE_CONTEXT); 
 
        /** @var CrudControllerInterface $controller */ 
        $controller = $this->container->get(ControllerFactory::class)->getCrudControllerInstance($autocompleteContext[EA::CRUD_CONTROLLER_FQCN], Action::INDEX, $context->getRequest()); 
        /** @var FieldDto $field */ 
        $field = FieldCollection::new($controller->configureFields($autocompleteContext['originatingPage']))->getByProperty($autocompleteContext['propertyName']); 
        /** @var \Closure|null $queryBuilderCallable */ 
        $queryBuilderCallable = $field->getCustomOption(AssociationField::OPTION_QUERY_BUILDER_CALLABLE); 
 
        if (null !== $queryBuilderCallable) { 
            $queryBuilderCallable($queryBuilder); 
        } 
 
        $paginator = $this->container->get(PaginatorFactory::class)->create($queryBuilder); 
 
        return JsonResponse::fromJsonString($paginator->getResultsAsJson()); 
    } 
 
    public function createIndexQueryBuilder(SearchDto $searchDto, EntityDto $entityDto, FieldCollection $fields, FilterCollection $filters): QueryBuilder 
    { 
        return $this->container->get(EntityRepository::class)->createQueryBuilder($searchDto, $entityDto, $fields, $filters); 
    } 
 
    public function renderFilters(AdminContext $context): KeyValueStore 
    { 
        $fields = FieldCollection::new($this->configureFields(Crud::PAGE_INDEX)); 
        $this->container->get(EntityFactory::class)->processFields($context->getEntity(), $fields); 
        $filters = $this->container->get(FilterFactory::class)->create($context->getCrud()->getFiltersConfig(), $context->getEntity()->getFields(), $context->getEntity()); 
 
        /** @var FiltersFormType $filtersForm */ 
        $filtersForm = $this->container->get(FormFactory::class)->createFiltersForm($filters, $context->getRequest()); 
        $formActionParts = parse_url($filtersForm->getConfig()->getAction()); 
        $queryString = $formActionParts[EA::QUERY] ?? ''; 
        parse_str($queryString, $queryStringAsArray); 
        unset($queryStringAsArray[EA::FILTERS], $queryStringAsArray[EA::PAGE]); 
 
        $responseParameters = KeyValueStore::new([ 
            'templateName' => 'crud/filters', 
            'filters_form' => $filtersForm, 
            'form_action_query_string_as_array' => $queryStringAsArray, 
        ]); 
 
        return $this->configureResponseParameters($responseParameters); 
    } 
 
    public function createEntity(string $entityFqcn) 
    { 
        return new $entityFqcn(); 
    } 
 
    public function updateEntity(EntityManagerInterface $entityManager, $entityInstance): void 
    { 
        $entityManager->persist($entityInstance); 
        $entityManager->flush(); 
    } 
 
    public function persistEntity(EntityManagerInterface $entityManager, $entityInstance): void 
    { 
        $entityManager->persist($entityInstance); 
        $entityManager->flush(); 
    } 
 
    public function deleteEntity(EntityManagerInterface $entityManager, $entityInstance): void 
    { 
        $entityManager->remove($entityInstance); 
        $entityManager->flush(); 
    } 
 
    public function createEditForm(EntityDto $entityDto, KeyValueStore $formOptions, AdminContext $context): FormInterface 
    { 
        return $this->createEditFormBuilder($entityDto, $formOptions, $context)->getForm(); 
    } 
 
    public function createEditFormBuilder(EntityDto $entityDto, KeyValueStore $formOptions, AdminContext $context): FormBuilderInterface 
    { 
        return $this->container->get(FormFactory::class)->createEditFormBuilder($entityDto, $formOptions, $context); 
    } 
 
    public function createNewForm(EntityDto $entityDto, KeyValueStore $formOptions, AdminContext $context): FormInterface 
    { 
        return $this->createNewFormBuilder($entityDto, $formOptions, $context)->getForm(); 
    } 
 
    public function createNewFormBuilder(EntityDto $entityDto, KeyValueStore $formOptions, AdminContext $context): FormBuilderInterface 
    { 
        return $this->container->get(FormFactory::class)->createNewFormBuilder($entityDto, $formOptions, $context); 
    } 
 
    /** 
     * Used to add/modify/remove parameters before passing them to the Twig template. 
     */ 
    public function configureResponseParameters(KeyValueStore $responseParameters): KeyValueStore 
    { 
        return $responseParameters; 
    } 
 
    protected function getContext(): ?AdminContext 
    { 
        return $this->container->get(AdminContextProvider::class)->getContext(); 
    } 
 
    protected function ajaxEdit(EntityDto $entityDto, ?string $propertyName, bool $newValue): AfterCrudActionEvent 
    { 
        $this->container->get(EntityUpdater::class)->updateProperty($entityDto, $propertyName, $newValue); 
 
        $event = new BeforeEntityUpdatedEvent($entityDto->getInstance()); 
        $this->container->get('event_dispatcher')->dispatch($event); 
        $entityInstance = $event->getEntityInstance(); 
 
        $this->updateEntity($this->container->get('doctrine')->getManagerForClass($entityDto->getFqcn()), $entityInstance); 
 
        $this->container->get('event_dispatcher')->dispatch(new AfterEntityUpdatedEvent($entityInstance)); 
 
        $entityDto->setInstance($entityInstance); 
 
        $parameters = KeyValueStore::new([ 
            'action' => Action::EDIT, 
            'entity' => $entityDto, 
        ]); 
 
        $event = new AfterCrudActionEvent($this->getContext(), $parameters); 
        $this->container->get('event_dispatcher')->dispatch($event); 
 
        return $event; 
    } 
 
    protected function processUploadedFiles(FormInterface $form): void 
    { 
        /** @var FormInterface $child */ 
        foreach ($form as $child) { 
            $config = $child->getConfig(); 
 
            if (!$config->getType()->getInnerType() instanceof FileUploadType) { 
                if ($config->getCompound()) { 
                    $this->processUploadedFiles($child); 
                } 
 
                continue; 
            } 
 
            /** @var FileUploadState $state */ 
            $state = $config->getAttribute('state'); 
 
            if (!$state->isModified()) { 
                continue; 
            } 
 
            $uploadDelete = $config->getOption('upload_delete'); 
 
            if ($state->hasCurrentFiles() && ($state->isDelete() || (!$state->isAddAllowed() && $state->hasUploadedFiles()))) { 
                foreach ($state->getCurrentFiles() as $file) { 
                    $uploadDelete($file); 
                } 
                $state->setCurrentFiles([]); 
            } 
 
            $filePaths = (array) $child->getData(); 
            $uploadDir = $config->getOption('upload_dir'); 
            $uploadNew = $config->getOption('upload_new'); 
 
            foreach ($state->getUploadedFiles() as $index => $file) { 
                $fileName = u($filePaths[$index])->replace($uploadDir, '')->toString(); 
                $uploadNew($file, $uploadDir, $fileName); 
            } 
        } 
    } 
 
    protected function getRedirectResponseAfterSave(AdminContext $context, string $action): RedirectResponse 
    { 
        $submitButtonName = $context->getRequest()->request->all()['ea']['newForm']['btn']; 
 
        if (Action::SAVE_AND_CONTINUE === $submitButtonName) { 
            $url = $this->container->get(AdminUrlGenerator::class) 
                ->setAction(Action::EDIT) 
                ->setEntityId($context->getEntity()->getPrimaryKeyValue()) 
                ->generateUrl(); 
 
            return $this->redirect($url); 
        } 
 
        if (Action::SAVE_AND_RETURN === $submitButtonName) { 
            $url = $context->getReferrer() 
                ?? $this->container->get(AdminUrlGenerator::class)->setAction(Action::INDEX)->generateUrl(); 
 
            return $this->redirect($url); 
        } 
 
        if (Action::SAVE_AND_ADD_ANOTHER === $submitButtonName) { 
            $url = $this->container->get(AdminUrlGenerator::class)->setAction(Action::NEW)->generateUrl(); 
 
            return $this->redirect($url); 
        } 
 
        return $this->redirectToRoute($context->getDashboardRouteName()); 
    } 
}