vendor/ibexa/core/src/lib/MVC/Symfony/View/Builder/ContentViewBuilder.php line 85

Open in your IDE?
  1. <?php
  2. /**
  3.  * @copyright Copyright (C) Ibexa AS. All rights reserved.
  4.  * @license For full copyright and license information view LICENSE file distributed with this source code.
  5.  */
  6. namespace Ibexa\Core\MVC\Symfony\View\Builder;
  7. use Ibexa\Contracts\Core\Repository\Exceptions\NotFoundException;
  8. use Ibexa\Contracts\Core\Repository\Repository;
  9. use Ibexa\Contracts\Core\Repository\Values\Content\Content;
  10. use Ibexa\Contracts\Core\Repository\Values\Content\Location;
  11. use Ibexa\Contracts\Core\Repository\Values\Content\VersionInfo;
  12. use Ibexa\Core\Base\Exceptions\InvalidArgumentException;
  13. use Ibexa\Core\Base\Exceptions\UnauthorizedException;
  14. use Ibexa\Core\Helper\ContentInfoLocationLoader;
  15. use Ibexa\Core\MVC\Exception\HiddenLocationException;
  16. use Ibexa\Core\MVC\Symfony\Controller\Content\PreviewController;
  17. use Ibexa\Core\MVC\Symfony\View\Configurator;
  18. use Ibexa\Core\MVC\Symfony\View\ContentView;
  19. use Ibexa\Core\MVC\Symfony\View\EmbedView;
  20. use Ibexa\Core\MVC\Symfony\View\ParametersInjector;
  21. use Symfony\Component\HttpFoundation\RequestStack;
  22. /**
  23.  * Builds ContentView objects.
  24.  */
  25. class ContentViewBuilder implements ViewBuilder
  26. {
  27.     /** @var \Ibexa\Contracts\Core\Repository\Repository */
  28.     private $repository;
  29.     /** @var \Ibexa\Contracts\Core\Repository\PermissionResolver */
  30.     private $permissionResolver;
  31.     /** @var \Ibexa\Core\MVC\Symfony\View\Configurator */
  32.     private $viewConfigurator;
  33.     /** @var \Ibexa\Core\MVC\Symfony\View\ParametersInjector */
  34.     private $viewParametersInjector;
  35.     /** @var \Symfony\Component\HttpFoundation\RequestStack */
  36.     private $requestStack;
  37.     /**
  38.      * Default templates, indexed per viewType (full, line, ...).
  39.      *
  40.      * @var array
  41.      */
  42.     private $defaultTemplates;
  43.     /** @var \Ibexa\Core\Helper\ContentInfoLocationLoader */
  44.     private $locationLoader;
  45.     public function __construct(
  46.         Repository $repository,
  47.         Configurator $viewConfigurator,
  48.         ParametersInjector $viewParametersInjector,
  49.         RequestStack $requestStack,
  50.         ContentInfoLocationLoader $locationLoader null
  51.     ) {
  52.         $this->repository $repository;
  53.         $this->viewConfigurator $viewConfigurator;
  54.         $this->viewParametersInjector $viewParametersInjector;
  55.         $this->locationLoader $locationLoader;
  56.         $this->permissionResolver $this->repository->getPermissionResolver();
  57.         $this->requestStack $requestStack;
  58.     }
  59.     public function matches($argument)
  60.     {
  61.         return strpos($argument'ibexa_content:') !== false;
  62.     }
  63.     /**
  64.      * @param array $parameters
  65.      *
  66.      * @return \Ibexa\Core\MVC\Symfony\View\ContentView|\Ibexa\Core\MVC\Symfony\View\View
  67.      *         If both contentId and locationId parameters are missing
  68.      *
  69.      * @throws \Ibexa\Core\Base\Exceptions\InvalidArgumentException
  70.      *         If both contentId and locationId parameters are missing
  71.      * @throws \Ibexa\Core\Base\Exceptions\UnauthorizedException
  72.      */
  73.     public function buildView(array $parameters)
  74.     {
  75.         $view = new ContentView(null, [], $parameters['viewType']);
  76.         $view->setIsEmbed($this->isEmbed($parameters));
  77.         if ($view->isEmbed() && $parameters['viewType'] === null) {
  78.             $view->setViewType(EmbedView::DEFAULT_VIEW_TYPE);
  79.         }
  80.         if (isset($parameters['location']) && $parameters['location'] instanceof Location) {
  81.             $location $parameters['location'];
  82.         } elseif (isset($parameters['locationId'])) {
  83.             $location $this->loadLocation($parameters['locationId']);
  84.         } else {
  85.             $location null;
  86.         }
  87.         if (isset($parameters['content'])) {
  88.             $content $parameters['content'];
  89.         } elseif ($location instanceof Location) {
  90.             // if we already have location load content true it so we avoid dual loading in case user does that in view
  91.             $content $location->getContent();
  92.             if (!$this->canRead($content$location$view->isEmbed())) {
  93.                 $missingPermission 'read' . ($view->isEmbed() ? '|view_embed' '');
  94.                 throw new UnauthorizedException(
  95.                     'content',
  96.                     $missingPermission,
  97.                     [
  98.                         'contentId' => $content->id,
  99.                         'locationId' => $location->id,
  100.                     ]
  101.                 );
  102.             }
  103.         } else {
  104.             if (isset($parameters['contentId'])) {
  105.                 $contentId $parameters['contentId'];
  106.             } elseif (isset($location)) {
  107.                 $contentId $location->contentId;
  108.             } else {
  109.                 throw new InvalidArgumentException('Content''Could not load any content from the parameters');
  110.             }
  111.             $languageCode $parameters['languageCode'] ?? $this->resolveMainRequestLanguageCode();
  112.             $content $view->isEmbed() ? $this->loadEmbeddedContent($contentId$location$languageCode) : $this->loadContent($contentId$languageCode);
  113.         }
  114.         $view->setContent($content);
  115.         if (isset($location)) {
  116.             if ($location->contentId !== $content->id) {
  117.                 throw new InvalidArgumentException('Location''Provided Location does not belong to the selected Content item');
  118.             }
  119.             if (isset($parameters['contentId']) && $location->contentId !== (int)$parameters['contentId']) {
  120.                 throw new InvalidArgumentException(
  121.                     'Location',
  122.                     'Provided Location does not belong to the Content item requested via the contentId parameter'
  123.                 );
  124.             }
  125.         } elseif (isset($this->locationLoader)) {
  126.             try {
  127.                 $location $this->locationLoader->loadLocation($content->contentInfo);
  128.             } catch (NotFoundException $e) {
  129.                 // nothing else to do
  130.             }
  131.         }
  132.         if (isset($location)) {
  133.             $view->setLocation($location);
  134.         }
  135.         if (
  136.             $view->isEmbed()
  137.             && $this->permissionResolver->canUser('content''view_embed'$content->contentInfo)
  138.             && !$this->permissionResolver->canUser('content''read'$content->contentInfo)
  139.         ) {
  140.             $parameters['params']['objectParameters']['doNotGenerateEmbedUrl'] = true;
  141.         }
  142.         $this->viewParametersInjector->injectViewParameters($view$parameters);
  143.         $this->viewConfigurator->configure($view);
  144.         return $view;
  145.     }
  146.     private function resolveMainRequestLanguageCode(): ?string
  147.     {
  148.         $mainRequest $this->requestStack->getMainRequest();
  149.         return $mainRequest === null null $mainRequest->attributes->get('languageCode');
  150.     }
  151.     /**
  152.      * Loads Content with id $contentId.
  153.      *
  154.      * @param mixed $contentId
  155.      *
  156.      * @return \Ibexa\Contracts\Core\Repository\Values\Content\Content
  157.      *
  158.      * @throws \Ibexa\Core\Base\Exceptions\UnauthorizedException
  159.      */
  160.     private function loadContent($contentId, ?string $languageCode null)
  161.     {
  162.         return $this->repository->getContentService()->loadContent(
  163.             $contentId,
  164.             $languageCode ? [$languageCode] : null
  165.         );
  166.     }
  167.     /**
  168.      * Loads the embedded content with id $contentId.
  169.      * Will load the content with sudo(), and check if the user can view_embed this content, for the given location
  170.      * if provided.
  171.      *
  172.      * @param mixed $contentId
  173.      * @param \Ibexa\Contracts\Core\Repository\Values\Content\Location|null $location
  174.      *
  175.      * @return \Ibexa\Contracts\Core\Repository\Values\Content\Content
  176.      *
  177.      * @throws \Ibexa\Core\Base\Exceptions\UnauthorizedException
  178.      */
  179.     private function loadEmbeddedContent($contentIdLocation $location null, ?string $languageCode null)
  180.     {
  181.         $content $this->repository->sudo(
  182.             static function (Repository $repository) use ($contentId$languageCode) {
  183.                 return $repository->getContentService()->loadContent($contentId$languageCode ? [$languageCode] : null);
  184.             }
  185.         );
  186.         if (!$this->canRead($content$location)) {
  187.             throw new UnauthorizedException(
  188.                 'content',
  189.                 'read|view_embed',
  190.                 ['contentId' => $contentId'locationId' => $location !== null $location->id 'n/a']
  191.             );
  192.         }
  193.         // Check that Content is published, since sudo allows loading unpublished content.
  194.         if (
  195.             $content->getVersionInfo()->status !== VersionInfo::STATUS_PUBLISHED
  196.             && !$this->permissionResolver->canUser('content''versionread'$content)
  197.         ) {
  198.             throw new UnauthorizedException('content''versionread', ['contentId' => $contentId]);
  199.         }
  200.         return $content;
  201.     }
  202.     /**
  203.      * Loads a visible Location.
  204.      *
  205.      * @param $locationId
  206.      *
  207.      * @return \Ibexa\Contracts\Core\Repository\Values\Content\Location
  208.      */
  209.     private function loadLocation($locationId)
  210.     {
  211.         $location $this->repository->sudo(
  212.             static function (Repository $repository) use ($locationId) {
  213.                 return $repository->getLocationService()->loadLocation($locationId);
  214.             }
  215.         );
  216.         $request $this->requestStack->getCurrentRequest();
  217.         if (!$request || !$request->attributes->get(PreviewController::PREVIEW_PARAMETER_NAMEfalse)) {
  218.             if ($location->invisible || $location->hidden) {
  219.                 throw new HiddenLocationException($location'Cannot display Location because it is flagged as invisible.');
  220.             }
  221.         }
  222.         return $location;
  223.     }
  224.     /**
  225.      * Checks if a user can read a content, or view it as an embed.
  226.      *
  227.      * @param \Ibexa\Contracts\Core\Repository\Values\Content\Content $content
  228.      * @param \Ibexa\Contracts\Core\Repository\Values\Content\Location|null $location
  229.      * @param bool $isEmbed
  230.      *
  231.      * @return bool
  232.      */
  233.     private function canRead(Content $contentLocation $location nullbool $isEmbed true): bool
  234.     {
  235.         $targets = isset($location) ? [$location] : [];
  236.         return
  237.             $this->permissionResolver->canUser('content''read'$content->contentInfo$targets) ||
  238.             ($isEmbed && $this->permissionResolver->canUser('content''view_embed'$content->contentInfo$targets));
  239.     }
  240.     /**
  241.      * Checks if the view is an embed one.
  242.      * Uses either the controller action (embedAction), or the viewType (embed/embed-inline).
  243.      *
  244.      * @param array $parameters The ViewBuilder parameters array.
  245.      *
  246.      * @return bool
  247.      */
  248.     private function isEmbed($parameters)
  249.     {
  250.         if ($parameters['_controller'] === 'ibexa_content:embedAction') {
  251.             return true;
  252.         }
  253.         if (\in_array($parameters['viewType'], ['embed''embed-inline'])) {
  254.             return true;
  255.         }
  256.         return false;
  257.     }
  258. }
  259. class_alias(ContentViewBuilder::class, 'eZ\Publish\Core\MVC\Symfony\View\Builder\ContentViewBuilder');