Files
kupshop/bundles/External/ZNZBundle/Synchronizers/BlockDescriptionSynchronizer.php
2025-08-02 16:30:27 +02:00

272 lines
9.3 KiB
PHP

<?php
declare(strict_types=1);
namespace External\ZNZBundle\Synchronizers;
use External\ZNZBundle\Exception\ZNZException;
use External\ZNZBundle\Resources\script\FixTranslatedBlocksScript;
use External\ZNZBundle\Util\ZNZApi;
use External\ZNZBundle\Util\ZNZTranslationsUtil;
use KupShop\ContentBundle\Util\Block;
use KupShop\KupShopBundle\Context\ContextManager;
use KupShop\KupShopBundle\Util\Database\QueryHint;
use Query\Operator;
use Symfony\Contracts\Service\Attribute\Required;
class BlockDescriptionSynchronizer extends BaseSynchronizer
{
protected static string $type = 'blockDescription';
#[Required]
public ZNZApi $znzApi;
#[Required]
public Block $block;
#[Required]
public ZNZTranslationsUtil $translationsUtil;
#[Required]
public ContextManager $contextManager;
public static function getHandledTables(): array
{
return [
'ProduktPopis' => 'processProductDescription',
// 'ZnackaPopis' => 'processProducerDescription',
'RadaPopis' => 'processSeriesDescription',
];
}
/**
* Aktualizace popisu+ směrem z Heliosu do e-shopu. Pokud jiný e-shop poslal změnu do Heliosu, tak byla vygenerována
* změna, kterou je potřeba na ostatních shopech zpracovat a tím zaktualizovat popis+.
*/
public function processProductDescription(array $item): void
{
[$productId, $_] = $this->znzUtil->getProductMapping($item['IDProdukt']);
if (!$productId) {
return;
}
$this->updateBlocks(
'products',
$productId,
$item
);
}
/**
* Aktualizace popisu výrobce. Pokud jiný e-shop poslal svůj popis do Heliosu, tak
* byla vygenerována změna, díky které se ten popis propíše na ostatní shopy. Tahle fce zařídí, že
* se ta změna propíše.
*/
public function processProducerDescription(array $item): void
{
$parameterListId = $this->znzUtil->getParameterValueMapping(
$this->znzUtil->getParameterByKey('ZnackaOption'),
$item['IDZnacka']
);
if (!$parameterListId) {
return;
}
// najdu vyrobce
$producerId = sqlQueryBuilder()
->select('pr.id')
->from('producers', 'pr')
->join('pr', 'parameters_list', 'pl', 'pl.id = :parameterListId')
->where('pr.name = pl.value')
->setParameter('parameterListId', $parameterListId)
->execute()->fetchOne();
if (!$producerId) {
return;
}
$this->updateBlocks(
'producers',
$producerId,
$item
);
}
/**
* Aktualizace obsahu šablony, Šablony se používají pro popisek Řady. Pokud jiný e-shop poslal svůj popis
* do Heliosu, tak byla vygenerována změna, díky které se ten popis propíše na ostatní shopy. Tahle fce
* zařídí, že se ta změna propíše.
*/
public function processSeriesDescription(array $item): void
{
if (empty($item['IDRada'])) {
return;
}
$templateId = $this->znzUtil->getSeriesTemplateId($item['IDRada']);
$this->updateBlocks(
'templates',
$templateId,
$item
);
}
public function updateBlocks(string $tableName, int $objectId, array $item): void
{
$data = $this->prepareData($item);
if ($data === null) {
return;
}
$blocks = $this->prepareBlocks($data);
$rootBlockId = QueryHint::withRouteToMaster(fn () => $this->block->getRootBlockId($tableName, $objectId));
if ($item['meta']['translation'] ?? false) {
[$localBlocksByPositions, $localBlocksContent] = sqlGetConnection()->transactional(function () use ($rootBlockId) {
$localBlocks = sqlQueryBuilder()
->select('id, position, json_content')
->from('blocks')
->where(Operator::equals(['id_root' => $rootBlockId]))
->execute()->fetchAllAssociative();
$blocksByPositions = [];
$blocksContent = [];
foreach ($localBlocks as $localBlock) {
$blocksByPositions[$localBlock['position']] = $localBlock['id'];
$blocksContent[$localBlock['id']] = $localBlock['json_content'];
}
if (count($blocksByPositions) === 1) {
$blocksByPositions = array_values($blocksByPositions);
}
return [$blocksByPositions, $blocksContent];
});
$this->translateBlocks($item['meta']['language'], $blocks, $localBlocksByPositions, $localBlocksContent);
$this->translationsUtil->updateBlockTranslations([$rootBlockId]);
return;
}
$this->block->updateBlocks($tableName, $objectId, $blocks);
$this->translationsUtil->updateBlockTranslations([$rootBlockId]);
}
private function prepareBlocks(array $data): array
{
// pripravim si fotky, ktere jsou pouzity v bloku
$photosMapping = [];
foreach ($data['photos'] ?? [] as $remoteId => [$znzPhotoId, $photoUrl]) {
if (!($photoId = $this->znzUtil->getMapping('photo', $znzPhotoId))) {
$photoId = $this->znzUtil->getDownloader()->importProductImage($photoUrl, true);
}
$photosMapping[$remoteId] = $photoId;
}
// rekurzivne produjdu bloky
return $this->recursivelyBlocksWalk($data['blocks'] ?? [], function ($block) use ($photosMapping) {
// rekurzivne nahradim ID fotek v json contentu
$data = $this->recursivelyReplacePhotoIdsInBlockContent(
json_decode($block['json_content'] ?? '', true) ?: [],
$photosMapping
);
$block['json_content'] = json_encode($data);
// smazu HTML obsah, aby se pri aktualizaci znovu vyrenderoval
unset($block['content']);
return $block;
});
}
/** Vraci rozparsovana data blocku. Pokud se vrati null, tak nechceme provadet update. */
private function prepareData(array $item): ?array
{
$data = json_decode($item['PopisRich'] ?: '', true) ?: [];
// pokud je aktualni website autorem zmeny, tak ji preskakuju, protoze je zbytecny synchronizovat neco, co uz mam
if (($data['from'] ?? null) === $this->configuration->getMainWebsite()) {
return null;
}
return $data;
}
private function translateBlocks(string $language, array $blocks, array $localBlocksByPositions, array $localBlocksContent): void
{
foreach ($blocks as $key => $block) {
if (!($blockId = ($localBlocksByPositions[$block['position']] ?? null))) {
if (!($blockId = ($localBlocksByPositions[$key] ?? null))) {
throw new ZNZException('Unable to update translation of block content: cannot find block to translate!');
}
}
$defaultData = json_decode($localBlocksContent[$blockId] ?? '', true) ?: [];
if (!empty($defaultData)) {
$fixedTranslatedData = FixTranslatedBlocksScript::fixBlockIds(
$defaultData,
json_decode($block['json_content'] ?: '', true) ?: []
);
if ($fixedTranslatedData) {
$block['json_content'] = json_encode($fixedTranslatedData);
}
}
$this->block->translateBlock($language, (int) $blockId, $block);
// Pokud mam sub bloky, tak prelozim i ty
foreach ($block['children'] ?? [] as $childBlock) {
$this->translateBlocks($language, $childBlock, $localBlocksByPositions, $localBlocksContent);
}
}
}
private function getBlockIdByPositions(int $position, array $blocksByPositions): ?int
{
if ($blockId = ($localBlocksByPositions[$position] ?? null)) {
return (int) $blockId;
}
return null;
}
private function recursivelyBlocksWalk(array $blocks, callable $fn): array
{
foreach ($blocks as &$block) {
$block = $fn($block);
if (!empty($block['children'])) {
$block['children'] = $this->recursivelyBlocksWalk($block['children'], $fn);
}
}
return $blocks;
}
private function recursivelyReplacePhotoIdsInBlockContent(array $data, array $photosMapping): array
{
foreach ($data as &$item) {
if (!empty($item['settings']['photo']['id'])) {
$item['settings']['photo']['id'] = $photosMapping[$item['settings']['photo']['id']] ?? $item['settings']['photo']['id'];
}
if (!empty($item['settings']['photos'])) {
foreach ($item['settings']['photos'] as $photoKey => $photoItem) {
$item['settings']['photos'][$photoKey]['photo']['id'] = $photosMapping[$photoItem['photo']['id']] ?? $photoItem['photo']['id'];
}
}
if (!empty($item['children'])) {
$item['children'] = $this->recursivelyReplacePhotoIdsInBlockContent($item['children'], $photosMapping);
}
}
return $data;
}
}