/[drupal]/contributions/sandbox/jamesandres/shrinksafe/shrinksafe.module
ViewVC logotype

Contents of /contributions/sandbox/jamesandres/shrinksafe/shrinksafe.module

Parent Directory Parent Directory | Revision Log Revision Log | View Revision Graph Revision Graph


Revision 1.1 - (show annotations) (download) (as text)
Thu Nov 1 21:53:27 2007 UTC (2 years ago) by jamesandres
Branch: MAIN
CVS Tags: HEAD
File MIME type: text/x-php
Initial import of the shrinksafe module
1 <?php
2 // $Id: shrinksafe.module,v 1.0 2007/10/29 16:34:25 jamesandres Exp $
3 /**
4 * @file shrinksafe.module
5 * Drupal wrapper and admin panel for the ShrinkSafe JS packer.
6 *
7 * @author James Andres
8 * @version 1.0
9 * @since October 29th, 2007
10 */
11
12 /**
13 * Implementation of hook_help()
14 */
15 function shrinksafe_help($section) {
16 switch ($section) {
17 case 'admin/modules#description':
18 return t('Drupal wrapper and admin panel for the ShrinkSafe JS packer.');
19 }
20 }
21
22 /**
23 * Implementation of hook_menu()
24 **/
25 function shrinksafe_menu($may_cache) {
26 if ($may_cache) {
27 $items[] = array(
28 'path' => 'admin/settings/shrinksafe',
29 'title' => t('ShrinkSafe JS Packer'),
30 'callback' => 'drupal_get_form',
31 'callback arguments' => array('shrinksafe_admin'),
32 'access' => user_access('access administration pages'),
33 );
34 }
35
36 return (array) $items;
37 }
38
39 /**
40 * Get all Javascripts using ShrinkSafe, this function is mostly a duplicate
41 * of drupal_get_js().
42 **/
43 function drupal_get_js_shrinksafe($scope = 'header', $javascript = NULL) {
44 $output = '';
45 if (is_null($javascript)) {
46 $javascript = drupal_add_js(NULL, NULL, $scope);
47 }
48
49 $shrinksafe_enabled = variable_get('shrinksafe_enabled', 0);
50 $shrinksafe_aggregate = variable_get('shrinksafe_aggregate', 0);
51 $aggregate_paths = array();
52
53 foreach ($javascript as $type => $data) {
54 if (!$data) continue;
55
56 switch ($type) {
57 case 'setting':
58 $output .= '<script type="text/javascript">Drupal.extend({ settings: '. drupal_to_js(call_user_func_array('array_merge_recursive', $data)) ." });</script>\n";
59 break;
60 case 'inline':
61 foreach ($data as $info) {
62 $output .= '<script type="text/javascript"'. ($info['defer'] ? ' defer="defer"' : '') .'>'. $info['code'] ."</script>\n";
63 }
64 break;
65
66 // This is the only case for which we can use ShrinkSafe
67 default:
68 foreach ($data as $path => $info) {
69 if ($shrinksafe_enabled && $info['cache']) {
70 $cache_path = shrinksafe_compress_and_cache($path);
71 } else {
72 $cache_path = $path;
73 }
74 if ($shrinksafe_enabled && $shrinksafe_aggregate && $cache_path != $path && !_is_excluded_from_aggregation($cache_path)) {
75 $aggregate_paths[] = $cache_path;
76 } else {
77 $output .= '<script type="text/javascript"'. ($info['defer'] ? ' defer="defer"' : '') .' src="'. check_url(base_path() . $cache_path) . ($info['cache'] ? '' : '?'. time()) ."\"></script>\n";
78 }
79 }
80 }
81 }
82
83 // If there are some paths to aggregate then group them and print the JS line.
84 if ($aggregate_paths) {
85 $aggregate_path = shrinksafe_aggregate($aggregate_paths);
86 $output .= '<script type="text/javascript" src="'. check_url(base_path() . $aggregate_path) ."\"></script>\n";
87 }
88
89 return $output;
90 }
91
92 /**
93 * Get the shrinksafe compressed version of the file and cache it, if it exists.
94 **/
95 function shrinksafe_compress_and_cache($path) {
96 $cache = cache_get('shrinksafe');
97
98 if ($cache->data) {
99 $cache = unserialize($cache->data);
100 } else {
101 $cache = array();
102 }
103
104 if ($cache[$path]['timestamp'] < strtotime("-15 minutes")) {
105 $filename = basename($path, '.js');
106 $key = md5_file($path); // Unique key of the file.
107
108 $temp_path = file_directory_temp() . '/shrinksafe';
109 if (!is_dir($temp_path)) {
110 // FIXME: This is PHP 5.0 only, not a big deal, but it could be less
111 // version dependant.
112 mkdir($temp_path, 0755, TRUE);
113 }
114
115 // This cache path will be unique for each file and will change when the
116 // original file changes.
117 $cache_path = $temp_path . '/' . $filename . '.' . $key . '.pak.js';
118
119 // Only attempt to run ShrinkSafe if the cache doesn't exist, the
120 // ShrinkSafe JAR exists, we have a running JRE, and the original file
121 // was not packed by the Dean Edwards packer (because repacking is
122 // inefficient).
123 if (($shrinksafe_jar = _shrinksafe_get_path()) && ($java_path = _shrinksafe_get_java_path()) && !_is_packed_by_dean_edwards($path)) {
124 $path_exists = (file_exists($path) && is_readable($path));
125 if (!file_exists($cache_path) && $path_exists) {
126 $shrinksafe_jar = escapeshellarg($shrinksafe_jar);
127
128 $path_escaped = escapeshellarg(realpath($path));
129 $cache_path_escaped = escapeshellarg($cache_path);
130
131 $command = "$java_path -jar $shrinksafe_jar -c $path_escaped > $cache_path_escaped 2>&1";
132 watchdog('shrinksafe', "Repacking javascript cache: '$path' --> '$cache_path' using command `$command`");
133 system($command);
134 } else if (!$path_exists) {
135 $cache_path = $path;
136 }
137
138 $cache[$path]['timestamp'] = time();
139 $cache[$path]['cache_path'] = $cache_path;
140 } else {
141 $cache[$path]['timestamp'] = time();
142 $cache[$path]['cache_path'] = $path;
143 }
144
145 cache_set('shrinksafe', 'cache', serialize($cache));
146 }
147
148 if (file_exists($cache[$path]['cache_path'])) {
149 return $cache[$path]['cache_path'];
150 } else {
151 // If something really unexpected happens, return what we were given.
152 return $path;
153 }
154 }
155
156 /**
157 * Aggregate multiple JS paths into one giant JS.
158 **/
159 function shrinksafe_aggregate($paths) {
160 // Since the path's are already compressed JS files with names that are unique
161 // to the MD5 of their file contents, it's enough to use the file names since
162 // they will change when their contents change.
163 $key = md5(implode('-', $paths));
164
165 $temp_path = file_directory_temp() . '/shrinksafe';
166 if (!is_dir($temp_path)) {
167 mkdir($temp_path, 0755, TRUE);
168 }
169
170 // Keep the .pak.js extension so the cache clearing tool can delete it later.
171 $aggregate_path = $temp_path . '/aggregate-' . $key . '.pak.js';
172
173 if (!file_exists($aggregate_path)) {
174 foreach ($paths as $path) {
175 $text .= "/* -- $path -- */\n";
176 if (file_exists($path) && is_readable($path)) {
177 $text .= file_get_contents($path);
178 } else {
179 $text .= "/* ERROR: Not able to read '$path' */";
180 }
181 $text .= "\n\n";
182 }
183
184 file_put_contents($aggregate_path, $text);
185 }
186
187 return $aggregate_path;
188 }
189
190 /**
191 * Determines if a file has been packed by the Dean Edwards Javascript Packer.
192 * Repacking these files with the ShrinkSafe packer actually makes them larger!
193 **/
194 function _is_packed_by_dean_edwards($path) {
195 $text = file_get_contents($path);
196
197 // This is rather hacky, but it seems to reliably find every DE packed file.
198 if (strstr($text, 'eval(function(p,a,c,k')) {
199 return TRUE;
200 }
201
202 return FALSE;
203 }
204
205 /**
206 * Determines if the path is excluded from the aggregator.
207 **/
208 function _is_excluded_from_aggregation($path) {
209 $excluded = explode(' ', variable_get('shrinksafe_aggregate_exclude', NULL) . ' drupal');
210 $excluded = array_diff(array_unique(array_map('trim', $excluded)), array(''));
211 $excluded = implode('|', $excluded);
212
213 $filename = basename($path);
214
215 // drupal_set_message("excluded: $excluded, path: $path");
216
217 return preg_match("/($excluded)\.[A-Za-z0-9]{32}\.pak\.js/", $filename);
218 }
219
220 /**
221 * The admin form.
222 **/
223 function shrinksafe_admin() {
224 $shrinksafe_path = _shrinksafe_get_path();
225
226 if ($_POST['op'] == t('Clear Javascript Cache')) {
227 _shrinksafe_clear_cache();
228 drupal_set_message(t('Javascript cache cleared.'));
229 }
230
231 $form['clear_cache'] = array(
232 '#type' => 'button',
233 '#value' => t('Clear Javascript Cache'),
234 );
235
236 $form['shrinksafe_enabled'] = array(
237 '#type' => 'radios',
238 '#title' => t('Enable ShrinkSafe JS Packer'),
239 '#default_value' => variable_get('shrinksafe_enabled', 0),
240 '#options' => array(0 => t('Disabled'), 1 => t('Enabled')),
241 '#description' => t('ShrinkSafe cannot be enabled until both paths have a status of OK.'),
242 '#required' => TRUE,
243 );
244
245 $form['shrinksafe_path'] = array(
246 '#type' => 'textfield',
247 '#title' => t('Location of ShrinkSafe JAR'),
248 '#size' => 60,
249 '#maxlength' => 255,
250 '#default_value' => $shrinksafe_path,
251 '#description' => theme('shrinksafe_status_box', $shrinksafe_path, t('ShrinkSafe Path: OK'), t('ShrinkSafe Path: ERROR')),
252 '#required' => TRUE,
253 );
254
255 $java_path = _shrinksafe_get_java_path();
256 $form['java_path'] = array(
257 '#type' => 'textfield',
258 '#title' => t('Location of Java Executable'),
259 '#size' => 60,
260 '#maxlength' => 255,
261 '#default_value' => $java_path,
262 '#description' => theme('shrinksafe_status_box', $java_path, t('Java Path: OK'), t('Java Path: ERROR')),
263 '#required' => TRUE,
264 );
265
266 $form['shrinksafe_aggregate'] = array(
267 '#type' => 'checkbox',
268 '#title' => t('Aggregate Packed Files'),
269 '#default_value' => variable_get('shrinksafe_aggregate', FALSE),
270 '#description' => t('This will allow single threaded browsers to download your javascript faster, but if most pages on your site include different javascript files this may cause needless downloads and caching.'),
271 );
272
273 // FIXME: Space seperating this list isn't very good. It should be based on
274 // full paths. Plus the lack of .js extensions will confuse a lot of people.
275 $form['shrinksafe_aggregate_exclude'] = array(
276 '#type' => 'textfield',
277 '#title' => t('Aggregator exclusions'),
278 '#size' => 40,
279 '#default_value' => variable_get('shrinksafe_aggregate_exclude', NULL),
280 '#description' => t('A list of space seperated files, without the .js extensions, that you want excluded from the aggregator. <em>The file drupal.js is ALWAYS excluded due to compatability issues.</em>.'),
281 '#required' => FALSE,
282 );
283
284 $form['submit'] = array(
285 '#type' => 'submit',
286 '#value' => t('Save'),
287 );
288
289 return $form;
290 }
291
292 /**
293 * Theme's a small status box.
294 **/
295 function theme_shrinksafe_status_box($path, $text_ok, $text_fail) {
296 if ($path) {
297 $bg = 'green';
298 $fg = 'white';
299 $text = $text_ok;
300 } else {
301 $bg = 'red';
302 $fg = 'black';
303 $text = $text_fail;
304 }
305 return '<div style="width: 200px; margin-top: 5px; background-color: ' . $bg . '; padding: 3px; font-weight: bold; color: ' . $fg . ';">' . $text . '</div>';
306 }
307
308 /**
309 * Implementation of hook_validate()
310 **/
311 function shrinksafe_admin_validate($form_id, $form_values) {
312 if ($form_values['shrinksafe_enabled']) {
313 if (!is_file($form_values['shrinksafe_path'])) {
314 form_set_error('shrinksafe_path', t('ShrinkSafe JAR could not be found at that path. Please locate the JAR file and set the path correctly before enabling.'));
315 } else if (!is_file($form_values['java_path']) || !is_executable($form_values['java_path'])) {
316 form_set_error('java_path', t('Java JRE could not be found at that path. Please set the path correctly before enabling.'));
317 }
318 }
319 }
320
321 /**
322 * Implementation of hook_submit()
323 **/
324 function shrinksafe_admin_submit($form_id, $form_values) {
325 variable_set('shrinksafe_path', $form_values['shrinksafe_path']);
326 variable_set('java_path', $form_values['java_path']);
327 variable_set('shrinksafe_enabled', $form_values['shrinksafe_enabled']);
328 variable_set('shrinksafe_aggregate', $form_values['shrinksafe_aggregate']);
329 variable_set('shrinksafe_aggregate_exclude', $form_values['shrinksafe_aggregate_exclude']);
330
331 drupal_set_message(t('Settings saved.'));
332 }
333
334 /**
335 * Get the path to the ShrinkSafe JAR, if it exists.
336 **/
337 function _shrinksafe_get_path() {
338 $shrinksafe_path = variable_get('shrinksafe_path', drupal_get_path('module', 'shrinksafe') . '/shrinksafe/custom_rhino.jar');
339
340 if (file_exists($shrinksafe_path) && is_readable($shrinksafe_path)) {
341 return $shrinksafe_path;
342 }
343
344 return FALSE;
345 }
346
347 /**
348 * Get the path to the Java JRE, if it exists.
349 **/
350 function _shrinksafe_get_java_path() {
351 $java_path = variable_get('java_path', NULL);
352
353 // Be nice, support some OSS.
354 $jres = array('/usr/bin/java', '/usr/bin/kaffe');
355
356 $default_jre = FALSE;
357 foreach ($jres as $jre) {
358 if (is_file($java_default) && is_executable($java_default)) {
359 $default_jre = $jre; break;
360 }
361 }
362
363 // This will return, in order of precedence: java_path, default_jre, or FALSE.
364 return variable_get('java_path', $default_jre);
365 }
366
367 /**
368 * Clear the Javascript cache.
369 **/
370 function _shrinksafe_clear_cache() {
371 $temp_path = file_directory_temp() . '/shrinksafe';
372 $contents = scandir($temp_path);
373
374 // Chop off . and ..
375 $contents = array_slice($contents, 2);
376 foreach ($contents as $path) {
377 if (preg_match('/[A-Za-z0-9]{32}.pak.js$/', $path)) {
378 unlink($temp_path . '/' . $path);
379 }
380 }
381
382 // Don't forget to wipe the Drupal caches.
383 cache_clear_all('shrinksafe', 'cache');
384 }

  ViewVC Help
Powered by ViewVC 1.1.2