Files
2025-08-02 16:30:27 +02:00

263 lines
9.2 KiB
PHP

<?php
declare(strict_types=1);
namespace KupShop\GraphQLBundle\ApiAdmin\Util;
use KupShop\ContentBundle\Util\Block;
use KupShop\GraphQLBundle\ApiAdmin\Types\Content\Response\EditableContentMutateResponse;
use KupShop\GraphQLBundle\ApiShared\Types\Content\EditableContent;
use KupShop\GraphQLBundle\ApiShared\Types\Content\Input\EditableAreaInput;
use KupShop\GraphQLBundle\ApiShared\Types\Content\Input\EditableContentInput;
use KupShop\GraphQLBundle\ApiShared\Types\Content\Input\EditableContentTranslationInput;
use KupShop\GraphQLBundle\Exception\GraphQLValidationException;
use KupShop\I18nBundle\Translations\BlocksTranslation;
use KupShop\KupShopBundle\Util\ArrayUtil;
use KupShop\KupShopBundle\Util\Database\QueryHint;
use KupShop\KupShopBundle\Util\Functional\Mapping;
use KupShop\KupShopBundle\Util\ObjectUtil;
use Query\Operator;
use TheCodingMachine\GraphQLite\Exceptions\GraphQLException;
class BlockUpdater
{
public function __construct(
private Block $block,
private ?BlocksTranslation $blocksTranslation,
) {
}
public function updateEditableContent(EditableContentInput $input): EditableContentMutateResponse
{
$this->validateEditableContentUpdate($input);
$deleteAreas = false;
if (ObjectUtil::isPropertyInitialized($input, 'overwrite')) {
$deleteAreas = $input->overwrite;
}
if ($deleteAreas) {
sqlQueryBuilder()
->delete('blocks')
->where(Operator::equals(['id_root' => $input->id]))
->execute();
}
$position = 0;
if (!$input->overwrite) {
$position = sqlQueryBuilder()
->select('MAX(position)')
->from('blocks')
->where(Operator::equals(['id_root' => $input->id]))
->execute()->fetchOne() ?: 0;
}
foreach ($input->areas as $area) {
$blockId = $deleteAreas ? null : $area->id;
$update = $this->getEditableAreaUpdateData($area);
if ($blockId === null) {
// create new block
$insert = [
'id_root' => $input->id,
'id_parent' => $input->id,
'position' => ++$position,
'name' => $update['name'] ?? null,
'content' => '',
'json_content' => '[]',
];
if ($area->id) {
$insert['id'] = $area->id;
}
$blockId = sqlGetConnection()->transactional(function () use ($insert) {
sqlQueryBuilder()
->insert('blocks')
->directValues($insert)
->execute();
return (int) sqlInsertId();
});
}
if (empty($update)) {
continue;
}
if (array_key_exists('json_content', $update)) {
$update['json_content'] = json_encode($update['json_content']) ?: '[]';
$rendered = $this->block->renderBlock($update['json_content'], "Block with ID {$blockId}");
if (($rendered['success'] ?? false) !== true) {
throw new GraphQLException('Error during EditableArea save! Render of EditableArea.data failed: '.$rendered['error']);
}
$update['content'] = $rendered['html'] ?? '';
$this->block->updateBlockPhotosRelationsByData($blockId, json_decode($update['json_content'], true) ?: []);
}
sqlQueryBuilder()
->update('blocks')
->directValues($update)
->where(Operator::equals(['id' => $blockId]))
->execute();
}
$blocks = QueryHint::withRouteToMaster(fn () => $this->block->getBlocks($input->id));
return new EditableContentMutateResponse(
true,
new EditableContent(
$input->id,
$blocks
)
);
}
public function updateEditableContentTranslation(EditableContentTranslationInput $input): bool
{
$this->validateEditableContentUpdate($input);
foreach ($input->areas as $area) {
$this->translateEditableArea($input->language, $area);
}
return true;
}
public function translateBlocks(string $language, array $blocks, array $localBlocksByPositions): array
{
$blocksMap = [];
foreach ($blocks as $block) {
$blockId = $localBlocksByPositions[$block['position']] ?? null;
if (!$blockId) {
throw new GraphQLValidationException('Unable to update translation of description plus: cannot find block to translate!');
}
$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);
}
}
return $blocksMap;
}
public function captureErrors(callable $fn): void
{
try {
$fn();
} catch (\InvalidArgumentException $e) {
throw new GraphQLValidationException(
sprintf('Unable to update editable area: %s', $e->getMessage())
);
} catch (\RuntimeException $e) {
throw new GraphQLException(
sprintf('Unable to update editable area, try again later: %s', $e->getMessage()),
500
);
}
}
private function translateEditableArea(string $language, EditableAreaInput $area): bool
{
if (!$this->blocksTranslation) {
return false;
}
if (!ObjectUtil::isPropertyInitialized($area, 'id') || !$area->id) {
throw new GraphQLValidationException(
sprintf('EditableArea.id is mandatory when translating areas!')
);
}
$update = $this->getEditableAreaUpdateData($area);
if (empty($update)) {
return false;
}
try {
return $this->blocksTranslation->saveSingleObjectForce(
$language,
$area->id,
$update,
failureAsException: true
);
} catch (\RuntimeException $e) {
throw new GraphQLException('Error during EditableArea save! Render of EditableArea.data failed: '.$e->getMessage());
}
}
private function getEditableAreaUpdateData(EditableAreaInput $area): array
{
$update = [];
if (ObjectUtil::isPropertyInitialized($area, 'name')) {
$update['name'] = $area->name;
}
if (ObjectUtil::isPropertyInitialized($area, 'dataPortable')) {
$area->data = $this->block->createBlockDataFromPortableData($area->dataPortable);
}
if (ObjectUtil::isPropertyInitialized($area, 'data')) {
$update['json_content'] = json_decode($area->data ?: '', true) ?: [];
}
return $update;
}
private function validateEditableContentUpdate(EditableContentInput $input): void
{
$blocks = Mapping::mapKeys(sqlQueryBuilder()
->select('id')
->from('blocks')
->where(Operator::equals(['id_root' => $input->id]))
->execute(), fn ($k, $v) => [$v['id'], true]);
foreach ($input->areas as $area) {
if (!ObjectUtil::isPropertyInitialized($area, 'id')) {
throw new GraphQLValidationException(
'EditableArea.id field is mandatory!'
);
}
// zvalidovat, ze bloky patri k danemu objektu. Pokud ne, tak hodit chybu.
if (ObjectUtil::isPropertyInitialized($area, 'id') && $area->id !== null) {
if (!($blocks[$area->id] ?? false)) {
throw new GraphQLValidationException(
'Unable to update EditableArea! Specified EditableArea.id does not belong to updated EditableContent!'
);
}
}
// zvalidovat, ze je v datech vyplneny validni json
if (ObjectUtil::isPropertyInitialized($area, 'data') || ObjectUtil::isPropertyInitialized($area, 'dataPortable')) {
if (ObjectUtil::isPropertyInitialized($area, 'dataPortable')) {
$plainDataField = 'dataPortable';
$plainData = $area->dataPortable;
} else {
$plainDataField = 'data';
$plainData = $area->data;
}
try {
$data = json_decode_strict($plainData, true);
} catch (\JsonException $e) {
throw new GraphQLValidationException("Failed to parse JSON in EditableArea.{$plainDataField}!");
}
if (!ArrayUtil::isVector($data)) {
throw new GraphQLValidationException("EditableArea.{$plainDataField} structure is not valid! The structure must be an array of blocks, so it must be inside the array [{...}, {...}]");
}
}
}
}
}