diff --git a/Controller/Adminhtml/Mass/Translate/Locked.php b/Controller/Adminhtml/Mass/Translate/Locked.php index 3198948..3e934d0 100644 --- a/Controller/Adminhtml/Mass/Translate/Locked.php +++ b/Controller/Adminhtml/Mass/Translate/Locked.php @@ -22,7 +22,8 @@ public function execute() $this->messageManager->addNotice( __( - 'This option is available in Magefan Translation Extra plan only. Please upgrade at ' .$url. '.', + 'This option is available in Magefan Translation Extra plan only. + Please upgrade plan to use this feature.', $url . $params ) ); diff --git a/Model/Config/Source/ScheduleSource.php b/Model/Config/Source/ScheduleSource.php index b651fa1..7ac267e 100644 --- a/Model/Config/Source/ScheduleSource.php +++ b/Model/Config/Source/ScheduleSource.php @@ -39,6 +39,8 @@ public function toOptionArray() ['value' => EntityManager::SECONDBLOG_CATEGORY_ID, 'label' => __('Magefan Second Blog Category')], ['value' => EntityManager::SECONDBLOG_AUTHOR_ID, 'label' => __('Magefan Second Blog Author')], + ['value' => EntityManager::REVIEW_ID, 'label' => __('Product Review')], + ['value' => EntityManager::TRANSLATE_ID, 'label' => __('Phrases from Search And Translate')] ]; diff --git a/Model/EntityManager.php b/Model/EntityManager.php index f770a49..aa83f1f 100644 --- a/Model/EntityManager.php +++ b/Model/EntityManager.php @@ -28,6 +28,8 @@ class EntityManager public const SECONDBLOG_CATEGORY_ID = 13; public const SECONDBLOG_AUTHOR_ID = 14; + public const REVIEW_ID = 15; + public const TYPE_CATEGORY = 'category'; public const TYPE_PRODUCT = 'product'; public const TYPE_PAGE = 'page'; @@ -45,6 +47,8 @@ class EntityManager public const TYPE_SECONDBLOG_CATEGORY = 'secondblog_category'; public const TYPE_SECONDBLOG_AUTHOR = 'secondblog_author'; + public const TYPE_REVIEW = 'review'; + public const TYPES = [ self::TYPE_PRODUCT => self::PRODUCT_ID, self::TYPE_CATEGORY => self::CATEGORY_ID, @@ -61,7 +65,9 @@ class EntityManager self::TYPE_SECONDBLOG_POST => self::SECONDBLOG_POST_ID, self::TYPE_SECONDBLOG_TAG => self::SECONDBLOG_TAG_ID, self::TYPE_SECONDBLOG_CATEGORY => self::SECONDBLOG_CATEGORY_ID, - self::TYPE_SECONDBLOG_AUTHOR => self::SECONDBLOG_AUTHOR_ID + self::TYPE_SECONDBLOG_AUTHOR => self::SECONDBLOG_AUTHOR_ID, + + self::TYPE_REVIEW => self::REVIEW_ID ]; /** diff --git a/Model/TranslatableData.php b/Model/TranslatableData.php index 9b21b73..c6d6361 100644 --- a/Model/TranslatableData.php +++ b/Model/TranslatableData.php @@ -136,6 +136,10 @@ public function getList(?string $type = null): array $data[EntityManager::TYPE_SECONDBLOG_AUTHOR] = $this->getBlogAuthorFields(); break; + case EntityManager::TYPE_REVIEW: + $data[EntityManager::TYPE_REVIEW] = $this->getReviewFields(); + break; + default: // If no type or unrecognized type is passed, return all $data[EntityManager::TYPE_CATEGORY] = $this->getCatalogAttributes($this->categoryAttributeRepository); @@ -157,6 +161,10 @@ public function getList(?string $type = null): array $data[EntityManager::TYPE_SECONDBLOG_AUTHOR] = $this->getBlogAuthorFields(); } + if ($this->moduleManager->isEnabled('Magento_Review')) { + $data[EntityManager::TYPE_REVIEW] = $this->getReviewFields(); + } + break; } @@ -351,6 +359,23 @@ private function getBlogCategoryFields(): array ]; } + /** + * Product review fields. + * + * Nickname is intentionally not listed - it holds the reviewer's name, + * which must not be machine translated. It is still stored per store view + * so it can be corrected manually. + * + * @return array[] + */ + private function getReviewFields(): array + { + return [ + ['label' => __('Summary of Review'), 'code' => 'title', 'type' => 'text'], + ['label' => __('Review'), 'code' => 'detail', 'type' => 'text'] + ]; + } + /** * @return array[] */ diff --git a/Plugin/Backend/Magento/Backend/Block/Widget/Grid/Massaction.php b/Plugin/Backend/Magento/Backend/Block/Widget/Grid/Massaction.php index ef168e9..6e2ca3a 100644 --- a/Plugin/Backend/Magento/Backend/Block/Widget/Grid/Massaction.php +++ b/Plugin/Backend/Magento/Backend/Block/Widget/Grid/Massaction.php @@ -24,6 +24,8 @@ class Massaction 'secondblog_category_index', 'secondblog_tag_index', 'secondblogauthor_author_index', + 'review_product_index', + 'review_product_pending', ]; /** diff --git a/Plugin/Backend/Magento/Review/Block/Adminhtml/Edit.php b/Plugin/Backend/Magento/Review/Block/Adminhtml/Edit.php new file mode 100644 index 0000000..d783e66 --- /dev/null +++ b/Plugin/Backend/Magento/Review/Block/Adminhtml/Edit.php @@ -0,0 +1,68 @@ +config = $config; + } + + /** + * @param \Magento\Review\Block\Adminhtml\Edit $subject + * @param \Magento\Framework\View\Layout $layout + * @return array + */ + public function beforeSetLayout( + \Magento\Review\Block\Adminhtml\Edit $subject, + $layout + ) { + if ($this->config->isEnabled() && $subject->getRequest()->getParam('id')) { + $subject->addButton( + 'mftranslation_locked_button', + [ + 'label' => __('Auto Translate'), + 'on_click' => $this->getOnClick(), + 'class' => 'mf_auto_translate', + ], + 10 + ); + } + + return [$layout]; + } + + /** + * @return string + */ + private function getOnClick(): string + { + return "require(['Magefan_Translation/js/mf-upgrade-plan-popup'], function(mfPopup){" + . "mfPopup('Extra', 'auto-translate', 'button');" + . "});"; + } +} diff --git a/Plugin/Backend/Magento/Review/Block/Adminhtml/Edit/FormPlugin.php b/Plugin/Backend/Magento/Review/Block/Adminhtml/Edit/FormPlugin.php new file mode 100644 index 0000000..42918af --- /dev/null +++ b/Plugin/Backend/Magento/Review/Block/Adminhtml/Edit/FormPlugin.php @@ -0,0 +1,183 @@ +config = $config; + $this->mfSecureRenderer = $mfSecureRenderer; + } + + /** + * @param \Magento\Review\Block\Adminhtml\Edit\Form $subject + * @param \Magento\Framework\Data\Form|null $form + * @return \Magento\Framework\Data\Form|null + */ + public function afterGetForm($subject, $form) + { + if (!$form || !$this->config->isEnabled()) { + return $form; + } + + $this->addLockedExcludeAutoTranslationField($form); + + return $form; + } + + /** + * Adds the "Auto Translation (Extra)" fieldset right after review_details, or + * returns null without touching the form if it's already there (getForm() runs + * more than once per page render) or review_details itself isn't present to + * insert after. + * + * Shared between this class's own locked field below and + * Magefan\TranslationPlus\...\FormPlugin's real one, so the guard only lives once. + * + * @param \Magento\Framework\Data\Form $form + * @return \Magento\Framework\Data\Form\Element\Fieldset|null + */ + protected function addAutoTranslationFieldset($form) + { + if (!$form->getElement('review_details') || $form->getElement('mf_auto_translation')) { + return null; + } + + return $form->addFieldset( + 'mf_auto_translation', + ['legend' => __('Auto Translation (Extra)')], + 'review_details' + ); + } + + /** + * Adds the locked "Exclude From Auto Translation" field: the same toggle a working + * install would show, disabled, with a click-anywhere overlay that opens the + * upgrade popup instead of doing anything. + * + * @param \Magento\Framework\Data\Form $form + * @return void + */ + protected function addLockedExcludeAutoTranslationField($form) + { + $fieldset = $this->addAutoTranslationFieldset($form); + + if (!$fieldset) { + return; + } + + $fieldset->addField( + 'mf_exclude_auto_translation', + 'note', + [ + 'label' => __('Exclude From Auto Translation'), + 'text' => $this->getLockedToggleHtml(), + ] + ); + } + + /** + * Renders a locked stand-in for a toggle-switch field: the same toggle + + * "Use Default Value" markup the real, working field would use, both inert, with a + * transparent overlay on top that shows the upgrade popup on any click. + * + * @return string + */ + private function getLockedToggleHtml() + { + $yes = $this->escapeAttr(__('Yes')); + $no = $this->escapeAttr(__('No')); + + return '
' + . '
' + . '
' + . '' + . '' + . '
' + . '' + . '
' + . '
' + . '
' + . $this->getLockedClickHandlerHtml(); + } + + /** + * @param \Magento\Framework\Phrase $phrase + * @return string + */ + private function escapeAttr($phrase) + { + return htmlspecialchars((string)$phrase, ENT_QUOTES); + } + + /** + * Delegated on document rather than an inline onclick attribute - inline handlers + * are blocked under a strict CSP. classList.contains() rather than a className + * substring match: className is an SVGAnimatedString (no .indexOf) on an SVG + * target, e.g. any of the admin icon sprites elsewhere on the page. + * + * @return string + */ + private function getLockedClickHandlerHtml() + { + $script = "document.addEventListener('click', function (event) {" + . "if (!event.target.classList || !event.target.classList.contains('mf-locked-toggle-overlay')) {" + . " return; }" + . "require(['Magefan_Translation/js/mf-upgrade-plan-popup'], function (mfPopup) {" + . " mfPopup('Extra', 'review-edit', 'fieldset');" + . "});" + . "});"; + + return $this->mfSecureRenderer->renderTag('script', [], $script, false); + } +} diff --git a/Setup/Recurring.php b/Setup/Recurring.php index cceaf60..3d0b4fa 100644 --- a/Setup/Recurring.php +++ b/Setup/Recurring.php @@ -30,6 +30,12 @@ public function install(SchemaSetupInterface $setup, ModuleContextInterface $con 'magefan_second_blog_category', 'magefan_second_blog_tag', 'magefan_second_blog_author', + // "review" and not "review_detail": ResourceModel\Review::_afterSave() + // writes only title/detail/nickname to review_detail, while the main table + // is saved through AbstractDb::_prepareDataForSave() and picks the column up. + // Handled here rather than in db_schema.xml because Magento_Review can be + // disabled, and declaring a column on an undeclared table breaks upgrade. + 'review', ]; $localizationTables = [ diff --git a/etc/adminhtml/di.xml b/etc/adminhtml/di.xml index 5c22063..df85055 100644 --- a/etc/adminhtml/di.xml +++ b/etc/adminhtml/di.xml @@ -13,4 +13,20 @@ + + + + + + + + + + + + + + diff --git a/view/adminhtml/layout/cms_block_edit.xml b/view/adminhtml/layout/cms_block_edit.xml new file mode 100644 index 0000000..349c455 --- /dev/null +++ b/view/adminhtml/layout/cms_block_edit.xml @@ -0,0 +1,19 @@ + + + + + + + + 1 + + + + + diff --git a/view/adminhtml/layout/cms_page_edit.xml b/view/adminhtml/layout/cms_page_edit.xml new file mode 100644 index 0000000..349c455 --- /dev/null +++ b/view/adminhtml/layout/cms_page_edit.xml @@ -0,0 +1,19 @@ + + + + + + + + 1 + + + + + diff --git a/view/adminhtml/layout/review_product_edit.xml b/view/adminhtml/layout/review_product_edit.xml new file mode 100644 index 0000000..349c455 --- /dev/null +++ b/view/adminhtml/layout/review_product_edit.xml @@ -0,0 +1,19 @@ + + + + + + + + 1 + + + + + diff --git a/view/adminhtml/templates/cms/store-switcher-promo.phtml b/view/adminhtml/templates/cms/store-switcher-promo.phtml new file mode 100644 index 0000000..b3eeab8 --- /dev/null +++ b/view/adminhtml/templates/cms/store-switcher-promo.phtml @@ -0,0 +1,90 @@ + +getWebsites()): ?> +
+ escapeHtml(__('Scope:')) ?> + + getHintHtml() ?> +
+ + + + renderTag('script', [], $script, false) ?> + + diff --git a/view/adminhtml/templates/form/js.phtml b/view/adminhtml/templates/form/js.phtml index c3d3e45..9462794 100644 --- a/view/adminhtml/templates/form/js.phtml +++ b/view/adminhtml/templates/form/js.phtml @@ -40,7 +40,7 @@ if ($block->getRequest()->getParam('store')) { var a = \$('" . $escaper->escapeHtml(__('Translate')) . "').click(function(e){ e.preventDefault(); require(['Magefan_Translation/js/mf-upgrade-plan-popup'], function(mfPopup) { - mfPopup('Plus or Extra', 'auto-translate', 'button'); + mfPopup('Plus or Extra', 'auto-translate', 'button-attr'); }); return false; });