Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 14 additions & 3 deletions app/code/Magento/Config/Model/Config/PathValidator.php
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?php
/**
* Copyright 2017 Adobe
* Copyright 2026 Adobe
* All Rights Reserved.
*/

Expand Down Expand Up @@ -34,7 +34,9 @@ public function __construct(Structure $structure)
/**
* Checks whether the config path present in configuration structure.
*
* @param string $path The config path
* Allows partial path validation: if any config path starts with the given path, it's valid.
*
* @param string $path The config path (can be partial)
* @return true The result of validation
* @throws ValidatorException If provided path is not valid
* @since 101.0.0
Expand All @@ -48,7 +50,16 @@ public function validate($path)

$allPaths = $this->structure->getFieldPaths();

if (!array_key_exists($path, $allPaths)) {
// Allow exact or partial path match
$found = false;
foreach (array_keys($allPaths) as $fullPath) {
if ($fullPath === $path || str_starts_with($fullPath, $path . '/')) {
$found = true;
break;
}
}

if (!$found) {
throw new ValidatorException(__('The "%1" path doesn\'t exist. Verify and try again.', $path));
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?php
/**
* Copyright 2017 Adobe
* Copyright 2026 Adobe
* All Rights Reserved.
*/

Expand Down Expand Up @@ -601,4 +601,146 @@ private function getPrivateProperty($object, $property)
$prop->setAccessible(true);
return $prop->getValue($object);
}

/**
* Test PathValidator partial path validation.
*
* @return void
*/
public function testPathValidatorAllowsPartialPath(): void
{
$partialPath = 'web/secure';
$fullPaths = [
'web/secure/base_url',
'web/secure/use_in_frontend',
'web/unsecure/base_url',
];
$structureMock = $this->getMockBuilder(\Magento\Config\Model\Config\Structure::class)
->disableOriginalConstructor()
->getMock();
$structureMock->method('getElementByConfigPath')->willReturn(null);
$structureMock->method('getFieldPaths')->willReturn(array_fill_keys($fullPaths, true));

$validator = new PathValidator($structureMock);
$this->assertTrue($validator->validate($partialPath));
}

/**
* Test PathValidator throws for non-existent partial path.
*
* @return void
*/
public function testPathValidatorThrowsForInvalidPartialPath(): void
{
$partialPath = 'web/does_not_exist';
$fullPaths = [
'web/secure/base_url',
'web/secure/use_in_frontend',
'web/unsecure/base_url',
];
$structureMock = $this->getMockBuilder(\Magento\Config\Model\Config\Structure::class)
->disableOriginalConstructor()
->getMock();
$structureMock->method('getElementByConfigPath')->willReturn(null);
$structureMock->method('getFieldPaths')->willReturn(array_fill_keys($fullPaths, true));

$validator = new PathValidator($structureMock);
$this->expectException(\Magento\Framework\Exception\ValidatorException::class);
$validator->validate($partialPath);
}

/**
* Test config:show with partial path and scope.
*/
public function testExecuteWithPartialPathAndScope(): void
{
$partialPath = 'web/secure';
$resolvedConfigPath = 'websites/1/web/secure';
$arrayConfigValue = [
'base_url' => 'https://example.com/',
'use_in_frontend' => 1
];
$this->scopeValidatorMock->expects($this->once())
->method('isValid')
->with('websites', '1')
->willReturn(true);
$this->pathResolverMock->expects($this->once())
->method('resolve')
->with($partialPath, 'websites', '1')
->willReturn($resolvedConfigPath);
$this->configSourceMock->expects($this->once())
->method('get')
->with($resolvedConfigPath)
->willReturn($arrayConfigValue);
$callCount = 0;
$this->valueProcessorMock->expects($this->exactly(2))
->method('process')
->willReturnCallback(function ($scope, $scopeCode, $value, $path) use ($partialPath, &$callCount) {
$callCount++;
if ($callCount === 1) {
$this->assertEquals('websites', $scope);
$this->assertEquals('1', $scopeCode);
$this->assertEquals('https://example.com/', $value);
$this->assertEquals($partialPath . '/base_url', $path);
return 'https://example.com/';
} elseif ($callCount === 2) {
$this->assertEquals('websites', $scope);
$this->assertEquals('1', $scopeCode);
$this->assertEquals(1, $value);
$this->assertEquals($partialPath . '/use_in_frontend', $path);
return '1';
}
return '';
});
$this->emulatedAreProcessorMock->expects($this->once())
->method('process')
->willReturnCallback(function ($function) {
return $function();
});
$this->localeEmulatorMock->expects($this->once())
->method('emulate')
->willReturnCallback(function ($callback) {
return $callback();
});
$tester = $this->getConfigShowCommandTester($partialPath, 'websites', '1');
$this->assertEquals(Cli::RETURN_SUCCESS, $tester->getStatusCode());
$display = $tester->getDisplay();
$this->assertStringContainsString($partialPath . '/base_url - https://example.com/', $display);
$this->assertStringContainsString($partialPath . '/use_in_frontend - 1', $display);
}

/**
* Test config:show with partial path, scope, and scope code, but no matches (should throw ValidatorException).
*/
public function testExecuteWithPartialPathAndScopeNoMatches(): void
{
$partialPath = 'web/does_not_exist';
$exception = new \Magento\Framework\Exception\ValidatorException(
__("The \"%1\" path doesn't exist. Verify and try again.", $partialPath)
);
$this->scopeValidatorMock->expects($this->once())
->method('isValid')
->with('stores', '2')
->willReturn(true);
$this->pathValidatorMock->expects($this->once())
->method('validate')
->with($partialPath)
->willThrowException($exception);
$this->emulatedAreProcessorMock->expects($this->once())
->method('process')
->willReturnCallback(function ($function) {
return $function();
});
$this->localeEmulatorMock->expects($this->once())
->method('emulate')
->willReturnCallback(function ($callback) {
return $callback();
});
$tester = $this->getConfigShowCommandTester($partialPath, 'stores', '2');
$this->assertEquals(Cli::RETURN_FAILURE, $tester->getStatusCode());
$this->assertStringContainsString(
"The \"$partialPath\" path doesn't exist. Verify and try again.",
$tester->getDisplay()
);
}
}