<?php declare(strict_types=1); 
 
namespace Shopware\Core\Content\Cms\Subscriber; 
 
use Doctrine\DBAL\Connection; 
use Shopware\Core\Content\Category\CategoryDefinition; 
use Shopware\Core\Content\Cms\CmsException; 
use Shopware\Core\Content\Cms\CmsPageDefinition; 
use Shopware\Core\Content\Cms\Exception\PageNotFoundException; 
use Shopware\Core\Content\Product\ProductDefinition; 
use Shopware\Core\Defaults; 
use Shopware\Core\Framework\DataAbstractionLayer\Event\BeforeDeleteEvent; 
use Shopware\Core\Framework\Uuid\Uuid; 
use Shopware\Core\System\SystemConfig\Event\BeforeSystemConfigChangedEvent; 
use Symfony\Component\EventDispatcher\EventSubscriberInterface; 
 
class CmsPageDefaultChangeSubscriber implements EventSubscriberInterface 
{ 
    /** 
     * @var array<string> 
     */ 
    public static array $defaultCmsPageConfigKeys = [ 
        ProductDefinition::CONFIG_KEY_DEFAULT_CMS_PAGE_PRODUCT, 
        CategoryDefinition::CONFIG_KEY_DEFAULT_CMS_PAGE_CATEGORY, 
    ]; 
 
    private Connection $connection; 
 
    /** 
     * @internal 
     */ 
    public function __construct( 
        Connection $connection 
    ) { 
        $this->connection = $connection; 
    } 
 
    public static function getSubscribedEvents(): array 
    { 
        return [ 
            BeforeSystemConfigChangedEvent::class => 'validateChangeOfDefaultCmsPage', 
            BeforeDeleteEvent::class => 'beforeDeletion', 
        ]; 
    } 
 
    /** 
     * @throws CmsException 
     * @throws \JsonException 
     */ 
    public function beforeDeletion(BeforeDeleteEvent $event): void 
    { 
        $cmsPageIds = $event->getIds(CmsPageDefinition::ENTITY_NAME); 
 
        // no cms page is affected by this deletion event 
        if (empty($cmsPageIds)) { 
            return; 
        } 
 
        $defaultPages = $this->cmsPageIsDefault($cmsPageIds); 
 
        // count !== 0 indicates that there are some cms pages which would be deleted but are currently a default 
        if (\count($defaultPages) !== 0) { 
            throw CmsException::deletionOfDefault($defaultPages); 
        } 
    } 
 
    /** 
     * @throws CmsException 
     * @throws PageNotFoundException 
     */ 
    public function validateChangeOfDefaultCmsPage(BeforeSystemConfigChangedEvent $event): void 
    { 
        $newDefaultCmsPageId = $event->getValue(); 
        $systemConfigKey = $event->getKey(); 
        $salesChannelId = $event->getSalesChannelId(); 
 
        if (!\in_array($systemConfigKey, self::$defaultCmsPageConfigKeys, true)) { 
            return; 
        } 
 
        // prevent deleting the overall default (salesChannelId === null) 
        // a sales channel specific default can still be deleted (salesChannelId !== null) 
        if ($newDefaultCmsPageId === null && $salesChannelId === null) { 
            $oldCmsPageId = $this->getCurrentOverallDefaultCmsPageId($systemConfigKey); 
 
            throw CmsException::overallDefaultSystemConfigDeletion($oldCmsPageId); 
        } 
 
        if (!\is_string($newDefaultCmsPageId) && $newDefaultCmsPageId !== null) { 
            throw new PageNotFoundException('invalid page'); 
        } 
 
        // prevent changing the default to an invalid cms page id 
        if (\is_string($newDefaultCmsPageId) && !$this->cmsPageExists($newDefaultCmsPageId)) { 
            throw new PageNotFoundException($newDefaultCmsPageId); 
        } 
    } 
 
    private function getCurrentOverallDefaultCmsPageId(string $systemConfigKey): string 
    { 
        $result = $this->connection->fetchOne( 
            'SELECT configuration_value FROM system_config WHERE configuration_key = :configKey AND sales_channel_id is NULL;', 
            [ 
                'configKey' => $systemConfigKey, 
            ] 
        ); 
 
        $config = json_decode($result, true); 
 
        return $config['_value']; 
    } 
 
    /** 
     * @param array<string> $cmsPageIds 
     * 
     * @return array<string> 
     */ 
    private function cmsPageIsDefault(array $cmsPageIds): array 
    { 
        $configurations = $this->connection->fetchAllAssociative( 
            'SELECT DISTINCT configuration_value FROM system_config WHERE configuration_key IN (:configKeys);', 
            [ 
                'configKeys' => self::$defaultCmsPageConfigKeys, 
            ], 
            [ 
                'configKeys' => Connection::PARAM_STR_ARRAY, 
            ] 
        ); 
 
        $defaultIds = []; 
        foreach ($configurations as $configuration) { 
            $configValue = $configuration['configuration_value']; 
            $config = json_decode($configValue, true); 
 
            $defaultIds[] = $config['_value']; 
        } 
 
        // returns from all provided cms pages the ones which are default 
        return array_intersect($cmsPageIds, $defaultIds); 
    } 
 
    private function cmsPageExists(string $cmsPageId): bool 
    { 
        $count = $this->connection->fetchOne( 
            'SELECT count(*) FROM cms_page WHERE id = :cmsPageId AND version_id = :versionId LIMIT 1;', 
            [ 
                'cmsPageId' => Uuid::fromHexToBytes($cmsPageId), 
                'versionId' => Uuid::fromHexToBytes(Defaults::LIVE_VERSION), 
            ] 
        ); 
 
        return $count === '1'; 
    } 
}