';
const MEMORY_FOR_PACKING = 67108864; // 64Mb
const FORCE_DISABLE_GZIP = false;
private $addrJsCacheFileInfo;
private $addrJsCacheFile;
private $addrCssCacheFileInfo;
private $addrCssCacheFile;
private $addrNumJsTrans;
private $addrNumCssTrans;
private $flgHtmlPack;
private $flgCssPack;
private $flgJsPack;
private $flgUseTransSystem;
private $gzipPostfix = '.gz';
private $pCallBackFunctionJS = NULL;
private $pCallBackFunctionCSS = NULL;
private $pFuncGetAddrJsPackFile = NULL;
private $pFuncGetAddrCssPackFile = NULL;
private $pGetPathJsFileFromUrl = NULL;
private $pGetPathCssFileFromUrl = NULL;
private $arrExeptions_js;
private $arrExeptionsNotAdd_js;
private $arrExeptions_css;
private $arrExeptionsNotAdd_css;
private $pFuncPreWriteCache;
private $pFuncPostWriteCache;
private $flgUseGZIP;
private $flgBuffering;
private $pPrepareEachFile = NULL;
// Только склеивать файлы
const ONLY_MERGE_CSS = false;
const ONLY_MERGE_JS = false;
// Перепаковывать каждый раз (удобно если = DEBUG_MODE)
const ALWAYS_INDEPENDENT_PACK = false;
const ALWAYS_REPACK = false;
private static function MakeDir($path)
{
if (is_dir($path))
{
return;
}
$path = explode("/", $path);
if (count($path) == 1)
{
$path = explode("\\", $path[0]);
}
$d = "";
foreach ($path as $dir)
{
$d .= $dir . "/";
if ($d != "/" && $d != "/home/" && !is_dir($d))
{
@mkdir($d, 0777);
@chmod($d, 0777);
}
}
}
private static function WriteFile($file, $b)
{
self::MakeDir(dirname($file));
fclose(fopen($file, "a+"));
$f = fopen($file,"r+");
fwrite($f, $b);
fclose($f);
@chmod($file, 0777);
}
private function HtmlPack($html)
{
return Minify_HTML::minify($html);
}
private function PrepareJsContent($jsContent)
{
if ($this->pCallBackFunctionJS)
{
$jsContent = call_user_func($this->pCallBackFunctionJS, $jsContent);
}
return $jsContent;
}
private function PrepareCssContent($cssContent)
{
if ($this->pCallBackFunctionCSS)
{
$cssContent = call_user_func($this->pCallBackFunctionCSS, $cssContent);
}
return $cssContent;
}
private static function ReadFile($file, $flag = false)
{
$file = trim($file);
if (!strlen(trim($file)))
{
return false;
}
if ($flag && $file[0] == "/")
{
$file = $_SERVER["DOCUMENT_ROOT"] . $file;
}
$b = false;
if (strstr($file, "http://") == $file)
{
$f = fopen($file, "r");
while (!feof($f))
{
$b .= fread($f, 1024);
}
}
elseif (file_exists($file) && is_readable($file))
{
$f = fopen($file, "r");
$b = fread($f, filesize($file));
}
if (isset($f) && $f)
{
fclose($f);
}
return $b;
}
private function GetTrans($type)
{
if (file_exists($type == 'js' ? $this->addrNumJsTrans : $this->addrNumCssTrans))
{
$number = self::ReadFile($type == 'js' ? $this->addrNumJsTrans : $this->addrNumCssTrans);
return (int)$number;
}
else
{
return 0;
}
}
private function IncTrans($type)
{
self::WriteFile($type == 'js' ? $this->addrNumJsTrans : $this->addrNumCssTrans, time());
}
private function UnickList($arrFiles)
{
$arrRes = array();
foreach ($arrFiles as $file)
{
$arrRes[$file['addr']] = $file;
}
return array_values($arrRes);
}
public static function MemInBytes($mem)
{
$lastSymbol = strtoupper(substr($mem, -1));
$mem = in_array($lastSymbol, array('K', 'M', 'G')) ? substr($mem, 0, -1) : $mem;
switch ($lastSymbol)
{
case 'K':
$mem *= 1024;
break;
case 'M':
$mem *= (1024 * 1024);
break;
case 'G':
$mem *= (1024 * 1024 * 1024);
break;
};
return $mem;
}
private function WriteDataInCache($type, $arrFiles, $arrExeptions = array())
{
// Устанавливаем памяти на выполнение побольше т.к. будет идти создание pack-версии js/css
global $g_config;
$memInBytes = self::MemInBytes($g_config['phpIni']['memory_limit']);
if (self::MEMORY_FOR_PACKING > $memInBytes)
{
ini_set('memory_limit', self::MEMORY_FOR_PACKING);
}
// Устанавлиаем время выполнения скрипта больше той что установлено сейчас на 30 секунд
ini_set('max_execution_time', intval(intval($g_config['phpIni']['max_execution_time']) + 30));
if ($this->pFuncPreWriteCache)
{
call_user_func($this->pFuncPreWriteCache);
}
if ($this->flgUseTransSystem)
{
$this->IncTrans($type);
}
if ($this->flgUseTransSystem)
{
$ext = '.' . $type;
$t = str_replace($ext, self::GetTrans($type) . $ext, $type == 'js' ? $this->addrJsCacheFile : $this->addrCssCacheFile);
$type == 'js' ? $this->addrJsCacheFile = $t : $this->addrCssCacheFile = $t;
}
// Удаляем файлы из списка для запаковки которых уже нет на сервере
foreach ($arrFiles as $k => $v)
{
if (!file_exists($v['addr']))
{
unset($arrFiles[$k]);
}
}
$packInfoAboutFiles = serialize($arrFiles);
self::WriteFile($type == 'js' ? $this->addrJsCacheFileInfo : $this->addrCssCacheFileInfo, $packInfoAboutFiles);
foreach ($arrExeptions as $k => $v)
{
$arrExeptions[$k] = call_user_func($type == 'js' ? $this->pGetPathJsFileFromUrl : $this->pGetPathCssFileFromUrl , $v);
}
$endl = $type == 'js' ? ";" : '';
$fullExeptionContent = "";
$fullContent = "";
foreach ($arrFiles as $file)
{
$file = $file['addr'];
$curContent = file_get_contents($file);
// Если стоит обработка каждого файла перед добавлением в сжатый архив
if ($this->pPrepareEachFile)
{
$curContent = call_user_func($this->pPrepareEachFile, $curContent, $file, $type);
}
if (in_array($file, $arrExeptions))
{
$fullExeptionContent .= $curContent . $endl;
}
else
{
$fullContent .= $curContent . $endl;
}
}
if ($type == 'js')
{
if (!self::ONLY_MERGE_JS)
{
$packed = JSMin::minify($fullContent);
}
else
{
$packed = $fullContent;
}
}
else
{
$less = new lessc();
$less->importDir = BASEPATH . 'i/css/';
$fullContent = $less->parse($fullContent);
if (!self::ONLY_MERGE_CSS)
{
$cssPacker = new CssPacker($fullContent);
$packed = $cssPacker->Pack();
}
else
{
$packed = $fullContent;
}
}
$packed = $type == 'js' ? ($packed . $fullExeptionContent) : ($fullExeptionContent . $packed);
$packed = $type == 'js' ? $this->PrepareJsContent($packed) : $this->PrepareCssContent($packed); // Дополнительно обработать контент пользовательской функцией (если есть)
self::WriteFile($type == 'js' ? $this->addrJsCacheFile : $this->addrCssCacheFile, $packed);
self::WriteFile(($type == 'js' ? $this->addrJsCacheFile : $this->addrCssCacheFile) . $this->gzipPostfix, gzencode($packed, self::COEF_GZIP_COMPRESS));
if ($this->pFuncPostWriteCache)
{
call_user_func($this->pFuncPostWriteCache);
}
}
/**
* Если массив now содержиться в storage то перепаковывать не надо т.к. все запакованные данные есть
*/
private function NeedRePack($storage, $now)
{
if (self::ALWAYS_REPACK) return true;
$ret = false;
foreach ($now as $v)
{
if (!in_array($v, $storage))
{
$ret = true;
break;
}
}
return $ret;
}
private function JsPack($html)
{
$arrExeptions = $this->arrExeptions_js;
$arrExeptionsNotAdd = $this->arrExeptionsNotAdd_js;
$s = preg_replace('//Uis', '', $html);
$m = array();
preg_match("~
(.*?)~is", $s, $m);
if (empty($m))
{
return $html;
}
$s = $m[1];
$m = array();
preg_match_all("~~", $s, $m);
if (empty($m))
{
return $html;
}
$arrJsFiles = array();
foreach ($m[1] as $k => $jsAddr)
{
$jsAddr = $jsAddr . ".js";
if (in_array($jsAddr, $arrExeptionsNotAdd))
{
unset($m[1][$k]);
continue;
}
$html = str_ireplace($m[0][$k], "", $html);
$jsAddr = call_user_func($this->pGetPathJsFileFromUrl, $jsAddr);
$whenModify = filemtime($jsAddr);
$arrJsFiles[] = array('time' => $whenModify, 'addr' => $jsAddr);
}
$arrJsFiles = $this->UnickList($arrJsFiles);
if (file_exists($this->addrJsCacheFileInfo))
{
$strInfoJsFiles = file_get_contents($this->addrJsCacheFileInfo);
$arrInfoJsFiles = unserialize($strInfoJsFiles);
if ($this->NeedRePack($arrInfoJsFiles, $arrJsFiles))
{
if (self::ALWAYS_INDEPENDENT_PACK)
{
$arrInfoJsFiles = array();
}
$arrAllJsFiles = self::AUnique(self::AMerge($arrInfoJsFiles, $arrJsFiles));
self::WriteDataInCache('js', $arrAllJsFiles, $arrExeptions);
}
else
{
if ($this->flgUseTransSystem)
{
$this->addrJsCacheFile = str_replace(".js", self::GetTrans('js') . ".js", $this->addrJsCacheFile);
}
}
}
else
{
self::WriteDataInCache('js', $arrJsFiles , $arrExeptions);
}
$includeFileAddr = ($this->flgBuffering && $this->flgUseGZIP) ? $this->addrJsCacheFile.$this->gzipPostfix : $this->addrJsCacheFile;
$inc = call_user_func($this->pFuncGetAddrJsPackFile, $includeFileAddr);
$html = str_ireplace(self::TAG_EXTRAPACKER, self::TAG_EXTRAPACKER . '', $html);
return $html;
}
private static function AUnique($arr)
{
$a = array();
foreach ($arr as $v)
{
$a[$v['addr']] = $v;
}
return array_values($a);
}
// По сути тот же самый array_merge
private static function AMerge()
{
$arrs = func_get_args(); // Массивы которые нужно слить в один
$eaxA = array(); // Результирующий массив
$eaxAddrsOnly = array(); // Массив только адресов к файлам (нужен просто для более локаничного вычисления)
foreach ($arrs as $arr)
{
foreach ($arr as $canBeElem)
{
// Если текущий файл уже был добавлен
if (in_array($canBeElem['addr'], $eaxAddrsOnly))
{
// То находим его и проверяем, если время его изменения в новом массиве больше старого то заменяем, иначе ничего не делаем
foreach ($eaxA as $k => $v)
{
if ($v['addr'] == $canBeElem['addr'])
{
if ($v['time'] < $canBeElem['time'])
{
$eaxA[$k] = $canBeElem;
}
}
}
}
// Если же этого файла нет в массиве то добавляем его следущим
else
{
$eaxA[] = $canBeElem;
$eaxAddrsOnly[] = $canBeElem['addr'];
}
}
}
return $eaxA;
}
private function CssPack($html)
{
$arrExeptions = $this->arrExeptions_css;
$arrExeptionsNotAdd = $this->arrExeptionsNotAdd_css;
$s = preg_replace('//Uis', '', $html);
$m = array();
preg_match("~(.*?)~is", $s, $m);
if (empty($m))
{
return $html;
}
$s = $m[1];
$m = array();
preg_match_all("~~", $s, $m);
if (empty($m))
{
return $html;
}
$arrCssFiles = array();
foreach ($m[1] as $k => $cssAddr)
{
$ext = $m[2][$k];
$cssAddr .= ".{$ext}";
if (in_array($cssAddr, $arrExeptionsNotAdd))
{
unset($m[1][$k]);
continue;
}
$html = str_ireplace($m[0][$k], "", $html);
$cssAddr = call_user_func($this->pGetPathCssFileFromUrl, $cssAddr);
$whenModify = filemtime($cssAddr);
$arrCssFiles[] = array('time' => $whenModify, 'addr' => $cssAddr);
}
$arrCssFiles = $this->UnickList($arrCssFiles);
if (file_exists($this->addrCssCacheFileInfo))
{
$strInfoCssFiles = file_get_contents($this->addrCssCacheFileInfo);
$arrInfoCssFiles = unserialize($strInfoCssFiles);
if ($this->NeedRePack($arrInfoCssFiles, $arrCssFiles))
{
if (self::ALWAYS_INDEPENDENT_PACK)
{
$arrInfoCssFiles = array();
}
$arrAllCssFiles = self::AUnique(self::AMerge($arrInfoCssFiles, $arrCssFiles));
self::WriteDataInCache('css', $arrAllCssFiles, $arrExeptions);
}
else
{
if ($this->flgUseTransSystem)
{
$this->addrCssCacheFile = str_replace(".css", self::GetTrans('css') . ".css", $this->addrCssCacheFile);
}
}
}
else
{
self::WriteDataInCache('css', $arrCssFiles , $arrExeptions);
}
$includeFileAddr = ($this->flgBuffering && $this->flgUseGZIP) ? $this->addrCssCacheFile.$this->gzipPostfix : $this->addrCssCacheFile;
$inc = call_user_func($this->pFuncGetAddrCssPackFile, $includeFileAddr);
$html = str_ireplace(self::TAG_EXTRAPACKER, self::TAG_EXTRAPACKER . '', $html);
return $html;
}
private function CanUseGZIP()
{
if (self::FORCE_DISABLE_GZIP)
{
return false;
}
$httpAcceptEncoding = isset($_SERVER['HTTP_ACCEPT_ENCODING']) ? $_SERVER['HTTP_ACCEPT_ENCODING'] : '';
$hasGzip = strstr($httpAcceptEncoding, 'gzip') !== false && extension_loaded('zlib');
$canEncoding = true;
if (!isset($_SERVER['HTTP_USER_AGENT']))
{
return false;
}
$matches = array();
if (stripos($_SERVER['HTTP_USER_AGENT'], 'safari') === false)
{
if (!strstr($_SERVER['HTTP_USER_AGENT'], 'Opera')
&&
preg_match('/^Mozilla\/4\.0 \(compatible; MSIE ([0-9]\.[0-9])/i', $_SERVER['HTTP_USER_AGENT'], $matches))
{
if (isset($matches[1]))
{
$version = floatval($matches[1]);
if ($version < 6)
{
$canEncoding = false;
}
if ($version == 6 && !strstr($_SERVER['HTTP_USER_AGENT'], 'EV1'))
{
$canEncoding = false;
}
}
}
}
else
{
$canEncoding = false;
}
return $canEncoding && $hasGzip;
}
/**
* Функция запаковки контента в зависимости от параметров пакера
*
* @param string html-контент документа в котором содержится head с подключенными js-ками и css-ами
*/
public function Pack($html)
{
// Если нету тега для замены ExtraPacker-ом то просто вернуть контент
if (strpos($html, self::TAG_EXTRAPACKER) === false)
{
return $html;
}
if ($this->flgJsPack)
{
$html = $this->JsPack($html);
}
if ($this->flgCssPack)
{
$html = $this->CssPack($html);
}
if ($this->flgHtmlPack)
{
$html = $this->HtmlPack($html);
}
return $html;
}
/**
* Конструктор объекта
*
* @param pointer $pGetPathJsFileFromUrl - ссылка на функцию которая получит из url в src js-ки физический путь на диске к этому файлу
* @param pointer $pGetPathCssFileFromUrl - ссылка на функцию которая получит из url в href css-ки физический путь на диске к этому файлу
* @param pointer $pFuncGetAddrJsPackFile - ссылка на фунцию которая получит из path на диске спакованного js-файла url который подставится в новый src спакованной js-ки
* @param pointer $pFuncGetAddrCssPackFile - ссылка на фунцию которая получит из path на диске спакованного css-файла url который подставится в новый href спакованной css-ки
* @param pointer $pFuncPreWriteCache - указатель на функцию выполняемую до начала работы по созданию нового кеш-файла (обычно блокировка сайта)
* @param pointer $pFuncPostWriteCache - указатель на функцию выполняемую по завершению работы и записи кеш-файла (обычно разблокировка сайта)
* @param string $addrJsCacheFileInfo - путь к временному файлу с информацией о спакованных js файлах в текущей транзакции
* @param string $addrJsCacheFile - путь к временному кеш файлу спакованных js-ок (GZIP будут с таким же именем только префикс из настроек добавится)
* @param string $addrCssCacheFileInfo - путь к временному файлу с информацией о спакованных css файлах в текущей транзакции
* @param string $addrCssCacheFile - путь к временному кеш файлу спакованных css-ок (GZIP будут с таким же именем только префикс из настроек добавится)
* @param bool $flgHtmlPack - флаг включения html-запаковки (могут быть проблемы если используете js-подстветки кода сайте т.к. в начале удалятся пробелы и табуляция)
* @param bool $flgCssPack - флаг включения css-запаковки
* @param bool $flgJsPack - флаг включения js-запаковки
* @param array $arrExeptions_js - массив исключений 1-го порядка (url-ы как и src), данные файлы прибавятся склеенному в конец, но не будут поковаться
* @param array $arrExeptionsNotAdd_js - массив исключений 2-го порядка - вообще не добавятся в запокованный файл
* @param array $arrExeptions_css - массив исключений 1-го порядка (url-ы как и href), данные файлы прибавятся склеенному в конец, но не будут поковаться
* @param array $arrExeptionsNotAdd_css - массив исключений 2-го порядка - вообще не добавятся в запокованный файл
* @param bool $flgUseTransSystem - использоватьс систему транзакций (в url запакованного файла ставится № говорящий о текущей транзакции)
* @param string $addrNumJsTrans - путь к файлу содержащий № js транзакций
* @param string $addrNumCssTrans - путь к файлу содержащий № css транзакций
* @param bool $flgBuffering - включить авто-определение GZIP буферизации для браузеров
*/
public function __construct(
$pGetPathJsFileFromUrl,
$pGetPathCssFileFromUrl,
$pFuncGetAddrJsPackFile,
$pFuncGetAddrCssPackFile,
$pFuncPreWriteCache = NULL,
$pFuncPostWriteCache = NULL,
$addrJsCacheFileInfo = '',
$addrJsCacheFile = '',
$addrCssCacheFileInfo = '',
$addrCssCacheFile = '',
$flgHtmlPack = true,
$flgCssPack = true,
$flgJsPack = true,
$arrExeptions_js = array(),
$arrExeptionsNotAdd_js = array(),
$arrExeptions_css = array(),
$arrExeptionsNotAdd_css = array(),
$flgUseTransSystem = true,
$addrNumJsTrans = '',
$addrNumCssTrans = '',
$flgBuffering = true,
$pPrepareEachFile = NULL,
$pCallBackFunctionCSS = NULL,
$pCallBackFunctionJS = NULL
)
{
$this->pGetPathJsFileFromUrl = $pGetPathJsFileFromUrl;
$this->pGetPathCssFileFromUrl = $pGetPathCssFileFromUrl;
$this->pFuncGetAddrJsPackFile = $pFuncGetAddrJsPackFile;
$this->pFuncGetAddrCssPackFile = $pFuncGetAddrCssPackFile;
$this->pFuncPreWriteCache = $pFuncPreWriteCache;
$this->pFuncPostWriteCache = $pFuncPostWriteCache;
$defaultDir = str_replace("\\", "/", dirname(__FILE__)) . '/tmp/';
$this->addrJsCacheFileInfo = $addrJsCacheFileInfo ? $addrJsCacheFileInfo : ($defaultDir . "js/cache/info.txt");
$this->addrJsCacheFile = $addrJsCacheFile ? $addrJsCacheFile : ($defaultDir . "js/cache/js.js");
$this->addrCssCacheFileInfo = $addrCssCacheFileInfo ? $addrCssCacheFileInfo : ($defaultDir . "css/cache/info.txt");
$this->addrCssCacheFile = $addrCssCacheFile ? $addrCssCacheFile : ($defaultDir . "css/cache/css.css");
$this->flgHtmlPack = $flgHtmlPack;
$this->flgCssPack = $flgCssPack;
$this->flgJsPack = $flgJsPack;
$this->arrExeptions_js = $arrExeptions_js;
$this->arrExeptionsNotAdd_js = $arrExeptionsNotAdd_js;
$this->arrExeptions_css = $arrExeptions_css;
$this->arrExeptionsNotAdd_css = $arrExeptionsNotAdd_css;
$this->flgUseTransSystem = $flgUseTransSystem;
$this->addrNumJsTrans = $addrNumJsTrans ? $addrNumJsTrans : ($defaultDir . 'js/cache/trans.txt');
$this->addrNumCssTrans = $addrNumCssTrans ? $addrNumCssTrans : ($defaultDir . 'css/cache/trans.txt');
$this->flgBuffering = $flgBuffering;
$this->flgUseGZIP = self::CanUseGZIP();
$this->pPrepareEachFile = $pPrepareEachFile;
$this->pCallBackFunctionCSS = $pCallBackFunctionCSS;
$this->pCallBackFunctionJS = $pCallBackFunctionJS;
}
};
?>