diff --git a/Model/CommandRepository.php b/Model/CommandRepository.php deleted file mode 100644 index 7984879..0000000 --- a/Model/CommandRepository.php +++ /dev/null @@ -1,34 +0,0 @@ -commands[$clearCache->getName()] = $clearCache; - $this->commands[$templatePathHints->getName()] = $templatePathHints; - } - - /** - * Return a list of object that implement the Command Interface - * @return \MX\HelperBar\Api\CommandInterface[] - */ - public function getAllCommands() - { - return $this->commands; - } -} diff --git a/Model/Commands/ClearCache.php b/Model/Commands/ClearCache.php deleted file mode 100644 index 3ea3654..0000000 --- a/Model/Commands/ClearCache.php +++ /dev/null @@ -1,60 +0,0 @@ -cacheTypeList = $cacheTypeList; - $this->urlBuilder = $urlInterface; - } - - public function getResourceId() { - return 'Magento_Backend::cache'; - } - - public function getName() - { - return 'Clear Cache'; - } - - /** - * Return the url to the mass refresh ajax controller - */ - public function getHandlerUrl() - { - return $this->urlBuilder->getUrl('helperbar/ajax_cache/massRefresh'); - } - - /** - * Return the list of Cache Type - */ - public function getOptions() - { - $cacheTypes["all"] = "All"; - foreach ($this->cacheTypeList->getTypes() as $id => $cacheType) { - $cacheTypes[$id] = $cacheType->getCacheType(); - } - - return $cacheTypes; - } -} diff --git a/Model/Commands/TemplatePathHints.php b/Model/Commands/TemplatePathHints.php deleted file mode 100644 index 516f6cb..0000000 --- a/Model/Commands/TemplatePathHints.php +++ /dev/null @@ -1,64 +0,0 @@ -urlBuilder = $urlInterface; - } - - public function getResourceId() { - return 'Magento_Config::dev'; - } - - /** - * Return the name for this command - * - * @return string - */ - public function getName() - { - return 'Template Path Hints'; - } - - /** - * Gives the url that will handle this command - * - * @return string - */ - public function getHandlerUrl() - { - return $this->urlBuilder->getUrl('helperbar/ajax_config/templatePathHints'); - } - - /** - * Return an array where the value is displayed to the user as available option - * - * @return string[] - */ - public function getOptions() - { - return [ - 'storefront_enable' => 'Storefront Enable', - 'storefront_disable' => 'Storefront Disable', - 'admin_enable' => 'Admin Enable', - 'admin_disable' => 'Admin Disable', - 'both_enable' => 'Both Enable', - 'both_disable' => 'Both Disable' - ]; - } -} diff --git a/README.md b/README.md index 377de31..76bd25a 100644 --- a/README.md +++ b/README.md @@ -4,21 +4,57 @@ The **MX Helper** Bar for Magento 2 is intended to help store admins and operato - showing at a glance which environment(Pipeline instance) they are using so they won't inadvertently make the changes to the wrong environment. - acting as a super shortcut bar which allows you to quickly search and perform a list of common tasks, such as clearing cache types, without having to navigate away from the page. + - **New Feature:** Customizable Commands- easily add your own shortcuts for your customers, or yourself to speed up M2 -![Demo of Bob, the quick shortcut helper bar for Magento 2](https://github.com/inviqa/MX_HelperBar/raw/master/use.gif) +![Demo of Bob, the quick shortcut helper bar for Magento 2](use.gif) + + +## Commands + +What follows is a list of the commands currently available. This list will grow and get better with time (like wine!). + +### Clear Cache (cc) + +This command will allow you to refresh cache types as if you were doing it from the Magento _Cache Management_ page. + +Start typing 'cc' in the Helper Bar textbox to see the list of available commmands. All the following are valid commands: + +``` + > cc + > cc configuration + > cc database ddl operations +``` + +### Template Path and Block Hints (tph) + +This command allow you to enable or disable template path hints as you were doing it from _Stores -> Configuration -> Advanced -> Developer -> Debug_ + +For instance choose _tph front en_ and this will set: +_Enabled Template Path Hints for Storefront_ and _Add Block Names to Hints_ to 'Yes' + +instead, if you choose _tph en_ this will enable the template hints on the storefront and in the adminhtml site area + +### Navigation shortcut (nav) + +The navigation shortcut commands allow you to quickly navigate to other magento pages. +For instance choose _nav cms block_ to be redirected to the Cms Blocks page. + +If you want to contribute adding more shortcuts you can easily do so by editing the _etc/di.xml_ file. + +Add a new _virtualType_ (see as an example _navigation_redirect_cms_page_) and inject it as a new argument in the type _MX\HelperBar\Model\NavigationRedirectRepository_ ## Installing Then add the module to the require section of the composer file: ```shell - $ ./php composer.phar require "mx/module-helper-bar": "~1.0.0" + $ ./php composer.phar require "mx/module-helper-bar": "~1.0.1" ``` This command will add: ```json "require": { - "mx/module-helper-bar": "~1.0.0" + "mx/module-helper-bar": "~1.0.1" } ``` @@ -35,36 +71,13 @@ Once the module is added as dependency, run the Magento setup module, clear the Now that the module is enabled, you need to make the Helper Bar visible. -![video of how to install ](https://github.com/inviqa/MX_HelperBar/raw/master/install.gif) +![video of how to install ](install.gif) Navigate to: _Stores -> Configuration -> Advanced -> Developer -> Debug_ and select _"Yes"_ for the option with label _"Enabled Helper Bar for Admin"_ Then refresh the page and you will see the Helper Bar at the bottom of the screen. If you wish, you can temporarily hide it by pressing the 'X' or using CTRL + ` as a keyboard shortcut. -## Commands - -What follows is a list of the commands currently available. This list will grow and get better with time (like wine!). - -### Clear Cache - -This command will allow you to refresh cache types as if you were doing it from the Magento _Cache Management_ page. - -Start typing 'Cache' in the Helper Bar textbox to see the list of available commmands. All the following are valid commands: - -``` - > Clear Cache for: All - > Clear Cache for: Configuration, Layouts, Blocks HTML output - > Clear Cache for: Database DDL operations -``` - -### Template Path and Block Hints - -This command allow you to enable or disable template path hints as you were doing it from _Stores -> Configuration -> Advanced -> Developer -> Debug_ - -For instance choose _Template Path Hints for: Storefront Enable_ and this will set: -_Enabled Template Path Hints for Storefront_ and _Add Block Names to Hints_ to 'Yes' - ## Contributing By making this open source we hope others will gain value from it and use it as part of their projects. Please share any feedback, suggestions and additions so we can help make Magento 2 even faster, easier and simpler to work with and to develop amazing e-Commerce experiences on. diff --git a/Test/Unit/Block/Adminhtml/HelperBarTest.php b/Test/Unit/Block/Adminhtml/HelperBarTest.php deleted file mode 100644 index f09b33b..0000000 --- a/Test/Unit/Block/Adminhtml/HelperBarTest.php +++ /dev/null @@ -1,153 +0,0 @@ -reader = $this->getMockBuilder('\Magento\Framework\App\DeploymentConfig\Reader')->disableOriginalConstructor()->getMock(); - $this->productMetadataInterfaceMock = $this->getMockBuilder('\Magento\Framework\App\ProductMetadataInterface')->disableOriginalConstructor()->getMock(); - $this->context = $this->getMockBuilder('\Magento\Backend\Block\Template\Context')->disableOriginalConstructor()->getMock(); - $this->scopeConfig = $this->getMockBuilder('\Magento\Framework\App\Config\ScopeConfigInterface')->disableOriginalConstructor()->getMock(); - $this->storeManager = $this->getMockBuilder('\Magento\Store\Model\StoreManagerInterface')->disableOriginalConstructor()->getMock(); - $this->commandRepository = $this->getMockBuilder('\MX\HelperBar\Api\CommandRepositoryInterface')->disableOriginalConstructor()->getMock(); - $this->jsonHelper = $this->getMockBuilder('\Magento\Framework\Json\Helper\Data')->disableOriginalConstructor()->getMock(); - $this->authorization = $this->getMockBuilder('\Magento\Framework\AuthorizationInterface')->disableOriginalConstructor()->getMock(); - $this->urlBuilder = $this->getMockBuilder('Magento\Framework\Url')->disableOriginalConstructor()->getMock(); - - $this->context->expects($this->any()) - ->method('getUrlBuilder') - ->will($this->returnValue($this->urlBuilder)); - - $this->helperBar = new HelperBar( - $this->reader, - $this->productMetadataInterfaceMock, - $this->scopeConfig, - $this->storeManager, - $this->jsonHelper, - $this->authorization, - $this->commandRepository, - $this->context - ); - } - - /** - * @dataProvider isEnabledDataProvider - */ - public function testIsEnabled($isEnabled, $appEnvSettings) - { - $mockStore = $this->getMockBuilder('\Magento\Store\Api\Data\StoreInterface')->disableOriginalConstructor()->getMock(); - $this->storeManager->expects($this->once()) - ->method('getStore') - ->will($this->returnValue($mockStore)); - - $this->scopeConfig->expects($this->once()) - ->method('getValue') - ->with(HelperBar::CONFIG_DATA_PATH, ScopeInterface::SCOPE_STORE, null) - ->will($this->returnValue($appEnvSettings)); - - $this->assertSame($isEnabled, $this->helperBar->isEnabled()); - } - - public function isEnabledDataProvider() - { - return [ - 'Setting missing' => [false, null], - 'Setting disabled' => [false, '0'], - 'Setting enabled' => [true, '1'] - ]; - } - - /** - * @dataProvider getMode - */ - public function testGetMode($mode, $environment) - { - $this->reader->expects($this->once()) - ->method('load') - ->with(ConfigFilePool::APP_ENV) - ->will($this->returnValue($environment)); - $this->assertSame($mode, $this->helperBar->getMode()); - } - - public function getMode() - { - return [ - "MAGE_MODE setting missing" => [null, null], - "MAGE_MODE is developer" => [State::MODE_DEVELOPER, [State::PARAM_MODE => State::MODE_DEVELOPER]], - "MAGE_MODE is default" => [State::MODE_DEFAULT, [State::PARAM_MODE => State::MODE_DEFAULT]], - "MAGE_MODE is production" => [State::MODE_PRODUCTION, [State::PARAM_MODE => State::MODE_PRODUCTION]], - ]; - } - - public function testGetProductMetadata() - { - $this->productMetadataInterfaceMock->expects($this->once()) - ->method('getName') - ->will($this->returnValue('Magento')); - $this->productMetadataInterfaceMock->expects($this->once()) - ->method('getEdition') - ->will($this->returnValue('Enterprise')); - $this->productMetadataInterfaceMock->expects($this->once()) - ->method('getVersion') - ->will($this->returnValue('3.0.0')); - $this->assertSame('Magento Enterprise 3.0.0', $this->helperBar->getProductMetadata()); - } - - /** - * @dataProvider isAllowed - */ - public function testIsAllowed($expected, $isAllowedResource) - { - $this->authorization->expects($this->once()) - ->method('isAllowed') - ->with(HelperBar::ADMIN_RESOURCE) - ->will($this->returnValue($isAllowedResource)); - $this->assertSame($expected, $this->helperBar->isAllowed()); - } - - public function isAllowed() - { - return [ - "User is not allowed resource" => [false, false], - "User is allowed resource" => [true, true], - ]; - } -} diff --git a/composer.json b/composer.json index 4b018a3..982d113 100644 --- a/composer.json +++ b/composer.json @@ -10,7 +10,7 @@ "magento", "magento2" ], - "version": "1.0.0", + "version": "1.0.1", "type": "magento2-module", "license": [ "OSL-3.0", @@ -34,12 +34,18 @@ "magento/module-customer": "^100.1.0", "magento/module-store": "^100.1.0" }, + "require-dev": { + "bossa/phpspec2-expect": "1.*", + "phpspec/phpspec": "~2.3", + "nagno/phpspec-bootstrap-magento2": "~1.0", + "phpunit/phpunit": "4.1.0" + }, "autoload": { "files": [ - "registration.php" + "src/registration.php" ], "psr-4": { - "MX\\HelperBar\\": "" + "MX\\HelperBar\\": "src/" } }, "authors": [ diff --git a/etc/di.xml b/etc/di.xml deleted file mode 100644 index 44286b7..0000000 --- a/etc/di.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - MX\HelperBar\Block\HelperBar::ENVIRONMENT_NAME - - - diff --git a/install.gif b/install.gif index e5f4c53..77065ce 100644 Binary files a/install.gif and b/install.gif differ diff --git a/phpspec.yml b/phpspec.yml new file mode 100644 index 0000000..f7d7b98 --- /dev/null +++ b/phpspec.yml @@ -0,0 +1,8 @@ +bootstrap: 'spec/bootstrap.php' +formatter.name: pretty +extensions: + - Nagno\Phpspec\BootstrapMagento2\Bootstrap +suites: + helperbar: + namespace: MX\HelperBar + psr4_prefix: MX\HelperBar diff --git a/spec/Model/Commands/Options/ClearCacheSpec.php b/spec/Model/Commands/Options/ClearCacheSpec.php new file mode 100644 index 0000000..d46f2ce --- /dev/null +++ b/spec/Model/Commands/Options/ClearCacheSpec.php @@ -0,0 +1,38 @@ +beConstructedWith($typeList); + } + + function it_returns_list_of_cache_type_options(\Magento\Framework\App\Cache\TypeList $typeList) + { + $typeList->getTypes()->willReturn([ + 'type_a' => new \Magento\Framework\DataObject( + [ + 'id' => 'type_a', + 'cache_type' => 'Cache Type A' + ]), + 'type_b' => new \Magento\Framework\DataObject( + [ + 'id' => 'type_b', + 'cache_type' => 'Cache Type B' + ]) + ]); + $this->getOptions()->shouldReturn([ + \MX\HelperBar\Model\Commands\Options\ClearCache::ALL => '', + 'type_a' => 'cache type a', + 'type_b' => 'cache type b' + ]); + } +} diff --git a/spec/bootstrap.php b/spec/bootstrap.php new file mode 100644 index 0000000..47e6ab2 --- /dev/null +++ b/spec/bootstrap.php @@ -0,0 +1,3 @@ +jsonHelper = $jsonHelper; $this->authorization = $authorization; $this->commandRepository = $commandRepository; + $this->navigationRedirectRepository = $navigationRedirectRepository; $this->environmentName = $environmentName; parent::__construct($context, $data); } @@ -146,11 +155,11 @@ public function getProductMetadata() public function getCommands() { $commands = []; - foreach ($this->commandRepository->getAllCommands() as $name => $command) { + foreach ($this->commandRepository->getAllCommands() as $command) { if (!$this->authorization->isAllowed($command->getResourceId())) { continue; } - $commands[$name] = [ + $commands[$command->getLabel()] = [ "url" => $command->getHandlerUrl(), "options" => $command->getOptions() ]; @@ -158,4 +167,19 @@ public function getCommands() return $commands; } + + public function getRedirects() + { + $redirects = []; + foreach ($this->navigationRedirectRepository->getRedirects() as $redirect) { + if (!$this->authorization->isAllowed($redirect->getResourceId())) { + continue; + } + $redirects[$redirect->getLabel()] = [ + "url" => $redirect->getUrl() + ]; + } + return $redirects; + } + } diff --git a/Controller/Adminhtml/Ajax/Cache/MassRefresh.php b/src/Controller/Adminhtml/Ajax/Cache/MassRefresh.php similarity index 81% rename from Controller/Adminhtml/Ajax/Cache/MassRefresh.php rename to src/Controller/Adminhtml/Ajax/Cache/MassRefresh.php index 93a8ada..30f87de 100644 --- a/Controller/Adminhtml/Ajax/Cache/MassRefresh.php +++ b/src/Controller/Adminhtml/Ajax/Cache/MassRefresh.php @@ -7,6 +7,7 @@ use Magento\Framework\App\Cache\TypeListInterface; use Magento\Framework\Controller\Result\JsonFactory; use Magento\Framework\Exception\LocalizedException; +use MX\HelperBar\Model\Commands\Options\ClearCache; class MassRefresh extends Action { @@ -37,6 +38,11 @@ class MassRefresh extends Action */ protected $resultJsonFactory; + /** + * @var ClearCache + */ + private $clearCacheOptions; + /** * @param Action\Context $context @@ -44,19 +50,23 @@ class MassRefresh extends Action * @param StateInterface $cacheState * @param Pool $cacheFrontendPool * @param JsonFactory $resultJsonFactory + * @param ClearCache $clearCacheOptions */ public function __construct( Action\Context $context, TypeListInterface $cacheTypeList, StateInterface $cacheState, Pool $cacheFrontendPool, - JsonFactory $resultJsonFactory - ) { + JsonFactory $resultJsonFactory, + ClearCache $clearCacheOptions + ) + { parent::__construct($context); $this->cacheTypeList = $cacheTypeList; $this->cacheState = $cacheState; $this->cacheFrontendPool = $cacheFrontendPool; $this->resultJsonFactory = $resultJsonFactory; + $this->clearCacheOptions = $clearCacheOptions; } /** @@ -100,22 +110,15 @@ public function execute() private function getTypes(array $labels) { + $options = $this->clearCacheOptions->getOptions(); $cacheTypes = []; - foreach ($this->cacheTypeList->getTypes() as $id => $cacheType) { - $cacheTypes[$cacheType->getCacheType()] = $id; - } - - if (array_search("All", $labels) !== false) { - return array_values($cacheTypes); - } - - $types = []; foreach ($labels as $label) { - if(array_key_exists($label, $cacheTypes)) { - $types[] = $cacheTypes[$label]; - } + $cacheTypes[] = array_search($label, $options); } - - return $types; + if (count($cacheTypes) === 1 && $cacheTypes[0] === ClearCache::ALL) { + $cacheTypes = array_keys($options); + unset($cacheTypes[array_search('all', $cacheTypes)]); + } + return $cacheTypes; } } diff --git a/Controller/Adminhtml/Ajax/Config/TemplatePathHints.php b/src/Controller/Adminhtml/Ajax/Config/TemplatePathHints.php similarity index 94% rename from Controller/Adminhtml/Ajax/Config/TemplatePathHints.php rename to src/Controller/Adminhtml/Ajax/Config/TemplatePathHints.php index 6a27b19..2034f8e 100644 --- a/Controller/Adminhtml/Ajax/Config/TemplatePathHints.php +++ b/src/Controller/Adminhtml/Ajax/Config/TemplatePathHints.php @@ -4,6 +4,7 @@ use Magento\Backend\App\Action; use Magento\Config\Model\ResourceModel\Config; use Magento\Framework\Controller\Result\JsonFactory; +use MX\HelperBar\Model\Commands\Options\PlainList; class TemplatePathHints extends Action { @@ -25,7 +26,7 @@ class TemplatePathHints extends Action protected $resultJsonFactory; /** - * @var \MX\HelperBar\Model\Commands\TemplatePathHints + * @var PlainList */ private $templatePathHintCommand; @@ -33,7 +34,7 @@ public function __construct( Action\Context $context, Config $config, JsonFactory $resultJsonFactory, - \MX\HelperBar\Model\Commands\TemplatePathHints $templatePathHintCommand + PlainList $templatePathHintCommand ) { parent::__construct($context); @@ -47,7 +48,7 @@ public function execute() $resultJson = $this->resultJsonFactory->create(); $selectedOption = $this->getSelectedOption(); - if (!$selectedOption) { + if ($selectedOption === false) { return $resultJson->setData(['success' => false, 'message' => 'Option not found']); } diff --git a/src/Model/CommandRepository.php b/src/Model/CommandRepository.php new file mode 100644 index 0000000..fcc2de8 --- /dev/null +++ b/src/Model/CommandRepository.php @@ -0,0 +1,37 @@ +commands = $commands; + $this->validateCommands(); + } + + /** + * Return a list of object that implement the Command Interface + * @return CommandInterface[] + */ + public function getAllCommands() + { + return $this->commands; + } + + private function validateCommands() + { + foreach ($this->commands as $command) { + if (!$command instanceof CommandInterface) { + throw new \InvalidArgumentException( + "Invalid command type. Expected " . CommandInterface::class + ); + } + } + } +} diff --git a/src/Model/Commands/Command.php b/src/Model/Commands/Command.php new file mode 100644 index 0000000..bfad34c --- /dev/null +++ b/src/Model/Commands/Command.php @@ -0,0 +1,93 @@ +urlBuilder = $urlInterface; + $this->options = $options; + $this->name = $name; + $this->label = $label; + $this->resourceId = $resourceId; + $this->handleUrl = $handleUrl; + } + + /** + * Return the name for this command + * + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * Return the label for this command + * + * @return string + */ + public function getLabel() + { + return $this->label; + } + + /** + * Gives the url that will handle this command + * + * @return string + */ + public function getHandlerUrl() + { + return $this->urlBuilder->getUrl($this->handleUrl); + } + + /** + * Return an array where the value is displayed to the user as available option + * + * @return string[] + */ + public function getOptions() + { + return $this->options->getOptions(); + } + + /** + * Return the acl resource for the command + */ + public function getResourceId() + { + $this->resourceId; + } +} diff --git a/src/Model/Commands/Options/ClearCache.php b/src/Model/Commands/Options/ClearCache.php new file mode 100644 index 0000000..927c585 --- /dev/null +++ b/src/Model/Commands/Options/ClearCache.php @@ -0,0 +1,36 @@ +cacheTypeList = $cacheTypeList; + } + + /** + * Return the list of Cache Type + */ + public function getOptions() + { + $cacheTypeList = array_map([$this, 'getCacheTypeLowerCase'], $this->cacheTypeList->getTypes()); + return array_merge([self::ALL => ''], $cacheTypeList); + } + + public function getCacheTypeLowerCase($cacheType) + { + return strtolower($cacheType->getCacheType()); + } +} diff --git a/src/Model/Commands/Options/PlainList.php b/src/Model/Commands/Options/PlainList.php new file mode 100644 index 0000000..6e1f880 --- /dev/null +++ b/src/Model/Commands/Options/PlainList.php @@ -0,0 +1,33 @@ +options = $options; + } + + /** + * Return an array where the value is displayed to the user as available option + * + * @return string[] + */ + public function getOptions() + { + return $this->options; + } +} diff --git a/src/Model/Navigation/Redirect.php b/src/Model/Navigation/Redirect.php new file mode 100644 index 0000000..fa7cf7b --- /dev/null +++ b/src/Model/Navigation/Redirect.php @@ -0,0 +1,76 @@ +url = $url; + $this->label = $label; + $this->path = $path; + $this->resourceId = $resourceId; + } + + /** + * Text string such as 'catalog' + * + * @return string + */ + public function getLabel() + { + return self::NAVIGATION_PREFIX . ' ' . $this->label; + } + + /** + * Text string referring to a path in magento. i.e.: catalog/index/view + * + * @return string + */ + public function getUrl() + { + return $this->url->getUrl($this->path); + } + + /** + * Text string referring to a resourceId. i.e.: Magento_Backend::dev + * + * @return string + */ + public function getResourceId() + { + return $this->resourceId; + } +} diff --git a/src/Model/NavigationRedirectRepository.php b/src/Model/NavigationRedirectRepository.php new file mode 100644 index 0000000..05d8063 --- /dev/null +++ b/src/Model/NavigationRedirectRepository.php @@ -0,0 +1,44 @@ +navigationRedirects = $navigationRedirects; + $this->validateRedirects(); + } + + /** + * Return an array of NavigationRedirect objects + * + * @return NavigationRedirectInterface[] + */ + public function getRedirects() + { + return $this->navigationRedirects; + } + + private function validateRedirects() + { + foreach ($this->navigationRedirects as $redirect) { + if (!$redirect instanceof NavigationRedirectInterface) { + throw new \InvalidArgumentException( + "Invalid command type. Expected " . NavigationRedirectInterface::class + ); + } + } + } +} diff --git a/Test/Functional/Block/Adminhtml/HelperBar.php b/src/Test/Functional/Block/Adminhtml/HelperBar.php similarity index 100% rename from Test/Functional/Block/Adminhtml/HelperBar.php rename to src/Test/Functional/Block/Adminhtml/HelperBar.php diff --git a/Test/Functional/Page/Adminhtml/CmsPageIndex.xml b/src/Test/Functional/Page/Adminhtml/CmsPageIndex.xml similarity index 100% rename from Test/Functional/Page/Adminhtml/CmsPageIndex.xml rename to src/Test/Functional/Page/Adminhtml/CmsPageIndex.xml diff --git a/Test/Functional/Page/Adminhtml/Dashboard.xml b/src/Test/Functional/Page/Adminhtml/Dashboard.xml similarity index 100% rename from Test/Functional/Page/Adminhtml/Dashboard.xml rename to src/Test/Functional/Page/Adminhtml/Dashboard.xml diff --git a/Test/Functional/README.txt b/src/Test/Functional/README.txt similarity index 100% rename from Test/Functional/README.txt rename to src/Test/Functional/README.txt diff --git a/Test/Functional/TestCase/HelperBarTest.php b/src/Test/Functional/TestCase/HelperBarTest.php similarity index 100% rename from Test/Functional/TestCase/HelperBarTest.php rename to src/Test/Functional/TestCase/HelperBarTest.php diff --git a/Test/Functional/TestCase/HelperBarTest.xml b/src/Test/Functional/TestCase/HelperBarTest.xml similarity index 100% rename from Test/Functional/TestCase/HelperBarTest.xml rename to src/Test/Functional/TestCase/HelperBarTest.xml diff --git a/src/Test/Unit/Block/HelperBarTest.php b/src/Test/Unit/Block/HelperBarTest.php new file mode 100644 index 0000000..0c5abb3 --- /dev/null +++ b/src/Test/Unit/Block/HelperBarTest.php @@ -0,0 +1,203 @@ +reader = $this->getMockBuilder('\Magento\Framework\App\DeploymentConfig\Reader')->disableOriginalConstructor()->getMock(); + $this->productMetadataInterfaceMock = $this->getMockBuilder('\Magento\Framework\App\ProductMetadataInterface')->disableOriginalConstructor()->getMock(); + $this->context = $this->getMockBuilder('\Magento\Backend\Block\Template\Context')->disableOriginalConstructor()->getMock(); + $this->scopeConfig = $this->getMockBuilder('\Magento\Framework\App\Config\ScopeConfigInterface')->disableOriginalConstructor()->getMock(); + $this->storeManager = $this->getMockBuilder('\Magento\Store\Model\StoreManagerInterface')->disableOriginalConstructor()->getMock(); + $this->commandRepository = $this->getMockBuilder('\MX\HelperBar\Api\CommandRepositoryInterface')->disableOriginalConstructor()->getMock(); + $this->navigationRedirectRepository = $this->getMockBuilder('\MX\HelperBar\Api\NavigationRedirectRepositoryInterface')->disableOriginalConstructor()->getMock(); + $this->jsonHelper = $this->getMockBuilder('\Magento\Framework\Json\Helper\Data')->disableOriginalConstructor()->getMock(); + $this->authorization = $this->getMockBuilder('\Magento\Framework\AuthorizationInterface')->disableOriginalConstructor()->getMock(); + $this->urlBuilder = $this->getMockBuilder('Magento\Framework\Url')->disableOriginalConstructor()->getMock(); + + $this->context->expects($this->any()) + ->method('getUrlBuilder') + ->willReturn($this->urlBuilder); + + $this->helperBar = new HelperBar( + $this->reader, + $this->productMetadataInterfaceMock, + $this->scopeConfig, + $this->storeManager, + $this->jsonHelper, + $this->authorization, + $this->commandRepository, + $this->navigationRedirectRepository, + $this->context, + [], + "" + ); + } + + /** + * @dataProvider isEnabledDataProvider + */ + public function testIsEnabled($isEnabled, $appEnvSettings) + { + $mockStore = $this->getMockBuilder('\Magento\Store\Api\Data\StoreInterface')->disableOriginalConstructor()->getMock(); + + $this->storeManager->expects($this->once()) + ->method('getStore') + ->willReturn($mockStore); + + $this->scopeConfig->expects($this->once()) + ->method('getValue') + ->with(HelperBar::CONFIG_DATA_PATH, ScopeInterface::SCOPE_STORE, null) + ->willReturn($appEnvSettings); + + $this->assertSame($isEnabled, $this->helperBar->isEnabled()); + } + + public function isEnabledDataProvider() + { + return [ + 'Setting missing' => [false, null], + 'Setting disabled' => [false, '0'], + 'Setting enabled' => [true, '1'] + ]; + } + + /** + * @dataProvider getMode + */ + public function testGetMode($mode, $environment) + { + $this->reader->expects($this->once()) + ->method('load') + ->with(ConfigFilePool::APP_ENV) + ->willReturn($environment); + $this->assertSame($mode, $this->helperBar->getMode()); + } + + public function getMode() + { + return [ + "MAGE_MODE setting missing" => [null, null], + "MAGE_MODE is developer" => [State::MODE_DEVELOPER, [State::PARAM_MODE => State::MODE_DEVELOPER]], + "MAGE_MODE is default" => [State::MODE_DEFAULT, [State::PARAM_MODE => State::MODE_DEFAULT]], + "MAGE_MODE is production" => [State::MODE_PRODUCTION, [State::PARAM_MODE => State::MODE_PRODUCTION]], + ]; + } + + public function testGetProductMetadata() + { + $this->productMetadataInterfaceMock->expects($this->once()) + ->method('getName') + ->willReturn('Magento'); + $this->productMetadataInterfaceMock->expects($this->once()) + ->method('getEdition') + ->willReturn('Enterprise'); + $this->productMetadataInterfaceMock->expects($this->once()) + ->method('getVersion') + ->willReturn('3.0.0'); + $this->assertSame('Magento Enterprise 3.0.0', $this->helperBar->getProductMetadata()); + } + + public function testGetCommands() + { + /** @var \PHPUnit_Framework_MockObject_MockObject $mockNotAllowedCommand */ + $mockNotAllowedCommand = $this->getMockBuilder('\MX\HelperBar\Api\CommandInterface')->disableOriginalConstructor()->getMock(); + $mockNotAllowedCommand->expects($this->once())->method('getResourceId')->willReturn('a-resource-id'); + $mockNotAllowedCommand->expects($this->never())->method('getLabel'); + $mockNotAllowedCommand->expects($this->never())->method('getHandlerUrl'); + $mockNotAllowedCommand->expects($this->never())->method('getOptions'); + + /** @var \PHPUnit_Framework_MockObject_MockObject $mockAllowedCommand */ + $mockAllowedCommand = $this->getMockBuilder('\MX\HelperBar\Api\CommandInterface')->disableOriginalConstructor()->getMock(); + $mockAllowedCommand->expects($this->once())->method('getResourceId')->willReturn('b-resource-id'); + $mockAllowedCommand->expects($this->once())->method('getLabel')->willReturn('b-label'); + $mockAllowedCommand->expects($this->once())->method('getHandlerUrl')->willReturn('b-url'); + $mockAllowedCommand->expects($this->once())->method('getOptions')->willReturn(['x', 'y', 'z']); + + $this->authorization->expects($this->at(0))->method('isAllowed')->with('a-resource-id')->willReturn(false); + $this->authorization->expects($this->at(1))->method('isAllowed')->with('b-resource-id')->willReturn(true); + + $this->commandRepository->expects($this->once()) + ->method('getAllCommands') + ->willReturn([$mockNotAllowedCommand, $mockAllowedCommand]); + $expectedCommands = [ + 'b-label' => [ + 'url' => 'b-url', + 'options' => [ + 'x', 'y', 'z' + ] + ] + ]; + $this->assertSame($expectedCommands, $this->helperBar->getCommands()); + } + + public function testGetRedirects() + { + /** @var \PHPUnit_Framework_MockObject_MockObject $mockNotAllowedRedirect */ + $mockNotAllowedRedirect = $this->getMockBuilder('\MX\HelperBar\Api\NavigationRedirectInterface')->disableOriginalConstructor()->getMock(); + $mockNotAllowedRedirect->expects($this->once())->method('getResourceId')->willReturn('a-resource-id'); + $mockNotAllowedRedirect->expects($this->never())->method('getLabel'); + $mockNotAllowedRedirect->expects($this->never())->method('getUrl'); + + /** @var \PHPUnit_Framework_MockObject_MockObject $mockAllowedRedirect */ + $mockAllowedRedirect = $this->getMockBuilder('\MX\HelperBar\Api\NavigationRedirectInterface')->disableOriginalConstructor()->getMock(); + $mockAllowedRedirect->expects($this->once())->method('getResourceId')->willReturn('b-resource-id'); + $mockAllowedRedirect->expects($this->once())->method('getLabel')->willReturn('b-label'); + $mockAllowedRedirect->expects($this->once())->method('getUrl')->willReturn('b-url'); + + $this->authorization->expects($this->at(0))->method('isAllowed')->with('a-resource-id')->willReturn(false); + $this->authorization->expects($this->at(1))->method('isAllowed')->with('b-resource-id')->willReturn(true); + + $this->navigationRedirectRepository->expects($this->once()) + ->method('getRedirects') + ->willReturn([$mockNotAllowedRedirect, $mockAllowedRedirect]); + $expectedRedirects = [ + 'b-label' => [ + 'url' => 'b-url' + ] + ]; + $this->assertSame($expectedRedirects, $this->helperBar->getRedirects()); + } + +} diff --git a/etc/adminhtml/routes.xml b/src/etc/adminhtml/routes.xml similarity index 100% rename from etc/adminhtml/routes.xml rename to src/etc/adminhtml/routes.xml diff --git a/etc/adminhtml/system.xml b/src/etc/adminhtml/system.xml similarity index 100% rename from etc/adminhtml/system.xml rename to src/etc/adminhtml/system.xml diff --git a/src/etc/di.xml b/src/etc/di.xml new file mode 100644 index 0000000..9aac498 --- /dev/null +++ b/src/etc/di.xml @@ -0,0 +1,157 @@ + + + + + + + + + + MX\HelperBar\Block\HelperBar::ENVIRONMENT_NAME + + + + + + template_path_hints_options + + + + + + + + + + + + Magento_Backend::cache + clear_cache + cc + + + + + + + + + front en + front dis + admin en + admin dis + en + dis + + + + + + + Magento_Config::dev + template_path_hints + tph + + template_path_hints_options + + + + + + + clear_cache + template_path_hints + + + + + + + cms page + + Magento_Cms::page + + + + + + cms block + + Magento_Cms::block + + + + + + + pro catal + + Magento_Catalog::catalog + + + + + + pro categ + + Magento_Catalog::categories + + + + + + sal order + + Magento_Sales::sales_order + + + + + + sal invoice + + Magento_Sales::sales_invoice + + + + + + rep sales + + Magento_Reports::salesroot_sales + + + + + + rep lowst + + Magento_Reports::lowstock + + + + + + sto config + + Magento_Config::config + + + + + + + navigation_redirect_cms_page + navigation_redirect_cms_block + navigation_redirect_products_catalog + navigation_redirect_products_categories + navigation_redirect_sales_order + navigation_redirect_sales_invoice + navigation_redirect_report_sales + navigation_redirect_report_lowstock + navigation_redirect_admin_system_config + + + + + diff --git a/etc/module.xml b/src/etc/module.xml similarity index 100% rename from etc/module.xml rename to src/etc/module.xml diff --git a/registration.php b/src/registration.php similarity index 100% rename from registration.php rename to src/registration.php diff --git a/view/adminhtml/layout/default.xml b/src/view/adminhtml/layout/default.xml similarity index 100% rename from view/adminhtml/layout/default.xml rename to src/view/adminhtml/layout/default.xml diff --git a/view/adminhtml/templates/helperbar.phtml b/src/view/adminhtml/templates/helperbar.phtml similarity index 83% rename from view/adminhtml/templates/helperbar.phtml rename to src/view/adminhtml/templates/helperbar.phtml index a7ee27c..485948e 100644 --- a/view/adminhtml/templates/helperbar.phtml +++ b/src/view/adminhtml/templates/helperbar.phtml @@ -1,9 +1,10 @@ helper('Magento\Framework\Json\Helper\Data')->jsonEncode($block->getCommands()); ?> +helper('Magento\Framework\Json\Helper\Data')->jsonEncode($block->getRedirects()); ?> isEnabled()): ?>
@@ -15,7 +16,7 @@ 0): ?> + placeholder=""> @@ -33,7 +34,7 @@ "closeSelector": "#helper-bar-close", "commandSearchSelector": "#helper-bar-command-search", "commands": , - "helperBarColumn": "#helper-bar-command-column" + "redirects": } } } diff --git a/view/base/web/helperbar.css b/src/view/base/web/helperbar.css similarity index 100% rename from view/base/web/helperbar.css rename to src/view/base/web/helperbar.css diff --git a/view/base/web/images/Flash-Left-Default.jpg b/src/view/base/web/images/Flash-Left-Default.jpg similarity index 100% rename from view/base/web/images/Flash-Left-Default.jpg rename to src/view/base/web/images/Flash-Left-Default.jpg diff --git a/view/base/web/images/Flash-Left-Developer.jpg b/src/view/base/web/images/Flash-Left-Developer.jpg similarity index 100% rename from view/base/web/images/Flash-Left-Developer.jpg rename to src/view/base/web/images/Flash-Left-Developer.jpg diff --git a/view/base/web/images/Flash-Left-Production.jpg b/src/view/base/web/images/Flash-Left-Production.jpg similarity index 100% rename from view/base/web/images/Flash-Left-Production.jpg rename to src/view/base/web/images/Flash-Left-Production.jpg diff --git a/view/base/web/images/Flash-Right-PT.jpg b/src/view/base/web/images/Flash-Right-PT.jpg similarity index 100% rename from view/base/web/images/Flash-Right-PT.jpg rename to src/view/base/web/images/Flash-Right-PT.jpg diff --git a/view/base/web/images/Flash-Right-RB.jpg b/src/view/base/web/images/Flash-Right-RB.jpg similarity index 100% rename from view/base/web/images/Flash-Right-RB.jpg rename to src/view/base/web/images/Flash-Right-RB.jpg diff --git a/view/base/web/images/Flash-Right-YP.jpg b/src/view/base/web/images/Flash-Right-YP.jpg similarity index 100% rename from view/base/web/images/Flash-Right-YP.jpg rename to src/view/base/web/images/Flash-Right-YP.jpg diff --git a/src/view/base/web/js/helperbar.js b/src/view/base/web/js/helperbar.js new file mode 100644 index 0000000..6d40044 --- /dev/null +++ b/src/view/base/web/js/helperbar.js @@ -0,0 +1,171 @@ +define([ + "jquery", + 'Magento_Ui/js/modal/alert', + "jquery/ui" +], function($, alert) { + "use strict"; + + var _this; + + $.widget('mx.helperbar', { + options: { + //selectors + closeSelector: "", + commandSearchSelector: "", + commands: [], + redirects: [] + }, + + _create: function() { + _this = this; + //setup binds and initAutocomplete + this.setupBinds(); + this.initAutocomplete($(this.options.commandSearchSelector)); + }, + /** + * Setup Binds for clicks and keypress + */ + setupBinds: function() { + $(this.options.closeSelector).on('click', this.toggleHelpBar); + $(document).on("keypress", this.onDocumentKeyCallback); + }, + + /** + * Add a list of commands in the given array pre-appending at each option the prefix for the command + * + * @param searchSource + * @returns {*} + */ + populateCommands: function(searchSource) { + for(var commandName in this.options.commands) { + if(!this.options.commands.hasOwnProperty(commandName)) continue; + var command = this.options.commands[commandName]; + for(var key in command.options) { + if(!command.options.hasOwnProperty(key)) continue; + searchSource.push({ + label: commandName + ' ' + command.options[key], + value: commandName + ' ' + command.options[key], + object: { + option: command.options[key], + command: commandName, + url: command.url + }, + isAjax: true + }); + } + } + return searchSource; + }, + + /** + * Add a list of redirects in the given array + * + * @param searchSource + * @returns {*} + */ + populateRedirects: function(searchSource) { + for(var prefix in this.options.redirects) { + if(!this.options.redirects.hasOwnProperty(prefix)) continue; + var redirect = this.options.redirects[prefix]; + searchSource.push({ + label: prefix, + value: prefix, + object: redirect, + isAjax: false + }) + } + return searchSource; + }, + + sendAjaxRequest: function(ajaxController) { + $.ajax({ + dataType: 'json', + url: ajaxController.url, + data: ajaxController.data + }).done($.proxy(function(data) { + if(data.error || data.success === false) { + console.log("Something wrong happened"); + console.log(data.message); + } else { + alert({ + title: 'Success', + content: data.message + }); + } + }, this)); + }, + + /** + * Initizalize autocomplete jQuery UI Widget + * @param commandSearch + */ + initAutocomplete: function(commandSearch) { + var me = this; + var $bodyHtml = $('body, html'); + + var searchSource = []; + searchSource = this.populateCommands(searchSource); + searchSource = this.populateRedirects(searchSource); + + //auto complete initialize function + commandSearch.autocomplete({ + source: searchSource, + autoFocus: true, + position: { my: "left bottom", at: "left top", collision: "flip" }, + open: function() { + $bodyHtml.addClass('overflow-y-hidden'); + }, + close: function() { + $bodyHtml.removeClass('overflow-y-hidden'); + }, + select: function(e, ui) { + if (ui.item.isAjax === true) { + var ajaxController = _this.getAjaxController(ui.item.object); + if (ajaxController === false) return; + me.sendAjaxRequest(ajaxController); + } else { + window.location = ui.item.object.url; + return; + } + }, + messages: { + noResults: '', + results: function() {} + } + }); + }, + /** + * Handle keypress callback combinations + * @param e + * @returns {boolean} + */ + onDocumentKeyCallback: function(e) { + e = e || event; + if(e.ctrlKey && ( e.which === 96 )) { + _this.toggleHelpBar(); + } + }, + /** + * Toggle toolbar + */ + toggleHelpBar: function() { + _this.element.toggle(); + }, + /** + * Return Ajax controller for selected option from list + * @param commandObject + * @returns {*} + */ + getAjaxController: function(commandObject) { + return { + url: commandObject.url, + data: { + labels: commandObject.option, + massaction_prepare_key: 'labels' + } + }; + } + }); + + return $.mx.helperbar; +}); diff --git a/view/frontend/layout/default.xml b/src/view/frontend/layout/default.xml similarity index 100% rename from view/frontend/layout/default.xml rename to src/view/frontend/layout/default.xml diff --git a/view/frontend/templates/helperbar.phtml b/src/view/frontend/templates/helperbar.phtml similarity index 82% rename from view/frontend/templates/helperbar.phtml rename to src/view/frontend/templates/helperbar.phtml index 89fee54..6684427 100644 --- a/view/frontend/templates/helperbar.phtml +++ b/src/view/frontend/templates/helperbar.phtml @@ -1,6 +1,6 @@ isEnabled()): ?> @@ -23,8 +23,7 @@ "MX_HelperBar/js/helperbar": { "closeSelector": "#helper-bar-close", - "commandSearchSelector": "#helper-bar-command-search", - "commands": helper('Magento\Framework\Json\Helper\Data')->jsonEncode($block->getCommands()); ?> + "commandSearchSelector": "#helper-bar-command-search" } } } diff --git a/use.gif b/use.gif index cd2b5ce..e1c3458 100644 Binary files a/use.gif and b/use.gif differ diff --git a/view/base/web/js/helperbar.js b/view/base/web/js/helperbar.js deleted file mode 100644 index 21564b8..0000000 --- a/view/base/web/js/helperbar.js +++ /dev/null @@ -1,131 +0,0 @@ -define([ - "jquery", - 'Magento_Ui/js/modal/alert', - "jquery/ui" -], function($, alert) { - "use strict"; - - var _this; - - $.widget('mx.helperbar', { - options: { - //selectors - closeSelector: "", - commandSearchSelector: "", - //command text settings - commandArgumentSeparator: "for:", - commands: [] - }, - - _create: function() { - _this = this; - //setup binds and initAutocomplete - this.setupBinds(); - this.initAutocomplete($(this.options.commandSearchSelector)); - }, - /** - * Setup Binds for clicks and keypress - */ - setupBinds: function() { - $(this.options.closeSelector).on('click', this.toggleHelpBar); - $(document).on("keypress", this.onDocumentKeyCallback); - }, - /** - * Initizalize autocomplete jQuery UI Widget - * @param commandSearch - */ - initAutocomplete: function(commandSearch) { - var searchSource = [], - $bodyHtml = $('body, html'); - - for (var prefix in this.options.commands) { - if(!this.options.commands.hasOwnProperty(prefix)) continue; - var beforeArguments = prefix + " " + this.options.commandArgumentSeparator + " "; - var command = this.options.commands[prefix]; - for (var key in command.options) { - if (!command.options.hasOwnProperty(key)) continue; - searchSource.push(beforeArguments + command.options[key]); - } - } - - //auto complete initialize function - commandSearch.autocomplete({ - source: searchSource, - position: { my: "left bottom", at: "left top", collision: "flip" }, - open: function() { - $bodyHtml.addClass('overflow-y-hidden'); - }, - close: function() { - $bodyHtml.removeClass('overflow-y-hidden'); - }, - select: function(e, ui) { - var selectedOption = ui.item.value, - ajaxController = _this.getAjaxController(selectedOption); - - if (ajaxController === false) return false; - - $.ajax({ - dataType: 'json', - url: ajaxController.url, - data: ajaxController.data - }).done($.proxy(function(data) { - if (data.error || data.success === false) { - console.log("Something wrong happened"); - console.log(data.message); - } else { - alert({ - title: 'Success', - content: data.message - }); - } - }, this)); - }, - messages: { - noResults: '', - results: function() {} - } - }); - }, - /** - * Handle keypress callback combinations - * @param e - * @returns {boolean} - */ - onDocumentKeyCallback: function(e) { - e = e || event; - if(e.ctrlKey && ( e.which === 96 )) { - _this.toggleHelpBar(); - } - }, - /** - * Toggle toolbar - */ - toggleHelpBar: function() { - _this.element.toggle(); - }, - /** - * Return Ajax controller for selected option from list - * @param selectedOption - * @returns {*} - */ - getAjaxController: function(selectedOption) { - var splitCommandArgument = selectedOption.split(this.options.commandArgumentSeparator).map(function(ele){return ele.trim()}), - command = splitCommandArgument[0], - argument = splitCommandArgument[1]; - - if (command === undefined || argument === undefined) return false; - - var url = this.options.commands[command].url; - - return { - url: url, - data: { - labels: argument, - massaction_prepare_key: 'labels' - } - }; - } - }); - - return $.mx.helperbar; -});