PageRenderTime 69ms CodeModel.GetById 11ms RepoModel.GetById 0ms app.codeStats 0ms

/wp-content/plugins/learnpress/inc/libraries/minify/src/CSS.php

https://gitlab.com/gregtyka/lfmawordpress
PHP | 573 lines | 319 code | 60 blank | 194 comment | 14 complexity | 0d500bf838be1e87d33b74c0e73bf045 MD5 | raw file
  1. <?php
  2. namespace MatthiasMullie\Minify;
  3. use MatthiasMullie\PathConverter\Converter;
  4. /**
  5. * CSS minifier.
  6. *
  7. * Please report bugs on https://github.com/matthiasmullie/minify/issues
  8. *
  9. * @author Matthias Mullie <minify@mullie.eu>
  10. * @author Tijs Verkoyen <minify@verkoyen.eu>
  11. * @copyright Copyright (c) 2012, Matthias Mullie. All rights reserved.
  12. * @license MIT License
  13. */
  14. class CSS extends Minify
  15. {
  16. /**
  17. * @var int
  18. */
  19. protected $maxImportSize = 5;
  20. /**
  21. * @var string[]
  22. */
  23. protected $importExtensions = array(
  24. 'gif' => 'data:image/gif',
  25. 'png' => 'data:image/png',
  26. 'jpe' => 'data:image/jpeg',
  27. 'jpg' => 'data:image/jpeg',
  28. 'jpeg' => 'data:image/jpeg',
  29. 'svg' => 'data:image/svg+xml',
  30. 'woff' => 'data:application/x-font-woff',
  31. 'tif' => 'image/tiff',
  32. 'tiff' => 'image/tiff',
  33. 'xbm' => 'image/x-xbitmap',
  34. );
  35. /**
  36. * Set the maximum size if files to be imported.
  37. *
  38. * Files larger than this size (in kB) will not be imported into the CSS.
  39. * Importing files into the CSS as data-uri will save you some connections,
  40. * but we should only import relatively small decorative images so that our
  41. * CSS file doesn't get too bulky.
  42. *
  43. * @param int $size Size in kB
  44. */
  45. public function setMaxImportSize($size)
  46. {
  47. $this->maxImportSize = $size;
  48. }
  49. /**
  50. * Set the type of extensions to be imported into the CSS (to save network
  51. * connections).
  52. * Keys of the array should be the file extensions & respective values
  53. * should be the data type.
  54. *
  55. * @param string[] $extensions Array of file extensions
  56. */
  57. public function setImportExtensions(array $extensions)
  58. {
  59. $this->importExtensions = $extensions;
  60. }
  61. /**
  62. * Move any import statements to the top.
  63. *
  64. * @param $content string Nearly finished CSS content
  65. *
  66. * @return string
  67. */
  68. protected function moveImportsToTop($content)
  69. {
  70. if (preg_match_all('/@import[^;]+;/', $content, $matches)) {
  71. // remove from content
  72. foreach ($matches[0] as $import) {
  73. $content = str_replace($import, '', $content);
  74. }
  75. // add to top
  76. $content = implode('', $matches[0]).$content;
  77. };
  78. return $content;
  79. }
  80. /**
  81. * Combine CSS from import statements.
  82. *
  83. * @import's will be loaded and their content merged into the original file,
  84. * to save HTTP requests.
  85. *
  86. * @param string $source The file to combine imports for.
  87. * @param string $content The CSS content to combine imports for.
  88. *
  89. * @return string
  90. */
  91. protected function combineImports($source, $content)
  92. {
  93. $importRegexes = array(
  94. // @import url(xxx)
  95. '/
  96. # import statement
  97. @import
  98. # whitespace
  99. \s+
  100. # open url()
  101. url\(
  102. # (optional) open path enclosure
  103. (?P<quotes>["\']?)
  104. # fetch path
  105. (?P<path>
  106. # do not fetch data uris or external sources
  107. (?!(
  108. ["\']?
  109. (data|https?):
  110. ))
  111. .+?
  112. )
  113. # (optional) close path enclosure
  114. (?P=quotes)
  115. # close url()
  116. \)
  117. # (optional) trailing whitespace
  118. \s*
  119. # (optional) media statement(s)
  120. (?P<media>[^;]*)
  121. # (optional) trailing whitespace
  122. \s*
  123. # (optional) closing semi-colon
  124. ;?
  125. /ix',
  126. // @import 'xxx'
  127. '/
  128. # import statement
  129. @import
  130. # whitespace
  131. \s+
  132. # open path enclosure
  133. (?P<quotes>["\'])
  134. # fetch path
  135. (?P<path>
  136. # do not fetch data uris or external sources
  137. (?!(
  138. ["\']?
  139. (data|https?):
  140. ))
  141. .+?
  142. )
  143. # close path enclosure
  144. (?P=quotes)
  145. # (optional) trailing whitespace
  146. \s*
  147. # (optional) media statement(s)
  148. (?P<media>[^;]*)
  149. # (optional) trailing whitespace
  150. \s*
  151. # (optional) closing semi-colon
  152. ;?
  153. /ix',
  154. );
  155. // find all relative imports in css
  156. $matches = array();
  157. foreach ($importRegexes as $importRegex) {
  158. if (preg_match_all($importRegex, $content, $regexMatches, PREG_SET_ORDER)) {
  159. $matches = array_merge($matches, $regexMatches);
  160. }
  161. }
  162. $search = array();
  163. $replace = array();
  164. // loop the matches
  165. foreach ($matches as $match) {
  166. // get the path for the file that will be imported
  167. $importPath = dirname($source).'/'.$match['path'];
  168. // only replace the import with the content if we can grab the
  169. // content of the file
  170. if (file_exists($importPath) && is_file($importPath)) {
  171. // grab referenced file & minify it (which may include importing
  172. // yet other @import statements recursively)
  173. $minifier = new static($importPath);
  174. $importContent = $minifier->execute($source);
  175. // check if this is only valid for certain media
  176. if ($match['media']) {
  177. $importContent = '@media '.$match['media'].'{'.$importContent.'}';
  178. }
  179. // add to replacement array
  180. $search[] = $match[0];
  181. $replace[] = $importContent;
  182. }
  183. }
  184. // replace the import statements
  185. $content = str_replace($search, $replace, $content);
  186. return $content;
  187. }
  188. /**
  189. * Import files into the CSS, base64-ized.
  190. *
  191. * @url(image.jpg) images will be loaded and their content merged into the
  192. * original file, to save HTTP requests.
  193. *
  194. * @param string $source The file to import files for.
  195. * @param string $content The CSS content to import files for.
  196. *
  197. * @return string
  198. */
  199. protected function importFiles($source, $content)
  200. {
  201. $extensions = array_keys($this->importExtensions);
  202. $regex = '/url\((["\']?)((?!["\']?data:).*?\.('.implode('|', $extensions).'))\\1\)/i';
  203. if ($extensions && preg_match_all($regex, $content, $matches, PREG_SET_ORDER)) {
  204. $search = array();
  205. $replace = array();
  206. // loop the matches
  207. foreach ($matches as $match) {
  208. // get the path for the file that will be imported
  209. $path = $match[2];
  210. $path = dirname($source).'/'.$path;
  211. $extension = $match[3];
  212. // only replace the import with the content if we're able to get
  213. // the content of the file, and it's relatively small
  214. $import = file_exists($path);
  215. $import = $import && is_file($path);
  216. $import = $import && filesize($path) <= $this->maxImportSize * 1024;
  217. if (!$import) {
  218. continue;
  219. }
  220. // grab content && base64-ize
  221. $importContent = $this->load($path);
  222. $importContent = base64_encode($importContent);
  223. // build replacement
  224. $search[] = $match[0];
  225. $replace[] = 'url('.$this->importExtensions[$extension].';base64,'.$importContent.')';
  226. }
  227. // replace the import statements
  228. $content = str_replace($search, $replace, $content);
  229. }
  230. return $content;
  231. }
  232. /**
  233. * Minify the data.
  234. * Perform CSS optimizations.
  235. *
  236. * @param string[optional] $path Path to write the data to.
  237. *
  238. * @return string The minified data.
  239. */
  240. public function execute($path = null)
  241. {
  242. $content = '';
  243. // loop files
  244. foreach ($this->data as $source => $css) {
  245. /*
  246. * Let's first take out strings & comments, since we can't just remove
  247. * whitespace anywhere. If whitespace occurs inside a string, we should
  248. * leave it alone. E.g.:
  249. * p { content: "a test" }
  250. */
  251. $this->extractStrings();
  252. $this->stripComments();
  253. $css = $this->replace($css);
  254. $css = $this->stripWhitespace($css);
  255. $css = $this->shortenHex($css);
  256. $css = $this->shortenZeroes($css);
  257. $css = $this->stripEmptyTags($css);
  258. // restore the string we've extracted earlier
  259. $css = $this->restoreExtractedData($css);
  260. /*
  261. * If we'll save to a new path, we'll have to fix the relative paths
  262. * to be relative no longer to the source file, but to the new path.
  263. * If we don't write to a file, fall back to same path so no
  264. * conversion happens (because we still want it to go through most
  265. * of the move code...)
  266. */
  267. $source = $source ?: '';
  268. $converter = new Converter($source, $path ?: $source);
  269. $css = $this->move($converter, $css);
  270. // if no target path is given, relative paths were not converted, so
  271. // they'll still be relative to the source file then
  272. $css = $this->importFiles($path ?: $source, $css);
  273. $css = $this->combineImports($path ?: $source, $css);
  274. // combine css
  275. $content .= $css;
  276. }
  277. $content = $this->moveImportsToTop($content);
  278. return $content;
  279. }
  280. /**
  281. * Moving a css file should update all relative urls.
  282. * Relative references (e.g. ../images/image.gif) in a certain css file,
  283. * will have to be updated when a file is being saved at another location
  284. * (e.g. ../../images/image.gif, if the new CSS file is 1 folder deeper).
  285. *
  286. * @param Converter $converter Relative path converter
  287. * @param string $content The CSS content to update relative urls for.
  288. *
  289. * @return string
  290. */
  291. protected function move(Converter $converter, $content)
  292. {
  293. /*
  294. * Relative path references will usually be enclosed by url(). @import
  295. * is an exception, where url() is not necessary around the path (but is
  296. * allowed).
  297. * This *could* be 1 regular expression, where both regular expressions
  298. * in this array are on different sides of a |. But we're using named
  299. * patterns in both regexes, the same name on both regexes. This is only
  300. * possible with a (?J) modifier, but that only works after a fairly
  301. * recent PCRE version. That's why I'm doing 2 separate regular
  302. * expressions & combining the matches after executing of both.
  303. */
  304. $relativeRegexes = array(
  305. // url(xxx)
  306. '/
  307. # open url()
  308. url\(
  309. \s*
  310. # open path enclosure
  311. (?P<quotes>["\'])?
  312. # fetch path
  313. (?P<path>
  314. # do not fetch data uris or external sources
  315. (?!(
  316. \s?
  317. ["\']?
  318. (data|https?):
  319. ))
  320. .+?
  321. )
  322. # close path enclosure
  323. (?(quotes)(?P=quotes))
  324. \s*
  325. # close url()
  326. \)
  327. /ix',
  328. // @import "xxx"
  329. '/
  330. # import statement
  331. @import
  332. # whitespace
  333. \s+
  334. # we don\'t have to check for @import url(), because the
  335. # condition above will already catch these
  336. # open path enclosure
  337. (?P<quotes>["\'])
  338. # fetch path
  339. (?P<path>
  340. # do not fetch data uris or external sources
  341. (?!(
  342. ["\']?
  343. (data|https?):
  344. ))
  345. .+?
  346. )
  347. # close path enclosure
  348. (?P=quotes)
  349. /ix',
  350. );
  351. // find all relative urls in css
  352. $matches = array();
  353. foreach ($relativeRegexes as $relativeRegex) {
  354. if (preg_match_all($relativeRegex, $content, $regexMatches, PREG_SET_ORDER)) {
  355. $matches = array_merge($matches, $regexMatches);
  356. }
  357. }
  358. $search = array();
  359. $replace = array();
  360. // loop all urls
  361. foreach ($matches as $match) {
  362. // determine if it's a url() or an @import match
  363. $type = (strpos($match[0], '@import') === 0 ? 'import' : 'url');
  364. // fix relative url
  365. $url = $converter->convert($match['path']);
  366. // build replacement
  367. $search[] = $match[0];
  368. if ($type == 'url') {
  369. $replace[] = 'url('.$url.')';
  370. } elseif ($type == 'import') {
  371. $replace[] = '@import "'.$url.'"';
  372. }
  373. }
  374. // replace urls
  375. $content = str_replace($search, $replace, $content);
  376. return $content;
  377. }
  378. /**
  379. * Shorthand hex color codes.
  380. * #FF0000 -> #F00.
  381. *
  382. * @param string $content The CSS content to shorten the hex color codes for.
  383. *
  384. * @return string
  385. */
  386. protected function shortenHex($content)
  387. {
  388. $content = preg_replace('/(?<![\'"])#([0-9a-z])\\1([0-9a-z])\\2([0-9a-z])\\3(?![\'"])/i', '#$1$2$3', $content);
  389. return $content;
  390. }
  391. /**
  392. * Shorthand 0 values to plain 0, instead of e.g. -0em.
  393. *
  394. * @param string $content The CSS content to shorten the zero values for.
  395. *
  396. * @return string
  397. */
  398. protected function shortenZeroes($content)
  399. {
  400. // reusable bits of code throughout these regexes:
  401. // before & after are used to make sure we don't match lose unintended
  402. // 0-like values (e.g. in #000, or in http://url/1.0)
  403. // units can be stripped from 0 values, or used to recognize non 0
  404. // values (where wa may be able to strip a .0 suffix)
  405. $before = '(?<=[:(, ])';
  406. $after = '(?=[ ,);}])';
  407. $units = '(em|ex|%|px|cm|mm|in|pt|pc|ch|rem|vh|vw|vmin|vmax|vm)';
  408. // strip units after zeroes (0px -> 0)
  409. // NOTE: it should be safe to remove all units for a 0 value, but in
  410. // practice, Webkit (especially Safari) seems to stumble over at least
  411. // 0%, potentially other units as well. Only stripping 'px' for now.
  412. // @see https://github.com/matthiasmullie/minify/issues/60
  413. $content = preg_replace('/'.$before.'(-?0*(\.0+)?)(?<=0)px'.$after.'/', '\\1', $content);
  414. // strip 0-digits (.0 -> 0)
  415. $content = preg_replace('/'.$before.'\.0+'.$units.'?'.$after.'/', '0\\1', $content);
  416. // strip trailing 0: 50.10 -> 50.1, 50.10px -> 50.1px
  417. $content = preg_replace('/'.$before.'(-?[0-9]+\.[0-9]+)0+'.$units.'?'.$after.'/', '\\1\\2', $content);
  418. // strip trailing 0: 50.00 -> 50, 50.00px -> 50px
  419. $content = preg_replace('/'.$before.'(-?[0-9]+)\.0+'.$units.'?'.$after.'/', '\\1\\2', $content);
  420. // strip leading 0: 0.1 -> .1, 01.1 -> 1.1
  421. $content = preg_replace('/'.$before.'(-?)0+([0-9]*\.[0-9]+)'.$units.'?'.$after.'/', '\\1\\2\\3', $content);
  422. // strip negative zeroes (-0 -> 0) & truncate zeroes (00 -> 0)
  423. $content = preg_replace('/'.$before.'-?0+'.$units.'?'.$after.'/', '0\\1', $content);
  424. return $content;
  425. }
  426. /**
  427. * Strip comments from source code.
  428. *
  429. * @param string $content
  430. * @return string
  431. */
  432. protected function stripEmptyTags($content)
  433. {
  434. return preg_replace('/(^|\})[^\{]+\{\s*\}/', '\\1', $content);
  435. }
  436. /**
  437. * Strip comments from source code.
  438. */
  439. protected function stripComments()
  440. {
  441. $this->registerPattern('/\/\*.*?\*\//s', '');
  442. }
  443. /**
  444. * Strip whitespace.
  445. *
  446. * @param string $content The CSS content to strip the whitespace for.
  447. *
  448. * @return string
  449. */
  450. protected function stripWhitespace($content)
  451. {
  452. // remove leading & trailing whitespace
  453. $content = preg_replace('/^\s*/m', '', $content);
  454. $content = preg_replace('/\s*$/m', '', $content);
  455. // replace newlines with a single space
  456. $content = preg_replace('/\s+/', ' ', $content);
  457. // remove whitespace around meta characters
  458. // inspired by stackoverflow.com/questions/15195750/minify-compress-css-with-regex
  459. $content = preg_replace('/\s*([\*$~^|]?+=|[{};,>~]|!important\b)\s*/', '$1', $content);
  460. $content = preg_replace('/([\[(:])\s+/', '$1', $content);
  461. $content = preg_replace('/\s+([\]\)])/', '$1', $content);
  462. $content = preg_replace('/\s+(:)(?![^\}]*\{)/', '$1', $content);
  463. // whitespace around + and - can only be stripped in selectors, like
  464. // :nth-child(3+2n), not in things like calc(3px + 2px) or shorthands
  465. // like 3px -2px
  466. $content = preg_replace('/\s*([+-])\s*(?=[^}]*{)/', '$1', $content);
  467. // remove semicolon/whitespace followed by closing bracket
  468. $content = str_replace(';}', '}', $content);
  469. return trim($content);
  470. }
  471. }