|
Moodle
2.2.1
http://www.collinsharper.com
|
00001 <?php 00010 require_once 'Minify/Source.php'; 00011 00030 class Minify { 00031 00032 const VERSION = '2.1.3'; 00033 const TYPE_CSS = 'text/css'; 00034 const TYPE_HTML = 'text/html'; 00035 // there is some debate over the ideal JS Content-Type, but this is the 00036 // Apache default and what Yahoo! uses.. 00037 const TYPE_JS = 'application/x-javascript'; 00038 00050 public static $uploaderHoursBehind = 0; 00051 00060 public static $importWarning = "/* See http://code.google.com/p/minify/wiki/CommonProblems#@imports_can_appear_in_invalid_locations_in_combined_CSS_files */\n"; 00061 00077 public static function setCache($cache = '', $fileLocking = true) 00078 { 00079 if (is_string($cache)) { 00080 require_once 'Minify/Cache/File.php'; 00081 self::$_cache = new Minify_Cache_File($cache, $fileLocking); 00082 } else { 00083 self::$_cache = $cache; 00084 } 00085 } 00086 00160 public static function serve($controller, $options = array()) 00161 { 00162 if (is_string($controller)) { 00163 // make $controller into object 00164 $class = 'Minify_Controller_' . $controller; 00165 if (! class_exists($class, false)) { 00166 require_once "Minify/Controller/" 00167 . str_replace('_', '/', $controller) . ".php"; 00168 } 00169 $controller = new $class(); 00170 } 00171 00172 // set up controller sources and mix remaining options with 00173 // controller defaults 00174 $options = $controller->setupSources($options); 00175 $options = $controller->analyzeSources($options); 00176 self::$_options = $controller->mixInDefaultOptions($options); 00177 00178 // check request validity 00179 if (! $controller->sources) { 00180 // invalid request! 00181 if (! self::$_options['quiet']) { 00182 header(self::$_options['badRequestHeader']); 00183 echo self::$_options['badRequestHeader']; 00184 return; 00185 } else { 00186 list(,$statusCode) = explode(' ', self::$_options['badRequestHeader']); 00187 return array( 00188 'success' => false 00189 ,'statusCode' => (int)$statusCode 00190 ,'content' => '' 00191 ,'headers' => array() 00192 ); 00193 } 00194 } 00195 00196 self::$_controller = $controller; 00197 00198 if (self::$_options['debug']) { 00199 self::_setupDebug($controller->sources); 00200 self::$_options['maxAge'] = 0; 00201 } 00202 00203 // determine encoding 00204 if (self::$_options['encodeOutput']) { 00205 if (self::$_options['encodeMethod'] !== null) { 00206 // controller specifically requested this 00207 $contentEncoding = self::$_options['encodeMethod']; 00208 } else { 00209 // sniff request header 00210 require_once 'HTTP/Encoder.php'; 00211 // depending on what the client accepts, $contentEncoding may be 00212 // 'x-gzip' while our internal encodeMethod is 'gzip'. Calling 00213 // getAcceptedEncoding(false, false) leaves out compress and deflate as options. 00214 list(self::$_options['encodeMethod'], $contentEncoding) = HTTP_Encoder::getAcceptedEncoding(false, false); 00215 } 00216 } else { 00217 self::$_options['encodeMethod'] = ''; // identity (no encoding) 00218 } 00219 00220 // check client cache 00221 require_once 'HTTP/ConditionalGet.php'; 00222 $cgOptions = array( 00223 'lastModifiedTime' => self::$_options['lastModifiedTime'] 00224 ,'isPublic' => self::$_options['isPublic'] 00225 ,'encoding' => self::$_options['encodeMethod'] 00226 ); 00227 if (self::$_options['maxAge'] > 0) { 00228 $cgOptions['maxAge'] = self::$_options['maxAge']; 00229 } 00230 $cg = new HTTP_ConditionalGet($cgOptions); 00231 if ($cg->cacheIsValid) { 00232 // client's cache is valid 00233 if (! self::$_options['quiet']) { 00234 $cg->sendHeaders(); 00235 return; 00236 } else { 00237 return array( 00238 'success' => true 00239 ,'statusCode' => 304 00240 ,'content' => '' 00241 ,'headers' => $cg->getHeaders() 00242 ); 00243 } 00244 } else { 00245 // client will need output 00246 $headers = $cg->getHeaders(); 00247 unset($cg); 00248 } 00249 00250 if (self::$_options['contentType'] === self::TYPE_CSS 00251 && self::$_options['rewriteCssUris']) { 00252 reset($controller->sources); 00253 while (list($key, $source) = each($controller->sources)) { 00254 if ($source->filepath 00255 && !isset($source->minifyOptions['currentDir']) 00256 && !isset($source->minifyOptions['prependRelativePath']) 00257 ) { 00258 $source->minifyOptions['currentDir'] = dirname($source->filepath); 00259 } 00260 } 00261 } 00262 00263 // check server cache 00264 if (null !== self::$_cache) { 00265 // using cache 00266 // the goal is to use only the cache methods to sniff the length and 00267 // output the content, as they do not require ever loading the file into 00268 // memory. 00269 $cacheId = 'minify_' . self::_getCacheId(); 00270 $fullCacheId = (self::$_options['encodeMethod']) 00271 ? $cacheId . '.gz' 00272 : $cacheId; 00273 // check cache for valid entry 00274 $cacheIsReady = self::$_cache->isValid($fullCacheId, self::$_options['lastModifiedTime']); 00275 if ($cacheIsReady) { 00276 $cacheContentLength = self::$_cache->getSize($fullCacheId); 00277 } else { 00278 // generate & cache content 00279 $content = self::_combineMinify(); 00280 self::$_cache->store($cacheId, $content); 00281 if (function_exists('gzencode')) { 00282 self::$_cache->store($cacheId . '.gz', gzencode($content, self::$_options['encodeLevel'])); 00283 } 00284 } 00285 } else { 00286 // no cache 00287 $cacheIsReady = false; 00288 $content = self::_combineMinify(); 00289 } 00290 if (! $cacheIsReady && self::$_options['encodeMethod']) { 00291 // still need to encode 00292 $content = gzencode($content, self::$_options['encodeLevel']); 00293 } 00294 00295 // add headers 00296 $headers['Content-Length'] = $cacheIsReady 00297 ? $cacheContentLength 00298 : strlen($content); 00299 $headers['Content-Type'] = self::$_options['contentTypeCharset'] 00300 ? self::$_options['contentType'] . '; charset=' . self::$_options['contentTypeCharset'] 00301 : self::$_options['contentType']; 00302 if (self::$_options['encodeMethod'] !== '') { 00303 $headers['Content-Encoding'] = $contentEncoding; 00304 } 00305 if (self::$_options['encodeOutput']) { 00306 $headers['Vary'] = 'Accept-Encoding'; 00307 } 00308 00309 if (! self::$_options['quiet']) { 00310 // output headers & content 00311 foreach ($headers as $name => $val) { 00312 header($name . ': ' . $val); 00313 } 00314 if ($cacheIsReady) { 00315 self::$_cache->display($fullCacheId); 00316 } else { 00317 echo $content; 00318 } 00319 } else { 00320 return array( 00321 'success' => true 00322 ,'statusCode' => 200 00323 ,'content' => $cacheIsReady 00324 ? self::$_cache->fetch($fullCacheId) 00325 : $content 00326 ,'headers' => $headers 00327 ); 00328 } 00329 } 00330 00343 public static function combine($sources, $options = array()) 00344 { 00345 $cache = self::$_cache; 00346 self::$_cache = null; 00347 $options = array_merge(array( 00348 'files' => (array)$sources 00349 ,'quiet' => true 00350 ,'encodeMethod' => '' 00351 ,'lastModifiedTime' => 0 00352 ), $options); 00353 $out = self::serve('Files', $options); 00354 self::$_cache = $cache; 00355 return $out['content']; 00356 } 00357 00366 public static function setDocRoot($unsetPathInfo = false) 00367 { 00368 if (isset($_SERVER['SERVER_SOFTWARE']) 00369 && 0 === strpos($_SERVER['SERVER_SOFTWARE'], 'Microsoft-IIS/') 00370 ) { 00371 $_SERVER['DOCUMENT_ROOT'] = rtrim(substr( 00372 $_SERVER['PATH_TRANSLATED'] 00373 ,0 00374 ,strlen($_SERVER['PATH_TRANSLATED']) - strlen($_SERVER['SCRIPT_NAME']) 00375 ), '\\'); 00376 if ($unsetPathInfo) { 00377 unset($_SERVER['PATH_INFO']); 00378 } 00379 require_once 'Minify/Logger.php'; 00380 Minify_Logger::log("setDocRoot() set DOCUMENT_ROOT to \"{$_SERVER['DOCUMENT_ROOT']}\""); 00381 } 00382 } 00383 00387 private static $_cache = null; 00388 00392 protected static $_controller = null; 00393 00397 protected static $_options = null; 00398 00406 protected static function _setupDebug($sources) 00407 { 00408 foreach ($sources as $source) { 00409 $source->minifier = array('Minify_Lines', 'minify'); 00410 $id = $source->getId(); 00411 $source->minifyOptions = array( 00412 'id' => (is_file($id) ? basename($id) : $id) 00413 ); 00414 } 00415 } 00416 00422 protected static function _combineMinify() 00423 { 00424 $type = self::$_options['contentType']; // ease readability 00425 00426 // when combining scripts, make sure all statements separated and 00427 // trailing single line comment is terminated 00428 $implodeSeparator = ($type === self::TYPE_JS) 00429 ? "\n;" 00430 : ''; 00431 // allow the user to pass a particular array of options to each 00432 // minifier (designated by type). source objects may still override 00433 // these 00434 $defaultOptions = isset(self::$_options['minifierOptions'][$type]) 00435 ? self::$_options['minifierOptions'][$type] 00436 : array(); 00437 // if minifier not set, default is no minification. source objects 00438 // may still override this 00439 $defaultMinifier = isset(self::$_options['minifiers'][$type]) 00440 ? self::$_options['minifiers'][$type] 00441 : false; 00442 00443 if (Minify_Source::haveNoMinifyPrefs(self::$_controller->sources)) { 00444 // all source have same options/minifier, better performance 00445 // to combine, then minify once 00446 foreach (self::$_controller->sources as $source) { 00447 $pieces[] = $source->getContent(); 00448 } 00449 $content = implode($implodeSeparator, $pieces); 00450 if ($defaultMinifier) { 00451 self::$_controller->loadMinifier($defaultMinifier); 00452 $content = call_user_func($defaultMinifier, $content, $defaultOptions); 00453 } 00454 } else { 00455 // minify each source with its own options and minifier, then combine 00456 foreach (self::$_controller->sources as $source) { 00457 // allow the source to override our minifier and options 00458 $minifier = (null !== $source->minifier) 00459 ? $source->minifier 00460 : $defaultMinifier; 00461 $options = (null !== $source->minifyOptions) 00462 ? array_merge($defaultOptions, $source->minifyOptions) 00463 : $defaultOptions; 00464 if ($minifier) { 00465 self::$_controller->loadMinifier($minifier); 00466 // get source content and minify it 00467 $pieces[] = call_user_func($minifier, $source->getContent(), $options); 00468 } else { 00469 $pieces[] = $source->getContent(); 00470 } 00471 } 00472 $content = implode($implodeSeparator, $pieces); 00473 } 00474 00475 if ($type === self::TYPE_CSS && false !== strpos($content, '@import')) { 00476 $content = self::_handleCssImports($content); 00477 } 00478 00479 // do any post-processing (esp. for editing build URIs) 00480 if (self::$_options['postprocessorRequire']) { 00481 require_once self::$_options['postprocessorRequire']; 00482 } 00483 if (self::$_options['postprocessor']) { 00484 $content = call_user_func(self::$_options['postprocessor'], $content, $type); 00485 } 00486 return $content; 00487 } 00488 00496 protected static function _getCacheId() 00497 { 00498 return md5(serialize(array( 00499 Minify_Source::getDigest(self::$_controller->sources) 00500 ,self::$_options['minifiers'] 00501 ,self::$_options['minifierOptions'] 00502 ,self::$_options['postprocessor'] 00503 ,self::$_options['bubbleCssImports'] 00504 ))); 00505 } 00506 00511 protected static function _handleCssImports($css) 00512 { 00513 if (self::$_options['bubbleCssImports']) { 00514 // bubble CSS imports 00515 preg_match_all('/@import.*?;/', $css, $imports); 00516 $css = implode('', $imports[0]) . preg_replace('/@import.*?;/', '', $css); 00517 } else if ('' !== self::$importWarning) { 00518 // remove comments so we don't mistake { in a comment as a block 00519 $noCommentCss = preg_replace('@/\\*[\\s\\S]*?\\*/@', '', $css); 00520 $lastImportPos = strrpos($noCommentCss, '@import'); 00521 $firstBlockPos = strpos($noCommentCss, '{'); 00522 if (false !== $lastImportPos 00523 && false !== $firstBlockPos 00524 && $firstBlockPos < $lastImportPos 00525 ) { 00526 // { appears before @import : prepend warning 00527 $css = self::$importWarning . $css; 00528 } 00529 } 00530 return $css; 00531 } 00532 }