277 lines
9.8 KiB
PHP
Executable File
277 lines
9.8 KiB
PHP
Executable File
<?php
|
|
/**
|
|
* Test constructions of layout files
|
|
*
|
|
* Copyright © Magento, Inc. All rights reserved.
|
|
* See COPYING.txt for license details.
|
|
*/
|
|
|
|
/**
|
|
* This test finds usages of modular view files, searched in non-modular context - it is obsolete and buggy
|
|
* functionality, initially introduced in Magento 2.
|
|
*
|
|
* The test goes through modular calls of view files, and finds out, whether there are theme non-modular files
|
|
* with the same path. Before fixing the bug, such call return theme files instead of modular files, which is
|
|
* incorrect. After fixing the bug, such calls will start returning modular files, which is not a file we got used
|
|
* to see, so such cases are probably should be fixed. The test finds such suspicious places.
|
|
*
|
|
* The test is intended to be deleted before Magento 2 release. With the release, having non-modular files with the
|
|
* same paths as modular ones, is legitimate.
|
|
*/
|
|
namespace Magento\Test\Integrity;
|
|
|
|
use Magento\Framework\Component\ComponentRegistrar;
|
|
|
|
/**
|
|
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
|
|
*/
|
|
class ViewFileReferenceTest extends \PHPUnit\Framework\TestCase
|
|
{
|
|
/**
|
|
* @var \Magento\Framework\View\Design\Fallback\Rule\RuleInterface
|
|
*/
|
|
protected static $_fallbackRule;
|
|
|
|
/**
|
|
* @var \Magento\Framework\View\Design\FileResolution\Fallback\StaticFile
|
|
*/
|
|
protected static $_viewFilesFallback;
|
|
|
|
/**
|
|
* @var \Magento\Framework\View\Design\FileResolution\Fallback\File
|
|
*/
|
|
protected static $_filesFallback;
|
|
|
|
/**
|
|
* @var array
|
|
*/
|
|
protected static $_checkThemeLocales = [];
|
|
|
|
/**
|
|
* @var \Magento\Theme\Model\Theme\Collection
|
|
*/
|
|
protected static $_themeCollection;
|
|
|
|
/**
|
|
* @var \Magento\Framework\Component\ComponentRegistrar
|
|
*/
|
|
protected static $_componentRegistrar;
|
|
|
|
public static function setUpBeforeClass(): void
|
|
{
|
|
$objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager();
|
|
$objectManager->configure(
|
|
['preferences' => [\Magento\Theme\Model\Theme::class => \Magento\Theme\Model\Theme\Data::class]]
|
|
);
|
|
|
|
self::$_componentRegistrar = $objectManager->get(\Magento\Framework\Component\ComponentRegistrar::class);
|
|
|
|
/** @var $fallbackPool \Magento\Framework\View\Design\Fallback\RulePool */
|
|
$fallbackPool = $objectManager->get(\Magento\Framework\View\Design\Fallback\RulePool::class);
|
|
self::$_fallbackRule = $fallbackPool->getRule(
|
|
$fallbackPool::TYPE_STATIC_FILE
|
|
);
|
|
|
|
self::$_viewFilesFallback = $objectManager->get(
|
|
\Magento\Framework\View\Design\FileResolution\Fallback\StaticFile::class
|
|
);
|
|
self::$_filesFallback = $objectManager->get(\Magento\Framework\View\Design\FileResolution\Fallback\File::class);
|
|
|
|
// Themes to be checked
|
|
self::$_themeCollection = $objectManager->get(\Magento\Theme\Model\Theme\Collection::class);
|
|
|
|
// 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_placeholder>', $locale, $patternDir);
|
|
if (is_dir($dir)) {
|
|
$result[] = $locale;
|
|
}
|
|
}
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* Return pattern for theme locale directories, where <locale_placeholder> 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 = '<locale_placeholder>';
|
|
$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;
|
|
}
|
|
}
|