<?php declare(strict_types=1); 
 
namespace Shopware\Core\Checkout\Customer\Subscriber; 
 
use Shopware\Core\Checkout\Customer\CustomerEvents; 
use Shopware\Core\Framework\DataAbstractionLayer\EntityWriteResult; 
use Shopware\Core\Framework\DataAbstractionLayer\Event\EntityDeletedEvent; 
use Shopware\Core\Framework\DataAbstractionLayer\Event\EntityWrittenEvent; 
use Shopware\Core\PlatformRequest; 
use Shopware\Core\System\SalesChannel\Context\SalesChannelContextPersister; 
use Shopware\Core\System\SalesChannel\SalesChannelContext; 
use Symfony\Component\EventDispatcher\EventSubscriberInterface; 
use Symfony\Component\HttpFoundation\RequestStack; 
 
class CustomerTokenSubscriber implements EventSubscriberInterface 
{ 
    private SalesChannelContextPersister $contextPersister; 
 
    private RequestStack $requestStack; 
 
    /** 
     * @internal 
     */ 
    public function __construct( 
        SalesChannelContextPersister $contextPersister, 
        RequestStack $requestStack 
    ) { 
        $this->contextPersister = $contextPersister; 
        $this->requestStack = $requestStack; 
    } 
 
    /** 
     * @return array<string, string|array{0: string, 1: int}|list<array{0: string, 1?: int}>> 
     */ 
    public static function getSubscribedEvents() 
    { 
        return [ 
            CustomerEvents::CUSTOMER_WRITTEN_EVENT => 'onCustomerWritten', 
            CustomerEvents::CUSTOMER_DELETED_EVENT => 'onCustomerDeleted', 
        ]; 
    } 
 
    public function onCustomerWritten(EntityWrittenEvent $event): void 
    { 
        foreach ($event->getWriteResults() as $writeResult) { 
            if ($writeResult->getOperation() !== EntityWriteResult::OPERATION_UPDATE) { 
                continue; 
            } 
 
            $payload = $writeResult->getPayload(); 
            if (!$this->customerCredentialsChanged($payload)) { 
                continue; 
            } 
 
            $customerId = $payload['id']; 
            $newToken = $this->invalidateUsingSession($customerId); 
 
            if ($newToken) { 
                $this->contextPersister->revokeAllCustomerTokens($customerId, $newToken); 
            } else { 
                $this->contextPersister->revokeAllCustomerTokens($customerId); 
            } 
        } 
    } 
 
    public function onCustomerDeleted(EntityDeletedEvent $event): void 
    { 
        foreach ($event->getIds() as $customerId) { 
            $this->contextPersister->revokeAllCustomerTokens($customerId); 
        } 
    } 
 
    private function customerCredentialsChanged(array $payload): bool 
    { 
        return isset($payload['password']); 
    } 
 
    private function invalidateUsingSession(string $customerId): ?string 
    { 
        $master = $this->requestStack->getMainRequest(); 
 
        if (!$master) { 
            return null; 
        } 
 
        // Is not a storefront request 
        if (!$master->attributes->has(PlatformRequest::ATTRIBUTE_SALES_CHANNEL_CONTEXT_OBJECT)) { 
            return null; 
        } 
 
        /** @var SalesChannelContext $context */ 
        $context = $master->attributes->get(PlatformRequest::ATTRIBUTE_SALES_CHANNEL_CONTEXT_OBJECT); 
 
        // Not loggedin skip 
        if ($context->getCustomer() === null) { 
            return null; 
        } 
 
        // The written customer is not the same as logged-in. We don't modify the user session 
        if ($context->getCustomer()->getId() !== $customerId) { 
            return null; 
        } 
 
        $token = $context->getToken(); 
 
        $newToken = $this->contextPersister->replace($token, $context); 
 
        $context->assign([ 
            'token' => $newToken, 
        ]); 
 
        if (!$master->hasSession()) { 
            return null; 
        } 
 
        $session = $master->getSession(); 
        $session->migrate(); 
        $session->set('sessionId', $session->getId()); 
 
        $session->set(PlatformRequest::HEADER_CONTEXT_TOKEN, $newToken); 
        $master->headers->set(PlatformRequest::HEADER_CONTEXT_TOKEN, $newToken); 
 
        return $newToken; 
    } 
}