. */ /* * A class to inline CSS. * * It honours !important attributes and doesn't choke on complex styles. * * */ class CSS { private $cssFiles = array(); private $parsed_css = array(); /* * Retrieves a CSS stylesheet and caches it before returning it. */ public function getCSS($url) { if(!isset($cssFiles[$url])) { $cssFiles[$url] = file_get_contents($url); } return $cssFiles[$url]; } /* * Take a list of absolute URLs pointing to CSS stylesheets, * retrieve the CSS, parse it, sort the rules by increasing order of specificity, * cache the rules, return them. */ public function getCSSFromFiles($urls) { $key = implode('::', $urls); if(!isset($this->parsed_css[$key])) { $texts = array(); foreach($urls as $url) { $texts[] = $this->getCSS($url); } $text = implode("\n\n", $texts); $this->parsed_css[$key] = $text; } return $this->parsed_css[$key]; } public static function splitMediaQueries($css) { $start = 0; $queries = ''; while (($start = strpos($css, "@media", $start)) !== false) { // stack to manage brackets $s = array(); // get the first opening bracket $i = strpos($css, "{", $start); // if $i is false, then there is probably a css syntax error if ($i !== false) { // push bracket onto stack array_push($s, $css[$i]); // move past first bracket $i++; while (!empty($s)) { // if the character is an opening bracket, push it onto the stack, otherwise pop the stack if ($css[$i] == "{") { array_push($s, "{"); } elseif ($css[$i] == "}") { array_pop($s); } $i++; } $queries .= substr($css, $start-1, $i+1-$start) . "\n"; $css = substr($css, 0, $start-1) . substr($css, $i); $i = $start; } } return array($css, $queries); } public function parseCSS($text) { $css = new csstidy(); $css->settings['compress_colors'] = false; $css->parse($text); $rules = array(); $position = 0; foreach($css->css as $declarations) { foreach($declarations as $selectors => $properties) { foreach(explode(",", $selectors) as $selector) { $rules[] = array( 'position' => $position, 'specificity' => self::calculateCSSSpecifity($selector), 'selector' => $selector, 'properties' => $properties ); } $position += 1; } } usort($rules, function($a, $b){ if($a['specificity'] > $b['specificity']) { return 1; } else if($a['specificity'] < $b['specificity']) { return -1; } else { if($a['position'] > $b['position']) { return 1; } else { return -1; } } }); return $rules; } /** * The following function fomes from CssToInlineStyles.php - here is the original licence FOR THIS FUNCTION * * CSS to Inline Styles class * * @author Tijs Verkoyen * @version 1.2.1 * @copyright Copyright (c), Tijs Verkoyen. All rights reserved. * @license BSD License */ public static function calculateCSSSpecifity($selector) { // cleanup selector $selector = str_replace(array('>', '+'), array(' > ', ' + '), $selector); // init var $specifity = 0; // split the selector into chunks based on spaces $chunks = explode(' ', $selector); // loop chunks foreach ($chunks as $chunk) { // an ID is important, so give it a high specifity if(strstr($chunk, '#') !== false) $specifity += 100; // classes are more important than a tag, but less important then an ID elseif(strstr($chunk, '.')) $specifity += 10; // anything else isn't that important else $specifity += 1; } // return return $specifity; } /* * Turns a CSS style string (like: "border: 1px solid black; color:red") * into an array of properties (like: array("border" => "1px solid black", "color" => "red")) */ public static function styleToArray($str) { $array = array(); if(trim($str) === '')return $array; foreach(explode(';', $str) as $kv) { if ($kv === '') continue; $key_value = explode(':', $kv); $array[trim($key_value[0])] = trim($key_value[1]); } return $array; } /* * Reverses what styleToArray does, see above. * array("border" => "1px solid black", "color" => "red") yields "border: 1px solid black; color:red" */ public static function arrayToStyle($array) { $parts = array(); foreach($array as $k => $v) { $parts[] = "$k:$v"; } return implode(';', $parts); } /* * Get an absolute URL from an URL ($relative_url, but relative or not actually!) * that is found on the page with url $page_url. * Determine it as a browser would do. For instance if "hi" * (here '/bob/hello.html' is the $relative_url) * is found on a page at $page_url := "http://example.com/stuff/index.html" * then the function returns "http://example.com/bob/hello.html" * because that's where you'd go to if you clicked on the link in your browser. * This is used to find where to download the CSS files from when inlining. */ public static function absolutify($page_url, $relative_url) { $parsed_url = parse_url($page_url); $absolute_url = ''; $parsed_relative_url = parse_url($relative_url); // If $relative_url has a host it is actually absolute, return it. if(isset($parsed_relative_url['host'])) { $absolute_url = $relative_url; } // If $relative_url begins with / then it is a path relative to the $page_url's host else if(preg_match('/^\//', $parsed_relative_url['path'])) { $absolute_url = $parsed_url['scheme'].'://'.$parsed_url['host'].$parsed_relative_url['path']; } // No leading slash: append the path of $relative_url to the 'folder' path of $page_url else { $absolute_url = $parsed_url['scheme'].'://'.$parsed_url['host'].dirname($parsed_url['path']).'/'.$parsed_relative_url['path']; } return $absolute_url; } /* * The core of the algorithm, takes a URL and returns the HTML found there with the CSS inlined. * If you pass $contents then the original HTML is not downloaded and $contents is used instead. * $url is mandatory as it is used to resolve the links to the stylesheets found in the HTML. */ function inlineCSS($url, $contents=null) { // Download the HTML if it was not provided if($contents === null) { $html = HtmlDomParser::file_get_html($url, false, null, -1, -1, true, true, DEFAULT_TARGET_CHARSET, false, DEFAULT_BR_TEXT, DEFAULT_SPAN_TEXT); } // Else use the data provided! else { $html = HtmlDomParser::str_get_html($contents, true, true, DEFAULT_TARGET_CHARSET, false, DEFAULT_BR_TEXT, DEFAULT_SPAN_TEXT); } if(!is_object($html)) { return false; } $css_urls = array(); // Find all stylesheets and determine their absolute URLs to retrieve them foreach($html->find('link[rel="stylesheet"]') as $style) { $css_urls[] = self::absolutify($url, $style->href); $style->outertext = ''; } $css_blocks = ''; // Find all