Code Coverage
 
Classes and Traits
Functions and Methods
Lines
Total
0.00% covered (danger)
0.00%
0 / 1
92.86% covered (success)
92.86%
13 / 14
CRAP
97.11% covered (success)
97.11%
168 / 173
Css
0.00% covered (danger)
0.00%
0 / 1
92.86% covered (success)
92.86%
13 / 14
74
97.11% covered (success)
97.11%
168 / 173
 addMedia
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
2 / 2
 getMedia
100.00% covered (success)
100.00%
1 / 1
2
100.00% covered (success)
100.00%
1 / 1
 getAllMedia
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
1 / 1
 removeMedia
100.00% covered (success)
100.00%
1 / 1
2
100.00% covered (success)
100.00%
3 / 3
 removeAllMedia
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
2 / 2
 parseString
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
3 / 3
 parseFile
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
3 / 3
 parseUri
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
3 / 3
 parseCss
0.00% covered (danger)
0.00%
0 / 1
38.20
94.85% covered (success)
94.85%
92 / 97
 parseCssFile
100.00% covered (success)
100.00%
1 / 1
2
100.00% covered (success)
100.00%
3 / 3
 parseCssUri
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
1 / 1
 render
100.00% covered (success)
100.00%
1 / 1
14
100.00% covered (success)
100.00%
31 / 31
 __toString
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
1 / 1
 parseSelectors
100.00% covered (success)
100.00%
1 / 1
8
100.00% covered (success)
100.00%
22 / 22
<?php
/**
 * Pop PHP Framework (http://www.popphp.org/)
 *
 * @link       https://github.com/popphp/popphp-framework
 * @author     Nick Sagona, III <dev@nolainteractive.com>
 * @copyright  Copyright (c) 2009-2017 NOLA Interactive, LLC. (http://www.nolainteractive.com)
 * @license    http://www.popphp.org/license     New BSD License
 */
/**
 * @namespace
 */
namespace Pop\Css;
/**
 * Pop CSS class
 *
 * @category   Pop
 * @package    Pop\Css
 * @author     Nick Sagona, III <dev@nolainteractive.com>
 * @copyright  Copyright (c) 2009-2017 NOLA Interactive, LLC. (http://www.nolainteractive.com)
 * @license    http://www.popphp.org/license     New BSD License
 * @version    1.0.0
 */
class Css extends AbstractCss
{
    /**
     * Media queries
     * @var array
     */
    protected $media = [];
    /**
     * Add media query
     *
     * @param  Media $media
     * @return self
     */
    public function addMedia(Media $media)
    {
        $this->media[] = $media;
        return $this;
    }
    /**
     * Get media query by index
     *
     * @param  int $i
     * @return Media
     */
    public function getMedia($i)
    {
        return (isset($this->media[$i])) ? $this->media[$i] : null;
    }
    /**
     * Get all media queries
     *
     * @return array
     */
    public function getAllMedia()
    {
        return $this->media;
    }
    /**
     * Remove media query by index
     *
     * @param  int $i
     * @return self
     */
    public function removeMedia($i)
    {
        if (isset($this->media[(int)$i])) {
            unset($this->media[(int)$i]);
        }
        return $this;
    }
    /**
     * Remove all media queries
     *
     * @return self
     */
    public function removeAllMedia()
    {
        $this->media = [];
        return $this;
    }
    /**
     * Parse CSS string
     *
     * @param  string $cssString
     * @return self
     */
    public static function parseString($cssString)
    {
        $css = new self();
        $css->parseCss($cssString);
        return $css;
    }
    /**
     * Parse CSS from file
     *
     * @param  string $cssFile
     * @return self
     */
    public static function parseFile($cssFile)
    {
        $css = new self();
        $css->parseCssFile($cssFile);
        return $css;
    }
    /**
     * Parse CSS from URI
     *
     * @param  string $cssUri
     * @return self
     */
    public static function parseUri($cssUri)
    {
        $css = new self();
        $css->parseCssUri($cssUri);
        return $css;
    }
    /**
     * Parse CSS string
     *
     * @param  string $cssString
     * @return self
     */
    public function parseCss($cssString)
    {
        // Parse media queries
        $origCssString = $cssString;
        $mediaComments = [];
        $matches       = [];
        preg_match_all('~@media\b[^{]*({((?:[^{}]+|(?1))*)})~', $cssString, $matches, PREG_OFFSET_CAPTURE);
        if (isset($matches[0]) && isset($matches[0][0])) {
            foreach ($matches[0] as $match) {
                // See if media query has a top-level comment
                $mediaComment = null;
                $char         = null;
                $pos          = $match[1];
                while (($char != '/') && ($char != '}') && ($pos != 0)) {
                    $pos--;
                    $char = $origCssString[$pos];
                }
                if ($char == '/') {
                    $mediaComment = substr($origCssString, 0, ($pos + 1));
                    $mediaComment = substr($mediaComment, strrpos($mediaComment, '/*'));
                    $mediaComment = explode(PHP_EOL, $mediaComment);
                    foreach ($mediaComment as $key => $line) {
                        $mediaComment[$key] = trim(str_replace(['/*', '*/', '*'], ['', '', ''], $line));
                    }
                }
                $cssString      = str_replace($match[0], '', $cssString);
                $mediaType      = null;
                $mediaCondition = null;
                $mediaFeatures  = [];
                $mediaQuery     = substr($match[0], 6);
                $mediaQuery     = trim(substr($mediaQuery, 0, strpos($mediaQuery, ' {')));
                $mediaQueryCss  = substr($match[0], (strpos($match[0], '{') + 1));
                $mediaQueryCss  = trim(substr($mediaQueryCss, 0, strrpos($match[0], '}')));
                if (strpos($mediaQuery, 'all') !== false) {
                    $mediaType = 'all';
                } else if (strpos($mediaQuery, 'print') !== false) {
                    $mediaType = 'print';
                } else if (strpos($mediaQuery, 'screen') !== false) {
                    $mediaType = 'screen';
                } else if (strpos($mediaQuery, 'speech') !== false) {
                    $mediaType = 'speech';
                }
                if (strpos($mediaQuery, 'not') !== false) {
                    $mediaCondition = 'not';
                }
                if (strpos($mediaQuery, 'only') !== false) {
                    $mediaCondition = 'only';
                }
                if ((strpos($mediaQuery, '(') !== false) && (strpos($mediaQuery, ')') !== false)) {
                    $features = substr($mediaQuery, strpos($mediaQuery, '('));
                    $features = substr($features, 0, (strrpos($features, ')') + 1));
                    $features = explode('and', $features);
                    foreach ($features as $feature) {
                        $feature = str_replace(['(', ')'], ['', ''], $feature);
                        $feature = explode(':', $feature);
                        if (count($feature) == 2) {
                            $mediaFeatures[trim($feature[0])] = trim($feature[1]);
                        }
                    }
                }
                $media = new Media($mediaType, $mediaFeatures, $mediaCondition);
                if (null !== $mediaComment) {
                    $media->addComment(new Comment(trim(implode(PHP_EOL, $mediaComment))));
                }
                $commentsMatches = [];
                preg_match_all('!/\*.*?\*/!s', $mediaQueryCss, $commentsMatches, PREG_OFFSET_CAPTURE);
                if (isset($commentsMatches[0]) && isset($commentsMatches[0][0])) {
                    foreach ($commentsMatches[0] as $match) {
                        $selectorName = substr($mediaQueryCss, $match[1]);
                        $selectorName = substr($selectorName, 0, strpos($selectorName, '{'));
                        $selectorName = trim(substr($selectorName, (strpos($selectorName, '*/') + 2)));
                        $comment = explode(PHP_EOL, $match[0]);
                        foreach ($comment as $key => $line) {
                            $comment[$key] = trim(str_replace(['/*', '*/', '*'], ['', '', ''], $line));
                        }
                        $mediaComments[$selectorName] = new Comment(trim(implode(PHP_EOL, $comment)));
                    }
                }
                $mediaQueryCss = preg_replace('!/\*.*?\*/!s', '', $mediaQueryCss);
                $mediaQueryCss = preg_replace('/\n\s*\n/', "\n", $mediaQueryCss);
                $selectors = $this->parseSelectors($mediaQueryCss);
                foreach ($selectors as $selector) {
                    $media->addSelector($selector);
                }
                if (count($mediaComments) > 0) {
                    foreach ($mediaComments as $selectorName => $comment) {
                        if ($media->hasSelector($selectorName)) {
                            $media->getSelector($selectorName)->addComment($comment);
                        }
                    }
                }
                $this->addMedia($media);
            }
        }
        // Parse comments
        $comments = [];
        $matches  = [];
        preg_match_all('!/\*.*?\*/!s', $cssString, $matches, PREG_OFFSET_CAPTURE);
        if (isset($matches[0]) && isset($matches[0][0])) {
            foreach ($matches[0] as $match) {
                $selectorName = null;
                if ($match[1] != 0) {
                    $selectorName = substr($cssString, $match[1]);
                    $selectorName = substr($selectorName, 0, strpos($selectorName, '{'));
                    $selectorName = trim(substr($selectorName, (strpos($selectorName, '*/') + 2)));
                }
                $comment = explode(PHP_EOL, $match[0]);
                foreach ($comment as $key => $line) {
                    $comment[$key] = trim(str_replace(['/*', '*/', '*'], ['', '', ''], $line));
                }
                if (null === $selectorName) {
                    $this->addComment(new Comment(trim(implode(PHP_EOL, $comment))));
                } else {
                    $comments[$selectorName] = new Comment(trim(implode(PHP_EOL, $comment)));
                }
            }
        }
        $cssString = preg_replace('!/\*.*?\*/!s', '', $cssString);
        $cssString = preg_replace('/\n\s*\n/', "\n", $cssString);
        // Parse everything else
        $selectors = $this->parseSelectors($cssString);
        foreach ($selectors as $selector) {
            $this->addSelector($selector);
        }
        if (count($comments) > 0) {
            foreach ($comments as $selectorName => $comment) {
                if ($this->hasSelector($selectorName)) {
                    $this->getSelector($selectorName)->addComment($comment);
                }
            }
        }
        return $this;
    }
    /**
     * Parse CSS string from file
     *
     * @param  string $cssFile
     * @throws Exception
     * @return self
     */
    public function parseCssFile($cssFile)
    {
        if (!file_exists($cssFile)) {
            throw new Exception('Error: That file does not exist.');
        }
        return $this->parseCss(file_get_contents($cssFile));
    }
    /**
     * Parse CSS string from URI
     *
     * @param  string $cssUri
     * @return self
     */
    public function parseCssUri($cssUri)
    {
        return $this->parseCss(file_get_contents($cssUri));
    }
    /**
     * Method to render the selector CSS
     *
     * @return string
     */
    public function render()
    {
        $css = '';
        if (!$this->minify) {
            foreach ($this->comments as $comment) {
                $css .= (string)$comment . PHP_EOL;
            }
        }
        foreach ($this->elements as $element) {
            if (isset($this->selectors[$element])) {
                $selector = $this->selectors[$element];
                $selector->minify($this->minify);
                $css .= (string)$selector;
                if (!$this->minify) {
                    $css .= PHP_EOL;
                }
            }
        }
        foreach ($this->ids as $id) {
            if (isset($this->selectors[$id])) {
                $selector = $this->selectors[$id];
                $selector->minify($this->minify);
                $css .= (string)$selector;
                if (!$this->minify) {
                    $css .= PHP_EOL;
                }
            }
        }
        foreach ($this->classes as $class) {
            if (isset($this->selectors[$class])) {
                $selector = $this->selectors[$class];
                $selector->minify($this->minify);
                $css .= (string)$selector;
                if (!$this->minify) {
                    $css .= PHP_EOL;
                }
            }
        }
        foreach ($this->media as $media) {
            $media->minify($this->minify);
            $css .= (string)$media;
            if (!$this->minify) {
                $css .= PHP_EOL;
            }
        }
        return $css;
    }
    /**
     * To string method
     *
     * @return string
     */
    public function __toString()
    {
        return $this->render();
    }
    /**
     * Method to parse the CSS selectors from a string
     *
     * @param  string $cssString
     * @return array
     */
    protected function parseSelectors($cssString)
    {
        $selectors = [];
        $matches = [];
        preg_match_all('/\{\s*([^}]*?)\s*}/m', $cssString, $matches, PREG_OFFSET_CAPTURE);
        if (isset($matches[0]) && isset($matches[0][0])) {
            $curPos = 0;
            foreach ($matches[0] as $match) {
                $selectorName = trim(substr($cssString, $curPos, $match[1]));
                if (strpos($selectorName, '{') !== false) {
                    $selectorName = trim(substr($selectorName, 0, strpos($selectorName, '{')));
                }
                $rules    = explode(';', trim(str_replace(['{', '}'], ['', ''], trim($match[0]))));
                $cssRules = [];
                foreach ($rules as $key => $value) {
                    if (!empty($value)) {
                        $value = trim($value);
                        $v = explode(':', $value);
                        if (count($v) == 2) {
                            $cssRules[trim($v[0])] = trim($v[1]);
                        }
                    }
                }
                $selector = new Selector($selectorName);
                $selector->setProperties($cssRules);
                $selectors[] = $selector;
                $curPos = $match[1] + strlen($match[0]);
            }
        }
        return $selectors;
    }
}