includes/clientside/jsres.php
changeset 1227 bdac73ed481e
parent 1226 de56132c008d
child 1252 e34c23a35dc9
equal deleted inserted replaced
1226:de56132c008d 1227:bdac73ed481e
    14 
    14 
    15 // define('ENANO_JS_DEBUG', 1);
    15 // define('ENANO_JS_DEBUG', 1);
    16 
    16 
    17 // if Enano's already loaded, we've been included from a helper script
    17 // if Enano's already loaded, we've been included from a helper script
    18 if ( defined('ENANO_CONFIG_FETCHED') )
    18 if ( defined('ENANO_CONFIG_FETCHED') )
    19   define('ENANO_JSRES_SETUP_ONLY', 1);
    19 	define('ENANO_JSRES_SETUP_ONLY', 1);
    20 
    20 
    21 if ( !defined('ENANO_JSRES_SETUP_ONLY') ):
    21 if ( !defined('ENANO_JSRES_SETUP_ONLY') ):
    22 
    22 
    23 /**
    23 /**
    24  * Returns a floating-point number with the current UNIX timestamp in microseconds. Defined very early because we gotta call it
    24  * Returns a floating-point number with the current UNIX timestamp in microseconds. Defined very early because we gotta call it
    27  */
    27  */
    28 
    28 
    29 // First check to see if something already declared this function.... it happens often.
    29 // First check to see if something already declared this function.... it happens often.
    30 if ( !function_exists('microtime_float') )
    30 if ( !function_exists('microtime_float') )
    31 {
    31 {
    32   function microtime_float()
    32 	function microtime_float()
    33   {
    33 	{
    34     list($usec, $sec) = explode(" ", microtime());
    34 		list($usec, $sec) = explode(" ", microtime());
    35     return ((float)$usec + (float)$sec);
    35 		return ((float)$usec + (float)$sec);
    36   }
    36 	}
    37 }
    37 }
    38 
    38 
    39 $local_start = microtime_float();
    39 $local_start = microtime_float();
    40 
    40 
    41 // Disable for IE, it causes problems.
    41 // Disable for IE, it causes problems.
    49 
    49 
    50 // We need to see if this is a specially marked Enano development server. You can create an Enano
    50 // We need to see if this is a specially marked Enano development server. You can create an Enano
    51 // development server using the script found on hg.enanocms.org.
    51 // development server using the script found on hg.enanocms.org.
    52 if ( strpos(__FILE__, '/repo/') && ( file_exists('../../.enanodev') || file_exists('../../../.enanodev') ) )
    52 if ( strpos(__FILE__, '/repo/') && ( file_exists('../../.enanodev') || file_exists('../../../.enanodev') ) )
    53 {
    53 {
    54   // We have a development directory. Remove /repo/ from the picture.
    54 	// We have a development directory. Remove /repo/ from the picture.
    55   $filename = str_replace('/repo/', '/', __FILE__);
    55 	$filename = str_replace('/repo/', '/', __FILE__);
    56 }
    56 }
    57 else
    57 else
    58 {
    58 {
    59   // Standard Enano installation
    59 	// Standard Enano installation
    60   $filename = __FILE__;
    60 	$filename = __FILE__;
    61 }
    61 }
    62 
    62 
    63 // ENANO_ROOT is sometimes defined by plugins like AjIM that need the constant before the Enano API is initialized
    63 // ENANO_ROOT is sometimes defined by plugins like AjIM that need the constant before the Enano API is initialized
    64 if ( !defined('ENANO_ROOT') )
    64 if ( !defined('ENANO_ROOT') )
    65   define('ENANO_ROOT', dirname(dirname(dirname($filename))));
    65 	define('ENANO_ROOT', dirname(dirname(dirname($filename))));
    66 
    66 
    67 chdir(ENANO_ROOT);
    67 chdir(ENANO_ROOT);
    68 
    68 
    69 // fetch only the site config
    69 // fetch only the site config
    70 define('ENANO_EXIT_AFTER_CONFIG', 1);
    70 define('ENANO_EXIT_AFTER_CONFIG', 1);
    74 
    74 
    75 // CONFIG
    75 // CONFIG
    76 
    76 
    77 // Files safe to run full (aggressive) compression on
    77 // Files safe to run full (aggressive) compression on
    78 $full_compress_safe = array(
    78 $full_compress_safe = array(
    79   // Sorted by file size, descending (du -b *.js | sort -n)
    79 	// Sorted by file size, descending (du -b *.js | sort -n)
    80   'crypto.js',
    80 	'crypto.js',
    81   'ajax.js',
    81 	'ajax.js',
    82   'editor.js',
    82 	'editor.js',
    83   'functions.js',
    83 	'functions.js',
    84   'login.js',
    84 	'login.js',
    85   'acl.js',
    85 	'acl.js',
    86   'misc.js',
    86 	'misc.js',
    87   'comments.js',
    87 	'comments.js',
    88   'autofill.js',
    88 	'autofill.js',
    89   'dropdown.js',
    89 	'dropdown.js',
    90   'paginate.js',
    90 	'paginate.js',
    91   'enano-lib-basic.js',
    91 	'enano-lib-basic.js',
    92   'pwstrength.js',
    92 	'pwstrength.js',
    93   'flyin.js',
    93 	'flyin.js',
    94   'rank-manager.js',
    94 	'rank-manager.js',
    95   'userpage.js',
    95 	'userpage.js',
    96   'template-compiler.js',
    96 	'template-compiler.js',
    97   'toolbar.js',
    97 	'toolbar.js',
    98 );
    98 );
    99 
    99 
   100 // Files that should NOT be compressed due to already being compressed, licensing, or invalid produced code
   100 // Files that should NOT be compressed due to already being compressed, licensing, or invalid produced code
   101 $compress_unsafe = array('json.js', 'fat.js', 'admin-menu.js', 'autofill.js', 'jquery.js', 'jquery-ui.js');
   101 $compress_unsafe = array('json.js', 'fat.js', 'admin-menu.js', 'autofill.js', 'jquery.js', 'jquery-ui.js');
   102 
   102 
   105 // try to gzip the output
   105 // try to gzip the output
   106 if ( !defined('ENANO_JSRES_SETUP_ONLY') ):
   106 if ( !defined('ENANO_JSRES_SETUP_ONLY') ):
   107 $do_gzip = false;
   107 $do_gzip = false;
   108 if ( isset($_SERVER['HTTP_ACCEPT_ENCODING']) && getConfig('gzip_output', false) == 1 )
   108 if ( isset($_SERVER['HTTP_ACCEPT_ENCODING']) && getConfig('gzip_output', false) == 1 )
   109 {
   109 {
   110   $acceptenc = str_replace(' ', '', strtolower($_SERVER['HTTP_ACCEPT_ENCODING']));
   110 	$acceptenc = str_replace(' ', '', strtolower($_SERVER['HTTP_ACCEPT_ENCODING']));
   111   $acceptenc = explode(',', $acceptenc);
   111 	$acceptenc = explode(',', $acceptenc);
   112   if ( in_array('gzip', $acceptenc) )
   112 	if ( in_array('gzip', $acceptenc) )
   113   {
   113 	{
   114     $do_gzip = true;
   114 		$do_gzip = true;
   115     ob_start();
   115 		ob_start();
   116   }
   116 	}
   117 }
   117 }
   118 
   118 
   119 // Output format will always be JS
   119 // Output format will always be JS
   120 header('Content-type: text/javascript');
   120 header('Content-type: text/javascript');
   121 
   121 
   127 // with a simple ETag and far future expires header
   127 // with a simple ETag and far future expires header
   128 
   128 
   129 // note - obfuscated for optimization purposes. The exact same code except properly indented is in enano-lib-basic.
   129 // note - obfuscated for optimization purposes. The exact same code except properly indented is in enano-lib-basic.
   130 if ( isset($_GET['early']) )
   130 if ( isset($_GET['early']) )
   131 {
   131 {
   132   header('ETag: enanocms-lib-early-r3');
   132 	header('ETag: enanocms-lib-early-r3');
   133   header('Expires: Wed, 1 Jan 2020 00:00:00 GMT');
   133 	header('Expires: Wed, 1 Jan 2020 00:00:00 GMT');
   134   
   134 	
   135   echo <<<JSEOF
   135 	echo <<<JSEOF
   136 window.loaded_components = window.loaded_components || {};
   136 window.loaded_components = window.loaded_components || {};
   137 window.onload_complete = false;
   137 window.onload_complete = false;
   138 var onload_hooks = new Array();function addOnloadHook(func){if ( typeof ( func ) == 'function' ){if ( typeof(onload_hooks.push) == 'function' ){onload_hooks.push(func);}else{onload_hooks[onload_hooks.length] = func;};};}
   138 var onload_hooks = new Array();function addOnloadHook(func){if ( typeof ( func ) == 'function' ){if ( typeof(onload_hooks.push) == 'function' ){onload_hooks.push(func);}else{onload_hooks[onload_hooks.length] = func;};};}
   139 JSEOF;
   139 JSEOF;
   140   
   140 	
   141   exit();
   141 	exit();
   142 }
   142 }
   143 
   143 
   144 // Load and parse enano_lib_basic
   144 // Load and parse enano_lib_basic
   145 $file = @file_get_contents('includes/clientside/static/enano-lib-basic.js');
   145 $file = @file_get_contents('includes/clientside/static/enano-lib-basic.js');
   146 
   146 
   147 $pos_start_includes = strpos($file, '/*!START_INCLUDER*/');
   147 $pos_start_includes = strpos($file, '/*!START_INCLUDER*/');
   148 $pos_end_includes = strpos($file, '/*!END_INCLUDER*/');
   148 $pos_end_includes = strpos($file, '/*!END_INCLUDER*/');
   149 
   149 
   150 if ( !$pos_start_includes || !$pos_end_includes )
   150 if ( !$pos_start_includes || !$pos_end_includes )
   151 {
   151 {
   152   die('// Error: enano-lib-basic does not have required metacomments');
   152 	die('// Error: enano-lib-basic does not have required metacomments');
   153 }
   153 }
   154 
   154 
   155 $pos_end_includes += strlen('/*!END_INCLUDER*/');
   155 $pos_end_includes += strlen('/*!END_INCLUDER*/');
   156 
   156 
   157 preg_match('/var thefiles = (\[([^\]]+?)\]);/', $file, $match);
   157 preg_match('/var thefiles = (\[([^\]]+?)\]);/', $file, $match);
   158 
   158 
   159 if ( empty($match) )
   159 if ( empty($match) )
   160   die('// Error: could not retrieve file list from enano-lib-basic');
   160 	die('// Error: could not retrieve file list from enano-lib-basic');
   161 
   161 
   162 // Decode file list
   162 // Decode file list
   163 try
   163 try
   164 {
   164 {
   165   $file_list = enano_json_decode($match[1]);
   165 	$file_list = enano_json_decode($match[1]);
   166 }
   166 }
   167 catch ( Exception $e )
   167 catch ( Exception $e )
   168 {
   168 {
   169   die("// Exception caught during file list parsing");
   169 	die("// Exception caught during file list parsing");
   170 }
   170 }
   171 
   171 
   172 $apex = filemtime('includes/clientside/static/enano-lib-basic.js');
   172 $apex = filemtime('includes/clientside/static/enano-lib-basic.js');
   173 
   173 
   174 $before_includes = substr($file, 0, $pos_start_includes);
   174 $before_includes = substr($file, 0, $pos_start_includes);
   175 $after_includes = substr($file, $pos_end_includes);
   175 $after_includes = substr($file, $pos_end_includes);
   176 
   176 
   177 if ( isset($_GET['f']) )
   177 if ( isset($_GET['f']) )
   178 {
   178 {
   179   // requested a single file
   179 	// requested a single file
   180   $js_file =& $_GET['f'];
   180 	$js_file =& $_GET['f'];
   181   if ( strstr($js_file, ',') )
   181 	if ( strstr($js_file, ',') )
   182   {
   182 	{
   183     $filelist = explode(',', $js_file);
   183 		$filelist = explode(',', $js_file);
   184     unset($js_file);
   184 		unset($js_file);
   185     $everything = '';
   185 		$everything = '';
   186     foreach ( $filelist as $js_file )
   186 		foreach ( $filelist as $js_file )
   187     {
   187 		{
   188       if ( !preg_match('/^[a-z0-9_-]+\.js$/i', $js_file) )
   188 			if ( !preg_match('/^[a-z0-9_-]+\.js$/i', $js_file) )
   189       {
   189 			{
   190         header('HTTP/1.1 404 Not Found');
   190 				header('HTTP/1.1 404 Not Found');
   191         exit('Not found');
   191 				exit('Not found');
   192       }
   192 			}
   193       
   193 			
   194       $apex = filemtime("includes/clientside/static/$js_file");
   194 			$apex = filemtime("includes/clientside/static/$js_file");
   195       
   195 			
   196       $file_contents = file_get_contents("includes/clientside/static/$js_file");
   196 			$file_contents = file_get_contents("includes/clientside/static/$js_file");
   197       $everything .= jsres_cache_check($js_file, $file_contents) . ' loaded_components[\'' . $js_file . '\'] = true;';
   197 			$everything .= jsres_cache_check($js_file, $file_contents) . ' loaded_components[\'' . $js_file . '\'] = true;';
   198     }
   198 		}
   199     $everything .= 'if ( onload_complete ) { runOnloadHooks(); onload_hooks = []; };';
   199 		$everything .= 'if ( onload_complete ) { runOnloadHooks(); onload_hooks = []; };';
   200   }
   200 	}
   201   else
   201 	else
   202   {
   202 	{
   203     if ( !preg_match('/^[a-z0-9_-]+\.js$/i', $js_file) )
   203 		if ( !preg_match('/^[a-z0-9_-]+\.js$/i', $js_file) )
   204     {
   204 		{
   205       header('HTTP/1.1 404 Not Found');
   205 			header('HTTP/1.1 404 Not Found');
   206       exit('Not found');
   206 			exit('Not found');
   207     }
   207 		}
   208     
   208 		
   209     $apex = filemtime("includes/clientside/static/$js_file");
   209 		$apex = filemtime("includes/clientside/static/$js_file");
   210     
   210 		
   211     $file_contents = file_get_contents("includes/clientside/static/$js_file");
   211 		$file_contents = file_get_contents("includes/clientside/static/$js_file");
   212     $everything = jsres_cache_check($js_file, $file_contents) . ' loaded_components[\'' . $js_file . '\'] = true; if ( onload_complete ) { runOnloadHooks(); onload_hooks = []; };';
   212 		$everything = jsres_cache_check($js_file, $file_contents) . ' loaded_components[\'' . $js_file . '\'] = true; if ( onload_complete ) { runOnloadHooks(); onload_hooks = []; };';
   213   }
   213 	}
   214 }
   214 }
   215 else
   215 else
   216 {
   216 {
   217   // compress enano-lib-basic
   217 	// compress enano-lib-basic
   218   $libbasic = "$before_includes\n$after_includes";
   218 	$libbasic = "$before_includes\n$after_includes";
   219   $libbasic = jsres_cache_check('enano-lib-basic.js', $libbasic);
   219 	$libbasic = jsres_cache_check('enano-lib-basic.js', $libbasic);
   220   $everything .= $libbasic;
   220 	$everything .= $libbasic;
   221   
   221 	
   222   // $everything .= $before_includes;
   222 	// $everything .= $before_includes;
   223   // $everything .= $after_includes;
   223 	// $everything .= $after_includes;
   224   
   224 	
   225   foreach ( $file_list as $js_file )
   225 	foreach ( $file_list as $js_file )
   226   {
   226 	{
   227     $file_contents = file_get_contents("includes/clientside/static/$js_file");
   227 		$file_contents = file_get_contents("includes/clientside/static/$js_file");
   228     $time = filemtime("includes/clientside/static/$js_file");
   228 		$time = filemtime("includes/clientside/static/$js_file");
   229     if ( $time > $apex )
   229 		if ( $time > $apex )
   230       $apex = $time;
   230 			$apex = $time;
   231     
   231 		
   232     $file_contents = jsres_cache_check($js_file, $file_contents);
   232 		$file_contents = jsres_cache_check($js_file, $file_contents);
   233     
   233 		
   234     $everything .= "\n\n// $js_file\n";
   234 		$everything .= "\n\n// $js_file\n";
   235     $everything .= "\n" . $file_contents;
   235 		$everything .= "\n" . $file_contents;
   236   }
   236 	}
   237 }
   237 }
   238 
   238 
   239 // generate ETag
   239 // generate ETag
   240 $etag = base64_encode(hexdecode(sha1($everything)));
   240 $etag = base64_encode(hexdecode(sha1($everything)));
   241 
   241 
   242 if ( isset($_SERVER['HTTP_IF_NONE_MATCH']) )
   242 if ( isset($_SERVER['HTTP_IF_NONE_MATCH']) )
   243 {
   243 {
   244   if ( "\"$etag\"" == $_SERVER['HTTP_IF_NONE_MATCH'] )
   244 	if ( "\"$etag\"" == $_SERVER['HTTP_IF_NONE_MATCH'] )
   245   {
   245 	{
   246     header('HTTP/1.1 304 Not Modified');
   246 		header('HTTP/1.1 304 Not Modified');
   247     exit();
   247 		exit();
   248   }
   248 	}
   249 }
   249 }
   250 
   250 
   251 // generate expires header
   251 // generate expires header
   252 $expires = date('r', mktime(0, 0, 0, intval(date('m')), intval(date('d')), intval(date('y'))+1));
   252 $expires = date('r', mktime(0, 0, 0, intval(date('m')), intval(date('d')), intval(date('y'))+1));
   253 
   253 
   255 
   255 
   256 $date = date('r', $apex);
   256 $date = date('r', $apex);
   257 
   257 
   258 if ( defined('ENANO_JSRES_SETUP_ONLY') )
   258 if ( defined('ENANO_JSRES_SETUP_ONLY') )
   259 {
   259 {
   260   return; // we're done setting up, break out
   260 	return; // we're done setting up, break out
   261 }
   261 }
   262 
   262 
   263 header("Date: $date");
   263 header("Date: $date");
   264 header("Last-Modified: $date");
   264 header("Last-Modified: $date");
   265 header("ETag: \"$etag\"");
   265 header("ETag: \"$etag\"");
   266 header("Expires: $expires");
   266 header("Expires: $expires");
   267 if ( !$do_gzip )
   267 if ( !$do_gzip )
   268   header("Content-Length: " . strlen($everything));
   268 	header("Content-Length: " . strlen($everything));
   269 
   269 
   270 $local_end = microtime_float();
   270 $local_end = microtime_float();
   271 $local_gentime = $local_end - $local_start;
   271 $local_gentime = $local_end - $local_start;
   272 $local_gentime = round($local_gentime, 5);
   272 $local_gentime = round($local_gentime, 5);
   273 header("X-Performance: generated in $local_gentime seconds");
   273 header("X-Performance: generated in $local_gentime seconds");
   274 
   274 
   275 echo $everything;
   275 echo $everything;
   276 
   276 
   277 if ( $do_gzip )
   277 if ( $do_gzip )
   278 {
   278 {
   279   gzip_output();
   279 	gzip_output();
   280 }
   280 }
   281 
   281 
   282 /**
   282 /**
   283  * Check the cache for the given JS file and return the best-compressed version.
   283  * Check the cache for the given JS file and return the best-compressed version.
   284  * @param string Javascript file (acl.js)
   284  * @param string Javascript file (acl.js)
   286  * @return string
   286  * @return string
   287  */
   287  */
   288 
   288 
   289 function jsres_cache_check($js_file, $file_contents)
   289 function jsres_cache_check($js_file, $file_contents)
   290 {
   290 {
   291   global $full_compress_safe, $compress_unsafe;
   291 	global $full_compress_safe, $compress_unsafe;
   292   global $disable_compress;
   292 	global $disable_compress;
   293   
   293 	
   294   if ( $disable_compress )
   294 	if ( $disable_compress )
   295     return $file_contents;
   295 		return $file_contents;
   296   
   296 	
   297   $file_md5 = md5($file_contents);
   297 	$file_md5 = md5($file_contents);
   298   
   298 	
   299   // Is this file cached?
   299 	// Is this file cached?
   300   $cache_path = ENANO_ROOT . "/cache/jsres_$js_file.json";
   300 	$cache_path = ENANO_ROOT . "/cache/jsres_$js_file.json";
   301   $loaded_cache = false;
   301 	$loaded_cache = false;
   302   
   302 	
   303   if ( file_exists($cache_path) )
   303 	if ( file_exists($cache_path) )
   304   {
   304 	{
   305     // Load the cache file and parse it.
   305 		// Load the cache file and parse it.
   306     $cache_file = file_get_contents($cache_path);
   306 		$cache_file = file_get_contents($cache_path);
   307     try
   307 		try
   308     {
   308 		{
   309       $cache_file = enano_json_decode($cache_file);
   309 			$cache_file = enano_json_decode($cache_file);
   310     }
   310 		}
   311     catch ( Exception $e )
   311 		catch ( Exception $e )
   312     {
   312 		{
   313       // Don't do anything - let our fallbacks come into place
   313 			// Don't do anything - let our fallbacks come into place
   314     }
   314 		}
   315     if ( is_array($cache_file) && isset($cache_file['md5']) && isset($cache_file['src']) )
   315 		if ( is_array($cache_file) && isset($cache_file['md5']) && isset($cache_file['src']) )
   316     {
   316 		{
   317       if ( $cache_file['md5'] === $file_md5 )
   317 			if ( $cache_file['md5'] === $file_md5 )
   318       {
   318 			{
   319         @header("X-Cache-Status: cache HIT, hash $file_md5");
   319 				@header("X-Cache-Status: cache HIT, hash $file_md5");
   320         $loaded_cache = true;
   320 				$loaded_cache = true;
   321         $file_contents = $cache_file['src'];
   321 				$file_contents = $cache_file['src'];
   322       }
   322 			}
   323     }
   323 		}
   324   }
   324 	}
   325   if ( !$loaded_cache && getConfig('cache_thumbs') == '1' )
   325 	if ( !$loaded_cache && getConfig('cache_thumbs') == '1' )
   326   {
   326 	{
   327     // Try to open the cache file and write to it. If we can't do that, just don't compress the code.
   327 		// Try to open the cache file and write to it. If we can't do that, just don't compress the code.
   328     $handle = @fopen($cache_path, 'w');
   328 		$handle = @fopen($cache_path, 'w');
   329     if ( $handle )
   329 		if ( $handle )
   330     {
   330 		{
   331       $aggressive = in_array($js_file, $full_compress_safe);
   331 			$aggressive = in_array($js_file, $full_compress_safe);
   332       if ( !in_array($js_file, $compress_unsafe) )
   332 			if ( !in_array($js_file, $compress_unsafe) )
   333         $file_contents = perform_js_compress($file_contents, $aggressive);
   333 				$file_contents = perform_js_compress($file_contents, $aggressive);
   334       
   334 			
   335       $payload = enano_json_encode(array(
   335 			$payload = enano_json_encode(array(
   336           'md5' => $file_md5,
   336 					'md5' => $file_md5,
   337           'src' => $file_contents
   337 					'src' => $file_contents
   338         ));
   338 				));
   339       fwrite($handle, $payload);
   339 			fwrite($handle, $payload);
   340       fclose($handle);
   340 			fclose($handle);
   341       @header("X-Cache-Status: cache MISS, new generated");
   341 			@header("X-Cache-Status: cache MISS, new generated");
   342     }
   342 		}
   343     else
   343 		else
   344     {
   344 		{
   345       @header("X-Cache-Status: cache MISS, not generated");
   345 			@header("X-Cache-Status: cache MISS, not generated");
   346     }
   346 		}
   347   }
   347 	}
   348   else if ( !$loaded_cache )
   348 	else if ( !$loaded_cache )
   349   {
   349 	{
   350     @header("X-Cache-Status: cache MISS, not generated");
   350 		@header("X-Cache-Status: cache MISS, not generated");
   351   }
   351 	}
   352   
   352 	
   353   return $file_contents;
   353 	return $file_contents;
   354 }
   354 }
   355 
   355