magento2-docker/dev/tests/integration/testsuite/Magento/Test/Integrity/ViewFileReferenceTest.php

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;
}
}