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