Css.php 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215
  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\Framework\Filesystem;
  12. use Magento\Framework\Filesystem\Directory\ReadInterface;
  13. use Magento\Framework\App\Filesystem\DirectoryList;
  14. use Magento\Framework\View\Url\CssResolver;
  15. use Magento\Framework\View\Asset\Minification;
  16. /**
  17. * Pre-processor for speeding up deployment of CSS files
  18. *
  19. * If there is a CSS file overridden in target theme and there are files in ancestor theme where this file was imported,
  20. * then all such parent files must be copied into target theme.
  21. */
  22. class Css implements ProcessorInterface
  23. {
  24. /**
  25. * @var ReadInterface
  26. */
  27. private $staticDir;
  28. /**
  29. * @var Minification
  30. */
  31. private $minification;
  32. /**
  33. * CssUrls constructor
  34. *
  35. * @param Filesystem $filesystem
  36. * @param Minification $minification
  37. */
  38. public function __construct(Filesystem $filesystem, Minification $minification)
  39. {
  40. $this->staticDir = $filesystem->getDirectoryRead(DirectoryList::STATIC_VIEW);
  41. $this->minification = $minification;
  42. }
  43. /**
  44. * CSS files map
  45. *
  46. * Collect information about CSS files which have been imported in other CSS files
  47. *
  48. * @var []
  49. */
  50. private $map = [];
  51. /**
  52. * Deployment procedure options
  53. *
  54. * @var array
  55. */
  56. private $options = [];
  57. /**
  58. * @inheritdoc
  59. */
  60. public function process(Package $package, array $options)
  61. {
  62. $this->options = $options;
  63. if ($this->options[DeployStaticOptions::NO_CSS] === true) {
  64. return false;
  65. }
  66. if ($package->getArea() !== Package::BASE_AREA && $package->getTheme() !== Package::BASE_THEME) {
  67. $files = $package->getParentFiles('css');
  68. foreach ($files as $file) {
  69. $packageFile = $package->getFile($file->getFileId());
  70. if ($packageFile && $packageFile->getPackage() === $package) {
  71. continue;
  72. }
  73. if ($this->hasOverrides($file, $package)) {
  74. $file = clone $file;
  75. $file->setArea($package->getArea());
  76. $file->setTheme($package->getTheme());
  77. $file->setLocale($package->getLocale());
  78. $file->setPackage($package);
  79. $package->addFileToMap($file);
  80. }
  81. }
  82. }
  83. return true;
  84. }
  85. /**
  86. * Checks if there are imports of CSS files or images within the given CSS file
  87. * which exists in the current package
  88. *
  89. * @param PackageFile $parentFile
  90. * @param Package $package
  91. * @return bool
  92. */
  93. private function hasOverrides(PackageFile $parentFile, Package $package)
  94. {
  95. $this->buildMap(
  96. $parentFile->getPackage()->getPath(),
  97. $parentFile->getDeployedFileName(),
  98. $parentFile->getDeployedFilePath()
  99. );
  100. $parentFiles = $this->collectFileMap($parentFile->getDeployedFilePath());
  101. $currentPackageFiles = $package->getFiles();
  102. $intersections = array_intersect_key($parentFiles, $currentPackageFiles);
  103. if ($intersections) {
  104. return true;
  105. }
  106. return false;
  107. }
  108. /**
  109. * Build map file
  110. *
  111. * @param string $packagePath
  112. * @param string $filePath
  113. * @param string $fullPath
  114. * @return void
  115. */
  116. private function buildMap($packagePath, $filePath, $fullPath)
  117. {
  118. if (!isset($this->map[$fullPath])) {
  119. $imports = [];
  120. $this->map[$fullPath] = [];
  121. $content = $this->staticDir->readFile($this->minification->addMinifiedSign($fullPath));
  122. $callback = function ($matchContent) use ($packagePath, $filePath, & $imports) {
  123. $importRelPath = $this->normalize(pathinfo($filePath, PATHINFO_DIRNAME) . '/' . $matchContent['path']);
  124. $imports[$importRelPath] = $this->normalize(
  125. $packagePath . '/' . pathinfo($filePath, PATHINFO_DIRNAME) . '/' . $matchContent['path']
  126. );
  127. };
  128. preg_replace_callback(
  129. \Magento\Framework\Css\PreProcessor\Instruction\Import::REPLACE_PATTERN,
  130. $callback,
  131. $content
  132. );
  133. preg_match_all(CssResolver::REGEX_CSS_RELATIVE_URLS, $content, $matches);
  134. if (!empty($matches[0]) && !empty($matches[1])) {
  135. $urls = array_combine($matches[0], $matches[1]);
  136. foreach ($urls as $url) {
  137. $importRelPath = $this->normalize(pathinfo($filePath, PATHINFO_DIRNAME) . '/' . $url);
  138. $imports[$importRelPath] = $this->normalize(
  139. $packagePath . '/' . pathinfo($filePath, PATHINFO_DIRNAME) . '/' . $url
  140. );
  141. }
  142. }
  143. $this->map[$fullPath] = $imports;
  144. foreach ($imports as $importRelPath => $importFullPath) {
  145. // only inner CSS files are concerned
  146. if (strtolower(pathinfo($importFullPath, PATHINFO_EXTENSION)) === 'css') {
  147. $this->buildMap($packagePath, $importRelPath, $importFullPath);
  148. }
  149. }
  150. }
  151. }
  152. /**
  153. * Flatten map tree into simple array
  154. *
  155. * Original map file information structure in form of tree,
  156. * and to have checking of overridden files simpler we need to flatten that tree
  157. *
  158. * @param string $fileName
  159. * @return array
  160. */
  161. private function collectFileMap($fileName)
  162. {
  163. $result = isset($this->map[$fileName]) ? $this->map[$fileName] : [];
  164. foreach ($result as $path) {
  165. $result = array_merge($result, $this->collectFileMap($path));
  166. }
  167. return array_unique($result);
  168. }
  169. /**
  170. * Return normalized path
  171. *
  172. * @param string $path
  173. * @return string
  174. */
  175. private function normalize($path)
  176. {
  177. if (strpos($path, '/../') === false) {
  178. return $path;
  179. }
  180. $pathParts = explode('/', $path);
  181. $realPath = [];
  182. foreach ($pathParts as $pathPart) {
  183. if ($pathPart == '.') {
  184. continue;
  185. }
  186. if ($pathPart == '..') {
  187. array_pop($realPath);
  188. continue;
  189. }
  190. $realPath[] = $pathPart;
  191. }
  192. return implode('/', $realPath);
  193. }
  194. }