Code Coverage |
||||||||||
Classes and Traits |
Functions and Methods |
Lines |
||||||||
Total | |
0.00% |
0 / 1 |
|
13.33% |
2 / 15 |
CRAP | |
39.16% |
65 / 166 |
AssetFile | |
0.00% |
0 / 1 |
|
13.33% |
2 / 15 |
1673.27 | |
39.16% |
65 / 166 |
getAssetFileType | |
0.00% |
0 / 1 |
2.15 | |
66.67% |
2 / 3 |
|||
setAssetFileType | |
0.00% |
0 / 1 |
2.06 | |
75.00% |
3 / 4 |
|||
getAssetFilePath | |
0.00% |
0 / 1 |
2.15 | |
66.67% |
2 / 3 |
|||
isAssetFilePathUrl | |
100.00% |
1 / 1 |
2 | |
100.00% |
1 / 1 |
|||
setAssetFilePath | |
0.00% |
0 / 1 |
42.62 | |
18.52% |
5 / 27 |
|||
getAssetFileContents | |
0.00% |
0 / 1 |
36.69 | |
44.44% |
12 / 27 |
|||
setAssetFileContents | |
0.00% |
0 / 1 |
4.04 | |
86.67% |
13 / 15 |
|||
getAssetFileExtension | |
100.00% |
1 / 1 |
2 | |
100.00% |
1 / 1 |
|||
getAssetFileLastModified | |
0.00% |
0 / 1 |
41.48 | |
36.84% |
7 / 19 |
|||
assetFileExists | |
0.00% |
0 / 1 |
3.33 | |
66.67% |
2 / 3 |
|||
getAssetFileSize | |
0.00% |
0 / 1 |
39.63 | |
33.33% |
5 / 15 |
|||
assetFileContentEquals | |
0.00% |
0 / 1 |
210.00 | |
0.00% |
0 / 31 |
|||
assetFileTypeExists | |
0.00% |
0 / 1 |
2.15 | |
66.67% |
2 / 3 |
|||
getAssetFileDefaultExtension | |
0.00% |
0 / 1 |
6.56 | |
75.00% |
6 / 8 |
|||
getAssetFileCompiledExtension | |
0.00% |
0 / 1 |
4.59 | |
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'); | |
} | |
} | |
} |