#18883478 Optimizer 개선

git-svn-id: http://xe-core.googlecode.com/svn/sandbox@7434 201d5d3c-b55e-5fd7-737f-ddc643e51545
This commit is contained in:
ngleader 2010-05-11 09:03:13 +00:00
parent 76c69c6405
commit bac20a9a40
2 changed files with 206 additions and 200 deletions

View file

@ -9,6 +9,7 @@
class Optimizer {
var $cache_path = "./files/cache/optimized/";
var $script_file = "./common/script.php?l=%s&t=%s";
/**
* @brief Constructor which check if a directory, 'optimized' exists in designated path. If not create a new one
@ -37,38 +38,37 @@
function getOptimizedFiles($source_files, $type = "js") {
if(!is_array($source_files) || !count($source_files)) return;
// $source_files의 역슬래쉬 경로를 슬래쉬로 변경 (윈도우즈 대비)
foreach($source_files as $key => $file){
$source_files[$key]['file'] = str_replace("\\","/",$file['file']);
}
// 관리자 설정시 설정이 되어 있지 않으면 패스
$db_info = Context::getDBInfo();
if($db_info->use_optimizer == 'N') return $this->_getOptimizedRemoved($source_files);
// 캐시 디렉토리가 없으면 실행하지 않음
if(!is_dir($this->cache_path)) return $this->_getOptimizedRemoved($source_files);
$files = array();
$db_info = Context::getDBInfo();
if($db_info->use_optimizer == 'N' || !is_dir($this->cache_path)) return $this->_getOptimizedRemoved($source_files);
if(!count($source_files)) return;
foreach($source_files as $file) {
if(!$file || !$file['file']) continue;
$files = array();
foreach($source_files as $key => $file) {
if(!$file || !$file['file'] || !file_exists($file['file'])) continue;
$file['file'] = $source_files[$key]['file'] = str_replace("\\","/",$file['file']);
if(empty($file['optimized']) || preg_match('/^https?:\/\//i', $file['file']) ) $files[] = $file;
else $targets[] = $file;
else{
$targets[] = $file;
}
}
if(!count($targets)) return $this->_getOptimizedRemoved($files);
$optimized_info = $this->getOptimizedInfo($targets);
$path = sprintf("%s%s", $this->cache_path, $optimized_info[0]);
$filename = sprintf("%s.%s.%s.php", $optimized_info[0], $optimized_info[1], $type);
$this->doOptimizedFile($path, $filename, $targets, $type);
array_unshift($files, array('file' => $path.'/'.$filename, 'media' => 'all'));
$list_file_hash = md5(join(' ', $targets));
$list_file = FileHandler::getRealPath($this->cache_path . $list_file_hash);
if(!file_exists($list_file)){
$str = '<?php $f=array();';
foreach($targets as $file) $str .= '$f[]="'. $file['file'] . '";';
$str .= ' return $f; ?>';
FileHandler::writeFile($list_file, $str);
}
array_unshift($files, array('file' => sprintf($this->script_file, $list_file_hash, $type) , 'media' => 'all'));
$files = $this->_getOptimizedRemoved($files);
if(!count($files)) return $files;
@ -78,7 +78,7 @@
foreach($files as $key => $val) {
$file = $val['file'];
if(substr($file,0,1)=='/' || strpos($file,'://')!==false) continue;
if($file{0} == '/' || strpos($file,'://')!==false) continue;
if(substr($file,0,2)=='./') $file = substr($file,2);
$file = $abpath.$file;
while(strpos($file,'/../')!==false) {
@ -86,184 +86,8 @@
}
$files[$key]['file'] = $file;
}
return $files;
}
/**
* @brief retrive a list of files from a given parameter
* @param[in] files a list containing files
**/
function _getOnlyFileList($files) {
foreach($files as $key => $val) $files[$key] = $val['file'];
return $files;
}
/**
* @brief method to generate a unique key from list of files along with a last modified date
* @param[in] files an array containing file names
**/
function getOptimizedInfo($files) {
// 개별 요소 파일이 갱신되었으면 새로 옵티마이징
$count = count($files);
$last_modified = 0;
for($i=0;$i<$count;$i++) {
$mtime = filemtime($files[$i]['file']);
if($last_modified < $mtime) $last_modified = $mtime;
}
$buff = implode("\n", $this->_getOnlyFileList($files));
return array(md5($buff), $last_modified);
}
/**
* @brief method that check if a valid cache file exits for a given filename. If not create new one.
* @param[in] path directory path of the cache files
* @param[in] filename a filename for cache files
* @param[in] targets
* @param[in] type a type of cache file
**/
function doOptimizedFile($path, $filename, $targets, $type) {
// 대상 파일이 있으면 그냥 패스~
if(file_exists($path.'/'.$filename)) return;
// 대상 파일이 없으면 hashed_filename으로 생성된 파일들을 모두 삭제
FileHandler::removeFilesInDir($path);
// 새로 캐시 파일을 생성
$this->makeOptimizedFile($path, $filename, $targets, $type);
}
/**
* @brief method produce a php code to merge css/js files, compress the resultant files and modified the HTML header accordingly.
* @param[in] path
* @param[in] filename a name of a resultant file
* @param[in] targets list of files to be used for the operation
* @param[in] type a type of file such as css or js
* @return NONE
**/
function makeOptimizedFile($path, $filename, $targets, $type) {
/**
* 실제 css나 js의 내용을 합친 것을 구함
**/
// 대상 파일의 내용을 구해오고 css 파일일 경우 url()내의 경로를 변경
$content_filename = substr($filename, 0, -4);
$file_object = FileHandler::openFile($path."/".$content_filename, "w");
if($type == 'css') $file_object->write('@charset "UTF-8";'."\n");
foreach($targets as $file) {
$str = FileHandler::readFile($file['file']);
$str = trim(Context::convertEncodingStr($str));
// css 일경우 background:url() 변경 / media 적용
if($type == 'css') {
$str = $this->replaceCssPath($file['file'], $str);
if($file['media'] != 'all') $str = '@media '.$file['media'].' {'."\n".$str."\n".'}';
}
$file_object->write($str);
$file_object->write("\n");
unset($str);
}
$file_object->close();
/**
* 캐시 타임을 제대로 이용하기 위한 헤더 파일 구함
**/
// 확장자별 content-type 체크
if($type == 'css') $content_type = 'text/css';
elseif($type == 'js') $content_type = 'text/javascript';
// 캐시를 위한 처리
$unique = crc32($content_filename);
$size = filesize($path.'/'.$content_file);
$mtime = filemtime($path.'/'.$content_file);
// js, css 파일을 php를 통해서 출력하고 이 출력시에 헤더값을 조작하여 캐싱과 압축전송이 되도록 함 (IE6는 CSS파일일 경우 gzip압축하지 않음)
$header_buff = '<?php
$content_filename = "'.$content_filename.'";
$mtime = '.$mtime.';
$cached = false;
$type = "'.$type.'";
if(isset($_SERVER["HTTP_IF_MODIFIED_SINCE"])) {
$time = strtotime(preg_replace("/;.*$/", "", $_SERVER["HTTP_IF_MODIFIED_SINCE"]));
if($mtime == $time) {
header("HTTP/1.1 304");
$cached = true;
}
}
if( preg_match("/MSIE 6.0/i",$_SERVER["HTTP_USER_AGENT"]) || strpos($_SERVER["HTTP_ACCEPT_ENCODING"], "gzip")===false || !function_exists("ob_gzhandler") ) {
$size = filesize($content_filename);
} else {
$f = fopen($content_filename,"r");
$buff = fread($f, filesize($content_filename));
fclose($f);
$buff = ob_gzhandler($buff, 5);
$size = strlen($buff);
header("Content-Encoding: gzip");
}
header("Content-Type: '.$content_type.'; charset=UTF-8");
header("Date: '.substr(gmdate('r'), 0, -5).'GMT");
header("Expires: '.substr(gmdate('r', strtotime('+1 MONTH')), 0, -5).'GMT");
header("Cache-Control: private, max-age=2592000");
header("Pragma: cache");
header("Last-Modified: '.substr(gmdate('r', $mtime), 0, -5).'GMT");
header("ETag: \"'.dechex($unique).'-".dechex($size)."-'.dechex($mtime).'\"");
if(!$cached) {
if(empty($buff)) {
$f = fopen($content_filename,"r");
fpassthru($f);
} else print $buff;
}
?>';
FileHandler::writeFile($path.'/'.$filename, $header_buff);
}
/**
* @brief method that modify a path for import or background element in a given css file
* @param[in] file a file to be modified
* @param[in] str a buffer to store resultant content
* @return Returns resultant content
**/
function replaceCssPath($file, $str) {
// css 파일의 위치를 구함
$this->tmp_css_path = preg_replace("/^\.\//is","",dirname($file))."/";
// url() 로 되어 있는 css 파일의 경로를 변경
$str = preg_replace_callback('/url\(([^\)]*)\)/is', array($this, '_replaceCssPath'), $str);
// charset 지정 문구를 제거
$str = preg_replace('!@charset([^;]*?);!is','',$str);
return $str;
}
/**
* @brief callback method that is responsible for replacing strings in css file with predefined ones.
* @param[in] matches a list of strings to be examined and modified if necessary
* @return Returns resultant content
**/
function _replaceCssPath($matches) {
static $abpath = null;
if(is_null($abpath)) {
$url_info = parse_url(Context::getRequestUri());
$abpath = $url_info['path'];
}
$path = str_replace(array('"',"'"),'',$matches[1]);
if(substr($path,0,1)=='/' || strpos($path,'://')!==false || strpos($path,'.htc')!==false) return 'url("'.$path.'")';
if(substr($path,0,2)=='./') $path = substr($path,2);
$target = $abpath.$this->tmp_css_path.$path;
while(strpos($target,'/../')!==false) {
$target = preg_replace('/\/([^\/]+)\/\.\.\//','/',$target);
}
return 'url("'.$target.'")';
}
}
?>

182
common/script.php Normal file
View file

@ -0,0 +1,182 @@
<?php
/**
* @author sol (sol@nhn.com)
* @brief css js Optimizer 처리 gateway
*
**/
if(!$_GET['t'] || !$_GET['l']) exit;
// set env
$XE_PATH = substr(dirname(__FILE__),0,strlen('common')*-1);
$XE_WEB_PATH = substr($XE_PATH,strlen($_SERVER['DOCUMENT_ROOT']));
$cache_path = $XE_PATH . 'files/cache/optimized/';
$type = $_GET['t'];
$list_file = $cache_path . $_GET['l'];
// check
if(!file_exists($list_file)) exit;
$list = include($list_file);
if(!is_array($list)) exit;
function getRealPath($file){
global $XE_PATH;
if($file{0}=='.' && $file{1} =='/') $file = $XE_PATH.substr($file, 2);
return $file;
}
function getMtime($file){
$file = getRealPath($file);
if(file_exists($file)) return filemtime($file);
}
function getMaxMtime($list){
$mtime = array();
foreach($list as $file) $mtime[] = getMtime($file);
return max($mtime);
}
// max mtime
$mtime = getMaxMtime($list);
if($type == '.css'){
$content_type = 'text/css';
} else if($type == '.js') {
$content_type = 'text/javascript';
}
header("Content-Type: ".$content_type."; charset=UTF-8");
// return 304
if (!empty($_SERVER['HTTP_IF_MODIFIED_SINCE'])) {
$modifiedSince = strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']);
if ($modifiedSince && ($modifiedSince == $mtime)) {
header('HTTP/1.1 304 Not Modified');
header("Connection: close");
exit;
}
}
header("Cache-Control: private, max-age=2592000");
header("Pragma: cache");
header("Connection: close");
header("Last-Modified: " . substr(gmdate('r', $mtime), 0, -5). "GMT");
//header("ETag: \"'.dechex($unique).'-".dechex($size)."-'.dechex($mtime).'\"");
function printFileList($list){
for($i=0,$c=count($list);$i<$c;$i++){
$file = getRealPath($list[$i]);
if(file_exists($file)){
$f = fopen($file,"r");
fpassthru($f);
}
}
}
if($type == '.css'){
function convertEncodingStr($str) {
$charset_list = array(
'UTF-8', 'EUC-KR', 'CP949', 'ISO8859-1', 'EUC-JP', 'SHIFT_JIS', 'CP932',
'EUC-CN', 'HZ', 'GBK', 'GB18030', 'EUC-TW', 'BIG5', 'CP950', 'BIG5-HKSCS',
'ISO2022-CN', 'ISO2022-CN-EXT', 'ISO2022-JP', 'ISO2022-JP-2', 'ISO2022-JP-1',
'ISO8859-6', 'ISO8859-8', 'JOHAB', 'ISO2022-KR', 'CP1255', 'CP1256', 'CP862',
'ASCII', 'ISO8859-1', 'ISO8850-2', 'ISO8850-3', 'ISO8850-4', 'ISO8850-5',
'ISO8850-7', 'ISO8850-9', 'ISO8850-10', 'ISO8850-13', 'ISO8850-14',
'ISO8850-15', 'ISO8850-16', 'CP1250', 'CP1251', 'CP1252', 'CP1253', 'CP1254',
'CP1257', 'CP850', 'CP866',
);
for($i=0;$i<count($charset_list);$i++) {
$charset = $charset_list[$i];
if($str){
$cstr = iconv($charset,$charset,$str);
if($str == $cstr);
return $cstr;
}
}
return $str;
}
function write($file_name, $buff, $mode='w'){
$file_name = getRealPath($file_name);
if(@!$fp = fopen($file_name,$mode)) return false;
fwrite($fp, $buff);
fclose($fp);
@chmod($file_name, 0644);
}
function read($file_name) {
$file_name = getRealPath($file_name);
if(!file_exists($file_name)) return;
$filesize = filesize($file_name);
if($filesize<1) return;
if(function_exists('file_get_contents')) return file_get_contents($file_name);
$fp = fopen($file_name, "r");
$buff = '';
if($fp) {
while(!feof($fp) && strlen($buff)<=$filesize) {
$str = fgets($fp, 1024);
$buff .= $str;
}
fclose($fp);
}
return $buff;
}
function makeCacheFileCSS($css_file, $cache_file){
$str = read($css_file);
$str = replaceCssPath($css_file, trim(convertEncodingStr($str)));
write($cache_file, $str."\n");
unset($str);
}
function replaceCssPath($file, $str) {
global $tmp_css_path;
// css 파일의 위치를 구함
$tmp_css_path = preg_replace("/^\.\//is","",dirname($file))."/";
// url() 로 되어 있는 css 파일의 경로를 변경
$str = preg_replace_callback('/url\(([^\)]*)\)/is', '_replaceCssPath', $str);
// charset 지정 문구를 제거
$str = preg_replace('!@charset([^;]*?);!is','',$str);
return $str;
}
function _replaceCssPath($matches) {
global $tmp_css_path, $XE_WEB_PATH;
$path = str_replace(array('"',"'"),'',$matches[1]);
if(substr($path,0,1)=='/' || strpos($path,'://')!==false || strpos($path,'.htc')!==false) return 'url("'.$path.'")';
if(substr($path,0,2)=='./') $path = substr($path,2);
$target = $XE_WEB_PATH.$tmp_css_path.$path;
while(strpos($target,'/../')!==false) {
$target = preg_replace('/\/([^\/]+)\/\.\.\//','/',$target);
}
return 'url("'.$target.'")';
}
foreach($list as $file){
$cache_file = $cache_path . md5($file);
$cache_mtime = getMtime($cache_file);
$css_mtime = getMtime($file);
// check modified
if($css_mtime > $cache_mtime){
makeCacheFileCSS($file, getRealPath($cache_file));
}
$css[] = getRealPath($cache_file);
}
printFileList($css);
}else{
printFileList($list);
}
?>