123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364 |
- <?php
- /**
- * SCSSPHP
- *
- * @copyright 2012-2015 Leaf Corcoran
- *
- * @license http://opensource.org/licenses/MIT MIT
- *
- * @link http://leafo.github.io/scssphp
- */
- namespace Leafo\ScssPhp;
- use Leafo\ScssPhp\Compiler;
- use Leafo\ScssPhp\Exception\ServerException;
- use Leafo\ScssPhp\Version;
- /**
- * Server
- *
- * @author Leaf Corcoran <leafot@gmail.com>
- */
- class Server
- {
- /**
- * @var boolean
- */
- private $showErrorsAsCSS;
- /**
- * @var string
- */
- private $dir;
- /**
- * @var string
- */
- private $cacheDir;
- /**
- * @var \Leafo\ScssPhp\Compiler
- */
- private $scss;
- /**
- * Join path components
- *
- * @param string $left Path component, left of the directory separator
- * @param string $right Path component, right of the directory separator
- *
- * @return string
- */
- protected function join($left, $right)
- {
- return rtrim($left, '/\\') . DIRECTORY_SEPARATOR . ltrim($right, '/\\');
- }
- /**
- * Get name of requested .scss file
- *
- * @return string|null
- */
- protected function inputName()
- {
- switch (true) {
- case isset($_GET['p']):
- return $_GET['p'];
- case isset($_SERVER['PATH_INFO']):
- return $_SERVER['PATH_INFO'];
- case isset($_SERVER['DOCUMENT_URI']):
- return substr($_SERVER['DOCUMENT_URI'], strlen($_SERVER['SCRIPT_NAME']));
- }
- }
- /**
- * Get path to requested .scss file
- *
- * @return string
- */
- protected function findInput()
- {
- if (($input = $this->inputName()) && strpos($input, '..') === false && substr($input, -5) === '.scss') {
- $name = $this->join($this->dir, $input);
- if (is_file($name) && is_readable($name)) {
- return $name;
- }
- }
- return false;
- }
- /**
- * Get path to cached .css file
- *
- * @return string
- */
- protected function cacheName($fname)
- {
- return $this->join($this->cacheDir, md5($fname) . '.css');
- }
- /**
- * Get path to meta data
- *
- * @return string
- */
- protected function metadataName($out)
- {
- return $out . '.meta';
- }
- /**
- * Determine whether .scss file needs to be re-compiled.
- *
- * @param string $out Output path
- * @param string $etag ETag
- *
- * @return boolean True if compile required.
- */
- protected function needsCompile($out, &$etag)
- {
- if (!is_file($out)) {
- return true;
- }
- $mtime = filemtime($out);
- $metadataName = $this->metadataName($out);
- if (is_readable($metadataName)) {
- $metadata = unserialize(file_get_contents($metadataName));
- foreach ($metadata['imports'] as $import => $originalMtime) {
- $currentMtime = filemtime($import);
- if ($currentMtime !== $originalMtime || $currentMtime > $mtime) {
- return true;
- }
- }
- $metaVars = crc32(serialize($this->scss->getVariables()));
- if ($metaVars !== $metadata['vars']) {
- return true;
- }
- $etag = $metadata['etag'];
- return false;
- }
- return true;
- }
- /**
- * Get If-Modified-Since header from client request
- *
- * @return string|null
- */
- protected function getIfModifiedSinceHeader()
- {
- $modifiedSince = null;
- if (isset($_SERVER['HTTP_IF_MODIFIED_SINCE'])) {
- $modifiedSince = $_SERVER['HTTP_IF_MODIFIED_SINCE'];
- if (false !== ($semicolonPos = strpos($modifiedSince, ';'))) {
- $modifiedSince = substr($modifiedSince, 0, $semicolonPos);
- }
- }
- return $modifiedSince;
- }
- /**
- * Get If-None-Match header from client request
- *
- * @return string|null
- */
- protected function getIfNoneMatchHeader()
- {
- $noneMatch = null;
- if (isset($_SERVER['HTTP_IF_NONE_MATCH'])) {
- $noneMatch = $_SERVER['HTTP_IF_NONE_MATCH'];
- }
- return $noneMatch;
- }
- /**
- * Compile .scss file
- *
- * @param string $in Input path (.scss)
- * @param string $out Output path (.css)
- *
- * @return array
- */
- protected function compile($in, $out)
- {
- $start = microtime(true);
- $css = $this->scss->compile(file_get_contents($in), $in);
- $elapsed = round(microtime(true) - $start, 4);
- $v = Version::VERSION;
- $t = date('r');
- $css = "/* compiled by scssphp {$v} on {$t} ({$elapsed}s) */\n\n" . $css;
- $etag = md5($css);
- file_put_contents($out, $css);
- file_put_contents($this->metadataName($out), serialize(array('etag' => $etag, 'imports' => $this->scss->getParsedFiles(), 'vars' => crc32(serialize($this->scss->getVariables())))));
- return array($css, $etag);
- }
- /**
- * Format error as a pseudo-element in CSS
- *
- * @param \Exception $error
- *
- * @return string
- */
- protected function createErrorCSS(\Exception $error)
- {
- $message = str_replace(array('\'', '
- '), array('\\\'', '\\A'), $error->getfile() . ':
- ' . $error->getMessage());
- return "body { display: none !important; }\n html:after {\n background: white;\n color: black;\n content: '{$message}';\n display: block !important;\n font-family: mono;\n padding: 1em;\n white-space: pre;\n }";
- }
- /**
- * Render errors as a pseudo-element within valid CSS, displaying the errors on any
- * page that includes this CSS.
- *
- * @param boolean $show
- */
- public function showErrorsAsCSS($show = true)
- {
- $this->showErrorsAsCSS = $show;
- }
- /**
- * Compile .scss file
- *
- * @param string $in Input file (.scss)
- * @param string $out Output file (.css) optional
- *
- * @return string|bool
- *
- * @throws \Leafo\ScssPhp\Exception\ServerException
- */
- public function compileFile($in, $out = null)
- {
- if (!is_readable($in)) {
- throw new ServerException('load error: failed to find ' . $in);
- }
- $pi = pathinfo($in);
- $this->scss->addImportPath($pi['dirname'] . '/');
- $compiled = $this->scss->compile(file_get_contents($in), $in);
- if ($out !== null) {
- return file_put_contents($out, $compiled);
- }
- return $compiled;
- }
- /**
- * Check if file need compiling
- *
- * @param string $in Input file (.scss)
- * @param string $out Output file (.css)
- *
- * @return bool
- */
- public function checkedCompile($in, $out)
- {
- if (!is_file($out) || filemtime($in) > filemtime($out)) {
- $this->compileFile($in, $out);
- return true;
- }
- return false;
- }
- /**
- * Compile requested scss and serve css. Outputs HTTP response.
- *
- * @param string $salt Prefix a string to the filename for creating the cache name hash
- */
- public function serve($salt = '')
- {
- $protocol = isset($_SERVER['SERVER_PROTOCOL']) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0';
- if ($input = $this->findInput()) {
- $output = $this->cacheName($salt . $input);
- $etag = $noneMatch = trim($this->getIfNoneMatchHeader(), '"');
- if ($this->needsCompile($output, $etag)) {
- try {
- list($css, $etag) = $this->compile($input, $output);
- $lastModified = gmdate('D, d M Y H:i:s', filemtime($output)) . ' GMT';
- header('Last-Modified: ' . $lastModified);
- header('Content-type: text/css');
- header('ETag: "' . $etag . '"');
- echo $css;
- } catch (\Exception $e) {
- if ($this->showErrorsAsCSS) {
- header('Content-type: text/css');
- echo $this->createErrorCSS($e);
- } else {
- header($protocol . ' 500 Internal Server Error');
- header('Content-type: text/plain');
- echo 'Parse error: ' . $e->getMessage() . '
- ';
- }
- }
- return;
- }
- header('X-SCSS-Cache: true');
- header('Content-type: text/css');
- header('ETag: "' . $etag . '"');
- if ($etag === $noneMatch) {
- header($protocol . ' 304 Not Modified');
- return;
- }
- $modifiedSince = $this->getIfModifiedSinceHeader();
- $mtime = filemtime($output);
- if (strtotime($modifiedSince) === $mtime) {
- header($protocol . ' 304 Not Modified');
- return;
- }
- $lastModified = gmdate('D, d M Y H:i:s', $mtime) . ' GMT';
- header('Last-Modified: ' . $lastModified);
- echo file_get_contents($output);
- return;
- }
- header($protocol . ' 404 Not Found');
- header('Content-type: text/plain');
- $v = Version::VERSION;
- echo "/* INPUT NOT FOUND scss {$v} */\n";
- }
- /**
- * Based on explicit input/output files does a full change check on cache before compiling.
- *
- * @param string $in
- * @param string $out
- * @param boolean $force
- *
- * @return string Compiled CSS results
- *
- * @throws \Leafo\ScssPhp\Exception\ServerException
- */
- public function checkedCachedCompile($in, $out, $force = false)
- {
- if (!is_file($in) || !is_readable($in)) {
- throw new ServerException('Invalid or unreadable input file specified.');
- }
- if (is_dir($out) || !is_writable(file_exists($out) ? $out : dirname($out))) {
- throw new ServerException('Invalid or unwritable output file specified.');
- }
- if ($force || $this->needsCompile($out, $etag)) {
- list($css, $etag) = $this->compile($in, $out);
- } else {
- $css = file_get_contents($out);
- }
- return $css;
- }
- /**
- * Constructor
- *
- * @param string $dir Root directory to .scss files
- * @param string $cacheDir Cache directory
- * @param \Leafo\ScssPhp\Compiler|null $scss SCSS compiler instance
- */
- public function __construct($dir, $cacheDir = null, $scss = null)
- {
- $this->dir = $dir;
- if (!isset($cacheDir)) {
- $cacheDir = $this->join($dir, 'scss_cache');
- }
- $this->cacheDir = $cacheDir;
- if (!is_dir($this->cacheDir)) {
- mkdir($this->cacheDir, 493, true);
- }
- if (!isset($scss)) {
- $scss = new Compiler();
- $scss->setImportPaths($this->dir);
- }
- $this->scss = $scss;
- $this->showErrorsAsCSS = false;
- if (!ini_get('date.timezone')) {
- date_default_timezone_set('UTC');
- }
- }
- /**
- * Helper method to serve compiled scss
- *
- * @param string $path Root path
- */
- public static function serveFrom($path)
- {
- $server = new self($path);
- $server->serve();
- }
- }
|