PluginProbe
WP Super Minify • Minify, Compress and Cache HTML, CSS & JavaScript / trunk
WP Super Minify • Minify, Compress and Cache HTML, CSS & JavaScript vtrunk
trunk 1.0 1.1 1.2 1.3 1.3.1 1.3.2 1.4 1.5 1.5.1 1.6 2.0 2.0.1
wp-super-minify / includes / min / lib / Minify / JS / ClosureCompiler.php

ClosureCompiler.php in WP Super Minify • Minify, Compress and Cache HTML, CSS & JavaScript trunk, at includes/min/lib/Minify/JS/ClosureCompiler.php

238 lines 7.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Class Minify_JS_ClosureCompiler
4 * @package Minify
5 */
6
7 /**
8 * Minify Javascript using Google's Closure Compiler API
9 *
10 * @link http://code.google.com/closure/compiler/
11 * @package Minify
12 * @author Stephen Clay <steve@mrclay.org>
13 *
14 * @todo can use a stream wrapper to unit test this?
15 */
16 class Minify_JS_ClosureCompiler
17 {
18
19 /**
20 * @var string The option key for the maximum POST byte size
21 */
22 const OPTION_MAX_BYTES = 'maxBytes';
23
24 /**
25 * @var string The option key for additional params. @see __construct
26 */
27 const OPTION_ADDITIONAL_OPTIONS = 'additionalParams';
28
29 /**
30 * @var string The option key for the fallback Minifier
31 */
32 const OPTION_FALLBACK_FUNCTION = 'fallbackFunc';
33
34 /**
35 * @var string The option key for the service URL
36 */
37 const OPTION_COMPILER_URL = 'compilerUrl';
38
39 /**
40 * @var int The default maximum POST byte size according to https://developers.google.com/closure/compiler/docs/api-ref
41 */
42 const DEFAULT_MAX_BYTES = 200000;
43
44 /**
45 * @var string[] $DEFAULT_OPTIONS The default options to pass to the compiler service
46 *
47 * @note This would be a constant if PHP allowed it
48 */
49 private static $DEFAULT_OPTIONS = array(
50 'output_format' => 'text',
51 'compilation_level' => 'SIMPLE_OPTIMIZATIONS'
52 );
53
54 /**
55 * @var string $url URL of compiler server. defaults to Google's
56 */
57 protected $serviceUrl = 'https://closure-compiler.appspot.com/compile';
58
59 /**
60 * @var int $maxBytes The maximum JS size that can be sent to the compiler server in bytes
61 */
62 protected $maxBytes = self::DEFAULT_MAX_BYTES;
63
64 /**
65 * @var string[] $additionalOptions Additional options to pass to the compiler service
66 */
67 protected $additionalOptions = array();
68
69 /**
70 * @var callable Function to minify JS if service fails. Default is JSMin
71 */
72 protected $fallbackMinifier = array('JSMin\\JSMin', 'minify');
73
74 /**
75 * Minify JavaScript code via HTTP request to a Closure Compiler API
76 *
77 * @param string $js input code
78 * @param array $options Options passed to __construct(). @see __construct
79 *
80 * @return string
81 */
82 public static function minify($js, array $options = array())
83 {
84 $obj = new self($options);
85
86 return $obj->min($js);
87 }
88
89 /**
90 * @param array $options Options with keys available below:
91 *
92 * fallbackFunc : (callable) function to minify if service unavailable. Default is JSMin.
93 *
94 * compilerUrl : (string) URL to closure compiler server
95 *
96 * maxBytes : (int) The maximum amount of bytes to be sent as js_code in the POST request.
97 * Defaults to 200000.
98 *
99 * additionalParams : (string[]) Additional parameters to pass to the compiler server. Can be anything named
100 * in https://developers.google.com/closure/compiler/docs/api-ref except for js_code and
101 * output_info
102 */
103 public function __construct(array $options = array())
104 {
105 if (isset($options[self::OPTION_FALLBACK_FUNCTION])) {
106 $this->fallbackMinifier = $options[self::OPTION_FALLBACK_FUNCTION];
107 }
108 if (isset($options[self::OPTION_COMPILER_URL])) {
109 $this->serviceUrl = $options[self::OPTION_COMPILER_URL];
110 }
111 if (isset($options[self::OPTION_ADDITIONAL_OPTIONS]) && is_array($options[self::OPTION_ADDITIONAL_OPTIONS])) {
112 $this->additionalOptions = $options[self::OPTION_ADDITIONAL_OPTIONS];
113 }
114 if (isset($options[self::OPTION_MAX_BYTES])) {
115 $this->maxBytes = (int) $options[self::OPTION_MAX_BYTES];
116 }
117 }
118
119 /**
120 * Call the service to perform the minification
121 *
122 * @param string $js JavaScript code
123 * @return string
124 * @throws Minify_JS_ClosureCompiler_Exception
125 */
126 public function min($js)
127 {
128 $postBody = $this->buildPostBody($js);
129
130 if ($this->maxBytes > 0) {
131 $bytes = (function_exists('mb_strlen') && ((int)ini_get('mbstring.func_overload') & 2))
132 ? mb_strlen($postBody, '8bit')
133 : strlen($postBody);
134 if ($bytes > $this->maxBytes) {
135 throw new Minify_JS_ClosureCompiler_Exception(
136 'POST content larger than ' . $this->maxBytes . ' bytes'
137 );
138 }
139 }
140
141 $response = $this->getResponse($postBody);
142
143 if (preg_match('/^Error\(\d\d?\):/', $response)) {
144 if (is_callable($this->fallbackMinifier)) {
145 // use fallback
146 $response = "/* Received errors from Closure Compiler API:\n$response"
147 . "\n(Using fallback minifier)\n*/\n";
148 $response .= call_user_func($this->fallbackMinifier, $js);
149 } else {
150 throw new Minify_JS_ClosureCompiler_Exception($response);
151 }
152 }
153
154 if ($response === '') {
155 $errors = $this->getResponse($this->buildPostBody($js, true));
156 throw new Minify_JS_ClosureCompiler_Exception($errors);
157 }
158
159 return $response;
160 }
161
162 /**
163 * Get the response for a given POST body
164 *
165 * @param string $postBody
166 * @return string
167 * @throws Minify_JS_ClosureCompiler_Exception
168 */
169 protected function getResponse($postBody)
170 {
171 $allowUrlFopen = preg_match('/1|yes|on|true/i', ini_get('allow_url_fopen'));
172
173 if ($allowUrlFopen) {
174 $contents = file_get_contents($this->serviceUrl, false, stream_context_create(array(
175 'http' => array(
176 'method' => 'POST',
177 'compilation_level' => 'SIMPLE',
178 'output_format' => 'text',
179 'output_info' => 'compiled_code',
180 'header' => "Content-type: application/x-www-form-urlencoded\r\nConnection: close\r\n",
181 'content' => $postBody,
182 'max_redirects' => 0,
183 'timeout' => 15,
184 )
185 )));
186 } elseif (defined('CURLOPT_POST')) {
187 $ch = curl_init($this->serviceUrl);
188 curl_setopt($ch, CURLOPT_POST, true);
189 curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
190 curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-type: application/x-www-form-urlencoded'));
191 curl_setopt($ch, CURLOPT_POSTFIELDS, $postBody);
192 curl_setopt($ch, CURLOPT_FOLLOWLOCATION, false);
193 curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 15);
194 $contents = curl_exec($ch);
195 curl_close($ch);
196 } else {
197 throw new Minify_JS_ClosureCompiler_Exception(
198 "Could not make HTTP request: allow_url_open is false and cURL not available"
199 );
200 }
201
202 if (false === $contents) {
203 throw new Minify_JS_ClosureCompiler_Exception(
204 "No HTTP response from server"
205 );
206 }
207
208 return trim($contents);
209 }
210
211 /**
212 * Build a POST request body
213 *
214 * @param string $js JavaScript code
215 * @param bool $returnErrors
216 * @return string
217 */
218 protected function buildPostBody($js, $returnErrors = false)
219 {
220 return http_build_query(
221 array_merge(
222 self::$DEFAULT_OPTIONS,
223 $this->additionalOptions,
224 array(
225 'js_code' => $js,
226 'output_info' => ($returnErrors ? 'errors' : 'compiled_code')
227 )
228 ),
229 null,
230 '&'
231 );
232 }
233 }
234
235 class Minify_JS_ClosureCompiler_Exception extends Exception
236 {
237 }
238