├── .gitignore ├── Api ├── BreezeThemeDetectionInterface.php ├── GetCategoryByProductInterface.php ├── GetModuleInfoInterface.php ├── GetModuleVersionInterface.php ├── GetParentProductIdsInterface.php ├── GetWebsitesMapInterface.php ├── HyvaThemeDetectionInterface.php └── SecureHtmlRendererInterface.php ├── Block ├── Adminhtml │ ├── Edit │ │ ├── BackButton.php │ │ ├── CreateButton.php │ │ ├── DeleteButton.php │ │ ├── DuplicateButton.php │ │ ├── GenericButton.php │ │ ├── PreviewButton.php │ │ ├── ResetButton.php │ │ ├── SaveAndContinueButton.php │ │ └── SaveButton.php │ ├── HyvaThemeChecker.php │ ├── Linv.php │ └── System │ │ └── Config │ │ └── Form │ │ ├── ExtensionsInfo.php │ │ ├── Info.php │ │ └── ProductKeyField.php └── JsScript.php ├── Controller └── Adminhtml │ ├── Actions.php │ └── Activate │ └── Extension.php ├── Cron └── Sections.php ├── LICENSE.txt ├── Model ├── AbstractThemeDetection.php ├── AdminNotificationFeed.php ├── BreezeThemeDetection.php ├── Config.php ├── GetCategoryByProduct.php ├── GetModuleInfo.php ├── GetModuleVersion.php ├── GetParentProductIds.php ├── GetWebsitesMap.php ├── HyvaThemeDetection.php ├── Magento │ ├── Framework │ │ └── Mail │ │ │ └── Template │ │ │ └── TransportBuilder.php │ ├── Product │ │ └── CollectionOptimizedForSqlValidator.php │ └── Rule │ │ └── Model │ │ └── Condition │ │ └── Sql │ │ └── Builder.php ├── Section.php ├── Section │ └── Info.php ├── SetLinvFlag.php ├── UrlChecker.php └── View │ └── Helper │ └── SecureHtmlRenderer.php ├── Observer ├── ConfigObserver.php └── PredispathAdminActionControllerObserver.php ├── Plugin └── Magento │ ├── Backend │ └── Model │ │ └── Menu │ │ └── BuilderPlugin.php │ └── Framework │ └── View │ └── TemplateEngine │ └── Php.php ├── README.md ├── Setup └── Patch │ └── Data │ └── UpdateWelcomeBlogPost.php ├── composer.json ├── etc ├── acl.xml ├── adminhtml │ ├── di.xml │ ├── events.xml │ ├── routes.xml │ └── system.xml ├── config.xml ├── crontab.xml ├── csp_whitelist.xml ├── di.xml └── module.xml ├── registration.php └── view ├── adminhtml ├── layout │ ├── default.xml │ └── marketplace_index_index.xml ├── templates │ ├── hyvathemechecker.phtml │ ├── linv.phtml │ ├── menu-magefan.phtml │ └── vendors.phtml └── web │ ├── css │ └── source │ │ └── _module.less │ ├── fonts │ └── variable │ │ └── LDIoaomQNQcsA88c7O9yZ4KMCoOg4Ko20yw.woff2 │ └── images │ ├── logo-config-section.png │ ├── logo-menu.png │ └── magefan-logo.png └── base └── templates └── js ├── ajax.phtml ├── getCookie.phtml ├── objToUrlParams.phtml └── setCookie.phtml /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ -------------------------------------------------------------------------------- /Api/BreezeThemeDetectionInterface.php: -------------------------------------------------------------------------------- 1 | __('Back'), 23 | 'on_click' => sprintf("location.href = '%s';", $this->getBackUrl()), 24 | 'class' => 'back', 25 | 'sort_order' => 10 26 | ]; 27 | } 28 | 29 | /** 30 | * Get URL for back (reset) button 31 | * 32 | * @return string 33 | */ 34 | public function getBackUrl() 35 | { 36 | return $this->getUrl('*/*/'); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Block/Adminhtml/Edit/CreateButton.php: -------------------------------------------------------------------------------- 1 | __('Create'), 23 | 'class' => 'save primary', 24 | 'data_attribute' => [ 25 | 'mage-init' => ['button' => ['event' => 'save']], 26 | 'form-role' => 'save', 27 | ], 28 | 'sort_order' => 10 29 | ]; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Block/Adminhtml/Edit/DeleteButton.php: -------------------------------------------------------------------------------- 1 | getObjectId()) { 24 | $data = [ 25 | 'label' => __('Delete'), 26 | 'class' => 'delete', 27 | 'on_click' => 'deleteConfirm(\'' . __( 28 | 'Are you sure you want to do this?' 29 | ) . '\', \'' . $this->getDeleteUrl() . '\')', 30 | 'sort_order' => 20, 31 | ]; 32 | } 33 | return $data; 34 | } 35 | 36 | /** 37 | * @return string 38 | */ 39 | public function getDeleteUrl() 40 | { 41 | return $this->getUrl('*/*/delete', ['id' => $this->getObjectId()]); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Block/Adminhtml/Edit/DuplicateButton.php: -------------------------------------------------------------------------------- 1 | getObjectId()) { 23 | $data = [ 24 | 'label' => __('Duplicate'), 25 | 'class' => 'duplicate', 26 | 'on_click' => 'window.location=\'' . $this->getDuplicateUrl() . '\'', 27 | 'sort_order' => 40, 28 | ]; 29 | } 30 | return $data; 31 | } 32 | 33 | /** 34 | * @return string 35 | */ 36 | public function getDuplicateUrl() 37 | { 38 | return $this->getUrl('*/*/duplicate', ['id' => $this->getObjectId()]); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Block/Adminhtml/Edit/GenericButton.php: -------------------------------------------------------------------------------- 1 | context = $context; 38 | $this->authorization = $authorization 39 | ?: \Magento\Framework\App\ObjectManager::getInstance()->get( 40 | \Magento\Framework\AuthorizationInterface::class 41 | ); 42 | } 43 | 44 | /** 45 | * Return CMS block ID 46 | * 47 | * @return int|null 48 | */ 49 | public function getObjectId() 50 | { 51 | return $this->context->getRequest()->getParam('id'); 52 | } 53 | 54 | /** 55 | * Generate URL by route and parameters 56 | * 57 | * @param string $route 58 | * @param array $params 59 | * @return string 60 | */ 61 | public function getUrl($route = '', $params = []) 62 | { 63 | if ($storeId = $this->context->getRequest()->getParam('store')) { 64 | $params['store'] = (int)$storeId; 65 | } 66 | return $this->context->getUrlBuilder()->getUrl($route, $params); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /Block/Adminhtml/Edit/PreviewButton.php: -------------------------------------------------------------------------------- 1 | getObjectId()) { 23 | $data = [ 24 | 'label' => __('Preview'), 25 | 'class' => 'preview', 26 | 'on_click' => 'window.open(\'' . $this->getPreviewUrl() . '\');', 27 | 'sort_order' => 35, 28 | ]; 29 | } 30 | return $data; 31 | } 32 | 33 | /** 34 | * @return string 35 | */ 36 | public function getPreviewUrl() 37 | { 38 | return $this->getUrl('*/*/preview', ['id' => $this->getObjectId()]); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Block/Adminhtml/Edit/ResetButton.php: -------------------------------------------------------------------------------- 1 | __('Reset'), 23 | 'class' => 'reset', 24 | 'on_click' => 'location.reload();', 25 | 'sort_order' => 30 26 | ]; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Block/Adminhtml/Edit/SaveAndContinueButton.php: -------------------------------------------------------------------------------- 1 | __('Save and Continue Edit'), 24 | 'class' => 'save', 25 | 'data_attribute' => [ 26 | 'mage-init' => [ 27 | 'button' => ['event' => 'saveAndContinueEdit'], 28 | ], 29 | ], 30 | 'sort_order' => 80, 31 | ]; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Block/Adminhtml/Edit/SaveButton.php: -------------------------------------------------------------------------------- 1 | __('Save'), 23 | 'class' => 'save primary', 24 | 'data_attribute' => [ 25 | 'mage-init' => ['button' => ['event' => 'save']], 26 | 'form-role' => 'save', 27 | ], 28 | 'sort_order' => 90, 29 | ]; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Block/Adminhtml/HyvaThemeChecker.php: -------------------------------------------------------------------------------- 1 | moduleManager = $moduleManager; 44 | $this->hyvaThemeDetection =$hyvaThemeDetection; 45 | } 46 | 47 | /** 48 | * @return array 49 | */ 50 | public function getWitchModuleIsInstalled(): array 51 | { 52 | $moduleGroups = [ 53 | 'Blog' => [ 54 | 'Magefan_BlogExtra' => 'magefan/hyva-theme-blog-extra', 55 | 'Magefan_BlogPlus' => 'magefan/hyva-theme-blog-plus', 56 | 'Magefan_Blog' => 'magefan/hyva-theme-blog' 57 | ], 58 | 'AutoRelatedProduct' => [ 59 | 'Magefan_AutoRelatedProductPlus' => 'magefan/hyva-theme-auto-related-product-plus', 60 | 'Magefan_AutoRelatedProduct' => 'magefan/hyva-theme-auto-related-product' 61 | ], 62 | 'AutoLanguageSwitcher' => [ 63 | 'Magefan_AutoLanguageSwitcher' => 'magefan/hyva-theme-auto-language-switcher' 64 | ] 65 | ]; 66 | 67 | $hyvaModules = []; 68 | foreach ($moduleGroups as $groupKey => $modules) { 69 | foreach ($modules as $module => $packageName) { 70 | if ($this->moduleManager->isEnabled($module)) { 71 | $hyvaModule = 'Hyva_' . str_replace('_', '', $module); 72 | if (!$this->moduleManager->isEnabled($hyvaModule)) { 73 | $hyvaModules[$hyvaModule] = $packageName; 74 | break; 75 | } 76 | } 77 | } 78 | } 79 | return $hyvaModules; 80 | } 81 | 82 | /** 83 | * Produce and return block's html output 84 | * 85 | * This method should not be overridden. You can override _toHtml() method in descendants if needed. 86 | * 87 | * @return string 88 | */ 89 | public function toHtml() 90 | { 91 | if (!$this->hyvaThemeDetection->execute()) { 92 | return ''; 93 | } 94 | 95 | return parent::toHtml(); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /Block/Adminhtml/Linv.php: -------------------------------------------------------------------------------- 1 | sectionFactory = $sectionFactory; 41 | $this->resource = $resource; 42 | } 43 | 44 | /** 45 | * @return array 46 | */ 47 | public function getItems() 48 | { 49 | $connection = $this->resource->getConnection(); 50 | $table = $this->resource->getTableName('core_config_data'); 51 | $path = '/g'.'en'.'er'.'al'.'/l'.'in'.'v'; 52 | $select = $connection->select() 53 | ->from([$table]) 54 | ->where( 'path LIKE ?', '%' . $path ) 55 | ->where('value = ?',1); 56 | $items = $connection->fetchAll($select); 57 | $result = []; 58 | 59 | foreach ($items as $config) { 60 | $configPath = explode('/', $config['path']); 61 | $moduleName = $configPath[0]; 62 | $section = $this->sectionFactory->create([ 63 | 'name' => $moduleName 64 | ]); 65 | $module = $section->getModule(true); 66 | if ($module && !$section->isEnabled()) { 67 | $result[] = $module; 68 | } 69 | 70 | } 71 | return $result; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /Block/Adminhtml/System/Config/Form/ExtensionsInfo.php: -------------------------------------------------------------------------------- 1 | moduleList = $moduleList; 48 | $this->getModuleVersion = $getModuleVersion; 49 | $this->getModuleInfo = $getModuleInfo; 50 | } 51 | 52 | /** 53 | * @param AbstractElement $element 54 | * @return string 55 | */ 56 | public function render(AbstractElement $element) 57 | { 58 | $modulesInfo = $this->getModuleInfo->execute(); 59 | if (!$modulesInfo) { 60 | return ''; 61 | } 62 | 63 | $lists = [ 64 | 'need_update' => __('Extensions to Update'), 65 | 'up_to_date' => __('Up-to-Date Extensions'), 66 | 'new_extensions' => __('Available NEW Extensions'), 67 | ]; 68 | 69 | $html = ''; 70 | foreach ($lists as $listKey => $lable) { 71 | $html .= '

' . $this->escapeHtml($lable) . '

'; 72 | $html .= ''; 73 | $html .= ''; 74 | $html .= ''; 75 | $html .= ''; 76 | $html .= ''; 77 | $html .= ''; 78 | $html .= ''; 79 | $html .= ''; 80 | $html .= ''; 81 | $html .= ''; 82 | 83 | foreach ($modulesInfo as $moduleKey => $moduleInfo) { 84 | 85 | $moduleName = 'Magefan_' . $moduleKey; 86 | $module = $this->moduleList->getOne($moduleName); 87 | 88 | if ((!$module && $listKey != 'new_extensions') || ($module && $listKey == 'new_extensions')) { 89 | continue; 90 | } 91 | if ($listKey == 'up_to_date' && version_compare($this->getModuleVersion->execute($moduleName), $moduleInfo->getVersion()) < 0) { 92 | continue; 93 | } 94 | if ($listKey == 'need_update' && version_compare($this->getModuleVersion->execute($moduleName), $moduleInfo->getVersion()) >= 0) { 95 | continue; 96 | } 97 | 98 | if ($listKey == 'need_update') { 99 | $version = $this->getModuleVersion->execute($moduleName) . ' -> ' . $moduleInfo->getVersion(); 100 | } elseif ($listKey == 'new_extensions') { 101 | $version = $moduleInfo->getVersion(); 102 | } else { 103 | $version = $this->getModuleVersion->execute($moduleName); 104 | } 105 | 106 | 107 | $html .= ''; 108 | $html .= ''; 109 | $html .= ''; 110 | $html .= ''; 111 | $html .= ''; 112 | $html .= ''; 113 | } 114 | 115 | $html .= ''; 116 | $html .= '
' . $this->escapeHtml(__('Extension')) . '' . $this->escapeHtml(__('Version')) . '' . $this->escapeHtml(__('Change Log')) . '' . $this->escapeHtml(__('User Guide')) . '
' . $this->escapeHtml($moduleInfo->getProductName()) . '' . $this->escapeHtml($version) . '' . $this->escapeHtml(__('Change Log')) . ''. $this->escapeHtml(__('User Guide')). '

'; 117 | } 118 | return $html; 119 | } 120 | 121 | } 122 | -------------------------------------------------------------------------------- /Block/Adminhtml/System/Config/Form/Info.php: -------------------------------------------------------------------------------- 1 | moduleList = $moduleList; 56 | $this->getModuleVersion = $getModuleVersion ?: \Magento\Framework\App\ObjectManager::getInstance()->get( 57 | \Magefan\Community\Api\GetModuleVersionInterface::class 58 | ); 59 | $this->mfSecureRenderer = $mfSecureRenderer ?: \Magento\Framework\App\ObjectManager::getInstance() 60 | ->get(SecureHtmlRendererInterface::class); 61 | $this->getModuleInfo = $getModuleInfo ?: \Magento\Framework\App\ObjectManager::getInstance() 62 | ->get(GetModuleInfoInterface::class); 63 | } 64 | 65 | /** 66 | * Return info block html 67 | * @param \Magento\Framework\Data\Form\Element\AbstractElement $element 68 | * @return string 69 | */ 70 | public function render(\Magento\Framework\Data\Form\Element\AbstractElement $element) 71 | { 72 | $moduleName = $this->getModuleName(); 73 | 74 | $currentVersion = $this->getModuleVersion->execute($moduleName); 75 | $moduleInfo = $this->getModuleInfo->execute($moduleName); 76 | 77 | $plan = ''; 78 | foreach (['Extra', 'Plus'] as $_plan) { 79 | if ($_currentVersion = $this->getModuleVersion->execute($moduleName . $_plan)) { 80 | $plan = $_plan; 81 | $currentVersion = $_currentVersion; 82 | break; 83 | } 84 | } 85 | 86 | if ($latestVersion = $moduleInfo->getVersion()) { 87 | 88 | $fullModuleTitle = $moduleInfo->getProductName(); 89 | $moduleUrl = $moduleInfo->getProductUrl(); 90 | $moduleImage = $moduleInfo->getProductImage(); 91 | 92 | $newVersionAvailable = version_compare($latestVersion, $currentVersion) > 0; 93 | $moduleTitle = str_replace(['Magento 2', 'Magento'], ['', ''], (string)$fullModuleTitle); 94 | $moduleTitle = trim($moduleTitle); 95 | 96 | } else { 97 | 98 | $fullModuleTitle = $moduleTitle = $this->getModuleTitle(); 99 | $newVersionAvailable = false; 100 | $moduleUrl = $this->getModuleUrl(); 101 | $moduleImage = ''; 102 | } 103 | 104 | $utmParam = '?utm_source=admin&utm_medium=config'; 105 | 106 | if ($moduleInfo->getMaxPlan()) { 107 | $canUpgradeToMaxPlan = !$this->getModuleVersion->execute($moduleName . ucfirst($moduleInfo->getMaxPlan())); 108 | } else { 109 | $canUpgradeToMaxPlan = false; 110 | } 111 | 112 | $html = '
113 |
114 |
115 | 116 | 117 | 118 |
119 |
120 |
121 |
122 | ' . 123 | $this->escapeHtml($moduleTitle) . ($plan ? ' (' . $plan . ')' : '') . 124 | ' v' . $this->escapeHtml($currentVersion) . ' 125 |
126 |
127 |
128 | developed by 129 | Mage' . 'fan 130 | ' . 131 | ($latestVersion ? ' 132 | 133 | 134 | 135 | 136 | 137 | 138 | User Guide 139 | ' : '') . ' 140 |
141 |
142 |
143 | 144 |
145 |
'; 146 | if ($canUpgradeToMaxPlan) { 147 | $html .= ''; 148 | } 149 | 150 | if ($newVersionAvailable) { 151 | $html .= ''; 155 | } 156 | $html .= '
157 | '; 158 | if ($newVersionAvailable) { 159 | $html .= '
Version v' . $this->escapeHtml($latestVersion) . ' is available
'; 160 | } 161 | $html .= '
162 |
163 | 198 | '; 199 | 200 | return $html; 201 | } 202 | 203 | /** 204 | * Return extension url 205 | * @return string 206 | */ 207 | protected function getModuleUrl() 208 | { 209 | return 'https://magefan.com/'; 210 | } 211 | 212 | /** 213 | * Return extension title 214 | * @return string 215 | */ 216 | protected function getModuleTitle() 217 | { 218 | return ucwords(str_replace('_', ' ', $this->getModuleName())) . ' Extension'; 219 | } 220 | 221 | } 222 | -------------------------------------------------------------------------------- /Block/Adminhtml/System/Config/Form/ProductKeyField.php: -------------------------------------------------------------------------------- 1 | getFieldConfig(); 29 | $path = explode('/', $fieldConfig['path']); 30 | $path = $path[0]; 31 | 32 | $section = $objectManager->create(Section::class, ['name' => $path]); 33 | if ($section->getModule()) { 34 | if (!$element->getComment()) { 35 | $url = 'htt' . 'ps' . ':' . '/'. '/'. 'ma' . 'g' . 'ef' . 'an' . '.' . 'co' 36 | . 'm/' . 'down' . 'loa' . 'dab' . 'le/' . 'cus' . 'tomer' . '/' . 'pr' . 'od' . 'ucts' . '/'; 37 | $element->setComment('You can find product key in your Magefan account.'); 38 | } 39 | return parent::render($element); 40 | } else { 41 | $config = ObjectManager::getInstance()->get(\Magefan\Community\Model\Config::class); 42 | $bp = $section->getName() . '/' . 'g' . 'e' . 'n' . 'e' . 'r' . 'a' . 'l' . '/' ; 43 | if (!$config->getConfig( $bp . Section::ACTIVE) && !$section->getType()) { 44 | $url = 'ht' . 'tps'. ':' . '/'. '/'. 'ma' . 'g' . 'ef' . 'an' . '.' . 'c' . 'o' . 'm' . '/' . 'mp' . 'k/a' . 'cti' . 'vat' . 'e/e' . 'xte' . 'nsi' . 'on/' . 'ret' . 'urn_' . 'ur' . 'l/' . 45 | base64_encode($this->getUrl('m' . 'f' . 'co' . 'mm' . 'uni' . 'ty/' . 'act' . 'iva' . 'te/ext' . 'ens' . 'ion', ['section' => $section->getName()])) 46 | . '/mo' . 'dul' . 'e/' . $section->getModuleName() . '/se' . 'cti' . 'on/' . $section->getName(); 47 | return ' 48 | 49 | 50 | 51 | 54 | 55 | 56 | ' ; 57 | } 58 | } 59 | return ''; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /Block/JsScript.php: -------------------------------------------------------------------------------- 1 | jsMethod = $method; 32 | return $this; 33 | } 34 | 35 | public function getTemplate() 36 | { 37 | if (!$this->_template) { 38 | $this->_template = 'Magefan_Community::js/' . $this->jsMethod . '.phtml'; 39 | } 40 | return parent::getTemplate(); 41 | } 42 | 43 | /** 44 | * @return string 45 | */ 46 | public function toHtml() 47 | { 48 | if (isset(self::$rendered[$this->jsMethod])) { 49 | return ''; 50 | } 51 | self::$rendered[$this->jsMethod] = 1; 52 | return parent::toHtml(); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /Controller/Adminhtml/Actions.php: -------------------------------------------------------------------------------- 1 | dataPersistor = $dataPersistor; 93 | parent::__construct($context); 94 | } 95 | 96 | /** 97 | * Action execute 98 | * @return \Magento\Framework\Controller\ResultInterface 99 | */ 100 | public function execute() 101 | { 102 | $_preparedActions = ['index', 'grid', 'new', 'edit', 'save', 'duplicate', 'delete', 'config', 'massStatus']; 103 | $_action = $this->getRequest()->getActionName(); 104 | if (in_array($_action, $_preparedActions)) { 105 | $method = '_'.$_action.'Action'; 106 | 107 | $this->_beforeAction(); 108 | $this->$method(); 109 | $this->_afterAction(); 110 | } 111 | } 112 | 113 | /** 114 | * Index action 115 | * @return void 116 | */ 117 | protected function _indexAction() 118 | { 119 | if ($this->getRequest()->getParam('ajax')) { 120 | $this->_forward('grid'); 121 | return; 122 | } 123 | 124 | $this->_view->loadLayout(); 125 | $this->_setActiveMenu($this->_activeMenu); 126 | $title = __('Manage %1', $this->_getModel(false)->getOwnTitle(true)); 127 | $this->_view->getPage()->getConfig()->getTitle()->prepend($title); 128 | $this->_addBreadcrumb($title, $title); 129 | $this->_view->renderLayout(); 130 | } 131 | 132 | /** 133 | * Grid action 134 | * @return void 135 | */ 136 | protected function _gridAction() 137 | { 138 | $this->_view->loadLayout(false); 139 | $this->_view->renderLayout(); 140 | } 141 | 142 | /** 143 | * New action 144 | * @return void 145 | */ 146 | protected function _newAction() 147 | { 148 | $this->_forward('edit'); 149 | } 150 | 151 | /** 152 | * Edit action 153 | * @return void 154 | */ 155 | public function _editAction() 156 | { 157 | 158 | try { 159 | $model = $this->_getModel(); 160 | $id = $this->getRequest()->getParam('id'); 161 | if (!$model->getId() && $id) { 162 | throw new \Exception("Item is not longer exist.", 1); 163 | } 164 | $this->_getRegistry()->register('current_model', $model); 165 | 166 | $this->_view->loadLayout(); 167 | $this->_setActiveMenu($this->_activeMenu); 168 | 169 | $title = $model->getOwnTitle(); 170 | if ($model->getId()) { 171 | $breadcrumbTitle = __('Edit %1', $title); 172 | $breadcrumbLabel = $breadcrumbTitle; 173 | } else { 174 | $breadcrumbTitle = __('New %1', $title); 175 | $breadcrumbLabel = __('Create %1', $title); 176 | } 177 | $this->_view->getPage()->getConfig()->getTitle()->prepend(__($title)); 178 | $this->_view->getPage()->getConfig()->getTitle()->prepend( 179 | $model->getId() ? $this->_getModelName($model) : __('New %1', $title) 180 | ); 181 | 182 | $this->_addBreadcrumb($breadcrumbLabel, $breadcrumbTitle); 183 | 184 | // restore data 185 | $values = $this->_getSession()->getData($this->_formSessionKey, true); 186 | if ($this->_paramsHolder) { 187 | $values = isset($values[$this->_paramsHolder]) ? $values[$this->_paramsHolder] : null; 188 | } 189 | 190 | if ($values) { 191 | $model->addData($values); 192 | } 193 | 194 | $this->_view->renderLayout(); 195 | } catch (\Exception $e) { 196 | $this->messageManager->addException( 197 | $e, 198 | __( 199 | 'Something went wrong: %1', 200 | $e->getMessage() 201 | ) 202 | ); 203 | $this->_redirect('*/*/'); 204 | } 205 | } 206 | 207 | /** 208 | * Retrieve model name 209 | * @param boolean $plural 210 | * @return string 211 | */ 212 | protected function _getModelName(\Magento\Framework\Model\AbstractModel $model) 213 | { 214 | return $model->getName() ?: $model->getTitle(); 215 | } 216 | 217 | /** 218 | * Save action 219 | * @return void 220 | */ 221 | public function _saveAction() 222 | { 223 | $request = $this->getRequest(); 224 | if (!$request->isPost()) { 225 | $this->getResponse()->setRedirect($this->getUrl('*/*')); 226 | } 227 | $model = $this->_getModel(); 228 | 229 | try { 230 | $params = $this->_paramsHolder ? $request->getParam($this->_paramsHolder) : $request->getParams(); 231 | $params = $this->filterParams($params); 232 | 233 | $idFieldName = $model->getResource()->getIdFieldName(); 234 | if (isset($params[$idFieldName]) && empty($params[$idFieldName])) { 235 | unset($params[$idFieldName]); 236 | } 237 | $model->addData($params); 238 | 239 | $this->_eventManager->dispatch('magefan_' . $this->getRequest()->getModuleName() . '_' . $this->getRequest()->getControllerName() . '_form_before_save', ['model' => $model]); 240 | 241 | $this->_beforeSave($model, $request); 242 | $model->save(); 243 | 244 | $this->_afterSave($model, $request); 245 | 246 | $this->_eventManager->dispatch('magefan_' . $this->getRequest()->getModuleName() . '_' . $this->getRequest()->getControllerName() . '_form_after_save', ['model' => $model]); 247 | 248 | $this->messageManager->addSuccess(__('%1 has been saved.', $model->getOwnTitle())); 249 | $this->_setFormData(false); 250 | } catch (\Magento\Framework\Exception\LocalizedException $e) { 251 | $this->messageManager->addError(nl2br($e->getMessage())); 252 | $this->_setFormData($params); 253 | } catch (\Exception $e) { 254 | $this->messageManager->addException( 255 | $e, 256 | __( 257 | 'Something went wrong while saving this %1. %2', 258 | strtolower($model->getOwnTitle()), 259 | $e->getMessage() 260 | ) 261 | ); 262 | $this->_setFormData($params); 263 | } 264 | 265 | $hasError = (bool)$this->messageManager->getMessages()->getCountByType( 266 | \Magento\Framework\Message\MessageInterface::TYPE_ERROR 267 | ); 268 | 269 | if ($request->getParam('isAjax')) { 270 | $block = $this->_objectManager->create(\Magento\Framework\View\Layout::class)->getMessagesBlock(); 271 | $block->setMessages($this->messageManager->getMessages(true)); 272 | 273 | $this->getResponse()->setBody(json_encode( 274 | [ 275 | 'messages' => $block->getGroupedHtml(), 276 | 'error' => $hasError, 277 | 'model' => $model->toArray(), 278 | ] 279 | )); 280 | } else { 281 | if ($hasError || $request->getParam('back')) { 282 | if ($storeId = $request->getParam('store')) { 283 | $this->_redirect('*/*/edit', [$this->_idKey => $model->getId(), 'store' => (int)$storeId]); 284 | } else { 285 | $this->_redirect('*/*/edit', [$this->_idKey => $model->getId()]); 286 | } 287 | } else { 288 | if ($storeId = $request->getParam('store')) { 289 | $this->_redirect('*/*', ['store' => (int)$storeId]); 290 | } else { 291 | $this->_redirect('*/*'); 292 | } 293 | } 294 | } 295 | } 296 | 297 | /** 298 | * Duplicat action 299 | * @return void 300 | */ 301 | protected function _duplicateAction() 302 | { 303 | try { 304 | $originModel = $this->_getModel(); 305 | if (!$originModel->getId()) { 306 | throw new \Exception("Item is not longer exist.", 1); 307 | } 308 | 309 | $model = $originModel->duplicate(); 310 | 311 | $this->messageManager->addSuccess(__('%1 has been duplicated.', $model->getOwnTitle())); 312 | $this->_redirect('*/*/edit', [$this->_idKey => $model->getId()]); 313 | } catch (\Exception $e) { 314 | $this->messageManager->addException( 315 | $e, 316 | __( 317 | 'Something went wrong while saving this %1. %2', 318 | strtolower(isset($model) ? $model->getOwnTitle() : 'item'), 319 | $e->getMessage() 320 | ) 321 | ); 322 | $this->_redirect('*/*/edit', [$this->_idKey => $originModel->getId()]); 323 | } 324 | } 325 | 326 | /** 327 | * Before model Save action 328 | * @return void 329 | */ 330 | protected function _beforeSave($model, $request) 331 | { 332 | } 333 | 334 | /** 335 | * After model action 336 | * @return void 337 | */ 338 | protected function _afterSave($model, $request) 339 | { 340 | } 341 | 342 | /** 343 | * Before action 344 | * @return void 345 | */ 346 | protected function _beforeAction() 347 | { 348 | } 349 | 350 | /** 351 | * After action 352 | * @return void 353 | */ 354 | protected function _afterAction() 355 | { 356 | } 357 | 358 | /** 359 | * Delete action 360 | * @return void 361 | */ 362 | protected function _deleteAction() 363 | { 364 | $ids = $this->getRequest()->getParam($this->_idKey); 365 | 366 | if (!is_array($ids)) { 367 | $ids = [$ids]; 368 | } 369 | 370 | $error = false; 371 | try { 372 | foreach ($ids as $id) { 373 | $this->_objectManager->create($this->_modelClass)->load($id)->delete(); 374 | } 375 | } catch (\Magento\Framework\Exception\LocalizedException $e) { 376 | $error = true; 377 | $this->messageManager->addError($e->getMessage()); 378 | } catch (\Exception $e) { 379 | $error = true; 380 | $this->messageManager->addException( 381 | $e, 382 | __( 383 | "We can't delete %1 right now. %2", 384 | strtolower($this->_getModel(false)->getOwnTitle()), 385 | $e->getMessage() 386 | ) 387 | ); 388 | } 389 | 390 | if (!$error) { 391 | $this->messageManager->addSuccess( 392 | __('%1 have been deleted.', $this->_getModel(false)->getOwnTitle(count($ids) > 1)) 393 | ); 394 | } 395 | 396 | $this->_redirect('*/*'); 397 | } 398 | 399 | /** 400 | * Change status action 401 | * @return void 402 | */ 403 | protected function _massStatusAction() 404 | { 405 | $ids = $this->getRequest()->getParam($this->_idKey); 406 | 407 | if (!is_array($ids)) { 408 | $ids = [$ids]; 409 | } 410 | 411 | $model = $this->_getModel(false); 412 | 413 | $error = false; 414 | 415 | try { 416 | $status = $this->getRequest()->getParam('status'); 417 | $statusFieldName = $this->_statusField; 418 | 419 | if (is_null($status)) { 420 | throw new \Exception(__('Parameter "Status" missing in request data.')); 421 | } 422 | 423 | if (is_null($statusFieldName)) { 424 | throw new \Exception(__('Status Field Name is not specified.')); 425 | } 426 | 427 | foreach ($ids as $id) { 428 | $this->_objectManager->create($this->_modelClass) 429 | ->load($id) 430 | ->setData($this->_statusField, $status) 431 | ->save(); 432 | } 433 | } catch (\Magento\Framework\Exception\LocalizedException $e) { 434 | $error = true; 435 | $this->messageManager->addError($e->getMessage()); 436 | } catch (\Exception $e) { 437 | $error = true; 438 | $this->messageManager->addException( 439 | $e, 440 | __( 441 | "We can't change status of %1 right now. %2", 442 | strtolower($model->getOwnTitle()), 443 | $e->getMessage() 444 | ) 445 | ); 446 | } 447 | 448 | if (!$error) { 449 | $this->messageManager->addSuccess( 450 | __('%1 status have been changed.', $model->getOwnTitle(count($ids) > 1)) 451 | ); 452 | } 453 | 454 | $this->_redirect('*/*'); 455 | } 456 | 457 | /** 458 | * Go to config section action 459 | * @return void 460 | */ 461 | protected function _configAction() 462 | { 463 | $this->_redirect('admin/system_config/edit', ['section' => $this->_configSection()]); 464 | } 465 | 466 | /** 467 | * Set form data 468 | * @return $this 469 | */ 470 | protected function _setFormData($data = null) 471 | { 472 | if (null === $data) { 473 | $data = $this->getRequest()->getParams(); 474 | } 475 | 476 | if (false === $data) { 477 | $this->dataPersistor->clear($this->_formSessionKey); 478 | } else { 479 | $this->dataPersistor->set($this->_formSessionKey, $data); 480 | } 481 | 482 | /* deprecated save in session */ 483 | $this->_getSession()->setData($this->_formSessionKey, $data); 484 | 485 | return $this; 486 | } 487 | 488 | /** 489 | * Filter request params 490 | * @param array $data 491 | * @return array 492 | */ 493 | protected function filterParams($data) 494 | { 495 | return $data; 496 | } 497 | 498 | /** 499 | * Get core registry 500 | * @return void 501 | */ 502 | protected function _getRegistry() 503 | { 504 | if (is_null($this->_coreRegistry)) { 505 | $this->_coreRegistry = $this->_objectManager->get(\Magento\Framework\Registry::class); 506 | } 507 | return $this->_coreRegistry; 508 | } 509 | 510 | /** 511 | * Check is allowed access 512 | * 513 | * @return bool 514 | */ 515 | protected function _isAllowed() 516 | { 517 | return $this->_authorization->isAllowed($this->_allowedKey); 518 | } 519 | 520 | /** 521 | * Retrieve model object 522 | * @return \Magento\Framework\Model\AbstractModel 523 | */ 524 | protected function _getModel($load = true) 525 | { 526 | if (is_null($this->_model)) { 527 | $this->_model = $this->_objectManager->create($this->_modelClass); 528 | 529 | $id = (int)$this->getRequest()->getParam($this->_idKey); 530 | $idFieldName = $this->_model->getResource()->getIdFieldName(); 531 | if (!$id && $this->_idKey !== $idFieldName) { 532 | $id = (int)$this->getRequest()->getParam($idFieldName); 533 | } 534 | 535 | if ($id && $load) { 536 | $this->_model->load($id); 537 | $this->_eventManager->dispatch('magefan_' . $this->getRequest()->getModuleName() . '_' . $this->getRequest()->getControllerName() . '_form_load_model_after', ['model' => $this->_model]); 538 | } 539 | } 540 | return $this->_model; 541 | } 542 | 543 | /** 544 | * @param array $filterRules 545 | * @param array $validatorRules 546 | * @param array|null $data 547 | * @return mixed 548 | */ 549 | protected function getFilterInput($filterRules, $validatorRules, $data) 550 | { 551 | if (class_exists('\Magento\Framework\Filter\FilterInput')) { 552 | $inputFilter = new \Magento\Framework\Filter\FilterInput( 553 | $filterRules, 554 | [], 555 | $data 556 | ); 557 | } else { 558 | $inputFilter = new \Zend_Filter_Input( 559 | $filterRules, 560 | [], 561 | $data 562 | ); 563 | } 564 | 565 | return $inputFilter; 566 | } 567 | } 568 | -------------------------------------------------------------------------------- /Controller/Adminhtml/Activate/Extension.php: -------------------------------------------------------------------------------- 1 | configWriter = $configWriter; 51 | $this->cacheTypeList = $cacheTypeList; 52 | $this->date = $date; 53 | parent::__construct($context); 54 | } 55 | 56 | /** 57 | * @return \Magento\Framework\App\ResponseInterface|\Magento\Framework\Controller\Result\Redirect|\Magento\Framework\Controller\ResultInterface 58 | * @throws NoSuchEntityException 59 | */ 60 | public function execute() 61 | { 62 | try { 63 | $activationKey = (string)$this->getRequest()->getParam('activation_key'); 64 | if (!$this->getRequest()->getParam('activation_key')) { 65 | throw new LocalizedException(__('Activation Key is missing. Please contact Magefan support.')); 66 | } 67 | 68 | $section = (string)$this->getRequest()->getParam('section'); 69 | if (!$section) { 70 | throw new LocalizedException(__('Section param is missing. Please contact Magefan support.')); 71 | } 72 | 73 | $urlInfo = parse_url($this->_url->getCurrentUrl()); 74 | $domain = isset($urlInfo['host']) ? $urlInfo['host'] : ''; 75 | 76 | $date = $this->date->gmtDate(); 77 | $key = sha1(date('y-m-d', strtotime($date)) . '_' . $section . '_' . $domain); 78 | if ($activationKey !== $key) { 79 | throw new LocalizedException(__('Invalid Activation Key. Please contact Magefan support.')); 80 | } 81 | 82 | $this->configWriter->save($section . '/g'.'e'.'n'.'e'.'r'.'a'.'l'.'/'.Section::ACTIVE, 1, ScopeConfigInterface::SCOPE_TYPE_DEFAULT, 0); 83 | $this->cacheTypeList->cleanType(Config::TYPE_IDENTIFIER); 84 | 85 | $this->messageManager->addSuccess(__('Thank you. Extension has been activated.')); 86 | return $this->resultRedirectFactory->create()->setUrl($this->_url->getUrl('adminhtml/system_config/edit', ['section' => $section])); 87 | } catch (LocalizedException $e) { 88 | $this->messageManager->addError($e->getMessage()); 89 | return $this->resultRedirectFactory->create()->setUrl($this->_url->getUrl('adminhtml')); 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /Cron/Sections.php: -------------------------------------------------------------------------------- 1 | resource = $resource; 53 | $this->sectionFactory = $sectionFactory; 54 | $this->info = $info; 55 | $this->setLinvFlag = $setLinvFlag; 56 | } 57 | 58 | /** 59 | * Execute cron job 60 | */ 61 | public function execute() 62 | { 63 | $connection = $this->resource->getConnection(); 64 | $table = $this->resource->getTableName('core_config_data'); 65 | $path = 'gen' . 'er' . 'al'. '/' . 'ena' . 'bled'; 66 | 67 | $select = $connection->select()->from( 68 | [$table] 69 | )->where( 70 | 'path LIKE ?', 71 | '%' . $path 72 | ); 73 | 74 | $sections = []; 75 | foreach ($connection->fetchAll($select) as $config) { 76 | $matches = false; 77 | preg_match("/(.*)\/" . str_replace('/', '\/', $path) . "/", $config['path'], $matches); 78 | if (empty($matches[1])) { 79 | continue; 80 | } 81 | $section = $this->sectionFactory->create([ 82 | 'name' => $matches[1] 83 | ]); 84 | 85 | if ($section->getModule()) { 86 | $sections[$section->getModule()] = $section; 87 | } else { 88 | unset($section); 89 | } 90 | } 91 | 92 | if (count($sections)) { 93 | $data = $this->info->load($sections); 94 | 95 | if ($data && is_array($data)) { 96 | foreach ($data as $module => $item) { 97 | $section = $sections[$module]; 98 | $moduleName = $section->getName(); 99 | if (!$section->validate($data)) { 100 | $connection->update( 101 | $table, 102 | [ 103 | 'value' => 0 104 | ], 105 | ['path = ? ' => $section->getName() . '/' . $path] 106 | ); 107 | $this->setLinvFlag->execute($moduleName, 1); 108 | } else { 109 | $this->setLinvFlag->execute($moduleName, 0); 110 | } 111 | } 112 | } 113 | } 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Please visit Magefan.com for license details (https://magefan.com/end-user-license-agreement) or email Magefan (support@magefan.com) to get a copy of license agreement. -------------------------------------------------------------------------------- /Model/AbstractThemeDetection.php: -------------------------------------------------------------------------------- 1 | moduleManager = $moduleManager; 59 | $this->storeManager = $storeManager; 60 | $this->scopeConfig = $scopeConfig; 61 | $this->themeProvider = $themeProvider; 62 | } 63 | 64 | /** 65 | * @return string 66 | */ 67 | abstract public function getThemeModuleName(): string; 68 | 69 | /** 70 | * @return string 71 | */ 72 | abstract public function getThemeName():string; 73 | 74 | /** 75 | * @param $storeId 76 | * @return bool 77 | * @throws NoSuchEntityException 78 | */ 79 | public function execute($storeId = null): bool 80 | { 81 | $key = 'store_' . $storeId; 82 | if (isset($this->result[$key])) { 83 | return $this->result[$key]; 84 | } 85 | 86 | $themeEnabled = $this->moduleManager->isEnabled($this->getThemeModuleName()); 87 | 88 | if ($themeEnabled) { 89 | 90 | if (null === $storeId) { 91 | $storeId = $this->storeManager->getStore()->getId(); 92 | } 93 | 94 | if (!$storeId){ 95 | $stores = $this->storeManager->getStores(); 96 | foreach ($stores as $store) { 97 | if ($this->isThemeInUse($store->getId())) { 98 | $this->result[$key] = true; 99 | return $this->result[$key]; 100 | } 101 | } 102 | } else { 103 | $this->result[$key] = $this->isThemeInUse($storeId); 104 | return $this->result[$key]; 105 | } 106 | } 107 | $this->result[$key] = false; 108 | return $this->result[$key]; 109 | } 110 | 111 | /** 112 | * @param $storeId 113 | * @return bool 114 | */ 115 | private function isThemeInUse($storeId) 116 | { 117 | $themeId = $this->scopeConfig->getValue( 118 | \Magento\Framework\View\DesignInterface::XML_PATH_THEME_ID, 119 | \Magento\Store\Model\ScopeInterface::SCOPE_STORE, 120 | $storeId 121 | ); 122 | if ($themeId) { 123 | try { 124 | $theme = $this->themeProvider->getThemeById($themeId); 125 | } catch (\Exception $e) { 126 | $theme = false; 127 | } 128 | 129 | while ($theme) { 130 | $themePath = $theme->getThemePath(); 131 | if (false !== stripos($themePath, $this->getThemeName())) { 132 | return true; 133 | } 134 | 135 | $theme = $theme->getParentTheme(); 136 | } 137 | } 138 | return false; 139 | } 140 | } -------------------------------------------------------------------------------- /Model/AdminNotificationFeed.php: -------------------------------------------------------------------------------- 1 | curlFactory = $curlFactory; 123 | $this->_deploymentConfig = $deploymentConfig; 124 | $this->productMetadata = $productMetadata; 125 | $this->urlBuilder = $urlBuilder; 126 | 127 | $this->_backendAuthSession = $backendAuthSession; 128 | $this->_moduleList = $moduleList; 129 | $this->_moduleManager = $moduleManager; 130 | $this->config = $config; 131 | $this->getModuleVersion = $getModuleVersion; 132 | } 133 | 134 | /** 135 | * Init model 136 | * 137 | * @return void 138 | * phpcs:disable Magento2.CodeAnalysis.EmptyBlock 139 | */ 140 | protected function _construct() 141 | { 142 | } 143 | 144 | /** 145 | * Retrieve feed url 146 | * 147 | * @return string 148 | */ 149 | public function getFeedUrl() 150 | { 151 | if (is_null($this->_feedUrl)) { 152 | $this->_feedUrl = 'https://mage'.'fan' 153 | .'.c'.'om/community/notifications'.'/'.'feed/'; 154 | } 155 | $urlInfo = parse_url($this->urlBuilder->getBaseUrl()); 156 | $domain = isset($urlInfo['host']) ? $urlInfo['host'] : ''; 157 | $url = $this->_feedUrl . 'domain/' . urlencode($domain); 158 | $modulesParams = []; 159 | foreach ($this->getMagefanModules() as $moduleName => $module) { 160 | $key = str_replace('Magefan_', '', $moduleName); 161 | $modulesParams[] = $key . ',' . $this->getModuleVersion->execute($moduleName); 162 | } 163 | if (count($modulesParams)) { 164 | $url .= '/modules/'.base64_encode(implode(';', $modulesParams)); 165 | } 166 | 167 | $receiveNotifications = $this->config->receiveNotifications(); 168 | $notificationsParams = []; 169 | foreach ($receiveNotifications as $notification => $notificationStatus) { 170 | $notificationsParams[] = $notification . ',' . $notificationStatus; 171 | } 172 | 173 | if (count($notificationsParams)) { 174 | $url .= '/notifications/' . base64_encode(implode(';', $notificationsParams)); 175 | } 176 | return $url; 177 | } 178 | 179 | /** 180 | * Retrieve Magefan modules info 181 | * 182 | * @return $this 183 | */ 184 | protected function getMagefanModules() 185 | { 186 | $modules = []; 187 | foreach ($this->_moduleList->getAll() as $moduleName => $module) { 188 | if (strpos($moduleName, 'Magefan_') !== false && $this->_moduleManager->isEnabled($moduleName)) { 189 | $modules[$moduleName] = $module; 190 | } 191 | } 192 | return $modules; 193 | } 194 | 195 | /** 196 | * Check feed for modification 197 | * 198 | * @return $this 199 | */ 200 | public function checkUpdate() 201 | { 202 | $session = $this->_backendAuthSession; 203 | $time = time(); 204 | $frequency = $this->getFrequency(); 205 | if (($frequency + $session->getMfNoticeLastUpdate() > $time) 206 | || ($frequency + $this->getLastUpdate() > $time) 207 | ) { 208 | return $this; 209 | } 210 | $session->setMfNoticeLastUpdate($time); 211 | 212 | if ($this->_moduleManager->isEnabled('Magento_AdminNotification')) { 213 | return $this->parentCheckUpdate(); 214 | } else { 215 | return $this; 216 | } 217 | } 218 | 219 | /** 220 | * Check feed for modification 221 | * 222 | * @return $this 223 | */ 224 | protected function parentCheckUpdate() 225 | { 226 | if ($this->getFrequency() + $this->getLastUpdate() > time()) { 227 | return $this; 228 | } 229 | 230 | $feedData = []; 231 | 232 | $feedXml = $this->getFeedData(); 233 | 234 | $installDate = strtotime($this->_deploymentConfig->get(ConfigOptionsListConstants::CONFIG_PATH_INSTALL_DATE)); 235 | 236 | if ($feedXml && $feedXml->channel && $feedXml->channel->item) { 237 | foreach ($feedXml->channel->item as $item) { 238 | $itemPublicationDate = strtotime((string)$item->pubDate); 239 | if ($installDate <= $itemPublicationDate) { 240 | $feedData[] = [ 241 | 'severity' => (int)$item->severity, 242 | 'date_added' => date('Y-m-d H:i:s', $itemPublicationDate), 243 | 'title' => $this->escapeString($item->title), 244 | 'description' => $this->escapeString($item->description), 245 | 'url' => $this->escapeString($item->link), 246 | ]; 247 | } 248 | } 249 | 250 | if ($feedData) { 251 | $this->getInboxFactory()->create()->parse(array_reverse($feedData)); 252 | } 253 | } 254 | $this->setLastUpdate(); 255 | 256 | return $this; 257 | } 258 | 259 | /** 260 | * Retrieve update аrequency 261 | * 262 | * @return int 263 | */ 264 | public function getFrequency() 265 | { 266 | return 86400 * 2; 267 | } 268 | 269 | /** 270 | * Retrieve Last update time 271 | * 272 | * @return int 273 | */ 274 | public function getLastUpdate() 275 | { 276 | return $this->_cacheManager->load(self::MAGEFAN_CACHE_KEY); 277 | } 278 | 279 | /** 280 | * Set last update time (now) 281 | * 282 | * @return $this 283 | */ 284 | public function setLastUpdate() 285 | { 286 | $this->_cacheManager->save(time(), self::MAGEFAN_CACHE_KEY); 287 | return $this; 288 | } 289 | 290 | /** 291 | * Retrieve feed data as XML element 292 | * 293 | * @return SimpleXMLElement 294 | */ 295 | public function getFeedData() 296 | { 297 | $getNotification = false; 298 | foreach ($this->config->receiveNotifications() as $key => $value) { 299 | if ($value) { 300 | $getNotification = true; 301 | break; 302 | } 303 | } 304 | 305 | if (!$getNotification) { 306 | return new SimpleXMLElement(' 307 | 308 | 309 | '); 310 | } 311 | 312 | return $this->parentGetFeedData(); 313 | } 314 | 315 | /** 316 | * Retrieve feed data as XML element 317 | * 318 | * @return SimpleXMLElement 319 | */ 320 | protected function parentGetFeedData() 321 | { 322 | /** @var Curl $curl */ 323 | $curl = $this->curlFactory->create(); 324 | $curl->setOptions( 325 | [ 326 | 'timeout' => 2, 327 | 'useragent' => $this->productMetadata->getName() 328 | . '/' . $this->productMetadata->getVersion() 329 | . ' (' . $this->productMetadata->getEdition() . ')', 330 | 'referer' => $this->urlBuilder->getUrl('*/*/*') 331 | ] 332 | ); 333 | $curl->write('GET', $this->getFeedUrl(), '1.0'); 334 | $data = $curl->read(); 335 | $data = preg_split('/^\r?$/m', $data, 2); 336 | $data = trim($data[1] ?? ''); 337 | $curl->close(); 338 | 339 | try { 340 | $xml = new SimpleXMLElement($data); 341 | } catch (\Exception $e) { 342 | return false; 343 | } 344 | 345 | return $xml; 346 | } 347 | /** 348 | * Retrieve feed as XML element 349 | * 350 | * @return SimpleXMLElement 351 | */ 352 | public function getFeedXml() 353 | { 354 | try { 355 | $data = $this->getFeedData(); 356 | $xml = new SimpleXMLElement($data); 357 | } catch (\Exception $e) { 358 | $xml = new SimpleXMLElement(''); 359 | } 360 | 361 | return $xml; 362 | } 363 | 364 | /** 365 | * Converts incoming data to string format and escapes special characters. 366 | * 367 | * @param SimpleXMLElement $data 368 | * @return string 369 | */ 370 | private function escapeString(SimpleXMLElement $data) 371 | { 372 | return htmlspecialchars((string)$data); 373 | } 374 | 375 | /** 376 | * @return mixed 377 | */ 378 | private function getInboxFactory() 379 | { 380 | if (null === $this->_inboxFactory) { 381 | $this->_inboxFactory = \Magento\Framework\App\ObjectManager::getInstance() 382 | ->get(\Magento\AdminNotification\Model\InboxFactory::class); 383 | } 384 | 385 | return $this->_inboxFactory; 386 | } 387 | } 388 | -------------------------------------------------------------------------------- /Model/BreezeThemeDetection.php: -------------------------------------------------------------------------------- 1 | scopeConfig = $scopeConfig; 41 | } 42 | 43 | /** 44 | * Receive Product Updates 45 | * 46 | * @param null $storeId 47 | * @return string 48 | */ 49 | public function receiveProductUpdates($storeId = null) 50 | { 51 | return $this->getConfig( 52 | self::XML_PATH_RECEIVE_PRODUCT_UPDATES, 53 | $storeId 54 | ); 55 | } 56 | 57 | /** 58 | * Receive Special Offers 59 | * 60 | * @param null $storeId 61 | * @return string 62 | */ 63 | public function receiveSpecialOffers($storeId = null) 64 | { 65 | return $this->getConfig( 66 | self::XML_PATH_RECEIVE_SPECIAL_OFFERS, 67 | $storeId 68 | ); 69 | } 70 | 71 | /** 72 | * Receive News 73 | * 74 | * @param null $storeId 75 | * @return string 76 | */ 77 | public function receiveNews($storeId = null) 78 | { 79 | return $this->getConfig( 80 | self::XML_PATH_RECEIVE_NEWS, 81 | $storeId 82 | ); 83 | } 84 | 85 | /** 86 | * Receive Tips & Tricks 87 | * 88 | * @param null $storeId 89 | * @return string 90 | */ 91 | public function receiveTipsAndTricks($storeId = null) 92 | { 93 | return $this->getConfig( 94 | self::XML_PATH_RECEIVE_TIPS_AND_TRICKS, 95 | $storeId 96 | ); 97 | } 98 | 99 | /** 100 | * Receive General Information 101 | * 102 | * @param null $storeId 103 | * @return string 104 | */ 105 | public function receiveGeneralInformation($storeId = null) 106 | { 107 | return $this->getConfig( 108 | self::XML_PATH_RECEIVE_GENERAL_INFORMATION, 109 | $storeId 110 | ); 111 | } 112 | 113 | /** 114 | * Receive Notifications 115 | * 116 | * @param null $storeId 117 | * @return array 118 | */ 119 | public function receiveNotifications($storeId = null) 120 | { 121 | return [ 122 | 'update' => $this->receiveProductUpdates(), 123 | 'offer' => $this->receiveSpecialOffers(), 124 | 'news' => $this->receiveNews(), 125 | 'tip_trick' => $this->receiveTipsAndTricks(), 126 | 'general' => $this->receiveGeneralInformation() 127 | ]; 128 | } 129 | 130 | /** 131 | * Display Menu 132 | * 133 | * @param null $storeId 134 | * @return string 135 | */ 136 | public function menuEnabled($storeId = null) 137 | { 138 | return $this->getConfig( 139 | self::XML_PATH_MENU_ENABLED, 140 | $storeId 141 | ); 142 | } 143 | 144 | /** 145 | * Retrieve store config value 146 | * 147 | * @param string $path 148 | * @param null $storeId 149 | * @return mixed 150 | */ 151 | public function getConfig($path, $storeId = null) 152 | { 153 | return $this->scopeConfig->getValue( 154 | $path, 155 | ScopeInterface::SCOPE_STORE, 156 | $storeId 157 | ); 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /Model/GetCategoryByProduct.php: -------------------------------------------------------------------------------- 1 | categoryRepository = $categoryRepository; 42 | $this->storeManager = $storeManager; 43 | } 44 | 45 | /** 46 | * @param mixed $product 47 | * @param mixed $storeId 48 | * @returnmixed 49 | */ 50 | public function execute($product, $storeId = null) 51 | { 52 | if (null === $storeId) { 53 | $storeId = $this->storeManager->getStore()->getId(); 54 | } 55 | 56 | $key = $product->getId() . '_' . $storeId; 57 | if (!isset($this->productCategory[$key])) { 58 | 59 | $this->productCategory[$key] = false; 60 | 61 | $categoryIds = $product->getCategoryIds(); 62 | if ($categoryIds) { 63 | $level = -1; 64 | $rootCategoryId = $this->storeManager->getStore($storeId)->getRootCategoryId(); 65 | 66 | foreach ($categoryIds as $categoryId) { 67 | try { 68 | $category = $this->categoryRepository->get($categoryId, $storeId); 69 | if ($category->getIsActive() 70 | && $category->getLevel() > $level 71 | && in_array($rootCategoryId, $category->getPathIds()) 72 | ) { 73 | $level = $category->getLevel(); 74 | $this->productCategory[$key] = $category; 75 | } 76 | } catch (\Exception $e) { // phpcs:ignore 77 | /* Do nothing */ 78 | } 79 | } 80 | } 81 | } 82 | 83 | 84 | return $this->productCategory[$key] ?: null; 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /Model/GetModuleInfo.php: -------------------------------------------------------------------------------- 1 | curl = $curl; 45 | $this->cache = $cache; 46 | } 47 | 48 | /** 49 | * @param $moduleName 50 | * @return array|DataObject|mixed 51 | */ 52 | public function execute($moduleName = null) 53 | { 54 | $modulesInfo = $this->getModulesInfo(); 55 | 56 | if (!$moduleName) { 57 | return $modulesInfo; 58 | } 59 | 60 | $moduleKey = explode('_', $moduleName)[1]; 61 | 62 | if (!isset($modulesInfo[$moduleKey])) { 63 | $modulesInfo[$moduleKey] = new DataObject(); 64 | } 65 | return $modulesInfo[$moduleKey]; 66 | } 67 | 68 | /** 69 | * @return array 70 | */ 71 | public function getModulesInfo() 72 | { 73 | if (null === $this->modulesInfo) { 74 | $modulesInfo = $this->load(); 75 | if (!$modulesInfo) { 76 | $modulesInfo = $this->loadFromCache(); 77 | } 78 | 79 | foreach ($modulesInfo as $moduleKey => $moduleInfo) { 80 | $modulesInfo[$moduleKey] = new DataObject($moduleInfo); 81 | } 82 | 83 | $this->modulesInfo = $modulesInfo; 84 | } 85 | 86 | return $this->modulesInfo; 87 | } 88 | 89 | /** 90 | * @return array 91 | */ 92 | private function load(): array 93 | { 94 | $data = []; 95 | try { 96 | $url = 'https://mage' . 'fan.com/media/product-versions-extended.json'; 97 | 98 | // Make the request 99 | $this->curl->get($url); 100 | 101 | // Get the response 102 | $response = $this->curl->getBody(); 103 | 104 | if ($response) { 105 | $this->cache->save($response, self::CACHE_KEY, [], self::CACHE_LIFETIME); 106 | $data = json_decode($response, true); 107 | } 108 | } catch (\Exception $e) { 109 | 110 | } 111 | 112 | return $data; 113 | } 114 | 115 | /** 116 | * @return array 117 | */ 118 | private function loadFromCache(): array 119 | { 120 | $cachedData = $this->cache->load(self::CACHE_KEY); 121 | if ($cachedData) { 122 | return json_decode($cachedData, true); 123 | } 124 | 125 | return []; 126 | } 127 | } -------------------------------------------------------------------------------- /Model/GetModuleVersion.php: -------------------------------------------------------------------------------- 1 | serializer = $serializer; 58 | $this->file = $file; 59 | $this->moduleReader = $moduleReader; 60 | $this->moduleList = $moduleList; 61 | } 62 | 63 | /** 64 | * @param string $moduleName 65 | * @return string 66 | */ 67 | public function execute(string $moduleName): string 68 | { 69 | if (!isset($this->versions[$moduleName])) { 70 | $module = $this->moduleList->getOne($moduleName); 71 | if (!$module) { 72 | $this->versions[$moduleName] = ''; 73 | } else { 74 | $fileDir = $this->moduleReader->getModuleDir('', $moduleName) . '/composer.json'; 75 | $data = $this->file->read($fileDir); 76 | if ($data) { 77 | try { 78 | $data = $this->serializer->unserialize($data); 79 | } catch (\Exception $e) { 80 | $data = []; 81 | } 82 | if (empty($data['version'])) { 83 | return !empty($module['setup_version']) ? $module['setup_version'] : ''; 84 | } 85 | } 86 | 87 | $this->versions[$moduleName] = !empty($data['version']) ? $data['version'] : ''; 88 | } 89 | } 90 | 91 | return $this->versions[$moduleName]; 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /Model/GetParentProductIds.php: -------------------------------------------------------------------------------- 1 | resourceConnection = $resourceConnection; 36 | $this->connection = $resourceConnection->getConnection(); 37 | } 38 | 39 | /** 40 | * @param array $productIds 41 | * @return array 42 | */ 43 | public function execute(array $productIds): array 44 | { 45 | $parentProductIds = []; 46 | 47 | /* Fix for configurable, bundle, grouped */ 48 | if ($productIds) { 49 | $productTable = $this->resourceConnection->getTableName('catalog_product_entity'); 50 | $entityIdColumn = $this->connection->tableColumnExists($productTable, 'row_id') ? 'row_id' : 'entity_id'; 51 | 52 | $select = $this->connection->select() 53 | ->from( 54 | ['main_table' => $this->resourceConnection->getTableName('catalog_product_relation')], 55 | [] 56 | )->join( 57 | ['e' => $productTable], 58 | 'e.' . $entityIdColumn . ' = main_table.parent_id', 59 | ['e.' .$entityIdColumn] 60 | ) 61 | ->where('main_table.child_id IN (?)', $productIds) 62 | ->where('e.entity_id IS NOT NULL'); 63 | 64 | foreach ($this->connection->fetchAll($select) as $product) { 65 | $parentProductIds[$product[$entityIdColumn]] = $product[$entityIdColumn]; 66 | } 67 | } 68 | /* End fix */ 69 | 70 | return $parentProductIds; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /Model/GetWebsitesMap.php: -------------------------------------------------------------------------------- 1 | storeManager = $storeManager; 35 | } 36 | 37 | /** 38 | * @return array 39 | */ 40 | public function execute(): array 41 | { 42 | if ($this->websitesMap === null) { 43 | $this->websitesMap = []; 44 | $websites = $this->storeManager->getWebsites(); 45 | foreach ($websites as $website) { 46 | // Continue if website has no store to be able to create catalog rule for website without store 47 | if ($website->getDefaultStore() === null) { 48 | continue; 49 | } 50 | $this->websitesMap[$website->getId()] = $website->getDefaultStore()->getId(); 51 | } 52 | } 53 | 54 | return $this->websitesMap; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /Model/HyvaThemeDetection.php: -------------------------------------------------------------------------------- 1 | templateFactory = $templateFactory; 165 | $this->objectManager = $objectManager; 166 | $this->_senderResolver = $senderResolver; 167 | $this->mailTransportFactory = $mailTransportFactory; 168 | $this->productMetadata = $productMetadata; 169 | $this->emailMessageInterfaceFactory = $emailMessageInterfaceFactory ?: $this->objectManager 170 | ->get(EmailMessageInterfaceFactory::class); 171 | $this->mimeMessageInterfaceFactory = $mimeMessageInterfaceFactory ?: $this->objectManager 172 | ->get(MimeMessageInterfaceFactory::class); 173 | $this->mimePartInterfaceFactory = $mimePartInterfaceFactory ?: $this->objectManager 174 | ->get(MimePartInterfaceFactory::class); 175 | $this->addressConverter = $addressConverter ?: $this->objectManager 176 | ->get(AddressConverter::class); 177 | } 178 | 179 | /** 180 | * Add cc address 181 | * 182 | * @param array|string $address 183 | * @param string $name 184 | * 185 | * @return $this 186 | */ 187 | public function addCc($address, $name = '') 188 | { 189 | $this->addAddressByType('cc', $address, $name); 190 | 191 | return $this; 192 | } 193 | 194 | /** 195 | * Add to address 196 | * 197 | * @param array|string $address 198 | * @param string $name 199 | * 200 | * @return $this 201 | * @throws InvalidArgumentException 202 | */ 203 | public function addTo($address, $name = '') 204 | { 205 | $this->addAddressByType('to', $address, $name); 206 | 207 | return $this; 208 | } 209 | 210 | /** 211 | * Add bcc address 212 | * 213 | * @param array|string $address 214 | * 215 | * @return $this 216 | * @throws InvalidArgumentException 217 | */ 218 | public function addBcc($address) 219 | { 220 | $this->addAddressByType('bcc', $address); 221 | 222 | return $this; 223 | } 224 | 225 | /** 226 | * Set Reply-To Header 227 | * 228 | * @param string $email 229 | * @param string|null $name 230 | * 231 | * @return $this 232 | * @throws InvalidArgumentException 233 | */ 234 | public function setReplyTo($email, $name = null) 235 | { 236 | $this->addAddressByType('replyTo', $email, $name); 237 | 238 | return $this; 239 | } 240 | 241 | /** 242 | * Set mail from address 243 | * 244 | * @param string|array $from 245 | * 246 | * @return $this 247 | * @throws InvalidArgumentException 248 | * @see setFromByScope() 249 | * 250 | * @deprecated 102.0.1 This function sets the from address but does not provide 251 | * a way of setting the correct from addresses based on the scope. 252 | */ 253 | public function setFrom($from) 254 | { 255 | return $this->setFromByScope($from); 256 | } 257 | 258 | /** 259 | * Set mail from address by scopeId 260 | * 261 | * @param string|array $from 262 | * @param string|int $scopeId 263 | * 264 | * @return $this 265 | * @throws InvalidArgumentException 266 | * @throws MailException 267 | * @since 102.0.1 268 | */ 269 | public function setFromByScope($from, $scopeId = null) 270 | { 271 | $result = $this->_senderResolver->resolve($from, $scopeId); 272 | $this->addAddressByType('from', $result['email'], $result['name']); 273 | 274 | return $this; 275 | } 276 | 277 | /** 278 | * Set template identifier 279 | * 280 | * @param string $templateIdentifier 281 | * 282 | * @return $this 283 | */ 284 | public function setTemplateIdentifier($templateIdentifier) 285 | { 286 | $this->templateIdentifier = $templateIdentifier; 287 | 288 | return $this; 289 | } 290 | 291 | /** 292 | * Set template model 293 | * 294 | * @param string $templateModel 295 | * 296 | * @return $this 297 | */ 298 | public function setTemplateModel($templateModel) 299 | { 300 | $this->templateModel = $templateModel; 301 | return $this; 302 | } 303 | 304 | /** 305 | * Set template vars 306 | * 307 | * @param array $templateVars 308 | * 309 | * @return $this 310 | */ 311 | public function setTemplateVars($templateVars) 312 | { 313 | $this->templateVars = $templateVars; 314 | 315 | return $this; 316 | } 317 | 318 | /** 319 | * Set template options 320 | * 321 | * @param array $templateOptions 322 | * @return $this 323 | */ 324 | public function setTemplateOptions($templateOptions) 325 | { 326 | $this->templateOptions = $templateOptions; 327 | 328 | return $this; 329 | } 330 | 331 | /** 332 | * Get mail transport 333 | * 334 | * @return TransportInterface 335 | * @throws LocalizedException 336 | */ 337 | public function getTransport() 338 | { 339 | try { 340 | $this->prepareMessage(); 341 | $mailTransport = $this->mailTransportFactory->create(['message' => clone $this->message]); 342 | } finally { 343 | $this->reset(); 344 | } 345 | 346 | return $mailTransport; 347 | } 348 | 349 | /** 350 | * @param string $content 351 | * @param string $name 352 | * @param string $type 353 | * @param $encoding 354 | * @param $disposition 355 | * @return $this 356 | */ 357 | public function addAttachment(string $content, string $name, string $type, $encoding = null, $disposition = null) 358 | { 359 | $this->attachments[] = [ 360 | 'content' => $content, 361 | 'name' => $name, 362 | 'type' => $type, 363 | 'encoding' => $encoding, 364 | 'disposition' => $disposition 365 | ]; 366 | 367 | return $this; 368 | } 369 | 370 | /** 371 | * @return array 372 | */ 373 | private function getLaminasParts() 374 | { 375 | $parts = []; 376 | 377 | foreach ($this->attachments as $attachmentData) { 378 | 379 | $attachment = new \Laminas\Mime\Part($attachmentData['content']); 380 | $attachment->filename = $attachmentData['name']; 381 | $attachment->type = $attachmentData['type']; 382 | $attachment->encoding = $attachmentData['encoding'] ?: \Laminas\Mime\Mime::ENCODING_BASE64; 383 | $attachment->disposition = $attachmentData['disposition'] ?: \Laminas\Mime\Mime::DISPOSITION_ATTACHMENT; 384 | 385 | $parts[] = $attachment; 386 | } 387 | 388 | return $parts; 389 | } 390 | 391 | /** 392 | * @return array 393 | */ 394 | private function getSymfonyParts() 395 | { 396 | $parts = []; 397 | 398 | foreach ($this->attachments as $attachmentData) { 399 | $attachment = new \Symfony\Component\Mime\Part\DataPart($attachmentData['content'], $attachmentData['name'], $attachmentData['type']); 400 | 401 | $parts[] = $attachment; 402 | } 403 | 404 | return $parts; 405 | } 406 | 407 | /** 408 | * Reset object state 409 | * 410 | * @return $this 411 | */ 412 | protected function reset() 413 | { 414 | $this->messageData = []; 415 | $this->templateIdentifier = null; 416 | $this->templateVars = null; 417 | $this->templateOptions = null; 418 | return $this; 419 | } 420 | 421 | /** 422 | * Get template 423 | * 424 | * @return TemplateInterface 425 | */ 426 | protected function getTemplate() 427 | { 428 | return $this->templateFactory->get($this->templateIdentifier, $this->templateModel) 429 | ->setVars($this->templateVars) 430 | ->setOptions($this->templateOptions); 431 | } 432 | 433 | /** 434 | * Prepare message. 435 | * 436 | * @return $this 437 | * @throws LocalizedException if template type is unknown 438 | */ 439 | protected function prepareMessage() 440 | { 441 | $template = $this->getTemplate(); 442 | $content = $template->processTemplate(); 443 | 444 | switch ($template->getType()) { 445 | case TemplateTypesInterface::TYPE_TEXT: 446 | $part['type'] = MimeInterface::TYPE_TEXT; 447 | break; 448 | 449 | case TemplateTypesInterface::TYPE_HTML: 450 | $part['type'] = MimeInterface::TYPE_HTML; 451 | break; 452 | 453 | default: 454 | throw new LocalizedException( 455 | new Phrase('Unknown template type') 456 | ); 457 | } 458 | 459 | /** @var \Magento\Framework\Mail\MimePartInterface $mimePart */ 460 | $mimePart = $this->mimePartInterfaceFactory->create(['content' => $content]); 461 | /* Custom code */ 462 | $parts = [$mimePart]; 463 | 464 | $attachmentParts = $this->isMagentoVersionLte248() 465 | ? $this->getLaminasParts() 466 | : $this->getSymfonyParts(); 467 | 468 | $parts = array_merge($parts, $attachmentParts); 469 | /* End custom code */ 470 | 471 | $this->messageData['encoding'] = $mimePart->getCharset(); 472 | $this->messageData['body'] = $this->mimeMessageInterfaceFactory->create( 473 | ['parts' => $parts] 474 | ); 475 | 476 | $this->messageData['subject'] = html_entity_decode( 477 | (string)$template->getSubject(), 478 | ENT_QUOTES 479 | ); 480 | 481 | $this->message = $this->emailMessageInterfaceFactory->create($this->messageData); 482 | 483 | $this->addSymfonyAttachment($attachmentParts); 484 | 485 | return $this; 486 | } 487 | 488 | /** 489 | * @param array $attachmentParts 490 | * @return void 491 | */ 492 | private function addSymfonyAttachment(array $attachmentParts): void 493 | { 494 | if ($this->message instanceof \Magento\Framework\Mail\EmailMessage && method_exists($this->message, 'getSymfonyMessage')) { 495 | $symfonyEmail = $this->message->getSymfonyMessage(); 496 | 497 | // Decode the original HTML body (quoted-printable) 498 | $decodedBody = quoted_printable_decode($symfonyEmail->getBody()->bodyToString()); 499 | 500 | // Create main HTML body part 501 | $htmlPart = new \Symfony\Component\Mime\Part\TextPart($decodedBody, 'utf-8', 'html'); 502 | 503 | // Rebuild the email body with attachments 504 | $symfonyEmail->setBody( 505 | new \Symfony\Component\Mime\Part\Multipart\MixedPart($htmlPart, ...$attachmentParts) 506 | ); 507 | } 508 | } 509 | 510 | /** 511 | * Handles possible incoming types of email (string or array) 512 | * 513 | * @param string $addressType 514 | * @param string|array $email 515 | * @param string|null $name 516 | * 517 | * @return void 518 | * @throws InvalidArgumentException 519 | */ 520 | private function addAddressByType(string $addressType, $email, ?string $name = null): void 521 | { 522 | if (is_string($email)) { 523 | $this->messageData[$addressType][] = $this->addressConverter->convert($email, $name); 524 | return; 525 | } 526 | $convertedAddressArray = $this->addressConverter->convertMany($email); 527 | if (isset($this->messageData[$addressType])) { 528 | $this->messageData[$addressType] = array_merge( 529 | $this->messageData[$addressType], 530 | $convertedAddressArray 531 | ); 532 | } else { 533 | $this->messageData[$addressType] = $convertedAddressArray; 534 | } 535 | } 536 | 537 | /** 538 | * @return bool 539 | */ 540 | private function isMagentoVersionLte248(): bool 541 | { 542 | $version = $this->productMetadata->getVersion(); 543 | return version_compare($version, '2.4.8', '<'); 544 | } 545 | } 546 | -------------------------------------------------------------------------------- /Model/Magento/Product/CollectionOptimizedForSqlValidator.php: -------------------------------------------------------------------------------- 1 | '; 22 | const IS_OPERATOR = '=='; 23 | 24 | /** 25 | * @var array 26 | */ 27 | private $stringConditionOperatorMap = [ 28 | '{}' => ':field LIKE ?', 29 | '!{}' => ':field NOT LIKE ?', 30 | ]; 31 | 32 | /** 33 | * @var AbstractCollection|null 34 | */ 35 | private $collectionCopy; 36 | 37 | /** 38 | * @var AttributeRepositoryInterface 39 | */ 40 | private $attributeRepository; 41 | 42 | public function __construct( 43 | ExpressionFactory $expressionFactory, 44 | AttributeRepositoryInterface $attributeRepository 45 | ) { 46 | parent::__construct($expressionFactory, $attributeRepository); 47 | $this->attributeRepository = $attributeRepository; 48 | } 49 | 50 | public function attachConditionToCollection( 51 | AbstractCollection $collection, 52 | Combine $combine 53 | ): void { 54 | $this->collectionCopy = $collection; 55 | parent::attachConditionToCollection($collection, $combine); 56 | $this->collectionCopy = null; 57 | } 58 | 59 | /** 60 | * Fixed issue with filter by default attribute value 61 | * 62 | * @param AbstractCondition $condition 63 | * @param string $value 64 | * @param bool $isDefaultStoreUsed 65 | * @return string 66 | * @throws \Magento\Framework\Exception\LocalizedException 67 | */ 68 | protected function _getMappedSqlCondition( 69 | AbstractCondition $condition, 70 | string $value = '', 71 | bool $isDefaultStoreUsed = true 72 | ): string { 73 | $argument = $condition->getMappedSqlField(); 74 | 75 | // If rule hasn't valid argument - prevent incorrect rule behavior. 76 | if (empty($argument)) { 77 | return (string) $this->_expressionFactory->create(['expression' => '1 = -1']); 78 | } elseif (preg_match('/[^a-z0-9\-_\.\`]/i', $argument) > 0 && !$argument instanceof \Zend_Db_Expr) { 79 | throw new \Magento\Framework\Exception\LocalizedException(__('Invalid field')); 80 | } 81 | 82 | if (self::UNDEFINED_OPERATOR === $condition->getOperatorForValidate()) { 83 | $condition->setOperator(self::IS_OPERATOR); 84 | $condition->setValue(''); 85 | } 86 | 87 | $conditionOperator = $condition->getOperatorForValidate(); 88 | 89 | if (!isset($this->_conditionOperatorMap[$conditionOperator])) { 90 | throw new \Magento\Framework\Exception\LocalizedException(__('Unknown condition operator')); 91 | } 92 | 93 | $defaultValue = $this->getDefaultValue($this->collectionCopy, $condition->getAttribute()); 94 | 95 | //operator 'contains {}' is mapped to 'IN()' query that cannot work with substrings 96 | // adding mapping to 'LIKE %%' 97 | if ($condition->getInputType() === 'string' 98 | && array_key_exists($conditionOperator, $this->stringConditionOperatorMap) 99 | ) { 100 | $sql = str_replace( 101 | ':field', 102 | (string) $this->_connection->getIfNullSql( 103 | $this->_connection->quoteIdentifier($argument), 104 | $defaultValue 105 | ), 106 | $this->stringConditionOperatorMap[$conditionOperator] 107 | ); 108 | $bindValue = $condition->getBindArgumentValue(); 109 | $expression = $value . $this->_connection->quoteInto($sql, "%$bindValue%"); 110 | } else { 111 | $sql = str_replace( 112 | ':field', 113 | (string) $this->_connection->getIfNullSql( 114 | $this->_connection->quoteIdentifier($argument), 115 | $defaultValue 116 | ), 117 | $this->_conditionOperatorMap[$conditionOperator] 118 | ); 119 | $bindValue = $condition->getBindArgumentValue(); 120 | $expression = $value . $this->_connection->quoteInto($sql, $bindValue); 121 | } 122 | // values for multiselect attributes can be saved in comma-separated format 123 | // below is a solution for matching such conditions with selected values 124 | if (is_array($bindValue) && \in_array($conditionOperator, ['()', '{}'], true)) { 125 | foreach ($bindValue as $item) { 126 | $ifNullSqlQuoteIdentifier = (string) $this->_connection->getIfNullSql( 127 | $this->_connection->quoteIdentifier($argument), 128 | $defaultValue 129 | ); 130 | 131 | $expression .= ($e = $this->_connection->quoteInto( 132 | " OR (FIND_IN_SET (?, {$ifNullSqlQuoteIdentifier}) > 0)", 133 | $item 134 | )); 135 | } 136 | } elseif (is_array($bindValue) && \in_array($conditionOperator, ['!()', '!{}'], true)) { 137 | foreach ($bindValue as $item) { 138 | $ifNullSqlQuoteIdentifier = (string) $this->_connection->getIfNullSql( 139 | $this->_connection->quoteIdentifier($argument), 140 | $defaultValue 141 | ); 142 | 143 | $expression .= ($e = $this->_connection->quoteInto( 144 | " AND (FIND_IN_SET (?, {$ifNullSqlQuoteIdentifier}) = 0)", 145 | $item 146 | )); 147 | } 148 | } 149 | 150 | return (string) $this->_expressionFactory->create( 151 | ['expression' => $expression] 152 | ); 153 | } 154 | 155 | /** 156 | * @param AbstractCollection $collection 157 | * @param string $attributeCode 158 | * @return int|string 159 | */ 160 | private function getDefaultValue(AbstractCollection $collection, string $attributeCode) 161 | { 162 | $isDefaultStoreUsed = (int) $collection->getStoreId() === (int) $collection->getDefaultStoreId(); 163 | 164 | $defaultValue = 0; 165 | 166 | if (!$isDefaultStoreUsed && $this->isDefaultValueAvaibleForAttribute($attributeCode)) { 167 | $defaultField = 'at_' . $attributeCode . '_default.value'; 168 | $defaultValue = $this->_connection->quoteIdentifier($defaultField); 169 | } 170 | 171 | return $defaultValue; 172 | } 173 | 174 | /** 175 | * @param string $attributeCode 176 | * @return bool 177 | */ 178 | private function isDefaultValueAvaibleForAttribute(string $attributeCode): bool 179 | { 180 | try { 181 | $attribute = $this->attributeRepository->get(Product::ENTITY, $attributeCode); 182 | } catch (NoSuchEntityException $e) { 183 | return false; 184 | } 185 | 186 | return !$attribute->isScopeGlobal(); 187 | } 188 | } 189 | -------------------------------------------------------------------------------- /Model/Section.php: -------------------------------------------------------------------------------- 1 | scopeConfig = $scopeConfig; 78 | $this->metadata = $metadata; 79 | $this->getModuleVersion = $getModuleVersion; 80 | $this->hyvaThemeDetection = $hyvaThemeDetection; 81 | $this->name = $name; 82 | $this->key = $key; 83 | } 84 | 85 | /** 86 | * @return bool 87 | */ 88 | final public function isEnabled() 89 | { 90 | return (bool)$this->getConfig(self::ENABLED); 91 | } 92 | 93 | /** 94 | * @param false $e 95 | * @return false|string 96 | */ 97 | final public function getModuleName($e = false) 98 | { 99 | $fs = $e ? [self::MODULE] : [self::MODULE . 'e', self::MODULE . 'p', self::MODULE]; 100 | foreach ($fs as $f) { 101 | $module = (string)$this->getConfig($f); 102 | if ($module) { 103 | break; 104 | } 105 | } 106 | 107 | return $module; 108 | } 109 | 110 | /** 111 | * @param false $e 112 | * @return false|string 113 | */ 114 | final public function getModule($e = false) 115 | { 116 | $module = $this->getModuleName(); 117 | 118 | $url = $this->scopeConfig->getValue( 119 | 'web/unsecure/base' . '_' . 'url', 120 | ScopeInterface::SCOPE_STORE, 121 | 0 122 | ); 123 | 124 | if (\Magefan\Community\Model\UrlChecker::showUrl($url)) { 125 | if ($module && $this->getType()) { 126 | return $module; 127 | } 128 | 129 | if ($module == ('B' . 'l' . 'o' . 'g') 130 | && version_compare($this->getModuleVersion->execute('Ma' . 'ge' . 'fa' . 'n_' . $module), '2.' . '11' . '.4', '>=') 131 | && $this->hyvaThemeDetection->execute() 132 | ) { 133 | return $module; 134 | } 135 | } 136 | return false; 137 | } 138 | 139 | /** 140 | * @return bool 141 | */ 142 | final public function getType() 143 | { 144 | return (!$this->getConfig(self::TYPE) 145 | || $this->getConfig(self::TYPE) && $this->metadata->getEdition() != 'C' . 'omm' . 'un' . 'ity' 146 | ); 147 | } 148 | 149 | /** 150 | * @return string 151 | */ 152 | final public function getKey() 153 | { 154 | if (null !== $this->key) { 155 | return $this->key; 156 | } else { 157 | return $this->getConfig(self::KEY); 158 | } 159 | } 160 | 161 | /** 162 | * @return string 163 | */ 164 | final public function getName() 165 | { 166 | return (string) $this->name; 167 | } 168 | 169 | /** 170 | * @param $data 171 | * @param null $k 172 | * @return bool 173 | */ 174 | final public function validate($data) 175 | { 176 | if (isset($data[$this->getModule()])) { 177 | return !empty($data[$this->getModule()]); 178 | } 179 | 180 | $k = $this->getKey(); 181 | 182 | foreach ([$this->getModule(), $this->getModule(true)] as $id) { 183 | foreach (['', 'Plus', 'Extra'] as $e) { 184 | if ($result = $this->validateIDK($id . $e, $k)) { 185 | return true; 186 | } 187 | } 188 | } 189 | 190 | return false; 191 | } 192 | 193 | /** 194 | * @param string $id 195 | * @param string $k 196 | * @return bool 197 | */ 198 | private function validateIDK($id, $k) 199 | { 200 | $l = substr($id, 1, 1); 201 | $d = (string) strlen($id); 202 | 203 | return (strlen($k) >= '3' . '2') 204 | && (strpos($k, $l, 5) == 5) 205 | && (strpos($k, $d, 19) == 19); 206 | } 207 | 208 | /** 209 | * @param string $field 210 | * @return mixed 211 | */ 212 | private function getConfig($field) 213 | { 214 | $g = 'general'; 215 | return $this->scopeConfig->getValue( 216 | implode('/', [$this->name, $g, $field]), 217 | ScopeInterface::SCOPE_STORE, 218 | 0 219 | ); 220 | } 221 | } 222 | -------------------------------------------------------------------------------- /Model/Section/Info.php: -------------------------------------------------------------------------------- 1 | metadata = $metadata; 47 | $this->storeManager = $storeManager; 48 | $this->curl = $curl; 49 | } 50 | 51 | /** 52 | * @param array $sections 53 | * @return bool|mixed 54 | */ 55 | final public function load(array $sections) 56 | { 57 | /*$this->curl->setOption(CURLOPT_SSL_VERIFYPEER, false);*/ 58 | try { 59 | $this->curl->post($u = 60 | implode('/', [ 61 | 'htt' . 'ps' . ':', 62 | '', 63 | 'm' . 'ag' . 'ef' . 'an.c' . 'om', 64 | 'mpk', 65 | 'info' 66 | ]), $d = [ 67 | 'version' => $this->metadata->getVersion(), 68 | 'edition' => $this->metadata->getEdition(), 69 | 'url' => $this->storeManager->getStore()->getBaseUrl(), 70 | 'sections' => $this->getSectionsParam($sections) 71 | ]); 72 | $body = $this->curl->getBody(); 73 | return json_decode($body, true); 74 | } catch (\Exception $e) { 75 | return false; 76 | } 77 | } 78 | 79 | /** 80 | * @param array $sections 81 | * @return array 82 | */ 83 | private function getSectionsParam(array $sections) 84 | { 85 | $result = []; 86 | foreach ($sections as $section) { 87 | $result[$section->getModule()] = [ 88 | 'key' => $section->getKey(), 89 | 'section' => $section->getName() 90 | ]; 91 | } 92 | return $result; 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /Model/SetLinvFlag.php: -------------------------------------------------------------------------------- 1 | configWriter = $configWriter; 37 | $this->cacheTypeList = $cacheTypeList; 38 | } 39 | 40 | /** 41 | * @param $module 42 | * @param $value 43 | * @return void 44 | */ 45 | public function execute($module, $value) 46 | { 47 | $this->configWriter->save($module . '/g'.'en'.'er'.'al'.'/l'.'in'.'v', $value, ScopeConfigInterface::SCOPE_TYPE_DEFAULT, 0); 48 | $this->cacheTypeList->cleanType(Config::TYPE_IDENTIFIER); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /Model/UrlChecker.php: -------------------------------------------------------------------------------- 1 | productMetadata = $productMetadata; 36 | $this->objectManager = $objectManager; 37 | } 38 | 39 | /** 40 | * @param string $tagName 41 | * @param array $attributes 42 | * @param string|null $content 43 | * @param bool $textContent 44 | * @return string 45 | */ 46 | public function renderTag( 47 | string $tagName, 48 | array $attributes, 49 | ?string $content = null, 50 | bool $textContent = true 51 | ) 52 | { 53 | $version = $this->productMetadata->getVersion(); 54 | if (version_compare($version, '2.4.0',">")) { 55 | return $this->objectManager->get(\Magento\Framework\View\Helper\SecureHtmlRenderer::class)->renderTag($tagName, $attributes, $content, $textContent); 56 | } else { 57 | $attrs = []; 58 | if ($attributes) { 59 | foreach ($attributes as $key => $value) { 60 | $attrs[] = $key . '="' . $value . '"'; 61 | } 62 | } 63 | return '<' . $tagName . ' ' . implode(' ', $attrs) . '>' . $content . ''; 64 | } 65 | 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /Observer/ConfigObserver.php: -------------------------------------------------------------------------------- 1 | sectionFactory = $sectionFactory; 63 | $this->info = $info; 64 | $this->messageManager = $messageManager; 65 | $this->setLinvFlag = $setLinvFlag; 66 | $this->config = $config; 67 | } 68 | 69 | /** 70 | * @param \Magento\Framework\Event\Observer $observer 71 | */ 72 | final public function execute(\Magento\Framework\Event\Observer $observer) 73 | { 74 | $request = $observer->getEvent()->getRequest(); 75 | $groups = $request->getParam('groups'); 76 | if (empty($groups['general']['fields']['enabled']['value'])) { 77 | return; 78 | } 79 | 80 | $key = isset($groups['general']['fields']['key']['value']) 81 | ? $groups['general']['fields']['key']['value'] 82 | : null; 83 | 84 | $section = $this->sectionFactory->create([ 85 | 'name' => $request->getParam('section'), 86 | 'key' => $key 87 | ]); 88 | 89 | if (!$section->getModule()) { 90 | $bp = $section->getName() . '/' . 'g' . 'e' . 'n' . 'e' . 'r' . 'a' . 'l' . '/' ; 91 | if (!$this->config->getConfig( $bp . Section::ACTIVE) && !$section->getType()) { 92 | $this->messageManager->addError( 93 | implode(array_reverse( 94 | [ 95 | '.','e','g','a','s','u',' ','e','e','r','f',' ','y','o','j','n','e',' ', 96 | 'o','t',' ','t','i',' ','e','t','a','v','i','t','c','a',' ','e','s','a', 97 | 'e','l','P',' ','.','d','e','t','a','v','i','t','c','a',' ','t','o','n', 98 | ' ','s','i',' ','n','o','i','s','n','e','t','x','e',' ','e','h','T' 99 | ] 100 | )) 101 | ); 102 | $groups['general']['fields']['enabled']['value'] = 0; 103 | $request->setPostValue('groups', $groups); 104 | } 105 | return; 106 | } 107 | $module = $section->getName(); 108 | $data = $this->info->load([$section]); 109 | 110 | if (!$section->validate($data)) { 111 | $groups['general']['fields']['enabled']['value'] = 0; 112 | $this->setLinvFlag->execute($module, 1); 113 | $request->setPostValue('groups', $groups); 114 | 115 | $this->messageManager->addError( 116 | implode(array_reverse( 117 | [ 118 | '.','d','e','l','b','a','s','i','d',' ','y','l','l','a','c','i','t','a','m', 119 | 'o','t','u','a',' ','n','e','e','b',' ','s','a','h',' ','n','o','i','s','n', 120 | 'e','t','x','e',' ','e','h','T',' ','.','d','i','l','a','v','n','i',' ','r', 121 | 'o',' ','y','t','p','m','e',' ','s','i',' ','y','e','K',' ','t','c','u','d', 122 | 'o','r','P' 123 | ] 124 | )) 125 | ); 126 | } else { 127 | $this->setLinvFlag->execute($module, 0); 128 | } 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /Observer/PredispathAdminActionControllerObserver.php: -------------------------------------------------------------------------------- 1 | feedFactory = $feedFactory; 35 | $this->backendAuthSession = $backendAuthSession; 36 | } 37 | 38 | /** 39 | * Predispath admin action controller 40 | * 41 | * @param \Magento\Framework\Event\Observer $observer 42 | * @return void 43 | * @SuppressWarnings(PHPMD.UnusedFormalParameter) 44 | */ 45 | public function execute(\Magento\Framework\Event\Observer $observer) 46 | { 47 | if ($this->backendAuthSession->isLoggedIn()) { 48 | $feedModel = $this->feedFactory->create(); 49 | /* @var $feedModel \Magefan\Community\Model\AdminNotificationFeed */ 50 | $feedModel->checkUpdate(); 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /Plugin/Magento/Backend/Model/Menu/BuilderPlugin.php: -------------------------------------------------------------------------------- 1 | menuItemFactory = $menuItemFactory; 71 | $this->config = $config; 72 | $this->structure = $structure; 73 | $this->moduleList = $moduleList; 74 | $this->moduleManager = $moduleManager; 75 | $this->magefanModules = $this->getMagefanModules(); 76 | } 77 | 78 | /** 79 | * @param Builder $subject 80 | * @param Menu $menu 81 | * @param $result 82 | * @return mixed $result 83 | */ 84 | public function afterGetResult(Builder $subject, Menu $menu, $result) 85 | { 86 | $menuEnabled = $this->config->menuEnabled(); 87 | if ($menuEnabled) { 88 | $item = $this->menuItemFactory->create([ 89 | 'data' => [ 90 | 'id' => 'Magefan_Community::elements', 91 | 'title' => 'Magefan', 92 | 'module' => 'Magefan_Community', 93 | 'resource' => 'Magefan_Community::elements' 94 | ] 95 | ]); 96 | $menu->add($item, null, 61); 97 | $subItems = $this->getSubItem($menu->toArray()); 98 | $this->createMenuItem($menu, $subItems, 'Magefan_Community::elements'); 99 | 100 | $item = $this->menuItemFactory->create([ 101 | 'data' => [ 102 | 'id' => 'Magefan_Community::extension_and_notification', 103 | 'title' => 'Extensions & Notifications', 104 | 'module' => 'Magefan_Community', 105 | 'resource' => 'Magefan_Community::elements' 106 | ] 107 | ]); 108 | $menu->add($item, 'Magefan_Community::elements', 1000); 109 | 110 | $item = $this->menuItemFactory->create([ 111 | 'data' => [ 112 | 'id' => 'Magefan_Community::extension_and_notification_view', 113 | 'title' => 'Manage', 114 | 'module' => 'Magefan_Community', 115 | 'resource' => 'Magefan_Community::elements', 116 | 'action' => 'adminhtml/system_config/edit/section/mfextension', 117 | ] 118 | ]); 119 | $menu->add($item, 'Magefan_Community::extension_and_notification', 1000); 120 | 121 | unset($this->configSections['Magefan_Community']); 122 | 123 | foreach ($this->magefanModules as $moduleName) { 124 | $section = $this->getConfigSections($moduleName); 125 | 126 | if (isset($section['id']) && 'mfextension' != $section['id']) { 127 | $item = $this->menuItemFactory->create([ 128 | 'data' => [ 129 | 'id' => $section['resource'] . '_custom', 130 | 'title' => $section['label'], 131 | 'module' => $moduleName, 132 | 'resource' => $section['resource'] 133 | ] 134 | ]); 135 | $menu->add($item, 'Magefan_Community::elements'); 136 | 137 | $item = $this->menuItemFactory->create([ 138 | 'data' => [ 139 | 'id' => $section['resource'] . '_menu', 140 | 'title' => 'Configuration', 141 | 'resource' => $section['resource'], 142 | 'action' => 'adminhtml/system_config/edit/section/' . $section['key'], 143 | 'module' => $moduleName 144 | ] 145 | ]); 146 | $menu->add($item, $section['resource'] . '_custom', 1000); 147 | } 148 | } 149 | } 150 | 151 | return $result; 152 | } 153 | 154 | /** 155 | * @param $moduleName 156 | * @return mixed|null 157 | */ 158 | private function getConfigSections($moduleName) 159 | { 160 | if (null === $this->configSections) { 161 | $sections = []; 162 | $this->configSections = []; 163 | 164 | try { 165 | $tabs = $this->structure->getTabs(); 166 | } catch (\Exception $e) { 167 | $tabs = []; 168 | } 169 | 170 | foreach ($tabs as $tab) { 171 | if (in_array($tab->getId(), ['magefan', 'mf_extensions_list'])) { 172 | $sections = array_merge($sections, $tab->getData()['children']); 173 | } 174 | } 175 | 176 | foreach ($sections as $key => $section) { 177 | if (empty($section['resource']) || 0 !== strpos($section['resource'], 'Magefan_')) { 178 | continue; 179 | } 180 | 181 | $section['key'] = $key; 182 | $mName = $this->getModuleNameByResource($section['resource']); 183 | $this->configSections[$mName] = $section; 184 | } 185 | } 186 | 187 | return isset($this->configSections[$moduleName]) ? $this->configSections[$moduleName] : null; 188 | } 189 | 190 | /** 191 | * @param $resource 192 | * @return string 193 | */ 194 | private function getModuleNameByResource($resource) 195 | { 196 | $moduleName = explode(':', $resource); 197 | $moduleName = $moduleName[0]; 198 | 199 | return $moduleName; 200 | } 201 | 202 | /** 203 | * @param $menu 204 | * @param $items 205 | * @param $parentId 206 | */ 207 | private function createMenuItem($menu, $items, $parentId) 208 | { 209 | foreach ($items as $item) { 210 | $moduleName = isset($item['module']) ? $item['module'] : null; 211 | $title = preg_replace('/(?menuItemFactory->create([ 216 | 'data' => [ 217 | 'id' => $item['id'] . '3', 218 | 'title' => $title, 219 | 'resource' => $item['resource'], 220 | 'module' => isset($item['module']) ? $item['module'] : null, 221 | ] 222 | ]); 223 | $menu->add($subItem, $parentId); 224 | } 225 | 226 | $subItem = $this->menuItemFactory->create([ 227 | 'data' => [ 228 | 'id' => $item['id'] . '2', 229 | 'title' => $item['title'], 230 | 'resource' => $item['resource'], 231 | 'action' => $item['action'], 232 | 'module' => isset($item['module']) ? $item['module'] : null, 233 | ] 234 | ]); 235 | if ($needCreateMenuItem) { 236 | $menu->add($subItem, $item['id'] . '3'); 237 | } else { 238 | $menu->add($subItem, $parentId); 239 | } 240 | 241 | if (!empty($item['sub_menu'])) { 242 | $this->createMenuItem($menu, $item['sub_menu'], $item['id'] . '2'); 243 | } 244 | 245 | if ('Magefan_Community::elements' == $parentId) { 246 | $addConfig = true; 247 | if (!empty($item['sub_menu'])) { 248 | foreach ($item['sub_menu'] as $subItem) { 249 | if ('Configuration' == $subItem['title']) { 250 | $addConfig = false; 251 | break; 252 | } 253 | } 254 | } 255 | 256 | if ($addConfig) { 257 | $section = $this->getConfigSections($moduleName); 258 | if ($section) { 259 | $subItem = $this->menuItemFactory->create([ 260 | 'data' => [ 261 | 'id' => $section['resource'] . '_menu', 262 | 'title' => 'Configuration', 263 | 'resource' => $section['resource'], 264 | 'action' => 'adminhtml/system_config/edit/section/' . $section['key'], 265 | 'module' => $moduleName 266 | ] 267 | ]); 268 | if ($needCreateMenuItem) { 269 | $menu->add($subItem, $item['id'] . '3'); 270 | } else { 271 | $menu->add($subItem, $item['id'] . '2'); 272 | } 273 | } 274 | } 275 | } 276 | unset($this->configSections[$moduleName]); 277 | $index = array_search($moduleName, $this->magefanModules); 278 | if (false !== $index) { 279 | unset($this->magefanModules[$index]); 280 | } 281 | } 282 | } 283 | 284 | /** 285 | * @param $items 286 | * @return array 287 | */ 288 | private function getSubItem($items) 289 | { 290 | $subItems = []; 291 | if (!empty($items)) { 292 | foreach ($items as $item) { 293 | if (isset($item['module']) && 0 === strpos($item['module'], 'Magefan_') 294 | || !isset($item['module']) && isset($item['id']) && 0 === strpos($item['id'], 'Magefan_') 295 | ) { 296 | if ('Magefan_Community::elements' != $item['id']) { 297 | $subItems[] = $item; 298 | } 299 | } elseif (!empty($item['sub_menu'])) { 300 | $subItems = array_merge($subItems, $this->getSubItem($item['sub_menu'])); 301 | } 302 | } 303 | } 304 | 305 | return $subItems; 306 | } 307 | 308 | /** 309 | * Retrieve Magefan modules info 310 | * 311 | * @return array 312 | */ 313 | private function getMagefanModules() 314 | { 315 | $modules = []; 316 | foreach ($this->moduleList->getNames() as $moduleName) { 317 | if (strpos($moduleName, 'Magefan_') !== false && $this->moduleManager->isEnabled($moduleName)) { 318 | $modules[] = $moduleName; 319 | } 320 | } 321 | return $modules; 322 | } 323 | } 324 | -------------------------------------------------------------------------------- /Plugin/Magento/Framework/View/TemplateEngine/Php.php: -------------------------------------------------------------------------------- 1 | mfSecureRenderer = $mfSecureRenderer; 39 | $this->mfHyvaThemeDetection = $mfHyvaThemeDetection; 40 | $this->mfBreezeThemeDetection = $mfBreezeThemeDetection; 41 | } 42 | 43 | /** 44 | * @param \Magento\Framework\View\TemplateEngine\Php $subject 45 | * @param \Magento\Framework\View\Element\BlockInterface $block 46 | * @param $fileName 47 | * @param array $dictionary 48 | * @return array 49 | */ 50 | public function beforeRender( 51 | \Magento\Framework\View\TemplateEngine\Php $subject, 52 | \Magento\Framework\View\Element\BlockInterface $block, 53 | $fileName, 54 | array $dictionary = [] 55 | ) { 56 | $dictionary['mfSecureRenderer'] = $this->mfSecureRenderer; 57 | $dictionary['mfHyvaThemeDetection'] = $this->mfHyvaThemeDetection; 58 | $dictionary['mfBreezeThemeDetection'] = $this->mfBreezeThemeDetection; 59 | 60 | return [$block, $fileName, $dictionary]; 61 | } 62 | } 63 | 64 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Magefan Community Extension by [Magefan](https://magefan.com/) 2 | 3 | [![Total Downloads](https://poser.pugx.org/magefan/module-community/downloads)](https://packagist.org/packages/magefan/module-community) 4 | [![Latest Stable Version](https://poser.pugx.org/magefan/module-community/v/stable)](https://packagist.org/packages/magefan/module-community) 5 | 6 | 7 | 8 | 9 | 10 | This module is required for other Magefan extensions for Magento 2 11 | 12 | ## Requirements 13 | * Magento Community Edition 2.1.x-2.4.x or Magento Enterprise Edition 2.1.x-2.4.x 14 | 15 | ## Installation Method 1 - Installing via composer 16 | * Open command line 17 | * Using command "cd" navigate to your magento2 root directory 18 | * Run the commands: 19 | 20 | ``` 21 | composer require magefan/module-community 22 | php bin/magento setup:upgrade 23 | php bin/magento setup:di:compile 24 | php bin/magento setup:static-content:deploy 25 | ``` 26 | 27 | ## Installation Method 2 - Installing via FTP using archive 28 | * Download [ZIP Archive](https://github.com/magefan/module-community/archive/master.zip) 29 | * Extract files 30 | * In your Magento 2 root directory create folder app/code/Magefan/Community 31 | * Copy files and folders from archive to that folder 32 | * In command line, using "cd", navigate to your Magento 2 root directory 33 | * Run the commands: 34 | ``` 35 | php bin/magento setup:upgrade 36 | php bin/magento setup:di:compile 37 | php bin/magento setup:static-content:deploy 38 | ``` 39 | 40 | ## Support 41 | If you have any issues, please [contact us](https://magefan.com/contact) 42 | then if you still need help, open a bug report in GitHub's 43 | [issue tracker](https://github.com/magefan/module-community/issues). 44 | 45 | ## License 46 | The code is licensed under [EULA](https://magefan.com/end-user-license-agreement). 47 | 48 | ## [Magento 2 Extensions](https://magefan.com/magento-2-extensions) by Magefan 49 | 50 | ### [Magento 2 Google Extensions](https://magefan.com/magento-2-extensions/google-extensions) 51 | 52 | * [Magento 2 Google Indexing](https://magefan.com/magento-2-google-indexing-api) 53 | * [Magento 2 Google Analytics 4](https://magefan.com/magento-2-google-analytics-4) 54 | * [Magento 2 Google Tag Manager](https://magefan.com/magento-2-google-tag-manager) 55 | * [Magento 2 Google Shopping Feed](https://magefan.com/magento-2-google-shopping-feed-extension) 56 | * [Magento 2 Google Customer Reviews](https://magefan.com/magento-2-google-customer-reviews) 57 | 58 | ### Magento 2 SEO Extensions 59 | 60 | * [Magento 2 SEO Extension](https://magefan.com/magento-2-seo-extension) 61 | * [Magento 2 Rich Snippets](https://magefan.com/magento-2-rich-snippets) 62 | * [Magento 2 HTML Sitemap](https://magefan.com/magento-2-html-sitemap-extension) 63 | * [Magento 2 XML Sitemap](https://magefan.com/magento-2-xml-sitemap-extension) 64 | * [Magento 2 Facebook Open Graph](https://magefan.com/magento-2-open-graph-extension-og-tags) 65 | * [Magento 2 Twitter Cards](https://magefan.com/magento-2-twitter-cards-extension) 66 | 67 | 68 | ### [Magento 2 Speed Optimization Extensions](https://magefan.com/magento-2-extensions/speed-optimization) 69 | 70 | * [Magento 2 Google Page Speed Optimizer](https://magefan.com/magento-2-google-page-speed-optimizer) 71 | * [Magento 2 Full Page Cache Warmer](https://magefan.com/magento-2-full-page-cache-warmer) 72 | * [Magento 2 Image Lazy Load](https://magefan.com/magento-2-image-lazy-load-extension) 73 | * [Magento 2 WebP Images](https://magefan.com/magento-2-webp-optimized-images) 74 | * [Magento 2 Rocket JavaScript](https://magefan.com/rocket-javascript-deferred-javascript) 75 | 76 | ### [Magento 2 Admin Panel Extensions](https://magefan.com/magento-2-extensions/admin-extensions) 77 | 78 | * [Magento 2 Size Chart Extension](https://magefan.com/magento-2-size-chart) 79 | * [Magento 2 Security Extension](https://magefan.com/magento-2-security-extension) 80 | * [Magento 2 Admin Action Log](https://magefan.com/magento-2-admin-action-log) 81 | * [Magento 2 Order Editor](https://magefan.com/magento-2-edit-order-extension) 82 | * [Magento 2 Better Order Grid](https://magefan.com/magento-2-better-order-grid-extension) 83 | * [Magento 2 Extended Proruct Grid](https://magefan.com/magento-2-product-grid-inline-editor) 84 | * [Magento 2 Product Tabs](https://magefan.com/magento-2/extensions/product-tabs) 85 | * [Magento 2 Facebook Pixel](https://magefan.com/magento-2-facebook-pixel-extension) 86 | * [Magento 2 Email Attachments](https://magefan.com/magento-2-email-attachments) 87 | * [Magento 2 Admin View](https://magefan.com/magento-2-admin-view-extension) 88 | * [Magento 2 Admin Email Notifications](https://magefan.com/magento-2-admin-email-notifications) 89 | * [Magento 2 Login As Customer](https://magefan.com/login-as-customer-magento-2-extension) 90 | 91 | ### Magento 2 Blog Extensions 92 | 93 | * [Magento 2 Blog](https://magefan.com/magento2-blog-extension) 94 | * [Magento 2 Multi Blog](https://magefan.com/magento-2-multi-blog-extension) 95 | * [Magento 2 Product Widget](https://magefan.com/magento-2-product-widget) 96 | 97 | ### [Magento 2 Marketing Automation Extensions](https://magefan.com/magento-2-extensions/marketing-automation) 98 | 99 | * [Magento 2 Cookie Consent](https://magefan.com/magento-2-cookie-consent) 100 | * [Magento 2 Product Labels](https://magefan.com/magento-2-product-labels) 101 | * [Magento 2 Base Price](https://magefan.com/magento-2-base-price) 102 | * [Magento 2 Dynamic Categories](https://magefan.com/magento-2-dynamic-categories) 103 | * [Magento 2 Dynamic Blocks and Pages Extension](https://magefan.com/magento-2-cms-display-rules-extension) 104 | * [Magento 2 Automatic Related Products](https://magefan.com/magento-2-automatic-related-products) 105 | * [Magento 2 Price History](https://magefan.com/magento-2-price-history) 106 | * [Magento 2 Mautic Integration](https://magefan.com/magento-2-mautic-extension) 107 | * [Magento 2 YouTube Video](https://magefan.com/magento2-youtube-extension) 108 | 109 | ### [Magento 2 Cart Extensions](https://magefan.com/magento-2-extensions/cart-extensions) 110 | 111 | * [Magento 2 Checkout Extension](https://magefan.com/better-magento-2-checkout-extension) 112 | * [Magento 2 Coupon Code](https://magefan.com/magento-2-coupon-code-link) 113 | * [Magento 2 Guest to Customer](https://magefan.com/magento2-convert-guest-to-customer) 114 | 115 | ### [Magento 2 Multi-Language Extensions](https://magefan.com/magento-2-extensions/multi-language-extensions) 116 | 117 | * [Magento 2 Hreflang Tags](https://magefan.com/magento2-alternate-hreflang-extension) 118 | * [Magento 2 Auto Currency Switcher](https://magefan.com/magento-2-currency-switcher-auto-currency-by-country) 119 | * [Magento 2 Auto Language Switcher](https://magefan.com/magento-2-auto-language-switcher) 120 | * [Magento 2 GeoIP Store Switcher](https://magefan.com/magento-2-geoip-switcher-extension) 121 | * [Magento 2 Translation](https://magefan.com/magento-2-translation-extension) 122 | 123 | ### [Developers Tools](https://magefan.com/magento-2-extensions/developer-tools) 124 | 125 | * [Magento 2 Zero Downtime Deployment](https://magefan.com/blog/magento-2-zero-downtime-deployment) 126 | * [Magento 2 Cron Schedule](https://magefan.com/magento-2-cron-schedule) 127 | * [Magento 2 CLI Extension](https://magefan.com/magento2-cli-extension) 128 | * [Magento 2 Conflict Detector](https://magefan.com/magento2-conflict-detector) 129 | 130 | ## [Shopify Apps](https://magefan.com/shopify/apps) by Magefan 131 | 132 | * [Shopify Login As Customer](https://apps.shopify.com/login-as-customer) 133 | * [Shopify Blog](https://apps.shopify.com/magefan-blog) 134 | * [Shopify Size Chart](https://magefan.com/shopify/apps/size-chart) 135 | * [Shopify Google Indexer](https://magefan.com/shopify/apps/google-indexing) 136 | * [Shopify Product Feeds](https://magefan.com/shopify/apps/product-feed) 137 | -------------------------------------------------------------------------------- /Setup/Patch/Data/UpdateWelcomeBlogPost.php: -------------------------------------------------------------------------------- 1 | moduleDataSetup = $moduleDataSetup; 25 | } 26 | 27 | /** 28 | * {@inheritdoc} 29 | */ 30 | public function apply() 31 | { 32 | $this->moduleDataSetup->getConnection()->startSetup(); 33 | 34 | 35 | $connection = $this->moduleDataSetup->getConnection(); 36 | $tableName = $this->moduleDataSetup->getTable('magefan_blog_post'); 37 | 38 | if ($connection->isTableExists($tableName)) { 39 | 40 | $replacesFrom = [ 41 | '\'href="https://magefan.com/magento2-blog-extension"\'', 42 | '\'href="https://magefan.com/magento2-extensions"\'', 43 | '\'href="https://magefan.com/magento-2-extensions"\'', 44 | '\'href="https://magefan.com/blog/magento-2-blog-extension-documentation"\'', 45 | '\'href="https://magefan.com/blog/add-read-more-tag-to-blog-post-content"\'', 46 | 47 | '\'href="https://github.com/magefan/module-blog"\'', 48 | '\'href="https://twitter.com/magento2fan"\'', 49 | '\'href="https://www.facebook.com/magefan/"\'' 50 | ]; 51 | 52 | $replaceTo = '\'href="#" rel="nofollow"\''; 53 | 54 | foreach ($replacesFrom as $replaceFrom) { 55 | $connection->update( 56 | $tableName, 57 | [ 58 | 'content' => new \Zend_Db_Expr( 59 | 'REPLACE(content, ' . $replaceFrom . ', ' . $replaceTo . ')' 60 | ) 61 | ], 62 | ['content LIKE ?' => '%This is your first post. Edit or delete it%'] 63 | ); 64 | } 65 | 66 | $replacesFrom = [ 67 | '\'Magefan\'', 68 | '\'Magento Blog\'', 69 | '\'Magento 2 Blog\'' 70 | ]; 71 | 72 | $replaceTo = '\'\''; 73 | 74 | foreach ($replacesFrom as $replaceFrom) { 75 | $connection->update( 76 | $tableName, 77 | [ 78 | 'content' => new \Zend_Db_Expr( 79 | 'REPLACE(content, ' . $replaceFrom . ', ' . $replaceTo . ')' 80 | ) 81 | ], 82 | ['content LIKE ?' => '%This is your first post. Edit or delete it%'] 83 | ); 84 | } 85 | } 86 | 87 | $this->moduleDataSetup->getConnection()->endSetup(); 88 | } 89 | 90 | /** 91 | * {@inheritdoc} 92 | */ 93 | public static function getDependencies() 94 | { 95 | return []; // Specify dependencies if any 96 | } 97 | 98 | /** 99 | * {@inheritdoc} 100 | */ 101 | public function getAliases() 102 | { 103 | return []; 104 | } 105 | } -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "magefan/module-community", 3 | "description": "Magefan core module, required for other Magefan extensions", 4 | "require": { 5 | "magefan/module-admin-user-guide" : ">=2.0.6" 6 | }, 7 | "version": "2.2.11", 8 | "type": "magento2-module", 9 | "autoload": { 10 | "files": [ 11 | "registration.php" 12 | ], 13 | "psr-4": { 14 | "Magefan\\Community\\": "" 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /etc/acl.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /etc/adminhtml/di.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /etc/adminhtml/events.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /etc/adminhtml/routes.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /etc/adminhtml/system.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | 11 | 12 | 13 |
14 | separator-top 15 | 16 | magefan 17 | Magefan_Community::config_section 18 | 19 | 20 | 1 21 | 22 | 23 | Magefan\Community\Block\Adminhtml\System\Config\Form\ExtensionsInfo 24 | 25 | 26 | 27 | 28 | 1 29 | 30 | 31 | Magento\Config\Model\Config\Source\Yesno 32 | 33 | 34 | 35 | Magento\Config\Model\Config\Source\Yesno 36 | 37 | 38 | 39 | Magento\Config\Model\Config\Source\Yesno 40 | 41 | 42 | 43 | Magento\Config\Model\Config\Source\Yesno 44 | 45 | 46 | 47 | Magento\Config\Model\Config\Source\Yesno 48 | 49 | 50 | 51 | 52 | 1 53 | 54 | 55 | Magento\Config\Model\Config\Source\Yesno 56 | Note that, after you save the config you have to flush the Magento cache for this option to be applied. 57 | 58 | 59 |
60 |
61 |
62 | -------------------------------------------------------------------------------- /etc/config.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 10 | 11 | 12 | 13 | 1 14 | 1 15 | 1 16 | 1 17 | 1 18 | 19 | 20 | 1 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /etc/crontab.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | 11 | 1 1 1,10 * * 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /etc/csp_whitelist.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 10 | 11 | 12 | 13 | magefan.com 14 | cm.magefan.com 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /etc/di.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | Magefan\Community\Model\View\Helper\SecureHtmlRenderer\Proxy 27 | Magefan\Community\Model\HyvaThemeDetection\Proxy 28 | Magefan\Community\Model\HyvaThemeDetection\Proxy 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /etc/module.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /registration.php: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /view/adminhtml/layout/marketplace_index_index.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /view/adminhtml/templates/hyvathemechecker.phtml: -------------------------------------------------------------------------------- 1 | 9 | 14 | getWitchModuleIsInstalled()) { ?> 15 |
16 |
17 |
18 | escapeHtml(__('It looks like you are using the Hyva theme, ' . 19 | 'but the Hyva Magefan extensions for Hyva compatibility are not installed. ' . 20 | 'Please install these packages for the proper functioning of the modules on the Hyva theme. Run the CLI commands:')) ?> 21 |
22 | $packageName) { 25 | $commands .= 'composer require hyva-themes/magento2-compat-module-fallback' . PHP_EOL; 26 | $commands .= 'composer require ' . $block->escapeHtml($packageName) . PHP_EOL; 27 | $commands .= 'bin/magento setup:upgrade' . PHP_EOL; 28 | $commands .= 'bin/magento setup:di:compile' . PHP_EOL; 29 | $commands .= 'bin/magento setup:static-content:deploy' . PHP_EOL; 30 | } 31 | ?> 32 |

33 |                 
34 |
35 |
36 |
37 | 38 | -------------------------------------------------------------------------------- /view/adminhtml/templates/linv.phtml: -------------------------------------------------------------------------------- 1 | 7 | 14 | getItems()) { ?> 15 | 16 |
17 |
18 |
19 | 20 |
21 |
22 |
    23 | $name) { ?> 24 |
  • 25 | escapeHtml($result = preg_replace('/([A-Z])/', ' $1', $name)); ?> 26 |
  • 27 | 28 |
29 |
30 | . 31 |
32 |
33 |
34 | 35 | 36 | -------------------------------------------------------------------------------- /view/adminhtml/templates/menu-magefan.phtml: -------------------------------------------------------------------------------- 1 | 9 | 10 | 15 | ul[role=menu]'); 24 | if (submenu.classList.contains('_show') && submenuDiv && submenuList) { 25 | submenuList.style.maxHeight = (document.documentElement.clientHeight - 120) + 'px'; 26 | submenuDiv.style.position = 'fixed'; 27 | submenuDiv.style.left = '88px'; 28 | document.body.style.overflowY = 'hidden'; 29 | } else { 30 | document.body.style.overflowY = 'auto'; 31 | } 32 | } 33 | 34 | let menuElement = document.querySelector('#menu-magefan-community-elements'); 35 | if (menuElement) { 36 | var observer = new MutationObserver(updateMenuHeight); 37 | observer.observe(menuElement, { attributes: true }); 38 | } 39 | }); 40 | " 41 | ?> 42 | 43 | renderTag('script', [], $script, false) ?> 44 | -------------------------------------------------------------------------------- /view/adminhtml/templates/vendors.phtml: -------------------------------------------------------------------------------- 1 | 7 | 8 |
9 |

escapeHtml(__('Extension Vendors')) ?>

10 |
11 |
12 | 13 | Magefan 14 | 15 |

Magefan

16 |

17 | Magefan is an eCommerce agency delivering top-notch extensions for Magento 2. We help eCommerce projects solve problems and reach their business goals faster.
18 | Magefan - eCommerce solutions you can trust.
19 | 20 | escapeHtml('Read More') ?> 21 | 22 |

23 |
24 |
25 |
26 | 27 | -------------------------------------------------------------------------------- /view/adminhtml/web/css/source/_module.less: -------------------------------------------------------------------------------- 1 | .admin__menu { 2 | 3 | // Magefan Community Menu Lvl-0 4 | #menu-magefan-community-elements { 5 | &._show { 6 | > .submenu { 7 | width: calc(100vw - 8.800000000000001rem); 8 | > ul[role=menu] { 9 | display: grid; 10 | grid-template-columns: repeat(auto-fill, minmax(28rem, 1fr)); 11 | overflow-x: hidden; 12 | overflow-y: auto; 13 | > .column { 14 | display: contents; 15 | > ul[role=menu] { 16 | display: contents; 17 | > li { 18 | display: flex; 19 | flex-direction: column; 20 | min-width: 24.8rem; 21 | border-bottom: 1px dotted rgba(255, 255, 255, 0.2); 22 | margin-bottom: 2rem; 23 | padding-bottom: 2.5rem; 24 | } 25 | } 26 | } 27 | } 28 | } 29 | } 30 | 31 | > a { 32 | &:before { 33 | content: ''; 34 | background: url("data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBzdGFuZGFsb25lPSJubyI/Pgo8c3ZnIHZlcnNpb249IjEuMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIgogd2lkdGg9IjUwMC4wMDAwMDBwdCIgaGVpZ2h0PSI1MDAuMDAwMDAwcHQiIHZpZXdCb3g9IjAgMCA1MDAuMDAwMDAwIDUwMC4wMDAwMDAiCiBwcmVzZXJ2ZUFzcGVjdFJhdGlvPSJ4TWlkWU1pZCBtZWV0Ij4KCjxnIHRyYW5zZm9ybT0idHJhbnNsYXRlKDAuMDAwMDAwLDUwMC4wMDAwMDApIHNjYWxlKDAuMTAwMDAwLC0wLjEwMDAwMCkiCmZpbGw9IiNhYWE2YTAiIHN0cm9rZT0ibm9uZSI+CjxwYXRoIGQ9Ik0zMjExIDQzOTkgYy0xMjIgLTM3IC0yMzUgLTE4OCAtMjkwIC0zODggbC0yMCAtNzMgMjIgNiBjMTIgNCA2MSAxMgoxMDkgMTggOTMgMTMgMjI0IDIgMjg3IC0yNCAzMCAtMTIgMjUgLTEzIC02NiAtMTMgLTI0OSAtMSAtNTAwIC0xNTMgLTYwMQotMzY1IC01NSAtMTE3IC02NyAtMTc0IC02NyAtMzIwIDEgLTEzNyAyMSAtMjMzIDcxIC0zMzMgMjggLTU2IDEwMCAtMTM3IDEyMQotMTM3IDcgMCAxMyAtNSAxMyAtMTEgMCAtMTcgLTgxIC00IC0xMjggMjAgLTIwMyAxMDMgLTI3MCA1MjggLTEyNyA4MDAgMjAgMzcKNTQgODYgNzUgMTA5IDIyIDIzIDQwIDQ0IDQwIDQ3IDAgMTAgLTU2IDUgLTExNSAtMTAgLTkyIC0yMyAtMjY3IC0xMTUgLTQwMAotMjExIC0xMzcgLTk4IC0yNDEgLTE1NCAtMzQ3IC0xODUgLTY2IC0yMCAtOTggLTIyIC0zMzggLTI0IC0yMzAgLTIgLTI3OSAtNQotMzcwIC0yNCAtMzM5IC03MiAtNjYwIC0zMzkgLTc3MCAtNjQwIC01NyAtMTU3IC03NCAtMzQyIC00NiAtNTAyIDEzIC03MiA3NwotMjg4IDM0OCAtMTE3NCAxMTYgLTM3OSAxMzQgLTQyMiAyMTAgLTQ5MSAxNDAgLTEyNiAzNjIgLTEyMiA1MTQgOSA4NSA3NCAxMzUKMjIzIDExNSAzNDYgLTUgMzMgLTYxIDIyNSAtMTIzIDQyOCAtMTExIDM2NCAtMTEzIDM2OSAtMTEzIDQ2OCAwIDkxIDMgMTA2IDMxCjE2NSAzNyA3OSA5NyAxNDAgMTcxIDE3NiA0NiAyMyA3MiAyOCAxNTcgMzIgODYgMyAxMDkgMSAxNTggLTE4IDYzIC0yMyAxNTMKLTk4IDE4OCAtMTU1IDEyIC0xOSA3OSAtMjIyIDE1MCAtNDUxIDExMSAtMzYzIDEzMyAtNDI0IDE2NyAtNDczIDE0NCAtMjA1CjM5OCAtMjI4IDU4NCAtNTQgNzQgNjkgMTAzIDEzNiAxMDcgMjUzIGwzIDk1IC0xMDYgMzUwIGMtNTggMTkzIC0xMDggMzc2Ci0xMTIgNDA3IC0xMSAxMDYgMjkgMjMzIDk3IDMxMSA0MCA0NiAxMzIgOTMgMjExIDEwOCA4MCAxNSAxNjggMiAyNDYgLTM2IDUxCi0yNiA3MiAtNDYgMTg2IC0xODUgMTEwIC0xMzQgMjgxIC0yNDcgNDMyIC0yODUgMTQyIC0zNiAzMDkgLTI5IDQ4MyAyMSAxMTMKMzIgMzMwIDE0MCA0MDcgMjAxIDc4IDY0IDg1IDg0IDU2IDE3MiAtMjYgNzkgLTQ0IDk5IC05MyAxMDcgLTM0IDYgLTM1IDUgLTYyCi00OSAtMzIgLTY3IC04OSAtMTIwIC0xNjUgLTE1NiAtNTEgLTI0IC02NyAtMjYgLTE3NiAtMjYgLTkwIDAgLTEzNCA1IC0xNzUKMTggLTEyNiA0MyAtMjIwIDEzOSAtMjYyIDI2NiAtMzIgMTAyIC0zNyAzMDQgLTggNDM2IDQ2IDIxNyAzNiAzODEgLTM1IDU1MAotMjYgNjMgLTM0IDk5IC0zOSAxNzAgLTIxIDM1OSAtMjY4IDcxMSAtNTExIDczMCAtMzMgMiAtNzUgMCAtOTQgLTZ6IG0xNDUKLTEwMzAgYzQxIC0zOSA1NyAtNzcgNTIgLTEyOSAtMyAtMjYgLTEzIC01OSAtMjQgLTczIC0xOCAtMjUgLTkzIC03MSAtMTAyCi02MyAtMiAyIDQgMTggMTIgMzUgMjEgNDAgMjAgOTkgLTMgMTM3IC0yMyAzNyAtODMgNjcgLTEyMiA2MiAtMzggLTYgLTMzIDEwCjEyIDQwIDQ3IDMyIDEzNiAyOCAxNzUgLTl6Ii8+CjwvZz4KPC9zdmc+Cg==") no-repeat center; 35 | background-size: 26px; 36 | margin: 0 auto .3rem auto; 37 | } 38 | &:hover { 39 | &:before { 40 | background: url("data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBzdGFuZGFsb25lPSJubyI/Pgo8c3ZnIHZlcnNpb249IjEuMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIgogd2lkdGg9IjUwMC4wMDAwMDBwdCIgaGVpZ2h0PSI1MDAuMDAwMDAwcHQiIHZpZXdCb3g9IjAgMCA1MDAuMDAwMDAwIDUwMC4wMDAwMDAiCiBwcmVzZXJ2ZUFzcGVjdFJhdGlvPSJ4TWlkWU1pZCBtZWV0Ij4KCjxnIHRyYW5zZm9ybT0idHJhbnNsYXRlKDAuMDAwMDAwLDUwMC4wMDAwMDApIHNjYWxlKDAuMTAwMDAwLC0wLjEwMDAwMCkiCmZpbGw9IiNmN2YzZWIiIHN0cm9rZT0ibm9uZSI+CjxwYXRoIGQ9Ik0zMjExIDQzOTkgYy0xMjIgLTM3IC0yMzUgLTE4OCAtMjkwIC0zODggbC0yMCAtNzMgMjIgNiBjMTIgNCA2MSAxMgoxMDkgMTggOTMgMTMgMjI0IDIgMjg3IC0yNCAzMCAtMTIgMjUgLTEzIC02NiAtMTMgLTI0OSAtMSAtNTAwIC0xNTMgLTYwMQotMzY1IC01NSAtMTE3IC02NyAtMTc0IC02NyAtMzIwIDEgLTEzNyAyMSAtMjMzIDcxIC0zMzMgMjggLTU2IDEwMCAtMTM3IDEyMQotMTM3IDcgMCAxMyAtNSAxMyAtMTEgMCAtMTcgLTgxIC00IC0xMjggMjAgLTIwMyAxMDMgLTI3MCA1MjggLTEyNyA4MDAgMjAgMzcKNTQgODYgNzUgMTA5IDIyIDIzIDQwIDQ0IDQwIDQ3IDAgMTAgLTU2IDUgLTExNSAtMTAgLTkyIC0yMyAtMjY3IC0xMTUgLTQwMAotMjExIC0xMzcgLTk4IC0yNDEgLTE1NCAtMzQ3IC0xODUgLTY2IC0yMCAtOTggLTIyIC0zMzggLTI0IC0yMzAgLTIgLTI3OSAtNQotMzcwIC0yNCAtMzM5IC03MiAtNjYwIC0zMzkgLTc3MCAtNjQwIC01NyAtMTU3IC03NCAtMzQyIC00NiAtNTAyIDEzIC03MiA3NwotMjg4IDM0OCAtMTE3NCAxMTYgLTM3OSAxMzQgLTQyMiAyMTAgLTQ5MSAxNDAgLTEyNiAzNjIgLTEyMiA1MTQgOSA4NSA3NCAxMzUKMjIzIDExNSAzNDYgLTUgMzMgLTYxIDIyNSAtMTIzIDQyOCAtMTExIDM2NCAtMTEzIDM2OSAtMTEzIDQ2OCAwIDkxIDMgMTA2IDMxCjE2NSAzNyA3OSA5NyAxNDAgMTcxIDE3NiA0NiAyMyA3MiAyOCAxNTcgMzIgODYgMyAxMDkgMSAxNTggLTE4IDYzIC0yMyAxNTMKLTk4IDE4OCAtMTU1IDEyIC0xOSA3OSAtMjIyIDE1MCAtNDUxIDExMSAtMzYzIDEzMyAtNDI0IDE2NyAtNDczIDE0NCAtMjA1CjM5OCAtMjI4IDU4NCAtNTQgNzQgNjkgMTAzIDEzNiAxMDcgMjUzIGwzIDk1IC0xMDYgMzUwIGMtNTggMTkzIC0xMDggMzc2Ci0xMTIgNDA3IC0xMSAxMDYgMjkgMjMzIDk3IDMxMSA0MCA0NiAxMzIgOTMgMjExIDEwOCA4MCAxNSAxNjggMiAyNDYgLTM2IDUxCi0yNiA3MiAtNDYgMTg2IC0xODUgMTEwIC0xMzQgMjgxIC0yNDcgNDMyIC0yODUgMTQyIC0zNiAzMDkgLTI5IDQ4MyAyMSAxMTMKMzIgMzMwIDE0MCA0MDcgMjAxIDc4IDY0IDg1IDg0IDU2IDE3MiAtMjYgNzkgLTQ0IDk5IC05MyAxMDcgLTM0IDYgLTM1IDUgLTYyCi00OSAtMzIgLTY3IC04OSAtMTIwIC0xNjUgLTE1NiAtNTEgLTI0IC02NyAtMjYgLTE3NiAtMjYgLTkwIDAgLTEzNCA1IC0xNzUKMTggLTEyNiA0MyAtMjIwIDEzOSAtMjYyIDI2NiAtMzIgMTAyIC0zNyAzMDQgLTggNDM2IDQ2IDIxNyAzNiAzODEgLTM1IDU1MAotMjYgNjMgLTM0IDk5IC0zOSAxNzAgLTIxIDM1OSAtMjY4IDcxMSAtNTExIDczMCAtMzMgMiAtNzUgMCAtOTQgLTZ6IG0xNDUKLTEwMzAgYzQxIC0zOSA1NyAtNzcgNTIgLTEyOSAtMyAtMjYgLTEzIC01OSAtMjQgLTczIC0xOCAtMjUgLTkzIC03MSAtMTAyCi02MyAtMiAyIDQgMTggMTIgMzUgMjEgNDAgMjAgOTkgLTMgMTM3IC0yMyAzNyAtODMgNjcgLTEyMiA2MiAtMzggLTYgLTMzIDEwCjEyIDQwIDQ3IDMyIDEzNiAyOCAxNzUgLTl6Ii8+CjwvZz4KPC9zdmc+Cg==") no-repeat center; 41 | background-size: 26px; 42 | margin: 0 auto .3rem auto; 43 | } 44 | } 45 | } 46 | } 47 | 48 | // Content 49 | [data-ui-id^=menu-magento-].level-0 { 50 | [data-ui-id^=menu-magefan-].level-1 { 51 | .submenu-group-title { 52 | span { 53 | display: flex; 54 | justify-content: space-between; 55 | &:after { 56 | content: ''; 57 | background: url("data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBzdGFuZGFsb25lPSJubyI/Pgo8c3ZnIHZlcnNpb249IjEuMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIgogd2lkdGg9IjUwMC4wMDAwMDBwdCIgaGVpZ2h0PSI1MDAuMDAwMDAwcHQiIHZpZXdCb3g9IjAgMCA1MDAuMDAwMDAwIDUwMC4wMDAwMDAiCiBwcmVzZXJ2ZUFzcGVjdFJhdGlvPSJ4TWlkWU1pZCBtZWV0Ij4KCjxnIHRyYW5zZm9ybT0idHJhbnNsYXRlKDAuMDAwMDAwLDUwMC4wMDAwMDApIHNjYWxlKDAuMTAwMDAwLC0wLjEwMDAwMCkiCmZpbGw9IiNmZmZmZmYiIHN0cm9rZT0ibm9uZSI+CjxwYXRoIGQ9Ik0zMjExIDQzOTkgYy0xMjIgLTM3IC0yMzUgLTE4OCAtMjkwIC0zODggbC0yMCAtNzMgMjIgNiBjMTIgNCA2MSAxMgoxMDkgMTggOTMgMTMgMjI0IDIgMjg3IC0yNCAzMCAtMTIgMjUgLTEzIC02NiAtMTMgLTI0OSAtMSAtNTAwIC0xNTMgLTYwMQotMzY1IC01NSAtMTE3IC02NyAtMTc0IC02NyAtMzIwIDEgLTEzNyAyMSAtMjMzIDcxIC0zMzMgMjggLTU2IDEwMCAtMTM3IDEyMQotMTM3IDcgMCAxMyAtNSAxMyAtMTEgMCAtMTcgLTgxIC00IC0xMjggMjAgLTIwMyAxMDMgLTI3MCA1MjggLTEyNyA4MDAgMjAgMzcKNTQgODYgNzUgMTA5IDIyIDIzIDQwIDQ0IDQwIDQ3IDAgMTAgLTU2IDUgLTExNSAtMTAgLTkyIC0yMyAtMjY3IC0xMTUgLTQwMAotMjExIC0xMzcgLTk4IC0yNDEgLTE1NCAtMzQ3IC0xODUgLTY2IC0yMCAtOTggLTIyIC0zMzggLTI0IC0yMzAgLTIgLTI3OSAtNQotMzcwIC0yNCAtMzM5IC03MiAtNjYwIC0zMzkgLTc3MCAtNjQwIC01NyAtMTU3IC03NCAtMzQyIC00NiAtNTAyIDEzIC03MiA3NwotMjg4IDM0OCAtMTE3NCAxMTYgLTM3OSAxMzQgLTQyMiAyMTAgLTQ5MSAxNDAgLTEyNiAzNjIgLTEyMiA1MTQgOSA4NSA3NCAxMzUKMjIzIDExNSAzNDYgLTUgMzMgLTYxIDIyNSAtMTIzIDQyOCAtMTExIDM2NCAtMTEzIDM2OSAtMTEzIDQ2OCAwIDkxIDMgMTA2IDMxCjE2NSAzNyA3OSA5NyAxNDAgMTcxIDE3NiA0NiAyMyA3MiAyOCAxNTcgMzIgODYgMyAxMDkgMSAxNTggLTE4IDYzIC0yMyAxNTMKLTk4IDE4OCAtMTU1IDEyIC0xOSA3OSAtMjIyIDE1MCAtNDUxIDExMSAtMzYzIDEzMyAtNDI0IDE2NyAtNDczIDE0NCAtMjA1CjM5OCAtMjI4IDU4NCAtNTQgNzQgNjkgMTAzIDEzNiAxMDcgMjUzIGwzIDk1IC0xMDYgMzUwIGMtNTggMTkzIC0xMDggMzc2Ci0xMTIgNDA3IC0xMSAxMDYgMjkgMjMzIDk3IDMxMSA0MCA0NiAxMzIgOTMgMjExIDEwOCA4MCAxNSAxNjggMiAyNDYgLTM2IDUxCi0yNiA3MiAtNDYgMTg2IC0xODUgMTEwIC0xMzQgMjgxIC0yNDcgNDMyIC0yODUgMTQyIC0zNiAzMDkgLTI5IDQ4MyAyMSAxMTMKMzIgMzMwIDE0MCA0MDcgMjAxIDc4IDY0IDg1IDg0IDU2IDE3MiAtMjYgNzkgLTQ0IDk5IC05MyAxMDcgLTM0IDYgLTM1IDUgLTYyCi00OSAtMzIgLTY3IC04OSAtMTIwIC0xNjUgLTE1NiAtNTEgLTI0IC02NyAtMjYgLTE3NiAtMjYgLTkwIDAgLTEzNCA1IC0xNzUKMTggLTEyNiA0MyAtMjIwIDEzOSAtMjYyIDI2NiAtMzIgMTAyIC0zNyAzMDQgLTggNDM2IDQ2IDIxNyAzNiAzODEgLTM1IDU1MAotMjYgNjMgLTM0IDk5IC0zOSAxNzAgLTIxIDM1OSAtMjY4IDcxMSAtNTExIDczMCAtMzMgMiAtNzUgMCAtOTQgLTZ6IG0xNDUKLTEwMzAgYzQxIC0zOSA1NyAtNzcgNTIgLTEyOSAtMyAtMjYgLTEzIC01OSAtMjQgLTczIC0xOCAtMjUgLTkzIC03MSAtMTAyCi02MyAtMiAyIDQgMTggMTIgMzUgMjEgNDAgMjAgOTkgLTMgMTM3IC0yMyAzNyAtODMgNjcgLTEyMiA2MiAtMzggLTYgLTMzIDEwCjEyIDQwIDQ3IDMyIDEzNiAyOCAxNzUgLTl6Ii8+CjwvZz4KPC9zdmc+Cg==") no-repeat center;background-size: contain; 58 | display: inline-block; 59 | width: 20px; 60 | min-width: 20px; 61 | height: 22px; 62 | margin-left: 15px; 63 | vertical-align: middle; 64 | padding: 0; 65 | } 66 | } 67 | } 68 | .submenu-group-title { 69 | + .submenu { 70 | [data-ui-id^=menu-magefan].level-2 { 71 | span { 72 | &:after { 73 | content: none; 74 | } 75 | } 76 | } 77 | } 78 | } 79 | } 80 | } 81 | 82 | [data-ui-id^=menu-magento-].level-0 { 83 | [data-ui-id^=menu-magento-].level-1 { 84 | [data-ui-id^=menu-magefan-].level-2 { 85 | span { 86 | display: flex; 87 | justify-content: space-between; 88 | &:after { 89 | content: ''; 90 | background: url("data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBzdGFuZGFsb25lPSJubyI/Pgo8c3ZnIHZlcnNpb249IjEuMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIgogd2lkdGg9IjUwMC4wMDAwMDBwdCIgaGVpZ2h0PSI1MDAuMDAwMDAwcHQiIHZpZXdCb3g9IjAgMCA1MDAuMDAwMDAwIDUwMC4wMDAwMDAiCiBwcmVzZXJ2ZUFzcGVjdFJhdGlvPSJ4TWlkWU1pZCBtZWV0Ij4KCjxnIHRyYW5zZm9ybT0idHJhbnNsYXRlKDAuMDAwMDAwLDUwMC4wMDAwMDApIHNjYWxlKDAuMTAwMDAwLC0wLjEwMDAwMCkiCmZpbGw9IiNmZmZmZmYiIHN0cm9rZT0ibm9uZSI+CjxwYXRoIGQ9Ik0zMjExIDQzOTkgYy0xMjIgLTM3IC0yMzUgLTE4OCAtMjkwIC0zODggbC0yMCAtNzMgMjIgNiBjMTIgNCA2MSAxMgoxMDkgMTggOTMgMTMgMjI0IDIgMjg3IC0yNCAzMCAtMTIgMjUgLTEzIC02NiAtMTMgLTI0OSAtMSAtNTAwIC0xNTMgLTYwMQotMzY1IC01NSAtMTE3IC02NyAtMTc0IC02NyAtMzIwIDEgLTEzNyAyMSAtMjMzIDcxIC0zMzMgMjggLTU2IDEwMCAtMTM3IDEyMQotMTM3IDcgMCAxMyAtNSAxMyAtMTEgMCAtMTcgLTgxIC00IC0xMjggMjAgLTIwMyAxMDMgLTI3MCA1MjggLTEyNyA4MDAgMjAgMzcKNTQgODYgNzUgMTA5IDIyIDIzIDQwIDQ0IDQwIDQ3IDAgMTAgLTU2IDUgLTExNSAtMTAgLTkyIC0yMyAtMjY3IC0xMTUgLTQwMAotMjExIC0xMzcgLTk4IC0yNDEgLTE1NCAtMzQ3IC0xODUgLTY2IC0yMCAtOTggLTIyIC0zMzggLTI0IC0yMzAgLTIgLTI3OSAtNQotMzcwIC0yNCAtMzM5IC03MiAtNjYwIC0zMzkgLTc3MCAtNjQwIC01NyAtMTU3IC03NCAtMzQyIC00NiAtNTAyIDEzIC03MiA3NwotMjg4IDM0OCAtMTE3NCAxMTYgLTM3OSAxMzQgLTQyMiAyMTAgLTQ5MSAxNDAgLTEyNiAzNjIgLTEyMiA1MTQgOSA4NSA3NCAxMzUKMjIzIDExNSAzNDYgLTUgMzMgLTYxIDIyNSAtMTIzIDQyOCAtMTExIDM2NCAtMTEzIDM2OSAtMTEzIDQ2OCAwIDkxIDMgMTA2IDMxCjE2NSAzNyA3OSA5NyAxNDAgMTcxIDE3NiA0NiAyMyA3MiAyOCAxNTcgMzIgODYgMyAxMDkgMSAxNTggLTE4IDYzIC0yMyAxNTMKLTk4IDE4OCAtMTU1IDEyIC0xOSA3OSAtMjIyIDE1MCAtNDUxIDExMSAtMzYzIDEzMyAtNDI0IDE2NyAtNDczIDE0NCAtMjA1CjM5OCAtMjI4IDU4NCAtNTQgNzQgNjkgMTAzIDEzNiAxMDcgMjUzIGwzIDk1IC0xMDYgMzUwIGMtNTggMTkzIC0xMDggMzc2Ci0xMTIgNDA3IC0xMSAxMDYgMjkgMjMzIDk3IDMxMSA0MCA0NiAxMzIgOTMgMjExIDEwOCA4MCAxNSAxNjggMiAyNDYgLTM2IDUxCi0yNiA3MiAtNDYgMTg2IC0xODUgMTEwIC0xMzQgMjgxIC0yNDcgNDMyIC0yODUgMTQyIC0zNiAzMDkgLTI5IDQ4MyAyMSAxMTMKMzIgMzMwIDE0MCA0MDcgMjAxIDc4IDY0IDg1IDg0IDU2IDE3MiAtMjYgNzkgLTQ0IDk5IC05MyAxMDcgLTM0IDYgLTM1IDUgLTYyCi00OSAtMzIgLTY3IC04OSAtMTIwIC0xNjUgLTE1NiAtNTEgLTI0IC02NyAtMjYgLTE3NiAtMjYgLTkwIDAgLTEzNCA1IC0xNzUKMTggLTEyNiA0MyAtMjIwIDEzOSAtMjYyIDI2NiAtMzIgMTAyIC0zNyAzMDQgLTggNDM2IDQ2IDIxNyAzNiAzODEgLTM1IDU1MAotMjYgNjMgLTM0IDk5IC0zOSAxNzAgLTIxIDM1OSAtMjY4IDcxMSAtNTExIDczMCAtMzMgMiAtNzUgMCAtOTQgLTZ6IG0xNDUKLTEwMzAgYzQxIC0zOSA1NyAtNzcgNTIgLTEyOSAtMyAtMjYgLTEzIC01OSAtMjQgLTczIC0xOCAtMjUgLTkzIC03MSAtMTAyCi02MyAtMiAyIDQgMTggMTIgMzUgMjEgNDAgMjAgOTkgLTMgMTM3IC0yMyAzNyAtODMgNjcgLTEyMiA2MiAtMzggLTYgLTMzIDEwCjEyIDQwIDQ3IDMyIDEzNiAyOCAxNzUgLTl6Ii8+CjwvZz4KPC9zdmc+Cg==") no-repeat center;background-size: contain; 91 | display: inline-block; 92 | width: 20px; 93 | min-width: 20px; 94 | height: 22px; 95 | margin-left: 15px; 96 | vertical-align: middle; 97 | padding: 0; 98 | } 99 | } 100 | } 101 | } 102 | } 103 | 104 | } 105 | 106 | 107 | 108 | // Store Configuration Magefan Extension 109 | .config-nav .magefan-tab .admin__page-nav-title:before { 110 | content: ''; 111 | background: url("data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBzdGFuZGFsb25lPSJubyI/Pgo8c3ZnIHZlcnNpb249IjEuMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIgogd2lkdGg9IjUwMC4wMDAwMDBwdCIgaGVpZ2h0PSI1MDAuMDAwMDAwcHQiIHZpZXdCb3g9IjAgMCA1MDAuMDAwMDAwIDUwMC4wMDAwMDAiCiBwcmVzZXJ2ZUFzcGVjdFJhdGlvPSJ4TWlkWU1pZCBtZWV0Ij4KCjxnIHRyYW5zZm9ybT0idHJhbnNsYXRlKDAuMDAwMDAwLDUwMC4wMDAwMDApIHNjYWxlKDAuMTAwMDAwLC0wLjEwMDAwMCkiCmZpbGw9IiMzMDMwMzAiIHN0cm9rZT0ibm9uZSI+CjxwYXRoIGQ9Ik0zMjExIDQzOTkgYy0xMjIgLTM3IC0yMzUgLTE4OCAtMjkwIC0zODggbC0yMCAtNzMgMjIgNiBjMTIgNCA2MSAxMgoxMDkgMTggOTMgMTMgMjI0IDIgMjg3IC0yNCAzMCAtMTIgMjUgLTEzIC02NiAtMTMgLTI0OSAtMSAtNTAwIC0xNTMgLTYwMQotMzY1IC01NSAtMTE3IC02NyAtMTc0IC02NyAtMzIwIDEgLTEzNyAyMSAtMjMzIDcxIC0zMzMgMjggLTU2IDEwMCAtMTM3IDEyMQotMTM3IDcgMCAxMyAtNSAxMyAtMTEgMCAtMTcgLTgxIC00IC0xMjggMjAgLTIwMyAxMDMgLTI3MCA1MjggLTEyNyA4MDAgMjAgMzcKNTQgODYgNzUgMTA5IDIyIDIzIDQwIDQ0IDQwIDQ3IDAgMTAgLTU2IDUgLTExNSAtMTAgLTkyIC0yMyAtMjY3IC0xMTUgLTQwMAotMjExIC0xMzcgLTk4IC0yNDEgLTE1NCAtMzQ3IC0xODUgLTY2IC0yMCAtOTggLTIyIC0zMzggLTI0IC0yMzAgLTIgLTI3OSAtNQotMzcwIC0yNCAtMzM5IC03MiAtNjYwIC0zMzkgLTc3MCAtNjQwIC01NyAtMTU3IC03NCAtMzQyIC00NiAtNTAyIDEzIC03MiA3NwotMjg4IDM0OCAtMTE3NCAxMTYgLTM3OSAxMzQgLTQyMiAyMTAgLTQ5MSAxNDAgLTEyNiAzNjIgLTEyMiA1MTQgOSA4NSA3NCAxMzUKMjIzIDExNSAzNDYgLTUgMzMgLTYxIDIyNSAtMTIzIDQyOCAtMTExIDM2NCAtMTEzIDM2OSAtMTEzIDQ2OCAwIDkxIDMgMTA2IDMxCjE2NSAzNyA3OSA5NyAxNDAgMTcxIDE3NiA0NiAyMyA3MiAyOCAxNTcgMzIgODYgMyAxMDkgMSAxNTggLTE4IDYzIC0yMyAxNTMKLTk4IDE4OCAtMTU1IDEyIC0xOSA3OSAtMjIyIDE1MCAtNDUxIDExMSAtMzYzIDEzMyAtNDI0IDE2NyAtNDczIDE0NCAtMjA1CjM5OCAtMjI4IDU4NCAtNTQgNzQgNjkgMTAzIDEzNiAxMDcgMjUzIGwzIDk1IC0xMDYgMzUwIGMtNTggMTkzIC0xMDggMzc2Ci0xMTIgNDA3IC0xMSAxMDYgMjkgMjMzIDk3IDMxMSA0MCA0NiAxMzIgOTMgMjExIDEwOCA4MCAxNSAxNjggMiAyNDYgLTM2IDUxCi0yNiA3MiAtNDYgMTg2IC0xODUgMTEwIC0xMzQgMjgxIC0yNDcgNDMyIC0yODUgMTQyIC0zNiAzMDkgLTI5IDQ4MyAyMSAxMTMKMzIgMzMwIDE0MCA0MDcgMjAxIDc4IDY0IDg1IDg0IDU2IDE3MiAtMjYgNzkgLTQ0IDk5IC05MyAxMDcgLTM0IDYgLTM1IDUgLTYyCi00OSAtMzIgLTY3IC04OSAtMTIwIC0xNjUgLTE1NiAtNTEgLTI0IC02NyAtMjYgLTE3NiAtMjYgLTkwIDAgLTEzNCA1IC0xNzUKMTggLTEyNiA0MyAtMjIwIDEzOSAtMjYyIDI2NiAtMzIgMTAyIC0zNyAzMDQgLTggNDM2IDQ2IDIxNyAzNiAzODEgLTM1IDU1MAotMjYgNjMgLTM0IDk5IC0zOSAxNzAgLTIxIDM1OSAtMjY4IDcxMSAtNTExIDczMCAtMzMgMiAtNzUgMCAtOTQgLTZ6IG0xNDUKLTEwMzAgYzQxIC0zOSA1NyAtNzcgNTIgLTEyOSAtMyAtMjYgLTEzIC01OSAtMjQgLTczIC0xOCAtMjUgLTkzIC03MSAtMTAyCi02MyAtMiAyIDQgMTggMTIgMzUgMjEgNDAgMjAgOTkgLTMgMTM3IC0yMyAzNyAtODMgNjcgLTEyMiA2MiAtMzggLTYgLTMzIDEwCjEyIDQwIDQ3IDMyIDEzNiAyOCAxNzUgLTl6Ii8+CjwvZz4KPC9zdmc+Cg==") no-repeat 0 0;background-size: contain; 112 | display: block; 113 | width: 30px; 114 | height: 30px; 115 | position: absolute; 116 | top: 11px; 117 | } 118 | .config-nav .magefan-tab .admin__page-nav-title strong { 119 | margin-left: 40px; 120 | } 121 | 122 | .accordion .form-inline .config .data-grid .magefan-section td { 123 | padding: 1rem; 124 | } 125 | .accordion .form-inline .config .magefan-section tr:nth-child(even) td { 126 | background-color: #f5f5f5; 127 | } 128 | -------------------------------------------------------------------------------- /view/adminhtml/web/fonts/variable/LDIoaomQNQcsA88c7O9yZ4KMCoOg4Ko20yw.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/magefan/module-community/6315f33cf02f80829bc7316775b59f4d63d8c538/view/adminhtml/web/fonts/variable/LDIoaomQNQcsA88c7O9yZ4KMCoOg4Ko20yw.woff2 -------------------------------------------------------------------------------- /view/adminhtml/web/images/logo-config-section.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/magefan/module-community/6315f33cf02f80829bc7316775b59f4d63d8c538/view/adminhtml/web/images/logo-config-section.png -------------------------------------------------------------------------------- /view/adminhtml/web/images/logo-menu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/magefan/module-community/6315f33cf02f80829bc7316775b59f4d63d8c538/view/adminhtml/web/images/logo-menu.png -------------------------------------------------------------------------------- /view/adminhtml/web/images/magefan-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/magefan/module-community/6315f33cf02f80829bc7316775b59f4d63d8c538/view/adminhtml/web/images/magefan-logo.png -------------------------------------------------------------------------------- /view/base/templates/js/ajax.phtml: -------------------------------------------------------------------------------- 1 | 7 | 13 | 37 | renderTag('script', [], $script, false) ?> 38 | -------------------------------------------------------------------------------- /view/base/templates/js/getCookie.phtml: -------------------------------------------------------------------------------- 1 | 7 | 13 | 31 | renderTag('script', [], $script, false) ?> 32 | -------------------------------------------------------------------------------- /view/base/templates/js/objToUrlParams.phtml: -------------------------------------------------------------------------------- 1 | 7 | 13 | `[${a}]`).join(\'\')}=${value}`)'; 15 | ?> 16 | 20 | Object.entries(obj).reduce((pairs, [key, value]) => { 21 | if (typeof value === 'object') 22 | pairs.push(...getPairs(value, [...keys, key])); 23 | else 24 | pairs.push([[...keys, key], value]); 25 | return pairs; 26 | }, []); 27 | 28 | let x = getPairs(obj) 29 | .map(([[key0, ...keysRest], value]) => 30 | {$mapCodeLine} 31 | .join('&'); 32 | 33 | return x; 34 | }; 35 | "; ?> 36 | renderTag('script', [], $script, false) ?> 37 | -------------------------------------------------------------------------------- /view/base/templates/js/setCookie.phtml: -------------------------------------------------------------------------------- 1 | 7 | 13 | 37 | renderTag('script', [], $script, false) ?> 38 | --------------------------------------------------------------------------------