<?php declare(strict_types=1); 
 
namespace Shopware\Storefront\Page\Product; 
 
use Shopware\Core\Content\Category\Exception\CategoryNotFoundException; 
use Shopware\Core\Content\Product\Aggregate\ProductMedia\ProductMediaCollection; 
use Shopware\Core\Content\Product\Aggregate\ProductMedia\ProductMediaEntity; 
use Shopware\Core\Content\Product\Exception\ProductNotFoundException; 
use Shopware\Core\Content\Product\SalesChannel\CrossSelling\AbstractProductCrossSellingRoute; 
use Shopware\Core\Content\Product\SalesChannel\Detail\AbstractProductDetailRoute; 
use Shopware\Core\Content\Product\SalesChannel\SalesChannelProductEntity; 
use Shopware\Core\Content\Property\Aggregate\PropertyGroupOption\PropertyGroupOptionCollection; 
use Shopware\Core\Content\Property\PropertyGroupCollection; 
use Shopware\Core\Framework\DataAbstractionLayer\Exception\InconsistentCriteriaIdsException; 
use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria; 
use Shopware\Core\Framework\Feature; 
use Shopware\Core\Framework\Routing\Exception\MissingRequestParameterException; 
use Shopware\Core\System\SalesChannel\SalesChannelContext; 
use Shopware\Storefront\Page\GenericPageLoaderInterface; 
use Shopware\Storefront\Page\Product\Review\ProductReviewLoader; 
use Symfony\Component\EventDispatcher\EventDispatcherInterface; 
use Symfony\Component\HttpFoundation\Request; 
 
class ProductPageLoader 
{ 
    private GenericPageLoaderInterface $genericLoader; 
 
    private EventDispatcherInterface $eventDispatcher; 
 
    private AbstractProductDetailRoute $productDetailRoute; 
 
    private ProductReviewLoader $productReviewLoader; 
 
    private AbstractProductCrossSellingRoute $crossSellingRoute; 
 
    /** 
     * @internal 
     */ 
    public function __construct( 
        GenericPageLoaderInterface $genericLoader, 
        EventDispatcherInterface $eventDispatcher, 
        AbstractProductDetailRoute $productDetailRoute, 
        ProductReviewLoader $productReviewLoader, 
        AbstractProductCrossSellingRoute $crossSellingRoute 
    ) { 
        $this->genericLoader = $genericLoader; 
        $this->eventDispatcher = $eventDispatcher; 
        $this->productDetailRoute = $productDetailRoute; 
        $this->productReviewLoader = $productReviewLoader; 
        $this->crossSellingRoute = $crossSellingRoute; 
    } 
 
    /** 
     * @throws CategoryNotFoundException 
     * @throws InconsistentCriteriaIdsException 
     * @throws MissingRequestParameterException 
     * @throws ProductNotFoundException 
     */ 
    public function load(Request $request, SalesChannelContext $context): ProductPage 
    { 
        $productId = $request->attributes->get('productId'); 
        if (!$productId) { 
            throw new MissingRequestParameterException('productId', '/productId'); 
        } 
 
        $criteria = (new Criteria()) 
            ->addAssociation('manufacturer.media') 
            ->addAssociation('options.group') 
            ->addAssociation('properties.group') 
            ->addAssociation('mainCategories.category') 
            ->addAssociation('media'); 
 
        $this->eventDispatcher->dispatch(new ProductPageCriteriaEvent($productId, $criteria, $context)); 
 
        $result = $this->productDetailRoute->load($productId, $request, $context, $criteria); 
        $product = $result->getProduct(); 
 
        if ($product->getMedia()) { 
            $product->getMedia()->sort(function (ProductMediaEntity $a, ProductMediaEntity $b) { 
                return $a->getPosition() <=> $b->getPosition(); 
            }); 
        } 
 
        if ($product->getMedia() && $product->getCover()) { 
            $product->setMedia(new ProductMediaCollection(array_merge( 
                [$product->getCover()->getId() => $product->getCover()], 
                $product->getMedia()->getElements() 
            ))); 
        } 
 
        if ($category = $product->getSeoCategory()) { 
            $request->request->set('navigationId', $category->getId()); 
        } 
 
        $page = $this->genericLoader->load($request, $context); 
        /** @var ProductPage $page */ 
        $page = ProductPage::createFrom($page); 
 
        $page->setProduct($product); 
        $page->setConfiguratorSettings($result->getConfigurator() ?? new PropertyGroupCollection()); 
        $page->setNavigationId($product->getId()); 
 
        if (!Feature::isActive('v6.5.0.0')) { 
            $this->loadDefaultAdditions($product, $page, $request, $context); 
        } elseif ($cmsPage = $product->getCmsPage()) { 
            $page->setCmsPage($cmsPage); 
        } 
 
        $this->loadOptions($page); 
        $this->loadMetaData($page); 
 
        $this->eventDispatcher->dispatch( 
            new ProductPageLoadedEvent($page, $context, $request) 
        ); 
 
        return $page; 
    } 
 
    private function loadOptions(ProductPage $page): void 
    { 
        $options = new PropertyGroupOptionCollection(); 
 
        if (($optionIds = $page->getProduct()->getOptionIds()) === null) { 
            $page->setSelectedOptions($options); 
 
            return; 
        } 
 
        foreach ($page->getConfiguratorSettings() as $group) { 
            $groupOptions = $group->getOptions(); 
            if ($groupOptions === null) { 
                continue; 
            } 
            foreach ($optionIds as $optionId) { 
                $groupOption = $groupOptions->get($optionId); 
                if ($groupOption !== null) { 
                    $options->add($groupOption); 
                } 
            } 
        } 
 
        $page->setSelectedOptions($options); 
    } 
 
    private function loadMetaData(ProductPage $page): void 
    { 
        $metaInformation = $page->getMetaInformation(); 
 
        if (!$metaInformation) { 
            return; 
        } 
 
        $metaDescription = $page->getProduct()->getTranslation('metaDescription') 
            ?? $page->getProduct()->getTranslation('description'); 
        $metaInformation->setMetaDescription((string) $metaDescription); 
 
        $metaInformation->setMetaKeywords((string) $page->getProduct()->getTranslation('keywords')); 
 
        if ((string) $page->getProduct()->getTranslation('metaTitle') !== '') { 
            $metaInformation->setMetaTitle((string) $page->getProduct()->getTranslation('metaTitle')); 
 
            return; 
        } 
 
        $metaTitleParts = [$page->getProduct()->getTranslation('name')]; 
 
        foreach ($page->getSelectedOptions() as $option) { 
            $metaTitleParts[] = $option->getTranslation('name'); 
        } 
 
        $metaTitleParts[] = $page->getProduct()->getProductNumber(); 
 
        $metaInformation->setMetaTitle(implode(' | ', $metaTitleParts)); 
    } 
 
    /** 
     * @@deprecated tag:v6.5.0 - will be removed because cms page id will always be set 
     */ 
    private function loadDefaultAdditions(SalesChannelProductEntity $product, ProductPage $page, Request $request, SalesChannelContext $context): void 
    { 
        if ($cmsPage = $product->getCmsPage()) { 
            $page->setCmsPage($cmsPage); 
 
            return; 
        } 
 
        $request->request->set('parentId', $product->getParentId()); 
        $reviews = $this->productReviewLoader->load($request, $context); 
        $reviews->setParentId($product->getParentId() ?? $product->getId()); 
 
        $page->setReviews($reviews); 
 
        $crossSellings = $this->crossSellingRoute->load($product->getId(), new Request(), $context, new Criteria()); 
 
        $page->setCrossSellings($crossSellings->getResult()); 
    } 
}