This commit is contained in:
2026-09-15 10:35:56 +08:00
commit 782f8ea7aa
4086 changed files with 631288 additions and 0 deletions
@@ -0,0 +1,76 @@
<?php
declare(strict_types=1);
namespace Esun\MultiLocaleTranslation\Controller\Adminhtml\Queue;
use Esun\MultiLocaleTranslation\Model\Queue\QueueManager;
use Esun\MultiLocaleTranslation\Model\Queue\SelectionRegistry;
use Magento\Backend\App\Action;
use Magento\Framework\Controller\Result\Redirect;
class Add extends Action
{
public const ADMIN_RESOURCE = 'Esun_MultiLocaleTranslation::queue_add';
public function __construct(
Action\Context $context,
private readonly QueueManager $queueManager,
private readonly SelectionRegistry $selectionRegistry
) {
parent::__construct($context);
}
public function execute(): Redirect
{
$token = (string)$this->getRequest()->getParam('token');
try {
$selection = $this->selectionRegistry->get($token);
$entityType = $selection['entity_type'] ?? (string)$this->getRequest()->getParam('entity_type');
$this->assertBlogPermission((string)$entityType);
$entityIds = $selection['entity_ids'] ?? $this->parseIds(
(string)$this->getRequest()->getParam('entity_ids')
);
$result = $this->queueManager->enqueue(
$entityType,
$entityIds,
(int)$this->getRequest()->getParam('source_store_id'),
(array)$this->getRequest()->getParam('target_store_ids', []),
(string)$this->getRequest()->getParam('provider')
);
$this->selectionRegistry->clear($token);
$this->messageManager->addSuccessMessage(
__('%1 task(s) added to the translation queue. %2 duplicate active task(s) skipped.',
$result['created'],
$result['skipped'])
);
return $this->resultRedirectFactory->create()->setPath('*/*/index', [
'batch_id' => $result['batch_id'],
]);
} catch (\Throwable $exception) {
$this->messageManager->addErrorMessage($exception->getMessage());
return $this->resultRedirectFactory->create()->setPath('*/*/new', ['token' => $token]);
}
}
/** @return int[] */
private function parseIds(string $value): array
{
$parts = preg_split('/[^0-9]+/', $value, -1, PREG_SPLIT_NO_EMPTY);
return array_values(array_unique(array_map('intval', $parts ?: [])));
}
private function assertBlogPermission(string $entityType): void
{
if ($entityType === 'blog_post'
&& !$this->_authorization->isAllowed('Esun_MultiLocaleTranslation::translate_blog')
) {
throw new \InvalidArgumentException((string)__('You are not allowed to translate blog posts.'));
}
if (in_array($entityType, ['wiki_category', 'wiki_homepage'], true)
&& !$this->_authorization->isAllowed('Esun_MultiLocaleTranslation::translate_wiki')
) {
throw new \InvalidArgumentException((string)__('You are not allowed to translate Wiki content.'));
}
}
}
@@ -0,0 +1,41 @@
<?php
declare(strict_types=1);
namespace Esun\MultiLocaleTranslation\Controller\Adminhtml\Queue;
use Esun\MultiLocaleTranslation\Model\Queue\QueueManager;
use Magento\Backend\App\Action;
use Magento\Framework\App\Action\HttpPostActionInterface;
use Magento\Framework\Controller\Result\Redirect;
class ClearAll extends Action implements HttpPostActionInterface
{
public const ADMIN_RESOURCE = 'Esun_MultiLocaleTranslation::queue_delete';
public function __construct(
Action\Context $context,
private readonly QueueManager $queueManager
) {
parent::__construct($context);
}
public function execute(): Redirect
{
try {
$result = $this->queueManager->clearAll();
$this->messageManager->addSuccessMessage(
__('%1 translation queue task(s) cleared.', $result['deleted'])
);
if ($result['processing'] > 0) {
$this->messageManager->addWarningMessage(
__('%1 task(s) were already processing and may still finish.', $result['processing'])
);
}
} catch (\Throwable $exception) {
$this->messageManager->addErrorMessage($exception->getMessage());
}
return $this->resultRedirectFactory->create()->setPath('*/*/index');
}
}
@@ -0,0 +1,27 @@
<?php
declare(strict_types=1);
namespace Esun\MultiLocaleTranslation\Controller\Adminhtml\Queue;
use Magento\Backend\App\Action;
use Magento\Framework\View\Result\Page;
use Magento\Framework\View\Result\PageFactory;
class Index extends Action
{
public const ADMIN_RESOURCE = 'Esun_MultiLocaleTranslation::translation_queue';
public function __construct(Action\Context $context, private readonly PageFactory $pageFactory)
{
parent::__construct($context);
}
public function execute(): Page
{
$page = $this->pageFactory->create();
$page->setActiveMenu('Esun_MultiLocaleTranslation::translation_queue');
$page->getConfig()->getTitle()->prepend(__('Translation Queue'));
return $page;
}
}
@@ -0,0 +1,44 @@
<?php
declare(strict_types=1);
namespace Esun\MultiLocaleTranslation\Controller\Adminhtml\Queue;
use Esun\MultiLocaleTranslation\Model\ResourceModel\Queue\CollectionFactory;
use Magento\Backend\App\Action;
use Magento\Framework\Controller\Result\Redirect;
use Magento\Ui\Component\MassAction\Filter;
class MassDelete extends Action
{
public const ADMIN_RESOURCE = 'Esun_MultiLocaleTranslation::queue_delete';
public function __construct(
Action\Context $context,
private readonly Filter $filter,
private readonly CollectionFactory $collectionFactory
) {
parent::__construct($context);
}
public function execute(): Redirect
{
$collection = $this->filter->getCollection($this->collectionFactory->create());
$deleted = 0;
$skipped = 0;
foreach ($collection as $task) {
if ($task->getData('status') === 'processing') {
$skipped++;
continue;
}
$task->delete();
$deleted++;
}
$this->messageManager->addSuccessMessage(__('%1 queue task(s) deleted.', $deleted));
if ($skipped > 0) {
$this->messageManager->addWarningMessage(__('%1 processing task(s) were not deleted.', $skipped));
}
return $this->resultRedirectFactory->create()->setPath('*/*/');
}
}
@@ -0,0 +1,43 @@
<?php
declare(strict_types=1);
namespace Esun\MultiLocaleTranslation\Controller\Adminhtml\Queue;
use Esun\MultiLocaleTranslation\Model\ResourceModel\Queue\CollectionFactory;
use Magento\Backend\App\Action;
use Magento\Framework\Controller\Result\Redirect;
use Magento\Ui\Component\MassAction\Filter;
class MassRetry extends Action
{
public const ADMIN_RESOURCE = 'Esun_MultiLocaleTranslation::queue_retry';
public function __construct(
Action\Context $context,
private readonly Filter $filter,
private readonly CollectionFactory $collectionFactory
) {
parent::__construct($context);
}
public function execute(): Redirect
{
$collection = $this->filter->getCollection($this->collectionFactory->create());
$retried = 0;
foreach ($collection as $task) {
if ($task->getData('status') !== 'failed') {
continue;
}
$task->setData('status', 'pending');
$task->setData('error_message', null);
$task->setData('started_at', null);
$task->setData('finished_at', null);
$task->save();
$retried++;
}
$this->messageManager->addSuccessMessage(__('%1 failed task(s) returned to the queue.', $retried));
return $this->resultRedirectFactory->create()->setPath('*/*/');
}
}
@@ -0,0 +1,27 @@
<?php
declare(strict_types=1);
namespace Esun\MultiLocaleTranslation\Controller\Adminhtml\Queue;
use Magento\Backend\App\Action;
use Magento\Framework\View\Result\Page;
use Magento\Framework\View\Result\PageFactory;
class NewAction extends Action
{
public const ADMIN_RESOURCE = 'Esun_MultiLocaleTranslation::queue_add';
public function __construct(Action\Context $context, private readonly PageFactory $pageFactory)
{
parent::__construct($context);
}
public function execute(): Page
{
$page = $this->pageFactory->create();
$page->setActiveMenu('Esun_MultiLocaleTranslation::batch_translation');
$page->getConfig()->getTitle()->prepend(__('Batch Translation'));
return $page;
}
}
@@ -0,0 +1,70 @@
<?php
declare(strict_types=1);
namespace Esun\MultiLocaleTranslation\Controller\Adminhtml\Queue;
use Esun\MultiLocaleTranslation\Model\Queue\SelectionRegistry;
use Magento\Backend\App\Action;
use Magento\Catalog\Model\ResourceModel\Product\CollectionFactory as ProductCollectionFactory;
use Magento\Cms\Model\ResourceModel\Block\CollectionFactory as BlockCollectionFactory;
use Magento\Cms\Model\ResourceModel\Page\CollectionFactory as PageCollectionFactory;
use Magento\Framework\Controller\Result\Redirect;
use Magento\Framework\Data\Collection\AbstractDb;
use Magento\Ui\Component\MassAction\Filter;
use Mageplaza\Blog\Model\ResourceModel\Post\CollectionFactory as BlogPostCollectionFactory;
class Prepare extends Action
{
public const ADMIN_RESOURCE = 'Esun_MultiLocaleTranslation::queue_add';
public function __construct(
Action\Context $context,
private readonly Filter $filter,
private readonly ProductCollectionFactory $productCollectionFactory,
private readonly PageCollectionFactory $pageCollectionFactory,
private readonly BlockCollectionFactory $blockCollectionFactory,
private readonly BlogPostCollectionFactory $blogPostCollectionFactory,
private readonly SelectionRegistry $selectionRegistry
) {
parent::__construct($context);
}
public function execute(): Redirect
{
try {
$entityType = (string)$this->getRequest()->getParam('entity_type');
$this->assertBlogPermission($entityType);
$collection = $this->collection($entityType);
$selected = $this->filter->getCollection($collection)->getAllIds();
if ($selected === []) {
throw new \InvalidArgumentException((string)__('Select at least one item.'));
}
$token = $this->selectionRegistry->save($entityType, array_map('intval', $selected));
return $this->resultRedirectFactory->create()->setPath('*/*/new', ['token' => $token]);
} catch (\Throwable $exception) {
$this->messageManager->addErrorMessage($exception->getMessage());
return $this->resultRedirectFactory->create()->setPath('*/*/new');
}
}
private function collection(string $entityType): AbstractDb
{
return match ($entityType) {
'product' => $this->productCollectionFactory->create(),
'cms_page' => $this->pageCollectionFactory->create(),
'cms_block' => $this->blockCollectionFactory->create(),
'blog_post' => $this->blogPostCollectionFactory->create(),
default => throw new \InvalidArgumentException((string)__('Unsupported batch entity type.')),
};
}
private function assertBlogPermission(string $entityType): void
{
if ($entityType === 'blog_post'
&& !$this->_authorization->isAllowed('Esun_MultiLocaleTranslation::translate_blog')
) {
throw new \InvalidArgumentException((string)__('You are not allowed to translate blog posts.'));
}
}
}
@@ -0,0 +1,45 @@
<?php
declare(strict_types=1);
namespace Esun\MultiLocaleTranslation\Controller\Adminhtml\Queue;
use Esun\MultiLocaleTranslation\Model\Queue\SelectionRegistry;
use Magento\Backend\App\Action;
use Magento\Framework\App\Action\HttpPostActionInterface;
use Magento\Framework\Controller\Result\Redirect;
class PrepareCategories extends Action implements HttpPostActionInterface
{
public const ADMIN_RESOURCE = 'Esun_MultiLocaleTranslation::queue_add';
public function __construct(
Action\Context $context,
private readonly SelectionRegistry $selectionRegistry
) {
parent::__construct($context);
}
public function execute(): Redirect
{
try {
$categoryIds = $this->parseIds((string)$this->getRequest()->getParam('entity_ids'));
if ($categoryIds === []) {
throw new \InvalidArgumentException((string)__('Select at least one category.'));
}
$token = $this->selectionRegistry->save('category', $categoryIds);
return $this->resultRedirectFactory->create()->setPath('*/*/new', ['token' => $token]);
} catch (\Throwable $exception) {
$this->messageManager->addErrorMessage($exception->getMessage());
return $this->resultRedirectFactory->create()->setPath('catalog/category/index');
}
}
/** @return int[] */
private function parseIds(string $value): array
{
$parts = preg_split('/[^0-9]+/', $value, -1, PREG_SPLIT_NO_EMPTY);
return array_values(array_unique(array_filter(array_map('intval', $parts ?: []))));
}
}
@@ -0,0 +1,45 @@
<?php
declare(strict_types=1);
namespace Esun\MultiLocaleTranslation\Controller\Adminhtml\Queue;
use Esun\MultiLocaleTranslation\Model\Queue\SelectionRegistry;
use Magento\Backend\App\Action;
use Magento\Framework\App\Action\HttpPostActionInterface;
use Magento\Framework\Controller\Result\Redirect;
class PrepareWikiCategories extends Action implements HttpPostActionInterface
{
public const ADMIN_RESOURCE = 'Esun_MultiLocaleTranslation::queue_add';
public function __construct(
Action\Context $context,
private readonly SelectionRegistry $selectionRegistry
) {
parent::__construct($context);
}
public function execute(): Redirect
{
try {
if (!$this->_authorization->isAllowed('Esun_MultiLocaleTranslation::translate_wiki')) {
throw new \InvalidArgumentException((string)__('You are not allowed to translate Wiki content.'));
}
$categoryIds = array_values(array_unique(array_filter(array_map(
'intval',
(array)$this->getRequest()->getParam('entity_ids', [])
))));
if ($categoryIds === []) {
throw new \InvalidArgumentException((string)__('Select at least one Wiki category.'));
}
$token = $this->selectionRegistry->save('wiki_category', $categoryIds);
return $this->resultRedirectFactory->create()->setPath('*/*/new', ['token' => $token]);
} catch (\Throwable $exception) {
$this->messageManager->addErrorMessage($exception->getMessage());
return $this->resultRedirectFactory->create()->setPath('esun_wiki/category/index');
}
}
}