2626use PHPSemVerChecker \Registry \Registry ;
2727use PHPSemVerChecker \Report \Report ;
2828use 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 "\n Properties: \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