<?php declare(strict_types=1);
namespace NetInventors\NetiNextStoreLocator\Decorator;
use NetInventors\NetiNextStoreLocator\Subscriber\ImportExportSubscriber;
use Shopware\Core\Framework\Context;
use Shopware\Core\Framework\DataAbstractionLayer\EntityDefinition;
use Shopware\Core\Framework\DataAbstractionLayer\EntityRepositoryInterface;
use Shopware\Core\Framework\DataAbstractionLayer\Event\EntityWrittenContainerEvent;
use Shopware\Core\Framework\DataAbstractionLayer\Search\AggregationResult\AggregationResultCollection;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
use Shopware\Core\Framework\DataAbstractionLayer\Search\EntitySearchResult;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\EqualsFilter;
use Shopware\Core\Framework\DataAbstractionLayer\Search\IdSearchResult;
use Shopware\Core\Framework\DataAbstractionLayer\Write\CloneBehavior;
use Shopware\Core\Framework\Uuid\Uuid;
class EntityRepositoryDecorator implements EntityRepositoryInterface
{
private const COUNTRY_ISOS = [
'2' => 'DE',
'3' => 'AE',
'4' => 'AU',
'5' => 'BE',
'7' => 'DK',
'8' => 'FI',
'9' => 'FR',
'10' => 'GR',
'11' => 'GB',
'12' => 'IE',
'13' => 'IS',
'14' => 'IT',
'15' => 'JP',
'16' => 'CA',
'18' => 'LU',
'20' => 'NA',
'21' => 'NL',
'22' => 'NO',
'23' => 'AT',
'24' => 'PT',
'25' => 'SE',
'26' => 'CH',
'27' => 'ES',
'28' => 'US',
'29' => 'LI',
'30' => 'PL',
'31' => 'HU',
'32' => 'TR',
'33' => 'CZ',
'34' => 'SK',
'35' => 'RO',
'36' => 'BR',
'37' => 'IL',
];
/**
* @var EntityRepositoryInterface
*/
private $repository;
/**
* @var EntityRepositoryInterface
*/
private $salesChannelRepository;
/**
* @var EntityRepositoryInterface
*/
private $countryRepository;
/**
* @var EntityRepositoryInterface
*/
private $countryStateRepository;
/** @var array<string, string> */
private array $cachedCountryIds = [];
/** @var array<string, string> */
private array $cachedIsoCountryIds = [];
/** @var array<string, string> */
private array $cachedSalesChannelIds = [];
/** @var string[] */
private array $defaultSalesChannelIds = [];
/**
* EntityRepositoryDecorator constructor.
*
* @param EntityRepositoryInterface $repository
* @param EntityRepositoryInterface $salesChannelRepository
* @param EntityRepositoryInterface $countryRepository
* @param EntityRepositoryInterface $countryStateRepository
*/
public function __construct(
EntityRepositoryInterface $repository,
EntityRepositoryInterface $salesChannelRepository,
EntityRepositoryInterface $countryRepository,
EntityRepositoryInterface $countryStateRepository
) {
$this->repository = $repository;
$this->salesChannelRepository = $salesChannelRepository;
$this->countryRepository = $countryRepository;
$this->countryStateRepository = $countryStateRepository;
}
public function getDefinition(): EntityDefinition
{
return $this->repository->getDefinition();
}
public function aggregate(Criteria $criteria, Context $context): AggregationResultCollection
{
return $this->repository->aggregate($criteria, $context);
}
public function searchIds(Criteria $criteria, Context $context): IdSearchResult
{
return $this->repository->searchIds($criteria, $context);
}
public function clone(string $id, Context $context, ?string $newId = null, ?CloneBehavior $behavior = null): EntityWrittenContainerEvent
{
return $this->repository->clone($id, $context, $newId, $behavior);
}
public function search(Criteria $criteria, Context $context): EntitySearchResult
{
return $this->repository->search($criteria, $context);
}
public function update(array $data, Context $context): EntityWrittenContainerEvent
{
return $this->repository->update($data, $context);
}
public function upsert(array $data, Context $context): EntityWrittenContainerEvent
{
if ($context->hasState(ImportExportSubscriber::STATE_IMPORT)) {
$data = $this->prepareDataForImport($data, $context);
}
return $this->repository->upsert($data, $context);
}
public function create(array $data, Context $context): EntityWrittenContainerEvent
{
return $this->repository->create($data, $context);
}
public function delete(array $data, Context $context): EntityWrittenContainerEvent
{
return $this->repository->delete($data, $context);
}
public function createVersion(string $id, Context $context, ?string $name = null, ?string $versionId = null): string
{
return $this->repository->createVersion($id, $context, $name, $versionId);
}
public function merge(string $versionId, Context $context): void
{
$this->repository->merge($versionId, $context);
}
private function prepareDataForImport(array $data, Context $context): array
{
$data[0]['countryId'] = $this->getCountryId($data[0]['countryId'], $context);
$data[0]['id'] = $this->createStoreId($data[0], $context);
/**
* Due to the condition above data must be an array
*
* @psalm-suppress MixedArgument
*/
$data[0]['showAlways'] = $this->setShowAlways($data[0]);
if (isset($data[0]['salesChannels'])) {
/**
* @var array $salesChannels
* @psalm-suppress MixedArrayAccess - It has to be available because it is checked by isset
*/
$salesChannels = $data[0]['salesChannels'];
/** @psalm-suppress MixedArrayAssignment */
$data[0]['salesChannels'] = $this->assignSalesChannel($salesChannels, $context);
} else {
$salesChannelIds = $this->getSalesChannelsIds($context);
unset($data[0]['salesChannels']);
foreach ($salesChannelIds as $channelId) {
$data[0]['salesChannels'][] = [ 'id' => $channelId ];
}
}
if (isset($data[0]['countryId'])) {
$data[0]['countryId'] = $this->assignCountryIdByIso($data[0], $context);
if (isset($data[0]['country'])) {
unset($data[0]['country']);
}
if (isset($data[0]['countryState'])) {
$data[0]['countryStateId'] = $this->assignCountryStateByShortCode($data[0], $context);
unset($data[0]['countryState']);
}
}
return $data;
}
private function getSalesChannelsIds(Context $context): array
{
if (empty($this->defaultSalesChannelIds)) {
/** @var string[] $ids */
$ids = $this->salesChannelRepository->searchIds(new Criteria(), $context)->getIds();
$this->defaultSalesChannelIds = $ids;
}
return $this->defaultSalesChannelIds;
}
/**
* @param string $id
* @param Context $context
*
* @return string
*/
private function getCountryId(string $id, Context $context): ?string
{
if ($this->isMatchingCountryId($id, $context)) {
return $id;
}
return $this->getCountryByIso(self::COUNTRY_ISOS[$id] ?? 'DE', $context);
}
/**
* @param array $data
* @param Context $context
*
* @return string
*/
private function createStoreId(array $data, Context $context): string
{
unset($data['salesChannels'], $data['id']);
$criteria = new Criteria();
foreach ($data as $key => $value) {
if ($key !== 'country' && !empty($value) && !is_array($value)) {
$criteria->addFilter(new EqualsFilter($key, $value));
}
}
$result = $this->repository->searchIds($criteria, $context)->firstId();
if (null === $result) {
$result = Uuid::randomHex();
}
return $result;
}
/**
* @param array $data
*
* @return string
*/
private function setShowAlways(array $data): string
{
$newDisplay = 'no';
if (!isset($data['showAlways'])) {
return $newDisplay;
}
$showAlways = (string) $data['showAlways'];
switch ($showAlways) {
case '0':
break;
case '1':
$newDisplay = 'top';
break;
case '2':
$newDisplay = 'bottom';
break;
}
return $newDisplay;
}
/**
* @param string $salesChannelId
* @param Context $context
*
* @return bool
*/
private function isMatchingSalesChannel(string $salesChannelId, Context $context): bool
{
if (isset($this->cachedSalesChannelIds[$salesChannelId])) {
return true;
}
$criteria = new Criteria([ $salesChannelId ]);
$result = $this->salesChannelRepository->searchIds($criteria, $context)->firstId();
if (is_string($result)) {
$this->cachedSalesChannelIds[$salesChannelId] = $result;
}
return isset($this->cachedSalesChannelIds[$salesChannelId]);
}
/**
* @param array $salesChannels
* @param Context $context
*
* @return array
*/
private function assignSalesChannel(array $salesChannels, Context $context): array
{
foreach ($salesChannels as $key => $salesChannel) {
if (!$this->isMatchingSalesChannel($salesChannel['id'], $context)) {
unset($salesChannels[$key]);
}
}
if (empty($salesChannels)) {
$defaultSalesChannelIds = $this->getSalesChannelsIds($context);
foreach ($defaultSalesChannelIds as $defaultSalesChannelId) {
$salesChannels[] = [ 'id' => $defaultSalesChannelId ];
}
}
return $salesChannels;
}
/**
* @param array $data
* @param Context $context
*
* @return string
*/
private function assignCountryIdByIso(array $data, Context $context): ?string
{
if (isset($data['countryId'], $data['country']['iso']) && !$this->isMatchingCountryId($data['countryId'], $context)) {
$data['countryId'] = $this->getCountryByIso($data['country']['iso'], $context);
}
if (!isset($data['countryId'])) {
$data['countryId'] = $this->getCountryId('deutsch', $context);
}
return $data['countryId'];
}
/**
* @param array $data
* @param Context $context
*
* @return string
*/
private function assignCountryStateByShortCode(array $data, Context $context): string
{
if (isset($data['countryStateId'], $data['countryState']['shortCode']) && !$this->isMatchingCountryStateId($data['countryStateId'], $context)) {
$data['countryStateId'] = $this->getCountryStateByShortCode($data['countryState']['shortCode'], $context);
}
return $data['countryStateId'];
}
/**
* @param string $countryId
* @param Context $context
*
* @return bool
*/
private function isMatchingCountryId(string $countryId, Context $context): bool
{
if (isset($this->cachedCountryIds[$countryId])) {
return true;
}
$criteria = new Criteria([ $countryId ]);
$result = $this->countryRepository->searchIds($criteria, $context)->firstId();
if (is_string($result)) {
$this->cachedCountryIds[$result] = $result;
}
return isset($this->cachedCountryIds[$countryId]);
}
/**
* @param string $iso
* @param Context $context
*
* @return string|null
*/
private function getCountryByIso(string $iso, Context $context): ?string
{
if (isset($this->cachedIsoCountryIds[$iso])) {
return $this->cachedIsoCountryIds[$iso];
}
$criteria = new Criteria();
$criteria->addFilter(new EqualsFilter('iso', $iso));
$countryId = $this->countryRepository->searchIds($criteria, $context)->firstId();
if (is_string($countryId)) {
$this->cachedIsoCountryIds[$iso] = $countryId;
}
return $countryId;
}
/**
* @param string $countryStateId
* @param Context $context
*
* @return bool
*/
private function isMatchingCountryStateId(string $countryStateId, Context $context): bool
{
$criteria = new Criteria([ $countryStateId]);
$result = $this->countryStateRepository->searchIds($criteria, $context)->getIds();
return !empty($result);
}
/**
* @param string $shortCode
* @param Context $context
*
* @return string|null
*/
private function getCountryStateByShortCode(string $shortCode, Context $context): ?string
{
$criteria = new Criteria();
$criteria->addFilter(new EqualsFilter('shortCode', $shortCode));
return $this->countryStateRepository->searchIds($criteria, $context)->firstId();
}
}