From d396bc7bbe7affedd7416dc7ea67c9be83721a38 Mon Sep 17 00:00:00 2001 From: Prasad <prasad@vtiger.com> Date: Thu, 29 Sep 2022 20:14:09 +0530 Subject: [PATCH] Updated InStyle library --- libraries/InStyle/InStyle.php | 384 +++++++++++++++++++++++++++------- 1 file changed, 305 insertions(+), 79 deletions(-) diff --git a/libraries/InStyle/InStyle.php b/libraries/InStyle/InStyle.php index a56e04e24..bf78d34fd 100644 --- a/libraries/InStyle/InStyle.php +++ b/libraries/InStyle/InStyle.php @@ -7,10 +7,9 @@ * * @author David Lim * @email miliak@orst.edu - * @link http://www.davidandjennilyn.com + * @link http://www.davidandjennilyn.com * @acknowledgements Simple HTML Dom - */ - + */ class InStyle { function convert($document) { @@ -20,102 +19,329 @@ // Strip out extra newlines and tabs from CSS $css = preg_replace("/[\n\r\t]+/s", "", $matches['css']); - - // Returns the css after removing media queries - $refactoredCss = $this->findAndRemoveMediaQueries($css); - - // Extract each CSS declaration - preg_match_all('/([a-zA-Z0-9_ ,#\.]+){([^}]+)}/s', $refactoredCss, $rules, PREG_SET_ORDER); - // For each CSS declaration, explode the selector and declaration into an array - // Array index 1 is the CSS selector - // Array index 2 is the CSS rule(s) - foreach ($rules as $rule) { + + // Returns the css after removing media queries + $refactoredCss = $this->findAndRemoveMediaQueries($css); + + // Extract each CSS declaration + preg_match_all('/([a-zA-Z0-9_ ,#\.]+){([^}]+)}/s', $refactoredCss, $rules, PREG_SET_ORDER); + // For each CSS declaration, explode the selector and declaration into an array + // Array index 1 is the CSS selector + // Array index 2 is the CSS rule(s) + foreach ($rules as $rule) { $styles[trim($rule['1'])] = $styles[trim($rule['1'])].trim($rule['2']); - } + } - // DEBUG: Show selector and declaration - if ($debug) { - echo '<pre>'; + // DEBUG: Show selector and declaration + if ($debug) { + echo '<pre>'; foreach ($styles as $selector=>$styling) { - echo $selector . ':<br>'; - echo $styling . '<br/><br/>'; - } - echo '</pre><hr/>'; + echo $selector . ':<br>'; + echo $styling . '<br/><br/>'; } - $html_dom = new simple_html_dom(); - // Load in the HTML without the head and style definitions - $html_dom->load($document); // Retaining styles without removing from head tag - - // For each style declaration, find the selector in the HTML and add the inline CSS - if (!empty($styles)) { + echo '</pre><hr/>'; + } + $html_dom = new simple_html_dom(); + // Load in the HTML without the head and style definitions + $html_dom->load($document); // Retaining styles without removing from head tag + // For each style declaration, find the selector in the HTML and add the inline CSS + if (!empty($styles)) { foreach ($styles as $selector=>$styling) { - foreach ($html_dom->find($selector) as $element) { - $elementStyle = $element->style; + foreach ($html_dom->find($selector) as $element) { + $elementStyle = $element->style; if(substr($elementStyle, -1) == ';'){ - $element->style .= $styling; - } else { + $element->style .= $styling; + } else { $element->style .= ";".$styling; - } } + } } - $inline_css_message = $html_dom->save(); - return $inline_css_message; - } - return false; + $inline_css_message = $html_dom->save(); + return $inline_css_message; } + return false; + } + + function emailsInlineConversion($document) { - /** - * Function to find and remove media queries and return css without media queries - * @param type $css - * @return type - */ - function findAndRemoveMediaQueries($css){ - $mediaBlocks = array(); - - $start = 0; - 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, "{"); + //TODO: After updating Database tables to utf8mb remove below code. + //http://redmine.vtiger.in/issues/57411 + // Commented removing emoji function as we are supporting emoji characters + // $document = Vtiger_Functions::removeEmoji($document); + + //Check whether html or not + if(strpos($document, '>') && $document != strip_tags($document)) { + //If is there any Unclosed and Extra tags is there in content, then It is formatting in correct way. + $document = str_replace(' ', '@nbsp;', $document); + $document = str_replace("\xc2\xa0",' ', $document); + + //PT69706:Here in the below code we are removing the comments contents but in outlook we are using condition in such syntex, so commenting the replace function. + // ref:-https://templates.mailchimp.com/development/css/outlook-conditional-css/ + + // Removing comment tags from content. + //$document = preg_replace('/<!--(.*)-->/Uis', '', $document); + + $dom = new DOMDocument(); + @$dom->loadHTML('<?xml encoding="utf-8" ?>' . $document); + $nodeHead = $dom->getElementsByTagName("head"); + $metaList = $dom->getElementsByTagName("meta"); + $convertBig5=false; + if ($nodeHead->length > 0) { + $completeAttrString = array(); + //storing content of meta tags + for ($i = $metaList->length - 1; $i >= 0; $i--) { + $nodeMeta = $dom->getElementsByTagName('meta')->item($i); + if ($nodeMeta->hasAttributes()) { + $attrNameVal = array(); + foreach ($nodeMeta->attributes as $attr) { + $name = $attr->nodeName; + $value = $attr->nodeValue; + //https://stackoverflow.com/questions/8169278/firefox-and-utf-16-encoding + if(stripos($value, 'utf-16') !== false){ + $value = str_ireplace('utf-16', 'utf-8',$value); + } elseif(stripos($value, 'big5') !== false){ + $convertBig5 = true; + $value = str_ireplace('big5', 'utf-8',$value); + } + $attrNameVal[] = array('name' => $name, 'value' => $value); + } + $completeAttrString[] = $attrNameVal; + } + } + //removing meta tags + while ($metaList->length > 0) { + $meta = $metaList->item(0); + $meta->parentNode->removeChild($meta); + } + //creating new meta tags with previous content and appending to head tag + foreach ($completeAttrString as $attrNameVals) { + $element = $dom->createElement('meta'); + foreach ($attrNameVals as $attrNameVal) { + try { + $element->setAttribute($attrNameVal['name'], $attrNameVal['value']); + } catch (Exception $ex) { + + } + } + $nodeHead = $dom->getElementsByTagName('head')->item(0); + $nodeHead->appendChild($element); + } + } else { + while ($metaList->length > 0) { + $meta = $metaList->item(0); + $meta->parentNode->removeChild($meta); + } + } + //#5066121 - Added check for supporting big5 charset + if(!$convertBig5){ + @$dom->loadHTML('<meta http-equiv="content-type" content="text/html; charset=utf-8">' .$document); + $document = rawurldecode($dom->saveHTML()); + } + //https://www.w3.org/International/questions/qa-html-encoding-declarations.en + @$dom->loadHTML('<meta http-equiv="content-type" content="text/html; charset=utf-8">' .$document); + //http://stackoverflow.com/questions/21350192/php-domdocument-loadhtml-turns-into-url-encoding24-unexpectedly + $document = rawurldecode($dom->saveHTML()); + + $document = str_replace('@nbsp;', ' ', $document); + //Removing script tags from content,using decode_html to convert the encoded script tags + $formattedContent = preg_replace('#<script(.*?)>(.*?)</script>#is', '', decode_html(decode_html($document))); + + // Extract the CSS + preg_match('/<style?[^>]+>(?<css>[^<]+)<\/style>/s', $document, $matches); + + if($matches) { + // Strip out extra newlines and tabs from CSS + $css = preg_replace("/[\n\r\t]+/s", "", $matches['css']); + + // Returns the css after removing media queries + $refactoredCss = $this->findAndRemoveMediaQueries($css); + + // Extract each CSS declaration + preg_match_all('/([a-zA-Z0-9_ ,#-:\.]+){([^}]+)}/s', $refactoredCss, $rules, PREG_SET_ORDER); + // For each CSS declaration, explode the selector and declaration into an array + // Array index 1 is the CSS selector + // Array index 2 is the CSS rule(s) + foreach ($rules as $rule) { + $styles[trim($rule['1'])] = $styles[trim($rule['1'])].trim($rule['2']); + } + + // DEBUG: Show selector and declaration + if ($debug) { + echo '<pre>'; + foreach ($styles as $selector=>$styling) { + echo $selector . ':<br>'; + echo $styling . '<br/><br/>'; + } + echo '</pre><hr/>'; + } + $html_dom = new simple_html_dom(); + // Load in the HTML without the head and style definitions + //PT80128:: bydefault we were strip out the \r \n thats updating the actual view. + $html_dom->load($document,true,false); // Retaining styles without removing from head tag + + $bodyStyleTag = ''; + // For each style declaration, find the selector in the HTML and add the inline CSS + if (!empty($styles)) { + foreach ($styles as $selector=>$styling) { + // this fix is workaround for #58277 + $selectProperties = explode(' ',$selector); + if(sizeof($selectProperties) != 0){ + foreach($selectProperties as $key => $property){ + if(strpos($property, ".") === false){ + continue; + } + $results = explode('.',$property); + //Id from selector + $selectorId = preg_grep('/\#(\w+)/i', $results); + $array_key = array_search($selectorId[0], $results); + if ($array_key !== false || !is_bool($array_key)) { + unset($results[$array_key]); + } + if(sizeof($results) > 1){ //if selector has more than one class of same element + //returns empty if selector has clases like .class1.class2 .. bug reported - https://sourceforge.net/p/simplehtmldom/bugs/3/ + $results = '[class='.trim(implode(' ', $results)).']'; + $selectProperties[$key] = $results; + } + if($selectorId[0]){ + $selectProperties[$key] = $selectorId[0].$selectProperties[$key]; + } + } + $selector = implode(' ',$selectProperties); } - elseif ($css[$i] == "}") - { - array_pop($s); + foreach ($html_dom->find($selector) as $element) { + $elementStyle = $element->style; + if(substr($elementStyle, -1) == ';'){ + $element->style .= $styling; + } else { + $element->style .= ";".$styling; + } + + if($selector == 'body'){ + $bodyStyleTag = $element->style; + } } + } + $inline_css_message = $html_dom->save(); - $i++; + //Removing style tags from content. + $formattedContent = preg_replace('#<style(.*?)>(.*?)</style>#is', '', $inline_css_message); + } + } + + $bodyTagAttributes = ''; + + $html_dom = new simple_html_dom(); + // Load in the HTML without the head and style definitions + $html_dom->load($document); + //Find only body tag + $bodyTagElement = $html_dom->getElementByTagName('body'); + //get its attributes + $bodyAttributes = $bodyTagElement->attr; + if($bodyAttributes && is_array($bodyAttributes)){ + foreach($bodyAttributes as $key => $value){ + if($key !== 'style'){ + $bodyTagAttributes .= "$key=".'"'.$value.'" '; } + } + } + + + //If there are content after closing body tag that is getting removed, so putting closing body tag at the end. + $formattedContent = str_replace('</body>', '', $formattedContent).'</body>'; + //Is there any inline styling for body tag, placing body in one <div> and applying styling to that <div>. + //Due to this, body styling is not effect the other eamils. + preg_match("/<body[^>]*>(.*?)<\/body>/is", $formattedContent, $matches); + $formattedContent = str_replace('<html>', '', $matches[1]); + $formattedContent = str_replace('</html>', '', $formattedContent); + //https://github.com/mike42/escpos-php/issues/37 To support Chinese characters with big5 or GBK charset + if($convertBig5){ + $formattedContent = iconv("UTF-8","GBK//IGNORE",$formattedContent); + } + $formattedContent = '<div style="' . $bodyStyleTag . '" '.$bodyTagAttributes.' >' . $formattedContent . '</div>'; + return $formattedContent; + } + return $document; + } + + function convertStylesToInlineCss($document) { + //converts styles to inline css + $inline_css_message = $this->emailsInlineConversion($document); + //Removing script tags from content. + $inline_css_message = preg_replace('#<script(.*?)>(.*?)</script>#is', '', $inline_css_message); + return $inline_css_message; + } + + /** + * Function to find and remove media queries and return css without media queries + * @param type $css + * @return type + */ + function findAndRemoveMediaQueries($css){ + $mediaBlocks = array(); + + $start = 0; + $cssLength = strlen($css); + while (($start = strpos($css, "@media", $start)) !== false) { +// stack to manage brackets + $s = array(); - // cut the media block out of the css and store - $mediaBlocks[] = substr($css, $start, ($i) - $start); +// get the first opening bracket + $i = strpos($css, "{", $start); - // set the new $start to the end of the block - $start = $i; +// 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, "{"); + $i++; + } elseif ($css[$i] == "}") { + array_pop($s); + $i++; + } else if ($i >= $cssLength) { +//If position of i reaches end of css and still didn't find +//closing brace, then remove the existing value from array $s + array_pop($s); + } else { + $i++; + } } + +// cut the media block out of the css and store + $mediaBlocks[] = substr($css, $start, ($i) - $start); + +// set the new $start to the end of the block + $start = $i; + } else { + // If no starting postion { found, then this loop need to be break. + break; } - foreach($mediaBlocks as $value){ - $css = str_replace($value,'',$css); + } + foreach ($mediaBlocks as $value) { + $css = str_replace($value, '', $css); + } + return $css; + } + //Function to replace body default style to only new content + function defaultFontStyleModification($emailContent){ + $formattedBody = $emailContent; + $html_dom = new simple_html_dom(); + $html_dom->load($emailContent); + if($html_dom->find('#mailExtraContent')){ + foreach($html_dom->find('#mailExtraContent') as $element){ + $extraContent = $element->innertext; + $element->outertext = ''; } - return $css; + $outerDiv = $html_dom->find('div',0); + $newEmailContent = $outerDiv->outertext; + $formattedBody = $newEmailContent.$extraContent; } + return $formattedBody; } +} -/* End of file inline_css.php */ \ No newline at end of file +/* End of file inline_css.php */ -- GitLab