Code Coverage |
||||||||||
Classes and Traits |
Functions and Methods |
Lines |
||||||||
| Total | |
0.00% |
0 / 1 |
|
22.22% |
2 / 9 |
CRAP | |
66.33% |
65 / 98 |
| AssetFilesCacheManager | |
0.00% |
0 / 1 |
|
22.22% |
2 / 9 |
109.35 | |
66.33% |
65 / 98 |
| __construct | |
0.00% |
0 / 1 |
2.15 | |
66.67% |
2 / 3 |
|||
| getAssetFileCachePath | |
0.00% |
0 / 1 |
9.19 | |
86.67% |
13 / 15 |
|||
| hasProductionCachedAssetFiles | |
0.00% |
0 / 1 |
3.58 | |
60.00% |
3 / 5 |
|||
| getProductionCachedAssetFiles | |
0.00% |
0 / 1 |
4.07 | |
83.33% |
10 / 12 |
|||
| cacheAssetFile | |
0.00% |
0 / 1 |
25.40 | |
58.14% |
25 / 43 |
|||
| isAssetFileCached | |
100.00% |
1 / 1 |
5 | |
100.00% |
6 / 6 |
|||
| sanitizeAssetFilePath | |
0.00% |
0 / 1 |
2.86 | |
40.00% |
2 / 5 |
|||
| setOptions | |
100.00% |
1 / 1 |
1 | |
100.00% |
2 / 2 |
|||
| getOptions | |
0.00% |
0 / 1 |
6.28 | |
28.57% |
2 / 7 |
|||
| <?php | |
| namespace AssetsBundle\AssetFile; | |
| class AssetFilesCacheManager | |
| { | |
| /** | |
| * @var \AssetsBundle\Service\ServiceOptions | |
| */ | |
| protected $options; | |
| /** | |
| * List of unwanted file path characters | |
| * @var array | |
| */ | |
| protected $unwantedFilePathChars = array('<', '>', '?', '*', '"', '|', ':'); | |
| /** | |
| * Constructor | |
| * @param \AssetsBundle\Service\ServiceOptions $oOptions | |
| */ | |
| public function __construct(\AssetsBundle\Service\ServiceOptions $oOptions = null) | |
| { | |
| if ($oOptions) { | |
| $this->setOptions($oOptions); | |
| } | |
| } | |
| /** | |
| * @param \AssetsBundle\AssetFile\AssetFile $oAssetFile | |
| * @return string | |
| * @throws \DomainException | |
| */ | |
| public function getAssetFileCachePath(\AssetsBundle\AssetFile\AssetFile $oAssetFile) : string | |
| { | |
| switch ($sAssetType = $oAssetFile->getAssetFileType()) { | |
| case \AssetsBundle\AssetFile\AssetFile::ASSET_CSS: | |
| case \AssetsBundle\AssetFile\AssetFile::ASSET_JS: | |
| // In production, css & js files have a unique name depending on current matching route | |
| if ($this->getOptions()->isProduction()) { | |
| $sCacheFilePath = $this->getOptions()->getCacheFileName() . '.' . \AssetsBundle\AssetFile\AssetFile::getAssetFileDefaultExtension($sAssetType); | |
| } | |
| // In development, css & js files dirname are displayed for easy debugging | |
| else { | |
| $sCacheFilePath = $this->sanitizeAssetFilePath($oAssetFile); | |
| } | |
| break; | |
| case \AssetsBundle\AssetFile\AssetFile::ASSET_LESS: | |
| case \AssetsBundle\AssetFile\AssetFile::ASSET_SCSS: | |
| // In production, css & js files have a unique name depending on current matching route | |
| if ($this->getOptions()->isProduction()) { | |
| throw new \DomainException('Asset\'s type "' . $sAssetType . '" can not be cached in production'); | |
| } | |
| // In development, compilded css files are displayed in a single file for easy debugging | |
| $sCacheFilePath = 'dev_' . $sAssetType . '_' . $this->getOptions()->getCacheFileName() . '.' . \AssetsBundle\AssetFile\AssetFile::getAssetFileCompiledExtension($sAssetType); | |
| break; | |
| case \AssetsBundle\AssetFile\AssetFile::ASSET_MEDIA: | |
| // In production, media dirname is encrypted | |
| if ($this->getOptions()->isProduction()) { | |
| $sCacheFilePath = md5(dirname($sAssetFilePath = $oAssetFile->getAssetFilePath())) . DIRECTORY_SEPARATOR . basename($sAssetFilePath); | |
| } | |
| // In development, media dirname is displayed for easy debugging | |
| else { | |
| $sCacheFilePath = $this->sanitizeAssetFilePath($oAssetFile); | |
| } | |
| break; | |
| default: | |
| throw new \DomainException('Asset\'s type "' . $sAssetType . '" can not be cached'); | |
| } | |
| return $this->getOptions()->getCachePath() . DIRECTORY_SEPARATOR . str_replace($this->unwantedFilePathChars, '_', ltrim(str_ireplace(getcwd(), '', $sCacheFilePath), DIRECTORY_SEPARATOR)); | |
| } | |
| /** | |
| * @param string $sAssetFileType | |
| * @return bool | |
| * @throws \InvalidArgumentException | |
| */ | |
| public function hasProductionCachedAssetFiles(string $sAssetFileType) : bool | |
| { | |
| if (!\AssetsBundle\AssetFile\AssetFile::assetFileTypeExists($sAssetFileType)) { | |
| throw new \InvalidArgumentException('Asset file type "' . $sAssetFileType . '" is not valid'); | |
| } | |
| if (in_array($sAssetFileType, array(\AssetsBundle\AssetFile\AssetFile::ASSET_CSS, \AssetsBundle\AssetFile\AssetFile::ASSET_JS))) { | |
| return file_exists($this->getOptions()->getCachePath() . DIRECTORY_SEPARATOR . $this->getOptions()->getCacheFileName() . '.' . \AssetsBundle\AssetFile\AssetFile::getAssetFileDefaultExtension($sAssetFileType)); | |
| } | |
| throw new \InvalidArgumentException(__METHOD__ . 'allows "' . \AssetsBundle\AssetFile\AssetFile::ASSET_CSS . '" & "' . \AssetsBundle\AssetFile\AssetFile::ASSET_JS . '" asset file type, "' . $sAssetFileType . '" given'); | |
| } | |
| /** | |
| * @param string $sAssetFileType | |
| * @return array | |
| * @throws \InvalidArgumentException | |
| * @throws \LogicException | |
| */ | |
| public function getProductionCachedAssetFiles(string $sAssetFileType) : array | |
| { | |
| if (!\AssetsBundle\AssetFile\AssetFile::assetFileTypeExists($sAssetFileType)) { | |
| throw new \InvalidArgumentException('Asset file type "' . $sAssetFileType . '" is not valid'); | |
| } | |
| if (!in_array($sAssetFileType, array(\AssetsBundle\AssetFile\AssetFile::ASSET_CSS, \AssetsBundle\AssetFile\AssetFile::ASSET_JS))) { | |
| throw new \InvalidArgumentException(__METHOD__ . 'allows "' . \AssetsBundle\AssetFile\AssetFile::ASSET_CSS . '" & "' . \AssetsBundle\AssetFile\AssetFile::ASSET_JS . '" asset file type, "' . $sAssetFileType . '" given'); | |
| } | |
| $sCacheFileName = $this->getOptions()->getCacheFileName(); | |
| $sCacheFileExtension = \AssetsBundle\AssetFile\AssetFile::getAssetFileDefaultExtension($sAssetFileType); | |
| $aAssetFiles = array(); | |
| foreach (glob($this->getOptions()->getCachePath() . DIRECTORY_SEPARATOR . $sCacheFileName . '*.' . $sCacheFileExtension) as $sAssetFilePath) { | |
| $aAssetFiles[] = new \AssetsBundle\AssetFile\AssetFile(array( | |
| 'asset_file_path' => $sAssetFilePath, | |
| 'asset_file_type' => $sAssetFileType | |
| )); | |
| } | |
| return $aAssetFiles; | |
| } | |
| /** | |
| * @param \AssetsBundle\AssetFile\AssetFile $oAssetFile | |
| * @param \AssetsBundle\AssetFile\AssetFile $oSourceAssetFile | |
| * @return \AssetsBundle\AssetFile\AssetFile | |
| * @throws \LogicException | |
| */ | |
| public function cacheAssetFile(\AssetsBundle\AssetFile\AssetFile $oAssetFile, \AssetsBundle\AssetFile\AssetFile $oSourceAssetFile = null) : \AssetsBundle\AssetFile\AssetFile | |
| { | |
| // Define source asset file | |
| if (!$oSourceAssetFile) { | |
| $oSourceAssetFile = $oAssetFile; | |
| } | |
| // Check that file need to be cached | |
| if ($this->isAssetFileCached($oSourceAssetFile)) { | |
| return $oAssetFile->setAssetFilePath($this->getAssetFileCachePath($oSourceAssetFile)); | |
| } | |
| // Retrieve asset file cache path | |
| $sCacheFilePath = $this->getAssetFileCachePath($oSourceAssetFile); | |
| // Retrieve cache file directory path | |
| $sCacheFileDirPath = dirname($sCacheFilePath); | |
| if ($sCacheFileDirPath === '.') { | |
| throw new \LogicException('Asset file cache path "' . $sCacheFilePath . '" does not provide a parent directory'); | |
| } | |
| // Create directory if not exists | |
| if (!is_dir($sCacheFileDirPath)) { | |
| \Zend\Stdlib\ErrorHandler::start(\E_ALL); | |
| if (!mkdir($sCacheFileDirPath, $this->getOptions()->getDirectoriesPermissions(), true)) { | |
| throw new \RuntimeException('Error occured while creating directory "' . $sCacheFileDirPath . '"'); | |
| } | |
| if ($oException = \Zend\Stdlib\ErrorHandler::stop()) { | |
| throw new \RuntimeException('Error occured while creating directory "' . $sCacheFileDirPath . '"', $oException->getCode(), $oException); | |
| } | |
| } elseif (!is_writable($sCacheFileDirPath)) { | |
| \Zend\Stdlib\ErrorHandler::start(\E_ALL); | |
| if (!chmod($sCacheFileDirPath, $this->getOptions()->getDirectoriesPermissions())) { | |
| throw new \RuntimeException('Error occured while changing mode on directory "' . $sCacheFileDirPath . '"'); | |
| } | |
| \Zend\Stdlib\ErrorHandler::stop(true); | |
| } | |
| $bFileExists = file_exists($sCacheFilePath); | |
| // Cache remote asset file | |
| if ($oAssetFile->isAssetFilePathUrl()) { | |
| \Zend\Stdlib\ErrorHandler::start(\E_ALL); | |
| $oAssetFileFileHandle = fopen($oAssetFile->getAssetFilePath(), 'rb'); | |
| \Zend\Stdlib\ErrorHandler::stop(true); | |
| if (!$oAssetFileFileHandle) { | |
| throw new \LogicException('Unable to open asset file "' . $oAssetFile->getAssetFilePath() . '"'); | |
| } | |
| \Zend\Stdlib\ErrorHandler::start(\E_ALL); | |
| file_put_contents($sCacheFilePath, stream_get_contents($oAssetFileFileHandle)); | |
| \Zend\Stdlib\ErrorHandler::stop(true); | |
| \Zend\Stdlib\ErrorHandler::start(\E_ALL); | |
| fclose($oAssetFileFileHandle); | |
| \Zend\Stdlib\ErrorHandler::stop(true); | |
| } | |
| // Cache local asset file | |
| else { | |
| \Zend\Stdlib\ErrorHandler::start(\E_ALL); | |
| $sAssetFilePath = $oAssetFile->getAssetFilePath(); | |
| if (!is_file($sAssetFilePath)) { | |
| throw new \LogicException('Asset file "' . $sAssetFilePath . '" does not exits'); | |
| } | |
| copy($sAssetFilePath, $sCacheFilePath); | |
| \Zend\Stdlib\ErrorHandler::stop(true); | |
| } | |
| if (!$bFileExists) { | |
| \Zend\Stdlib\ErrorHandler::start(\E_ALL); | |
| chmod($sCacheFilePath, $this->getOptions()->getFilesPermissions()); | |
| \Zend\Stdlib\ErrorHandler::stop(true); | |
| } | |
| return $oAssetFile->setAssetFilePath($sCacheFilePath); | |
| } | |
| /** | |
| * @param \AssetsBundle\AssetFile\AssetFile $oAssetFile | |
| * @return bool | |
| */ | |
| public function isAssetFileCached(\AssetsBundle\AssetFile\AssetFile $oAssetFile) : bool | |
| { | |
| if (file_exists($sAssetFileCachedPath = $this->getAssetFileCachePath($oAssetFile))) { | |
| \Zend\Stdlib\ErrorHandler::start(\E_ALL); | |
| // Can't retrieve last modified from url, don't reload it | |
| $bIsUpdated = (!($iLastModified = $oAssetFile->getAssetFileLastModified()) && $oAssetFile->isAssetFilePathUrl()) ? true : ($iLastModified && filemtime($sAssetFileCachedPath) >= $iLastModified); | |
| \Zend\Stdlib\ErrorHandler::stop(true); | |
| return $bIsUpdated; | |
| } | |
| return false; | |
| } | |
| /** | |
| * @param \AssetsBundle\AssetFile\AssetFile $oAssetFile | |
| * @return string | |
| */ | |
| public function sanitizeAssetFilePath(\AssetsBundle\AssetFile\AssetFile $oAssetFile) : string | |
| { | |
| return $oAssetFile->isAssetFilePathUrl() ? str_replace( | |
| array_merge(array('/'), $this->unwantedFilePathChars), | |
| '_', | |
| implode('/', array_slice(explode('/', preg_replace('/http:\/\/|https:\/\/|www./', '', $oAssetFile->getAssetFilePath())), 0, 1)) | |
| ) : $oAssetFile->getAssetFilePath(); | |
| } | |
| /** | |
| * @param \AssetsBundle\Service\ServiceOptions $oOptions | |
| * @return \AssetsBundle\AssetFile\AssetFilesCacheManager | |
| */ | |
| public function setOptions(\AssetsBundle\Service\ServiceOptions $oOptions) : \AssetsBundle\AssetFile\AssetFilesCacheManager | |
| { | |
| $this->options = $oOptions; | |
| return $this; | |
| } | |
| /** | |
| * @return \AssetsBundle\Service\ServiceOptions | |
| * @throws \LogicException | |
| */ | |
| public function getOptions() : \AssetsBundle\Service\ServiceOptions | |
| { | |
| if ($this->options instanceof \AssetsBundle\Service\ServiceOptions) { | |
| return $this->options; | |
| } | |
| throw new \LogicException( | |
| 'Property "options" expects an instance of "\AssetsBundle\Service\ServiceOptions", "'.( | |
| is_object($this->options) | |
| ? get_class($this->options) | |
| : gettype($this->options) | |
| ).'" defined' | |
| ); | |
| } | |
| } |