/css/cc-css.php
PHP | 539 lines | 414 code | 98 blank | 27 comment | 59 complexity | 4df2d77abf64f7e791e665bdefaacc6b MD5 | raw file
- <?php
-
- /**
- * ===================================================================
- * Copyright (c) 2013 tany
- * Licensed under The MIT License <http://opensource.org/licenses/MIT>
- * ===================================================================
- */
-
- // ------ call ------ //
-
- $css = new CC_CSS();
-
- //$css->output('../css-output'); // output directory
-
- $css->prepend('usual.cc.css'); // auto prepend file
-
- $css->cache()->decode()->flush(); // output..
-
- // ------ class ------ //
-
- class CC_CSS {
-
- public
- $meta = array("@charset \"UTF-8\";\n");
-
- protected
- $_output = null;
-
- protected
- $_cache = null;
-
- protected
- $_mtime = null;
-
- protected
- $_file = null;
-
- protected
- $_uri = null;
-
- protected
- $_prepended = array();
-
- protected
- $_current = array();
-
- protected
- $_contents = array();
-
- protected
- $_css = '';
-
- protected
- $_vars = array();
-
- protected
- $_errors = array();
-
- public function __construct() {
- $this->_uri = dirname($_SERVER['SCRIPT_NAME']);
- $this->_mtime = filemtime(__FILE__);
- }
-
- public function output($dir) {
- $this->_output = $dir;
- }
-
- public function prepend($file) {
- $this->_readFile($file);
- $this->_prepended[] = $file;
- return $this;
- }
-
- public function cache($expires = '+3 days') {
- $this->_cache = $expires;
- return $this;
- }
-
- public function decode() {
- $file = isset($_REQUEST['file']) ? "{$_REQUEST['file']}.cc.css" : null;
- if (strpos($file, '..') !== false || strpos($file, '://') !== false || $file[0] === '/' || !is_file($file)) {
- header('HTTP/1.1 404 Not Found');
- exit;
- }
- $this->_file = $file;
- $this->_readFile($file);
-
- if ($this->_cache) {
- $headers = apache_request_headers();
- if (isset($headers['If-Modified-Since']) && (strtotime($headers['If-Modified-Since']) == $this->_mtime)) {
- $this->_cache = true;
- return $this;
- }
- }
- foreach ($this->_prepended as $file) {
- $this->_css .= $this->_decodeFile($file);
- }
- $this->_css .= $this->_decodeFile($this->_file);
-
- return $this;
- }
-
- public function flush() {
- // cache
- if (count($this->_errors) == 0) {
- if ($this->_cache === true) {
- header('Last-Modified: ' . gmdate('D, d M Y H:i:s', $this->_mtime) . ' GMT', true, 304);
- return $this;
- } else if ($this->_cache) {
- $expires = new DateTime($this->_cache);
- header('Last-Modified: ' . gmdate('D, d M Y H:i:s', $this->_mtime) . ' GMT');
- header('Cache-Control: max-age=' . ($expires->format('U') - time()));
- }
- }
-
- $css = implode('', array_unique($this->meta)) . "\n{$this->_css}";
- $css = preg_replace('/\n{2,}/m', "\n\n", $css);
- if (count($this->_errors)) {
- $hr = str_repeat('=', 70);
- $err = "/*\n\nNotice: compile error\n{$hr} \n- " . implode("\n- ", $this->_errors) . "\n{$hr} \n\n*/\n\n";
- $css = preg_replace('/^(?!@charset|@import)/m', $err . '$1', $css, 1);
- }
- if ($this->_output) {
- $this->_outputFile($css);
- }
- header('Content-type: text/css; charset=utf-8');
- //header('Content-type: text/plain; charset=utf-8');
- print $css;
- }
-
- protected function _readFile($file, $src = null) {
- if (!is_file($file = $this->_fixPath($src, $file))) {
- return $this->_error("no such file: {$file} in {$src}");
- }
- if (!isset($this->_contents[$file])) {
- $this->_contents[$file] = file_get_contents($file);
- $this->_mtime = max($this->_mtime, filemtime($file));
- if (preg_match_all('/^\s*@include ["\'](.*?)["\'];/m', $this->_contents[$file], $m)) {
- foreach ($m[1] as $_file) {
- $this->_readFile($_file, $file);
- }
- }
- }
- }
-
- protected function _outputFile($css) {
- $base = $this->_output;
- $file = "{$base}/" . preg_replace('/\.cc\.css$/', '.css', $this->_file);
- $dir = dirname($file);
-
- if (!is_dir($base)) {
- mkdir($base);
- chmod($base, 0757);
- }
- if (!is_dir($dir) || !is_writable($dir)) {
- $dir = $base;
- foreach (explode('/', dirname($this->_file)) as $name) {
- $dir .= "/{$name}";
- if (!is_dir($dir)) mkdir($dir);
- chmod($dir, 0757);
- }
- }
- if (is_file($file)) {
- if (!is_writable($file)) chmod($file, 0646);
- if (file_get_contents($file) !== $css) file_put_contents($file, $css);
- } else {
- file_put_contents($file, $css);
- chmod($file, 0646);
- }
- }
-
- protected function _decodeFile($file) {
- if (!isset($this->_contents[$file])) {
- return null;
- }
- array_unshift($this->_current, $file);
-
- $css = $this->_contents[$file];
- $css = preg_replace('/^@charset.*?(\n|$)/', '', $css); // remove @charset
- $css = preg_replace('/(^\s*|\s+)\/\/.*$/m', '', $css); // remove comment
-
- if (count($this->_current) > 1) { // rewrite url on @include
- $css = preg_replace_callback('/^@import ["\'](.*?)["\'];/m', array($this, '_importCallback'), $css);
- $css = preg_replace_callback('/url\(["\']?(.*?)["\']?\)/', array($this, '_urlCallback'), $css);
- }
- if (preg_match_all('/^@import .*?(?:\r\n|\n|$)/m', $css, $m)) { // stack @import
- $this->meta = array_merge($this->meta, $m[0]);
- $css = preg_replace('/^(@import .*?(\r\n|\n|$))/m', '', $css);
- }
-
- $xml = htmlspecialchars($css);
- $xml = preg_replace('/\s*,(\r\n|\n)\s*/ms', ', ', $xml);
- $xml = preg_replace('/((?:^|\{|\}|;|)\s*)(.*?)\s*\{/m', '$1<s name="$2">', $xml);
- $xml = preg_replace('/\}/', '</s>', $xml);
-
- $doc = new DOMDocument;
- $doc->loadXML("<xml>{$xml}</xml>");
-
- $css = $this->_decodeNode($doc->firstChild);
- $css = htmlspecialchars_decode($css);
-
- array_shift($this->_current);
- return $css;
- }
-
- protected function _decodeNode($node, $path = null) {
- $css = '';
-
- if ($node instanceof DOMText) {
- $value = $node->nodeValue;
-
- // @include
- if (preg_match_all('/^\s*@include ["\'](.*?)["\'];/m', $value, $m)) {
- foreach ($m[1] as $i => $file) {
- $file = $this->_fixPath($this->_current[0], $file);
- $value = preg_replace('/' . preg_quote($m[0][$i], '/') . '/', $this->_decodeFile($file), $value, 1);
- }
- }
-
- // $var or make css
- if (preg_match_all('/\$([a-zA-Z\-_][\w\-]*)\s*(?::\s*(.*?))?;/', $value, $m)) {
- foreach ($m[1] as $i => $key) {
- $method = 'css_' . str_replace('-', '_', $key);
- if (isset($this->_vars[$key])) {
- $args = trim($m[2][$i]) ? preg_split('/\s*,\s*/', $m[2][$i]) : array();
- $xml = $this->_vars[$key][1];
- foreach ($this->_vars[$key][0] as $j => $arg) {
- $arg = isset($args[$j]) && $args[$j] !== 'null' ? $args[$j] : $arg;
- $xml = preg_replace('/\$' . ($j + 1) . '/', $arg, $xml);
- }
- $value = preg_replace('/' . preg_quote($m[0][$i], '/') . '/', $xml, $value, 1);
- } else if (method_exists($this, $method)) {
- $args = trim($m[2][$i]) ? preg_split('/\s*,\s*/', $m[2][$i]) : array();
- $res = call_user_func_array(array($this, $method), $args);
- $value = preg_replace('/' . preg_quote($m[0][$i], '/') . '/', $res, $value, 1);
- } else {
- $value = preg_replace('/' . preg_quote($m[0][$i], '/') . '/', '', $value);
- $this->_error('undefined variable: $' . "{$key} in {$this->_current[0]}");
- }
- }
- $doc = new DOMDocument;
- $doc->loadXML("<xml>{$value}</xml>");
- $node = $doc->firstChild;
- } else if ($path) {
- $value = trim($value);
- $css .= implode(", ", $path) . " {\n" . preg_replace('/^\s*/m', ' ', $value) . "\n}\n";
- // foreach ($path as $p) {
- // $css[] = $p ? "{$p} {\n" . preg_replace('/^\s*/m', ' ', $value) . "\n}\n" : "{$value}\n";
- // }
- } else {
- $css .= $value;
- }
- }
-
- if (!($node instanceof DOMElement)) {
- return $css;
- }
- $name = $node->getAttribute('name');
-
- // @media
- if ($name && $name[0] === '@') {
- $node->setAttribute('name', '');
- return "{$name} {\n" . preg_replace('/^/m', ' ', $this->_decodeNode($node)) . "}\n";
- }
-
- // $var
- if ($key = preg_filter('/.*\$([\w\-]+).*/', '$1', $name)) {
- $xml = array();
- foreach ($node->childNodes as $cn) {
- $xml[] = $cn->C14N();
- }
- $args = preg_split('/\s*,\s*/', preg_filter('/.*\((.*?)\).*/', '$1', $name));
- $this->_vars[$key] = array($args, implode('', $xml));
-
- $name = trim(preg_replace('/(^|,)\s*\$[\w\-]+\s*(\(.*?\))?(,|$)/', '$1', $name), ',');
- if (!$name) return $css;
- }
-
- foreach ($node->childNodes as $child) {
- if (($child instanceof DOMText) && !trim($child->nodeValue)) {
- continue;
- }
- if ($path && $name) {
- $next = array();
- foreach (preg_split('/\s*,\s*/', $name) as $n) {
- foreach ($path as $p) {
- $next[] = str_replace(' :', ':', trim("{$p} {$n}"));
- }
- }
- } else if ($path) {
- $next = $path;
- } else if ($name) {
- $next = preg_split('/\s*,\s*/', $name);
- } else {
- $next = null;
- }
- $css .= $this->_decodeNode($child, $next);
- }
- return $css;
- }
-
- protected function _fixPath($src, $dst) {
- if ($dst[0] === '/') {
- return preg_replace('/^' . preg_quote($this->_uri . '/', '/') . '/', '', $dst);
- }
- $path = $src ? dirname($src) . "/{$dst}" : $dst;
- $path = str_replace('/./', '/', $path);
- $path = preg_replace('/[^\/]+\/\.\.\//', '', $path);
- return $path;
- }
-
- protected function _fixUri($path) {
- $path = str_replace('/./', '/', $path);
- $path = str_replace('//', '/', $path);
- $path = preg_replace('/[^\/]+\/\.\.\//', '', $path);
- $path = preg_replace('/^\.\//', '', $path);
- return $path;
- }
-
- protected function _importCallback($m) {
- $file = $m[1];
- if ($file[0] === '/' || strpos($file, '//') !== false) {
- return $m[0];
- }
- $file = dirname($this->_current[0]) . "/{$file}";
- return '@import "'. $this->_fixUri($file) . '";';
- }
-
- protected function _urlCallback($m) {
- $file = $m[1];
- if ($file[0] === '/' || strpos($file, '//') !== false) {
- return $m[0];
- }
- $file = "{$this->_uri}/" . dirname($this->_current[0]) . "/{$file}";
- return 'url(' . $this->_fixUri($file) . ')';
- }
-
- protected function _error($msg = null) {
- $this->_errors[] = $msg;
- return false;
- }
-
- // ------ vendor prefix ------ //
-
- protected function css_vendor_prefix($name, $value) {
- return implode("\n", array(
- "{$name}: {$value};",
- "-o-{$name}: {$value};",
- "-ms-{$name}: {$value};",
- "-moz-{$name}: {$value};",
- "-webkit-{$name}: {$value};",
- ));
- }
-
- // ------ background ------ //
-
- protected function css_background_clip($value = 'border') {
- $value = str_replace('-box', '', $value);
- return implode("\n", array(
- "background-clip: {$value}-box;",
- "-o-background-clip: {$value}-box;",
- "-ms-background-clip: {$value}-box;",
- "-moz-background-clip: {$value};",
- "-webkit-background-clip: {$value}-box;",
- ));
- }
-
- protected function css_background_origin($value = 'padding') {
- $value = str_replace('-box', '', $value);
- return implode("\n", array(
- "background-origin: {$value}-box;",
- "-o-background-origin: {$value}-box;",
- "-ms-background-origin: {$value}-box;",
- "-moz-background-origin: {$value};",
- "-webkit-background-origin: {$value}-box;",
- ));
- }
-
- protected function css_background_size($value = 'auto') {
- return $this->css_vendor_prefix('background-size', $value);
- }
-
- // ------ border ------ //
-
- protected function css_border_radius($value = '0') {
- return $this->css_vendor_prefix('border-radius', $value);
- }
-
- protected function css_border_top_left_radius($value = '0') {
- return $this->css_vendor_prefix('border-top-left-radius', $value);
- }
-
- protected function css_border_top_right_radius($value = '0') {
- return $this->css_vendor_prefix('border-top-right-radius', $value);
- }
-
- protected function css_border_bottom_left_radius($value = '0') {
- return $this->css_vendor_prefix('border-bottom-left-radius', $value);
- }
-
- protected function css_border_bottom_right_radius($value = '0') {
- return $this->css_vendor_prefix('border-bottom-right-radius', $value);
- }
-
- protected function css_border_image($value = 'none') {
- return $this->css_vendor_prefix('border-image', $value);
- }
-
- // ------ border ------ //
-
- protected function css_border_image_source($value = 'none') {
- return $this->css_vendor_prefix('border_image_source', $value);
- }
-
- protected function css_box_shadow($value = 'none') {
- return $this->css_vendor_prefix('box-shadow', $value);
- }
-
- // ------ transform ------ //
-
- protected function css_transform($value = 'none') {
- return $this->css_vendor_prefix('transform', $value);
- }
-
- // ------ transition ------ //
-
- protected function css_transition($value = 'none 0 ease 0') {
- return $this->css_vendor_prefix('transition', $value);
- }
-
- protected function css_transition_property($value = 'none') {
- return $this->css_vendor_prefix('transition-property', $value);
- }
-
- protected function css_transition_duration($value = '0') {
- return $this->css_vendor_prefix('transition-duration', $value);
- }
-
- protected function css_transition_timing_function($value = 'ease') {
- return $this->css_vendor_prefix('transition-timing-function', $value);
- }
-
- protected function css_transition_delay($value = '0') {
- return $this->css_vendor_prefix('transition-delay', $value);
- }
-
- // ------ animation ------ //
-
- protected function css_animation($value = 'none 0 ease 0 1 normal') {
- return $this->css_vendor_prefix('animation', $value);
- }
-
- protected function css_animation_name($value = 'none') {
- return $this->css_vendor_prefix('animation-name', $value);
- }
-
- protected function css_animation_duration($value = '0') {
- return $this->css_vendor_prefix('animation-duration', $value);
- }
-
- protected function css_animation_timing_function($value = 'ease') {
- return $this->css_vendor_prefix('animation-timing-function', $value);
- }
-
- protected function css_animation_iteration_count($value = '1') {
- return $this->css_vendor_prefix('animation-iteration-count', $value);
- }
-
- protected function css_animation_direction($value = 'normal') {
- return $this->css_vendor_prefix('animation-direction', $value);
- }
-
- protected function css_animation_play_state($value = 'none') {
- return $this->css_vendor_prefix('animation-play-state', $value);
- }
-
- protected function css_animation_delay($value = '0') {
- return $this->css_vendor_prefix('animation-delay', $value);
- }
-
- // ------ gradation ------ //
-
- protected function css_linear_gradient($value = 'none') {
- $value = implode(', ', func_get_args());
- return implode("\n", array(
- "background: linear-gradient({$value});",
- "background: -o-linear-gradient({$value});",
- "background: -ms-linear-gradient({$value});",
- "background: -moz-linear-gradient({$value});",
- "background: -webkit-linear-gradient({$value});",
- ));
- }
-
- protected function css_radial_gradient($value = 'none') {
- $value = implode(', ', func_get_args());
- return implode("\n", array(
- "background: radial-gradient({$value});",
- "background: -o-radial-gradient({$value});",
- "background: -ms-radial-gradient({$value});",
- "background: -moz-radial-gradient({$value});",
- "background: -webkit-radial-gradient({$value});",
- ));
- }
-
- //------ box layout ------ //
-
- protected function css_box_orient($value = 'inline-axis') {
- return $this->css_vendor_prefix('box-orient', $value);
- }
-
- protected function css_box_direction($value = 'normal') {
- return $this->css_vendor_prefix('box-direction', $value);
- }
-
- protected function css_box_ordinal_group($value = '1') {
- return $this->css_vendor_prefix('box-ordinal-group', $value);
- }
-
- protected function css_box_align($value = 'stretch') {
- return $this->css_vendor_prefix('box-align', $value);
- }
-
- protected function css_box_flex($value = '0.0') {
- return $this->css_vendor_prefix('box-flex', $value);
- }
-
- protected function css_box_flex_group($value = '1') {
- return $this->css_vendor_prefix('box-flex-group', $value);
- }
-
- protected function css_box_pack($value = 'start') {
- return $this->css_vendor_prefix('box-pack', $value);
- }
-
- protected function css_box_lines($value = 'single') {
- return $this->css_vendor_prefix('box-lines', $value);
- }
- }