432 lines
15 KiB
PHP
432 lines
15 KiB
PHP
<?php
|
|
/**
|
|
* Zira project.
|
|
* assets.php
|
|
* (c)2016 https://github.com/ziracms/zira
|
|
*/
|
|
|
|
namespace Zira;
|
|
|
|
class Assets {
|
|
const CACHE_LIFETIME = 86400;
|
|
const CSS_ASSETS_CACHE_FILE = '.css.cache';
|
|
const JS_ASSETS_CACHE_FILE = '.js.cache';
|
|
const CSS_CONTENT_ASSETS_CACHE_FILE = '.css.content.cache';
|
|
const JS_CONTENT_ASSETS_CACHE_FILE = '.js.content.cache';
|
|
const CSS_ASSETS_GZIP_CACHE_FILE = '.css.gz.cache';
|
|
const JS_ASSETS_GZIP_CACHE_FILE = '.js.gz.cache';
|
|
const CSS_SCRIPT = 'assets/css/index.php';
|
|
const JS_SCRIPT = 'assets/js/index.php';
|
|
const CSS_SCRIPT_CLEAN = 'assets/css/cache';
|
|
const JS_SCRIPT_CLEAN = 'assets/js/cache';
|
|
const LOCK_FILE = 'assetlock';
|
|
protected static $_lock_handler;
|
|
|
|
protected static $_active = false;
|
|
protected static $_gzip = null;
|
|
protected static $_css_mtime = null;
|
|
protected static $_js_mtime = null;
|
|
|
|
protected static $_css_assets = array(
|
|
'bootstrap.min.css',
|
|
'bootstrap-datetimepicker.min.css',
|
|
'bootstrap-theme.min.css',
|
|
'cropper.css',
|
|
'lightbox.css',
|
|
'bxslider.css',
|
|
'slider3d.css',
|
|
'carousel.slider.css',
|
|
'slick.css',
|
|
'jplayer.css',
|
|
'zira.css'
|
|
);
|
|
|
|
protected static $_theme_css = 'main.css';
|
|
|
|
protected static $_js_assets = array(
|
|
'jquery.min.js',
|
|
'bootstrap.min.js',
|
|
'moment.min.js',
|
|
'bootstrap-datetimepicker.min.js',
|
|
'cropper.js',
|
|
'bxslider.min.js',
|
|
'slider3d.js',
|
|
'carousel.slider.js',
|
|
'slick.min.js',
|
|
'md5.js',
|
|
'parse.js',
|
|
'upload.inc.js',
|
|
'upload.js',
|
|
'zira.js'
|
|
);
|
|
|
|
protected static $_js_assets_if_in_body = array(
|
|
'lightbox.min.js'
|
|
);
|
|
|
|
protected static $_css_assets_contents = array();
|
|
protected static $_js_assets_contents = array();
|
|
|
|
public static function isAssetsCacheFile($file) {
|
|
if ($file == self::CSS_ASSETS_CACHE_FILE) return true;
|
|
if ($file == self::JS_ASSETS_CACHE_FILE) return true;
|
|
if ($file == self::CSS_CONTENT_ASSETS_CACHE_FILE) return true;
|
|
if ($file == self::JS_CONTENT_ASSETS_CACHE_FILE) return true;
|
|
if ($file == self::CSS_ASSETS_GZIP_CACHE_FILE) return true;
|
|
if ($file == self::JS_ASSETS_GZIP_CACHE_FILE) return true;
|
|
return false;
|
|
}
|
|
|
|
public static function isAssetsLockFile($file) {
|
|
return $file == '.' . self::LOCK_FILE . '.cache';
|
|
}
|
|
|
|
public static function getCSSAssets() {
|
|
return self::$_css_assets;
|
|
}
|
|
|
|
public static function getJSAssets() {
|
|
return self::$_js_assets;
|
|
}
|
|
|
|
public static function registerCSSAsset($asset) {
|
|
self::$_css_assets [] = $asset;
|
|
}
|
|
|
|
public static function registerJSAsset($asset) {
|
|
self::$_js_assets []= $asset;
|
|
}
|
|
|
|
public static function registerCSSAssetContent($content, $module='zira') {
|
|
self::$_css_assets_contents[$module] = $content;
|
|
}
|
|
|
|
public static function registerJSAssetContent($content, $module='zira') {
|
|
self::$_js_assets_contents[$module]= $content;
|
|
}
|
|
|
|
public static function unregisterCSSAsset($asset) {
|
|
$index = array_search($asset, self::$_css_assets);
|
|
unset(self::$_css_assets[$index]);
|
|
}
|
|
|
|
public static function unregisterJSAsset($asset) {
|
|
$index = array_search($asset, self::$_js_assets);
|
|
unset(self::$_js_assets[$index]);
|
|
}
|
|
|
|
public static function unregisterCSSAssetContent($module) {
|
|
self::$_css_assets_contents[$module] = null;
|
|
}
|
|
|
|
public static function unregisterJSAssetContent($module) {
|
|
self::$_js_assets_contents[$module]= null;
|
|
}
|
|
|
|
public static function mergeCSS() {
|
|
self::$_css_mtime = null;
|
|
if (!is_writable(ROOT_DIR . DIRECTORY_SEPARATOR . CACHE_DIR)) throw new \Exception('Cache dir is not writable');
|
|
$url = CACHE_DIR . DIRECTORY_SEPARATOR . self::CSS_ASSETS_CACHE_FILE;
|
|
$css_file = ROOT_DIR . DIRECTORY_SEPARATOR . $url;
|
|
|
|
$f=fopen($css_file,'wb');
|
|
if (!$f) throw new \Exception('Failed to open file');
|
|
foreach(self::$_css_assets as $css_asset) {
|
|
$path = ROOT_DIR . DIRECTORY_SEPARATOR . ASSETS_DIR . DIRECTORY_SEPARATOR . CSS_DIR . DIRECTORY_SEPARATOR . $css_asset;
|
|
if (!file_exists($path) || !is_readable($path)) throw new \Exception('Asset '.$css_asset.' not found');
|
|
$data = '/** '.$css_asset.' **/'."\r\n\r\n";
|
|
$content = file_get_contents($path);
|
|
while(strpos($content, '../../')!==false) {
|
|
$content = str_replace('../../', '../', $content);
|
|
}
|
|
$data .= $content;
|
|
$data .= "\r\n\r\n";
|
|
fwrite($f, $data);
|
|
}
|
|
|
|
$theme_css = self::getThemeCSS();
|
|
fwrite($f, $theme_css);
|
|
|
|
fclose($f);
|
|
|
|
View::registerRenderHook(get_called_class(), 'mergeCSSContent');
|
|
}
|
|
|
|
public static function mergeCSSContent($lock=true) {
|
|
if (count(self::$_css_assets_contents)>0) {
|
|
if ($lock && !self::lock(true)) return false;
|
|
|
|
$content_url = CACHE_DIR . DIRECTORY_SEPARATOR . self::CSS_CONTENT_ASSETS_CACHE_FILE;
|
|
$css_content_file = ROOT_DIR . DIRECTORY_SEPARATOR . $content_url;
|
|
|
|
$cf=fopen($css_content_file,'wb');
|
|
if ($cf) {
|
|
foreach(self::$_css_assets_contents as $module=>$content) {
|
|
if (!in_array($module, Config::get('modules')) || !$content) continue;
|
|
$data = '/** extra css **/'."\r\n\r\n";
|
|
$data .= $content;
|
|
$data .= "\r\n\r\n";
|
|
fwrite($cf, $data);
|
|
}
|
|
fclose($cf);
|
|
}
|
|
|
|
$gzip_cache = ROOT_DIR . DIRECTORY_SEPARATOR . CACHE_DIR . DIRECTORY_SEPARATOR . self::CSS_ASSETS_GZIP_CACHE_FILE;
|
|
if (file_exists($gzip_cache)) {
|
|
@unlink($gzip_cache);
|
|
}
|
|
|
|
if($lock) self::unlock();
|
|
}
|
|
}
|
|
|
|
public static function getThemeCSS() {
|
|
$path = ROOT_DIR . DIRECTORY_SEPARATOR . THEMES_DIR . DIRECTORY_SEPARATOR . View::getTheme() . DIRECTORY_SEPARATOR . ASSETS_DIR . DIRECTORY_SEPARATOR . CSS_DIR . DIRECTORY_SEPARATOR . self::$_theme_css;
|
|
if (!file_exists($path) || !is_readable($path)) return;
|
|
$data = '/** '.self::$_theme_css.' **/'."\r\n\r\n";
|
|
$content = file_get_contents($path);
|
|
while(strpos($content, '../')!==false) {
|
|
$content = str_replace('../', rtrim(BASE_URL,'/') . '/' .THEMES_DIR.'/'.View::getTheme().'/'.ASSETS_DIR.'/', $content);
|
|
}
|
|
$data .= $content;
|
|
$data .= "\r\n\r\n";
|
|
|
|
return $data;
|
|
}
|
|
|
|
public static function mergeJS() {
|
|
self::$_js_mtime = null;
|
|
if (!is_writable(ROOT_DIR . DIRECTORY_SEPARATOR . CACHE_DIR)) throw new \Exception('Cache dir is not writable');
|
|
$url = CACHE_DIR . DIRECTORY_SEPARATOR . self::JS_ASSETS_CACHE_FILE;
|
|
$js_file = ROOT_DIR . DIRECTORY_SEPARATOR . $url;
|
|
|
|
$f=fopen($js_file,'wb');
|
|
if (!$f) throw new \Exception('Failed to open file');
|
|
foreach(self::$_js_assets as $js_asset) {
|
|
$path = ROOT_DIR . DIRECTORY_SEPARATOR . ASSETS_DIR . DIRECTORY_SEPARATOR . JS_DIR . DIRECTORY_SEPARATOR . $js_asset;
|
|
if (!file_exists($path) || !is_readable($path)) throw new \Exception('Asset '.$js_asset.' not found');
|
|
$data = '/** '.$js_asset.' **/'."\r\n\r\n";
|
|
$data .= file_get_contents($path);
|
|
$data .= "\r\n\r\n";
|
|
fwrite($f, $data);
|
|
}
|
|
|
|
fclose($f);
|
|
|
|
View::registerRenderHook(get_called_class(), 'mergeJSContent');
|
|
}
|
|
|
|
public static function mergeJSContent($lock=true) {
|
|
if (count(self::$_js_assets_contents)>0) {
|
|
if ($lock && !self::lock(true)) return false;
|
|
|
|
$content_url = CACHE_DIR . DIRECTORY_SEPARATOR . self::JS_CONTENT_ASSETS_CACHE_FILE;
|
|
$js_content_file = ROOT_DIR . DIRECTORY_SEPARATOR . $content_url;
|
|
|
|
$cf=fopen($js_content_file,'wb');
|
|
if ($cf) {
|
|
foreach(self::$_js_assets_contents as $module=>$content) {
|
|
if (!in_array($module, Config::get('modules')) || !$content) continue;
|
|
$data = '/** extra js **/'."\r\n\r\n";
|
|
$data .= $content;
|
|
$data .= "\r\n\r\n";
|
|
fwrite($cf, $data);
|
|
}
|
|
fclose($cf);
|
|
}
|
|
|
|
$gzip_cache = ROOT_DIR . DIRECTORY_SEPARATOR . CACHE_DIR . DIRECTORY_SEPARATOR . self::JS_ASSETS_GZIP_CACHE_FILE;
|
|
if (file_exists($gzip_cache)) {
|
|
@unlink($gzip_cache);
|
|
}
|
|
|
|
if ($lock) self::unlock();
|
|
}
|
|
}
|
|
|
|
public static function merge($lock=true) {
|
|
try {
|
|
if ($lock && !self::lock(true)) return false;
|
|
self::mergeCSS();
|
|
self::mergeJS();
|
|
if ($lock) self::unlock();
|
|
return self::isCached();
|
|
} catch(\Exception $e) {
|
|
if ($lock) self::unlock();
|
|
return false;
|
|
}
|
|
}
|
|
|
|
public static function getCSSMTime() {
|
|
if (self::$_css_mtime!==null) return self::$_css_mtime;
|
|
$url = CACHE_DIR . DIRECTORY_SEPARATOR . self::CSS_ASSETS_CACHE_FILE;
|
|
$file = ROOT_DIR . DIRECTORY_SEPARATOR . $url;
|
|
$mtime = @filemtime($file);
|
|
self::$_css_mtime = $mtime;
|
|
return $mtime;
|
|
}
|
|
|
|
public static function getJSMTime() {
|
|
if (self::$_js_mtime!==null) return self::$_js_mtime;
|
|
$url = CACHE_DIR . DIRECTORY_SEPARATOR . self::JS_ASSETS_CACHE_FILE;
|
|
$file = ROOT_DIR . DIRECTORY_SEPARATOR . $url;
|
|
$mtime = @filemtime($file);
|
|
self::$_js_mtime = $mtime;
|
|
return $mtime;
|
|
}
|
|
|
|
public static function isCSSExpired() {
|
|
$mtime = self::getCSSMTime();
|
|
if (!$mtime) return true;
|
|
return time()-$mtime>self::CACHE_LIFETIME;
|
|
}
|
|
|
|
public static function isJSExpired() {
|
|
$mtime = self::getJSMTime();
|
|
if (!$mtime) return true;
|
|
return time()-$mtime>self::CACHE_LIFETIME;
|
|
}
|
|
|
|
public static function isCSSCached() {
|
|
$url = CACHE_DIR . DIRECTORY_SEPARATOR . self::CSS_ASSETS_CACHE_FILE;
|
|
$css_file = ROOT_DIR . DIRECTORY_SEPARATOR . $url;
|
|
return file_exists($css_file) && is_readable($css_file) && filesize($css_file)>0;
|
|
}
|
|
|
|
public static function isJSCached() {
|
|
$url = CACHE_DIR . DIRECTORY_SEPARATOR . self::JS_ASSETS_CACHE_FILE;
|
|
$js_file = ROOT_DIR . DIRECTORY_SEPARATOR . $url;
|
|
return file_exists($js_file) && is_readable($js_file) && filesize($js_file)>0;
|
|
}
|
|
|
|
public static function isCached() {
|
|
return self::isCSSCached() && self::isJSCached();
|
|
}
|
|
|
|
public static function isCachedAndNotExpired() {
|
|
if (defined('DEBUG') && DEBUG && !View::isAjax()) return false;
|
|
if (!self::lock()) return false;
|
|
$isActive = self::isCached() && !self::isCSSExpired() && !self::isJSExpired();
|
|
self::unlock();
|
|
return $isActive;
|
|
}
|
|
|
|
public static function setActive($active) {
|
|
self::$_active = (bool)$active;
|
|
}
|
|
|
|
public static function isActive() {
|
|
return self::$_active;
|
|
}
|
|
|
|
public static function isMergedCSS($file) {
|
|
return in_array($file, self::$_css_assets);
|
|
}
|
|
|
|
public static function isMergedJS($file) {
|
|
return in_array($file, self::$_js_assets);
|
|
}
|
|
|
|
public static function getCSSURL() {
|
|
if (Config::get('clean_url')) {
|
|
$url = Helper::baseUrl(self::CSS_SCRIPT_CLEAN);
|
|
} else {
|
|
$url = Helper::baseUrl(self::CSS_SCRIPT);
|
|
}
|
|
$t = self::getCSSMTime();
|
|
if (!$t) $t = time();
|
|
$q = '?t='.(intval(self::isGzipEnabled())+1).$t;
|
|
return $url.$q;
|
|
}
|
|
|
|
public static function getJSURL() {
|
|
if (Config::get('clean_url')) {
|
|
$url = Helper::baseUrl(self::JS_SCRIPT_CLEAN);
|
|
} else {
|
|
$url = Helper::baseUrl(self::JS_SCRIPT);
|
|
}
|
|
$t = self::getJSMTime();
|
|
if (!$t) $t = time();
|
|
$q = '?t='.(intval(self::isGzipEnabled())+1).$t;
|
|
return $url.$q;
|
|
}
|
|
|
|
public static function addStyle() {
|
|
$attributes = array();
|
|
$attributes['rel'] = 'stylesheet';
|
|
$attributes['type'] = 'text/css';
|
|
$attributes['href'] = self::getCSSURL();
|
|
View::addHTML(Helper::tag_short('link', $attributes),View::VAR_STYLES);
|
|
}
|
|
|
|
public static function addScript() {
|
|
$attributes = array();
|
|
$attributes['src'] = self::getJSURL();
|
|
View::addHTML(Helper::tag('script', null, $attributes),View::VAR_SCRIPTS);
|
|
}
|
|
|
|
public static function init() {
|
|
// must be called even on ajax requests
|
|
if (INSERT_SCRIPTS_TO_BODY) {
|
|
self::$_js_assets = array_merge(self::$_js_assets, self::$_js_assets_if_in_body);
|
|
}
|
|
if (Config::get('caching') && self::isCachedAndNotExpired()) {
|
|
self::setActive(true);
|
|
} else if (Config::get('caching') && self::merge()) {
|
|
self::setActive(true);
|
|
} else {
|
|
self::setActive(false);
|
|
}
|
|
if (self::isActive() && !Request::isAjax()) {
|
|
self::addStyle();
|
|
self::addScript();
|
|
}
|
|
}
|
|
|
|
public static function isGzipEnabled() {
|
|
if (self::$_gzip!==null) return self::$_gzip;
|
|
$accept_encoding = '';
|
|
if (isset($_SERVER['HTTP_ACCEPT_ENCODING']) && preg_match( '/\b(x-gzip|gzip)\b/', strtolower($_SERVER['HTTP_ACCEPT_ENCODING']), $match)) {
|
|
$accept_encoding = $match[1];
|
|
}
|
|
if (empty($accept_encoding) && defined('FORCE_GZIP_ASSETS') && FORCE_GZIP_ASSETS) $accept_encoding = 'gzip';
|
|
if (Config::get('gzip') && function_exists('gzencode') && !@ini_get('zlib.output_compression') && !empty($accept_encoding)) {
|
|
self::$_gzip = true;
|
|
} else {
|
|
self::$_gzip = false;
|
|
}
|
|
return self::$_gzip;
|
|
}
|
|
|
|
public static function lock($write=false,$block=false) {
|
|
$cache_file = ROOT_DIR . DIRECTORY_SEPARATOR .
|
|
CACHE_DIR . DIRECTORY_SEPARATOR .
|
|
'.' . self::LOCK_FILE . '.cache';
|
|
|
|
self::$_lock_handler=@fopen($cache_file,'wb');
|
|
if (!self::$_lock_handler) return false;
|
|
|
|
if (!$block) {
|
|
$lock = $write ? (LOCK_EX | LOCK_NB) : (LOCK_SH | LOCK_NB);
|
|
} else {
|
|
$lock = $write ? LOCK_EX : LOCK_SH;
|
|
}
|
|
|
|
return flock(self::$_lock_handler, $lock);
|
|
}
|
|
|
|
public static function unlock() {
|
|
$cache_file = ROOT_DIR . DIRECTORY_SEPARATOR .
|
|
CACHE_DIR . DIRECTORY_SEPARATOR .
|
|
'.' . self::LOCK_FILE . '.cache';
|
|
|
|
if (!file_exists($cache_file)) return false;
|
|
if (!self::$_lock_handler) return false;
|
|
|
|
$result = flock(self::$_lock_handler, LOCK_UN);
|
|
|
|
fclose(self::$_lock_handler);
|
|
|
|
return $result;
|
|
}
|
|
} |