<?php
namespace B2bSellersCore\Components\B2bPlatform\Subscriber;
use B2bSellersCore\Components\B2bPlatform\Events\BeforeB2bRedirectAccountPageEvent;
use B2bSellersCore\Components\B2bPlatform\Events\BeforeB2bRedirectEmployeeCreationPageEvent;
use B2bSellersCore\Components\B2bPlatform\Events\BeforeB2bRedirectEmployeeFirstAdminEvent;
use B2bSellersCore\Components\B2bPlatform\Events\BeforeB2bRedirectEvent;
use B2bSellersCore\Components\B2bPlatform\Events\BeforeB2bRedirectSalesRepresentativeEvent;
use B2bSellersCore\Components\B2bPlatform\Traits\B2bContextTrait;
use B2bSellersCore\Components\Employee\Aggregate\EmployeeCustomer\EmployeeCustomerCollection;
use B2bSellersCore\Storefront\Page\B2bCreateEmployee\B2bCreateEmployeePage;
use B2bSellersCore\Storefront\Page\B2bPlatform\B2bPlatformPage;
use B2bSellersCore\Storefront\Page\B2bSelectEmployeeAdministrator\B2bSelectEmployeeAdministratorPage;
use Shopware\Core\Content\Cms\CmsPageEntity;
use Shopware\Core\Framework\DataAbstractionLayer\EntityRepositoryInterface;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\EqualsFilter;
use Shopware\Core\System\SalesChannel\SalesChannelContext;
use Shopware\Core\System\SystemConfig\SystemConfigService;
use Shopware\Storefront\Framework\Routing\StorefrontResponse;
use Shopware\Storefront\Page\Account\Order\AccountOrderPage;
use Shopware\Storefront\Page\Account\Overview\AccountOverviewPage;
use Shopware\Storefront\Page\Account\Profile\AccountProfilePage;
use Shopware\Storefront\Page\Address\Detail\AddressDetailPage;
use Shopware\Storefront\Page\Address\Listing\AddressListingPage;
use Shopware\Storefront\Page\Checkout\Offcanvas\OffcanvasCartPage;
use Shopware\Storefront\Page\Page;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Event\ResponseEvent;
use Symfony\Component\HttpKernel\HttpKernelInterface;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\Routing\RouterInterface;
class ResponseSubscriber implements EventSubscriberInterface
{
use B2bContextTrait;
public const SALES_REPRESENTATIVE_PAGE_WHITELIST = [
B2bPlatformPage::class,
OffcanvasCartPage::class
];
public const SW_LOGIN_PAGE_WHITELIST = [
'frontend.account.login',
'frontend.account.login.page',
'frontend.account.recover.page',
'frontend.b2b_create_employee_invitation.page'
];
public const B2B_LOGIN_PAGE_WHITELIST = [
'frontend.b2b_account.login.page',
'frontend.b2b_account.login.login'
];
public const B2B_GENERAL_PAGE_WHITELIST = [
'frontend.b2b_account_activation.page',
'frontend.b2b_employee.recover.password.page',
'frontend.b2b_employee.recover.password.reset',
'frontend.b2b_account.registration.page',
'frontend.b2b_account.registration.register',
'frontend.b2b_passwordless_login.page',
'frontend.b2b_passwordless_login.post',
'frontend.b2b_account.registration.register',
'frontend.b2b_account.registration.save',
'frontend.b2b_create_employee_invitation.page',
'frontend.b2b_passwordless_login.page'
];
public const ROUTE_CREATE_EMPLOYEE = 'frontend.b2b_create_employee';
public const ROUTE_B2B_PLATFORM = 'frontend.b2b_platform.index';
public const ROUTE_B2B_PLATFORM_PATH = 'frontend.b2b_platform.path';
public const ROUTE_SELECT_EMPLOYEE_ADMINISTRATOR = 'frontend.b2b_select_employee_admin';
private RouterInterface $router;
private SystemConfigService $configService;
private array $config;
private EntityRepositoryInterface $employeeCustomerRepository;
private EventDispatcherInterface $eventDispatcher;
public function __construct(
EventDispatcherInterface $eventDispatcher,
RouterInterface $router,
SystemConfigService $configService,
EntityRepositoryInterface $employeeCustomerRepository
)
{
$this->eventDispatcher = $eventDispatcher;
$this->router = $router;
$this->configService = $configService;
$this->employeeCustomerRepository = $employeeCustomerRepository;
}
public static function getSubscribedEvents(): array
{
return [
KernelEvents::RESPONSE => 'onKernelResponse',
];
}
public function onKernelResponse(ResponseEvent $event)
{
if($this->isWatcherProxyRequest($event->getRequest())) {
return;
}
if($event->getRequestType() === HttpKernelInterface::SUB_REQUEST) {
return;
}
if (!$event->getResponse() instanceof StorefrontResponse) {
return;
}
/** @var SalesChannelContext $context */
$context = $event->getResponse()->getContext();
if (empty($context)) {
$this->config = $this->getPluginConfig();
} else {
$this->config = $this->getPluginConfig($context->getSalesChannelId());
}
if (empty($context) || empty($context->getCustomer())) {
$this->checkShopAccessibility($event);
return;
}
if (!$this->hasB2bPlatformContext($context)) {
return;
}
$responseData = $event->getResponse()->getData();
if (!isset($responseData['page']) || !$responseData['page'] instanceof Page) {
return;
}
$this->eventDispatcher->dispatch(new BeforeB2bRedirectEvent($context));
$page = $responseData['page'];
$b2bPlatformContext = $this->getB2bPlatformContext($context);
$isSalesRepresentative = !$this->isSalesRepresentativeLoggedInAsCustomer($context) && $b2bPlatformContext->isSalesRepresentative();
/**
* As B2b Customer select first employee administrator if not set
*/
$this->eventDispatcher->dispatch(new BeforeB2bRedirectEmployeeFirstAdminEvent($context));
if (!$b2bPlatformContext->isEmployee() && !$isSalesRepresentative && $this->employeeExists($context)) {
if ($page instanceof B2bSelectEmployeeAdministratorPage || $page instanceof OffcanvasCartPage) {
return;
}
if (!$this->employeeAdministratorExists($context)) {
return;
}
$redirectResponse = new RedirectResponse($this->router->generate(self::ROUTE_SELECT_EMPLOYEE_ADMINISTRATOR));
$event->setResponse($redirectResponse);
return;
}
/**
* Redirect B2b Customers without employees to the employee creation
*/
$this->eventDispatcher->dispatch(new BeforeB2bRedirectEmployeeCreationPageEvent($context));
if (!$b2bPlatformContext->isEmployee() && !$isSalesRepresentative) {
if ($page instanceof B2bCreateEmployeePage || $page instanceof OffcanvasCartPage) {
return;
}
$request = $event->getRequest();
$params = [];
if(($route = $request->attributes->get('_route')) !== self::ROUTE_CREATE_EMPLOYEE) {
$params = [
'redirectTo' => $route,
'redirectParameters' => json_encode($request->attributes->get('_route_params'))
];
}
$redirectResponse = new RedirectResponse($this->router->generate(self::ROUTE_CREATE_EMPLOYEE, $params));
$event->setResponse($redirectResponse);
return;
}
/**
* Redirect b2b customer from the account page to the b2b platform page
*/
$this->eventDispatcher->dispatch(new BeforeB2bRedirectAccountPageEvent($context));
if ($b2bPlatformContext->isEmployee() && !$b2bPlatformContext->isSalesRepresentative()) {
if ($page instanceof AccountOverviewPage) {
$redirectResponse = new RedirectResponse($this->router->generate(self::ROUTE_B2B_PLATFORM_PATH, [
'path' => 'account'
]));
$event->setResponse($redirectResponse);
return;
}
if ($page instanceof AccountOrderPage) {
$redirectResponse = new RedirectResponse($this->router->generate(self::ROUTE_B2B_PLATFORM_PATH, [
'path' => 'order'
]));
$event->setResponse($redirectResponse);
return;
}
if ($page instanceof AccountProfilePage) {
$redirectResponse = new RedirectResponse($this->router->generate(self::ROUTE_B2B_PLATFORM_PATH, [
'path' => 'account'
]));
$event->setResponse($redirectResponse);
return;
}
if ($page instanceof AddressListingPage) {
$request = $event->getRequest();
if ($request->getPathInfo() !== '/account/address') {
return;
}
$redirectResponse = new RedirectResponse($this->router->generate(self::ROUTE_B2B_PLATFORM_PATH, [
'path' => 'address'
]));
$event->setResponse($redirectResponse);
return;
}
if ($page instanceof AddressDetailPage) {
$redirectResponse = new RedirectResponse($this->router->generate(self::ROUTE_B2B_PLATFORM_PATH, [
'path' => 'address/' . $page->getAddress()->getId()
]));
$event->setResponse($redirectResponse);
return;
}
}
/**
* Redirect sales representative to his allowed routes
*/
$this->eventDispatcher->dispatch(new BeforeB2bRedirectSalesRepresentativeEvent($context));
if ($isSalesRepresentative) {
if (in_array(get_class($page), self::SALES_REPRESENTATIVE_PAGE_WHITELIST)) {
return;
}
$redirectResponse = new RedirectResponse($this->router->generate(self::ROUTE_B2B_PLATFORM));
$event->setResponse($redirectResponse);
}
}
private function checkShopAccessibility(ResponseEvent $event)
{
$closedShopControllerWhitelist = [];
if (isset($this->config) && isset($this->config['closedShopControllerWhitelist'])) {
$closedShopControllerWhitelist = preg_split("/\r\n|\n|\r/", $this->config['closedShopControllerWhitelist']);
}
if (in_array($event->getRequest()->get('_route'), self::SW_LOGIN_PAGE_WHITELIST)) {
if ($event->getRequest()->get('_route') === 'frontend.account.login.page' &&
!in_array($this->config['closedShopRedirectTarget'], self::SW_LOGIN_PAGE_WHITELIST)
) {
if (isset($this->config['closedShop']) && $this->config['closedShop'] === true) {
$this->generateRouteResponse($event);
}
}
return;
}
if (!isset($this->config['closedShop']) || $this->config['closedShop'] !== true) {
if (in_array($event->getRequest()->get('_route'), self::B2B_LOGIN_PAGE_WHITELIST)) {
$redirectResponse = new RedirectResponse($this->router->generate('frontend.account.login'));
$event->setResponse($redirectResponse);
}
return;
}
if (in_array($event->getRequest()->get('_route'), self::B2B_LOGIN_PAGE_WHITELIST)) {
return;
}
if (in_array($event->getRequest()->get('_route'), self::B2B_GENERAL_PAGE_WHITELIST)) {
return;
}
if (in_array($event->getRequest()->get('_route'), $closedShopControllerWhitelist)) {
return;
}
if (isset($this->config['closedShopWhitelist'])) {
$responseData = $event->getResponse()->getData();
/** @var Page|null $page */
$page = $responseData['page'] ?? null;
/** @var CmsPageEntity $cmsPage */
$cmsPage = $responseData['cmsPage'] ?? null;
if (empty($page) && empty($cmsPage)) {
return;
}
// check if page is in whitelist
if (isset($page) && method_exists($page,
'getCmsPage') && $page->getCmsPage() !== null && in_array($page->getCmsPage()->getId(),
$this->config['closedShopWhitelist'])) {
return;
}
// check if cms page is in whitelist
if (isset($cmsPage) && in_array($cmsPage->getId(), $this->config['closedShopWhitelist'])) {
return;
}
}
if (isset($responseData['cookieGroups'])) {
return;
}
$this->generateRouteResponse($event);
}
private function generateRouteResponse(ResponseEvent $event)
{
$redirectResponse = new RedirectResponse($this->router->generate('frontend.account.login'));
if (isset($this->config['closedShopRedirectTarget'])) {
$redirectResponse = new RedirectResponse($this->router->generate($this->config['closedShopRedirectTarget']));
}
$event->setResponse($redirectResponse);
}
private function getPluginConfig(?string $salesChannelId = null): array
{
return $this->configService->get('B2bSellersCore.config', $salesChannelId);
}
private function employeeAdministratorExists(SalesChannelContext $context): bool
{
$criteria = new Criteria();
$criteria->addFilter(new EqualsFilter('customerId', $context->getCustomer()->getId()));
/** @var EmployeeCustomerCollection $employeeCustomers */
$employeeCustomers = $this->employeeCustomerRepository->search($criteria,
$context->getContext())->getEntities();
return $employeeCustomers->getAdmins()->count() <= 0;
}
private function employeeExists(SalesChannelContext $context): bool
{
$criteria = new Criteria();
$criteria->addFilter(new EqualsFilter('customerId', $context->getCustomer()->getId()));
/** @var EmployeeCustomerCollection $employeeCustomers */
$employeeCustomers = $this->employeeCustomerRepository->search($criteria,
$context->getContext())->getEntities();
return $employeeCustomers->count() > 0;
}
private function isWatcherProxyRequest(Request $request): bool
{
if($request->headers->get('x-b2b-platform-watcher') !== '1') {
return false;
}
$referer = $request->headers->get('referer');
if(empty($referer)) {
return false;
}
if(!filter_var($referer, FILTER_VALIDATE_URL)) {
return false;
}
$port = parse_url($referer)['port'] ?? null;
return $port === 9998;
}
}