341 lines
12 KiB
PHP
Executable File
341 lines
12 KiB
PHP
Executable File
<?php
|
|
/**
|
|
* Copyright © Magento, Inc. All rights reserved.
|
|
* See COPYING.txt for license details.
|
|
*/
|
|
|
|
namespace Magento\Test\Integrity;
|
|
|
|
use Magento\Framework\App\Filesystem\DirectoryList;
|
|
|
|
/**
|
|
* An integrity test that searches for references to static files and asserts that they are resolved via fallback
|
|
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
|
|
*/
|
|
class StaticFilesTest extends \PHPUnit\Framework\TestCase
|
|
{
|
|
/**
|
|
* @var \Magento\Framework\View\Design\FileResolution\Fallback\StaticFile
|
|
*/
|
|
private $fallback;
|
|
|
|
/**
|
|
* @var \Magento\Framework\View\Design\FileResolution\Fallback\Resolver\Simple
|
|
*/
|
|
private $explicitFallback;
|
|
|
|
/**
|
|
* @var \Magento\Framework\View\Design\Theme\FlyweightFactory
|
|
*/
|
|
private $themeRepo;
|
|
|
|
/**
|
|
* @var \Magento\Framework\View\DesignInterface
|
|
*/
|
|
private $design;
|
|
|
|
/**
|
|
* @var \Magento\Framework\View\Design\ThemeInterface
|
|
*/
|
|
private $baseTheme;
|
|
|
|
/**
|
|
* @var \Magento\Framework\View\Design\FileResolution\Fallback\Resolver\Alternative
|
|
*/
|
|
private $alternativeResolver;
|
|
|
|
/**
|
|
* Factory for simple rule
|
|
*
|
|
* @var \Magento\Framework\View\Design\Fallback\Rule\SimpleFactory
|
|
*/
|
|
private $simpleFactory;
|
|
|
|
/**
|
|
* @var \Magento\Framework\Filesystem
|
|
*/
|
|
private $filesystem;
|
|
|
|
protected function setUp(): void
|
|
{
|
|
$om = \Magento\TestFramework\Helper\Bootstrap::getObjectmanager();
|
|
$this->fallback = $om->get(\Magento\Framework\View\Design\FileResolution\Fallback\StaticFile::class);
|
|
$this->explicitFallback = $om->get(
|
|
\Magento\Framework\View\Design\FileResolution\Fallback\Resolver\Simple::class
|
|
);
|
|
$this->themeRepo = $om->get(\Magento\Framework\View\Design\Theme\FlyweightFactory::class);
|
|
$this->design = $om->get(\Magento\Framework\View\DesignInterface::class);
|
|
$this->baseTheme = $om->get(\Magento\Framework\View\Design\ThemeInterface::class);
|
|
$this->alternativeResolver = $om->get(
|
|
\Magento\Framework\View\Design\FileResolution\Fallback\Resolver\Alternative::class
|
|
);
|
|
$this->simpleFactory = $om->get(\Magento\Framework\View\Design\Fallback\Rule\SimpleFactory::class);
|
|
$this->filesystem = $om->get(\Magento\Framework\Filesystem::class);
|
|
}
|
|
|
|
/**
|
|
* Scan references to files from other static files and assert they are correct
|
|
*
|
|
* The CSS or LESS files may refer to other resources using `import` or url() notation
|
|
* We want to check integrity of all these references
|
|
* Note that the references may have syntax specific to the Magento preprocessing subsystem
|
|
*
|
|
* @param string $area
|
|
* @param string $themePath
|
|
* @param string $locale
|
|
* @param string $module
|
|
* @param string $filePath
|
|
* @param string $absolutePath
|
|
* @dataProvider referencesFromStaticFilesDataProvider
|
|
*/
|
|
public function testReferencesFromStaticFiles($area, $themePath, $locale, $module, $filePath, $absolutePath)
|
|
{
|
|
$contents = file_get_contents($absolutePath);
|
|
preg_match_all(
|
|
\Magento\Framework\View\Url\CssResolver::REGEX_CSS_RELATIVE_URLS,
|
|
$contents,
|
|
$matches
|
|
);
|
|
foreach ($matches[1] as $relatedResource) {
|
|
if (false !== strpos($relatedResource, '@')) { // unable to parse paths with LESS variables/mixins
|
|
continue;
|
|
}
|
|
list($relatedModule, $relatedPath) =
|
|
\Magento\Framework\View\Asset\Repository::extractModule($relatedResource);
|
|
if ($relatedModule) {
|
|
$fallbackModule = $relatedModule;
|
|
} else {
|
|
if ('less' == pathinfo($filePath, PATHINFO_EXTENSION)) {
|
|
/**
|
|
* The LESS library treats the related resources with relative links not in the same way as CSS:
|
|
* when another LESS file is included, it is embedded directly into the resulting document, but the
|
|
* relative paths of related resources are not adjusted accordingly to the new root file.
|
|
* Probably it is a bug of the LESS library.
|
|
*/
|
|
$this->markTestSkipped("Due to LESS library specifics, the '{$relatedResource}' cannot be tested.");
|
|
}
|
|
$fallbackModule = $module;
|
|
$relatedPath = \Magento\Framework\View\FileSystem::getRelatedPath($filePath, $relatedResource);
|
|
}
|
|
// the $relatedPath will be suitable for feeding to the fallback system
|
|
$staticFile = $this->getStaticFile($area, $themePath, $locale, $relatedPath, $fallbackModule);
|
|
if (empty($staticFile) && substr($relatedPath, 0, 2) === '..') {
|
|
//check if static file exists on lib level
|
|
$path = substr($relatedPath, 2);
|
|
$libDir = rtrim($this->filesystem->getDirectoryRead(DirectoryList::LIB_WEB)->getAbsolutePath(), '/');
|
|
$rule = $this->simpleFactory->create(['pattern' => $libDir]);
|
|
$params = ['area' => $area, 'theme' => $themePath, 'locale' => $locale];
|
|
$staticFile = $this->alternativeResolver->resolveFile($rule, $path, $params);
|
|
}
|
|
$this->assertNotEmpty(
|
|
$staticFile,
|
|
"The related resource cannot be resolved through fallback: '{$relatedResource}'"
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get a default theme path for specified area
|
|
*
|
|
* @param string $area
|
|
* @return string
|
|
* @throws \LogicException
|
|
*/
|
|
private function getDefaultThemePath($area)
|
|
{
|
|
switch ($area) {
|
|
case 'frontend':
|
|
return $this->design->getConfigurationDesignTheme($area);
|
|
case 'adminhtml':
|
|
return $this->design->getConfigurationDesignTheme($area);
|
|
case 'doc':
|
|
return 'Magento/blank';
|
|
default:
|
|
throw new \LogicException('Unable to determine theme path');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get static file through fallback system using specified params
|
|
*
|
|
* @param string $area
|
|
* @param string|\Magento\Framework\View\Design\ThemeInterface $theme - either theme path (string) or theme object
|
|
* @param string $locale
|
|
* @param string $filePath
|
|
* @param string $module
|
|
* @param bool $isExplicit
|
|
* @return bool|string
|
|
*/
|
|
private function getStaticFile($area, $theme, $locale, $filePath, $module = null, $isExplicit = false)
|
|
{
|
|
if ($area == 'base') {
|
|
$theme = $this->baseTheme;
|
|
}
|
|
if (!is_object($theme)) {
|
|
$themePath = $theme ?: $this->getDefaultThemePath($area);
|
|
$theme = $this->themeRepo->create($themePath, $area);
|
|
}
|
|
if ($isExplicit) {
|
|
$type = \Magento\Framework\View\Design\Fallback\RulePool::TYPE_STATIC_FILE;
|
|
return $this->explicitFallback->resolve($type, $filePath, $area, $theme, $locale, $module);
|
|
}
|
|
return $this->fallback->getFile($area, $theme, $locale, $filePath, $module);
|
|
}
|
|
|
|
/**
|
|
* @return array
|
|
*/
|
|
public function referencesFromStaticFilesDataProvider()
|
|
{
|
|
return \Magento\Framework\App\Utility\Files::init()->getStaticPreProcessingFiles('*.{less,css}');
|
|
}
|
|
|
|
/**
|
|
* There must be either .css or .less file, because if there are both, then .less will not be found by fallback
|
|
*
|
|
* @param string $area
|
|
* @param string $themePath
|
|
* @param string $locale
|
|
* @param string $module
|
|
* @param string $filePath
|
|
* @dataProvider lessNotConfusedWithCssDataProvider
|
|
*/
|
|
public function testLessNotConfusedWithCss($area, $themePath, $locale, $module, $filePath)
|
|
{
|
|
if (false !== strpos($filePath, 'widgets.css')) {
|
|
$filePath .= '';
|
|
}
|
|
$fileName = pathinfo($filePath, PATHINFO_FILENAME);
|
|
$dirName = dirname($filePath);
|
|
if ('.' == $dirName) {
|
|
$dirName = '';
|
|
} else {
|
|
$dirName .= '/';
|
|
}
|
|
$cssPath = $dirName . $fileName . '.css';
|
|
$lessPath = $dirName . $fileName . '.less';
|
|
$cssFile = $this->getStaticFile($area, $themePath, $locale, $cssPath, $module, true);
|
|
$lessFile = $this->getStaticFile($area, $themePath, $locale, $lessPath, $module, true);
|
|
$this->assertFalse(
|
|
$cssFile && $lessFile,
|
|
"A resource file of only one type must exist. Both found: '$cssFile' and '$lessFile'"
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @return array
|
|
*/
|
|
public function lessNotConfusedWithCssDataProvider()
|
|
{
|
|
return \Magento\Framework\App\Utility\Files::init()->getStaticPreProcessingFiles('*.{less,css}');
|
|
}
|
|
|
|
/**
|
|
* Test if references $this->getViewFileUrl() in .phtml-files are correct
|
|
*
|
|
* @param string $phtmlFile
|
|
* @param string $area
|
|
* @param string $themePath
|
|
* @param string $fileId
|
|
* @dataProvider referencesFromPhtmlFilesDataProvider
|
|
*/
|
|
public function testReferencesFromPhtmlFiles($phtmlFile, $area, $themePath, $fileId)
|
|
{
|
|
list($module, $filePath) = \Magento\Framework\View\Asset\Repository::extractModule($fileId);
|
|
$this->assertNotEmpty(
|
|
$this->getStaticFile($area, $themePath, 'en_US', $filePath, $module),
|
|
"Unable to locate '{$fileId}' reference from {$phtmlFile}"
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @return array
|
|
*/
|
|
public function referencesFromPhtmlFilesDataProvider()
|
|
{
|
|
$result = [];
|
|
foreach (\Magento\Framework\App\Utility\Files::init()->getPhtmlFiles(true, false) as $info) {
|
|
list($area, $themePath, , , $file) = $info;
|
|
foreach ($this->collectGetViewFileUrl($file) as $fileId) {
|
|
$result[] = [$file, $area, $themePath, $fileId];
|
|
}
|
|
}
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* Find invocations of $block->getViewFileUrl() and extract the first argument value
|
|
*
|
|
* @param string $file
|
|
* @return array
|
|
*/
|
|
private function collectGetViewFileUrl($file)
|
|
{
|
|
$result = [];
|
|
if (preg_match_all('/\$block->getViewFileUrl\(\'([^\']+?)\'\)/', file_get_contents($file), $matches)) {
|
|
foreach ($matches[1] as $fileId) {
|
|
$result[] = $fileId;
|
|
}
|
|
}
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* @param string $layoutFile
|
|
* @param string $area
|
|
* @param string $themePath
|
|
* @param string $fileId
|
|
* @dataProvider referencesFromLayoutFilesDataProvider
|
|
*/
|
|
public function testReferencesFromLayoutFiles($layoutFile, $area, $themePath, $fileId)
|
|
{
|
|
list($module, $filePath) = \Magento\Framework\View\Asset\Repository::extractModule($fileId);
|
|
$this->assertNotEmpty(
|
|
$this->getStaticFile($area, $themePath, 'en_US', $filePath, $module),
|
|
"Unable to locate '{$fileId}' reference from layout XML in {$layoutFile}"
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @return array
|
|
*/
|
|
public function referencesFromLayoutFilesDataProvider()
|
|
{
|
|
$result = [];
|
|
$files = \Magento\Framework\App\Utility\Files::init()->getLayoutFiles(['with_metainfo' => true], false);
|
|
foreach ($files as $metaInfo) {
|
|
list($area, $themePath, , , $file) = array_pad($metaInfo, 5, null);
|
|
|
|
if (!is_string($file)) {
|
|
$this->addWarning(
|
|
'Wrong layout file configuration provided. The `file` meta info must be the type of string'
|
|
);
|
|
continue;
|
|
}
|
|
foreach ($this->collectFileIdsFromLayout($file) as $fileId) {
|
|
$result[] = [$file, $area, $themePath, $fileId];
|
|
}
|
|
}
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* Collect view file declarations in layout XML-files
|
|
*
|
|
* @param string $file
|
|
* @return array
|
|
*/
|
|
private function collectFileIdsFromLayout($file)
|
|
{
|
|
$xml = simplexml_load_file($file);
|
|
$elements = $xml->xpath('//head/css|link|script');
|
|
$result = [];
|
|
if ($elements) {
|
|
foreach ($elements as $node) {
|
|
$result[] = (string)$node;
|
|
}
|
|
}
|
|
return $result;
|
|
}
|
|
}
|