From 408f17175bc26cccbb86435e9c2b6257d33bff32 Mon Sep 17 00:00:00 2001 From: bb Date: Tue, 18 Aug 2026 17:08:38 +0300 Subject: [PATCH] 15244-upgrade-plan-in-menu-v2 --- Block/Adminhtml/System/Config/Form/Info.php | 44 ++++- .../Backend/Model/Menu/BuilderPlugin.php | 166 +++++++++++++++--- view/adminhtml/web/js/magefan-menu.js | 29 ++- 3 files changed, 201 insertions(+), 38 deletions(-) diff --git a/Block/Adminhtml/System/Config/Form/Info.php b/Block/Adminhtml/System/Config/Form/Info.php index 29b9141..ae39606 100755 --- a/Block/Adminhtml/System/Config/Form/Info.php +++ b/Block/Adminhtml/System/Config/Form/Info.php @@ -11,6 +11,7 @@ use Magefan\Community\Api\GetModuleVersionInterface; use Magefan\Community\Api\SecureHtmlRendererInterface; use Magefan\Community\Api\GetModuleInfoInterface; +use Magefan\Community\Model\SectionFactory; use Magento\Backend\Block\Template\Context; use Magento\Framework\Data\Form\Element\AbstractElement; use Magento\Framework\Module\ModuleListInterface; @@ -40,6 +41,11 @@ class Info extends \Magento\Config\Block\System\Config\Form\Field */ protected $getModuleInfo; + /** + * @var SectionFactory + */ + protected $sectionFactory; + /** * @param ModuleListInterface $moduleList * @param Context $context @@ -47,6 +53,7 @@ class Info extends \Magento\Config\Block\System\Config\Form\Field * @param GetModuleVersionInterface|null $getModuleVersion * @param SecureHtmlRendererInterface|null $mfSecureRenderer * @param ModuleInfoInterface|null $getModuleInfo + * @param SectionFactory|null $sectionFactory */ public function __construct( ModuleListInterface $moduleList, @@ -54,7 +61,8 @@ public function __construct( array $data = [], ?GetModuleVersionInterface $getModuleVersion = null, ?SecureHtmlRendererInterface $mfSecureRenderer = null, - ?GetModuleInfoInterface $getModuleInfo = null + ?GetModuleInfoInterface $getModuleInfo = null, + ?SectionFactory $sectionFactory = null ) { parent::__construct($context, $data); $this->moduleList = $moduleList; @@ -65,6 +73,8 @@ public function __construct( ->get(SecureHtmlRendererInterface::class); $this->getModuleInfo = $getModuleInfo ?: \Magento\Framework\App\ObjectManager::getInstance() ->get(GetModuleInfoInterface::class); + $this->sectionFactory = $sectionFactory ?: \Magento\Framework\App\ObjectManager::getInstance() + ->get(SectionFactory::class); } /** @@ -206,12 +216,20 @@ public function render(AbstractElement $element)
'; if ($canUpgradeToMaxPlan) { - $escapedUrl = $this->escapeHtml($moduleUrl . '/pricing' . $utmParam); + $productKey = $this->getProductKey($element); + + if ($productKey) { + $upgradeUrl = 'https://magefan.com/mfplanupgrade/upgrade/index?product_key=' . urlencode($productKey); + } else { + $upgradeUrl = $moduleUrl . '/pricing' . $utmParam . '&utm_campaign=upgrade-plan'; + } + + $escapedUrl = $this->escapeHtml($upgradeUrl); $html .= ''; @@ -351,6 +369,26 @@ class="action-update" return $html; } + /** + * Get configured product key for the current section, if any + * + * @param AbstractElement $element + * @return string|null + */ + protected function getProductKey(AbstractElement $element) + { + $fieldConfig = $element->getFieldConfig(); + $sectionName = !empty($fieldConfig['path']) ? explode('/', $fieldConfig['path'])[0] : ''; + + if (!$sectionName) { + return null; + } + + $productKey = $this->sectionFactory->create(['name' => $sectionName])->getKey(); + + return $productKey ?: null; + } + /** * Return extension url * diff --git a/Plugin/Magento/Backend/Model/Menu/BuilderPlugin.php b/Plugin/Magento/Backend/Model/Menu/BuilderPlugin.php index c371647..016d7e7 100644 --- a/Plugin/Magento/Backend/Model/Menu/BuilderPlugin.php +++ b/Plugin/Magento/Backend/Model/Menu/BuilderPlugin.php @@ -9,10 +9,12 @@ namespace Magefan\Community\Plugin\Magento\Backend\Model\Menu; use Magefan\Community\Api\GetModuleInfoInterface; +use Magefan\Community\Api\GetModuleVersionInterface; use Magento\Backend\Model\Menu\Builder; use Magento\Backend\Model\Menu; use Magento\Backend\Model\Menu\ItemFactory; use Magefan\Community\Model\Config; +use Magefan\Community\Model\SectionFactory; use Magento\Config\Model\Config\Structure; use Magento\Framework\Module\Manager; use Magento\Framework\Module\ModuleListInterface; @@ -49,6 +51,11 @@ class BuilderPlugin */ private $configSections; + /** + * @var array + */ + private $allConfigSections; + /** * @var array */ @@ -59,6 +66,16 @@ class BuilderPlugin */ private $getModuleInfo; + /** + * @var GetModuleVersionInterface + */ + private $getModuleVersion; + + /** + * @var SectionFactory + */ + private $sectionFactory; + /** * BuilderPlugin constructor. * @param ItemFactory $menuItemFactory @@ -67,6 +84,8 @@ class BuilderPlugin * @param ModuleListInterface $moduleList * @param Manager $moduleManager * @param GetModuleInfoInterface $getModuleInfo + * @param GetModuleVersionInterface|null $getModuleVersion + * @param SectionFactory|null $sectionFactory */ public function __construct( ItemFactory $menuItemFactory, @@ -74,7 +93,9 @@ public function __construct( Structure $structure, ModuleListInterface $moduleList, Manager $moduleManager, - GetModuleInfoInterface $getModuleInfo + GetModuleInfoInterface $getModuleInfo, + ?GetModuleVersionInterface $getModuleVersion = null, + ?SectionFactory $sectionFactory = null ) { $this->menuItemFactory = $menuItemFactory; $this->config = $config; @@ -83,6 +104,12 @@ public function __construct( $this->moduleManager = $moduleManager; $this->magefanModules = $this->getMagefanModules(); $this->getModuleInfo = $getModuleInfo; + $this->getModuleVersion = $getModuleVersion ?: \Magento\Framework\App\ObjectManager::getInstance()->get( + GetModuleVersionInterface::class + ); + $this->sectionFactory = $sectionFactory ?: \Magento\Framework\App\ObjectManager::getInstance()->get( + SectionFactory::class + ); } /** @@ -236,32 +263,12 @@ private function addUserGuideLinks(Menu $menu): void $added[] = $id; // extract module name from id e.g. Magefan_Blog::elements -> Magefan_Blog - $module = explode('::', $id)[0]; - $module = explode('_', $module)[1]; - $url = !empty($modulesInfo[$module]) ? $modulesInfo[$module]->getDocumentationUrl() : ''; - // unique id per module to avoid conflicts - $newItemId = $id . '_user_guides'; - if (!$url) { - continue; - } - - try { - $encodedUrl = 'mf-ug-url-start' . rtrim(strtr(base64_encode($url), '+/', '-_'), '=') . 'mf-ug-url-end'; - - $userGuideItem = $this->menuItemFactory->create([ - 'data' => [ - 'id' => $newItemId, - 'title' => 'User Guides', - 'module' => 'Magefan_Community', - 'resource' => 'Magefan_Community::elements', - 'action' => $encodedUrl, - ] - ]); - - $menu->add($userGuideItem, $id, 6000); + $moduleFullName = explode('::', $id)[0]; + $module = explode('_', $moduleFullName)[1]; + $moduleInfo = !empty($modulesInfo[$module]) ? $modulesInfo[$module] : null; - } catch (\Exception $e) { - } + $this->addUserGuideLink($menu, $id, $moduleInfo); + $this->addUpgradePlanLink($menu, $id, $moduleFullName, $moduleInfo); } } } @@ -270,6 +277,90 @@ private function addUserGuideLinks(Menu $menu): void } } + /** + * Add "User Guides" sub menu item for a module + * + * @param Menu $menu + * @param string $id + * @param \Magento\Framework\DataObject|null $moduleInfo + * @return void + */ + private function addUserGuideLink(Menu $menu, string $id, $moduleInfo): void + { + $url = $moduleInfo ? $moduleInfo->getDocumentationUrl() : ''; + if (!$url) { + return; + } + + try { + $encodedUrl = 'mf-ug-url-start' . rtrim(strtr(base64_encode($url), '+/', '-_'), '=') . 'mf-ug-url-end'; + + $userGuideItem = $this->menuItemFactory->create([ + 'data' => [ + 'id' => $id . '_user_guides', + 'title' => 'User Guides', + 'module' => 'Magefan_Community', + 'resource' => 'Magefan_Community::elements', + 'action' => $encodedUrl, + ] + ]); + + $menu->add($userGuideItem, $id, 6000); + } catch (\Exception $e) { + } + } + + /** + * Add "Upgrade Plan" sub menu item for a module, if a higher paid plan is available for it + * + * @param Menu $menu + * @param string $id + * @param string $moduleFullName + * @param \Magento\Framework\DataObject|null $moduleInfo + * @return void + */ + private function addUpgradePlanLink(Menu $menu, string $id, string $moduleFullName, $moduleInfo): void + { + if (!$moduleInfo || !$moduleInfo->getMaxPlan()) { + return; + } + + $canUpgradeToMaxPlan = !$this->getModuleVersion->execute( + $moduleFullName . ucfirst($moduleInfo->getMaxPlan()) + ); + + if (!$canUpgradeToMaxPlan) { + return; + } + + $productKey = $this->getProductKeyForModule($moduleFullName); + + if ($productKey) { + $url = 'https://magefan.com/mfplanupgrade/upgrade/index?product_key=' . urlencode($productKey); + } else { + $moduleUrl = $moduleInfo->getProductUrl() ?: 'https://magefan.com/'; + $url = rtrim($moduleUrl, '/') . '/pricing' + . '?utm_source=admin&utm_medium=menu&utm_campaign=upgrade-plan'; + } + + try { + $encodedUrl = 'mf-upg-url-start' . rtrim(strtr(base64_encode($url), '+/', '-_'), '=') . 'mf-upg-url-end'; + + $upgradePlanItem = $this->menuItemFactory->create([ + 'data' => [ + 'id' => $id . '_upgrade_plan', + 'title' => 'Upgrade Plan', + 'module' => 'Magefan_Community', + 'resource' => 'Magefan_Community::elements', + 'action' => $encodedUrl, + ] + ]); + + $menu->add($upgradePlanItem, $id, 6001); + } catch (\Exception $e) { + } + } + /** * Ge config by module name * @@ -304,11 +395,34 @@ private function getConfigSections($moduleName) $mName = $this->getModuleNameByResource($section['resource']); $this->configSections[$mName] = $section; } + + $this->allConfigSections = $this->configSections; } return isset($this->configSections[$moduleName]) ? $this->configSections[$moduleName] : null; } + /** + * Get configured product key for a Magefan module, if any + * + * @param string $moduleFullName + * @return string|null + */ + private function getProductKeyForModule(string $moduleFullName): ?string + { + // ensure sections are loaded, regardless of whether createMenuItem() already consumed them + $this->getConfigSections($moduleFullName); + + $sectionKey = $this->allConfigSections[$moduleFullName]['key'] ?? null; + if (!$sectionKey) { + return null; + } + + $productKey = $this->sectionFactory->create(['name' => $sectionKey])->getKey(); + + return $productKey ?: null; + } + /** * Get module by resource * diff --git a/view/adminhtml/web/js/magefan-menu.js b/view/adminhtml/web/js/magefan-menu.js index 6e91d50..e9634ae 100644 --- a/view/adminhtml/web/js/magefan-menu.js +++ b/view/adminhtml/web/js/magefan-menu.js @@ -119,7 +119,7 @@ var MagefanMenuManager = { if (groupTitleParent && groupTitleParent.nodeName === 'STRONG') { var link = document.createElement('a'); link.href = "#"; - link.className = 'mf-submenu-group-title'; + link.className = 'mf-submenu-group-title'; link.innerHTML = groupTitleParent.innerHTML; groupTitleParent.replaceWith(link); @@ -182,7 +182,7 @@ var MagefanMenuManager = { } var titleEl = parent.querySelector('.mf-submenu-group-title span') || - parent.querySelector('.submenu-group-title span'); + parent.querySelector('.submenu-group-title span'); var titleText = titleEl ? titleEl.textContent.trim() : ''; self.addSubmenuHeader(submenu, titleText); @@ -355,7 +355,7 @@ var MagefanMenuManager = { var getItemText = function(item) { var span = item.querySelector('.mf-submenu-group-title span') || - item.querySelector('.submenu-group-title span'); + item.querySelector('.submenu-group-title span'); return (span ? span.textContent : '').trim(); }; @@ -407,7 +407,7 @@ var MagefanMenuManager = { if (baseItem) { var titleEl = baseItem.querySelector('.submenu-group-title span') || - baseItem.querySelector('.mf-submenu-group-title span'); + baseItem.querySelector('.mf-submenu-group-title span'); if (titleEl) { titleEl.textContent = 'General'; titleEl.setAttribute('data-original-text', 'General'); @@ -733,10 +733,11 @@ var MagefanMenuManager = { }); } - function addUserGuideLinks() { - document.querySelectorAll('a[href*="mf-ug-url-start"]').forEach(function(link) { - var match = link.getAttribute('href') - .match(/mf-ug-url-start(.+?)mf-ug-url-end/); + function addEncodedMenuLinks(marker, endMarker, utmCampaign) { + var regex = new RegExp(marker + '(.+?)' + endMarker); + + document.querySelectorAll('a[href*="' + marker + '"]').forEach(function(link) { + var match = link.getAttribute('href').match(regex); if (!match) return; var encoded = match[1]; @@ -753,7 +754,8 @@ var MagefanMenuManager = { if (!decoded) return; - link.href = decoded + '?utm_source=admin&utm_medium=menu&utm_campaign=sub-guide'; + var separator = decoded.indexOf('?') !== -1 ? '&' : '?'; + link.href = decoded + separator + 'utm_source=admin&utm_medium=menu&utm_campaign=' + utmCampaign; link.target = '_blank'; link.addEventListener('click', function(e) { e.preventDefault(); @@ -762,6 +764,15 @@ var MagefanMenuManager = { }); } + function addUserGuideLinks() { + addEncodedMenuLinks('mf-ug-url-start', 'mf-ug-url-end', 'sub-guide'); + } + + function addUpgradePlanLinks() { + addEncodedMenuLinks('mf-upg-url-start', 'mf-upg-url-end', 'upgrade-plan'); + } + addMainUserGuideAndMarketplaceLinks(); addUserGuideLinks(); + addUpgradePlanLinks(); })();