<?php declare(strict_types=1);
namespace Acris\ShopSwitch\Components;
use Acris\ShopSwitch\Components\Exception\ActiveDomainNotFoundException;
use Acris\ShopSwitch\Components\ShopSwitch\ShopSwitchResult;
use Acris\ShopSwitch\Components\ShopSwitch\ShopSwitchRouteResponse;
use Acris\ShopSwitch\Components\Struct\AbstractPossibleStruct;
use Acris\ShopSwitch\Components\Struct\PossibleCombinationCollection;
use Acris\ShopSwitch\Components\Struct\PossibleCombinationStruct;
use Acris\ShopSwitch\Components\Struct\PossibleCountryCollection;
use Acris\ShopSwitch\Components\Struct\PossibleCountryStruct;
use Acris\ShopSwitch\Components\Struct\PossibleLanguageCollection;
use Acris\ShopSwitch\Components\Struct\PossibleLanguageStruct;
use Acris\ShopSwitch\Custom\ShopSwitchRuleDefinition;
use Acris\ShopSwitch\Custom\ShopSwitchRuleDomainEntity;
use Acris\ShopSwitch\Custom\ShopSwitchRuleEntity;
use Acris\ShopSwitch\Storefront\Controller\ShopSwitchController;
use Shopware\Core\Framework\Context;
use Shopware\Core\Framework\DataAbstractionLayer\EntityRepositoryInterface;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
use Shopware\Core\Framework\Struct\Collection;
use Shopware\Core\SalesChannelRequest;
use Shopware\Core\System\Country\CountryCollection;
use Shopware\Core\System\SalesChannel\Aggregate\SalesChannelDomain\SalesChannelDomainEntity;
use Shopware\Core\System\SalesChannel\SalesChannelContext;
use Shopware\Core\System\SalesChannel\SalesChannelEntity;
use Symfony\Component\HttpFoundation\Request;
/**
* Helper Servce for ShopSwitchRoute!
* Aim: To reduce the amount of code in ShopSwitchroute
* Why: ShopSwitchRoute becomes too complex to maintain and create features.
* Dependent on ShopSwitchRoute
*/
class ShopSwitchHelperService
{
private ShopSwitchRuleService $shopSwitchRuleService;
private EntityRepositoryInterface $salesChannelDomainRepository;
private SalesChannelCountryLanguageService $salesChannelCountryLanguageService;
public function __construct(
ShopSwitchRuleService $shopSwitchRuleService,
EntityRepositoryInterface $salesChannelDomainRepository,
SalesChannelCountryLanguageService $salesChannelCountryLanguageService
)
{
$this->shopSwitchRuleService = $shopSwitchRuleService;
$this->salesChannelDomainRepository = $salesChannelDomainRepository;
$this->salesChannelCountryLanguageService = $salesChannelCountryLanguageService;
}
public function getValidResponse(string $activeDomainId, ShopSwitchRuleEntity $activeRule, string $activeDomainUrl, Request $request, SalesChannelEntity $activeSalesChannel, Context $context, ShopSwitchRuleDomainEntity $activeRuleDomain, ?array $languageIds, ?string $countryIso): ShopSwitchRouteResponse
{
$defaultCountryId = $activeRuleDomain->getDefaultShippingCountryId();
$activeDomainEntity = $this->getDomainById($activeDomainId, $context);
$possibleLanguageCollection = $this->getPossibleLanguages($activeRule, $request, $activeDomainUrl, $context, $languageIds);
$possibleCountryCollection = $this->getPossibleCountries($activeRule, $request, $activeDomainUrl, $context, $countryIso);
$possibleCountryCollectionForShipping = $possibleCountryCollection;
// if only language redirect is wanted and switch type is not set only country, then set possible country collection to null
if($activeRule->getSwitchType() !== ShopSwitchRuleDefinition::SWITCH_TYPE_SET_ONLY_COUNTRY && $activeRule->getRuleDefinitionType() === ShopSwitchRuleDefinition::RULE_DEFINITION_TYPE_LANGUAGE) {
$possibleCountryCollection = new PossibleCountryCollection();
}
if(!empty($possibleCountries) && !empty($possibleLanguages)) {
$this->removeNotExistingCombinations($possibleCountryCollection, $possibleLanguageCollection);
}
$possibleCombinationCollection = $this->getPossibleCombinations($possibleCountryCollection, $possibleLanguageCollection);
// if the country which was detected is not inside the possible countries of the sales channel, we want to take the best blacklist rule what we find
if($possibleCombinationCollection->count() === 0) {
$possibleCombinationCollection = $this->getPossibleBlacklistCombination($activeRule, $possibleCountryCollection);
}
$this->sortPossibleCombinationsByActiveDomain($possibleCombinationCollection, $activeDomainUrl);
if($this->getStayOnPageWithoutAsk($activeRule, $possibleCombinationCollection, $activeDomainUrl, $request, $activeSalesChannel) === true) {
$countryId = $this->getCountryId($possibleCombinationCollection, $possibleCountryCollectionForShipping, $activeDomainEntity->getUrl(), $defaultCountryId, $context);
return new ShopSwitchRouteResponse(new ShopSwitchResult($activeDomainId, $possibleCountryCollection, $possibleLanguageCollection, $activeRule, $activeDomainUrl, true, null, $countryId));
}
if($activeRule->getSwitchType() === ShopSwitchRuleDefinition::SWITCH_TYPE_DIRECT) {
$redirectDomainUrl = '';
$firstPossibleCombination = $possibleCombinationCollection->first();
if($firstPossibleCombination instanceof PossibleCombinationStruct) {
$redirectDomainUrl = $firstPossibleCombination->getDomainUrl();
if($firstPossibleCombination->getPossibleStruct() instanceof AbstractPossibleStruct && empty($firstPossibleCombination->getPossibleStruct()->getRuleDomainEntity()->getDefaultShippingCountryId()) === false) {
$defaultCountryId = $firstPossibleCombination->getPossibleStruct()->getRuleDomainEntity()->getDefaultShippingCountryId();
}
}
$stayOnPageWithoutAsk = false;
if(empty($redirectDomainUrl)) {
$redirectDomainUrl = $activeDomainUrl;
}
if($redirectDomainUrl === $activeDomainUrl) {
$stayOnPageWithoutAsk = true;
}
$countryId = "";
// for redirect to external domain we don't want to get the shipping country as a parameter
if($stayOnPageWithoutAsk !== true && $this->isExternalRedirect($redirectDomainUrl, $activeRule) !== true) {
$countryId = $this->getCountryId($possibleCombinationCollection, $possibleCountryCollectionForShipping, $activeDomainEntity->getUrl(), $defaultCountryId, $context);
}
return new ShopSwitchRouteResponse(new ShopSwitchResult($activeDomainId, $possibleCountryCollection, $possibleLanguageCollection, $activeRule, $activeDomainUrl, $stayOnPageWithoutAsk, $redirectDomainUrl, $countryId));
}
if($activeRule->getSwitchType() === ShopSwitchRuleDefinition::SWITCH_TYPE_SET_ONLY_COUNTRY) {
$stayOnPageWithoutAsk = true;
} else {
$stayOnPageWithoutAsk = false;
}
$countryId = $this->getCountryId($possibleCombinationCollection, $possibleCountryCollectionForShipping, $activeDomainEntity->getUrl(), $defaultCountryId, $context);
return new ShopSwitchRouteResponse(new ShopSwitchResult($activeDomainId, $possibleCountryCollection, $possibleLanguageCollection, $activeRule, $activeDomainUrl, $stayOnPageWithoutAsk, "", $countryId));
}
public function getPossibleCountries(ShopSwitchRuleEntity $activeRule, Request $request, string $activeDomainUrl, Context $context, ?string $countryIso): PossibleCountryCollection
{
// if only language is checked, we also want to find the correct shipping country
if($activeRule->getSwitchType() === ShopSwitchRuleDefinition::SWITCH_TYPE_SET_ONLY_COUNTRY
|| $activeRule->getRuleDefinitionType() === ShopSwitchRuleDefinition::RULE_DEFINITION_TYPE_LANGUAGE) {
$possibleCountryCollection = $this->shopSwitchRuleService->getPossibleCountriesOfSalesChannel($activeRule, $context);
} else {
$possibleCountryCollection = $this->shopSwitchRuleService->getPossibleCountriesOfRule($activeRule, $activeDomainUrl, $context, $request);
}
$this->setActiveCountry($possibleCountryCollection, $countryIso);
return $possibleCountryCollection;
}
public function getRawPossibleCountries(ShopSwitchRuleEntity $activeRule, Request $request, string $activeDomainUrl, Context $context): PossibleCountryCollection
{
if($activeRule->getSwitchType() === ShopSwitchRuleDefinition::SWITCH_TYPE_SET_ONLY_COUNTRY) {
$possibleCountryCollection = $this->shopSwitchRuleService->getPossibleCountriesOfSalesChannel($activeRule, $context);
} else {
$possibleCountryCollection = $this->shopSwitchRuleService->getPossibleCountriesOfRule($activeRule, $activeDomainUrl, $context, $request);
}
return $possibleCountryCollection;
}
/**
* Gets possibleLanguages for ShopSwitchResult.
* @param ShopSwitchRuleEntity $activeRule
* @param Request $request
* @param string $activeDomainUrl
* @param Context $context
* @return array
*/
public function getPossibleLanguages(ShopSwitchRuleEntity $activeRule, Request $request, string $activeDomainUrl, Context $context, ?array $languageIds = []): PossibleLanguageCollection
{
if($activeRule->getRuleDefinitionType() === ShopSwitchRuleDefinition::RULE_DEFINITION_TYPE_LANGUAGE || $activeRule->getRuleDefinitionType() === ShopSwitchRuleDefinition::RULE_DEFINITION_TYPE_COUNTRY_LANGUAGE) {
$possibleLanguageCollection = $this->shopSwitchRuleService->getPossibleLanguagesOfRule($activeRule, $activeDomainUrl, $context, $request);
if(empty($languageIds) === false) {
$this->setActiveLanguage($possibleLanguageCollection, $languageIds);
}
} else {
$possibleLanguageCollection = new PossibleLanguageCollection();
}
return $possibleLanguageCollection;
}
public function getDomainByIdAndSalesChannelContext(string $activeDomainId, SalesChannelContext $salesChannelContext): SalesChannelDomainEntity
{
if($salesChannelContext->getSalesChannel()->getDomains() && $salesChannelContext->getSalesChannel()->getDomains()->has($activeDomainId)) {
return $salesChannelContext->getSalesChannel()->getDomains()->get($activeDomainId);
}
return $this->getDomainById($activeDomainId, $salesChannelContext->getContext());
}
public function getDomainById(string $activeDomainId, Context $context): SalesChannelDomainEntity
{
/** @var SalesChannelDomainEntity $domainEntity */
$domainEntity = $this->salesChannelDomainRepository->search(
(new Criteria([$activeDomainId]))
->addAssociation('salesChannel')
->addAssociation('salesChannel.domains')
, $context)->first();
if (empty($domainEntity) === true) {
throw new ActiveDomainNotFoundException();
}
return $domainEntity;
}
private function setActiveCountry(PossibleCountryCollection $possibleCountryCollection, ?string $countryIso): void
{
if ($countryIso === null) {
return;
}
/**
* @var string $key
* @var PossibleCountryStruct $possibleCountryStruct
*/
foreach ($possibleCountryCollection->getElements() as $key => $possibleCountryStruct) {
if ($possibleCountryStruct->getIso() === $countryIso) {
$possibleCountryStruct->setActive(true);
} else {
$possibleCountryStruct->setActive(false);
}
}
}
private function setActiveLanguage(PossibleLanguageCollection $possibleLanguageCollection, array $languageIds): void
{
/**
* @var string $key
* @var PossibleLanguageStruct $possibleLanguageStruct
*/
foreach ($possibleLanguageCollection->getElements() as $key => $possibleLanguageStruct) {
$possibleLanguageStruct->setActive(false);
foreach ($languageIds as $languageId) {
if (strtolower($possibleLanguageStruct->getLanguageId()) === $languageId) {
$possibleLanguageStruct->setActive(true);
break;
}
}
}
}
public function removeNotExistingCombinations(PossibleCountryCollection $possibleCountryCollection, PossibleLanguageCollection $possibleLanguageCollection)
{
foreach ($possibleCountryCollection->getElements() as $possibleCountryKey => $possibleCountry) {
foreach ($possibleLanguageCollection->getElements() as $possibleLanguageKey => $possibleLanguage) {
foreach ($possibleCountry->getDomainUrls() as $domainUrlCountry) {
foreach ($possibleLanguage->getDomainUrls() as $domainUrlLanguage) {
if ($domainUrlCountry === $domainUrlLanguage) {
continue 4;
}
}
}
}
$possibleCountryCollection->remove($possibleCountryKey);
}
foreach ($possibleLanguageCollection->getElements() as $possibleLanguageKey => $possibleLanguage) {
foreach ($possibleCountryCollection->getElements() as $possibleCountryKey => $possibleCountry) {
foreach ($possibleLanguage->getDomainUrls() as $domainUrlLanguage) {
foreach ($possibleCountry->getDomainUrls() as $domainUrlCountry) {
if ($domainUrlCountry === $domainUrlLanguage) {
continue 4;
}
}
}
}
$possibleLanguageCollection->remove($possibleLanguageKey);
}
}
public function getPossibleCombinations(PossibleCountryCollection $possibleCountryCollection, PossibleLanguageCollection $possibleLanguageCollection): PossibleCombinationCollection
{
$possibleCombinationCollection1 = $this->getCombinationOfCollections($possibleCountryCollection, $possibleLanguageCollection);
$possibleCombinationCollection2 = $this->getCombinationOfCollections($possibleLanguageCollection, $possibleCountryCollection);
return $this->mergeAndUniqueCombinations($possibleCombinationCollection1, $possibleCombinationCollection2);
}
private function getCombinationOfCollections(Collection $collection1, Collection $collection2): PossibleCombinationCollection
{
$possibleCombinationCollection = new PossibleCombinationCollection();
/** @var AbstractPossibleStruct $item1 */
foreach ($collection1->getElements() as $item1) {
if ($item1->isActive() !== true) {
continue;
}
if ($collection2->count() === 0) {
foreach ($item1->getDomainUrls() as $domainUrlItem1) {
$possibleCombinationCollection->add(new PossibleCombinationStruct($domainUrlItem1, $item1));
}
} else {
/** @var AbstractPossibleStruct $item2 */
foreach ($collection2->getElements() as $item2) {
if ($item2->isActive() !== true) {
continue;
}
foreach ($item2->getDomainUrls() as $domainUrlItem2) {
foreach ($item1->getDomainUrls() as $domainUrlItem1) {
if ($domainUrlItem1 === $domainUrlItem2) {
$possibleCombinationCollection->add(new PossibleCombinationStruct($domainUrlItem1, $item1));
}
}
}
}
}
}
return $possibleCombinationCollection;
}
private function mergeAndUniqueCombinations(PossibleCombinationCollection $possibleCombinationCollection1, PossibleCombinationCollection $possibleCombinationCollection2): PossibleCombinationCollection
{
$possibleCombinationCollection = $possibleCombinationCollection1;
foreach ($possibleCombinationCollection2->getElements() as $item2) {
foreach ($possibleCombinationCollection as $possibleCombinationStruct) {
if($possibleCombinationStruct->getDomainUrl() === $item2->getDomainUrl()) {
continue 2;
}
}
$possibleCombinationCollection->add($item2);
}
return $possibleCombinationCollection;
}
private function getStayOnPageWithoutAsk(ShopSwitchRuleEntity $activeRule, PossibleCombinationCollection $possibleCombinationCollection, string $activeDomainUrl, Request $request, SalesChannelEntity $salesChannelEntity): bool
{
return $this->comesFromOwnDomain($request, $activeRule) ||
(($activeRule->isAskIfMatch() === false || $activeRule->getSwitchType() === ShopSwitchRuleDefinition::SWITCH_TYPE_DIRECT)
&& $possibleCombinationCollection->hasUrl($activeDomainUrl) === true);
}
private function comesFromOwnDomain(Request $request, ShopSwitchRuleEntity $activeRule): bool
{
if ($request->attributes->has(ShopSwitchController::SHOP_SWITCH_AJAX)) {
return false;
}
if (empty($referer = $request->server->get('HTTP_REFERER')) === false) {
$referer = rtrim($referer, '/');
$currentDomainId = $request->attributes->get(SalesChannelRequest::ATTRIBUTE_DOMAIN_ID);
foreach ($activeRule->getDomains() as $domainEntity) {
if ($domainEntity->getDomainId() === $currentDomainId || empty($domainEntity->getDomain()) || empty($domainEntity->getDomain()->getUrl())) {
continue;
}
if (strpos($referer, $domainEntity->getDomain()->getUrl()) === 0
|| strpos($domainEntity->getDomain()->getUrl(), $referer) === 0) {
return true;
}
}
}
return false;
}
public function getCountryId(PossibleCombinationCollection $possibleCombinationCollection, PossibleCountryCollection $possibleCountryCollection, string $redirectDomainUrl, ?string $defaultCountryId, Context $context): string
{
$countryId = $this->getCountryIdFromPossibleCombinations($possibleCombinationCollection, $possibleCountryCollection);
// try to get first active county from
if (!$countryId) {
foreach ($possibleCountryCollection->getElements() as $possibleCountryStruct) {
if ($possibleCountryStruct->isActive() === true) {
if ($this->salesChannelCountryLanguageService->isCountryInSalesChannelDomain($possibleCountryStruct->getCountryId(), $redirectDomainUrl, $context) === true) {
return $possibleCountryStruct->getCountryId();
}
}
}
$countryId = $defaultCountryId;
}
if (!$countryId) {
return "";
}
if ($this->salesChannelCountryLanguageService->isCountryInSalesChannelDomain($countryId, $redirectDomainUrl, $context) === true) {
return $countryId;
}
return "";
}
private function getCountryIdFromPossibleCombinations(PossibleCombinationCollection $possibleCombinationCollection, PossibleCountryCollection $possibleCountryCollection)
{
// if more countries match on more domains -> prefer the active one
foreach ($possibleCountryCollection->getElements() as $possibleCountryStruct) {
if ($possibleCountryStruct->isActive() === true) {
foreach ($possibleCombinationCollection->getElements() as $possibleCombinationStruct) {
if ($possibleCombinationStruct->getDomainUrl() === $possibleCountryStruct->getDomainUrl()) {
return $possibleCountryStruct->getCountryId();
}
}
}
}
foreach ($possibleCountryCollection->getElements() as $possibleCountryStruct) {
foreach ($possibleCombinationCollection->getElements() as $possibleCombinationStruct) {
if ($possibleCombinationStruct->getDomainUrl() === $possibleCountryStruct->getDomainUrl()) {
return $possibleCountryStruct->getCountryId();
}
}
}
return "";
}
public function sortPossibleCombinationsByActiveDomain(PossibleCombinationCollection $possibleCombinationCollection, string $activeDomainUrl): void
{
$possibleCombinationCollection->sort(function (PossibleCombinationStruct $possibleCombinationStructA, PossibleCombinationStruct $possibleCombinationStructB) use ($activeDomainUrl) {
if ($possibleCombinationStructA->getDomainUrl() === $activeDomainUrl) {
return -1;
} elseif ($possibleCombinationStructB->getDomainUrl() === $activeDomainUrl) {
return 1;
} else {
return 0;
}
});
}
private function isExternalRedirect(?string $redirectDomainUrl, ShopSwitchRuleEntity $activeRule): bool
{
if (empty($redirectDomainUrl)) {
return false;
}
foreach ($activeRule->getDomains() as $domain) {
if ($domain->isActive() === true && $domain->getRedirectTo() === 'external' && $redirectDomainUrl === $domain->getExternalUrl()) {
return true;
}
}
return false;
}
private function getPossibleBlacklistCombination(ShopSwitchRuleEntity $activeRule, PossibleCountryCollection $possibleCountryCollection): PossibleCombinationCollection
{
$possibleCombinationCollection = new PossibleCombinationCollection();
foreach ($activeRule->getDomains() as $ruleDomainEntity) {
if(empty($ruleDomainEntity->getFoundUrl()) === true) continue;
if($ruleDomainEntity->getCountryBlacklist() instanceof CountryCollection && $ruleDomainEntity->getCountryBlacklist()->count() > 0) {
if(empty($ruleDomainEntity->getDefaultShippingCountryId()) === false && $possibleCountryCollection->has($ruleDomainEntity->getDefaultShippingCountryId()) === true) {
$possibleCountryStruct = $possibleCountryCollection->get($ruleDomainEntity->getDefaultShippingCountryId());
$possibleCountryStruct->setActive(true);
$possibleCombinationCollection->add(new PossibleCombinationStruct($ruleDomainEntity->getFoundUrl(), $possibleCountryStruct));
} else {
$possibleCombinationCollection->add(new PossibleCombinationStruct($ruleDomainEntity->getFoundUrl()));
}
}
}
return $possibleCombinationCollection;
}
}