diff --git a/src/Oro/Bundle/ConfigBundle/Command/ConfigViewCommand.php b/src/Oro/Bundle/ConfigBundle/Command/ConfigViewCommand.php new file mode 100644 index 00000000000..504cfaf8326 --- /dev/null +++ b/src/Oro/Bundle/ConfigBundle/Command/ConfigViewCommand.php @@ -0,0 +1,121 @@ +configManager = $configManager; + $this->formProvider = $formProvider; + + parent::__construct(); + } + + /** @noinspection PhpMissingParentCallCommonInspection */ + protected function configure(): void + { + $this + ->addArgument('name', InputArgument::REQUIRED, 'Config parameter name') + ->setDescription('Views a configuration value in the global scope.') + ->setHelp( + <<<'HELP' +The %command.name% command views a configuration value in the global scope. + + php %command.full_name% + +For example, to view the back-office and storefront URLs of an OroCommerce instance respectively: + + php %command.full_name% oro_ui.application_url + php %command.full_name% oro_website.url + php %command.full_name% oro_website.secure_url + +HELP + ) + ; + } + + /** + * Find a field node by name from the config tree + * + * @param GroupNodeDefinition $node + * @param string $fieldName + * @return ?FieldNodeDefinition null if no matching node was found + */ + protected function findFieldNode(GroupNodeDefinition $node, string $fieldName): ?FieldNodeDefinition + { + foreach ($node as $child) { + if ($child instanceof GroupNodeDefinition) { + $result = $this->findFieldNode($child, $fieldName); + if ($result !== null) { + return $result; + } + } elseif ($child instanceof FieldNodeDefinition) { + if ($child->getName() === $fieldName) { + return $child; + } + } + } + + return null; + } + + /** + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + * @noinspection PhpMissingParentCallCommonInspection + */ + protected function execute(InputInterface $input, OutputInterface $output): int + { + $symfonyStyle = new SymfonyStyle($input, $output); + $configManager = $this->configManager; + $fieldName = $input->getArgument('name'); + + $configTree = $this->formProvider->getTree(); + $configField = $this->findFieldNode($configTree, $fieldName); + if ($configField !== null + && $configField->getType() === OroEncodedPlaceholderPasswordType::class + ) { + $symfonyStyle->error("Encrypted value"); + return Command::INVALID; + } + + $value = $configManager->get($fieldName); + if (is_null($value) && $configField === null) { + $symfonyStyle->error("Unknown config field"); + return Command::FAILURE; + } + if (is_array($value) || is_object($value) || is_bool($value) || is_null($value)) { + $value = json_encode($value, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES); + } + if (!is_scalar($value)) { + $symfonyStyle->error("Value cannot be displayed"); + return Command::FAILURE; + } + + $output->writeln($value); + return Command::SUCCESS; + } +} diff --git a/src/Oro/Bundle/ConfigBundle/Resources/config/commands.yml b/src/Oro/Bundle/ConfigBundle/Resources/config/commands.yml index 59c3e074d1c..21c8edd91be 100644 --- a/src/Oro/Bundle/ConfigBundle/Resources/config/commands.yml +++ b/src/Oro/Bundle/ConfigBundle/Resources/config/commands.yml @@ -7,3 +7,10 @@ services: - '@oro_config.global' tags: - { name: console.command } + + Oro\Bundle\ConfigBundle\Command\ConfigViewCommand: + arguments: + - '@oro_config.global' + - '@oro_config.provider.system_configuration.form_provider' + tags: + - { name: console.command } diff --git a/src/Oro/Bundle/ConfigBundle/Tests/Functional/Command/ConfigViewCommandTest.php b/src/Oro/Bundle/ConfigBundle/Tests/Functional/Command/ConfigViewCommandTest.php new file mode 100644 index 00000000000..6a5bbdb82e8 --- /dev/null +++ b/src/Oro/Bundle/ConfigBundle/Tests/Functional/Command/ConfigViewCommandTest.php @@ -0,0 +1,70 @@ +createMock(ConfigManager::class); + $configManager->method('get')->will( + $this->returnCallback(function ($fieldName) { + $configValues = [ + // Plain values + 'oro_frontend.web_api' => true, + 'oro_locale.default_localization' => 1, + 'oro_sales.opportunity_statuses' => null, + 'oro_website.secure_url' => 'https://example.com', + 'oro_locale.enabled_localizations' => [1, 2, 3], + 'oro_example.dummy_object' => (object)['test' => 'value'], + + // Encrypted value + 'oro_example.secret_value' => 'Shh, keep it secret', + + // Nonsense value + 'oro_example.nonsense_value' => fopen('php://stdin', 'r'), + ]; + return $configValues[$fieldName] ?? null; + }) + ); + + $encryptedField = $this->createConfiguredMock(FieldNodeDefinition::class, [ + 'getName' => 'oro_example.secret_value', + 'getType' => OroEncodedPlaceholderPasswordType::class, + ]); + + $nullField = $this->createConfiguredMock(FieldNodeDefinition::class, [ + 'getName' => 'oro_sales.opportunity_statuses', + 'getType' => OpportunityStatusConfigType::class, + ]); + + $fieldGroup = $this->createConfiguredMock(GroupNodeDefinition::class, [ + 'getIterator' => new \ArrayIterator([ + $encryptedField, + $nullField, + ]), + ]); + + $formProvider = $this->createConfiguredMock(SystemConfigurationFormProvider::class, [ + 'getTree' => $fieldGroup, + ]); + + $this->command = new ConfigViewCommand( + $configManager, + $formProvider + ); + } + + private function validateConfigView(string $configFieldName, string $expectedValue): void + { + $commandTester = $this->doExecuteCommand($this->command, ['name' => $configFieldName]); + $this->assertOutputContains($commandTester, $expectedValue); + } + + public function testViewScalarValues(): void + { + $this->validateConfigView('oro_frontend.web_api', 'true'); + $this->validateConfigView('oro_locale.default_localization', '1'); + $this->validateConfigView('oro_sales.opportunity_statuses', 'null'); + $this->validateConfigView('oro_website.secure_url', 'https://example.com'); + } + + public function testViewArrayValue(): void + { + $this->validateConfigView('oro_locale.enabled_localizations', '[ 1, 2, 3 ]'); + } + + public function testViewObjectValue(): void + { + $this->validateConfigView('oro_example.dummy_object', '{ "test": "value" }'); + } + + public function testViewEncryptedValue(): void + { + $this->assertProducedError( + $this->doExecuteCommand($this->command, ['name' => 'oro_example.secret_value']), + "Encrypted value" + ); + } + + public function testViewInvalidValue(): void + { + $this->assertProducedError( + $this->doExecuteCommand($this->command, ['name' => 'oro_example.nonsense_value']), + "Value cannot be displayed" + ); + } + + public function testViewNonexistentValue(): void + { + $this->assertProducedError( + $this->doExecuteCommand($this->command, ['name' => 'oro_example.nonexistent_field']), + "Unknown config field" + ); + } +}