vendor/ezsystems/ezpublish-kernel/eZ/Publish/Core/MVC/Symfony/Controller/Content/ViewController.php line 62

Open in your IDE?
  1. <?php
  2. /**
  3.  * @copyright Copyright (C) eZ Systems AS. All rights reserved.
  4.  * @license For full copyright and license information view LICENSE file distributed with this source code.
  5.  */
  6. namespace eZ\Publish\Core\MVC\Symfony\Controller\Content;
  7. use eZ\Publish\API\Repository\Repository;
  8. use eZ\Publish\API\Repository\Values\Content\Content;
  9. use eZ\Publish\API\Repository\Values\Content\Location;
  10. use eZ\Publish\Core\Base\Exceptions\NotFoundException;
  11. use eZ\Publish\Core\MVC\Symfony\Controller\Controller;
  12. use eZ\Publish\Core\MVC\Symfony\MVCEvents;
  13. use eZ\Publish\Core\MVC\Symfony\Event\APIContentExceptionEvent;
  14. use eZ\Publish\Core\MVC\Symfony\Security\Authorization\Attribute as AuthorizationAttribute;
  15. use eZ\Publish\Core\Base\Exceptions\UnauthorizedException;
  16. use eZ\Publish\Core\MVC\Symfony\View\ContentView;
  17. use eZ\Publish\Core\MVC\Symfony\View\ViewManagerInterface;
  18. use Symfony\Component\HttpFoundation\Response;
  19. use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
  20. use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
  21. use Symfony\Component\Security\Core\Exception\AccessDeniedException;
  22. use DateTime;
  23. use Exception;
  24. /**
  25.  * This controller provides the content view feature.
  26.  *
  27.  * @since 6.0.0 All methods except `view()` are deprecated and will be removed in the future.
  28.  */
  29. class ViewController extends Controller
  30. {
  31.     /** @var \eZ\Publish\Core\MVC\Symfony\View\ViewManagerInterface */
  32.     protected $viewManager;
  33.     /** @var \Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface */
  34.     private $authorizationChecker;
  35.     public function __construct(ViewManagerInterface $viewManagerAuthorizationCheckerInterface $authorizationChecker)
  36.     {
  37.         $this->viewManager $viewManager;
  38.         $this->authorizationChecker $authorizationChecker;
  39.     }
  40.     /**
  41.      * This is the default view action or a ContentView object.
  42.      *
  43.      * It doesn't do anything by itself: the returned View object is rendered by the ViewRendererListener
  44.      * into an HttpFoundation Response.
  45.      *
  46.      * This action can be selectively replaced by a custom action by means of content_view
  47.      * configuration. Custom actions can add parameters to the view and customize the Response the View will be
  48.      * converted to. They may also bypass the ViewRenderer by returning an HttpFoundation Response.
  49.      *
  50.      * Cache is in both cases handled by the CacheViewResponseListener.
  51.      *
  52.      * @param \eZ\Publish\Core\MVC\Symfony\View\ContentView $view
  53.      *
  54.      * @return \eZ\Publish\Core\MVC\Symfony\View\ContentView
  55.      */
  56.     public function viewAction(ContentView $view)
  57.     {
  58.         return $view;
  59.     }
  60.     /**
  61.      * Embed a content.
  62.      * Behaves mostly like viewAction(), but with specific content load permission handling.
  63.      *
  64.      * @param \eZ\Publish\Core\MVC\Symfony\View\ContentView $view
  65.      *
  66.      * @return \eZ\Publish\Core\MVC\Symfony\View\ContentView
  67.      */
  68.     public function embedAction(ContentView $view)
  69.     {
  70.         return $view;
  71.     }
  72.     /**
  73.      * Build the response so that depending on settings it's cacheable.
  74.      *
  75.      * @param string|null $etag
  76.      * @param \DateTime|null $lastModified
  77.      *
  78.      * @return \Symfony\Component\HttpFoundation\Response
  79.      */
  80.     protected function buildResponse($etag nullDateTime $lastModified null)
  81.     {
  82.         $request $this->getRequest();
  83.         $response = new Response();
  84.         if ($this->getParameter('content.view_cache') === true) {
  85.             $response->setPublic();
  86.             if ($etag !== null) {
  87.                 $response->setEtag($etag);
  88.             }
  89.             if ($this->getParameter('content.ttl_cache') === true) {
  90.                 $response->setSharedMaxAge(
  91.                     $this->getParameter('content.default_ttl')
  92.                 );
  93.             }
  94.             // Make the response vary against X-User-Hash header ensures that an HTTP
  95.             // reverse proxy caches the different possible variations of the
  96.             // response as it can depend on user role for instance.
  97.             if ($request->headers->has('X-User-Hash')) {
  98.                 $response->setVary('X-User-Hash');
  99.             }
  100.             if ($lastModified != null) {
  101.                 $response->setLastModified($lastModified);
  102.             }
  103.         }
  104.         return $response;
  105.     }
  106.     /**
  107.      * Main action for viewing content through a location in the repository.
  108.      * Response will be cached with HttpCache validation model (Etag).
  109.      *
  110.      * @param int $locationId
  111.      * @param string $viewType
  112.      * @param bool $layout
  113.      * @param array $params
  114.      *
  115.      * @throws \Symfony\Component\Security\Core\Exception\AccessDeniedException
  116.      * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
  117.      * @throws \Exception
  118.      *
  119.      * @return \Symfony\Component\HttpFoundation\Response
  120.      *
  121.      * @deprecated Since 6.0.0. Viewing locations is now done with ViewContent.
  122.      */
  123.     public function viewLocation($locationId$viewType$layout false, array $params = [])
  124.     {
  125.         @trigger_error(
  126.             "ViewController::viewLocation() is deprecated since kernel 6.0.0, and will be removed in the future.\n" .
  127.             'Use ViewController::viewAction() instead.',
  128.             E_USER_DEPRECATED
  129.         );
  130.         $this->performAccessChecks();
  131.         $response $this->buildResponse();
  132.         try {
  133.             if (isset($params['location']) && $params['location'] instanceof Location) {
  134.                 $location $params['location'];
  135.             } else {
  136.                 $location $this->getRepository()->getLocationService()->loadLocation($locationId);
  137.                 if ($location->invisible) {
  138.                     throw new NotFoundHttpException("Location #$locationId cannot be displayed as it is flagged as invisible.");
  139.                 }
  140.             }
  141.             $response->headers->set('X-Location-Id'$locationId);
  142.             $response->setContent(
  143.                 $this->renderLocation(
  144.                     $location,
  145.                     $viewType,
  146.                     $layout,
  147.                     $params
  148.                 )
  149.             );
  150.             return $response;
  151.         } catch (UnauthorizedException $e) {
  152.             throw new AccessDeniedException();
  153.         } catch (NotFoundException $e) {
  154.             throw new NotFoundHttpException($e->getMessage(), $e);
  155.         } catch (NotFoundHttpException $e) {
  156.             throw $e;
  157.         } catch (Exception $e) {
  158.             return $this->handleViewException($response$params$e$viewTypenull$locationId);
  159.         }
  160.     }
  161.     /**
  162.      * Main action for viewing embedded location.
  163.      * Response will be cached with HttpCache validation model (Etag).
  164.      *
  165.      * @param int $locationId
  166.      * @param string $viewType
  167.      * @param bool $layout
  168.      * @param array $params
  169.      *
  170.      * @throws \Symfony\Component\Security\Core\Exception\AccessDeniedException
  171.      * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
  172.      * @throws \Exception
  173.      *
  174.      * @return \Symfony\Component\HttpFoundation\Response
  175.      *
  176.      * @deprecated Since 6.0.0. Viewing locations is now done with ViewContent.
  177.      */
  178.     public function embedLocation($locationId$viewType$layout false, array $params = [])
  179.     {
  180.         @trigger_error(
  181.             "ViewController::embedLocation() is deprecated since kernel 6.0.0, and will be removed in the future.\n" .
  182.             'Use ViewController::viewAction() instead.',
  183.             E_USER_DEPRECATED
  184.         );
  185.         $this->performAccessChecks();
  186.         $response $this->buildResponse();
  187.         try {
  188.             /** @var \eZ\Publish\API\Repository\Values\Content\Location $location */
  189.             $location $this->getRepository()->sudo(
  190.                 function (Repository $repository) use ($locationId) {
  191.                     return $repository->getLocationService()->loadLocation($locationId);
  192.                 }
  193.             );
  194.             if ($location->invisible) {
  195.                 throw new NotFoundHttpException("Location #{$locationId} cannot be displayed as it is flagged as invisible.");
  196.             }
  197.             // Check both 'content/read' and 'content/view_embed'.
  198.             if (
  199.                 !$this->authorizationChecker->isGranted(
  200.                     new AuthorizationAttribute(
  201.                         'content',
  202.                         'read',
  203.                         ['valueObject' => $location->contentInfo'targets' => $location]
  204.                     )
  205.                 )
  206.                 && !$this->authorizationChecker->isGranted(
  207.                     new AuthorizationAttribute(
  208.                         'content',
  209.                         'view_embed',
  210.                         ['valueObject' => $location->contentInfo'targets' => $location]
  211.                     )
  212.                 )
  213.             ) {
  214.                 throw new AccessDeniedException();
  215.             }
  216.             if ($response->isNotModified($this->getRequest())) {
  217.                 return $response;
  218.             }
  219.             $response->headers->set('X-Location-Id'$locationId);
  220.             $response->setContent(
  221.                 $this->renderLocation(
  222.                     $location,
  223.                     $viewType,
  224.                     $layout,
  225.                     $params
  226.                 )
  227.             );
  228.             return $response;
  229.         } catch (UnauthorizedException $e) {
  230.             throw new AccessDeniedException();
  231.         } catch (NotFoundException $e) {
  232.             throw new NotFoundHttpException($e->getMessage(), $e);
  233.         } catch (Exception $e) {
  234.             return $this->handleViewException($response$params$e$viewTypenull$locationId);
  235.         }
  236.     }
  237.     /**
  238.      * Main action for viewing content.
  239.      * Response will be cached with HttpCache validation model (Etag).
  240.      *
  241.      * @param int $contentId
  242.      * @param string $viewType
  243.      * @param bool $layout
  244.      * @param array $params
  245.      *
  246.      * @throws \Symfony\Component\Security\Core\Exception\AccessDeniedException
  247.      * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
  248.      * @throws \Exception
  249.      *
  250.      * @return \Symfony\Component\HttpFoundation\Response
  251.      *
  252.      * @deprecated Since 6.0.0. Viewing content is now done with ViewAction.
  253.      */
  254.     public function viewContent($contentId$viewType$layout false, array $params = [])
  255.     {
  256.         @trigger_error(
  257.             "ViewController::viewContent() is deprecated since kernel 6.0.0, and will be removed in the future.\n" .
  258.             'Use ViewController::viewAction() instead.',
  259.             E_USER_DEPRECATED
  260.         );
  261.         if ($viewType === 'embed') {
  262.             return $this->embedContent($contentId$viewType$layout$params);
  263.         }
  264.         $this->performAccessChecks();
  265.         $response $this->buildResponse();
  266.         try {
  267.             $content $this->getRepository()->getContentService()->loadContent($contentId);
  268.             if ($response->isNotModified($this->getRequest())) {
  269.                 return $response;
  270.             }
  271.             if (!isset($params['location']) && !isset($params['locationId'])) {
  272.                 $params['location'] = $this->getRepository()->getLocationService()->loadLocation($content->contentInfo->mainLocationId);
  273.             }
  274.             $response->headers->set('X-Location-Id'$content->contentInfo->mainLocationId);
  275.             $response->setContent(
  276.                 $this->renderContent($content$viewType$layout$params)
  277.             );
  278.             return $response;
  279.         } catch (UnauthorizedException $e) {
  280.             throw new AccessDeniedException();
  281.         } catch (NotFoundException $e) {
  282.             throw new NotFoundHttpException($e->getMessage(), $e);
  283.         } catch (Exception $e) {
  284.             return $this->handleViewException($response$params$e$viewType$contentId);
  285.         }
  286.     }
  287.     /**
  288.      * Main action for viewing embedded content.
  289.      * Response will be cached with HttpCache validation model (Etag).
  290.      *
  291.      * @param int $contentId
  292.      * @param string $viewType
  293.      * @param bool $layout
  294.      * @param array $params
  295.      *
  296.      * @throws \Symfony\Component\Security\Core\Exception\AccessDeniedException
  297.      * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
  298.      * @throws \Exception
  299.      *
  300.      * @return \Symfony\Component\HttpFoundation\Response
  301.      *
  302.      * @deprecated Since 6.0.0. Embedding content is now done with EmbedAction.
  303.      */
  304.     public function embedContent($contentId$viewType$layout false, array $params = [])
  305.     {
  306.         @trigger_error(
  307.             "ViewController::embedContent() is deprecated since kernel 6.0.0, and will be removed in the future.\n" .
  308.             'Use ViewController::viewAction() instead.',
  309.             E_USER_DEPRECATED
  310.         );
  311.         $this->performAccessChecks();
  312.         $response $this->buildResponse();
  313.         try {
  314.             /** @var \eZ\Publish\API\Repository\Values\Content\Content $content */
  315.             $content $this->getRepository()->sudo(
  316.                 function (Repository $repository) use ($contentId) {
  317.                     return $repository->getContentService()->loadContent($contentId);
  318.                 }
  319.             );
  320.             // Check both 'content/read' and 'content/view_embed'.
  321.             if (
  322.                 !$this->authorizationChecker->isGranted(
  323.                     new AuthorizationAttribute('content''read', ['valueObject' => $content])
  324.                 )
  325.                 && !$this->authorizationChecker->isGranted(
  326.                     new AuthorizationAttribute('content''view_embed', ['valueObject' => $content])
  327.                 )
  328.             ) {
  329.                 throw new AccessDeniedException();
  330.             }
  331.             // Check that Content is published, since sudo allows loading unpublished content.
  332.             if (
  333.                 !$content->getVersionInfo()->isPublished()
  334.                 && !$this->authorizationChecker->isGranted(
  335.                     new AuthorizationAttribute('content''versionread', ['valueObject' => $content])
  336.                 )
  337.             ) {
  338.                 throw new AccessDeniedException();
  339.             }
  340.             if ($response->isNotModified($this->getRequest())) {
  341.                 return $response;
  342.             }
  343.             $response->setContent(
  344.                 $this->renderContent($content$viewType$layout$params)
  345.             );
  346.             return $response;
  347.         } catch (UnauthorizedException $e) {
  348.             throw new AccessDeniedException();
  349.         } catch (NotFoundException $e) {
  350.             throw new NotFoundHttpException($e->getMessage(), $e);
  351.         } catch (Exception $e) {
  352.             return $this->handleViewException($response$params$e$viewType$contentId);
  353.         }
  354.     }
  355.     protected function handleViewException(Response $response$paramsException $e$viewType$contentId null$locationId null)
  356.     {
  357.         $event = new APIContentExceptionEvent(
  358.             $e,
  359.             [
  360.                 'contentId' => $contentId,
  361.                 'locationId' => $locationId,
  362.                 'viewType' => $viewType,
  363.             ]
  364.         );
  365.         $this->getEventDispatcher()->dispatch(MVCEvents::API_CONTENT_EXCEPTION$event);
  366.         if ($event->hasContentView()) {
  367.             $response->setContent(
  368.                 $this->viewManager->renderContentView(
  369.                     $event->getContentView(),
  370.                     $params
  371.                 )
  372.             );
  373.             return $response;
  374.         }
  375.         throw $e;
  376.     }
  377.     /**
  378.      * Creates the content to be returned when viewing a Location.
  379.      *
  380.      * @param Location $location
  381.      * @param string $viewType
  382.      * @param bool $layout
  383.      * @param array $params
  384.      *
  385.      * @return string
  386.      */
  387.     protected function renderLocation(Location $location$viewType$layout false, array $params = [])
  388.     {
  389.         return $this->viewManager->renderLocation($location$viewType$params + ['noLayout' => !$layout]);
  390.     }
  391.     /**
  392.      * Creates the content to be returned when viewing a Content.
  393.      *
  394.      * @param Content $content
  395.      * @param string $viewType
  396.      * @param bool $layout
  397.      * @param array $params
  398.      *
  399.      * @return string
  400.      */
  401.     protected function renderContent(Content $content$viewType$layout false, array $params = [])
  402.     {
  403.         return $this->viewManager->renderContent($content$viewType$params + ['noLayout' => !$layout]);
  404.     }
  405.     /**
  406.      * Performs the access checks.
  407.      */
  408.     protected function performAccessChecks()
  409.     {
  410.         if (!$this->isGranted(new AuthorizationAttribute('content''read'))) {
  411.             throw new AccessDeniedException();
  412.         }
  413.     }
  414. }