Laravel version update

Laravel version update
This commit is contained in:
Manish Verma
2018-08-06 18:48:58 +05:30
parent d143048413
commit 126fbb0255
13678 changed files with 1031482 additions and 778530 deletions

View File

@@ -7,16 +7,17 @@ modification, are permitted provided that the following conditions are met:
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. The name of the author may not be used to endorse or promote products
derived from this software without specific prior written permission.
3. Neither the name of the copyright holder nor the names of its contributors
may be used to endorse or promote products derived from this software
without specific prior written permission.
This software is provided by the author "as is" and any express or implied
warranties, including, but not limited to, the implied warranties of
merchantability and fitness for a particular purpose are disclaimed. In no event
shall the author be liable for any direct, indirect, incidental, special,
exemplary, or consequential damages (including, but not limited to, procurement
of substitute goods or services; loss of use, data, or profits; or business
interruption) however caused and on any theory of liability, whether in
contract, strict liability, or tort (including negligence or otherwise) arising
in any way out of the use of this software, even if advised of the possibility
of such damage.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@@ -1,31 +1,31 @@
{
"name": "tijsverkoyen/css-to-inline-styles",
"type": "library",
"name": "tijsverkoyen/css-to-inline-styles",
"type": "library",
"description": "CssToInlineStyles is a class that enables you to convert HTML-pages/files into HTML-pages/files with inline styles. This is very useful when you're sending emails.",
"homepage": "https://github.com/tijsverkoyen/CssToInlineStyles",
"license": "BSD",
"authors": [
"homepage": "https://github.com/tijsverkoyen/CssToInlineStyles",
"license": "BSD-3-Clause",
"authors": [
{
"name": "Tijs Verkoyen",
"name": "Tijs Verkoyen",
"email": "css_to_inline_styles@verkoyen.eu",
"role": "Developer"
"role": "Developer"
}
],
"require": {
"php": ">=5.3.0",
"symfony/css-selector": "~2.1|~3.0"
"require": {
"php": "^5.5 || ^7.0",
"symfony/css-selector": "^2.7 || ^3.0 || ^4.0"
},
"require-dev": {
"phpunit/phpunit": "~4.0"
"phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0"
},
"autoload": {
"autoload": {
"psr-4": {
"TijsVerkoyen\\CssToInlineStyles\\": "src"
}
},
"extra": {
"extra": {
"branch-alias": {
"dev-master": "1.5.x-dev"
"dev-master": "2.2.x-dev"
}
}
}

View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit backupGlobals="false"
backupStaticAttributes="false"
bootstrap="vendor/autoload.php"
colors="true"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
processIsolation="false"
stopOnFailure="false"
syntaxCheck="false"
>
<testsuites>
<testsuite name="CssToInlineStyles Tests">
<directory suffix=".php">./tests/</directory>
</testsuite>
</testsuites>
</phpunit>

View File

@@ -0,0 +1,68 @@
<?php
namespace TijsVerkoyen\CssToInlineStyles\Css;
use TijsVerkoyen\CssToInlineStyles\Css\Rule\Processor as RuleProcessor;
use TijsVerkoyen\CssToInlineStyles\Css\Rule\Rule;
class Processor
{
/**
* Get the rules from a given CSS-string
*
* @param string $css
* @param array $existingRules
* @return Rule[]
*/
public function getRules($css, $existingRules = array())
{
$css = $this->doCleanup($css);
$rulesProcessor = new RuleProcessor();
$rules = $rulesProcessor->splitIntoSeparateRules($css);
return $rulesProcessor->convertArrayToObjects($rules, $existingRules);
}
/**
* Get the CSS from the style-tags in the given HTML-string
*
* @param string $html
* @return string
*/
public function getCssFromStyleTags($html)
{
$css = '';
$matches = array();
$htmlNoComments = preg_replace('|<!--.*?-->|s', '', $html);
preg_match_all('|<style(?:\s.*)?>(.*)</style>|isU', $htmlNoComments, $matches);
if (!empty($matches[1])) {
foreach ($matches[1] as $match) {
$css .= trim($match) . "\n";
}
}
return $css;
}
/**
* @param string $css
* @return string
*/
private function doCleanup($css)
{
// remove charset
$css = preg_replace('/@charset "[^"]++";/', '', $css);
// remove media queries
$css = preg_replace('/@media [^{]*+{([^{}]++|{[^{}]*+})*+}/', '', $css);
$css = str_replace(array("\r", "\n"), '', $css);
$css = str_replace(array("\t"), ' ', $css);
$css = str_replace('"', '\'', $css);
$css = preg_replace('|/\*.*?\*/|', '', $css);
$css = preg_replace('/\s\s++/', ' ', $css);
$css = trim($css);
return $css;
}
}

View File

@@ -0,0 +1,122 @@
<?php
namespace TijsVerkoyen\CssToInlineStyles\Css\Property;
use Symfony\Component\CssSelector\Node\Specificity;
class Processor
{
/**
* Split a string into seperate properties
*
* @param string $propertiesString
* @return array
*/
public function splitIntoSeparateProperties($propertiesString)
{
$propertiesString = $this->cleanup($propertiesString);
$properties = (array) explode(';', $propertiesString);
$keysToRemove = array();
$numberOfProperties = count($properties);
for ($i = 0; $i < $numberOfProperties; $i++) {
$properties[$i] = trim($properties[$i]);
// if the new property begins with base64 it is part of the current property
if (isset($properties[$i + 1]) && strpos(trim($properties[$i + 1]), 'base64,') === 0) {
$properties[$i] .= ';' . trim($properties[$i + 1]);
$keysToRemove[] = $i + 1;
}
}
if (!empty($keysToRemove)) {
foreach ($keysToRemove as $key) {
unset($properties[$key]);
}
}
return array_values($properties);
}
/**
* @param $string
* @return mixed|string
*/
private function cleanup($string)
{
$string = str_replace(array("\r", "\n"), '', $string);
$string = str_replace(array("\t"), ' ', $string);
$string = str_replace('"', '\'', $string);
$string = preg_replace('|/\*.*?\*/|', '', $string);
$string = preg_replace('/\s\s+/', ' ', $string);
$string = trim($string);
$string = rtrim($string, ';');
return $string;
}
/**
* Convert a property-string into an object
*
* @param string $property
* @return Property|null
*/
public function convertToObject($property, Specificity $specificity = null)
{
if (strpos($property, ':') === false) {
return null;
}
list($name, $value) = explode(':', $property, 2);
$name = trim($name);
$value = trim($value);
if ($value === '') {
return null;
}
return new Property($name, $value, $specificity);
}
/**
* Convert an array of property-strings into objects
*
* @param array $properties
* @return Property[]
*/
public function convertArrayToObjects(array $properties, Specificity $specificity = null)
{
$objects = array();
foreach ($properties as $property) {
$object = $this->convertToObject($property, $specificity);
if ($object === null) {
continue;
}
$objects[] = $object;
}
return $objects;
}
/**
* Build the property-string for multiple properties
*
* @param array $properties
* @return string
*/
public function buildPropertiesString(array $properties)
{
$chunks = array();
foreach ($properties as $property) {
$chunks[] = $property->toString();
}
return implode(' ', $chunks);
}
}

View File

@@ -0,0 +1,90 @@
<?php
namespace TijsVerkoyen\CssToInlineStyles\Css\Property;
use Symfony\Component\CssSelector\Node\Specificity;
final class Property
{
/**
* @var string
*/
private $name;
/**
* @var string
*/
private $value;
/**
* @var Specificity
*/
private $originalSpecificity;
/**
* Property constructor.
* @param $name
* @param $value
* @param Specificity|null $specificity
*/
public function __construct($name, $value, Specificity $specificity = null)
{
$this->name = $name;
$this->value = $value;
$this->originalSpecificity = $specificity;
}
/**
* Get name
*
* @return string
*/
public function getName()
{
return $this->name;
}
/**
* Get value
*
* @return string
*/
public function getValue()
{
return $this->value;
}
/**
* Get originalSpecificity
*
* @return Specificity
*/
public function getOriginalSpecificity()
{
return $this->originalSpecificity;
}
/**
* Is this property important?
*
* @return bool
*/
public function isImportant()
{
return (stripos($this->value, '!important') !== false);
}
/**
* Get the textual representation of the property
*
* @return string
*/
public function toString()
{
return sprintf(
'%1$s: %2$s;',
$this->name,
$this->value
);
}
}

View File

@@ -0,0 +1,155 @@
<?php
namespace TijsVerkoyen\CssToInlineStyles\Css\Rule;
use Symfony\Component\CssSelector\Node\Specificity;
use \TijsVerkoyen\CssToInlineStyles\Css\Property\Processor as PropertyProcessor;
class Processor
{
/**
* Split a string into seperate rules
*
* @param string $rulesString
* @return array
*/
public function splitIntoSeparateRules($rulesString)
{
$rulesString = $this->cleanup($rulesString);
return (array) explode('}', $rulesString);
}
/**
* @param string $string
* @return string
*/
private function cleanup($string)
{
$string = str_replace(array("\r", "\n"), '', $string);
$string = str_replace(array("\t"), ' ', $string);
$string = str_replace('"', '\'', $string);
$string = preg_replace('|/\*.*?\*/|', '', $string);
$string = preg_replace('/\s\s+/', ' ', $string);
$string = trim($string);
$string = rtrim($string, '}');
return $string;
}
/**
* Convert a rule-string into an object
*
* @param string $rule
* @param int $originalOrder
* @return array
*/
public function convertToObjects($rule, $originalOrder)
{
$rule = $this->cleanup($rule);
$chunks = explode('{', $rule);
if (!isset($chunks[1])) {
return array();
}
$propertiesProcessor = new PropertyProcessor();
$rules = array();
$selectors = (array) explode(',', trim($chunks[0]));
$properties = $propertiesProcessor->splitIntoSeparateProperties($chunks[1]);
foreach ($selectors as $selector) {
$selector = trim($selector);
$specificity = $this->calculateSpecificityBasedOnASelector($selector);
$rules[] = new Rule(
$selector,
$propertiesProcessor->convertArrayToObjects($properties, $specificity),
$specificity,
$originalOrder
);
}
return $rules;
}
/**
* Calculate the specificity based on a CSS Selector string,
* Based on the patterns from premailer/css_parser by Alex Dunae
*
* @see https://github.com/premailer/css_parser/blob/master/lib/css_parser/regexps.rb
* @param string $selector
* @return Specificity
*/
public function calculateSpecificityBasedOnASelector($selector)
{
$idSelectorsPattern = " \#";
$classAttributesPseudoClassesSelectorsPattern = " (\.[\w]+) # classes
|
\[(\w+) # attributes
|
(\:( # pseudo classes
link|visited|active
|hover|focus
|lang
|target
|enabled|disabled|checked|indeterminate
|root
|nth-child|nth-last-child|nth-of-type|nth-last-of-type
|first-child|last-child|first-of-type|last-of-type
|only-child|only-of-type
|empty|contains
))";
$typePseudoElementsSelectorPattern = " ((^|[\s\+\>\~]+)[\w]+ # elements
|
\:{1,2}( # pseudo-elements
after|before
|first-letter|first-line
|selection
)
)";
return new Specificity(
preg_match_all("/{$idSelectorsPattern}/ix", $selector, $matches),
preg_match_all("/{$classAttributesPseudoClassesSelectorsPattern}/ix", $selector, $matches),
preg_match_all("/{$typePseudoElementsSelectorPattern}/ix", $selector, $matches)
);
}
/**
* @param array $rules
* @return Rule[]
*/
public function convertArrayToObjects(array $rules, array $objects = array())
{
$order = 1;
foreach ($rules as $rule) {
$objects = array_merge($objects, $this->convertToObjects($rule, $order));
$order++;
}
return $objects;
}
/**
* Sort an array on the specificity element in an ascending way
* Lower specificity will be sorted to the beginning of the array
*
* @return int
* @param Rule $e1 The first element.
* @param Rule $e2 The second element.
*/
public static function sortOnSpecificity(Rule $e1, Rule $e2)
{
$e1Specificity = $e1->getSpecificity();
$value = $e1Specificity->compareTo($e2->getSpecificity());
// if the specificity is the same, use the order in which the element appeared
if ($value === 0) {
$value = $e1->getOrder() - $e2->getOrder();
}
return $value;
}
}

View File

@@ -0,0 +1,84 @@
<?php
namespace TijsVerkoyen\CssToInlineStyles\Css\Rule;
use Symfony\Component\CssSelector\Node\Specificity;
final class Rule
{
/**
* @var string
*/
private $selector;
/**
* @var array
*/
private $properties;
/**
* @var Specificity
*/
private $specificity;
/**
* @var integer
*/
private $order;
/**
* Rule constructor.
*
* @param string $selector
* @param Property[] $properties
* @param Specificity $specificity
* @param int $order
*/
public function __construct($selector, array $properties, Specificity $specificity, $order)
{
$this->selector = $selector;
$this->properties = $properties;
$this->specificity = $specificity;
$this->order = $order;
}
/**
* Get selector
*
* @return string
*/
public function getSelector()
{
return $this->selector;
}
/**
* Get properties
*
* @return array
*/
public function getProperties()
{
return $this->properties;
}
/**
* Get specificity
*
* @return Specificity
*/
public function getSpecificity()
{
return $this->specificity;
}
/**
* Get order
*
* @return int
*/
public function getOrder()
{
return $this->order;
}
}

View File

@@ -1,677 +1,235 @@
<?php
namespace TijsVerkoyen\CssToInlineStyles;
/**
* CSS to Inline Styles class
*
* @author Tijs Verkoyen <php-css-to-inline-styles@verkoyen.eu>
* @version 1.5.5
* @copyright Copyright (c), Tijs Verkoyen. All rights reserved.
* @license Revised BSD License
*/
use Symfony\Component\CssSelector\CssSelector;
use Symfony\Component\CssSelector\CssSelectorConverter;
use Symfony\Component\CssSelector\Exception\ExceptionInterface;
use TijsVerkoyen\CssToInlineStyles\Css\Processor;
use TijsVerkoyen\CssToInlineStyles\Css\Property\Processor as PropertyProcessor;
use TijsVerkoyen\CssToInlineStyles\Css\Rule\Processor as RuleProcessor;
use TijsVerkoyen\CssToInlineStyles\Css\Rule\Rule;
class CssToInlineStyles
{
/**
* The CSS to use
*
* @var string
*/
private $css;
private $cssConverter;
/**
* Should the generated HTML be cleaned
*
* @var bool
*/
private $cleanup = false;
/**
* The encoding to use.
*
* @var string
*/
private $encoding = 'UTF-8';
/**
* The HTML to process
*
* @var string
*/
private $html;
/**
* Use inline-styles block as CSS
*
* @var bool
*/
private $useInlineStylesBlock = false;
/**
* Strip original style tags
*
* @var bool
*/
private $stripOriginalStyleTags = false;
/**
* Exclude the media queries from the inlined styles
*
* @var bool
*/
private $excludeMediaQueries = true;
/**
* Creates an instance, you could set the HTML and CSS here, or load it
* later.
*
* @return void
* @param string [optional] $html The HTML to process.
* @param string [optional] $css The CSS to use.
*/
public function __construct($html = null, $css = null)
public function __construct()
{
if ($html !== null) {
$this->setHTML($html);
if (class_exists('Symfony\Component\CssSelector\CssSelectorConverter')) {
$this->cssConverter = new CssSelectorConverter();
}
}
/**
* Will inline the $css into the given $html
*
* Remark: if the html contains <style>-tags those will be used, the rules
* in $css will be appended.
*
* @param string $html
* @param string $css
* @return string
*/
public function convert($html, $css = null)
{
$document = $this->createDomDocumentFromHtml($html);
$processor = new Processor();
// get all styles from the style-tags
$rules = $processor->getRules(
$processor->getCssFromStyleTags($html)
);
if ($css !== null) {
$this->setCSS($css);
$rules = $processor->getRules($css, $rules);
}
$document = $this->inline($document, $rules);
return $this->getHtmlFromDocument($document);
}
/**
* Remove id and class attributes.
* Inline the given properties on an given DOMElement
*
* @return string
* @param \DOMXPath $xPath The DOMXPath for the entire document.
* @param \DOMElement $element
* @param Css\Property\Property[] $properties
* @return \DOMElement
*/
private function cleanupHTML(\DOMXPath $xPath)
public function inlineCssOnElement(\DOMElement $element, array $properties)
{
$nodes = $xPath->query('//@class | //@id');
foreach ($nodes as $node) {
$node->ownerElement->removeAttributeNode($node);
}
}
/**
* Converts the loaded HTML into an HTML-string with inline styles based on the loaded CSS
*
* @return string
* @param bool [optional] $outputXHTML Should we output valid XHTML?
*/
public function convert($outputXHTML = false)
{
// redefine
$outputXHTML = (bool) $outputXHTML;
// validate
if ($this->html == null) {
throw new Exception('No HTML provided.');
if (empty($properties)) {
return $element;
}
// should we use inline style-block
if ($this->useInlineStylesBlock) {
// init var
$matches = array();
$cssProperties = array();
$inlineProperties = array();
// match the style blocks
preg_match_all('|<style(.*)>(.*)</style>|isU', $this->html, $matches);
foreach ($this->getInlineStyles($element) as $property) {
$inlineProperties[$property->getName()] = $property;
}
// any style-blocks found?
if (!empty($matches[2])) {
// add
foreach ($matches[2] as $match) {
$this->css .= trim($match) . "\n";
}
foreach ($properties as $property) {
if (!isset($inlineProperties[$property->getName()])) {
$cssProperties[$property->getName()] = $property;
}
}
// process css
$cssRules = $this->processCSS();
$rules = array();
foreach (array_merge($cssProperties, $inlineProperties) as $property) {
$rules[] = $property->toString();
}
$element->setAttribute('style', implode(' ', $rules));
// create new DOMDocument
$document = new \DOMDocument('1.0', $this->getEncoding());
return $element;
}
// set error level
/**
* Get the current inline styles for a given DOMElement
*
* @param \DOMElement $element
* @return Css\Property\Property[]
*/
public function getInlineStyles(\DOMElement $element)
{
$processor = new PropertyProcessor();
return $processor->convertArrayToObjects(
$processor->splitIntoSeparateProperties(
$element->getAttribute('style')
)
);
}
/**
* @param string $html
* @return \DOMDocument
*/
protected function createDomDocumentFromHtml($html)
{
$document = new \DOMDocument('1.0', 'UTF-8');
$internalErrors = libxml_use_internal_errors(true);
// load HTML
$document->loadHTML($this->html);
// Restore error level
$document->loadHTML(mb_convert_encoding($html, 'HTML-ENTITIES', 'UTF-8'));
libxml_use_internal_errors($internalErrors);
$document->formatOutput = true;
return $document;
}
/**
* @param \DOMDocument $document
* @return string
*/
protected function getHtmlFromDocument(\DOMDocument $document)
{
// retrieve the document element
// we do it this way to preserve the utf-8 encoding
$htmlElement = $document->documentElement;
$html = $document->saveHTML($htmlElement);
$html = trim($html);
// retrieve the doctype
$document->removeChild($htmlElement);
$doctype = $document->saveHTML();
$doctype = trim($doctype);
// if it is the html5 doctype convert it to lowercase
if ($doctype === '<!DOCTYPE html>') {
$doctype = strtolower($doctype);
}
return $doctype."\n".$html;
}
/**
* @param \DOMDocument $document
* @param Css\Rule\Rule[] $rules
* @return \DOMDocument
*/
protected function inline(\DOMDocument $document, array $rules)
{
if (empty($rules)) {
return $document;
}
$propertyStorage = new \SplObjectStorage();
// create new XPath
$xPath = new \DOMXPath($document);
// any rules?
if (!empty($cssRules)) {
// loop rules
foreach ($cssRules as $rule) {
usort($rules, array(RuleProcessor::class, 'sortOnSpecificity'));
$selector = new Selector($rule['selector']);
$query = $selector->toXPath();
if (is_null($query)) {
continue;
}
// search elements
$elements = $xPath->query($query);
// validate elements
if ($elements === false) {
continue;
}
// loop found elements
foreach ($elements as $element) {
// no styles stored?
if ($element->attributes->getNamedItem(
'data-css-to-inline-styles-original-styles'
) == null
) {
// init var
$originalStyle = '';
if ($element->attributes->getNamedItem('style') !== null) {
$originalStyle = $element->attributes->getNamedItem('style')->value;
}
// store original styles
$element->setAttribute(
'data-css-to-inline-styles-original-styles',
$originalStyle
);
// clear the styles
$element->setAttribute('style', '');
}
// init var
$properties = array();
// get current styles
$stylesAttribute = $element->attributes->getNamedItem('style');
// any styles defined before?
if ($stylesAttribute !== null) {
// get value for the styles attribute
$definedStyles = (string) $stylesAttribute->value;
// split into properties
$definedProperties = $this->splitIntoProperties($definedStyles);
// loop properties
foreach ($definedProperties as $property) {
// validate property
if ($property == '') {
continue;
}
// split into chunks
$chunks = (array) explode(':', trim($property), 2);
// validate
if (!isset($chunks[1])) {
continue;
}
// loop chunks
$properties[$chunks[0]] = trim($chunks[1]);
}
}
// add new properties into the list
foreach ($rule['properties'] as $key => $value) {
// If one of the rules is already set and is !important, don't apply it,
// except if the new rule is also important.
if (
!isset($properties[$key])
|| stristr($properties[$key], '!important') === false
|| (stristr(implode('', $value), '!important') !== false)
) {
$properties[$key] = $value;
}
}
// build string
$propertyChunks = array();
// build chunks
foreach ($properties as $key => $values) {
foreach ((array) $values as $value) {
$propertyChunks[] = $key . ': ' . $value . ';';
}
}
// build properties string
$propertiesString = implode(' ', $propertyChunks);
// set attribute
if ($propertiesString != '') {
$element->setAttribute('style', $propertiesString);
}
}
}
// reapply original styles
// search elements
$elements = $xPath->query('//*[@data-css-to-inline-styles-original-styles]');
// loop found elements
foreach ($elements as $element) {
// get the original styles
$originalStyle = $element->attributes->getNamedItem(
'data-css-to-inline-styles-original-styles'
)->value;
if ($originalStyle != '') {
$originalProperties = array();
$originalStyles = $this->splitIntoProperties($originalStyle);
foreach ($originalStyles as $property) {
// validate property
if ($property == '') {
continue;
}
// split into chunks
$chunks = (array) explode(':', trim($property), 2);
// validate
if (!isset($chunks[1])) {
continue;
}
// loop chunks
$originalProperties[$chunks[0]] = trim($chunks[1]);
}
// get current styles
$stylesAttribute = $element->attributes->getNamedItem('style');
$properties = array();
// any styles defined before?
if ($stylesAttribute !== null) {
// get value for the styles attribute
$definedStyles = (string) $stylesAttribute->value;
// split into properties
$definedProperties = $this->splitIntoProperties($definedStyles);
// loop properties
foreach ($definedProperties as $property) {
// validate property
if ($property == '') {
continue;
}
// split into chunks
$chunks = (array) explode(':', trim($property), 2);
// validate
if (!isset($chunks[1])) {
continue;
}
// loop chunks
$properties[$chunks[0]] = trim($chunks[1]);
}
}
// add new properties into the list
foreach ($originalProperties as $key => $value) {
$properties[$key] = $value;
}
// build string
$propertyChunks = array();
// build chunks
foreach ($properties as $key => $values) {
foreach ((array) $values as $value) {
$propertyChunks[] = $key . ': ' . $value . ';';
}
}
// build properties string
$propertiesString = implode(' ', $propertyChunks);
// set attribute
if ($propertiesString != '') {
$element->setAttribute(
'style',
$propertiesString
);
}
}
// remove placeholder
$element->removeAttribute(
'data-css-to-inline-styles-original-styles'
);
}
}
// strip original style tags if we need to
if ($this->stripOriginalStyleTags) {
$this->stripOriginalStyleTags($xPath);
}
// cleanup the HTML if we need to
if ($this->cleanup) {
$this->cleanupHTML($xPath);
}
// should we output XHTML?
if ($outputXHTML) {
// set formating
$document->formatOutput = true;
// get the HTML as XML
$html = $document->saveXML(null, LIBXML_NOEMPTYTAG);
// remove the XML-header
$html = ltrim(preg_replace('/<\?xml (.*)\?>/', '', $html));
} // just regular HTML 4.01 as it should be used in newsletters
else {
// get the HTML
$html = $document->saveHTML();
}
// return
return $html;
}
/**
* Split a style string into an array of properties.
* The returned array can contain empty strings.
*
* @param string $styles ex: 'color:blue;font-size:12px;'
* @return array an array of strings containing css property ex: array('color:blue','font-size:12px')
*/
private function splitIntoProperties($styles) {
$properties = (array) explode(';', $styles);
for ($i = 0; $i < count($properties); $i++) {
// If next property begins with base64,
// Then the ';' was part of this property (and we should not have split on it).
if (isset($properties[$i + 1]) && strpos($properties[$i + 1], 'base64,') === 0) {
$properties[$i] .= ';' . $properties[$i + 1];
$properties[$i + 1] = '';
$i += 1;
}
}
return $properties;
}
/**
* Get the encoding to use
*
* @return string
*/
private function getEncoding()
{
return $this->encoding;
}
/**
* Process the loaded CSS
*
* @return array
*/
private function processCSS()
{
// init vars
$css = (string) $this->css;
$cssRules = array();
// remove newlines
$css = str_replace(array("\r", "\n"), '', $css);
// replace double quotes by single quotes
$css = str_replace('"', '\'', $css);
// remove comments
$css = preg_replace('|/\*.*?\*/|', '', $css);
// remove spaces
$css = preg_replace('/\s\s+/', ' ', $css);
if ($this->excludeMediaQueries) {
$css = preg_replace('/@media [^{]*{([^{}]|{[^{}]*})*}/', '', $css);
}
// rules are splitted by }
$rules = (array) explode('}', $css);
// init var
$i = 1;
// loop rules
foreach ($rules as $rule) {
// split into chunks
$chunks = explode('{', $rule);
// invalid rule?
if (!isset($chunks[1])) {
try {
if (null !== $this->cssConverter) {
$expression = $this->cssConverter->toXPath($rule->getSelector());
} else {
// Compatibility layer for Symfony 2.7 and older
$expression = CssSelector::toXPath($rule->getSelector());
}
} catch (ExceptionInterface $e) {
continue;
}
// set the selectors
$selectors = trim($chunks[0]);
$elements = $xPath->query($expression);
// get cssProperties
$cssProperties = trim($chunks[1]);
if ($elements === false) {
continue;
}
// split multiple selectors
$selectors = (array) explode(',', $selectors);
// loop selectors
foreach ($selectors as $selector) {
// cleanup
$selector = trim($selector);
// build an array for each selector
$ruleSet = array();
// store selector
$ruleSet['selector'] = $selector;
// process the properties
$ruleSet['properties'] = $this->processCSSProperties(
$cssProperties
foreach ($elements as $element) {
$propertyStorage[$element] = $this->calculatePropertiesToBeApplied(
$rule->getProperties(),
$propertyStorage->contains($element) ? $propertyStorage[$element] : array()
);
// calculate specificity
$ruleSet['specificity'] = Specificity::fromSelector($selector);
// remember the order in which the rules appear
$ruleSet['order'] = $i;
// add into global rules
$cssRules[] = $ruleSet;
}
// increment
$i++;
}
// sort based on specificity
if (!empty($cssRules)) {
usort($cssRules, array(__CLASS__, 'sortOnSpecificity'));
foreach ($propertyStorage as $element) {
$this->inlineCssOnElement($element, $propertyStorage[$element]);
}
return $cssRules;
return $document;
}
/**
* Process the CSS-properties
* Merge the CSS rules to determine the applied properties.
*
* @return array
* @param string $propertyString The CSS-properties.
* @param Css\Property\Property[] $properties
* @param Css\Property\Property[] $cssProperties existing applied properties indexed by name
*
* @return Css\Property\Property[] updated properties, indexed by name
*/
private function processCSSProperties($propertyString)
private function calculatePropertiesToBeApplied(array $properties, array $cssProperties)
{
// split into chunks
$properties = $this->splitIntoProperties($propertyString);
if (empty($properties)) {
return $cssProperties;
}
// init var
$pairs = array();
// loop properties
foreach ($properties as $property) {
// split into chunks
$chunks = (array) explode(':', $property, 2);
if (isset($cssProperties[$property->getName()])) {
$existingProperty = $cssProperties[$property->getName()];
// validate
if (!isset($chunks[1])) {
continue;
}
//skip check to overrule if existing property is important and current is not
if ($existingProperty->isImportant() && !$property->isImportant()) {
continue;
}
// cleanup
$chunks[0] = trim($chunks[0]);
$chunks[1] = trim($chunks[1]);
//overrule if current property is important and existing is not, else check specificity
$overrule = !$existingProperty->isImportant() && $property->isImportant();
if (!$overrule) {
$overrule = $existingProperty->getOriginalSpecificity()->compareTo($property->getOriginalSpecificity()) <= 0;
}
// add to pairs array
if (!isset($pairs[$chunks[0]]) ||
!in_array($chunks[1], $pairs[$chunks[0]])
) {
$pairs[$chunks[0]][] = $chunks[1];
}
}
// sort the pairs
ksort($pairs);
// return
return $pairs;
}
/**
* Should the IDs and classes be removed?
*
* @return void
* @param bool [optional] $on Should we enable cleanup?
*/
public function setCleanup($on = true)
{
$this->cleanup = (bool) $on;
}
/**
* Set CSS to use
*
* @return void
* @param string $css The CSS to use.
*/
public function setCSS($css)
{
$this->css = (string) $css;
}
/**
* Set the encoding to use with the DOMDocument
*
* @return void
* @param string $encoding The encoding to use.
*
* @deprecated Doesn't have any effect
*/
public function setEncoding($encoding)
{
$this->encoding = (string) $encoding;
}
/**
* Set HTML to process
*
* @return void
* @param string $html The HTML to process.
*/
public function setHTML($html)
{
$this->html = (string) $html;
}
/**
* Set use of inline styles block
* If this is enabled the class will use the style-block in the HTML.
*
* @return void
* @param bool [optional] $on Should we process inline styles?
*/
public function setUseInlineStylesBlock($on = true)
{
$this->useInlineStylesBlock = (bool) $on;
}
/**
* Set strip original style tags
* If this is enabled the class will remove all style tags in the HTML.
*
* @return void
* @param bool [optional] $on Should we process inline styles?
*/
public function setStripOriginalStyleTags($on = true)
{
$this->stripOriginalStyleTags = (bool) $on;
}
/**
* Set exclude media queries
*
* If this is enabled the media queries will be removed before inlining the rules
*
* @return void
* @param bool [optional] $on
*/
public function setExcludeMediaQueries($on = true)
{
$this->excludeMediaQueries = (bool) $on;
}
/**
* Strip style tags into the generated HTML
*
* @return string
* @param \DOMXPath $xPath The DOMXPath for the entire document.
*/
private function stripOriginalStyleTags(\DOMXPath $xPath)
{
// Get all style tags
$nodes = $xPath->query('descendant-or-self::style');
foreach ($nodes as $node) {
if ($this->excludeMediaQueries) {
//Search for Media Queries
preg_match_all('/@media [^{]*{([^{}]|{[^{}]*})*}/', $node->nodeValue, $mqs);
// Replace the nodeValue with just the Media Queries
$node->nodeValue = implode("\n", $mqs[0]);
if ($overrule) {
unset($cssProperties[$property->getName()]);
$cssProperties[$property->getName()] = $property;
}
} else {
// Remove the entire style tag
$node->parentNode->removeChild($node);
$cssProperties[$property->getName()] = $property;
}
}
}
/**
* Sort an array on the specificity element
*
* @return int
* @param array $e1 The first element.
* @param array $e2 The second element.
*/
private static function sortOnSpecificity($e1, $e2)
{
// Compare the specificity
$value = $e1['specificity']->compareTo($e2['specificity']);
// if the specificity is the same, use the order in which the element appeared
if ($value === 0) {
$value = $e1['order'] - $e2['order'];
}
return $value;
return $cssProperties;
}
}

View File

@@ -1,11 +0,0 @@
<?php
namespace TijsVerkoyen\CssToInlineStyles;
/**
* CssToInlineStyles Exception class
*
* @author Tijs Verkoyen <php-css-to-inline-styles@verkoyen.eu>
*/
class Exception extends \Exception
{
}

View File

@@ -1,44 +0,0 @@
<?php
namespace TijsVerkoyen\CssToInlineStyles;
use Symfony\Component\CssSelector\CssSelector;
use Symfony\Component\CssSelector\CssSelectorConverter;
use Symfony\Component\CssSelector\Exception\ExceptionInterface;
/**
* CSS to Inline Styles Selector class.
*
*/
class Selector
{
/**
* The CSS selector
*
* @var string
*/
protected $selector;
/**
* @param string $selector The CSS selector
*/
public function __construct($selector)
{
$this->selector = $selector;
}
public function toXPath()
{
try {
if (class_exists('Symfony\Component\CssSelector\CssSelectorConverter')) {
$converter = new CssSelectorConverter();
$query = $converter->toXPath($this->selector);
} else {
$query = CssSelector::toXPath($this->selector);
}
} catch (ExceptionInterface $e) {
$query = null;
}
return $query;
}
}

View File

@@ -1,133 +0,0 @@
<?php
namespace TijsVerkoyen\CssToInlineStyles;
/**
* CSS to Inline Styles Specificity class.
*
* Compare specificity based on the CSS3 spec.
*
* @see http://www.w3.org/TR/selectors/#specificity
*
*/
class Specificity
{
/**
* The number of ID selectors in the selector
*
* @var int
*/
private $a;
/**
*
* The number of class selectors, attributes selectors, and pseudo-classes in the selector
*
* @var int
*/
private $b;
/**
* The number of type selectors and pseudo-elements in the selector
*
* @var int
*/
private $c;
/**
* @param int $a The number of ID selectors in the selector
* @param int $b The number of class selectors, attributes selectors, and pseudo-classes in the selector
* @param int $c The number of type selectors and pseudo-elements in the selector
*/
public function __construct($a = 0, $b = 0, $c = 0)
{
$this->a = $a;
$this->b = $b;
$this->c = $c;
}
/**
* Increase the current specificity by adding the three values
*
* @param int $a The number of ID selectors in the selector
* @param int $b The number of class selectors, attributes selectors, and pseudo-classes in the selector
* @param int $c The number of type selectors and pseudo-elements in the selector
*/
public function increase($a, $b, $c)
{
$this->a += $a;
$this->b += $b;
$this->c += $c;
}
/**
* Get the specificity values as an array
*
* @return array
*/
public function getValues()
{
return array($this->a, $this->b, $this->c);
}
/**
* Calculate the specificity based on a CSS Selector string,
* Based on the patterns from premailer/css_parser by Alex Dunae
*
* @see https://github.com/premailer/css_parser/blob/master/lib/css_parser/regexps.rb
* @param string $selector
* @return static
*/
public static function fromSelector($selector)
{
$pattern_a = " \#";
$pattern_b = " (\.[\w]+) # classes
|
\[(\w+) # attributes
|
(\:( # pseudo classes
link|visited|active
|hover|focus
|lang
|target
|enabled|disabled|checked|indeterminate
|root
|nth-child|nth-last-child|nth-of-type|nth-last-of-type
|first-child|last-child|first-of-type|last-of-type
|only-child|only-of-type
|empty|contains
))";
$pattern_c = " ((^|[\s\+\>\~]+)[\w]+ # elements
|
\:{1,2}( # pseudo-elements
after|before
|first-letter|first-line
|selection
)
)";
return new static(
preg_match_all("/{$pattern_a}/ix", $selector, $matches),
preg_match_all("/{$pattern_b}/ix", $selector, $matches),
preg_match_all("/{$pattern_c}/ix", $selector, $matches)
);
}
/**
* Returns <0 when $specificity is greater, 0 when equal, >0 when smaller
*
* @param Specificity $specificity
* @return int
*/
public function compareTo(Specificity $specificity)
{
if ($this->a !== $specificity->a) {
return $this->a - $specificity->a;
} elseif ($this->b !== $specificity->b) {
return $this->b - $specificity->b;
} else {
return $this->c - $specificity->c;
}
}
}