Skip to content

Commit 66389c1

Browse files
committed
AC-3483:: SVC false-positive: modifying system.xml file from another module
1 parent e5c1ea1 commit 66389c1

File tree

1 file changed

+165
-90
lines changed

1 file changed

+165
-90
lines changed

src/Analyzer/SystemXml/Analyzer.php

Lines changed: 165 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
use PHPSemVerChecker\Registry\Registry;
2727
use PHPSemVerChecker\Report\Report;
2828
use Magento\SemanticVersionChecker\Operation\SystemXml\FieldDuplicated;
29+
use RecursiveDirectoryIterator;
2930

3031
/**
3132
* Analyzes <kbd>system.xml</kbd> files:
@@ -82,7 +83,7 @@ public function analyze($registryBefore, $registryAfter)
8283

8384
//process removed files
8485
$this->reportRemovedFiles($removedModules, $registryBefore);
85-
86+
$duplicateNode = [];
8687
//process common files
8788
foreach ($commonModules as $moduleName) {
8889
$moduleNodesBefore = $nodesBefore[$moduleName];
@@ -93,13 +94,12 @@ public function analyze($registryBefore, $registryAfter)
9394
$beforeFile = $registryBefore->mapping[XmlRegistry::NODES_KEY][$moduleName];
9495
$this->reportRemovedNodes($beforeFile, $removedNodes);
9596
}
96-
print_r('Added Nodes');
97-
print_r($addedNodes);
97+
9898
if ($addedNodes) {
9999
$afterFile = $registryAfter->mapping[XmlRegistry::NODES_KEY][$moduleName];
100-
$duplicateNode = $this->reportAddedNodesWithDuplicateCheck($afterFile, $addedNodes, $moduleNodesBefore);
101-
print_r('Duplicate node ' . ($duplicateNode ? 'found' : 'not found'));
102-
print_r("After file ". $afterFile );
100+
$duplicateNode = $this->reportAddedNodesWithDuplicateCheck($afterFile, $addedNodes);
101+
102+
print_r($duplicateNode);
103103
if ($duplicateNode) {
104104
$this->reportDuplicateNodes($afterFile, $addedNodes);
105105
} else {
@@ -111,117 +111,148 @@ public function analyze($registryBefore, $registryAfter)
111111
}
112112

113113
/**
114-
* Check and Report duplicate nodes
114+
* Get Magento Base directory from the path
115115
*
116-
* @param $afterFile
117-
* @param $addedNodes
118-
* @param $moduleNodesBefore
119-
* @return bool|void
116+
* @param string $filePath
117+
* @return string|null
120118
*/
121-
private function reportAddedNodesWithDuplicateCheck($afterFile, $addedNodes, $moduleNodesBefore)
119+
private function getBaseDir($filePath)
122120
{
123-
print_r('Report Added Nodes function called.');
124-
$isDuplicate = false;
125-
126-
foreach ($addedNodes as $nodeId => $node) {
127-
$this->inspectObject($node);
128-
129-
// Check for duplicates by comparing node content except for 'id'
130-
foreach ($moduleNodesBefore as $existingNodeId => $existingNode) {
131-
if ($this->isDuplicateNode($node, $existingNode)) {
132-
$isDuplicate = true;
133-
break 2; // Break out of both loops if a duplicate is found
134-
}
121+
$currentDir = dirname($filePath);
122+
while ($currentDir !== '/' && $currentDir !== false) {
123+
// Check if current directory contains files unique to Magento root
124+
if (file_exists($currentDir . '/SECURITY.md')) {
125+
return $currentDir; // Found the Magento base directory
135126
}
127+
$currentDir = dirname($currentDir);
136128
}
137-
print_r("Duplicate node values: $isDuplicate");
138-
139-
return $isDuplicate;
129+
return null;
140130
}
141131

142-
private function inspectObject($object)
132+
/**
133+
* Search for system.xml files in both app/code and vendor directories, excluding the provided file.
134+
*
135+
* @param string $magentoBaseDir The base directory of Magento.
136+
* @param string $excludeFile The file to exclude from the search.
137+
* @return array An array of paths to system.xml files, excluding the specified file.
138+
*/
139+
private function searchSystemXmlFiles($magentoBaseDir, $excludeFile = null)
143140
{
144-
$reflection = new \ReflectionClass($object);
145-
$properties = $reflection->getProperties();
146-
$methods = $reflection->getMethods();
141+
$systemXmlFiles = [];
142+
$directoryToSearch = [
143+
$magentoBaseDir.'/app/code'
144+
];
147145

148-
echo "\nProperties:\n";
149-
foreach ($properties as $property) {
150-
// echo $property->getName() . "\n";
146+
// Check if 'vendor' directory exists, and only add it if it does
147+
if (is_dir($magentoBaseDir . '/vendor')) {
148+
$directoriesToSearch[] = $magentoBaseDir . '/vendor';
151149
}
152-
153-
echo "\nMethods:\n";
154-
foreach ($methods as $method) {
155-
// echo $method->getName() . "\n";
150+
foreach ($directoryToSearch as $directory) {
151+
$iterator = new \RecursiveIteratorIterator(new RecursiveDirectoryIterator($directory));
152+
foreach ($iterator as $file) {
153+
if ($file->getfileName() === 'system.xml') {
154+
$filePath = $file->getRealPath();
155+
if ($filePath !== $excludeFile) {
156+
$systemXmlFiles[] = $file->getRealPath();
157+
}
158+
}
159+
}
156160
}
161+
return $systemXmlFiles;
157162
}
158163

159164
/**
160-
* Check if node is duplicate or not
165+
* Check and Report duplicate nodes
161166
*
162-
* @param $node
163-
* @param $existingNode
167+
* @param $afterFile
168+
* @param $addedNodes
169+
* @param $moduleNodesBefore
164170
* @return bool
171+
* @throws \Exception
165172
*/
166-
private function isDuplicateNode($node, $existingNode)
173+
private function reportAddedNodesWithDuplicateCheck($afterFile, $addedNodes)
167174
{
168-
// Extract data from both nodes
169-
$newNodeData = $this->getNodeData($node);
170-
$existingNodeData = $this->getNodeData($existingNode);
171-
/* echo "\n New Node\n";
172-
print_r($newNodeData);
173-
echo "\n~~~~~~~~~~~~~~~\n";
174-
echo "\n Existing Node\n";
175-
print_r($existingNodeData);*/
176-
// Remove 'id' from both nodes for comparison, including from the path
177-
unset($newNodeData['id'], $existingNodeData['id']);
178-
$newNodePath = $this->removeLastPart($newNodeData['path']);
179-
$existingNodePath = $this->removeLastPart($existingNodeData['path']);
180-
181-
// print_r($newNodeData());
182-
183-
// Set the modified paths without the 'id'
184-
$newNodeData['path'] = $newNodePath;
185-
$existingNodeData['path'] = $existingNodePath;
186-
187-
// Compare the remaining data (skip source_model since it's not provided)
188-
foreach ($newNodeData as $key => $newValue) {
189-
if (isset($existingNodeData[$key])) {
190-
$existingValue = $existingNodeData[$key];
191-
echo "\nNew Value".$newValue ." !== Existing Value". $existingValue."\n";
192-
if (trim($newValue) == trim($existingValue)) {
193-
echo "\nCame in condition.\n";
194-
echo "Property '$key' does not match between nodes.\n";
195-
return true;
196-
}
175+
$baseDir = $this->getBaseDir($afterFile);
176+
$hasDuplicate = false;
177+
178+
foreach ($addedNodes as $nodeId => $node) {
179+
$newNodeData = $this->getNodeData($node);
180+
$nodePath = $newNodeData['path'];
181+
182+
// Extract section, group, and fieldId with error handling
183+
$extractedData = $this->extractSectionGroupField($nodePath);
184+
185+
// var_dump($extractedData);
186+
if ($extractedData === null) {
187+
// Skip the node if its path is invalid
188+
// echo "Skipping node with invalid path: $nodePath\n";
189+
continue;
190+
}
191+
192+
// Extract section, group, and fieldId
193+
list($sectionId, $groupId, $fieldId) = $extractedData;
194+
195+
196+
197+
// Call function to check if this field is duplicated in other system.xml files
198+
$isDuplicated = $this->isDuplicatedFieldInXml($baseDir, $sectionId, $groupId, $fieldId, $afterFile);
199+
200+
if ($isDuplicated) {
201+
$hasDuplicate = true;
202+
echo "\n\nDuplicate found for the field: $fieldId\n\n";
197203
} else {
198-
echo "Property '$key' missing in the existing node.\n";
199-
return false;
204+
205+
$hasDuplicate = false;
206+
echo "\n\nNo duplicates for the field: $fieldId\n";
200207
}
208+
209+
// $nodeIds = strrpos($newNodeData['path'],'/');
210+
211+
// Extract the substring after the last "/"
212+
// if ($nodeIds !== false) {
213+
// $fieldId = substr($newNodeData['path'], $nodeIds + 1);
214+
// }
215+
// echo "\nDuplicate $hasDuplicate for the\n";
216+
// echo "Checking for duplicates for Section: $sectionId, Group: $groupId, Field: $fieldId\n";
217+
218+
// return $hasDuplicate;
201219
}
202220

203-
// If all properties match (except 'id'), the nodes are duplicates
204-
echo "Nodes are duplicates based on their content.\n";
205-
return false;
221+
return $hasDuplicate;
222+
223+
// return $this->isDuplicatedFieldInXml($baseDir, $fieldId, $afterFile);
206224
}
207225

208-
/* private function getNodeData($node)
226+
/**
227+
* Method to extract section, group and field from the Node
228+
*
229+
* @param $nodePath
230+
* @return array|null
231+
*/
232+
private function extractSectionGroupField($nodePath)
209233
{
210-
return [
211-
'path' => $node->getPath(),
212-
'uniqueKey' => $node->getUniqueKey(),
213-
// Add more properties to compare if available, but exclude 'source_model'
214-
];
215-
}*/
234+
$parts = explode('/', $nodePath);
216235

217-
private function removeLastPart($path)
218-
{
219-
// Remove the last part of the path (usually the 'id') to compare node structure without it
220-
$pathParts = explode('/', $path);
221-
array_pop($pathParts); // Remove the last part
222-
return implode('/', $pathParts); // Return the path without the 'id'
236+
if (count($parts) < 3) {
237+
// Invalid path if there are fewer than 3 parts
238+
return null;
239+
}
240+
241+
$sectionId = $parts[0];
242+
$groupId = $parts[1];
243+
$fieldId = $parts[2];
244+
245+
246+
return [$sectionId, $groupId, $fieldId];
223247
}
224248

249+
/**
250+
* Method to get Node Data using reflection class
251+
*
252+
* @param $node
253+
* @return array
254+
* @throws \ReflectionException
255+
*/
225256
private function getNodeData($node)
226257
{
227258
$data = [];
@@ -316,7 +347,6 @@ private function reportAddedNodes(string $file, array $nodes)
316347
*/
317348
private function reportDuplicateNodes(string $file, array $nodes)
318349
{
319-
print_r('Duplicate Nodes switch case');
320350
foreach ($nodes as $node) {
321351
switch (true) {
322352
case $node instanceof Field:
@@ -365,4 +395,49 @@ private function reportRemovedNodes(string $file, array $nodes)
365395
}
366396
}
367397
}
398+
399+
/**
400+
* @param string|null $baseDir
401+
* @param string $sectionId
402+
* @param string $groupId
403+
* @param string $fieldId
404+
* @param string $afterFile
405+
* @return array
406+
* @throws \Exception
407+
*/
408+
private function isDuplicatedFieldInXml(?string $baseDir, string $sectionId, string $groupId, string $fieldId, string $afterFile): array
409+
{
410+
if ($baseDir) {
411+
$systemXmlFiles = $this->searchSystemXmlFiles($baseDir, $afterFile);
412+
413+
$result = [];
414+
415+
416+
foreach ($systemXmlFiles as $systemXmlFile) {
417+
$xmlContent = file_get_contents($systemXmlFile);
418+
try {
419+
$xml = new \SimpleXMLElement($xmlContent);
420+
} catch (\Exception $e) {
421+
echo "\nError parsing XML: " . $e->getMessage() . "\n";
422+
continue; // Skip this file if there's a parsing error
423+
}
424+
// Find <field> nodes with the given field ID
425+
// XPath to search for <field> within a specific section and group
426+
$fields = $xml->xpath("//section[@id='$sectionId']/group[@id='$groupId']/field[@id='$fieldId']");
427+
428+
429+
if (!empty($fields)) {
430+
echo "\n\nFound match in: $systemXmlFile\n";
431+
$result[] = [
432+
'status' => 'patch',
433+
'field' => $fieldId
434+
];
435+
// break ;
436+
}
437+
}
438+
439+
}
440+
441+
return $result;
442+
}
368443
}

0 commit comments

Comments
 (0)