Skip to content
Open
121 changes: 121 additions & 0 deletions src/Oro/Bundle/ConfigBundle/Command/ConfigViewCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
<?php

declare(strict_types=1);

namespace Oro\Bundle\ConfigBundle\Command;

use Oro\Bundle\ConfigBundle\Config\ConfigManager;
use Oro\Bundle\ConfigBundle\Config\Tree\FieldNodeDefinition;
use Oro\Bundle\ConfigBundle\Config\Tree\GroupNodeDefinition;
use Oro\Bundle\ConfigBundle\Provider\SystemConfigurationFormProvider;
use Oro\Bundle\FormBundle\Form\Type\OroEncodedPlaceholderPasswordType;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;

/**
* Views a configuration value in the global scope.
*/
class ConfigViewCommand extends Command
{
protected static $defaultName = 'oro:config:view';

private ConfigManager $configManager;
private SystemConfigurationFormProvider $formProvider;

public function __construct(
ConfigManager $configManager,
SystemConfigurationFormProvider $formProvider,
) {
$this->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 <info>%command.name%</info> command views a configuration value in the global scope.

<info>php %command.full_name% <name></info>

For example, to view the back-office and storefront URLs of an OroCommerce instance respectively:

<info>php %command.full_name% oro_ui.application_url</info>
<info>php %command.full_name% oro_website.url</info>
<info>php %command.full_name% oro_website.secure_url</info>

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;
}
}
7 changes: 7 additions & 0 deletions src/Oro/Bundle/ConfigBundle/Resources/config/commands.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
<?php

declare(strict_types=1);

namespace Oro\Bundle\ConfigBundle\Tests\Functional\Command;

use Oro\Bundle\TestFrameworkBundle\Test\WebTestCase;

class ConfigViewCommandTest extends WebTestCase
{
public function testBoolTrue(): void
{
$output = self::runCommand('oro:config:view', ['oro_frontend.web_api']);
self::assertEquals('true', $output);
}

public function testBoolFalse(): void
{
$output = self::runCommand('oro:config:view', ['oro_report.display_sql_query']);
self::assertEquals('false', $output);
}

public function testNull(): void
{
$output = self::runCommand('oro:config:view', ['oro_shopping_list.default_guest_shopping_list_owner']);
self::assertEquals('null', $output);
}

public function testInt(): void
{
$output = self::runCommand('oro:config:view', ['oro_product.new_arrivals_products_segment_id']);
self::assertEquals('2', $output);
}

public function testFloat(): void
{
$output = self::runCommand('oro:config:view', ['oro_seo.sitemap_priority_product']);
self::assertEquals('0.5', $output);
}

public function testString(): void
{
$output = self::runCommand('oro:config:view', ['oro_pricing_pro.default_currency']);
self::assertEquals('USD', $output);
}

public function testArray(): void
{
$output = self::runCommand('oro:config:view', ['oro_shipping.length_units']);
self::assertEquals('[ "inch", "foot", "cm", "m" ]', $output);
}

public function testAssocArray(): void
{
$output = self::runCommand('oro:config:view', ['oro_locale.quarter_start']);
self::assertEquals('{ "month": "1", "day": "1" }', $output);
}

public function testEncryptedValue(): void
{
$output = self::runCommand('oro:config:view', ['oro_google_integration.client_secret']);
self::assertEquals('[ERROR] Encrypted value', $output);
}

public function testNonexistentField(): void
{
$output = self::runCommand('oro:config:view', ['oro_example.nonexistent_field']);
self::assertEquals('[ERROR] Unknown config field', $output);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
<?php

declare(strict_types=1);

namespace Oro\Bundle\ConfigBundle\Tests\Unit\Command;

use Oro\Bundle\ConfigBundle\Command\ConfigViewCommand;
use Oro\Bundle\ConfigBundle\Config\ConfigManager;
use Oro\Bundle\ConfigBundle\Config\Tree\FieldNodeDefinition;
use Oro\Bundle\ConfigBundle\Config\Tree\GroupNodeDefinition;
use Oro\Bundle\ConfigBundle\Provider\SystemConfigurationFormProvider;
use Oro\Bundle\FormBundle\Form\Type\OroEncodedPlaceholderPasswordType;
use Oro\Bundle\SalesBundle\Form\Type\OpportunityStatusConfigType;
use Oro\Component\Testing\Command\CommandTestingTrait;
use PHPUnit\Framework\TestCase;

class ConfigViewCommandTest extends TestCase
{
use CommandTestingTrait;

private ConfigViewCommand $command;

protected function setUp(): void
{
$configManager = $this->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"
);
}
}