├── .github └── FUNDING.yml ├── Api └── IsAllowedInterface.php ├── LICENSE ├── Model ├── AllowedRule │ ├── AllowedByDefault.php │ └── DeniedByDefault.php ├── Config.php ├── Config │ ├── Backend │ │ └── Cache.php │ ├── Source │ │ ├── Options.php │ │ └── Rules.php │ └── Structure │ │ └── Data.php ├── CustomModuleList.php ├── CustomTabList.php ├── IsAllowedMenuChildren.php ├── IsAllowedMenuId.php ├── IsAllowedModule.php ├── IsAllowedStrategy.php ├── RuleConfig.php └── RuleFactory.php ├── Plugin ├── Block │ └── MenuBlock.php └── Model │ ├── MenuBuilderCommand.php │ ├── MenuItem.php │ └── MenuPlugin.php ├── README.md ├── Spi ├── ListInterface.php └── RuleConfigInterface.php ├── composer.json ├── etc ├── acl.xml ├── adminhtml │ ├── di.xml │ ├── menu.xml │ └── system.xml ├── config.xml ├── di.xml └── module.xml ├── registration.php └── view └── adminhtml ├── layout └── adminhtml_system_config_edit.xml ├── templates └── system │ └── config │ └── tabs.phtml └── web ├── css └── source │ └── _module.less └── js └── collapsible.js /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: ["https://www.paypal.me/redchamps"] 13 | -------------------------------------------------------------------------------- /Api/IsAllowedInterface.php: -------------------------------------------------------------------------------- 1 | forbiddenList = $items; 30 | } 31 | 32 | public function isAllowed(string $name): bool 33 | { 34 | return !in_array($name, $this->forbiddenList, true); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Model/AllowedRule/DeniedByDefault.php: -------------------------------------------------------------------------------- 1 | forbiddenList = array_diff($defaultItems, $items); 31 | } 32 | 33 | public function isAllowed(string $name): bool 34 | { 35 | return !in_array($name, $this->forbiddenList, true); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Model/Config.php: -------------------------------------------------------------------------------- 1 | cacheManager = $cacheManager; 37 | parent::__construct($context, $registry, $config, $cacheTypeList, $resource, $resourceCollection, $data); 38 | } 39 | 40 | public function afterSave() 41 | { 42 | if ($this->isValueChanged()) { 43 | $this->cacheManager->flush($this->getData('cache_tags')); 44 | } 45 | 46 | return $this; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /Model/Config/Source/Options.php: -------------------------------------------------------------------------------- 1 | list = $list; 30 | } 31 | 32 | public function toOptionArray(): array 33 | { 34 | return $this->options ?? $this->options = $this->loadOptions(); 35 | } 36 | 37 | private function loadOptions(): array 38 | { 39 | $options = []; 40 | 41 | foreach ($this->list->getList() as $value => $label) { 42 | $options[] = ['label' => new Phrase($label), 'value' => $value]; 43 | } 44 | 45 | return $options; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /Model/Config/Source/Rules.php: -------------------------------------------------------------------------------- 1 | options = $options; 23 | } 24 | 25 | public function toOptionArray(): array 26 | { 27 | return $this->options; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Model/Config/Structure/Data.php: -------------------------------------------------------------------------------- 1 | isAllowed = $isAllowed; 33 | parent::__construct($reader, $configScope, $cache, $cacheId, $serializer); 34 | } 35 | 36 | public function get($path = null, $default = null) 37 | { 38 | $data = parent::get($path, $default); 39 | if (isset($data['tabs'], $data['sections'])) { 40 | $thirdPartyTabs = []; 41 | foreach ($data['sections'] as $sectionId => $section) { 42 | $sectionTab = $section['tab'] ?? ''; 43 | if ($sectionTab && !$this->isAllowed->isAllowed($sectionTab)) { 44 | if (isset($data['tabs'][$sectionTab])) { 45 | $section['tab_original'] = $data['tabs'][$sectionTab]; 46 | } 47 | $section['tab'] = 'extensions_list'; 48 | $thirdPartyTabs[$sectionTab][$sectionId] = $section; 49 | unset($data['sections'][$sectionId]); 50 | } 51 | } 52 | ksort($thirdPartyTabs); 53 | 54 | $data['sections'] = array_merge($data['sections'], array_merge(...array_values($this->sortByLabel($thirdPartyTabs)))); 55 | } 56 | 57 | return $data; 58 | } 59 | 60 | private function sortByLabel($array): array 61 | { 62 | return array_map( 63 | static function (array $sections): array { 64 | uasort( 65 | $sections, 66 | static function (array $a, array $b): int { 67 | return ($a['label'] ?? '') <=> ($b['label'] ?? ''); 68 | } 69 | ); 70 | 71 | return $sections; 72 | }, 73 | $array 74 | ); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /Model/CustomModuleList.php: -------------------------------------------------------------------------------- 1 | componentRegistrar = $componentRegistrar; 39 | } 40 | 41 | public function getList(): array 42 | { 43 | return $this->list ?? $this->list = $this->prepareList(); 44 | } 45 | 46 | private function prepareList(): array 47 | { 48 | $modules = $this->resolveCustomModules(); 49 | 50 | return array_combine($modules, $modules); 51 | } 52 | 53 | private function resolveCustomModules(): array 54 | { 55 | return array_keys(array_filter( 56 | $this->componentRegistrar->getPaths(ComponentRegistrar::MODULE), 57 | static function (string $moduleName): bool { 58 | return strncmp($moduleName, self::MAGENTO_MODULE_PREFIX, strlen(self::MAGENTO_MODULE_PREFIX)) !== 0 59 | && $moduleName !== self::MODULE_NAME; 60 | }, 61 | ARRAY_FILTER_USE_KEY 62 | )); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /Model/CustomTabList.php: -------------------------------------------------------------------------------- 1 | structureData = $structureData; 44 | } 45 | 46 | public function getList(): array 47 | { 48 | return $this->list ?? $this->list = $this->resolveCustomTabs(); 49 | } 50 | 51 | private function resolveCustomTabs(): array 52 | { 53 | $data = $this->structureData->get(); 54 | 55 | $tabs = array_diff_key($data['tabs'], array_flip(self::MANDATORY_TABS)); 56 | 57 | return array_combine(array_keys($tabs), array_column($tabs, 'label')); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /Model/IsAllowedMenuChildren.php: -------------------------------------------------------------------------------- 1 | getChildren() as $child) { 17 | if ($child->isAllowed() && !$child->isDisabled()) { 18 | return true; 19 | } 20 | } 21 | 22 | return false; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Model/IsAllowedMenuId.php: -------------------------------------------------------------------------------- 1 | scopeConfig = $scopeConfig; 28 | } 29 | 30 | public function isAllowed(string $name): bool 31 | { 32 | return in_array($name, $this->resolveAllowedMenus(), true); 33 | } 34 | 35 | public function resolveAllowedMenus(): array 36 | { 37 | return preg_split( 38 | '/\r\n|[\r\n]/', 39 | (string) $this->scopeConfig->getValue(self::CONFIG_PATH_ALLOWED_MENU_IDS) 40 | ) ?: []; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Model/IsAllowedModule.php: -------------------------------------------------------------------------------- 1 | isAllowed = $isAllowed; 34 | $this->scopeConfig = $scopeConfig; 35 | } 36 | 37 | public function isAllowed(string $name): bool 38 | { 39 | $isAllowed = true; 40 | 41 | if ($name === self::MODULE_NAME) { 42 | $isAllowed = !$this->scopeConfig->isSetFlag(self::CONFIG_PATH_MAGENTO_MARKETPLACE_MOVED); 43 | } 44 | 45 | return $isAllowed && $this->isAllowed->isAllowed($name); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /Model/IsAllowedStrategy.php: -------------------------------------------------------------------------------- 1 | ruleFactory = $ruleFactory; 38 | $this->config = $config; 39 | } 40 | 41 | public function isAllowed(string $name): bool 42 | { 43 | if (!$this->isAllowed) { 44 | $this->isAllowed = $this->ruleFactory->create( 45 | $this->config->getRuleId(), 46 | ['defaultItems' => $this->config->getDefaultItems(), 'items' => $this->config->getItems()] 47 | ); 48 | } 49 | 50 | return $this->isAllowed->isAllowed($name); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /Model/RuleConfig.php: -------------------------------------------------------------------------------- 1 | scopeConfig = $scopeConfig; 43 | $this->list = $list; 44 | $this->configPaths = $configPaths; 45 | } 46 | 47 | public function getRuleId(): string 48 | { 49 | return $this->configCache['ruleId'] ?? 50 | $this->configCache['ruleId'] = (string) $this->scopeConfig->getValue($this->configPaths['ruleId']); 51 | } 52 | 53 | public function getItems(): array 54 | { 55 | return $this->configCache['items'] ?? 56 | $this->configCache['items'] = explode( 57 | ',', 58 | $this->scopeConfig->getValue($this->configPaths['items']) ?? '' 59 | ); 60 | } 61 | 62 | public function getDefaultItems(): array 63 | { 64 | return $this->configCache['defaultItems'] ?? 65 | $this->configCache['defaultItems'] = array_keys($this->list->getList()); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /Model/RuleFactory.php: -------------------------------------------------------------------------------- 1 | objectManager = $objectManager; 30 | $this->rules = $rules; 31 | } 32 | 33 | public function create(string $ruleId, array $arguments): IsAllowedInterface 34 | { 35 | return $this->objectManager->create($this->rules[$ruleId], $arguments); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Plugin/Block/MenuBlock.php: -------------------------------------------------------------------------------- 1 | getFirstAvailable(); 26 | 27 | if ($level === 1 && $firstItem && $firstItem->toArray()['toolTip'] === Config::MENU_ID) { 28 | $level = 0; 29 | $limit = self::MAX_ITEMS; 30 | foreach ($colBrakes ?? [] as $key => $colBrake) { 31 | if (isset($colBrake['colbrake'])) { 32 | if ($colBrake['colbrake']) { 33 | $colBrakes[$key]['colbrake'] = false; 34 | } elseif ((($key - 1) % $limit) === 0) { 35 | $colBrakes[$key]['colbrake'] = true; 36 | } 37 | } 38 | } 39 | } 40 | 41 | return [$menu, $level, $limit, $colBrakes]; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Plugin/Model/MenuBuilderCommand.php: -------------------------------------------------------------------------------- 1 | isAllowedModule = $isAllowedModule; 32 | $this->isAllowedMenuId = $isAllowedMenuId; 33 | } 34 | 35 | public function afterExecute(AbstractCommand $subject, $result): array 36 | { 37 | if ((isset($result['id']) && $this->isAllowedMenuId->isAllowed($result['id'])) || 38 | ( 39 | isset($result['module']) && 40 | !($result['parent'] ?? '') && 41 | !$this->isAllowedModule->isAllowed($result['module']) 42 | ) 43 | ) { 44 | $result['parent'] = Config::MENU_ID; 45 | } 46 | 47 | return $result; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /Plugin/Model/MenuItem.php: -------------------------------------------------------------------------------- 1 | isAllowedMenuChildren = $isAllowedMenuChildren; 21 | } 22 | 23 | public function afterGetChildren(Item $subject, $result) 24 | { 25 | if ($subject->getId() === Config::MENU_ID) { 26 | $firstItem = $result->getFirstAvailable(); 27 | if ($firstItem && $firstItem->getId()) { 28 | $firstItem->setTooltip(Config::MENU_ID); 29 | } 30 | } 31 | 32 | return $result; 33 | } 34 | 35 | public function afterIsAllowed(Item $subject, bool $result): bool 36 | { 37 | return ($subject->toArray()['resource'] ?? '') === Config::MENU_ID 38 | ? $this->isAllowedMenuChildren->execute($subject) 39 | : $result; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Plugin/Model/MenuPlugin.php: -------------------------------------------------------------------------------- 1 | isAllowedModule = $isAllowedModule; 33 | $this->isAllowedMenuId = $isAllowedMenuId; 34 | } 35 | 36 | public function beforeAdd(Menu $subject, Item $item, $parentId = null, $index = null): array 37 | { 38 | if ($parentId === null && 39 | $subject->get(Config::MENU_ID) && 40 | ( 41 | $this->isAllowedMenuId->isAllowed($item->getId()) || 42 | !$this->isAllowedModule->isAllowed($item->toArray()['module']) 43 | ) 44 | ) { 45 | $parentId = Config::MENU_ID; 46 | } 47 | 48 | return [$item, $parentId, $index]; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Clean Admin Menu - Magento 2 Extension 2 | 3 | [![Latest Stable Version](https://img.shields.io/packagist/v/redchamps/module-clean-admin-menu.svg?style=flat-square)](https://packagist.org/packages/redchamps/module-clean-admin-menu) 4 | [![License: MIT](https://img.shields.io/github/license/redchamps/clean-admin-menu.svg?style=flat-square)](./LICENSE) 5 | [![Packagist](https://img.shields.io/packagist/dt/redchamps/module-clean-admin-menu.svg?style=flat-square)](https://packagist.org/packages/redchamps/module-clean-admin-menu/stats) 6 | [![Packagist](https://img.shields.io/packagist/dm/redchamps/module-clean-menu.svg?style=flat-square)](https://packagist.org/packages/redchamps/module-clean-admin-menu/stats) 7 | Supported Magento Versions 8 | 9 | ## Overview 10 | 11 | Clean Admin Menu is a Magento 2 extension that organizes and simplifies your admin panel by consolidating third-party extension menus. It follows Magento's best practices for admin menu organization by: 12 | 13 | 1. Merging all third-party extension menu items into a single "**Extensions**" menu item in the backend's primary navigation 14 | 2. Consolidating third-party extension configuration tabs under `Stores > Configuration` into a single "**Extensions**" tab 15 | 3. Placing the consolidated "Extensions" tab after native Magento tabs 16 | 17 | This organization aligns with Magento's official [Admin Best Practices](https://developer.adobe.com/commerce/php/best-practices/admin/placement-and-design/#feature-extensions) for feature-level extensions. 18 | 19 | ## Features 20 | 21 | - Consolidates all third-party extension menus into a single "Extensions" section 22 | - Organizes extension configuration settings under a unified tab 23 | - Follows Magento's recommended admin menu structure 24 | - Customizable menu organization through admin configuration 25 | - Compatible with Magento 2.4.0 through 2.4.8+ 26 | 27 | ## Installation 28 | 29 | Install via Composer: 30 | 31 | ```bash 32 | composer require redchamps/module-clean-admin-menu 33 | ``` 34 | 35 | After installation: 36 | 1. Run `bin/magento setup:upgrade` 37 | 2. Run `bin/magento setup:di:compile` 38 | 3. Run `bin/magento cache:clean` 39 | 40 | ## Configuration 41 | 42 | Access the extension settings at: 43 | > Stores > Configuration > Extensions > RedChamps > Clean Admin Menu 44 | 45 | ### Available Settings: 46 | - Menu organization preferences 47 | - Configuration tab placement 48 | - Developer tools for custom menu ID management 49 | 50 | ## Visual Examples 51 | 52 | ### Main Navigation 53 | ![Primary Navigation](https://raw.githubusercontent.com/redchamps/repo-images/master/after-primary-menu.png) 54 | 55 | ### Extensions Configuration 56 | ![System Config](https://raw.githubusercontent.com/redchamps/repo-images/master/after-system-config.png) 57 | 58 | ### Before and After Comparison 59 | ![Before and after comparison](https://raw.githubusercontent.com/redchamps/repo-images/master/before-after.gif) 60 | 61 | ## Troubleshooting 62 | 63 | If any extension menu item is not automatically moved under the "Extensions" menu: 64 | 65 | 1. Navigate to: Stores > Configuration > Extensions > RedChamps > Clean Admin Menu > Developer Tools 66 | 2. Find the menu ID of the extension that needs to be moved 67 | 3. Add the menu ID to the "Move Menu ID's" setting 68 | 69 | ## Requirements 70 | 71 | - Magento 2.4.0 or higher 72 | - PHP 7.4 or higher 73 | 74 | ## Support 75 | 76 | - [GitHub Issues](https://github.com/redchamps/clean-admin-menu/issues/) 77 | - [Documentation](https://redchamps.com/clean-admin-menu-magento-2-extension.html) 78 | - [Contact Support](mailto:hello@redchamps.com) 79 | 80 | ## Authors 81 | 82 | - **RedChamps** [Maintainer] [![Twitter Follow](https://img.shields.io/twitter/follow/_redChamps.svg?style=social)](https://twitter.com/_redChamps) 83 | - **Ravinder** [Maintainer] [![Twitter Follow](https://img.shields.io/twitter/follow/_iAmRav.svg?style=social)](https://twitter.com/_iAmRav) 84 | - **Thomas Klein** [Contributor] [![Twitter Follow](https://img.shields.io/twitter/follow/lead_dave.svg?style=social)](https://twitter.com/lead_dave) 85 | - **Prince Antil** [Contributor] [![Twitter Follow](https://img.shields.io/twitter/follow/prince_antil.svg?style=social)](https://twitter.com/prince_antil) 86 | 87 | ## License 88 | 89 | This project is licensed under the MIT License - see the [LICENSE](./LICENSE) file for details. 90 | 91 | ## More Extensions 92 | 93 | Visit our [store](https://redchamps.com) for more free and paid Magento 2 extensions. 94 | -------------------------------------------------------------------------------- /Spi/ListInterface.php: -------------------------------------------------------------------------------- 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 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | RedChamps\CleanMenu\Model\Config\Structure\Data 21 | 22 | 23 | 24 | 25 | RedChamps\CleanMenu\Model\CustomModuleList 26 | 27 | 28 | 29 | 30 | RedChamps\CleanMenu\Model\CustomTabList 31 | 32 | 33 | 34 | 35 | 36 | 37 | RedChamps\CleanMenu\Model\AllowedRule\AllowedByDefault::RULE_CODE 38 | Selected items are denied 39 | 40 | 41 | RedChamps\CleanMenu\Model\AllowedRule\DeniedByDefault::RULE_CODE 42 | Selected items are allowed 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | clean_admin_menu/settings/rule_modules 51 | clean_admin_menu/settings/allowed_modules 52 | 53 | RedChamps\CleanMenu\Model\CustomModuleList 54 | 55 | 56 | 57 | 58 | 59 | clean_admin_menu/settings/rule_tabs 60 | clean_admin_menu/settings/allowed_tabs 61 | 62 | RedChamps\CleanMenu\Model\CustomTabList 63 | 64 | 65 | 66 | 67 | RedChamps\CleanMenu\Model\ModuleRuleConfig 68 | 69 | 70 | 71 | 72 | RedChamps\CleanMenu\Model\IsAllowedModuleStrategy 73 | 74 | 75 | 76 | 77 | RedChamps\CleanMenu\Model\TabRuleConfig 78 | 79 | 80 | 81 | 82 | 83 | RedChamps\CleanMenu\Model\AllowedRule\AllowedByDefault 84 | RedChamps\CleanMenu\Model\AllowedRule\DeniedByDefault 85 | 86 | 87 | 88 | 89 | 90 | RedChamps\CleanMenu\Model\IsAllowedModule 91 | RedChamps\CleanMenu\Model\IsAllowedMenuId 92 | 93 | 94 | 95 | 96 | RedChamps\CleanMenu\Model\IsAllowedModule 97 | RedChamps\CleanMenu\Model\IsAllowedMenuId 98 | 99 | 100 | 101 | 102 | RedChamps\CleanMenu\Model\IsAllowedTab 103 | 104 | 105 | 106 | 107 | 108 | 109 | -------------------------------------------------------------------------------- /etc/adminhtml/menu.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /etc/adminhtml/system.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | redchamps 18 | 19 | RedChamps_CleanMenu::settings 20 | 21 | 22 | 23 | 24 | RedChamps\CleanMenu\Model\Config\Source\Modules 25 | RedChamps\CleanMenu\Model\Config\Backend\Modules 26 | 1 27 | clean_admin_menu/settings/allowed_modules 28 | We will allow the selected modules to have their own menus if they have their own. 29 | 30 | 31 | 32 | RedChamps\CleanMenu\Model\Config\Source\Rules 33 | RedChamps\CleanMenu\Model\Config\Backend\Modules 34 | clean_admin_menu/settings/rule_modules 35 | We will change the behavior of the allowed module list. 36 | 37 | 38 | 39 | RedChamps\CleanMenu\Model\Config\Source\Tabs 40 | 1 41 | clean_admin_menu/settings/allowed_tabs 42 | We will allow the selected tabs in the config section. 43 | 44 | 45 | 46 | RedChamps\CleanMenu\Model\Config\Source\Rules 47 | clean_admin_menu/settings/rule_tabs 48 | We will change the behavior of the allowed tab list. 49 | 50 | 51 | 52 | 53 | 54 | 55 | Magento\Config\Model\Config\Source\Enabledisable 56 | RedChamps\CleanMenu\Model\Config\Backend\Modules 57 | Magento_Marketplace 58 | clean_admin_menu/marketplace/enabled 59 | We will remove the Magento Marketplace menu item if disabled. 60 | 61 | 62 | 63 | 1 64 | 65 | 66 | Magento\Config\Model\Config\Source\Yesno 67 | RedChamps\CleanMenu\Model\Config\Backend\Modules 68 | Magento_Marketplace 69 | clean_admin_menu/marketplace/move 70 | We will move the Magento Marketplace menu item to the "extensions" menu. 71 | 72 | 73 | 74 | 75 | 76 | 77 | RedChamps\CleanMenu\Model\Config\Backend\Modules 78 | clean_admin_menu/developer/allowed_menu_ids 79 | Enter each menu ID in next line. We will move the specified menu id's under "Extensions" menu. 80 | 81 | 82 |
83 |
84 |
85 | -------------------------------------------------------------------------------- /etc/config.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | 11 | 12 | 13 | deniedByDefault 14 | ddg_automation 15 | deniedByDefault 16 | 1 17 | 18 | 19 | 1 20 | 1 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /etc/di.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | 11 | 12 | 13 | Magento\Backend\Block\Menu::CACHE_TAGS 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /etc/module.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /registration.php: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /view/adminhtml/templates/system/config/tabs.phtml: -------------------------------------------------------------------------------- 1 | 19 | getTabs()) : ?> 20 |
21 | getTabs() as $_tab) : 24 | $activeCollapsible = false; 25 | foreach ($_tab->getChildren() as $_section) : 26 | if ($block->isSectionActive($_section)) : 27 | $activeCollapsible = true; 28 | endif; 29 | endforeach; ?> 30 |
39 |
40 | escapeHtml($_tab->getLabel()) ?> 41 |
42 | 51 |
52 | 53 | getAttribute('tab_original')['id']] = true; ?> 54 |
57 |
58 | 59 |
60 | 74 |
75 | 76 | 77 |
  • 81 | 83 | escapeHtml($_section->getLabel()) ?> 84 | 85 |
  • 86 | 87 | 88 | 89 | 90 |
    91 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /view/adminhtml/web/css/source/_module.less: -------------------------------------------------------------------------------- 1 | // /** 2 | // * Copyright © RedChamps, All rights reserved. 3 | // * See LICENSE bundled with this library for license details. 4 | // */ 5 | 6 | #menu-redchamps-cleanmenu-extensions { 7 | .submenu { 8 | .submenu-title { 9 | margin-bottom: 4px; 10 | } 11 | >ul[role='menubar'] { 12 | min-height: 720px; 13 | } 14 | .level-0 { 15 | >a { 16 | &:before { 17 | float: left; 18 | margin-right: 10px; 19 | margin-left: 0; 20 | width: 30px; 21 | display: table; 22 | background-size: 20px; 23 | background-position: top; 24 | font-size: 2.2rem; 25 | } 26 | display: block; 27 | width: 99%; 28 | min-height: 0; 29 | margin: .7rem 0; 30 | padding: 1.25rem 1rem 1.25rem 3rem; 31 | font-weight: 600; 32 | font-size: 1.4rem; 33 | text-align: left; 34 | color: @submenu-link__color; 35 | text-transform: none; 36 | &:hover { 37 | background-color: @submenu-link__focus__background-color; 38 | } 39 | span { 40 | display: block!important; 41 | } 42 | } 43 | } 44 | .column { 45 | .parent { 46 | margin-bottom: 0; 47 | } 48 | } 49 | .parent { 50 | margin-bottom: 0.5rem; 51 | } 52 | } 53 | .level-0 { 54 | &:first-child { 55 | >a { 56 | &:after { 57 | content: ''; 58 | display: none; 59 | } 60 | } 61 | } 62 | } 63 | .item-container { 64 | .submenu { 65 | transform: none; 66 | opacity: 1; 67 | transition-property: opacity; 68 | } 69 | } 70 | .item-container._show { 71 | .submenu { 72 | opacity: 1; 73 | } 74 | } 75 | >a { 76 | &:before { 77 | content: '\e612'; 78 | } 79 | } 80 | } 81 | .admin__menu { 82 | .submenu { 83 | .column { 84 | &:only-of-type { 85 | .submenu-group-title { 86 | display: block !important; 87 | } 88 | } 89 | } 90 | .submenu { 91 | .submenu-title { 92 | height: 60px; 93 | } 94 | } 95 | } 96 | .level-0 { 97 | >.submenu { 98 | .submenu { 99 | width: auto !important; 100 | } 101 | } 102 | } 103 | } 104 | .rc-section-title { 105 | strong { 106 | padding: 0 0 8px 8px !important; 107 | } 108 | } 109 | .rc-section-title._active { 110 | border: none; 111 | } 112 | #system_config_tabs .rc-section-title._collapsed._show ._collapsible:after { 113 | content: '\e62b'; 114 | } 115 | #system_config_tabs .rc-section-title ._collapsible::after { 116 | content: '\e628'; 117 | } 118 | //extension specific rules 119 | #nav { 120 | li.item-weltpixel { 121 | >a { 122 | >span { 123 | font-size: inherit !important; 124 | } 125 | } 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /view/adminhtml/web/js/collapsible.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright © RedChamps, All rights reserved. 3 | * See LICENSE bundled with this library for license details. 4 | */ 5 | 6 | require([ 7 | "jquery", 8 | "collapsible", 9 | "domReady!" 10 | ], function ($) { 11 | "use strict"; 12 | 13 | $("[data-role=ext-tabs]").collapsible({ 14 | "openedState": "_show", 15 | "closedState": "_hide", 16 | "collapsible": true, 17 | "animate": 200 18 | }); 19 | $(".rc-section-title ._active").closest(".rc-section-title").collapsible("activate"); 20 | }); 21 | --------------------------------------------------------------------------------