Skip to content

Commit 3847442

Browse files
committed
feat: enhance VendorFileMapper with theme area validation and extraction
1 parent 869537a commit 3847442

1 file changed

Lines changed: 122 additions & 11 deletions

File tree

src/Service/VendorFileMapper.php

Lines changed: 122 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -11,20 +11,36 @@
1111

1212
class VendorFileMapper
1313
{
14+
/**
15+
* @param ComponentRegistrarInterface $componentRegistrar
16+
* @param DirectoryList $directoryList
17+
*/
1418
public function __construct(
1519
private readonly ComponentRegistrarInterface $componentRegistrar,
1620
private readonly DirectoryList $directoryList
17-
) {}
21+
) {
22+
}
1823

24+
/**
25+
* Map a vendor file path to the correct theme override path
26+
*
27+
* @param string $sourcePath
28+
* @param string $themePath
29+
* @return string
30+
* @throws RuntimeException
31+
*/
1932
public function mapToThemePath(string $sourcePath, string $themePath): string
2033
{
21-
// 1. Normalize: Ensure $sourcePath is relative from Magento Root if it's absolute
34+
// 1. Determine target theme area (frontend or adminhtml)
35+
$themeArea = $this->extractThemeArea($themePath);
36+
37+
// 2. Normalize: Ensure $sourcePath is relative from Magento Root if it's absolute
2238
$rootPath = rtrim($this->directoryList->getRoot(), '/');
2339
if (str_starts_with($sourcePath, $rootPath . '/')) {
2440
$sourcePath = substr($sourcePath, strlen($rootPath) + 1);
2541
}
2642

27-
// 2. Detect "Standard Module" Pattern (Priority 1) - Best for Local Modules & Composer Packages
43+
// 3. Detect "Standard Module" Pattern (Priority 1) - Best for Local Modules & Composer Packages
2844
$modules = $this->componentRegistrar->getPaths(ComponentRegistrar::MODULE);
2945
foreach ($modules as $moduleName => $path) {
3046
// Normalize module path relative to root
@@ -36,18 +52,14 @@ public function mapToThemePath(string $sourcePath, string $themePath): string
3652
if (str_starts_with($sourcePath, $path . '/')) {
3753
$pathInsideModule = substr($sourcePath, strlen($path) + 1);
3854

39-
// Remove view/frontend/ or view/base/ from the path
40-
$cleanPath = (string) preg_replace(
41-
'#^view/(frontend|base)/#',
42-
'',
43-
$pathInsideModule
44-
);
55+
// Validate area and extract clean path
56+
$cleanPath = $this->validateAndExtractViewPath($pathInsideModule, $themeArea, $sourcePath);
4557

4658
return rtrim($themePath, '/') . '/' . $moduleName . '/' . ltrim($cleanPath, '/');
4759
}
4860
}
4961

50-
// 3. Detect "Nested Module" Pattern (Priority 2) - Works for Hyva Compat & Vendor Themes
62+
// 4. Detect "Nested Module" Pattern (Priority 2) - Works for Hyva Compat & Vendor Themes
5163
// Regex search for a segment matching Vendor_Module (e.g. Magento_Catalog).
5264
// Captures (Group 1): "Vendor_Module"
5365
if (preg_match('/([A-Z][a-zA-Z0-9]*_[A-Z][a-zA-Z0-9]*)/', $sourcePath, $matches, PREG_OFFSET_CAPTURE)) {
@@ -56,10 +68,109 @@ public function mapToThemePath(string $sourcePath, string $themePath): string
5668
// Extract from Vendor_Module onwards (e.g. "Mollie_Payment/templates/file.phtml")
5769
$relativePath = substr($sourcePath, $offset);
5870

71+
// Validate that this path contains a valid view area
72+
// Extract the part after Vendor_Module to check
73+
$parts = explode('/', $relativePath, 3);
74+
if (count($parts) >= 3 && $parts[1] === 'view') {
75+
// Format: Vendor_Module/view/{area}/...
76+
$area = $parts[2];
77+
if (!$this->isAreaCompatible($area, $themeArea)) {
78+
throw new RuntimeException(
79+
sprintf(
80+
"Cannot map file from area '%s' to %s theme. File: %s",
81+
$area,
82+
$themeArea,
83+
$sourcePath
84+
)
85+
);
86+
}
87+
}
88+
5989
return rtrim($themePath, '/') . '/' . ltrim($relativePath, '/');
6090
}
6191

62-
// 4. Fallback
92+
// 5. Fallback
6393
throw new RuntimeException("Could not determine target module or theme structure for file: " . $sourcePath);
6494
}
95+
96+
/**
97+
* Extract theme area from theme path
98+
*
99+
* @param string $themePath
100+
* @return string
101+
* @throws RuntimeException
102+
*/
103+
private function extractThemeArea(string $themePath): string
104+
{
105+
if (preg_match('#/(frontend|adminhtml)/#', $themePath, $matches)) {
106+
return $matches[1];
107+
}
108+
109+
throw new RuntimeException("Could not determine theme area from path: " . $themePath);
110+
}
111+
112+
/**
113+
* Validate that the path is under view/{area}/ and compatible with target theme area
114+
*
115+
* @param string $pathInsideModule
116+
* @param string $targetArea
117+
* @param string $originalPath
118+
* @return string Clean path without view/{area}/ prefix
119+
* @throws RuntimeException
120+
*/
121+
private function validateAndExtractViewPath(
122+
string $pathInsideModule,
123+
string $targetArea,
124+
string $originalPath
125+
): string {
126+
// Check if path starts with view/{area}/
127+
if (!preg_match('#^view/([^/]+)/#', $pathInsideModule, $matches)) {
128+
throw new RuntimeException(
129+
sprintf(
130+
"File is not under a view/ directory. " .
131+
"Only files under view/{area}/ can be mapped to themes. File: %s",
132+
$originalPath
133+
)
134+
);
135+
}
136+
137+
$sourceArea = $matches[1];
138+
139+
// Validate area compatibility
140+
if (!$this->isAreaCompatible($sourceArea, $targetArea)) {
141+
throw new RuntimeException(
142+
sprintf(
143+
"Cannot map file from area '%s' to %s theme. File: %s",
144+
$sourceArea,
145+
$targetArea,
146+
$originalPath
147+
)
148+
);
149+
}
150+
151+
// Remove view/{area}/ prefix
152+
return (string) preg_replace('#^view/[^/]+/#', '', $pathInsideModule);
153+
}
154+
155+
/**
156+
* Check if source area is compatible with target theme area
157+
*
158+
* @param string $sourceArea
159+
* @param string $targetArea
160+
* @return bool
161+
*/
162+
private function isAreaCompatible(string $sourceArea, string $targetArea): bool
163+
{
164+
// Exact match
165+
if ($sourceArea === $targetArea) {
166+
return true;
167+
}
168+
169+
// 'base' area is compatible with both frontend and adminhtml
170+
if ($sourceArea === 'base') {
171+
return true;
172+
}
173+
174+
return false;
175+
}
65176
}

0 commit comments

Comments
 (0)