['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 $fields * @return array */ 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 $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; } }