PageRenderTime 26ms CodeModel.GetById 13ms RepoModel.GetById 0ms app.codeStats 0ms

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

https://gitlab.com/gregtyka/lfmawordpress
PHP | 382 lines | 142 code | 48 blank | 192 comment | 19 complexity | fa8c782e6eeab23ede3ad779316e5707 MD5 | raw file
  1. <?php
  2. //namespace MatthiasMullie\Minify;
  3. //use Psr\Cache\CacheItemInterface;
  4. /**
  5. * Abstract minifier class.
  6. *
  7. * Please report bugs on https://github.com/matthiasmullie/minify/issues
  8. *
  9. * @author Matthias Mullie <minify@mullie.eu>
  10. * @copyright Copyright (c) 2012, Matthias Mullie. All rights reserved.
  11. * @license MIT License
  12. */
  13. abstract class Minify
  14. {
  15. /**
  16. * The data to be minified.
  17. *
  18. * @var string[]
  19. */
  20. protected $data = array();
  21. /**
  22. * Array of patterns to match.
  23. *
  24. * @var string[]
  25. */
  26. protected $patterns = array();
  27. /**
  28. * This array will hold content of strings and regular expressions that have
  29. * been extracted from the JS source code, so we can reliably match "code",
  30. * without having to worry about potential "code-like" characters inside.
  31. *
  32. * @var string[]
  33. */
  34. public $extracted = array();
  35. /**
  36. * Init the minify class - optionally, code may be passed along already.
  37. */
  38. public function __construct(/* $data = null, ... */)
  39. {
  40. // it's possible to add the source through the constructor as well ;)
  41. if (func_num_args()) {
  42. call_user_func_array(array($this, 'add'), func_get_args());
  43. }
  44. }
  45. /**
  46. * Add a file or straight-up code to be minified.
  47. *
  48. * @param string $data
  49. */
  50. public function add($data /* $data = null, ... */)
  51. {
  52. // bogus "usage" of parameter $data: scrutinizer warns this variable is
  53. // not used (we're using func_get_args instead to support overloading),
  54. // but it still needs to be defined because it makes no sense to have
  55. // this function without argument :)
  56. $args = array($data) + func_get_args();
  57. // this method can be overloaded
  58. foreach ($args as $data) {
  59. // redefine var
  60. $data = (string) $data;
  61. // load data
  62. $value = $this->load($data);
  63. $key = ($data != $value) ? $data : count($this->data);
  64. // store data
  65. $this->data[$key] = $value;
  66. }
  67. }
  68. /**
  69. * Load data.
  70. *
  71. * @param string $data Either a path to a file or the content itself.
  72. *
  73. * @return string
  74. */
  75. protected function load($data)
  76. {
  77. // check if the data is a file
  78. if (file_exists($data) && is_file($data)) {
  79. $data = file_get_contents($data);
  80. // strip BOM, if any
  81. if (substr($data, 0, 3) == "\xef\xbb\xbf") {
  82. $data = substr($data, 3);
  83. }
  84. }
  85. return $data;
  86. }
  87. /**
  88. * Save to file.
  89. *
  90. * @param string $content The minified data.
  91. * @param string $path The path to save the minified data to.
  92. *
  93. * @throws Exception
  94. */
  95. protected function save($content, $path)
  96. {
  97. // create file & open for writing
  98. if (($handler = @fopen($path, 'w')) === false) {
  99. throw new Exception('The file "'.$path.'" could not be opened. Check if PHP has enough permissions.');
  100. }
  101. // write to file
  102. if (@fwrite($handler, $content) === false) {
  103. throw new Exception('The file "'.$path.'" could not be written to. Check if PHP has enough permissions.');
  104. }
  105. // close the file
  106. @fclose($handler);
  107. }
  108. /**
  109. * Minify the data & (optionally) saves it to a file.
  110. *
  111. * @param string[optional] $path Path to write the data to.
  112. *
  113. * @return string The minified data.
  114. */
  115. public function minify($path = null)
  116. {
  117. $content = $this->execute($path);
  118. // save to path
  119. if ($path !== null) {
  120. $this->save($content, $path);
  121. }
  122. return $content;
  123. }
  124. /**
  125. * Minify & gzip the data & (optionally) saves it to a file.
  126. *
  127. * @param string[optional] $path Path to write the data to.
  128. * @param int[optional] $level Compression level, from 0 to 9.
  129. *
  130. * @return string The minified & gzipped data.
  131. */
  132. public function gzip($path = null, $level = 9)
  133. {
  134. $content = $this->execute($path);
  135. $content = gzencode($content, $level, FORCE_GZIP);
  136. // save to path
  137. if ($path !== null) {
  138. $this->save($content, $path);
  139. }
  140. return $content;
  141. }
  142. /**
  143. * Minify the data & write it to a CacheItemInterface object.
  144. *
  145. * @param CacheItemInterface $item Cache item to write the data to.
  146. *
  147. * @return CacheItemInterface Cache item with the minifier data.
  148. */
  149. public function cache(CacheItemInterface $item)
  150. {
  151. $content = $this->execute();
  152. $item->set($content);
  153. return $item;
  154. }
  155. /**
  156. * Minify the data.
  157. *
  158. * @param string[optional] $path Path to write the data to.
  159. *
  160. * @return string The minified data.
  161. */
  162. abstract public function execute($path = null);
  163. /**
  164. * Register a pattern to execute against the source content.
  165. *
  166. * @param string $pattern PCRE pattern.
  167. * @param string|callable $replacement Replacement value for matched pattern.
  168. *
  169. * @throws Exception
  170. */
  171. protected function registerPattern($pattern, $replacement = '')
  172. {
  173. // study the pattern, we'll execute it more than once
  174. $pattern .= 'S';
  175. $this->patterns[] = array($pattern, $replacement);
  176. }
  177. /**
  178. * We can't "just" run some regular expressions against JavaScript: it's a
  179. * complex language. E.g. having an occurrence of // xyz would be a comment,
  180. * unless it's used within a string. Of you could have something that looks
  181. * like a 'string', but inside a comment.
  182. * The only way to accurately replace these pieces is to traverse the JS one
  183. * character at a time and try to find whatever starts first.
  184. *
  185. * @param string $content The content to replace patterns in.
  186. *
  187. * @return string The (manipulated) content.
  188. */
  189. protected function replace($content)
  190. {
  191. $processed = '';
  192. $positions = array_fill(0, count($this->patterns), -1);
  193. $matches = array();
  194. while ($content) {
  195. // find first match for all patterns
  196. foreach ($this->patterns as $i => $pattern) {
  197. list($pattern, $replacement) = $pattern;
  198. // no need to re-run matches that are still in the part of the
  199. // content that hasn't been processed
  200. if ($positions[$i] >= 0) {
  201. continue;
  202. }
  203. $match = null;
  204. if (preg_match($pattern, $content, $match)) {
  205. $matches[$i] = $match;
  206. // we'll store the match position as well; that way, we
  207. // don't have to redo all preg_matches after changing only
  208. // the first (we'll still know where those others are)
  209. $positions[$i] = strpos($content, $match[0]);
  210. } else {
  211. // if the pattern couldn't be matched, there's no point in
  212. // executing it again in later runs on this same content;
  213. // ignore this one until we reach end of content
  214. unset($matches[$i]);
  215. $positions[$i] = strlen($content);
  216. }
  217. }
  218. // no more matches to find: everything's been processed, break out
  219. if (!$matches) {
  220. $processed .= $content;
  221. break;
  222. }
  223. // see which of the patterns actually found the first thing (we'll
  224. // only want to execute that one, since we're unsure if what the
  225. // other found was not inside what the first found)
  226. $discardLength = min($positions);
  227. $firstPattern = array_search($discardLength, $positions);
  228. $match = $matches[$firstPattern][0];
  229. // execute the pattern that matches earliest in the content string
  230. list($pattern, $replacement) = $this->patterns[$firstPattern];
  231. $replacement = $this->replacePattern($pattern, $replacement, $content);
  232. // figure out which part of the string was unmatched; that's the
  233. // part we'll execute the patterns on again next
  234. $content = substr($content, $discardLength);
  235. $unmatched = (string) substr($content, strpos($content, $match) + strlen($match));
  236. // move the replaced part to $processed and prepare $content to
  237. // again match batch of patterns against
  238. $processed .= substr($replacement, 0, strlen($replacement) - strlen($unmatched));
  239. $content = $unmatched;
  240. // first match has been replaced & that content is to be left alone,
  241. // the next matches will start after this replacement, so we should
  242. // fix their offsets
  243. foreach ($positions as $i => $position) {
  244. $positions[$i] -= $discardLength + strlen($match);
  245. }
  246. }
  247. return $processed;
  248. }
  249. /**
  250. * This is where a pattern is matched against $content and the matches
  251. * are replaced by their respective value.
  252. * This function will be called plenty of times, where $content will always
  253. * move up 1 character.
  254. *
  255. * @param string $pattern Pattern to match.
  256. * @param string|callable $replacement Replacement value.
  257. * @param string $content Content to match pattern against.
  258. *
  259. * @return string
  260. */
  261. protected function replacePattern($pattern, $replacement, $content)
  262. {
  263. if (is_callable($replacement)) {
  264. return preg_replace_callback($pattern, $replacement, $content, 1, $count);
  265. } else {
  266. return preg_replace($pattern, $replacement, $content, 1, $count);
  267. }
  268. }
  269. /**
  270. * Strings are a pattern we need to match, in order to ignore potential
  271. * code-like content inside them, but we just want all of the string
  272. * content to remain untouched.
  273. *
  274. * This method will replace all string content with simple STRING#
  275. * placeholder text, so we've rid all strings from characters that may be
  276. * misinterpreted. Original string content will be saved in $this->extracted
  277. * and after doing all other minifying, we can restore the original content
  278. * via restoreStrings()
  279. *
  280. * @param string[optional] $chars
  281. */
  282. protected function extractStrings($chars = '\'"')
  283. {
  284. // PHP only supports $this inside anonymous functions since 5.4
  285. $minifier = $this;
  286. $callback = function ($match) use ($minifier) {
  287. if (!$match[1]) {
  288. /*
  289. * Empty strings need no placeholder; they can't be confused for
  290. * anything else anyway.
  291. * But we still needed to match them, for the extraction routine
  292. * to skip over this particular string.
  293. */
  294. return $match[0];
  295. }
  296. $count = count($minifier->extracted);
  297. $placeholder = $match[1].$count.$match[1];
  298. $minifier->extracted[$placeholder] = $match[1].$match[2].$match[1];
  299. return $placeholder;
  300. };
  301. /*
  302. * The \\ messiness explained:
  303. * * Don't count ' or " as end-of-string if it's escaped (has backslash
  304. * in front of it)
  305. * * Unless... that backslash itself is escaped (another leading slash),
  306. * in which case it's no longer escaping the ' or "
  307. * * So there can be either no backslash, or an even number
  308. * * multiply all of that times 4, to account for the escaping that has
  309. * to be done to pass the backslash into the PHP string without it being
  310. * considered as escape-char (times 2) and to get it in the regex,
  311. * escaped (times 2)
  312. */
  313. $this->registerPattern('/(['.$chars.'])(.*?(?<!\\\\)(\\\\\\\\)*+)\\1/s', $callback);
  314. }
  315. /**
  316. * This method will restore all extracted data (strings, regexes) that were
  317. * replaced with placeholder text in extract*(). The original content was
  318. * saved in $this->extracted.
  319. *
  320. * @param string $content
  321. *
  322. * @return string
  323. */
  324. protected function restoreExtractedData($content)
  325. {
  326. if (!$this->extracted) {
  327. // nothing was extracted, nothing to restore
  328. return $content;
  329. }
  330. $content = strtr($content, $this->extracted);
  331. $this->extracted = array();
  332. return $content;
  333. }
  334. }