Code Coverage
 
Classes and Traits
Functions and Methods
Lines
Total
0.00% covered (danger)
0.00%
0 / 1
13.33% covered (danger)
13.33%
2 / 15
CRAP
39.16% covered (danger)
39.16%
65 / 166
AssetFile
0.00% covered (danger)
0.00%
0 / 1
13.33% covered (danger)
13.33%
2 / 15
1673.27
39.16% covered (danger)
39.16%
65 / 166
 getAssetFileType
0.00% covered (danger)
0.00%
0 / 1
2.15
66.67% covered (warning)
66.67%
2 / 3
 setAssetFileType
0.00% covered (danger)
0.00%
0 / 1
2.06
75.00% covered (warning)
75.00%
3 / 4
 getAssetFilePath
0.00% covered (danger)
0.00%
0 / 1
2.15
66.67% covered (warning)
66.67%
2 / 3
 isAssetFilePathUrl
100.00% covered (success)
100.00%
1 / 1
2
100.00% covered (success)
100.00%
1 / 1
 setAssetFilePath
0.00% covered (danger)
0.00%
0 / 1
42.62
18.52% covered (danger)
18.52%
5 / 27
 getAssetFileContents
0.00% covered (danger)
0.00%
0 / 1
36.69
44.44% covered (danger)
44.44%
12 / 27
 setAssetFileContents
0.00% covered (danger)
0.00%
0 / 1
4.04
86.67% covered (warning)
86.67%
13 / 15
 getAssetFileExtension
100.00% covered (success)
100.00%
1 / 1
2
100.00% covered (success)
100.00%
1 / 1
 getAssetFileLastModified
0.00% covered (danger)
0.00%
0 / 1
41.48
36.84% covered (danger)
36.84%
7 / 19
 assetFileExists
0.00% covered (danger)
0.00%
0 / 1
3.33
66.67% covered (warning)
66.67%
2 / 3
 getAssetFileSize
0.00% covered (danger)
0.00%
0 / 1
39.63
33.33% covered (danger)
33.33%
5 / 15
 assetFileContentEquals
0.00% covered (danger)
0.00%
0 / 1
210.00
0.00% covered (danger)
0.00%
0 / 31
 assetFileTypeExists
0.00% covered (danger)
0.00%
0 / 1
2.15
66.67% covered (warning)
66.67%
2 / 3
 getAssetFileDefaultExtension
0.00% covered (danger)
0.00%
0 / 1
6.56
75.00% covered (warning)
75.00%
6 / 8
 getAssetFileCompiledExtension
0.00% covered (danger)
0.00%
0 / 1
4.59
66.67% covered (warning)
66.67%
4 / 6
<?php
namespace AssetsBundle\AssetFile;
class AssetFile extends \Zend\Stdlib\AbstractOptions
{
    const ASSET_CSS = 'css';
    const ASSET_JS = 'js';
    const ASSET_LESS = 'less';
    const ASSET_SCSS = 'scss';
    const ASSET_MEDIA = 'media';
    
    const ALL_ASSET_TYPES = [
        self::ASSET_CSS,
        self::ASSET_JS,
        self::ASSET_LESS,
        self::ASSET_SCSS,
        self::ASSET_MEDIA,
    ];
    const COMPILED_CSS_TYPES = [
        self::ASSET_LESS,
        self::ASSET_SCSS,
    ];
    /**
     * @var string
     */
    protected $assetFileType;
    /**
     * @var string
     */
    protected $assetFilePath;
    /**
     * @var string
     */
    protected $assetFileContents;
    /**
     * @var string
     */
    protected $assetFileContentsLastRetrievedTime;
    /**
     * @var string
     */
    protected $assetFileExtension;
    /**
     * @return string
     * @throws \LogicException
     */
    public function getAssetFileType() : string
    {
        if (self::assetFileTypeExists($this->assetFileType)) {
            return $this->assetFileType;
        }
        throw new \LogicException('Asset file type is undefined');
    }
    /**
     * @param string $sAssetFileType
     * @return \AssetsBundle\AssetFile\AssetFile
     * @throws \InvalidArgumentException
     */
    public function setAssetFileType(string $sAssetFileType) : \AssetsBundle\AssetFile\AssetFile
    {
        if (self::assetFileTypeExists($sAssetFileType)) {
            $this->assetFileType = $sAssetFileType;
            return $this;
        }
        throw new \InvalidArgumentException('Asset file type "' . $sAssetFileType . '" does not exist');
    }
    /**
     * @return string
     * @throws \LogicException
     */
    public function getAssetFilePath() : string
    {
        if (is_string($this->assetFilePath)) {
            return $this->assetFilePath;
        }
        throw new \LogicException('Asset file path is undefined');
    }
    /**
     * @return bool
     */
    public function isAssetFilePathUrl() : bool
    {
        return filter_var($sAssetFilePath = $this->getAssetFilePath(), FILTER_VALIDATE_URL) && preg_match('/^\/|http/', $sAssetFilePath);
    }
    /**
     * @param string $sAssetFilePath
     * @return \AssetsBundle\AssetFile\AssetFile
     * @throws \InvalidArgumentException
     */
    public function setAssetFilePath(string $sAssetFilePath) : \AssetsBundle\AssetFile\AssetFile
    {
        if (!is_string($sAssetFilePath)) {
            throw new \InvalidArgumentException('Asset file path expects string, "' . gettype($sAssetFilePath) . '" given');
        }
        // Reset asset file contents
        $this->assetFileContents = null;
        if (is_readable($sAssetFilePath)) {
            $this->assetFilePath = $sAssetFilePath;
            return $this;
        }
        // Asset file path is an url
        if (strpos($sAssetFilePath, '://') === false) {
            throw new \InvalidArgumentException('Asset\'s file "' . $sAssetFilePath . '" does not exist');
        } elseif (in_array($this->getAssetFileType(), self::COMPILED_CSS_TYPES, true)) {
            throw new \InvalidArgumentException($this->getAssetFileType().' assets does not support urls, "' . $sAssetFilePath . '" given');
        }
        if (!($sFilteredAssetFilePath = filter_var($sAssetFilePath, FILTER_VALIDATE_URL))) {
            throw new \InvalidArgumentException('Asset\'s file path "' . $sAssetFilePath . '" is not a valid url');
        }
        \Zend\Stdlib\ErrorHandler::start(\E_ALL);
        $oFileHandle = fopen($sFilteredAssetFilePath, 'r');
        \Zend\Stdlib\ErrorHandler::stop(true);
        if (!$oFileHandle) {
            throw new \InvalidArgumentException('Unable to open asset file "' . $sFilteredAssetFilePath . '"');
        }
        \Zend\Stdlib\ErrorHandler::start(\E_ALL);
        $aMetaData = stream_get_meta_data($oFileHandle);
        \Zend\Stdlib\ErrorHandler::stop(true);
        if (empty($aMetaData['uri'])) {
            throw new \InvalidArgumentException('Unable to retreive uri metadata from file "' . $sFilteredAssetFilePath . '"');
        }
        $this->assetFilePath = $aMetaData['uri'];
        \Zend\Stdlib\ErrorHandler::start(\E_ALL);
        fclose($oFileHandle);
        \Zend\Stdlib\ErrorHandler::stop(true);
        return $this;
    }
    /**
     * @return string
     * @throws \RuntimeException
     */
    public function getAssetFileContents() : string
    {
        if (
                $this->assetFileContents &&
                (($iLastModified = $this->getAssetFileLastModified()) && $iLastModified < $this->assetFileContentsLastRetrievedTime)
        ) {
            return $this->assetFileContents;
        }
        if(!$this->assetFileExists()){
            if ($this->assetFileContents) {
                return $this->assetFileContents;
            }
            throw new \LogicException('Asset file "'.$this->getAssetFilePath().'" does not exist, unable to retrieve its contents');
        }
        $sAssetFilePath = $this->getAssetFilePath();
        if ($this->isAssetFilePathUrl()) {
            \Zend\Stdlib\ErrorHandler::start(\E_ALL);
            $oFileHandle = fopen($sAssetFilePath, 'r');
            \Zend\Stdlib\ErrorHandler::stop(true);
            $this->assetFileContents = '';
            while (($sContent = fgets($oFileHandle)) !== false) {
                $this->assetFileContents .= $sContent . PHP_EOL;
            }
            if (!feof($oFileHandle)) {
                throw new \RuntimeException('Unable to retrieve asset contents from file "' . $sAssetFilePath . '"');
            }
            fclose($oFileHandle);
        } elseif (strtolower(pathinfo($sAssetFilePath, PATHINFO_EXTENSION)) === 'php') {
            ob_start();
            if (false === include $sAssetFilePath) {
                throw new \RuntimeException('Error appends while including asset file "' . $sAssetFilePath . '"');
            }
            $this->assetFileContents = ob_get_clean();
        } elseif (($this->assetFileContents = file_get_contents($sAssetFilePath)) === false) {
            throw new \RuntimeException('Unable to retrieve asset contents from file "' . $sAssetFilePath . '"');
        }
        // Update content last retrieved time
        $this->assetFileContentsLastRetrievedTime = time();
        return $this->assetFileContents;
    }
    /**
     * @param string $sAssetFileContents
     * @param bool $bFileAppend
     * @return \AssetsBundle\AssetFile\AssetFile
     * @throws \InvalidArgumentException
     */
    public function setAssetFileContents(string $sAssetFileContents, bool $bFileAppend = true) : \AssetsBundle\AssetFile\AssetFile
    {
        if (!is_string($sAssetFileContents)) {
            throw new \InvalidArgumentException('Asset file content expects string, "' . gettype($sAssetFileContents) . '" given');
        }
        $sAssetFilePath = $this->getAssetFilePath();
        if ($bFileAppend) {
            if ($this->assetFileContents) {
                $this->assetFileContents .= $sAssetFileContents;
            }
            \Zend\Stdlib\ErrorHandler::start(\E_ALL);
            file_put_contents($sAssetFilePath, $sAssetFileContents, FILE_APPEND);
            \Zend\Stdlib\ErrorHandler::stop(true);
        } else {
            $this->assetFileContents = $sAssetFileContents;
            \Zend\Stdlib\ErrorHandler::start(\E_ALL);
            file_put_contents($sAssetFilePath, $sAssetFileContents);
            \Zend\Stdlib\ErrorHandler::stop(true);
        }
       
        // Update content last retrieved time
        $this->assetFileContentsLastRetrievedTime = time();
        return $this;
    }
    /**
     * @return string
     */
    public function getAssetFileExtension() : string
    {
        return $this->assetFileExtension ? : $this->assetFileExtension = strtolower(pathinfo($this->getAssetFilePath(), PATHINFO_EXTENSION));
    }
    /**
     * Retrieve asset file last modified timestamp
     * @return int|null
     */
    public function getAssetFileLastModified() : ?int
    {
        $sAssetFilePath = $this->getAssetFilePath();
        if ($this->isAssetFilePathUrl()) {
            if (
                    // Retrieve headers
                    ($aHeaders = get_headers($sAssetFilePath, 1))
                    // Assert return is OK
                    && strstr($aHeaders[0], '200') !== false
                    // Retrieve last modified as DateTime
                    && !empty($aHeaders['Last-Modified']) && $oLastModified = new \DateTime($aHeaders['Last-Modified'])
            ) {
                return $oLastModified->getTimestamp();
            }
            $oCurlHandle = curl_init($sAssetFilePath);
            curl_setopt($oCurlHandle, CURLOPT_NOBODY, true);
            curl_setopt($oCurlHandle, CURLOPT_RETURNTRANSFER, true);
            curl_setopt($oCurlHandle, CURLOPT_FILETIME, true);
            if (curl_exec($oCurlHandle) === false) {
                return null;
            }
            return curl_getinfo($oCurlHandle, CURLINFO_FILETIME) ? : null;
        }
        if (!file_exists($sAssetFilePath)) {
            throw new \LogicException('Asset file "'.$sAssetFilePath.'" does not exist');
        }
        \Zend\Stdlib\ErrorHandler::start(\E_ALL);
        $iAssetFileFilemtime = filemtime($sAssetFilePath);
        \Zend\Stdlib\ErrorHandler::stop(true);
        return $iAssetFileFilemtime ? : null;
    }
    /**
     * Check if the asset file exists
     * @return bool
     */
    public function assetFileExists() : bool
    {
        // Remote file
        if ($this->isAssetFilePathUrl()) {
            // Retrieve headers & assert return is OK
            return ($aHeaders = get_headers($sAssetFilePath = $this->getAssetFilePath(), 1)) && strstr($aHeaders[0], '200') !== false;
        }
        return file_exists($this->getAssetFilePath());
    }
    
    /**
     * Retrieve asset file size
     * @return int|null
     */
    public function getAssetFileSize() : ?int
    {         
        // Remote file
        if ($this->isAssetFilePathUrl()) {
            if (
                    // Retrieve headers
                    ($aHeaders = get_headers($sAssetFilePath = $this->getAssetFilePath(), 1))
                    // Assert return is OK
                    && strstr($aHeaders[0], '200') !== false
                    // Retrieve content length
                    && !empty($aHeaders['Content-Length']) && $iAssetFileSize = $aHeaders['Content-Length']
            ) {
                return $iAssetFileSize;
            }
            $oCurlHandle = curl_init($sAssetFilePath);
            curl_setopt($oCurlHandle, CURLOPT_NOBODY, true);
            curl_setopt($oCurlHandle, CURLOPT_RETURNTRANSFER, true);
            if (curl_exec($oCurlHandle) === false) {
                return null;
            }
            return curl_getinfo($oCurlHandle, CURLINFO_CONTENT_LENGTH_DOWNLOAD) ? : null;
        }
        
        // Local file
        \Zend\Stdlib\ErrorHandler::start(\E_ALL);
        $iAssetFileSize = filesize($this->getAssetFilePath());
        \Zend\Stdlib\ErrorHandler::stop(true);
        return $iAssetFileSize ? : null;
    }
    public function assetFileContentEquals(\AssetsBundle\AssetFile\AssetFile $oAssetFile)
    {
        // If files exists both
        $bFileAExists = $this->assetFileExists();
        $bFileBExists = $oAssetFile->assetFileExists();
        $iFileASize = $bFileAExists ? $this->getAssetFileSize() : strlen($this->getAssetFileContent());
        $iFileBSize = $bFileBExists ? $oAssetFile->getAssetFileSize() : strlen($oAssetFile->getAssetFileContent());
        // Compare size first
        if ($this->getAssetFileSize() !==  $oAssetFile->getAssetFileSize()) {
            return false;
        }
        if ($bFileAExists && $bFileBExists) {
            // Then compare content
            $rFileHandleA = fopen($this->getAssetFilePath(), 'rb');
            $rFileHandleB = fopen($oAssetFile->getAssetFilePath(), 'rb');
            while (($sCharA = fread($rFileHandleA, 4096)) !== false) {
                $sCharB = fread($rFileHandleB, 4096);
                if ($sCharA !== $sCharB) {
                    fclose($rFileHandleA);
                    fclose($rFileHandleB);
                    return false;
                }
            }
            fclose($rFileHandleA);
            fclose($rFileHandleB);
            return true;
        }
        if (!$bFileAExists && !$bFileBExists) {
            return $this->getAssetFileContents() === $oAssetFile->getAssetFileContents();
        }
        $rFileHandle = fopen($bFileAExists ? $this->getAssetFilePath() : $oAssetFile->getAssetFilePath(), 'rb');
        $sFileContent = $bFileAExists ? $this->getAssetFileContents() : $oAssetFile->getAssetFileContents();
        $iCharLength = 4096;
        $iCharsChecked = 0;
        while (($sCharA = fread($rFileHandle, $iCharLength)) !== false) {
            $sCharB = substr($sFileContent, $iCharsChecked, $iCharLength);
            if ($sCharA !== $sCharB) {
                fclose($rFileHandle);
                return false;
            }
            $iCharsChecked += $iCharLength;
        }
        return true;
    }
    /**
     * Check if asset file's type is valid
     * @param string $sAssetFileType
     * @throws \InvalidArgumentException
     * @return bool
     */
    public static function assetFileTypeExists(string $sAssetFileType) : bool
    {
        if (!is_string($sAssetFileType)) {
            throw new \InvalidArgumentException('Asset file type expects string, "' . gettype($sAssetFileType) . '" given');
        }
        return in_array($sAssetFileType, self::ALL_ASSET_TYPES, true);
    }
    /**
     * @throws \DomainException
     * @throws \InvalidArgumentException
     * @return string
     */
    public static function getAssetFileDefaultExtension(string $sAssetFileType) : string
    {
        if (!is_string($sAssetFileType)) {
            throw new \InvalidArgumentException('Asset file type expects string, "' . gettype($sAssetFileType) . '" given');
        }
        switch ($sAssetFileType) {
            case self::ASSET_CSS:
            case self::ASSET_LESS:
            case self::ASSET_SCSS:
            case self::ASSET_JS:
                return $sAssetFileType;
            default:
                throw new \DomainException('Asset file type "' . $sAssetFileType . '" has no default extension');
        }
    }
    /**
     * @throws \DomainException
     * @throws \InvalidArgumentException
     * @return string
     *
     */
    public static function getAssetFileCompiledExtension(string $sAssetFileType) : string
    {
        if (!is_string($sAssetFileType)) {
            throw new \InvalidArgumentException('Asset file type expects string, "' . gettype($sAssetFileType) . '" given');
        }
        switch ($sAssetFileType) {
            case self::ASSET_LESS:
            case self::ASSET_SCSS:
                return self::ASSET_CSS;
            default:
                throw new \DomainException('Asset file type "' . $sAssetFileType . '" has no compilded extension');
        }
    }
}