ClosureCompiler.php 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133
  1. <?php
  2. /**
  3. * Class Minify_JS_ClosureCompiler
  4. * @package Minify
  5. */
  6. /**
  7. * Minify Javascript using Google's Closure Compiler API
  8. *
  9. * @link http://code.google.com/closure/compiler/
  10. * @package Minify
  11. * @author Stephen Clay <steve@mrclay.org>
  12. *
  13. * @todo can use a stream wrapper to unit test this?
  14. */
  15. class Minify_JS_ClosureCompiler {
  16. const URL = 'http://closure-compiler.appspot.com/compile';
  17. /**
  18. * Minify Javascript code via HTTP request to the Closure Compiler API
  19. *
  20. * @param string $js input code
  21. * @param array $options unused at this point
  22. * @return string
  23. */
  24. public static function minify($js, array $options = array())
  25. {
  26. $obj = new self($options);
  27. return $obj->min($js);
  28. }
  29. /**
  30. *
  31. * @param array $options
  32. *
  33. * fallbackFunc : default array($this, 'fallback');
  34. */
  35. public function __construct(array $options = array())
  36. {
  37. $this->_fallbackFunc = isset($options['fallbackMinifier'])
  38. ? $options['fallbackMinifier']
  39. : array($this, '_fallback');
  40. }
  41. public function min($js)
  42. {
  43. $postBody = $this->_buildPostBody($js);
  44. $bytes = (function_exists('mb_strlen') && ((int)ini_get('mbstring.func_overload') & 2))
  45. ? mb_strlen($postBody, '8bit')
  46. : strlen($postBody);
  47. if ($bytes > 200000) {
  48. throw new Minify_JS_ClosureCompiler_Exception(
  49. 'POST content larger than 200000 bytes'
  50. );
  51. }
  52. $response = $this->_getResponse($postBody);
  53. if (preg_match('/^Error\(\d\d?\):/', $response)) {
  54. if (is_callable($this->_fallbackFunc)) {
  55. $response = "/* Received errors from Closure Compiler API:\n$response"
  56. . "\n(Using fallback minifier)\n*/\n";
  57. $response .= call_user_func($this->_fallbackFunc, $js);
  58. } else {
  59. throw new Minify_JS_ClosureCompiler_Exception($response);
  60. }
  61. }
  62. if ($response === '') {
  63. $errors = $this->_getResponse($this->_buildPostBody($js, true));
  64. throw new Minify_JS_ClosureCompiler_Exception($errors);
  65. }
  66. return $response;
  67. }
  68. protected $_fallbackFunc = null;
  69. protected function _getResponse($postBody)
  70. {
  71. $allowUrlFopen = preg_match('/1|yes|on|true/i', ini_get('allow_url_fopen'));
  72. if ($allowUrlFopen) {
  73. $contents = file_get_contents(self::URL, false, stream_context_create(array(
  74. 'http' => array(
  75. 'method' => 'POST',
  76. 'header' => 'Content-type: application/x-www-form-urlencoded',
  77. 'content' => $postBody,
  78. 'max_redirects' => 0,
  79. 'timeout' => 15,
  80. )
  81. )));
  82. } elseif (defined('CURLOPT_POST')) {
  83. $ch = curl_init(self::URL);
  84. curl_setopt($ch, CURLOPT_POST, true);
  85. curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
  86. curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-type: application/x-www-form-urlencoded'));
  87. curl_setopt($ch, CURLOPT_POSTFIELDS, $postBody);
  88. curl_setopt($ch, CURLOPT_FOLLOWLOCATION, false);
  89. curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 15);
  90. $contents = curl_exec($ch);
  91. curl_close($ch);
  92. } else {
  93. throw new Minify_JS_ClosureCompiler_Exception(
  94. "Could not make HTTP request: allow_url_open is false and cURL not available"
  95. );
  96. }
  97. if (false === $contents) {
  98. throw new Minify_JS_ClosureCompiler_Exception(
  99. "No HTTP response from server"
  100. );
  101. }
  102. return trim($contents);
  103. }
  104. protected function _buildPostBody($js, $returnErrors = false)
  105. {
  106. return http_build_query(array(
  107. 'js_code' => $js,
  108. 'output_info' => ($returnErrors ? 'errors' : 'compiled_code'),
  109. 'output_format' => 'text',
  110. 'compilation_level' => 'SIMPLE_OPTIMIZATIONS'
  111. ), null, '&');
  112. }
  113. /**
  114. * Default fallback function if CC API fails
  115. * @param string $js
  116. * @return string
  117. */
  118. protected function _fallback($js)
  119. {
  120. require_once 'JSMin.php';
  121. return JSMin::minify($js);
  122. }
  123. }
  124. class Minify_JS_ClosureCompiler_Exception extends Exception {}