Files
esun3d/app/code/Esun/Translation/Model/Entity/CmsPageEntity.php
T
2026-09-15 10:35:56 +08:00

217 lines
6.6 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\PageRepositoryInterface;
use Magento\Cms\Model\Page;
use Magento\Cms\Model\PageFactory;
use Magento\Cms\Model\ResourceModel\Page\CollectionFactory;
class CmsPageEntity implements TranslatableEntityInterface
{
private const FIELDS = [
'title' => ['label' => 'Page Title', 'is_html' => false],
'content_heading' => ['label' => 'Content Heading', 'is_html' => false],
'content' => ['label' => 'Content', 'is_html' => true],
'meta_title' => ['label' => 'Meta Title', 'is_html' => false],
'meta_keywords' => ['label' => 'Meta Keywords', 'is_html' => false],
'meta_description' => ['label' => 'Meta Description', 'is_html' => false],
];
public function __construct(
private readonly PageRepositoryInterface $pageRepository,
private readonly PageFactory $pageFactory,
private readonly CollectionFactory $collectionFactory,
private readonly HtmlTranslationValidator $htmlValidator
) {
}
public function getType(): string
{
return 'cms_page';
}
public function getLabel(): string
{
return 'CMS Page';
}
public function getTitle(int $entityId): string
{
return (string)$this->getPage($entityId)->getTitle();
}
public function getEditUrlPath(): string
{
return 'cms/page/edit';
}
public function getEditIdParam(): string
{
return 'page_id';
}
public function getFields(int $entityId, array $targetStoreIds = []): array
{
$source = $this->getPage($entityId);
$target = $this->findTargetPage($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->getPage($entityId);
$fields = $this->filterAndValidateFields($fields);
if ($fields === []) {
throw new \RuntimeException('No translatable CMS page fields were provided.');
}
$updated = 0;
$created = 0;
foreach (array_unique(array_map('intval', $storeIds)) as $storeId) {
if ($storeId <= 0) {
continue;
}
$page = $this->findByIdentifierAndStore((string)$source->getIdentifier(), $storeId);
if ($page) {
$this->applyFields($page, $fields);
$page->setStores([$storeId]);
$this->pageRepository->save($page);
$updated++;
continue;
}
if (in_array(0, array_map('intval', (array)$source->getStores()), true)) {
throw new \RuntimeException(
'This CMS page is assigned to All Store Views. Create store-specific pages first or remove All Store Views before creating localized copies.'
);
}
$copy = $this->cloneForStore($source, $storeId);
$this->applyFields($copy, $fields);
$this->pageRepository->save($copy);
$created++;
}
if ($updated + $created === 0) {
throw new \RuntimeException('No valid target store views were provided.');
}
return [
'updated' => $updated,
'created' => $created,
'message' => __('CMS page translations applied. Updated: %1, Created: %2.', $updated, $created)->render(),
];
}
private function getPage(int $pageId): Page
{
$page = $this->pageRepository->getById($pageId);
if (!$page instanceof Page) {
throw new \RuntimeException('CMS page model type is not supported.');
}
return $page;
}
/**
* @param int[] $targetStoreIds
*/
private function findTargetPage(Page $source, array $targetStoreIds): ?Page
{
foreach ($targetStoreIds as $storeId) {
$page = $this->findByIdentifierAndStore((string)$source->getIdentifier(), (int)$storeId);
if ($page && (int)$page->getId() !== (int)$source->getId()) {
return $page;
}
}
return null;
}
private function findByIdentifierAndStore(string $identifier, int $storeId): ?Page
{
if ($storeId <= 0) {
return null;
}
$collection = $this->collectionFactory->create();
$collection->addFieldToFilter('identifier', $identifier);
$collection->addStoreFilter($storeId, false);
$collection->setPageSize(1);
$page = $collection->getFirstItem();
return $page && $page->getId() ? $page : 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(Page $page, array $fields): void
{
foreach ($fields as $code => $value) {
$page->setData($code, $value);
}
}
private function cloneForStore(Page $source, int $storeId): Page
{
$data = $source->getData();
unset($data['page_id'], $data['store_id']);
$copy = $this->pageFactory->create();
$copy->setData($data);
$copy->setStores([$storeId]);
$copy->setData('store_id', [$storeId]);
return $copy;
}
}