Less.php 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225
  1. <?php
  2. /**
  3. * Copyright © Magento, Inc. All rights reserved.
  4. * See COPYING.txt for license details.
  5. */
  6. namespace Magento\Deploy\Package\Processor\PreProcessor;
  7. use Magento\Deploy\Console\DeployStaticOptions;
  8. use Magento\Deploy\Package\Package;
  9. use Magento\Deploy\Package\PackageFile;
  10. use Magento\Deploy\Package\Processor\ProcessorInterface;
  11. use Magento\Deploy\Service\DeployStaticFile;
  12. use Magento\Framework\View\Asset\PreProcessor\FileNameResolver;
  13. use Magento\Framework\View\Asset\Minification;
  14. use Magento\Framework\Css\PreProcessor\Instruction\Import;
  15. use Magento\Framework\View\Asset\NotationResolver;
  16. use Magento\Framework\View\Asset\Repository;
  17. /**
  18. * Pre-processor for speeding up deployment of Less files
  19. *
  20. * LESS-to-CSS compilation should happen only when it is really needed. If in some package there is no LESS files
  21. * overridden which used for generating some CSS file, then such CSS file can be copied from ancestor package as is
  22. */
  23. class Less implements ProcessorInterface
  24. {
  25. /**
  26. * Resolver allows to distinguish "root" LESS files from "imported" LESS files
  27. *
  28. * @var FileNameResolver
  29. */
  30. private $fileNameResolver;
  31. /**
  32. * @var \Magento\Framework\View\Asset\NotationResolver\Module
  33. */
  34. private $notationResolver;
  35. /**
  36. * @var DeployStaticFile
  37. */
  38. private $deployStaticFile;
  39. /**
  40. * @var Minification
  41. */
  42. private $minification;
  43. /**
  44. * Deployment procedure options
  45. *
  46. * @var array
  47. */
  48. private $options = [];
  49. /**
  50. * @var array
  51. */
  52. private $map = [];
  53. /**
  54. * Less constructor
  55. *
  56. * @param FileNameResolver $fileNameResolver
  57. * @param NotationResolver\Module $notationResolver
  58. * @param DeployStaticFile $deployStaticFile
  59. * @param Minification $minification
  60. */
  61. public function __construct(
  62. FileNameResolver $fileNameResolver,
  63. NotationResolver\Module $notationResolver,
  64. DeployStaticFile $deployStaticFile,
  65. Minification $minification
  66. ) {
  67. $this->fileNameResolver = $fileNameResolver;
  68. $this->notationResolver = $notationResolver;
  69. $this->deployStaticFile = $deployStaticFile;
  70. $this->minification = $minification;
  71. }
  72. /**
  73. * @inheritdoc
  74. */
  75. public function process(Package $package, array $options)
  76. {
  77. $this->options = $options;
  78. if ($this->options[DeployStaticOptions::NO_CSS] === true) {
  79. return false;
  80. }
  81. if ($package->getArea() !== Package::BASE_AREA && $package->getTheme() !== Package::BASE_THEME) {
  82. $files = $package->getParentFiles('less');
  83. foreach ($files as $file) {
  84. $packageFile = $package->getFile($file->getFileId());
  85. if ($packageFile && $packageFile->getOrigPackage() === $package) {
  86. continue;
  87. }
  88. $deployFileName = $this->fileNameResolver->resolve($file->getFileName());
  89. if ($deployFileName !== $file->getFileName()) {
  90. if ($this->hasOverrides($file, $package)) {
  91. $file = clone $file;
  92. $file->setArea($package->getArea());
  93. $file->setTheme($package->getTheme());
  94. $file->setLocale($package->getLocale());
  95. $file->setPackage($package);
  96. $package->addFileToMap($file);
  97. }
  98. }
  99. }
  100. }
  101. return true;
  102. }
  103. /**
  104. * Checks if there are LESS files in current package which used for generating given CSS file from parent package
  105. *
  106. * If true then such CSS file must be re-compiled for current package to use overridden LESS files
  107. *
  108. * @param PackageFile $parentFile
  109. * @param Package $package
  110. * @return bool
  111. */
  112. private function hasOverrides(PackageFile $parentFile, Package $package)
  113. {
  114. $map = $this->buildMap(
  115. $parentFile->getFilePath(),
  116. $parentFile->getPackage()->getPath(),
  117. $parentFile->getExtension()
  118. );
  119. /** @var PackageFile[] $currentPackageFiles */
  120. $currentPackageFiles = array_merge($package->getFilesByType('less'), $package->getFilesByType('css'));
  121. foreach ($currentPackageFiles as $file) {
  122. if ($this->inParentFiles($file->getDeployedFileName(), $parentFile->getFileName(), $map)) {
  123. return true;
  124. }
  125. }
  126. return false;
  127. }
  128. /**
  129. * @param string $fileName
  130. * @param string $parentFile
  131. * @param array $map
  132. * @return bool
  133. */
  134. private function inParentFiles($fileName, $parentFile, $map)
  135. {
  136. if (isset($map[$parentFile])) {
  137. if (in_array($fileName, $map[$parentFile])) {
  138. return true;
  139. } else {
  140. foreach ($map[$parentFile] as $pFile) {
  141. return $this->inParentFiles($fileName, $pFile, $map);
  142. }
  143. }
  144. }
  145. return false;
  146. }
  147. /**
  148. * Build map of imported files
  149. *
  150. * @param string $filePath
  151. * @param string $packagePath
  152. * @param string $contentType
  153. * @return array
  154. */
  155. private function buildMap($filePath, $packagePath, $contentType)
  156. {
  157. $content = $this->deployStaticFile->readTmpFile($filePath, $packagePath);
  158. $replaceCallback = function ($matchedContent) use ($filePath, $packagePath, $contentType) {
  159. $matchedFileId = $matchedContent['path'];
  160. if (!pathinfo($matchedContent['path'], PATHINFO_EXTENSION)) {
  161. $matchedFileId .= '.' . $contentType;
  162. }
  163. if (strpos($matchedFileId, Repository::FILE_ID_SEPARATOR) !== false) {
  164. $basePath = $packagePath;
  165. } else {
  166. $basePath = pathinfo($filePath, PATHINFO_DIRNAME);
  167. }
  168. $resolvedPath = str_replace(Repository::FILE_ID_SEPARATOR, '/', $matchedFileId);
  169. if (strpos($resolvedPath, '@{baseUrl}') === 0) {
  170. $resolvedMapPath = str_replace('@{baseUrl}', '', $resolvedPath);
  171. } else {
  172. $resolvedMapPath = $this->normalizePath($basePath . '/' . $resolvedPath);
  173. }
  174. if (!isset($this->map[$filePath])) {
  175. $this->map[$filePath] = [];
  176. }
  177. $this->map[$filePath][] = $resolvedMapPath;
  178. $this->buildMap($resolvedMapPath, $packagePath, $contentType);
  179. };
  180. if ($content) {
  181. preg_replace_callback(Import::REPLACE_PATTERN, $replaceCallback, $content);
  182. }
  183. return $this->map;
  184. }
  185. /**
  186. * Return normalized path
  187. *
  188. * @param string $path
  189. * @return string
  190. */
  191. private function normalizePath($path)
  192. {
  193. if (strpos($path, '/../') === false) {
  194. return $path;
  195. }
  196. $pathParts = explode('/', $path);
  197. $realPath = [];
  198. foreach ($pathParts as $pathPart) {
  199. if ($pathPart == '.') {
  200. continue;
  201. }
  202. if ($pathPart == '..') {
  203. array_pop($realPath);
  204. continue;
  205. }
  206. $realPath[] = $pathPart;
  207. }
  208. return implode('/', $realPath);
  209. }
  210. }