@@ -257,7 +257,7 @@ function () {
257257
258258 /**
259259 * Manual fallback to parse etc/frontend/di.xml for compatibility registration.
260- * This is required because CLI runs in global scope and might not load frontend DI args .
260+ * Use DOMDocument for robust XML parsing .
261261 *
262262 * @param string $moduleName
263263 * @return string|null
@@ -270,36 +270,66 @@ private function parseCompatModuleXml(string $moduleName): ?string
270270 return null ;
271271 }
272272
273- $ diPath = $ path . '/etc/frontend/di.xml ' ;
274- if (!file_exists ($ diPath )) {
275- return null ;
276- }
273+ // Check frontend/di.xml first, then global di.xml
274+ $ diFile = $ path . '/etc/frontend/di.xml ' ;
275+ $ globalDiFile = $ path . '/etc/di.xml ' ;
277276
278- $ xmlContent = file_get_contents ($ diPath );
279- if (!$ xmlContent ) {
280- return null ;
277+ $ filesToCheck = [];
278+ if (file_exists ($ diFile )) {
279+ $ filesToCheck [] = $ diFile ;
280+ }
281+ if (file_exists ($ globalDiFile )) {
282+ $ filesToCheck [] = $ globalDiFile ;
281283 }
282284
283- // Simple Regex to extract original_module for this compat module
284- // We look for the item where compat_module is defined as our module name
285- // Then extract the sibling item original_module
285+ foreach ($ filesToCheck as $ diPath ) {
286+ if (!is_file ($ diPath )) { // Extra check for symlinks/is_file
287+ continue ;
288+ }
286289
287- // Pattern to match the array structure for compatModules argument
288- // <item name="..." xsi:type="array">
289- // <item name="original_module" xsi:type="string">Original_Module</item>
290- // <item name="compat_module" xsi:type="string">Compat_Module</item>
291- // </item>
290+ $ content = file_get_contents ($ diPath );
291+ if (!$ content ) {
292+ continue ;
293+ }
292294
293- // Note: Order of items inside the array is not guaranteed, so we check if the block contains our module name as compat_module
295+ $ dom = new \DOMDocument ();
296+ // Suppress warnings for malformed XML or namespace issues
297+ $ libxmlState = libxml_use_internal_errors (true );
298+ $ dom ->loadXML ($ content );
299+ libxml_use_internal_errors ($ libxmlState );
294300
295- if (preg_match_all ('/<item\s+name="[^"]+"\s+xsi:type="array">(.*?)<\/item>/s ' , $ xmlContent , $ matches )) {
296- foreach ($ matches [1 ] as $ block ) {
297- if (str_contains ($ block , $ moduleName )) {
298- // This block likely belongs to our module. Extract original_module
299- if (preg_match ('/<item\s+name="original_module"\s+xsi:type="string">([^<]+)<\/item>/ ' , $ block , $ origMatch )) {
300- return $ origMatch [1 ];
301- }
302- }
301+ $ xpath = new \DOMXPath ($ dom );
302+ // Register namespace? Usually not needed if query is correct
303+ // Try to find the compatModules argument node
304+ $ query = "//argument[@name='compatModules'][@xsi:type='array']/item[@xsi:type='array'] " ;
305+ $ items = $ xpath ->query ($ query );
306+
307+ if ($ items === false || $ items ->length === 0 ) {
308+ continue ;
309+ }
310+
311+ foreach ($ items as $ item ) {
312+ // Check children items for compat_module/original_module keys
313+ $ compatModuleValue = null ;
314+ $ originalModuleValue = null ;
315+
316+ /** @var \DOMElement $item */
317+ $ childNodes = $ xpath ->query ('item ' , $ item );
318+ foreach ($ childNodes as $ childNode ) {
319+ /** @var \DOMElement $childNode */
320+ $ nameAttr = $ childNode ->getAttribute ('name ' );
321+ $ value = trim ($ childNode ->nodeValue );
322+
323+ if ($ nameAttr === 'compat_module ' ) {
324+ $ compatModuleValue = $ value ;
325+ } elseif ($ nameAttr === 'original_module ' ) {
326+ $ originalModuleValue = $ value ;
327+ }
328+ }
329+
330+ if ($ compatModuleValue === $ moduleName && $ originalModuleValue ) {
331+ return $ originalModuleValue ;
332+ }
303333 }
304334 }
305335 } catch (\Throwable $ e ) {
0 commit comments