213 lines
6.3 KiB
PHP
213 lines
6.3 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace Esun\Translation\Model\Entity;
|
|
|
|
use Esun\Translation\Api\TranslatableEntityInterface;
|
|
use Esun\Translation\Model\Html\HtmlTranslationValidator;
|
|
use Magento\Cms\Api\BlockRepositoryInterface;
|
|
use Magento\Cms\Model\Block;
|
|
use Magento\Cms\Model\BlockFactory;
|
|
use Magento\Cms\Model\ResourceModel\Block\CollectionFactory;
|
|
|
|
class CmsBlockEntity implements TranslatableEntityInterface
|
|
{
|
|
private const FIELDS = [
|
|
'title' => ['label' => 'Block Title', 'is_html' => false],
|
|
'content' => ['label' => 'Content', 'is_html' => true],
|
|
];
|
|
|
|
public function __construct(
|
|
private readonly BlockRepositoryInterface $blockRepository,
|
|
private readonly BlockFactory $blockFactory,
|
|
private readonly CollectionFactory $collectionFactory,
|
|
private readonly HtmlTranslationValidator $htmlValidator
|
|
) {
|
|
}
|
|
|
|
public function getType(): string
|
|
{
|
|
return 'cms_block';
|
|
}
|
|
|
|
public function getLabel(): string
|
|
{
|
|
return 'CMS Block';
|
|
}
|
|
|
|
public function getTitle(int $entityId): string
|
|
{
|
|
return (string)$this->getBlock($entityId)->getTitle();
|
|
}
|
|
|
|
public function getEditUrlPath(): string
|
|
{
|
|
return 'cms/block/edit';
|
|
}
|
|
|
|
public function getEditIdParam(): string
|
|
{
|
|
return 'block_id';
|
|
}
|
|
|
|
public function getFields(int $entityId, array $targetStoreIds = []): array
|
|
{
|
|
$source = $this->getBlock($entityId);
|
|
$target = $this->findTargetBlock($source, $targetStoreIds);
|
|
$fields = [];
|
|
|
|
foreach (self::FIELDS as $code => $metadata) {
|
|
$fields[] = [
|
|
'code' => $code,
|
|
'label' => $metadata['label'],
|
|
'source_value' => (string)($source->getData($code) ?? ''),
|
|
'target_value' => $target ? (string)($target->getData($code) ?? '') : '',
|
|
'is_html' => (bool)$metadata['is_html'],
|
|
];
|
|
}
|
|
|
|
return $fields;
|
|
}
|
|
|
|
public function apply(int $entityId, array $storeIds, array $fields): array
|
|
{
|
|
$source = $this->getBlock($entityId);
|
|
$fields = $this->filterAndValidateFields($fields);
|
|
if ($fields === []) {
|
|
throw new \RuntimeException('No translatable CMS block fields were provided.');
|
|
}
|
|
|
|
$updated = 0;
|
|
$created = 0;
|
|
foreach (array_unique(array_map('intval', $storeIds)) as $storeId) {
|
|
if ($storeId <= 0) {
|
|
continue;
|
|
}
|
|
|
|
$block = $this->findByIdentifierAndStore((string)$source->getIdentifier(), $storeId);
|
|
if ($block) {
|
|
$this->applyFields($block, $fields);
|
|
$block->setStores([$storeId]);
|
|
$this->blockRepository->save($block);
|
|
$updated++;
|
|
continue;
|
|
}
|
|
|
|
if (in_array(0, array_map('intval', (array)$source->getStores()), true)) {
|
|
throw new \RuntimeException(
|
|
'This CMS block is assigned to All Store Views. Create store-specific blocks first or remove All Store Views before creating localized copies.'
|
|
);
|
|
}
|
|
|
|
$copy = $this->cloneForStore($source, $storeId);
|
|
$this->applyFields($copy, $fields);
|
|
$this->blockRepository->save($copy);
|
|
$created++;
|
|
}
|
|
|
|
if ($updated + $created === 0) {
|
|
throw new \RuntimeException('No valid target store views were provided.');
|
|
}
|
|
|
|
return [
|
|
'updated' => $updated,
|
|
'created' => $created,
|
|
'message' => __('CMS block translations applied. Updated: %1, Created: %2.', $updated, $created)->render(),
|
|
];
|
|
}
|
|
|
|
private function getBlock(int $blockId): Block
|
|
{
|
|
$block = $this->blockRepository->getById($blockId);
|
|
if (!$block instanceof Block) {
|
|
throw new \RuntimeException('CMS block model type is not supported.');
|
|
}
|
|
|
|
return $block;
|
|
}
|
|
|
|
/**
|
|
* @param int[] $targetStoreIds
|
|
*/
|
|
private function findTargetBlock(Block $source, array $targetStoreIds): ?Block
|
|
{
|
|
foreach ($targetStoreIds as $storeId) {
|
|
$block = $this->findByIdentifierAndStore((string)$source->getIdentifier(), (int)$storeId);
|
|
if ($block && (int)$block->getId() !== (int)$source->getId()) {
|
|
return $block;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private function findByIdentifierAndStore(string $identifier, int $storeId): ?Block
|
|
{
|
|
if ($storeId <= 0) {
|
|
return null;
|
|
}
|
|
|
|
$collection = $this->collectionFactory->create();
|
|
$collection->addFieldToFilter('identifier', $identifier);
|
|
$collection->addStoreFilter($storeId, false);
|
|
$collection->setPageSize(1);
|
|
$block = $collection->getFirstItem();
|
|
|
|
return $block && $block->getId() ? $block : null;
|
|
}
|
|
|
|
/**
|
|
* @param array<string, mixed> $fields
|
|
* @return array<string, string>
|
|
*/
|
|
private function filterAndValidateFields(array $fields): array
|
|
{
|
|
$result = [];
|
|
foreach ($fields as $code => $value) {
|
|
$code = (string)$code;
|
|
if (!isset(self::FIELDS[$code])) {
|
|
continue;
|
|
}
|
|
|
|
$value = (string)$value;
|
|
if ((bool)self::FIELDS[$code]['is_html']) {
|
|
$errors = $this->htmlValidator->validate($value);
|
|
if ($errors !== []) {
|
|
throw new \RuntimeException(sprintf(
|
|
'HTML for "%s" is invalid: %s',
|
|
$code,
|
|
implode('; ', $errors)
|
|
));
|
|
}
|
|
}
|
|
$result[$code] = $value;
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* @param array<string, string> $fields
|
|
*/
|
|
private function applyFields(Block $block, array $fields): void
|
|
{
|
|
foreach ($fields as $code => $value) {
|
|
$block->setData($code, $value);
|
|
}
|
|
}
|
|
|
|
private function cloneForStore(Block $source, int $storeId): Block
|
|
{
|
|
$data = $source->getData();
|
|
unset($data['block_id'], $data['store_id']);
|
|
|
|
$copy = $this->blockFactory->create();
|
|
$copy->setData($data);
|
|
$copy->setStores([$storeId]);
|
|
$copy->setData('store_id', [$storeId]);
|
|
|
|
return $copy;
|
|
}
|
|
}
|