diff --git a/lib/internal/Magento/Framework/Css/PreProcessor/Instruction/MagentoImport.php b/lib/internal/Magento/Framework/Css/PreProcessor/Instruction/MagentoImport.php index ce11bc21c25d8..831fb29022a6c 100644 --- a/lib/internal/Magento/Framework/Css/PreProcessor/Instruction/MagentoImport.php +++ b/lib/internal/Magento/Framework/Css/PreProcessor/Instruction/MagentoImport.php @@ -3,17 +3,25 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\Framework\Css\PreProcessor\Instruction; +use Magento\Framework\App\DeploymentConfig; use Magento\Framework\App\ObjectManager; use Magento\Framework\Css\PreProcessor\ErrorHandlerInterface; +use Magento\Framework\Module\Manager as ModuleManager; use Magento\Framework\ObjectManager\ResetAfterRequestInterface; use Magento\Framework\View\Asset\File\FallbackContext; use Magento\Framework\View\Asset\LocalInterface; use Magento\Framework\View\Asset\PreProcessorInterface; +use Magento\Framework\View\Asset\Repository as AssetRepository; +use Magento\Framework\View\Design\Theme\ListInterface as ThemeListInterface; use Magento\Framework\View\Design\Theme\ThemeProviderInterface; +use Magento\Framework\View\Design\ThemeInterface; use Magento\Framework\View\DesignInterface; use Magento\Framework\View\File\CollectorInterface; +use Magento\Framework\View\Asset\PreProcessor\Chain; /** * @magento_import instruction preprocessor @@ -27,6 +35,8 @@ class MagentoImport implements PreProcessorInterface, ResetAfterRequestInterface public const REPLACE_PATTERN = '#//@magento_import(?P\s+\(reference\))?\s+[\'\"](?P(?![/\\\]|\w:[/\\\])[^\"\']+)[\'\"]\s*?;#'; + private const CONFIG_PATH_SCD_ONLY_ENABLED_MODULES = 'static_content_only_enabled_modules'; + /** * @var DesignInterface */ @@ -43,12 +53,12 @@ class MagentoImport implements PreProcessorInterface, ResetAfterRequestInterface protected $errorHandler; /** - * @var \Magento\Framework\View\Asset\Repository + * @var AssetRepository */ protected $assetRepo; /** - * @var \Magento\Framework\View\Design\Theme\ListInterface + * @var ThemeListInterface * @deprecated 100.0.2 * @see not used */ @@ -59,31 +69,47 @@ class MagentoImport implements PreProcessorInterface, ResetAfterRequestInterface */ private $themeProvider; + /** + * @var DeploymentConfig + */ + private DeploymentConfig $deploymentConfig; + + /** + * @var ModuleManager + */ + private ModuleManager $moduleManager; + /** * @param DesignInterface $design * @param CollectorInterface $fileSource * @param ErrorHandlerInterface $errorHandler - * @param \Magento\Framework\View\Asset\Repository $assetRepo - * @param \Magento\Framework\View\Design\Theme\ListInterface $themeList + * @param AssetRepository $assetRepo + * @param ThemeListInterface $themeList + * @param DeploymentConfig|null $deploymentConfig + * @param ModuleManager|null $moduleManager */ public function __construct( DesignInterface $design, CollectorInterface $fileSource, ErrorHandlerInterface $errorHandler, - \Magento\Framework\View\Asset\Repository $assetRepo, - \Magento\Framework\View\Design\Theme\ListInterface $themeList + AssetRepository $assetRepo, + ThemeListInterface $themeList, + ?DeploymentConfig $deploymentConfig = null, + ?ModuleManager $moduleManager = null ) { $this->design = $design; $this->fileSource = $fileSource; $this->errorHandler = $errorHandler; $this->assetRepo = $assetRepo; $this->themeList = $themeList; + $this->deploymentConfig = $deploymentConfig ?? ObjectManager::getInstance() ->get(DeploymentConfig::class); + $this->moduleManager = $moduleManager ?? ObjectManager::getInstance()->get(ModuleManager::class); } /** * @inheritDoc */ - public function process(\Magento\Framework\View\Asset\PreProcessor\Chain $chain) + public function process(Chain $chain) { $asset = $chain->getAsset(); $replaceCallback = function ($matchContent) use ($asset) { @@ -108,24 +134,42 @@ protected function replace(array $matchedContent, LocalInterface $asset) $relatedAsset = $this->assetRepo->createRelated($matchedFileId, $asset); $resolvedPath = $relatedAsset->getFilePath(); $importFiles = $this->fileSource->getFiles($this->getTheme($relatedAsset), $resolvedPath); + $deployOnlyEnabled = $this->hasEnabledFlagDeployEnabledModules(); /** @var $importFile \Magento\Framework\View\File */ foreach ($importFiles as $importFile) { + $moduleName = $importFile->getModule(); $referenceString = $isReference ? '(reference) ' : ''; - $importsContent .= $importFile->getModule() - ? "@import $referenceString'{$importFile->getModule()}::{$resolvedPath}';\n" - : "@import $referenceString'{$matchedFileId}';\n"; + + if ($moduleName) { + if (!$deployOnlyEnabled || $this->moduleManager->isEnabled($moduleName)) { + $importsContent .= "@import $referenceString'{$moduleName}::{$resolvedPath}';\n"; + } + } else { + $importsContent .= "@import $referenceString'{$matchedFileId}';\n"; + } } } catch (\LogicException $e) { $this->errorHandler->processException($e); } + return $importsContent; } + /** + * Retrieve flag deploy enabled modules + * + * @return bool + */ + private function hasEnabledFlagDeployEnabledModules(): bool + { + return (bool) $this->deploymentConfig->get(self::CONFIG_PATH_SCD_ONLY_ENABLED_MODULES); + } + /** * Get theme model based on the information from asset * * @param LocalInterface $asset - * @return \Magento\Framework\View\Design\ThemeInterface + * @return ThemeInterface */ protected function getTheme(LocalInterface $asset) { @@ -135,6 +179,7 @@ protected function getTheme(LocalInterface $asset) $context->getAreaCode() . '/' . $context->getThemePath() ); } + return $this->design->getDesignTheme(); } @@ -143,7 +188,7 @@ protected function getTheme(LocalInterface $asset) * * @return ThemeProviderInterface */ - private function getThemeProvider() + private function getThemeProvider(): ThemeProviderInterface { if (null === $this->themeProvider) { $this->themeProvider = ObjectManager::getInstance()->get(ThemeProviderInterface::class); diff --git a/lib/internal/Magento/Framework/Css/Test/Unit/PreProcessor/Instruction/MagentoImportTest.php b/lib/internal/Magento/Framework/Css/Test/Unit/PreProcessor/Instruction/MagentoImportTest.php index 0192ca4572d03..9ac37f7b96e2c 100644 --- a/lib/internal/Magento/Framework/Css/Test/Unit/PreProcessor/Instruction/MagentoImportTest.php +++ b/lib/internal/Magento/Framework/Css/Test/Unit/PreProcessor/Instruction/MagentoImportTest.php @@ -7,14 +7,17 @@ namespace Magento\Framework\Css\Test\Unit\PreProcessor\Instruction; +use Magento\Framework\App\DeploymentConfig; use Magento\Framework\Css\PreProcessor\ErrorHandlerInterface; use Magento\Framework\Css\PreProcessor\Instruction\Import; use Magento\Framework\Css\PreProcessor\Instruction\MagentoImport; +use Magento\Framework\Module\Manager as ModuleManager; use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; use Magento\Framework\View\Asset\File; use Magento\Framework\View\Asset\File\FallbackContext; use Magento\Framework\View\Asset\PreProcessor\Chain; use Magento\Framework\View\Asset\Repository; +use Magento\Framework\View\Design\Theme\ListInterface as ThemeListInterface; use Magento\Framework\View\Design\Theme\ThemeProviderInterface; use Magento\Framework\View\Design\ThemeInterface; use Magento\Framework\View\DesignInterface; @@ -30,33 +33,47 @@ class MagentoImportTest extends TestCase /** * @var DesignInterface|MockObject */ - private $design; + private $designMock; /** * @var CollectorInterface|MockObject */ - private $fileSource; + private $fileSourceMock; /** * @var ErrorHandlerInterface|MockObject */ - private $errorHandler; + private $errorHandlerMock; /** * @var File|MockObject */ - private $asset; + private $assetMock; /** * @var Repository|MockObject */ - private $assetRepo; + private $assetRepoMock; /** - * @var ThemeProviderInterface|MockObject + * @var ThemeListInterface|MockObject */ - private $themeProvider; + private $themeListMock; + /** + * @var DeploymentConfig|MockObject + */ + private $deploymentConfigMock; + + /** + * @var ModuleManager|MockObject + */ + private $moduleManagerMock; + + /** + * @var ThemeProviderInterface|MockObject + */ + private $themeProviderMock; /** * @var Import */ @@ -64,21 +81,28 @@ class MagentoImportTest extends TestCase protected function setUp(): void { - $this->design = $this->getMockForAbstractClass(DesignInterface::class); - $this->fileSource = $this->getMockForAbstractClass(CollectorInterface::class); - $this->errorHandler = $this->getMockForAbstractClass( - ErrorHandlerInterface::class - ); - $this->asset = $this->createMock(File::class); - $this->asset->expects($this->any())->method('getContentType')->willReturn('css'); - $this->assetRepo = $this->createMock(Repository::class); - $this->themeProvider = $this->getMockForAbstractClass(ThemeProviderInterface::class); + $this->designMock = $this->createMock(DesignInterface::class); + $this->fileSourceMock = $this->createMock(CollectorInterface::class); + $this->errorHandlerMock = $this->createMock(ErrorHandlerInterface::class); + $this->assetMock = $this->createMock(File::class); + $this->assetMock->method('getContentType')->willReturn('css'); + $this->assetRepoMock = $this->createMock(Repository::class); + $this->themeListMock = $this->createMock(ThemeListInterface::class); + $this->deploymentConfigMock = $this->createMock(DeploymentConfig::class); + $this->moduleManagerMock = $this->createMock(ModuleManager::class); + + $this->themeProviderMock = $this->createMock(ThemeProviderInterface::class); + $this->object = (new ObjectManager($this))->getObject(MagentoImport::class, [ - 'design' => $this->design, - 'fileSource' => $this->fileSource, - 'errorHandler' => $this->errorHandler, - 'assetRepo' => $this->assetRepo, - 'themeProvider' => $this->themeProvider + 'design' => $this->designMock, + 'fileSource' => $this->fileSourceMock, + 'errorHandler' => $this->errorHandlerMock, + 'assetRepo' => $this->assetRepoMock, + 'themeList' => $this->themeListMock, + 'deploymentConfig' => $this->deploymentConfigMock, + 'moduleManager' => $this->moduleManagerMock, + // Mocking private property + 'themeProvider' => $this->themeProviderMock, ]); } @@ -88,48 +112,66 @@ protected function setUp(): void * @param string $resolvedPath * @param array $foundFiles * @param string $expectedContent - * + * @param array $enabledModules + * @param bool $onlyEnabled * @dataProvider processDataProvider */ - public function testProcess($originalContent, $foundPath, $resolvedPath, $foundFiles, $expectedContent) - { - $chain = new Chain($this->asset, $originalContent, 'css', 'path'); + public function testProcess( + string $originalContent, + string $foundPath, + string $resolvedPath, + array $foundFiles, + string $expectedContent, + array $enabledModules, + bool $onlyEnabled + ): void { + $chain = new Chain($this->assetMock, $originalContent, 'css', 'path'); $relatedAsset = $this->createMock(File::class); $relatedAsset->expects($this->once()) ->method('getFilePath') ->willReturn($resolvedPath); $context = $this->createMock(FallbackContext::class); - $this->assetRepo->expects($this->once()) + $this->assetRepoMock->expects($this->once()) ->method('createRelated') - ->with($foundPath, $this->asset) + ->with($foundPath, $this->assetMock) ->willReturn($relatedAsset); $relatedAsset->expects($this->once())->method('getContext')->willReturn($context); $theme = $this->getMockForAbstractClass(ThemeInterface::class); - $this->themeProvider->expects($this->once())->method('getThemeByFullPath')->willReturn($theme); + $this->themeProviderMock->expects($this->once())->method('getThemeByFullPath')->willReturn($theme); $files = []; foreach ($foundFiles as $file) { $fileObject = $this->createMock(\Magento\Framework\View\File::class); - $fileObject->expects($this->any()) + $fileObject ->method('getModule') ->willReturn($file['module']); - $fileObject->expects($this->any()) + $fileObject ->method('getFilename') ->willReturn($file['filename']); $files[] = $fileObject; } - $this->fileSource->expects($this->once()) + $this->fileSourceMock->expects($this->once()) ->method('getFiles') ->with($theme, $resolvedPath) ->willReturn($files); + + $this->deploymentConfigMock->method('get')->with('static_content_only_enabled_modules') + ->willReturn($onlyEnabled); + + $this->moduleManagerMock->method('isEnabled') + ->willReturnCallback(function ($moduleName) use ($enabledModules) { + return in_array($moduleName, $enabledModules, true); + }); + $this->object->process($chain); $this->assertEquals($expectedContent, $chain->getContent()); $this->assertEquals('css', $chain->getContentType()); } /** + * @SuppressWarnings(PHPMD.ExcessiveMethodLength) * @return array */ - public function processDataProvider() + public function processDataProvider(): array { return [ 'non-modular notation' => [ @@ -141,6 +183,8 @@ public function processDataProvider() ['module' => null, 'filename' => 'theme/some/file.css'], ], "@import 'some/file.css';\n@import 'some/file.css';\n", + [], + true ], 'modular' => [ '//@magento_import "Magento_Module::some/file.css";', @@ -151,6 +195,32 @@ public function processDataProvider() ['module' => 'Magento_Two', 'filename' => 'some/file.css'], ], "@import 'Magento_Module::some/file.css';\n@import 'Magento_Two::some/file.css';\n", + ['Magento_Module', 'Magento_Two'], + true + ], + 'modular with disabled module' => [ + '//@magento_import "Magento_Module::some/file.css";', + 'Magento_Module::some/file.css', + 'some/file.css', + [ + ['module' => 'Magento_Module', 'filename' => 'some/file.css'], + ['module' => 'Magento_Two', 'filename' => 'some/file.css'], + ], + "@import 'Magento_Two::some/file.css';\n", + ['Magento_Two'], + true + ], + 'modular with disabled all modules' => [ + '//@magento_import "Magento_Module::some/file.css";', + 'Magento_Module::some/file.css', + 'some/file.css', + [ + ['module' => 'Magento_Module', 'filename' => 'some/file.css'], + ['module' => 'Magento_Two', 'filename' => 'some/file.css'], + ], + '', + [], + true ], 'non-modular reference notation' => [ '//@magento_import (reference) "some/file.css";', @@ -161,6 +231,8 @@ public function processDataProvider() ['module' => null, 'filename' => 'theme/some/file.css'], ], "@import (reference) 'some/file.css';\n@import (reference) 'some/file.css';\n", + [], + true ], 'modular reference' => [ '//@magento_import (reference) "Magento_Module::some/file.css";', @@ -172,35 +244,87 @@ public function processDataProvider() ], "@import (reference) 'Magento_Module::some/file.css';\n" . "@import (reference) 'Magento_Two::some/file.css';\n", + ['Magento_Module', 'Magento_Two'], + true + ], + 'modular reference with disabled module' => [ + '//@magento_import (reference) "Magento_Module::some/file.css";', + 'Magento_Module::some/file.css', + 'some/file.css', + [ + ['module' => 'Magento_Module', 'filename' => 'some/file.css'], + ['module' => 'Magento_Two', 'filename' => 'some/file.css'], + ], + "@import (reference) 'Magento_Module::some/file.css';\n", + ['Magento_Module'], + true + ], + 'modular reference with disabled module and disabled "only enabled modules" flag' => [ + '//@magento_import (reference) "Magento_Module::some/file.css";', + 'Magento_Module::some/file.css', + 'some/file.css', + [ + ['module' => 'Magento_Module', 'filename' => 'some/file.css'], + ['module' => 'Magento_Two', 'filename' => 'some/file.css'], + ], + "@import (reference) 'Magento_Module::some/file.css';\n" . + "@import (reference) 'Magento_Two::some/file.css';\n", + ['Magento_Module'], + false + ], + 'modular reference with disabled all modules' => [ + '//@magento_import (reference) "Magento_Module::some/file.css";', + 'Magento_Module::some/file.css', + 'some/file.css', + [ + ['module' => 'Magento_Module', 'filename' => 'some/file.css'], + ['module' => 'Magento_Two', 'filename' => 'some/file.css'], + ], + '', + [], + true + ], + 'modular reference with disabled all modules and disabled "only enabled modules" flag' => [ + '//@magento_import (reference) "Magento_Module::some/file.css";', + 'Magento_Module::some/file.css', + 'some/file.css', + [ + ['module' => 'Magento_Module', 'filename' => 'some/file.css'], + ['module' => 'Magento_Two', 'filename' => 'some/file.css'], + ], + "@import (reference) 'Magento_Module::some/file.css';\n" . + "@import (reference) 'Magento_Two::some/file.css';\n", + [], + false ], ]; } - public function testProcessNoImport() + public function testProcessNoImport(): void { $originalContent = 'color: #000000;'; $expectedContent = 'color: #000000;'; - $chain = new Chain($this->asset, $originalContent, 'css', 'orig'); - $this->assetRepo->expects($this->never()) + $chain = new Chain($this->assetMock, $originalContent, 'css', 'orig'); + $this->assetRepoMock->expects($this->never()) ->method('createRelated'); $this->object->process($chain); $this->assertEquals($expectedContent, $chain->getContent()); $this->assertEquals('css', $chain->getContentType()); } - public function testProcessException() + public function testProcessException(): void { $chain = new Chain( - $this->asset, + $this->assetMock, '//@magento_import "some/file.css";', 'css', 'path' ); $exception = new \LogicException('Error happened'); - $this->assetRepo->expects($this->once()) + $this->assetRepoMock->expects($this->once()) ->method('createRelated') ->willThrowException($exception); - $this->errorHandler->expects($this->once()) + $this->errorHandlerMock->expects($this->once()) ->method('processException') ->with($exception); $this->object->process($chain);