PageRenderTime 39ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 0ms

/vendor/leafo/scssphp/example/Server.php

https://bitbucket.org/ltdwebant/laboratorium
PHP | 452 lines | 329 code | 36 blank | 87 comment | 14 complexity | 0515025ba55922dde17ee328ab86477c MD5 | raw file
Possible License(s): Apache-2.0, BSD-3-Clause
  1. <?php
  2. /**
  3. * SCSSPHP
  4. *
  5. * @copyright 2012-2017 Leaf Corcoran
  6. *
  7. * @license http://opensource.org/licenses/MIT MIT
  8. *
  9. * @link http://leafo.github.io/scssphp
  10. */
  11. namespace Leafo\ScssPhp;
  12. use Leafo\ScssPhp\Compiler;
  13. use Leafo\ScssPhp\Exception\ServerException;
  14. use Leafo\ScssPhp\Version;
  15. /**
  16. * Server
  17. *
  18. * @author Leaf Corcoran <leafot@gmail.com>
  19. */
  20. class Server
  21. {
  22. /**
  23. * @var boolean
  24. */
  25. private $showErrorsAsCSS;
  26. /**
  27. * @var string
  28. */
  29. private $dir;
  30. /**
  31. * @var string
  32. */
  33. private $cacheDir;
  34. /**
  35. * @var \Leafo\ScssPhp\Compiler
  36. */
  37. private $scss;
  38. /**
  39. * Join path components
  40. *
  41. * @param string $left Path component, left of the directory separator
  42. * @param string $right Path component, right of the directory separator
  43. *
  44. * @return string
  45. */
  46. protected function join($left, $right)
  47. {
  48. return rtrim($left, '/\\') . DIRECTORY_SEPARATOR . ltrim($right, '/\\');
  49. }
  50. /**
  51. * Get name of requested .scss file
  52. *
  53. * @return string|null
  54. */
  55. protected function inputName()
  56. {
  57. switch (true) {
  58. case isset($_GET['p']):
  59. return $_GET['p'];
  60. case isset($_SERVER['PATH_INFO']):
  61. return $_SERVER['PATH_INFO'];
  62. case isset($_SERVER['DOCUMENT_URI']):
  63. return substr($_SERVER['DOCUMENT_URI'], strlen($_SERVER['SCRIPT_NAME']));
  64. }
  65. }
  66. /**
  67. * Get path to requested .scss file
  68. *
  69. * @return string
  70. */
  71. protected function findInput()
  72. {
  73. if (($input = $this->inputName())
  74. && strpos($input, '..') === false
  75. && substr($input, -5) === '.scss'
  76. ) {
  77. $name = $this->join($this->dir, $input);
  78. if (is_file($name) && is_readable($name)) {
  79. return $name;
  80. }
  81. }
  82. return false;
  83. }
  84. /**
  85. * Get path to cached .css file
  86. *
  87. * @return string
  88. */
  89. protected function cacheName($fname)
  90. {
  91. return $this->join($this->cacheDir, md5($fname) . '.css');
  92. }
  93. /**
  94. * Get path to meta data
  95. *
  96. * @return string
  97. */
  98. protected function metadataName($out)
  99. {
  100. return $out . '.meta';
  101. }
  102. /**
  103. * Determine whether .scss file needs to be re-compiled.
  104. *
  105. * @param string $out Output path
  106. * @param string $etag ETag
  107. *
  108. * @return boolean True if compile required.
  109. */
  110. protected function needsCompile($out, &$etag)
  111. {
  112. if (! is_file($out)) {
  113. return true;
  114. }
  115. $mtime = filemtime($out);
  116. $metadataName = $this->metadataName($out);
  117. if (is_readable($metadataName)) {
  118. $metadata = unserialize(file_get_contents($metadataName));
  119. foreach ($metadata['imports'] as $import => $originalMtime) {
  120. $currentMtime = filemtime($import);
  121. if ($currentMtime !== $originalMtime || $currentMtime > $mtime) {
  122. return true;
  123. }
  124. }
  125. $metaVars = crc32(serialize($this->scss->getVariables()));
  126. if ($metaVars !== $metadata['vars']) {
  127. return true;
  128. }
  129. $etag = $metadata['etag'];
  130. return false;
  131. }
  132. return true;
  133. }
  134. /**
  135. * Get If-Modified-Since header from client request
  136. *
  137. * @return string|null
  138. */
  139. protected function getIfModifiedSinceHeader()
  140. {
  141. $modifiedSince = null;
  142. if (isset($_SERVER['HTTP_IF_MODIFIED_SINCE'])) {
  143. $modifiedSince = $_SERVER['HTTP_IF_MODIFIED_SINCE'];
  144. if (false !== ($semicolonPos = strpos($modifiedSince, ';'))) {
  145. $modifiedSince = substr($modifiedSince, 0, $semicolonPos);
  146. }
  147. }
  148. return $modifiedSince;
  149. }
  150. /**
  151. * Get If-None-Match header from client request
  152. *
  153. * @return string|null
  154. */
  155. protected function getIfNoneMatchHeader()
  156. {
  157. $noneMatch = null;
  158. if (isset($_SERVER['HTTP_IF_NONE_MATCH'])) {
  159. $noneMatch = $_SERVER['HTTP_IF_NONE_MATCH'];
  160. }
  161. return $noneMatch;
  162. }
  163. /**
  164. * Compile .scss file
  165. *
  166. * @param string $in Input path (.scss)
  167. * @param string $out Output path (.css)
  168. *
  169. * @return array
  170. */
  171. protected function compile($in, $out)
  172. {
  173. $start = microtime(true);
  174. $css = $this->scss->compile(file_get_contents($in), $in);
  175. $elapsed = round((microtime(true) - $start), 4);
  176. $v = Version::VERSION;
  177. $t = date('r');
  178. $css = "/* compiled by scssphp $v on $t (${elapsed}s) */\n\n" . $css;
  179. $etag = md5($css);
  180. file_put_contents($out, $css);
  181. file_put_contents(
  182. $this->metadataName($out),
  183. serialize([
  184. 'etag' => $etag,
  185. 'imports' => $this->scss->getParsedFiles(),
  186. 'vars' => crc32(serialize($this->scss->getVariables())),
  187. ])
  188. );
  189. return [$css, $etag];
  190. }
  191. /**
  192. * Format error as a pseudo-element in CSS
  193. *
  194. * @param \Exception $error
  195. *
  196. * @return string
  197. */
  198. protected function createErrorCSS(\Exception $error)
  199. {
  200. $message = str_replace(
  201. ["'", "\n"],
  202. ["\\'", "\\A"],
  203. $error->getfile() . ":\n\n" . $error->getMessage()
  204. );
  205. return "body { display: none !important; }
  206. html:after {
  207. background: white;
  208. color: black;
  209. content: '$message';
  210. display: block !important;
  211. font-family: mono;
  212. padding: 1em;
  213. white-space: pre;
  214. }";
  215. }
  216. /**
  217. * Render errors as a pseudo-element within valid CSS, displaying the errors on any
  218. * page that includes this CSS.
  219. *
  220. * @param boolean $show
  221. */
  222. public function showErrorsAsCSS($show = true)
  223. {
  224. $this->showErrorsAsCSS = $show;
  225. }
  226. /**
  227. * Compile .scss file
  228. *
  229. * @param string $in Input file (.scss)
  230. * @param string $out Output file (.css) optional
  231. *
  232. * @return string|bool
  233. *
  234. * @throws \Leafo\ScssPhp\Exception\ServerException
  235. */
  236. public function compileFile($in, $out = null)
  237. {
  238. if (! is_readable($in)) {
  239. throw new ServerException('load error: failed to find ' . $in);
  240. }
  241. $pi = pathinfo($in);
  242. $this->scss->addImportPath($pi['dirname'] . '/');
  243. $compiled = $this->scss->compile(file_get_contents($in), $in);
  244. if ($out !== null) {
  245. return file_put_contents($out, $compiled);
  246. }
  247. return $compiled;
  248. }
  249. /**
  250. * Check if file need compiling
  251. *
  252. * @param string $in Input file (.scss)
  253. * @param string $out Output file (.css)
  254. *
  255. * @return bool
  256. */
  257. public function checkedCompile($in, $out)
  258. {
  259. if (! is_file($out) || filemtime($in) > filemtime($out)) {
  260. $this->compileFile($in, $out);
  261. return true;
  262. }
  263. return false;
  264. }
  265. /**
  266. * Compile requested scss and serve css. Outputs HTTP response.
  267. *
  268. * @param string $salt Prefix a string to the filename for creating the cache name hash
  269. */
  270. public function serve($salt = '')
  271. {
  272. $protocol = isset($_SERVER['SERVER_PROTOCOL'])
  273. ? $_SERVER['SERVER_PROTOCOL']
  274. : 'HTTP/1.0';
  275. if ($input = $this->findInput()) {
  276. $output = $this->cacheName($salt . $input);
  277. $etag = $noneMatch = trim($this->getIfNoneMatchHeader(), '"');
  278. if ($this->needsCompile($output, $etag)) {
  279. try {
  280. list($css, $etag) = $this->compile($input, $output);
  281. $lastModified = gmdate('D, d M Y H:i:s', filemtime($output)) . ' GMT';
  282. header('Last-Modified: ' . $lastModified);
  283. header('Content-type: text/css');
  284. header('ETag: "' . $etag . '"');
  285. echo $css;
  286. } catch (\Exception $e) {
  287. if ($this->showErrorsAsCSS) {
  288. header('Content-type: text/css');
  289. echo $this->createErrorCSS($e);
  290. } else {
  291. header($protocol . ' 500 Internal Server Error');
  292. header('Content-type: text/plain');
  293. echo 'Parse error: ' . $e->getMessage() . "\n";
  294. }
  295. }
  296. return;
  297. }
  298. header('X-SCSS-Cache: true');
  299. header('Content-type: text/css');
  300. header('ETag: "' . $etag . '"');
  301. if ($etag === $noneMatch) {
  302. header($protocol . ' 304 Not Modified');
  303. return;
  304. }
  305. $modifiedSince = $this->getIfModifiedSinceHeader();
  306. $mtime = filemtime($output);
  307. if (strtotime($modifiedSince) === $mtime) {
  308. header($protocol . ' 304 Not Modified');
  309. return;
  310. }
  311. $lastModified = gmdate('D, d M Y H:i:s', $mtime) . ' GMT';
  312. header('Last-Modified: ' . $lastModified);
  313. echo file_get_contents($output);
  314. return;
  315. }
  316. header($protocol . ' 404 Not Found');
  317. header('Content-type: text/plain');
  318. $v = Version::VERSION;
  319. echo "/* INPUT NOT FOUND scss $v */\n";
  320. }
  321. /**
  322. * Based on explicit input/output files does a full change check on cache before compiling.
  323. *
  324. * @param string $in
  325. * @param string $out
  326. * @param boolean $force
  327. *
  328. * @return string Compiled CSS results
  329. *
  330. * @throws \Leafo\ScssPhp\Exception\ServerException
  331. */
  332. public function checkedCachedCompile($in, $out, $force = false)
  333. {
  334. if (! is_file($in) || ! is_readable($in)) {
  335. throw new ServerException('Invalid or unreadable input file specified.');
  336. }
  337. if (is_dir($out) || ! is_writable(file_exists($out) ? $out : dirname($out))) {
  338. throw new ServerException('Invalid or unwritable output file specified.');
  339. }
  340. if ($force || $this->needsCompile($out, $etag)) {
  341. list($css, $etag) = $this->compile($in, $out);
  342. } else {
  343. $css = file_get_contents($out);
  344. }
  345. return $css;
  346. }
  347. /**
  348. * Constructor
  349. *
  350. * @param string $dir Root directory to .scss files
  351. * @param string $cacheDir Cache directory
  352. * @param \Leafo\ScssPhp\Compiler|null $scss SCSS compiler instance
  353. */
  354. public function __construct($dir, $cacheDir = null, $scss = null)
  355. {
  356. $this->dir = $dir;
  357. if (! isset($cacheDir)) {
  358. $cacheDir = $this->join($dir, 'scss_cache');
  359. }
  360. $this->cacheDir = $cacheDir;
  361. if (! is_dir($this->cacheDir)) {
  362. throw new ServerException('Cache directory doesn\'t exist: ' . $cacheDir);
  363. }
  364. if (! isset($scss)) {
  365. $scss = new Compiler();
  366. $scss->setImportPaths($this->dir);
  367. }
  368. $this->scss = $scss;
  369. $this->showErrorsAsCSS = false;
  370. if (! ini_get('date.timezone')) {
  371. throw new ServerException('Default date.timezone not set');
  372. }
  373. }
  374. }