configure( ['preferences' => ['Magento\Theme\Model\Theme' => 'Magento\Theme\Model\Theme\Data']] ); self::$_componentRegistrar = $objectManager->get('Magento\Framework\Component\ComponentRegistrar'); /** @var $fallbackPool \Magento\Framework\View\Design\Fallback\RulePool */ $fallbackPool = $objectManager->get('Magento\Framework\View\Design\Fallback\RulePool'); self::$_fallbackRule = $fallbackPool->getRule( $fallbackPool::TYPE_STATIC_FILE ); self::$_viewFilesFallback = $objectManager->get( 'Magento\Framework\View\Design\FileResolution\Fallback\StaticFile' ); self::$_filesFallback = $objectManager->get('Magento\Framework\View\Design\FileResolution\Fallback\File'); // Themes to be checked self::$_themeCollection = $objectManager->get('Magento\Theme\Model\Theme\Collection'); // Compose list of locales, needed to be checked for themes self::$_checkThemeLocales = []; foreach (self::$_themeCollection as $theme) { $themeLocales = self::_getThemeLocales($theme); $themeLocales[] = null; // Default non-localized file will need to be checked as well self::$_checkThemeLocales[$theme->getFullPath()] = $themeLocales; } } /** * Return array of locales, supported by the theme * * @param \Magento\Framework\View\Design\ThemeInterface $theme * @return array */ protected static function _getThemeLocales(\Magento\Framework\View\Design\ThemeInterface $theme) { $result = []; $patternDir = self::_getLocalePatternDir($theme); foreach (\ResourceBundle::getLocales('') as $locale) { $dir = str_replace('', $locale, $patternDir); if (is_dir($dir)) { $result[] = $locale; } } return $result; } /** * Return pattern for theme locale directories, where is placed to mark a locale's location. * * @param \Magento\Framework\View\Design\ThemeInterface $theme * @return string * @throws \Exception */ protected static function _getLocalePatternDir(\Magento\Framework\View\Design\ThemeInterface $theme) { $localePlaceholder = ''; $params = ['area' => $theme->getArea(), 'theme' => $theme, 'locale' => $localePlaceholder]; $patternDirs = self::$_fallbackRule->getPatternDirs($params); $themePath = self::$_componentRegistrar->getPath( \Magento\Framework\Component\ComponentRegistrar::THEME, $theme->getFullPath() ); foreach ($patternDirs as $patternDir) { $patternPath = $patternDir . '/'; if ((strpos($patternPath, $themePath) !== false) // It is theme's directory && (strpos($patternPath, $localePlaceholder) !== false) // It is localized directory ) { return $patternDir; } } throw new \Exception('Unable to determine theme locale path'); } /** * @param string $modularCall * @param array $usages * @param null|string $area * @dataProvider modularFallbackDataProvider */ public function testModularFallback($modularCall, array $usages, $area) { list(, $file) = explode(\Magento\Framework\View\Asset\Repository::FILE_ID_SEPARATOR, $modularCall); $wrongResolutions = []; foreach (self::$_themeCollection as $theme) { if ($area && $theme->getArea() != $area) { continue; } $found = $this->_getFileResolutions($theme, $file); $wrongResolutions = array_merge($wrongResolutions, $found); } if ($wrongResolutions) { // If file is found, then old functionality (find modular files in non-modular locations) is used $message = sprintf( "Found modular call:\n %s in\n %s\n which may resolve to non-modular location(s):\n %s", $modularCall, implode(', ', $usages), implode(', ', $wrongResolutions) ); $this->fail($message); } } /** * Resolves file to find its fallback'ed paths * * @param \Magento\Framework\View\Design\ThemeInterface $theme * @param string $file * @return array */ protected function _getFileResolutions(\Magento\Framework\View\Design\ThemeInterface $theme, $file) { $found = []; $fileResolved = self::$_filesFallback->getFile($theme->getArea(), $theme, $file); if (file_exists($fileResolved)) { $found[$fileResolved] = $fileResolved; } foreach (self::$_checkThemeLocales[$theme->getFullPath()] as $locale) { $fileResolved = self::$_viewFilesFallback->getFile($theme->getArea(), $theme, $locale, $file); if (file_exists($fileResolved)) { $found[$fileResolved] = $fileResolved; } } return $found; } /** * @return array */ public static function modularFallbackDataProvider() { $result = []; foreach (self::_getFilesToProcess() as $file) { $file = (string)$file; $modulePattern = '[A-Z][a-z]+_[A-Z][a-z]+'; $filePattern = '[[:alnum:]_/-]+\\.[[:alnum:]_./-]+'; $pattern = '#' . $modulePattern . preg_quote(\Magento\Framework\View\Asset\Repository::FILE_ID_SEPARATOR) . $filePattern . '#S'; if (!preg_match_all($pattern, file_get_contents($file), $matches)) { continue; } $area = self::_getArea($file); foreach ($matches[0] as $modularCall) { $dataSetKey = $modularCall . ' @ ' . ($area ?: 'any area'); if (!isset($result[$dataSetKey])) { $result[$dataSetKey] = ['modularCall' => $modularCall, 'usages' => [], 'area' => $area]; } $result[$dataSetKey]['usages'][$file] = $file; } } return $result; } /** * Return list of files, that must be processed, searching for modular calls to view files * * @return array */ protected static function _getFilesToProcess() { $result = []; $componentRegistrar = new \Magento\Framework\Component\ComponentRegistrar(); $dirs = array_merge( $componentRegistrar->getPaths(ComponentRegistrar::MODULE), $componentRegistrar->getPaths(ComponentRegistrar::THEME) ); foreach ($dirs as $dir) { $iterator = new \RecursiveIteratorIterator( new \RecursiveDirectoryIterator($dir, \RecursiveDirectoryIterator::SKIP_DOTS) ); $result = array_merge($result, iterator_to_array($iterator)); } return $result; } /** * Get the area, where file is located. * * Null is returned, if the file is not within an area, e.g. it is a model/block/helper php-file. * * @param string $file * @return string|null */ protected static function _getArea($file) { $file = str_replace('\\', '/', $file); $areaPatterns = []; $componentRegistrar = new ComponentRegistrar(); foreach ($componentRegistrar->getPaths(ComponentRegistrar::THEME) as $themeDir) { $areaPatterns[] = '#' . $themeDir . '/([^/]+)/#S'; } foreach ($componentRegistrar->getPaths(ComponentRegistrar::MODULE) as $moduleDir) { $areaPatterns[] = '#' . $moduleDir . '/view/([^/]+)/#S'; } foreach ($areaPatterns as $pattern) { if (preg_match($pattern, $file, $matches)) { return $matches[1]; } } return null; } }