Account Suspended
Account Suspended
This Account has been suspended.
Contact your hosting provider for more information.
 All Data Structures Functions Variables Pages
ZurmoCssInUtil.php
1 <?php
2  /*********************************************************************************
3  * Zurmo is a customer relationship management program developed by
4  * Zurmo, Inc. Copyright (C) 2017 Zurmo Inc.
5  *
6  * Zurmo is free software; you can redistribute it and/or modify it under
7  * the terms of the GNU Affero General Public License version 3 as published by the
8  * Free Software Foundation with the addition of the following permission added
9  * to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED WORK
10  * IN WHICH THE COPYRIGHT IS OWNED BY ZURMO, ZURMO DISCLAIMS THE WARRANTY
11  * OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
12  *
13  * Zurmo is distributed in the hope that it will be useful, but WITHOUT
14  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
15  * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
16  * details.
17  *
18  * You should have received a copy of the GNU Affero General Public License along with
19  * this program; if not, see http://www.gnu.org/licenses or write to the Free
20  * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
21  * 02110-1301 USA.
22  *
23  * You can contact Zurmo, Inc. with a mailing address at 27 North Wacker Drive
24  * Suite 370 Chicago, IL 60606. or at email address contact@zurmo.com.
25  *
26  * The interactive user interfaces in original and modified versions
27  * of this program must display Appropriate Legal Notices, as required under
28  * Section 5 of the GNU Affero General Public License version 3.
29  *
30  * In accordance with Section 7(b) of the GNU Affero General Public License version 3,
31  * these Appropriate Legal Notices must retain the display of the Zurmo
32  * logo and Zurmo copyright notice. If the display of the logo is not reasonably
33  * feasible for technical reasons, the Appropriate Legal Notices must display the words
34  * "Copyright Zurmo Inc. 2017. All rights reserved".
35  ********************************************************************************/
36 
37  Yii::import('ext.cssIn.src.CSSIN');
38  class ZurmoCssInUtil extends CSSIN
39  {
40  protected $combineStyleBlocks = false;
41 
42  protected $moveStyleBlocksToBody = false;
43 
49  protected $html;
50 
51  public function setCombineStyleBlock($combineStyleBlocks = true)
52  {
53  $this->combineStyleBlocks = $combineStyleBlocks;
54  }
55 
56  public function setMoveStyleBlocksToBody($moveStyleBlocksToBody = true)
57  {
58  $this->moveStyleBlocksToBody = $moveStyleBlocksToBody;
59  }
60 
61  protected function moveStyleBlocks($html)
62  {
63  $this->html = $html;
64  $styles = $this->resolveStyleBlockContent();
65  $html = $this->stripOriginalStyleTags($html);
66  if ($this->moveStyleBlocksToBody)
67  {
68  return $this->combineAndMoveStylesToBody($styles, $html);
69  }
70  return $this->combineAndMoveStylesToHead($styles, $html);
71  }
72 
73  protected function stripOriginalStyleTags($html)
74  {
75  return preg_replace('|<style(.*)>(.*)</style>|isU', '', $html);
76  }
77 
78  protected function combineAndMoveStylesToBody($styles, $html)
79  {
80  $html = $this->combineAndMoveStyles($styles, $html, false);
81  return $html;
82  }
83 
84  protected function combineAndMoveStylesToHead($styles, $html)
85  {
86  $html = $this->combineAndMoveStyles($styles, $html, true);
87  return $html;
88  }
89 
90  protected function combineAndMoveStyles($styles, $html, $moveToHead)
91  {
92  $search = 'body';
93  if ($moveToHead)
94  {
95  $search = '/head';
96  }
97  $matches = preg_split('#(<' . $search . '.*?>)#i', $html, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE);
98  if (count($matches) > 1)
99  {
100  if ($moveToHead)
101  {
102  $styles = $styles . $matches[1];
103  }
104  else
105  {
106  $styles = $matches[1] . $styles;
107  }
108  $html = $matches[0] . $styles . $matches[2];
109  }
110  return $html;
111  }
112 
113  protected function resolveStyleBlockContent()
114  {
115  $html = $this->html;
116  $matches = array();
117  preg_match_all('|<style(.*)>(.*)</style>|isU', $html, $matches);
118  if ($this->combineStyleBlocks)
119  {
120  $styleBlockContent = implode(PHP_EOL, $matches[2]);
121  $style = ZurmoHtml::tag('style', array(), $styleBlockContent);
122  }
123  else
124  {
125  $style = implode(PHP_EOL, $matches[0]);
126  }
127  return $style;
128  }
129 
130  function inlineCSS($url, $contents = null)
131  {
132  // Download the HTML if it was not provided
133  if ($contents === null)
134  {
135  $html = file_get_html($url, false, null, -1, -1, true, true, DEFAULT_TARGET_CHARSET, false, DEFAULT_BR_TEXT, DEFAULT_SPAN_TEXT);
136  }
137  // Else use the data provided!
138  else
139  {
140  $html = str_get_html($contents, true, true, DEFAULT_TARGET_CHARSET, false, DEFAULT_BR_TEXT, DEFAULT_SPAN_TEXT);
141  }
142 
143  if (!is_object($html))
144  {
145  return false;
146  }
147 
148  $cloneNodesArray = array();
149  foreach ($html->nodes as $node)
150  {
151  $cloneNodesArray[] = clone $node;
152  }
153 
154  $cssUrls = array();
155 
156  // Find all stylesheets and determine their absolute URLs to retrieve them
157  foreach ($html->find('link[rel="stylesheet"]') as $style)
158  {
159  $cssUrls[] = self::absolutify($url, $style->href);
160  $style->outertext = '';
161  }
162 
163  $cssBlocks = $this->processStylesCleanup($html);
164 
165  $rawCss = '';
166  if (!empty($cssUrls))
167  {
168  $rawCss .= $this->getCSSFromFiles($cssUrls);
169  }
170  if (!empty($cssBlocks))
171  {
172  $rawCss .= $cssBlocks;
173  }
174 
175  // Get the CSS rules by decreasing order of specificity.
176  // This is an array with, amongst other things, the keys 'properties', which hold the CSS properties
177  // and the 'selector', which holds the CSS selector
178  $rules = $this->parseCSS($rawCss);
179 
180  // We loop over each rule by increasing order of specificity, find the nodes matching the selector
181  // and apply the CSS properties
182  foreach ($rules as $rule)
183  {
184  foreach ($html->find($rule['selector']) as $node)
185  {
186  // Unserialize the style array, merge the rule's CSS into it...
187  $style = array_merge(self::styleToArray($node->style), $rule['properties']);
188  // And put the CSS back as a string!
189  $node->style = self::arrayToStyle($style);
190  }
191  }
192 
193  // Now a tricky part: do a second pass with only stuff marked !important
194  // because !important properties do not care about specificity, except when fighting
195  // agains another !important property
196  foreach ($rules as $rule)
197  {
198  foreach ($rule['properties'] as $key => $value)
199  {
200  if (strpos($value, '!important') !== false)
201  {
202  foreach ($html->find($rule['selector']) as $node)
203  {
204  $style = self::styleToArray($node->style);
205  $style[$key] = $value;
206  }
207  }
208  }
209  }
210 
211  foreach ($html->nodes as $index => $node)
212  {
213  $nodeStyle = self::styleToArray($node->style);
214  $cloneNodeStyle = self::styleToArray($cloneNodesArray[$index]->style);
215  $style = $this->mergeStyles(self::styleToArray($node->style), self::styleToArray($cloneNodesArray[$index]->style));
216  $style = self::arrayToStyle($style);
217  if ($style != '')
218  {
219  $node->style = $style;
220  }
221  }
222  // Let simple_html_dom give us back our HTML with inline CSS!
223  $html = $this->moveStyleBlocks((string)$html);
224  return $html;
225  }
226 
227  protected function mergeStyles(Array $firstStyle, Array $secondStyle)
228  {
229  $stylesToRemove = array();
230  foreach ($secondStyle as $styleTag => $value)
231  {
232  $matches = array();
233  preg_match('#(.*)-(.*)#i', $styleTag, $matches);
234  if (isset($matches[1]))
235  {
236  $stylesToRemove[] = $matches[1];
237  }
238  }
239  $stylesToAddInTheBegining = array();
240  foreach ($stylesToRemove as $styleRoRemove)
241  {
242  if (isset($firstStyle[$styleRoRemove]))
243  {
244  $stylesToAddInTheBegining[$styleRoRemove] = $firstStyle[$styleRoRemove];
245  unset($firstStyle[$styleRoRemove]);
246  }
247  }
248  $mergeArray = array_merge($firstStyle, $secondStyle);
249  return array_merge($stylesToAddInTheBegining, $mergeArray);
250  }
251 
252  protected function processStylesCleanup($html)
253  {
254  $cssBlocks = '';
255  // Find all <style> blocks and cut styles from them (leaving media queries)
256  foreach ($html->find('style') as $style)
257  {
258  list($cssToParse, $cssToKeep) = self::splitMediaQueries($style->innertext());
259  $cssBlocks .= $cssToParse;
260  }
261  return $cssBlocks;
262  }
263 
264  public static function calculateCSSSpecifity($selector)
265  {
266  // cleanup selector
267  $selector = str_replace(array('>', '+'), array(' > ', ' + '), $selector); // Not Coding Standard
268 
269  // init var
270  $specifity = 0;
271 
272  // split the selector into chunks based on spaces
273  $chunks = explode(' ', $selector);
274 
275  // loop chunks
276  foreach ($chunks as $chunk)
277  {
278  // an ID is important, so give it a high specifity
279  if (strstr($chunk, '#') !== false) $specifity += 100;
280 
281  // classes are more important than a tag, but less important then an ID
282  elseif (strstr($chunk, '.')) $specifity += 10;
283 
284  // anything else isn't that important
285  else $specifity += 1;
286  }
287 
288  // return
289  return $specifity;
290  }
291 
292  public function parseCSS($text)
293  {
294  $css = new \csstidy();
295  $css->parse($text);
296  $rules = array();
297  $position = 0;
298 
299  foreach ($css->css as $declarations)
300  {
301  foreach ($declarations as $selectors => $properties)
302  {
303  foreach (explode(",", $selectors) as $selector) // Not Coding Standard
304  {
305  $rules[] = array(
306  'position' => $position,
307  'specificity' => self::calculateCSSSpecifity($selector),
308  'selector' => $selector,
309  'properties' => $properties
310  );
311  }
312 
313  $position += 1;
314  }
315  }
316 
317  usort($rules, function($a, $b)
318  {
319  if ($a['specificity'] > $b['specificity'])
320  {
321  return 1;
322  }
323  elseif ($a['specificity'] < $b['specificity'])
324  {
325  return -1;
326  }
327  else
328  {
329  if ($a['position'] > $b['position'])
330  {
331  return 1;
332  }
333  else
334  {
335  return -1;
336  }
337  }
338  });
339 
340  return $rules;
341  }
342 
343  public static function splitMediaQueries($css)
344  {
345  // Remove CSS-Comments
346  $css = preg_replace('/\/\*.*?\*\//ms', '', $css);
347  return parent::splitMediaQueries(' ' . $css . ' ');
348  }
349 
350  public static function absolutify($pageUrl, $relativeUrl)
351  {
352  // if relative url starts with // then its schema-less url
353  if (preg_match('/^\/\//', $relativeUrl))
354  {
355  $absoluteUrl = 'http:' . $relativeUrl;
356  }
357  else
358  {
359  $parsedUrl = parse_url($pageUrl);
360  $parsedRelativeUrl = parse_url($relativeUrl);
361  if (isset($parsedUrl['scheme']))
362  {
363  $parsedUrl['scheme'] .= '://';
364  }
365  $qualifiedHost = @$parsedUrl['scheme'] . @$parsedUrl['host'];
366 
367  // If $relativeUrl has a host it is actually absolute, return it.
368  if (isset($parsedRelativeUrl['host']))
369  {
370  $absoluteUrl = $relativeUrl;
371  }
372  // If $relativeUrl begins with / then it is a path relative to the $pageUrl's host
373  elseif (preg_match('/^\//', $parsedRelativeUrl['path']))
374  {
375  $absoluteUrl = $qualifiedHost . $parsedRelativeUrl['path'];
376  }
377  // No leading slash: append the path of $relativeUrl to the 'folder' path of $pageUrl
378  else
379  {
380  $absoluteUrl = $qualifiedHost . dirname($parsedUrl['path']) . '/' . $parsedRelativeUrl['path'];
381  }
382  }
383  return $absoluteUrl;
384  }
385 
386  public function getCSS($url)
387  {
388  if (!isset($cssFiles[$url]))
389  {
390  $cssFiles[$url] = @file_get_contents($url);
391  }
392  return $cssFiles[$url];
393  }
394  }
395 ?>
Generated on Wed Jul 1 2020 07:10:29
Account Suspended
Account Suspended
This Account has been suspended.
Contact your hosting provider for more information.