<?php 
 
/* 
 * This file is part of the Sylius package. 
 * 
 * (c) Paweł Jędrzejewski 
 * 
 * For the full copyright and license information, please view the LICENSE 
 * file that was distributed with this source code. 
 */ 
 
declare(strict_types=1); 
 
namespace Sylius\Bundle\ResourceBundle\Controller; 
 
use Doctrine\Persistence\ObjectManager; 
use FOS\RestBundle\View\View; 
use Sylius\Bundle\ResourceBundle\Event\ResourceControllerEvent; 
use Sylius\Component\Resource\Exception\DeleteHandlingException; 
use Sylius\Component\Resource\Exception\UpdateHandlingException; 
use Sylius\Component\Resource\Factory\FactoryInterface; 
use Sylius\Component\Resource\Metadata\MetadataInterface; 
use Sylius\Component\Resource\Model\ResourceInterface; 
use Sylius\Component\Resource\Repository\RepositoryInterface; 
use Sylius\Component\Resource\ResourceActions; 
use Symfony\Component\DependencyInjection\ContainerAwareTrait; 
use Symfony\Component\DependencyInjection\ContainerInterface; 
use Symfony\Component\HttpFoundation\Request; 
use Symfony\Component\HttpFoundation\Response; 
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; 
use Symfony\Component\HttpKernel\Exception\HttpException; 
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; 
use Symfony\Component\Security\Core\Exception\AccessDeniedException; 
 
class ResourceController 
{ 
    use ControllerTrait; 
    use ContainerAwareTrait; 
 
    /** @var MetadataInterface */ 
    protected $metadata; 
 
    /** @var RequestConfigurationFactoryInterface */ 
    protected $requestConfigurationFactory; 
 
    /** @var ViewHandlerInterface|null */ 
    protected $viewHandler; 
 
    /** @var RepositoryInterface */ 
    protected $repository; 
 
    /** @var FactoryInterface */ 
    protected $factory; 
 
    /** @var NewResourceFactoryInterface */ 
    protected $newResourceFactory; 
 
    /** @var ObjectManager */ 
    protected $manager; 
 
    /** @var SingleResourceProviderInterface */ 
    protected $singleResourceProvider; 
 
    /** @var ResourcesCollectionProviderInterface */ 
    protected $resourcesCollectionProvider; 
 
    /** @var ResourceFormFactoryInterface */ 
    protected $resourceFormFactory; 
 
    /** @var RedirectHandlerInterface */ 
    protected $redirectHandler; 
 
    /** @var FlashHelperInterface */ 
    protected $flashHelper; 
 
    /** @var AuthorizationCheckerInterface */ 
    protected $authorizationChecker; 
 
    /** @var EventDispatcherInterface */ 
    protected $eventDispatcher; 
 
    /** @var StateMachineInterface|null */ 
    protected $stateMachine; 
 
    /** @var ResourceUpdateHandlerInterface */ 
    protected $resourceUpdateHandler; 
 
    /** @var ResourceDeleteHandlerInterface */ 
    protected $resourceDeleteHandler; 
 
    public function __construct( 
        MetadataInterface $metadata, 
        RequestConfigurationFactoryInterface $requestConfigurationFactory, 
        ?ViewHandlerInterface $viewHandler, 
        RepositoryInterface $repository, 
        FactoryInterface $factory, 
        NewResourceFactoryInterface $newResourceFactory, 
        ObjectManager $manager, 
        SingleResourceProviderInterface $singleResourceProvider, 
        ResourcesCollectionProviderInterface $resourcesFinder, 
        ResourceFormFactoryInterface $resourceFormFactory, 
        RedirectHandlerInterface $redirectHandler, 
        FlashHelperInterface $flashHelper, 
        AuthorizationCheckerInterface $authorizationChecker, 
        EventDispatcherInterface $eventDispatcher, 
        ?StateMachineInterface $stateMachine, 
        ResourceUpdateHandlerInterface $resourceUpdateHandler, 
        ResourceDeleteHandlerInterface $resourceDeleteHandler 
    ) { 
        $this->metadata = $metadata; 
        $this->requestConfigurationFactory = $requestConfigurationFactory; 
        $this->viewHandler = $viewHandler; 
        $this->repository = $repository; 
        $this->factory = $factory; 
        $this->newResourceFactory = $newResourceFactory; 
        $this->manager = $manager; 
        $this->singleResourceProvider = $singleResourceProvider; 
        $this->resourcesCollectionProvider = $resourcesFinder; 
        $this->resourceFormFactory = $resourceFormFactory; 
        $this->redirectHandler = $redirectHandler; 
        $this->flashHelper = $flashHelper; 
        $this->authorizationChecker = $authorizationChecker; 
        $this->eventDispatcher = $eventDispatcher; 
        $this->stateMachine = $stateMachine; 
        $this->resourceUpdateHandler = $resourceUpdateHandler; 
        $this->resourceDeleteHandler = $resourceDeleteHandler; 
    } 
 
    public function showAction(Request $request): Response 
    { 
        $configuration = $this->requestConfigurationFactory->create($this->metadata, $request); 
 
        $this->isGrantedOr403($configuration, ResourceActions::SHOW); 
        $resource = $this->findOr404($configuration); 
 
        $this->eventDispatcher->dispatch(ResourceActions::SHOW, $configuration, $resource); 
 
        if ($configuration->isHtmlRequest()) { 
            return $this->render($configuration->getTemplate(ResourceActions::SHOW . '.html'), [ 
                'configuration' => $configuration, 
                'metadata' => $this->metadata, 
                'resource' => $resource, 
                $this->metadata->getName() => $resource, 
            ]); 
        } 
 
        return $this->createRestView($configuration, $resource); 
    } 
 
    public function indexAction(Request $request): Response 
    { 
        $configuration = $this->requestConfigurationFactory->create($this->metadata, $request); 
 
        $this->isGrantedOr403($configuration, ResourceActions::INDEX); 
        $resources = $this->resourcesCollectionProvider->get($configuration, $this->repository); 
 
        $this->eventDispatcher->dispatchMultiple(ResourceActions::INDEX, $configuration, $resources); 
 
        if ($configuration->isHtmlRequest()) { 
            return $this->render($configuration->getTemplate(ResourceActions::INDEX . '.html'), [ 
                'configuration' => $configuration, 
                'metadata' => $this->metadata, 
                'resources' => $resources, 
                $this->metadata->getPluralName() => $resources, 
            ]); 
        } 
 
        return $this->createRestView($configuration, $resources); 
    } 
 
    public function createAction(Request $request): Response 
    { 
        $configuration = $this->requestConfigurationFactory->create($this->metadata, $request); 
 
        $this->isGrantedOr403($configuration, ResourceActions::CREATE); 
        $newResource = $this->newResourceFactory->create($configuration, $this->factory); 
 
        $form = $this->resourceFormFactory->create($configuration, $newResource); 
        $form->handleRequest($request); 
 
        if ($request->isMethod('POST') && $form->isSubmitted() && $form->isValid()) { 
            $newResource = $form->getData(); 
 
            $event = $this->eventDispatcher->dispatchPreEvent(ResourceActions::CREATE, $configuration, $newResource); 
 
            if ($event->isStopped() && !$configuration->isHtmlRequest()) { 
                throw new HttpException($event->getErrorCode(), $event->getMessage()); 
            } 
            if ($event->isStopped()) { 
                $this->flashHelper->addFlashFromEvent($configuration, $event); 
 
                $eventResponse = $event->getResponse(); 
                if (null !== $eventResponse) { 
                    return $eventResponse; 
                } 
 
                return $this->redirectHandler->redirectToIndex($configuration, $newResource); 
            } 
 
            if ($configuration->hasStateMachine()) { 
                $stateMachine = $this->getStateMachine(); 
                $stateMachine->apply($configuration, $newResource); 
            } 
 
            $this->repository->add($newResource); 
 
            if ($configuration->isHtmlRequest()) { 
                $this->flashHelper->addSuccessFlash($configuration, ResourceActions::CREATE, $newResource); 
            } 
 
            $postEvent = $this->eventDispatcher->dispatchPostEvent(ResourceActions::CREATE, $configuration, $newResource); 
 
            if (!$configuration->isHtmlRequest()) { 
                return $this->createRestView($configuration, $newResource, Response::HTTP_CREATED); 
            } 
 
            $postEventResponse = $postEvent->getResponse(); 
            if (null !== $postEventResponse) { 
                return $postEventResponse; 
            } 
 
            return $this->redirectHandler->redirectToResource($configuration, $newResource); 
        } 
 
        if (!$configuration->isHtmlRequest()) { 
            return $this->createRestView($configuration, $form, Response::HTTP_BAD_REQUEST); 
        } 
 
        $initializeEvent = $this->eventDispatcher->dispatchInitializeEvent(ResourceActions::CREATE, $configuration, $newResource); 
        $initializeEventResponse = $initializeEvent->getResponse(); 
        if (null !== $initializeEventResponse) { 
            return $initializeEventResponse; 
        } 
 
        return $this->render($configuration->getTemplate(ResourceActions::CREATE . '.html'), [ 
            'configuration' => $configuration, 
            'metadata' => $this->metadata, 
            'resource' => $newResource, 
            $this->metadata->getName() => $newResource, 
            'form' => $form->createView(), 
        ]); 
    } 
 
    public function updateAction(Request $request): Response 
    { 
        $configuration = $this->requestConfigurationFactory->create($this->metadata, $request); 
 
        $this->isGrantedOr403($configuration, ResourceActions::UPDATE); 
        $resource = $this->findOr404($configuration); 
 
        $form = $this->resourceFormFactory->create($configuration, $resource); 
        $form->handleRequest($request); 
 
        if ( 
            in_array($request->getMethod(), ['POST', 'PUT', 'PATCH'], true) 
            && $form->isSubmitted() 
            && $form->isValid() 
        ) { 
            $resource = $form->getData(); 
 
            /** @var ResourceControllerEvent $event */ 
            $event = $this->eventDispatcher->dispatchPreEvent(ResourceActions::UPDATE, $configuration, $resource); 
 
            if ($event->isStopped() && !$configuration->isHtmlRequest()) { 
                throw new HttpException($event->getErrorCode(), $event->getMessage()); 
            } 
            if ($event->isStopped()) { 
                $this->flashHelper->addFlashFromEvent($configuration, $event); 
 
                $eventResponse = $event->getResponse(); 
                if (null !== $eventResponse) { 
                    return $eventResponse; 
                } 
 
                return $this->redirectHandler->redirectToResource($configuration, $resource); 
            } 
 
            try { 
                $this->resourceUpdateHandler->handle($resource, $configuration, $this->manager); 
            } catch (UpdateHandlingException $exception) { 
                if (!$configuration->isHtmlRequest()) { 
                    return $this->createRestView($configuration, $form, $exception->getApiResponseCode()); 
                } 
 
                $this->flashHelper->addErrorFlash($configuration, $exception->getFlash()); 
 
                return $this->redirectHandler->redirectToReferer($configuration); 
            } 
 
            if ($configuration->isHtmlRequest()) { 
                $this->flashHelper->addSuccessFlash($configuration, ResourceActions::UPDATE, $resource); 
            } 
 
            $postEvent = $this->eventDispatcher->dispatchPostEvent(ResourceActions::UPDATE, $configuration, $resource); 
 
            if (!$configuration->isHtmlRequest()) { 
                if ($configuration->getParameters()->get('return_content', false)) { 
                    return $this->createRestView($configuration, $resource, Response::HTTP_OK); 
                } 
 
                return $this->createRestView($configuration, null, Response::HTTP_NO_CONTENT); 
            } 
 
            $postEventResponse = $postEvent->getResponse(); 
            if (null !== $postEventResponse) { 
                return $postEventResponse; 
            } 
 
            return $this->redirectHandler->redirectToResource($configuration, $resource); 
        } 
 
        if (!$configuration->isHtmlRequest()) { 
            return $this->createRestView($configuration, $form, Response::HTTP_BAD_REQUEST); 
        } 
 
        $initializeEvent = $this->eventDispatcher->dispatchInitializeEvent(ResourceActions::UPDATE, $configuration, $resource); 
        $initializeEventResponse = $initializeEvent->getResponse(); 
        if (null !== $initializeEventResponse) { 
            return $initializeEventResponse; 
        } 
 
        return $this->render($configuration->getTemplate(ResourceActions::UPDATE . '.html'), [ 
            'configuration' => $configuration, 
            'metadata' => $this->metadata, 
            'resource' => $resource, 
            $this->metadata->getName() => $resource, 
            'form' => $form->createView(), 
        ]); 
    } 
 
    public function deleteAction(Request $request): Response 
    { 
        $configuration = $this->requestConfigurationFactory->create($this->metadata, $request); 
 
        $this->isGrantedOr403($configuration, ResourceActions::DELETE); 
        $resource = $this->findOr404($configuration); 
 
        if ($configuration->isCsrfProtectionEnabled() && !$this->isCsrfTokenValid((string) $resource->getId(), (string) $request->request->get('_csrf_token'))) { 
            throw new HttpException(Response::HTTP_FORBIDDEN, 'Invalid csrf token.'); 
        } 
 
        $event = $this->eventDispatcher->dispatchPreEvent(ResourceActions::DELETE, $configuration, $resource); 
 
        if ($event->isStopped() && !$configuration->isHtmlRequest()) { 
            throw new HttpException($event->getErrorCode(), $event->getMessage()); 
        } 
        if ($event->isStopped()) { 
            $this->flashHelper->addFlashFromEvent($configuration, $event); 
 
            $eventResponse = $event->getResponse(); 
            if (null !== $eventResponse) { 
                return $eventResponse; 
            } 
 
            return $this->redirectHandler->redirectToIndex($configuration, $resource); 
        } 
 
        try { 
            $this->resourceDeleteHandler->handle($resource, $this->repository); 
        } catch (DeleteHandlingException $exception) { 
            if (!$configuration->isHtmlRequest()) { 
                return $this->createRestView($configuration, null, $exception->getApiResponseCode()); 
            } 
 
            $this->flashHelper->addErrorFlash($configuration, $exception->getFlash()); 
 
            return $this->redirectHandler->redirectToReferer($configuration); 
        } 
 
        if ($configuration->isHtmlRequest()) { 
            $this->flashHelper->addSuccessFlash($configuration, ResourceActions::DELETE, $resource); 
        } 
 
        $postEvent = $this->eventDispatcher->dispatchPostEvent(ResourceActions::DELETE, $configuration, $resource); 
 
        if (!$configuration->isHtmlRequest()) { 
            return $this->createRestView($configuration, null, Response::HTTP_NO_CONTENT); 
        } 
 
        $postEventResponse = $postEvent->getResponse(); 
        if (null !== $postEventResponse) { 
            return $postEventResponse; 
        } 
 
        return $this->redirectHandler->redirectToIndex($configuration, $resource); 
    } 
 
    public function bulkDeleteAction(Request $request): Response 
    { 
        $configuration = $this->requestConfigurationFactory->create($this->metadata, $request); 
 
        $this->isGrantedOr403($configuration, ResourceActions::BULK_DELETE); 
        $resources = $this->resourcesCollectionProvider->get($configuration, $this->repository); 
 
        if ( 
            $configuration->isCsrfProtectionEnabled() && 
            !$this->isCsrfTokenValid(ResourceActions::BULK_DELETE, (string) $request->request->get('_csrf_token')) 
        ) { 
            throw new HttpException(Response::HTTP_FORBIDDEN, 'Invalid csrf token.'); 
        } 
 
        $this->eventDispatcher->dispatchMultiple(ResourceActions::BULK_DELETE, $configuration, $resources); 
 
        foreach ($resources as $resource) { 
            $event = $this->eventDispatcher->dispatchPreEvent(ResourceActions::DELETE, $configuration, $resource); 
 
            if ($event->isStopped() && !$configuration->isHtmlRequest()) { 
                throw new HttpException($event->getErrorCode(), $event->getMessage()); 
            } 
            if ($event->isStopped()) { 
                $this->flashHelper->addFlashFromEvent($configuration, $event); 
 
                $eventResponse = $event->getResponse(); 
                if (null !== $eventResponse) { 
                    return $eventResponse; 
                } 
 
                return $this->redirectHandler->redirectToIndex($configuration, $resource); 
            } 
 
            try { 
                $this->resourceDeleteHandler->handle($resource, $this->repository); 
            } catch (DeleteHandlingException $exception) { 
                if (!$configuration->isHtmlRequest()) { 
                    return $this->createRestView($configuration, null, $exception->getApiResponseCode()); 
                } 
 
                $this->flashHelper->addErrorFlash($configuration, $exception->getFlash()); 
 
                return $this->redirectHandler->redirectToReferer($configuration); 
            } 
 
            $postEvent = $this->eventDispatcher->dispatchPostEvent(ResourceActions::DELETE, $configuration, $resource); 
        } 
 
        if (!$configuration->isHtmlRequest()) { 
            return $this->createRestView($configuration, null, Response::HTTP_NO_CONTENT); 
        } 
 
        $this->flashHelper->addSuccessFlash($configuration, ResourceActions::BULK_DELETE); 
 
        if (isset($postEvent)) { 
            $postEventResponse = $postEvent->getResponse(); 
            if (null !== $postEventResponse) { 
                return $postEventResponse; 
            } 
        } 
 
        return $this->redirectHandler->redirectToIndex($configuration); 
    } 
 
    public function applyStateMachineTransitionAction(Request $request): Response 
    { 
        $stateMachine = $this->getStateMachine(); 
        $configuration = $this->requestConfigurationFactory->create($this->metadata, $request); 
 
        $this->isGrantedOr403($configuration, ResourceActions::UPDATE); 
        $resource = $this->findOr404($configuration); 
 
        if ($configuration->isCsrfProtectionEnabled() && !$this->isCsrfTokenValid((string) $resource->getId(), $request->get('_csrf_token'))) { 
            throw new HttpException(Response::HTTP_FORBIDDEN, 'Invalid CSRF token.'); 
        } 
 
        $event = $this->eventDispatcher->dispatchPreEvent(ResourceActions::UPDATE, $configuration, $resource); 
 
        if ($event->isStopped() && !$configuration->isHtmlRequest()) { 
            throw new HttpException($event->getErrorCode(), $event->getMessage()); 
        } 
        if ($event->isStopped()) { 
            $this->flashHelper->addFlashFromEvent($configuration, $event); 
 
            $eventResponse = $event->getResponse(); 
            if (null !== $eventResponse) { 
                return $eventResponse; 
            } 
 
            return $this->redirectHandler->redirectToResource($configuration, $resource); 
        } 
 
        if (!$stateMachine->can($configuration, $resource)) { 
            throw new BadRequestHttpException(); 
        } 
 
        try { 
            $this->resourceUpdateHandler->handle($resource, $configuration, $this->manager); 
        } catch (UpdateHandlingException $exception) { 
            if (!$configuration->isHtmlRequest()) { 
                return $this->createRestView($configuration, $resource, $exception->getApiResponseCode()); 
            } 
 
            $this->flashHelper->addErrorFlash($configuration, $exception->getFlash()); 
 
            return $this->redirectHandler->redirectToReferer($configuration); 
        } 
 
        if ($configuration->isHtmlRequest()) { 
            $this->flashHelper->addSuccessFlash($configuration, ResourceActions::UPDATE, $resource); 
        } 
 
        $postEvent = $this->eventDispatcher->dispatchPostEvent(ResourceActions::UPDATE, $configuration, $resource); 
 
        if (!$configuration->isHtmlRequest()) { 
            if ($configuration->getParameters()->get('return_content', true)) { 
                return $this->createRestView($configuration, $resource, Response::HTTP_OK); 
            } 
 
            return $this->createRestView($configuration, null, Response::HTTP_NO_CONTENT); 
        } 
 
        $postEventResponse = $postEvent->getResponse(); 
        if (null !== $postEventResponse) { 
            return $postEventResponse; 
        } 
 
        return $this->redirectHandler->redirectToResource($configuration, $resource); 
    } 
 
    /** 
     * @return mixed 
     */ 
    protected function getParameter(string $name) 
    { 
        if (!$this->container instanceof ContainerInterface) { 
            throw new \RuntimeException(sprintf( 
                'Container passed to "%s" has to implements "%s".', 
                self::class, 
                ContainerInterface::class 
            )); 
        } 
 
        return $this->container->getParameter($name); 
    } 
 
    /** 
     * @throws AccessDeniedException 
     */ 
    protected function isGrantedOr403(RequestConfiguration $configuration, string $permission): void 
    { 
        if (!$configuration->hasPermission()) { 
            return; 
        } 
 
        $permission = $configuration->getPermission($permission); 
 
        if (!$this->authorizationChecker->isGranted($configuration, $permission)) { 
            throw new AccessDeniedException(); 
        } 
    } 
 
    /** 
     * @throws NotFoundHttpException 
     */ 
    protected function findOr404(RequestConfiguration $configuration): ResourceInterface 
    { 
        if (null === $resource = $this->singleResourceProvider->get($configuration, $this->repository)) { 
            throw new NotFoundHttpException(sprintf('The "%s" has not been found', $this->metadata->getHumanizedName())); 
        } 
 
        return $resource; 
    } 
 
    /** 
     * @param mixed $data 
     */ 
    protected function createRestView(RequestConfiguration $configuration, $data, int $statusCode = null): Response 
    { 
        if (null === $this->viewHandler) { 
            throw new \LogicException('You can not use the "non-html" request if FriendsOfSymfony Rest Bundle is not available. Try running "composer require friendsofsymfony/rest-bundle".'); 
        } 
 
        $view = View::create($data, $statusCode); 
 
        return $this->viewHandler->handle($configuration, $view); 
    } 
 
    protected function getStateMachine(): StateMachineInterface 
    { 
        if (null === $this->stateMachine) { 
            throw new \LogicException('You can not use the "state-machine" if Winzou State Machine Bundle is not available. Try running "composer require winzou/state-machine-bundle".'); 
        } 
 
        return $this->stateMachine; 
    } 
}