sonarrradarrplexorganizrnginxdashboardmuximuxlandingpagestartpagelandinghtpcserverhomepagesabnzbdheimdallembycouchpotatonzbgetbookmarkapplication-dashboard
		
		
		
		
			You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							235 lines
						
					
					
						
							7.2 KiB
						
					
					
				
			
		
		
		
			
			
			
				
					
				
				
					
				
			
		
		
	
	
							235 lines
						
					
					
						
							7.2 KiB
						
					
					
				| <?php | |
| 
 | |
| namespace TijsVerkoyen\CssToInlineStyles; | |
| 
 | |
| 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 | |
| { | |
|     private $cssConverter; | |
| 
 | |
|     public function __construct() | |
|     { | |
|         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) { | |
|             $rules = $processor->getRules($css, $rules); | |
|         } | |
| 
 | |
|         $document = $this->inline($document, $rules); | |
| 
 | |
|         return $this->getHtmlFromDocument($document); | |
|     } | |
| 
 | |
|     /** | |
|      * Inline the given properties on an given DOMElement | |
|      * | |
|      * @param \DOMElement             $element | |
|      * @param Css\Property\Property[] $properties | |
|      * @return \DOMElement | |
|      */ | |
|     public function inlineCssOnElement(\DOMElement $element, array $properties) | |
|     { | |
|         if (empty($properties)) { | |
|             return $element; | |
|         } | |
| 
 | |
|         $cssProperties = array(); | |
|         $inlineProperties = array(); | |
| 
 | |
|         foreach ($this->getInlineStyles($element) as $property) { | |
|             $inlineProperties[$property->getName()] = $property; | |
|         } | |
| 
 | |
|         foreach ($properties as $property) { | |
|             if (!isset($inlineProperties[$property->getName()])) { | |
|                 $cssProperties[$property->getName()] = $property; | |
|             } | |
|         } | |
| 
 | |
|         $rules = array(); | |
|         foreach (array_merge($cssProperties, $inlineProperties) as $property) { | |
|             $rules[] = $property->toString(); | |
|         } | |
|         $element->setAttribute('style', implode(' ', $rules)); | |
| 
 | |
|         return $element; | |
|     } | |
| 
 | |
|     /** | |
|      * 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); | |
|         $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(); | |
| 
 | |
|         $xPath = new \DOMXPath($document); | |
| 
 | |
|         usort($rules, array(RuleProcessor::class, 'sortOnSpecificity')); | |
| 
 | |
|         foreach ($rules as $rule) { | |
|             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; | |
|             } | |
| 
 | |
|             $elements = $xPath->query($expression); | |
| 
 | |
|             if ($elements === false) { | |
|                 continue; | |
|             } | |
| 
 | |
|             foreach ($elements as $element) { | |
|                 $propertyStorage[$element] = $this->calculatePropertiesToBeApplied( | |
|                     $rule->getProperties(), | |
|                     $propertyStorage->contains($element) ? $propertyStorage[$element] : array() | |
|                 ); | |
|             } | |
|         } | |
| 
 | |
|         foreach ($propertyStorage as $element) { | |
|             $this->inlineCssOnElement($element, $propertyStorage[$element]); | |
|         } | |
| 
 | |
|         return $document; | |
|     } | |
| 
 | |
|     /** | |
|      * Merge the CSS rules to determine the applied 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 calculatePropertiesToBeApplied(array $properties, array $cssProperties) | |
|     { | |
|         if (empty($properties)) { | |
|             return $cssProperties; | |
|         } | |
| 
 | |
|         foreach ($properties as $property) { | |
|             if (isset($cssProperties[$property->getName()])) { | |
|                 $existingProperty = $cssProperties[$property->getName()]; | |
| 
 | |
|                 //skip check to overrule if existing property is important and current is not | |
|                 if ($existingProperty->isImportant() && !$property->isImportant()) { | |
|                     continue; | |
|                 } | |
| 
 | |
|                 //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; | |
|                 } | |
| 
 | |
|                 if ($overrule) { | |
|                     unset($cssProperties[$property->getName()]); | |
|                     $cssProperties[$property->getName()] = $property; | |
|                 } | |
|             } else { | |
|                 $cssProperties[$property->getName()] = $property; | |
|             } | |
|         } | |
| 
 | |
|         return $cssProperties; | |
|     } | |
| }
 | |
| 
 |