Fast Velocity Minify - Version 2.7.8

Version Description

[2020.02.06] = * updated PHP Minify with full support for PHP 7.4 * added try, catch wrappers for merged javacript files with console log errors (instead of letting the browser stop execution on error) * improved compatibility with windows servers * improved compatibility for font paths with some themes

Download this release

Release Info

Developer Alignak
Plugin Icon 128x128 Fast Velocity Minify
Version 2.7.8
Comparing to
See all releases

Code changes from version 2.7.4 to 2.7.8

fvm.php CHANGED
@@ -5,7 +5,7 @@ Plugin URI: http://fastvelocity.com
5
  Description: Improve your speed score on GTmetrix, Pingdom Tools and Google PageSpeed Insights by merging and minifying CSS and JavaScript files into groups, compressing HTML and other speed optimizations.
6
  Author: Raul Peixoto
7
  Author URI: http://fastvelocity.com
8
- Version: 2.7.4
9
  License: GPL2
10
 
11
  ------------------------------------------------------------------------
@@ -303,7 +303,7 @@ function fastvelocity_admintoolbar() {
303
 
304
  # function to list all cache files
305
  function fastvelocity_min_files_callback() {
306
-
307
  # must be able to cleanup cache
308
  if (!current_user_can('manage_options')) {
309
  wp_die( __('You do not have sufficient permissions to access this page.'));
@@ -317,9 +317,9 @@ function fastvelocity_min_files_callback() {
317
 
318
  # inspect directory with opendir, since glob might not be available in some systems
319
  clearstatcache();
320
- if ($handle = opendir($cachedir.'/')) {
321
  while (false !== ($file = readdir($handle))) {
322
- $file = $cachedir.'/'.$file;
323
  $ext = pathinfo($file, PATHINFO_EXTENSION);
324
  if (in_array($ext, array('js', 'css'))) {
325
  $log = ''; if (file_exists($file.'.txt')) { $log = file_get_contents($file.'.txt'); }
@@ -1302,7 +1302,7 @@ for($i=0,$l=count($header);$i<$l;$i++) {
1302
  $hash = 'header-'.hash('adler32',implode('',$header[$i]['handles']));
1303
 
1304
  # create cache files and urls
1305
- $file = $cachedir.'/'.$hash.'.min.js';
1306
  $file_url = fvm_get_protocol($cachedirurl.'/'.$hash.'.min.js');
1307
 
1308
  # generate a new cache file
@@ -1346,16 +1346,23 @@ for($i=0,$l=count($header);$i<$l;$i++) {
1346
  continue;
1347
  }
1348
 
 
 
 
 
 
 
 
1349
  # append code to merged file
1350
- $code.= $res['code'];
1351
  $log.= $res['log'];
1352
 
1353
- # Add extra data from wp_add_inline_script before
1354
  if (!empty( $wp_scripts->registered[$handle]->extra)) {
1355
- if (!empty( $wp_scripts->registered[$handle]->extra['before'])){
1356
- $code.= PHP_EOL . implode(PHP_EOL, $wp_scripts->registered[$handle]->extra['before']);
1357
  }
1358
- }
1359
 
1360
  # consider dependencies on handles with an empty src
1361
  } else {
@@ -1496,7 +1503,7 @@ for($i=0,$l=count($footer);$i<$l;$i++) {
1496
  $hash = 'footer-'.hash('adler32',implode('',$footer[$i]['handles']));
1497
 
1498
  # create cache files and urls
1499
- $file = $cachedir.'/'.$hash.'.min.js';
1500
  $file_url = fvm_get_protocol($cachedirurl.'/'.$hash.'.min.js');
1501
 
1502
  # generate a new cache file
@@ -1541,14 +1548,21 @@ for($i=0,$l=count($footer);$i<$l;$i++) {
1541
  continue;
1542
  }
1543
 
1544
- # append code to merged file
1545
- $code.= $res['code'];
1546
- $log.= $res['log'];
1547
-
1548
  # Add extra data from wp_add_inline_script before
1549
  if (!empty($wp_scripts->registered[$handle]->extra)){
1550
  if (!empty($wp_scripts->registered[$handle]->extra['before'])){
1551
- $code.= PHP_EOL.implode(PHP_EOL, $wp_scripts->registered[$handle]->extra['before']);
 
 
 
 
 
 
 
 
 
 
 
1552
  }
1553
  }
1554
 
@@ -1976,7 +1990,7 @@ for($i=0,$l=count($header);$i<$l;$i++) {
1976
  $hash = 'header-'.hash('adler32',implode('',$header[$i]['handles']).$inline_css_hash);
1977
 
1978
  # create cache files and urls
1979
- $file = $cachedir.'/'.$hash.'.min.css';
1980
  $file_url = fvm_get_protocol($cachedirurl.'/'.$hash.'.min.css');
1981
 
1982
  # generate a new cache file
@@ -2366,7 +2380,7 @@ for($i=0,$l=count($footer);$i<$l;$i++) {
2366
  $hash = 'footer-'.hash('adler32',implode('',$footer[$i]['handles']).$inline_css_hash);
2367
 
2368
  # create cache files and urls
2369
- $file = $cachedir.'/'.$hash.'.min.css';
2370
  $file_url = fvm_get_protocol($cachedirurl.'/'.$hash.'.min.css');
2371
 
2372
  # generate a new cache file
5
  Description: Improve your speed score on GTmetrix, Pingdom Tools and Google PageSpeed Insights by merging and minifying CSS and JavaScript files into groups, compressing HTML and other speed optimizations.
6
  Author: Raul Peixoto
7
  Author URI: http://fastvelocity.com
8
+ Version: 2.7.8
9
  License: GPL2
10
 
11
  ------------------------------------------------------------------------
303
 
304
  # function to list all cache files
305
  function fastvelocity_min_files_callback() {
306
+
307
  # must be able to cleanup cache
308
  if (!current_user_can('manage_options')) {
309
  wp_die( __('You do not have sufficient permissions to access this page.'));
317
 
318
  # inspect directory with opendir, since glob might not be available in some systems
319
  clearstatcache();
320
+ if ($handle = opendir($cachedir.fastvelocity_get_os_slash())) {
321
  while (false !== ($file = readdir($handle))) {
322
+ $file = $cachedir.fastvelocity_get_os_slash().$file;
323
  $ext = pathinfo($file, PATHINFO_EXTENSION);
324
  if (in_array($ext, array('js', 'css'))) {
325
  $log = ''; if (file_exists($file.'.txt')) { $log = file_get_contents($file.'.txt'); }
1302
  $hash = 'header-'.hash('adler32',implode('',$header[$i]['handles']));
1303
 
1304
  # create cache files and urls
1305
+ $file = $cachedir.fastvelocity_get_os_slash().$hash.'.min.js';
1306
  $file_url = fvm_get_protocol($cachedirurl.'/'.$hash.'.min.js');
1307
 
1308
  # generate a new cache file
1346
  continue;
1347
  }
1348
 
1349
+ # Add extra data from wp_add_inline_script before
1350
+ if (!empty( $wp_scripts->registered[$handle]->extra)) {
1351
+ if (!empty( $wp_scripts->registered[$handle]->extra['before'])){
1352
+ $code.= PHP_EOL . fastvelocity_try_catch_wrap(implode(PHP_EOL, $wp_scripts->registered[$handle]->extra['before']));
1353
+ }
1354
+ }
1355
+
1356
  # append code to merged file
1357
+ $code.= fastvelocity_try_catch_wrap($res['code']);
1358
  $log.= $res['log'];
1359
 
1360
+ # Add extra data from wp_add_inline_script after
1361
  if (!empty( $wp_scripts->registered[$handle]->extra)) {
1362
+ if (!empty( $wp_scripts->registered[$handle]->extra['after'])){
1363
+ $code.= PHP_EOL . fastvelocity_try_catch_wrap(implode(PHP_EOL, $wp_scripts->registered[$handle]->extra['after']));
1364
  }
1365
+ }
1366
 
1367
  # consider dependencies on handles with an empty src
1368
  } else {
1503
  $hash = 'footer-'.hash('adler32',implode('',$footer[$i]['handles']));
1504
 
1505
  # create cache files and urls
1506
+ $file = $cachedir.fastvelocity_get_os_slash().$hash.'.min.js';
1507
  $file_url = fvm_get_protocol($cachedirurl.'/'.$hash.'.min.js');
1508
 
1509
  # generate a new cache file
1548
  continue;
1549
  }
1550
 
 
 
 
 
1551
  # Add extra data from wp_add_inline_script before
1552
  if (!empty($wp_scripts->registered[$handle]->extra)){
1553
  if (!empty($wp_scripts->registered[$handle]->extra['before'])){
1554
+ $code.= PHP_EOL . fastvelocity_try_catch_wrap(implode(PHP_EOL, $wp_scripts->registered[$handle]->extra['before']));
1555
+ }
1556
+ }
1557
+
1558
+ # append code to merged file
1559
+ $code.= fastvelocity_try_catch_wrap($res['code']);
1560
+ $log.= $res['log'];
1561
+
1562
+ # Add extra data from wp_add_inline_script after
1563
+ if (!empty($wp_scripts->registered[$handle]->extra)){
1564
+ if (!empty($wp_scripts->registered[$handle]->extra['after'])){
1565
+ $code.= PHP_EOL . fastvelocity_try_catch_wrap(implode(PHP_EOL, $wp_scripts->registered[$handle]->extra['after']));
1566
  }
1567
  }
1568
 
1990
  $hash = 'header-'.hash('adler32',implode('',$header[$i]['handles']).$inline_css_hash);
1991
 
1992
  # create cache files and urls
1993
+ $file = $cachedir.fastvelocity_get_os_slash().$hash.'.min.css';
1994
  $file_url = fvm_get_protocol($cachedirurl.'/'.$hash.'.min.css');
1995
 
1996
  # generate a new cache file
2380
  $hash = 'footer-'.hash('adler32',implode('',$footer[$i]['handles']).$inline_css_hash);
2381
 
2382
  # create cache files and urls
2383
+ $file = $cachedir.fastvelocity_get_os_slash().$hash.'.min.css';
2384
  $file_url = fvm_get_protocol($cachedirurl.'/'.$hash.'.min.css');
2385
 
2386
  # generate a new cache file
inc/functions-cache.php CHANGED
@@ -1,6 +1,19 @@
1
  <?php
2
 
3
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4
  # Fix the permission bits on generated files
5
  function fastvelocity_fix_permission_bits($file){
6
  if(function_exists('stat') && fvm_function_available('stat')) {
@@ -23,7 +36,7 @@ function fastvelocity_fix_permission_bits($file){
23
  if ($perms != ($perms & ~umask())){
24
  $folder_parts = explode( '/', substr( $file, strlen(dirname($file)) + 1 ) );
25
  for ( $i = 1, $c = count( $folder_parts ); $i <= $c; $i++ ) {
26
- @chmod(dirname($file) . '/' . implode( '/', array_slice( $folder_parts, 0, $i ) ), $perms );
27
  }
28
  }
29
  }
@@ -36,8 +49,8 @@ function fastvelocity_fix_permission_bits($file){
36
  function fvm_cachepath() {
37
 
38
  # custom directory
39
- $fvm_change_cache_path = trim(rtrim(get_option('fastvelocity_min_change_cache_path', ''), '/'));
40
- $fvm_change_cache_base = trim(rtrim(get_option('fastvelocity_min_change_cache_base_url', ''), '/'));
41
  $upload = array();
42
 
43
  if(strlen($fvm_change_cache_path) > 1 && strlen($fvm_change_cache_base) > 10 && is_dir($fvm_change_cache_path) && is_writable($fvm_change_cache_path)) {
@@ -53,13 +66,13 @@ if(strlen($fvm_change_cache_path) > 1 && strlen($fvm_change_cache_base) > 10 &&
53
  $ctime = get_option('fvm-last-cache-update', '0');
54
 
55
  # create
56
- $uploadsdir = str_ireplace('cache/cache', 'cache', $upload['basedir'].'/cache');
57
  $uploadsurl = str_ireplace('cache/cache', 'cache', $upload['baseurl'].'/cache');
58
- $cachebase = $uploadsdir.'/fvm/'.$ctime;
59
  $cachebaseurl = $uploadsurl.'/fvm/'.$ctime;
60
- $cachedir = $cachebase.'/out';
61
- $tmpdir = $cachebase.'/tmp';
62
- $headerdir = $cachebase.'/header';
63
  $cachedirurl = $cachebaseurl.'/out';
64
 
65
  # get permissions from uploads directory
@@ -74,9 +87,9 @@ foreach ($dirs as $target) {
74
  if(!is_dir($target)) {
75
  if (@mkdir($target, $dir_perms, true)){
76
  if ($dir_perms != ($dir_perms & ~umask())){
77
- $folder_parts = explode( '/', substr($target, strlen(dirname($target)) + 1 ));
78
  for ($i = 1, $c = count($folder_parts ); $i <= $c; $i++){
79
- @chmod(dirname($target) . '/' . implode( '/', array_slice( $folder_parts, 0, $i ) ), $dir_perms );
80
  }
81
  }
82
  } else {
@@ -141,7 +154,7 @@ function fvm_purge_old() {
141
  while (false !== ($d = readdir($handle))) {
142
  if (strcmp($d, '.')==0 || strcmp($d, '..')==0) { continue; }
143
  if($d != $ctime && (is_numeric($d) && $d <= $expires)) {
144
- $dir = $cachebaseparent.'/'.$d;
145
  if(is_dir($dir)) {
146
  fastvelocity_rrmdir($dir);
147
  if(is_dir($dir)) { @rmdir($dir); }
@@ -177,7 +190,7 @@ function fastvelocity_purge_all_global() {
177
  function fvm_get_transient($key) {
178
  $cachepath = fvm_cachepath();
179
  $tmpdir = $cachepath['tmpdir'];
180
- $f = $tmpdir.'/'.$key.'.transient';
181
  clearstatcache();
182
  if(file_exists($f)) {
183
  return file_get_contents($f);
@@ -191,7 +204,7 @@ function fvm_set_transient($key, $code) {
191
  if(is_null($code) || empty($code)) { return false; }
192
  $cachepath = fvm_cachepath();
193
  $tmpdir = $cachepath['tmpdir'];
194
- $f = $tmpdir.'/'.$key.'.transient';
195
  file_put_contents($f, $code);
196
  fastvelocity_fix_permission_bits($f);
197
  return true;
1
  <?php
2
 
3
 
4
+ # get windows or unix slashes
5
+ function fastvelocity_get_os_slash() {
6
+
7
+ if(fvm_server_is_windows() === false) {
8
+ $slash = '/'; # unix
9
+ } else {
10
+ $slash = '\\'; # windows
11
+ }
12
+
13
+ return $slash;
14
+ }
15
+
16
+
17
  # Fix the permission bits on generated files
18
  function fastvelocity_fix_permission_bits($file){
19
  if(function_exists('stat') && fvm_function_available('stat')) {
36
  if ($perms != ($perms & ~umask())){
37
  $folder_parts = explode( '/', substr( $file, strlen(dirname($file)) + 1 ) );
38
  for ( $i = 1, $c = count( $folder_parts ); $i <= $c; $i++ ) {
39
+ @chmod(dirname($file) . fastvelocity_get_os_slash() . implode( fastvelocity_get_os_slash(), array_slice( $folder_parts, 0, $i ) ), $perms );
40
  }
41
  }
42
  }
49
  function fvm_cachepath() {
50
 
51
  # custom directory
52
+ $fvm_change_cache_path = trim(rtrim(get_option('fastvelocity_min_change_cache_path', ''), fastvelocity_get_os_slash()));
53
+ $fvm_change_cache_base = trim(rtrim(get_option('fastvelocity_min_change_cache_base_url', ''), fastvelocity_get_os_slash()));
54
  $upload = array();
55
 
56
  if(strlen($fvm_change_cache_path) > 1 && strlen($fvm_change_cache_base) > 10 && is_dir($fvm_change_cache_path) && is_writable($fvm_change_cache_path)) {
66
  $ctime = get_option('fvm-last-cache-update', '0');
67
 
68
  # create
69
+ $uploadsdir = str_ireplace('cache'.fastvelocity_get_os_slash().'cache', 'cache', $upload['basedir'].fastvelocity_get_os_slash().'cache');
70
  $uploadsurl = str_ireplace('cache/cache', 'cache', $upload['baseurl'].'/cache');
71
+ $cachebase = $uploadsdir.fastvelocity_get_os_slash().'fvm'.fastvelocity_get_os_slash().$ctime;
72
  $cachebaseurl = $uploadsurl.'/fvm/'.$ctime;
73
+ $cachedir = $cachebase.fastvelocity_get_os_slash().'out';
74
+ $tmpdir = $cachebase.fastvelocity_get_os_slash().'tmp';
75
+ $headerdir = $cachebase.fastvelocity_get_os_slash().'header';
76
  $cachedirurl = $cachebaseurl.'/out';
77
 
78
  # get permissions from uploads directory
87
  if(!is_dir($target)) {
88
  if (@mkdir($target, $dir_perms, true)){
89
  if ($dir_perms != ($dir_perms & ~umask())){
90
+ $folder_parts = explode( fastvelocity_get_os_slash(), substr($target, strlen(dirname($target)) + 1 ));
91
  for ($i = 1, $c = count($folder_parts ); $i <= $c; $i++){
92
+ @chmod(dirname($target) . fastvelocity_get_os_slash() . implode( fastvelocity_get_os_slash(), array_slice( $folder_parts, 0, $i ) ), $dir_perms );
93
  }
94
  }
95
  } else {
154
  while (false !== ($d = readdir($handle))) {
155
  if (strcmp($d, '.')==0 || strcmp($d, '..')==0) { continue; }
156
  if($d != $ctime && (is_numeric($d) && $d <= $expires)) {
157
+ $dir = $cachebaseparent.fastvelocity_get_os_slash().$d;
158
  if(is_dir($dir)) {
159
  fastvelocity_rrmdir($dir);
160
  if(is_dir($dir)) { @rmdir($dir); }
190
  function fvm_get_transient($key) {
191
  $cachepath = fvm_cachepath();
192
  $tmpdir = $cachepath['tmpdir'];
193
+ $f = $tmpdir.fastvelocity_get_os_slash().$key.'.transient';
194
  clearstatcache();
195
  if(file_exists($f)) {
196
  return file_get_contents($f);
204
  if(is_null($code) || empty($code)) { return false; }
205
  $cachepath = fvm_cachepath();
206
  $tmpdir = $cachepath['tmpdir'];
207
+ $f = $tmpdir.fastvelocity_get_os_slash().$key.'.transient';
208
  file_put_contents($f, $code);
209
  fastvelocity_fix_permission_bits($f);
210
  return true;
inc/functions.php CHANGED
@@ -110,6 +110,11 @@ function fastvelocity_plugin_uninstall() {
110
  }
111
 
112
 
 
 
 
 
 
113
 
114
  # detect external or internal scripts
115
  function fvm_is_local_domain($src) {
@@ -351,9 +356,14 @@ global $wp_domain, $fvm_debug;
351
  $css = fastvelocity_min_remove_utf8_bom($css);
352
 
353
  # fix url paths
354
- if(!empty($url)) {
 
 
355
  $css = preg_replace("/url\(\s*['\"]?(?!data:)(?!http)(?![\/'\"])(.+?)['\"]?\s*\)/ui", "url(".dirname($url)."/$1)", $css);
356
- }
 
 
 
357
 
358
  # remove query strings from fonts (for better seo, but add a small cache buster based on most recent updates)
359
  $ctime = get_option('fvm-last-cache-update', '0'); # last update or zero
110
  }
111
 
112
 
113
+ # try catch wrapper for merged javascript
114
+ function fastvelocity_try_catch_wrap($js) {
115
+ return 'try{'.PHP_EOL . $js . PHP_EOL . '}' . PHP_EOL . 'catch(e){console.error("An error has occurred: "+e.message);}'.PHP_EOL;
116
+ }
117
+
118
 
119
  # detect external or internal scripts
120
  function fvm_is_local_domain($src) {
356
  $css = fastvelocity_min_remove_utf8_bom($css);
357
 
358
  # fix url paths
359
+ if(!empty($url)) {
360
+ $matches = array(); preg_match_all("/url\(\s*['\"]?(?!data:)(?!http)(?![\/'\"])(.+?)['\"]?\s*\)/ui", $css, $matches);
361
+ foreach($matches[1] as $a) { $b = trim($a); if($b != $a) { $css = str_replace($a, $b, $css); } }
362
  $css = preg_replace("/url\(\s*['\"]?(?!data:)(?!http)(?![\/'\"])(.+?)['\"]?\s*\)/ui", "url(".dirname($url)."/$1)", $css);
363
+ }
364
+
365
+ # no utf8 garbage
366
+ $css = str_ireplace('@charset "UTF-8";', '', $css);
367
 
368
  # remove query strings from fonts (for better seo, but add a small cache buster based on most recent updates)
369
  $ctime = get_option('fvm-last-cache-update', '0'); # last update or zero
libs/matthiasmullie/minify/bin/minifycss CHANGED
@@ -1,45 +1,45 @@
1
- #!/usr/bin/env php
2
- <?php
3
- use MatthiasMullie\Minify;
4
-
5
- // command line utility to minify CSS
6
- if (file_exists(__DIR__ . '/../../../autoload.php')) {
7
- // if composer install
8
- require_once __DIR__ . '/../../../autoload.php';
9
- } else {
10
- require_once __DIR__ . '/../src/Minify.php';
11
- require_once __DIR__ . '/../src/CSS.php';
12
- require_once __DIR__ . '/../src/Exception.php';
13
- }
14
-
15
- error_reporting(E_ALL);
16
- // check PHP setup for cli arguments
17
- if (!isset($_SERVER['argv']) && !isset($argv)) {
18
- fwrite(STDERR, 'Please enable the "register_argc_argv" directive in your php.ini' . PHP_EOL);
19
- exit(1);
20
- } elseif (!isset($argv)) {
21
- $argv = $_SERVER['argv'];
22
- }
23
- // check if path to file given
24
- if (!isset($argv[1])) {
25
- fwrite(STDERR, 'Argument expected: path to file' . PHP_EOL);
26
- exit(1);
27
- }
28
- // check if script run in cli environment
29
- if ('cli' !== php_sapi_name()) {
30
- fwrite(STDERR, $argv[1] . ' must be run in the command line' . PHP_EOL);
31
- exit(1);
32
- }
33
- // check if source file exists
34
- if (!file_exists($argv[1])) {
35
- fwrite(STDERR, 'Source file "' . $argv[1] . '" not found' . PHP_EOL);
36
- exit(1);
37
- }
38
-
39
- try {
40
- $minifier = new Minify\CSS($argv[1]);
41
- echo $minifier->minify();
42
- } catch (Exception $e) {
43
- fwrite(STDERR, $e->getMessage(), PHP_EOL);
44
- exit(1);
45
- }
1
+ #!/usr/bin/env php
2
+ <?php
3
+ use MatthiasMullie\Minify;
4
+
5
+ // command line utility to minify CSS
6
+ if (file_exists(__DIR__ . '/../../../autoload.php')) {
7
+ // if composer install
8
+ require_once __DIR__ . '/../../../autoload.php';
9
+ } else {
10
+ require_once __DIR__ . '/../src/Minify.php';
11
+ require_once __DIR__ . '/../src/CSS.php';
12
+ require_once __DIR__ . '/../src/Exception.php';
13
+ }
14
+
15
+ error_reporting(E_ALL);
16
+ // check PHP setup for cli arguments
17
+ if (!isset($_SERVER['argv']) && !isset($argv)) {
18
+ fwrite(STDERR, 'Please enable the "register_argc_argv" directive in your php.ini' . PHP_EOL);
19
+ exit(1);
20
+ } elseif (!isset($argv)) {
21
+ $argv = $_SERVER['argv'];
22
+ }
23
+ // check if path to file given
24
+ if (!isset($argv[1])) {
25
+ fwrite(STDERR, 'Argument expected: path to file' . PHP_EOL);
26
+ exit(1);
27
+ }
28
+ // check if script run in cli environment
29
+ if ('cli' !== php_sapi_name()) {
30
+ fwrite(STDERR, $argv[1] . ' must be run in the command line' . PHP_EOL);
31
+ exit(1);
32
+ }
33
+ // check if source file exists
34
+ if (!file_exists($argv[1])) {
35
+ fwrite(STDERR, 'Source file "' . $argv[1] . '" not found' . PHP_EOL);
36
+ exit(1);
37
+ }
38
+
39
+ try {
40
+ $minifier = new Minify\CSS($argv[1]);
41
+ echo $minifier->minify();
42
+ } catch (Exception $e) {
43
+ fwrite(STDERR, $e->getMessage(), PHP_EOL);
44
+ exit(1);
45
+ }
libs/matthiasmullie/minify/bin/minifyjs CHANGED
@@ -1,45 +1,45 @@
1
- #!/usr/bin/env php
2
- <?php
3
- use MatthiasMullie\Minify;
4
-
5
- // command line utility to minify JS
6
- if (file_exists(__DIR__ . '/../../../autoload.php')) {
7
- // if composer install
8
- require_once __DIR__ . '/../../../autoload.php';
9
- } else {
10
- require_once __DIR__ . '/../src/Minify.php';
11
- require_once __DIR__ . '/../src/JS.php';
12
- require_once __DIR__ . '/../src/Exception.php';
13
- }
14
-
15
- error_reporting(E_ALL);
16
- // check PHP setup for cli arguments
17
- if (!isset($_SERVER['argv']) && !isset($argv)) {
18
- fwrite(STDERR, 'Please enable the "register_argc_argv" directive in your php.ini' . PHP_EOL);
19
- exit(1);
20
- } elseif (!isset($argv)) {
21
- $argv = $_SERVER['argv'];
22
- }
23
- // check if path to file given
24
- if (!isset($argv[1])) {
25
- fwrite(STDERR, 'Argument expected: path to file' . PHP_EOL);
26
- exit(1);
27
- }
28
- // check if script run in cli environment
29
- if ('cli' !== php_sapi_name()) {
30
- fwrite(STDERR, $argv[1] . ' must be run in the command line' . PHP_EOL);
31
- exit(1);
32
- }
33
- // check if source file exists
34
- if (!file_exists($argv[1])) {
35
- fwrite(STDERR, 'Source file "' . $argv[1] . '" not found' . PHP_EOL);
36
- exit(1);
37
- }
38
-
39
- try {
40
- $minifier = new Minify\JS($argv[1]);
41
- echo $minifier->minify();
42
- } catch (Exception $e) {
43
- fwrite(STDERR, $e->getMessage(), PHP_EOL);
44
- exit(1);
45
- }
1
+ #!/usr/bin/env php
2
+ <?php
3
+ use MatthiasMullie\Minify;
4
+
5
+ // command line utility to minify JS
6
+ if (file_exists(__DIR__ . '/../../../autoload.php')) {
7
+ // if composer install
8
+ require_once __DIR__ . '/../../../autoload.php';
9
+ } else {
10
+ require_once __DIR__ . '/../src/Minify.php';
11
+ require_once __DIR__ . '/../src/JS.php';
12
+ require_once __DIR__ . '/../src/Exception.php';
13
+ }
14
+
15
+ error_reporting(E_ALL);
16
+ // check PHP setup for cli arguments
17
+ if (!isset($_SERVER['argv']) && !isset($argv)) {
18
+ fwrite(STDERR, 'Please enable the "register_argc_argv" directive in your php.ini' . PHP_EOL);
19
+ exit(1);
20
+ } elseif (!isset($argv)) {
21
+ $argv = $_SERVER['argv'];
22
+ }
23
+ // check if path to file given
24
+ if (!isset($argv[1])) {
25
+ fwrite(STDERR, 'Argument expected: path to file' . PHP_EOL);
26
+ exit(1);
27
+ }
28
+ // check if script run in cli environment
29
+ if ('cli' !== php_sapi_name()) {
30
+ fwrite(STDERR, $argv[1] . ' must be run in the command line' . PHP_EOL);
31
+ exit(1);
32
+ }
33
+ // check if source file exists
34
+ if (!file_exists($argv[1])) {
35
+ fwrite(STDERR, 'Source file "' . $argv[1] . '" not found' . PHP_EOL);
36
+ exit(1);
37
+ }
38
+
39
+ try {
40
+ $minifier = new Minify\JS($argv[1]);
41
+ echo $minifier->minify();
42
+ } catch (Exception $e) {
43
+ fwrite(STDERR, $e->getMessage(), PHP_EOL);
44
+ exit(1);
45
+ }
libs/matthiasmullie/minify/data/js/keywords_after.txt CHANGED
@@ -1,7 +1,7 @@
1
- in
2
- public
3
- extends
4
- private
5
- protected
6
- implements
7
  instanceof
1
+ in
2
+ public
3
+ extends
4
+ private
5
+ protected
6
+ implements
7
  instanceof
libs/matthiasmullie/minify/data/js/keywords_before.txt CHANGED
@@ -1,26 +1,26 @@
1
- do
2
- in
3
- let
4
- new
5
- var
6
- case
7
- else
8
- enum
9
- void
10
- with
11
- class
12
- const
13
- yield
14
- delete
15
- export
16
- import
17
- public
18
- static
19
- typeof
20
- extends
21
- package
22
- private
23
- function
24
- protected
25
- implements
26
  instanceof
1
+ do
2
+ in
3
+ let
4
+ new
5
+ var
6
+ case
7
+ else
8
+ enum
9
+ void
10
+ with
11
+ class
12
+ const
13
+ yield
14
+ delete
15
+ export
16
+ import
17
+ public
18
+ static
19
+ typeof
20
+ extends
21
+ package
22
+ private
23
+ function
24
+ protected
25
+ implements
26
  instanceof
libs/matthiasmullie/minify/data/js/keywords_reserved.txt CHANGED
@@ -1,63 +1,63 @@
1
- do
2
- if
3
- in
4
- for
5
- let
6
- new
7
- try
8
- var
9
- case
10
- else
11
- enum
12
- eval
13
- null
14
- this
15
- true
16
- void
17
- with
18
- break
19
- catch
20
- class
21
- const
22
- false
23
- super
24
- throw
25
- while
26
- yield
27
- delete
28
- export
29
- import
30
- public
31
- return
32
- static
33
- switch
34
- typeof
35
- default
36
- extends
37
- finally
38
- package
39
- private
40
- continue
41
- debugger
42
- function
43
- arguments
44
- interface
45
- protected
46
- implements
47
- instanceof
48
- abstract
49
- boolean
50
- byte
51
- char
52
- double
53
- final
54
- float
55
- goto
56
- int
57
- long
58
- native
59
- short
60
- synchronized
61
- throws
62
- transient
63
  volatile
1
+ do
2
+ if
3
+ in
4
+ for
5
+ let
6
+ new
7
+ try
8
+ var
9
+ case
10
+ else
11
+ enum
12
+ eval
13
+ null
14
+ this
15
+ true
16
+ void
17
+ with
18
+ break
19
+ catch
20
+ class
21
+ const
22
+ false
23
+ super
24
+ throw
25
+ while
26
+ yield
27
+ delete
28
+ export
29
+ import
30
+ public
31
+ return
32
+ static
33
+ switch
34
+ typeof
35
+ default
36
+ extends
37
+ finally
38
+ package
39
+ private
40
+ continue
41
+ debugger
42
+ function
43
+ arguments
44
+ interface
45
+ protected
46
+ implements
47
+ instanceof
48
+ abstract
49
+ boolean
50
+ byte
51
+ char
52
+ double
53
+ final
54
+ float
55
+ goto
56
+ int
57
+ long
58
+ native
59
+ short
60
+ synchronized
61
+ throws
62
+ transient
63
  volatile
libs/matthiasmullie/minify/data/js/operators.txt CHANGED
@@ -1,46 +1,46 @@
1
- +
2
- -
3
- *
4
- /
5
- %
6
- =
7
- +=
8
- -=
9
- *=
10
- /=
11
- %=
12
- <<=
13
- >>=
14
- >>>=
15
- &=
16
- ^=
17
- |=
18
- &
19
- |
20
- ^
21
- ~
22
- <<
23
- >>
24
- >>>
25
- ==
26
- ===
27
- !=
28
- !==
29
- >
30
- <
31
- >=
32
- <=
33
- &&
34
- ||
35
- !
36
- .
37
- [
38
- ]
39
- ?
40
- :
41
- ,
42
- ;
43
- (
44
- )
45
- {
46
  }
1
+ +
2
+ -
3
+ *
4
+ /
5
+ %
6
+ =
7
+ +=
8
+ -=
9
+ *=
10
+ /=
11
+ %=
12
+ <<=
13
+ >>=
14
+ >>>=
15
+ &=
16
+ ^=
17
+ |=
18
+ &
19
+ |
20
+ ^
21
+ ~
22
+ <<
23
+ >>
24
+ >>>
25
+ ==
26
+ ===
27
+ !=
28
+ !==
29
+ >
30
+ <
31
+ >=
32
+ <=
33
+ &&
34
+ ||
35
+ !
36
+ .
37
+ [
38
+ ]
39
+ ?
40
+ :
41
+ ,
42
+ ;
43
+ (
44
+ )
45
+ {
46
  }
libs/matthiasmullie/minify/data/js/operators_after.txt CHANGED
@@ -1,43 +1,43 @@
1
- +
2
- -
3
- *
4
- /
5
- %
6
- =
7
- +=
8
- -=
9
- *=
10
- /=
11
- %=
12
- <<=
13
- >>=
14
- >>>=
15
- &=
16
- ^=
17
- |=
18
- &
19
- |
20
- ^
21
- <<
22
- >>
23
- >>>
24
- ==
25
- ===
26
- !=
27
- !==
28
- >
29
- <
30
- >=
31
- <=
32
- &&
33
- ||
34
- .
35
- [
36
- ]
37
- ?
38
- :
39
- ,
40
- ;
41
- (
42
- )
43
  }
1
+ +
2
+ -
3
+ *
4
+ /
5
+ %
6
+ =
7
+ +=
8
+ -=
9
+ *=
10
+ /=
11
+ %=
12
+ <<=
13
+ >>=
14
+ >>>=
15
+ &=
16
+ ^=
17
+ |=
18
+ &
19
+ |
20
+ ^
21
+ <<
22
+ >>
23
+ >>>
24
+ ==
25
+ ===
26
+ !=
27
+ !==
28
+ >
29
+ <
30
+ >=
31
+ <=
32
+ &&
33
+ ||
34
+ .
35
+ [
36
+ ]
37
+ ?
38
+ :
39
+ ,
40
+ ;
41
+ (
42
+ )
43
  }
libs/matthiasmullie/minify/data/js/operators_before.txt CHANGED
@@ -1,43 +1,43 @@
1
- +
2
- -
3
- *
4
- /
5
- %
6
- =
7
- +=
8
- -=
9
- *=
10
- /=
11
- %=
12
- <<=
13
- >>=
14
- >>>=
15
- &=
16
- ^=
17
- |=
18
- &
19
- |
20
- ^
21
- ~
22
- <<
23
- >>
24
- >>>
25
- ==
26
- ===
27
- !=
28
- !==
29
- >
30
- <
31
- >=
32
- <=
33
- &&
34
- ||
35
- !
36
- .
37
- [
38
- ?
39
- :
40
- ,
41
- ;
42
- (
43
- {
1
+ +
2
+ -
3
+ *
4
+ /
5
+ %
6
+ =
7
+ +=
8
+ -=
9
+ *=
10
+ /=
11
+ %=
12
+ <<=
13
+ >>=
14
+ >>>=
15
+ &=
16
+ ^=
17
+ |=
18
+ &
19
+ |
20
+ ^
21
+ ~
22
+ <<
23
+ >>
24
+ >>>
25
+ ==
26
+ ===
27
+ !=
28
+ !==
29
+ >
30
+ <
31
+ >=
32
+ <=
33
+ &&
34
+ ||
35
+ !
36
+ .
37
+ [
38
+ ?
39
+ :
40
+ ,
41
+ ;
42
+ (
43
+ {
libs/matthiasmullie/minify/src/CSS.php CHANGED
@@ -1,736 +1,751 @@
1
- <?php
2
- /**
3
- * CSS Minifier
4
- *
5
- * Please report bugs on https://github.com/matthiasmullie/minify/issues
6
- *
7
- * @author Matthias Mullie <minify@mullie.eu>
8
- * @copyright Copyright (c) 2012, Matthias Mullie. All rights reserved
9
- * @license MIT License
10
- */
11
-
12
- namespace MatthiasMullie\Minify;
13
-
14
- use MatthiasMullie\Minify\Exceptions\FileImportException;
15
- use MatthiasMullie\PathConverter\ConverterInterface;
16
- use MatthiasMullie\PathConverter\Converter;
17
-
18
- /**
19
- * CSS minifier
20
- *
21
- * Please report bugs on https://github.com/matthiasmullie/minify/issues
22
- *
23
- * @package Minify
24
- * @author Matthias Mullie <minify@mullie.eu>
25
- * @author Tijs Verkoyen <minify@verkoyen.eu>
26
- * @copyright Copyright (c) 2012, Matthias Mullie. All rights reserved
27
- * @license MIT License
28
- */
29
- class CSS extends Minify
30
- {
31
- /**
32
- * @var int maximum inport size in kB
33
- */
34
- protected $maxImportSize = 5;
35
-
36
- /**
37
- * @var string[] valid import extensions
38
- */
39
- protected $importExtensions = array(
40
- 'gif' => 'data:image/gif',
41
- 'png' => 'data:image/png',
42
- 'jpe' => 'data:image/jpeg',
43
- 'jpg' => 'data:image/jpeg',
44
- 'jpeg' => 'data:image/jpeg',
45
- 'svg' => 'data:image/svg+xml',
46
- 'woff' => 'data:application/x-font-woff',
47
- 'tif' => 'image/tiff',
48
- 'tiff' => 'image/tiff',
49
- 'xbm' => 'image/x-xbitmap',
50
- );
51
-
52
- /**
53
- * Set the maximum size if files to be imported.
54
- *
55
- * Files larger than this size (in kB) will not be imported into the CSS.
56
- * Importing files into the CSS as data-uri will save you some connections,
57
- * but we should only import relatively small decorative images so that our
58
- * CSS file doesn't get too bulky.
59
- *
60
- * @param int $size Size in kB
61
- */
62
- public function setMaxImportSize($size)
63
- {
64
- $this->maxImportSize = $size;
65
- }
66
-
67
- /**
68
- * Set the type of extensions to be imported into the CSS (to save network
69
- * connections).
70
- * Keys of the array should be the file extensions & respective values
71
- * should be the data type.
72
- *
73
- * @param string[] $extensions Array of file extensions
74
- */
75
- public function setImportExtensions(array $extensions)
76
- {
77
- $this->importExtensions = $extensions;
78
- }
79
-
80
- /**
81
- * Move any import statements to the top.
82
- *
83
- * @param string $content Nearly finished CSS content
84
- *
85
- * @return string
86
- */
87
- protected function moveImportsToTop($content)
88
- {
89
- if (preg_match_all('/(;?)(@import (?<url>url\()?(?P<quotes>["\']?).+?(?P=quotes)(?(url)\)));?/', $content, $matches)) {
90
- // remove from content
91
- foreach ($matches[0] as $import) {
92
- $content = str_replace($import, '', $content);
93
- }
94
-
95
- // add to top
96
- $content = implode(';', $matches[2]).';'.trim($content, ';');
97
- }
98
-
99
- return $content;
100
- }
101
-
102
- /**
103
- * Combine CSS from import statements.
104
- *
105
- * @import's will be loaded and their content merged into the original file,
106
- * to save HTTP requests.
107
- *
108
- * @param string $source The file to combine imports for
109
- * @param string $content The CSS content to combine imports for
110
- * @param string[] $parents Parent paths, for circular reference checks
111
- *
112
- * @return string
113
- *
114
- * @throws FileImportException
115
- */
116
- protected function combineImports($source, $content, $parents)
117
- {
118
- $importRegexes = array(
119
- // @import url(xxx)
120
- '/
121
- # import statement
122
- @import
123
-
124
- # whitespace
125
- \s+
126
-
127
- # open url()
128
- url\(
129
-
130
- # (optional) open path enclosure
131
- (?P<quotes>["\']?)
132
-
133
- # fetch path
134
- (?P<path>.+?)
135
-
136
- # (optional) close path enclosure
137
- (?P=quotes)
138
-
139
- # close url()
140
- \)
141
-
142
- # (optional) trailing whitespace
143
- \s*
144
-
145
- # (optional) media statement(s)
146
- (?P<media>[^;]*)
147
-
148
- # (optional) trailing whitespace
149
- \s*
150
-
151
- # (optional) closing semi-colon
152
- ;?
153
-
154
- /ix',
155
-
156
- // @import 'xxx'
157
- '/
158
-
159
- # import statement
160
- @import
161
-
162
- # whitespace
163
- \s+
164
-
165
- # open path enclosure
166
- (?P<quotes>["\'])
167
-
168
- # fetch path
169
- (?P<path>.+?)
170
-
171
- # close path enclosure
172
- (?P=quotes)
173
-
174
- # (optional) trailing whitespace
175
- \s*
176
-
177
- # (optional) media statement(s)
178
- (?P<media>[^;]*)
179
-
180
- # (optional) trailing whitespace
181
- \s*
182
-
183
- # (optional) closing semi-colon
184
- ;?
185
-
186
- /ix',
187
- );
188
-
189
- // find all relative imports in css
190
- $matches = array();
191
- foreach ($importRegexes as $importRegex) {
192
- if (preg_match_all($importRegex, $content, $regexMatches, PREG_SET_ORDER)) {
193
- $matches = array_merge($matches, $regexMatches);
194
- }
195
- }
196
-
197
- $search = array();
198
- $replace = array();
199
-
200
- // loop the matches
201
- foreach ($matches as $match) {
202
- // get the path for the file that will be imported
203
- $importPath = dirname($source).'/'.$match['path'];
204
-
205
- // only replace the import with the content if we can grab the
206
- // content of the file
207
- if (!$this->canImportByPath($match['path']) || !$this->canImportFile($importPath)) {
208
- continue;
209
- }
210
-
211
- // check if current file was not imported previously in the same
212
- // import chain.
213
- if (in_array($importPath, $parents)) {
214
- throw new FileImportException('Failed to import file "'.$importPath.'": circular reference detected.');
215
- }
216
-
217
- // grab referenced file & minify it (which may include importing
218
- // yet other @import statements recursively)
219
- $minifier = new static($importPath);
220
- $importContent = $minifier->execute($source, $parents);
221
-
222
- // check if this is only valid for certain media
223
- if (!empty($match['media'])) {
224
- $importContent = '@media '.$match['media'].'{'.$importContent.'}';
225
- }
226
-
227
- // add to replacement array
228
- $search[] = $match[0];
229
- $replace[] = $importContent;
230
- }
231
-
232
- // replace the import statements
233
- return str_replace($search, $replace, $content);
234
- }
235
-
236
- /**
237
- * Import files into the CSS, base64-ized.
238
- *
239
- * @url(image.jpg) images will be loaded and their content merged into the
240
- * original file, to save HTTP requests.
241
- *
242
- * @param string $source The file to import files for
243
- * @param string $content The CSS content to import files for
244
- *
245
- * @return string
246
- */
247
- protected function importFiles($source, $content)
248
- {
249
- $regex = '/url\((["\']?)(.+?)\\1\)/i';
250
- if ($this->importExtensions && preg_match_all($regex, $content, $matches, PREG_SET_ORDER)) {
251
- $search = array();
252
- $replace = array();
253
-
254
- // loop the matches
255
- foreach ($matches as $match) {
256
- $extension = substr(strrchr($match[2], '.'), 1);
257
- if ($extension && !array_key_exists($extension, $this->importExtensions)) {
258
- continue;
259
- }
260
-
261
- // get the path for the file that will be imported
262
- $path = $match[2];
263
- $path = dirname($source).'/'.$path;
264
-
265
- // only replace the import with the content if we're able to get
266
- // the content of the file, and it's relatively small
267
- if ($this->canImportFile($path) && $this->canImportBySize($path)) {
268
- // grab content && base64-ize
269
- $importContent = $this->load($path);
270
- $importContent = base64_encode($importContent);
271
-
272
- // build replacement
273
- $search[] = $match[0];
274
- $replace[] = 'url('.$this->importExtensions[$extension].';base64,'.$importContent.')';
275
- }
276
- }
277
-
278
- // replace the import statements
279
- $content = str_replace($search, $replace, $content);
280
- }
281
-
282
- return $content;
283
- }
284
-
285
- /**
286
- * Minify the data.
287
- * Perform CSS optimizations.
288
- *
289
- * @param string[optional] $path Path to write the data to
290
- * @param string[] $parents Parent paths, for circular reference checks
291
- *
292
- * @return string The minified data
293
- */
294
- public function execute($path = null, $parents = array())
295
- {
296
- $content = '';
297
-
298
- // loop CSS data (raw data and files)
299
- foreach ($this->data as $source => $css) {
300
- /*
301
- * Let's first take out strings & comments, since we can't just
302
- * remove whitespace anywhere. If whitespace occurs inside a string,
303
- * we should leave it alone. E.g.:
304
- * p { content: "a test" }
305
- */
306
- $this->extractStrings();
307
- $this->stripComments();
308
- $css = $this->replace($css);
309
-
310
- $css = $this->stripWhitespace($css);
311
- $css = $this->shortenHex($css);
312
- $css = $this->shortenZeroes($css);
313
- $css = $this->shortenFontWeights($css);
314
- $css = $this->stripEmptyTags($css);
315
-
316
- // restore the string we've extracted earlier
317
- $css = $this->restoreExtractedData($css);
318
-
319
- $source = is_int($source) ? '' : $source;
320
- $parents = $source ? array_merge($parents, array($source)) : $parents;
321
- $css = $this->combineImports($source, $css, $parents);
322
- $css = $this->importFiles($source, $css);
323
-
324
- /*
325
- * If we'll save to a new path, we'll have to fix the relative paths
326
- * to be relative no longer to the source file, but to the new path.
327
- * If we don't write to a file, fall back to same path so no
328
- * conversion happens (because we still want it to go through most
329
- * of the move code, which also addresses url() & @import syntax...)
330
- */
331
- $converter = $this->getPathConverter($source, $path ?: $source);
332
- $css = $this->move($converter, $css);
333
-
334
- // combine css
335
- $content .= $css;
336
- }
337
-
338
- $content = $this->moveImportsToTop($content);
339
-
340
- return $content;
341
- }
342
-
343
- /**
344
- * Moving a css file should update all relative urls.
345
- * Relative references (e.g. ../images/image.gif) in a certain css file,
346
- * will have to be updated when a file is being saved at another location
347
- * (e.g. ../../images/image.gif, if the new CSS file is 1 folder deeper).
348
- *
349
- * @param ConverterInterface $converter Relative path converter
350
- * @param string $content The CSS content to update relative urls for
351
- *
352
- * @return string
353
- */
354
- protected function move(ConverterInterface $converter, $content)
355
- {
356
- /*
357
- * Relative path references will usually be enclosed by url(). @import
358
- * is an exception, where url() is not necessary around the path (but is
359
- * allowed).
360
- * This *could* be 1 regular expression, where both regular expressions
361
- * in this array are on different sides of a |. But we're using named
362
- * patterns in both regexes, the same name on both regexes. This is only
363
- * possible with a (?J) modifier, but that only works after a fairly
364
- * recent PCRE version. That's why I'm doing 2 separate regular
365
- * expressions & combining the matches after executing of both.
366
- */
367
- $relativeRegexes = array(
368
- // url(xxx)
369
- '/
370
- # open url()
371
- url\(
372
-
373
- \s*
374
-
375
- # open path enclosure
376
- (?P<quotes>["\'])?
377
-
378
- # fetch path
379
- (?P<path>.+?)
380
-
381
- # close path enclosure
382
- (?(quotes)(?P=quotes))
383
-
384
- \s*
385
-
386
- # close url()
387
- \)
388
-
389
- /ix',
390
-
391
- // @import "xxx"
392
- '/
393
- # import statement
394
- @import
395
-
396
- # whitespace
397
- \s+
398
-
399
- # we don\'t have to check for @import url(), because the
400
- # condition above will already catch these
401
-
402
- # open path enclosure
403
- (?P<quotes>["\'])
404
-
405
- # fetch path
406
- (?P<path>.+?)
407
-
408
- # close path enclosure
409
- (?P=quotes)
410
-
411
- /ix',
412
- );
413
-
414
- // find all relative urls in css
415
- $matches = array();
416
- foreach ($relativeRegexes as $relativeRegex) {
417
- if (preg_match_all($relativeRegex, $content, $regexMatches, PREG_SET_ORDER)) {
418
- $matches = array_merge($matches, $regexMatches);
419
- }
420
- }
421
-
422
- $search = array();
423
- $replace = array();
424
-
425
- // loop all urls
426
- foreach ($matches as $match) {
427
- // determine if it's a url() or an @import match
428
- $type = (strpos($match[0], '@import') === 0 ? 'import' : 'url');
429
-
430
- $url = $match['path'];
431
- if ($this->canImportByPath($url)) {
432
- // attempting to interpret GET-params makes no sense, so let's discard them for awhile
433
- $params = strrchr($url, '?');
434
- $url = $params ? substr($url, 0, -strlen($params)) : $url;
435
-
436
- // fix relative url
437
- $url = $converter->convert($url);
438
-
439
- // now that the path has been converted, re-apply GET-params
440
- $url .= $params;
441
- }
442
-
443
- /*
444
- * Urls with control characters above 0x7e should be quoted.
445
- * According to Mozilla's parser, whitespace is only allowed at the
446
- * end of unquoted urls.
447
- * Urls with `)` (as could happen with data: uris) should also be
448
- * quoted to avoid being confused for the url() closing parentheses.
449
- * And urls with a # have also been reported to cause issues.
450
- * Urls with quotes inside should also remain escaped.
451
- *
452
- * @see https://developer.mozilla.org/nl/docs/Web/CSS/url#The_url()_functional_notation
453
- * @see https://hg.mozilla.org/mozilla-central/rev/14abca4e7378
454
- * @see https://github.com/matthiasmullie/minify/issues/193
455
- */
456
- $url = trim($url);
457
- if (preg_match('/[\s\)\'"#\x{7f}-\x{9f}]/u', $url)) {
458
- $url = $match['quotes'] . $url . $match['quotes'];
459
- }
460
-
461
- // build replacement
462
- $search[] = $match[0];
463
- if ($type === 'url') {
464
- $replace[] = 'url('.$url.')';
465
- } elseif ($type === 'import') {
466
- $replace[] = '@import "'.$url.'"';
467
- }
468
- }
469
-
470
- // replace urls
471
- return str_replace($search, $replace, $content);
472
- }
473
-
474
- /**
475
- * Shorthand hex color codes.
476
- * #FF0000 -> #F00.
477
- *
478
- * @param string $content The CSS content to shorten the hex color codes for
479
- *
480
- * @return string
481
- */
482
- protected function shortenHex($content)
483
- {
484
- $content = preg_replace('/(?<=[: ])#([0-9a-z])\\1([0-9a-z])\\2([0-9a-z])\\3(?=[; }])/i', '#$1$2$3', $content);
485
-
486
- // we can shorten some even more by replacing them with their color name
487
- $colors = array(
488
- '#F0FFFF' => 'azure',
489
- '#F5F5DC' => 'beige',
490
- '#A52A2A' => 'brown',
491
- '#FF7F50' => 'coral',
492
- '#FFD700' => 'gold',
493
- '#808080' => 'gray',
494
- '#008000' => 'green',
495
- '#4B0082' => 'indigo',
496
- '#FFFFF0' => 'ivory',
497
- '#F0E68C' => 'khaki',
498
- '#FAF0E6' => 'linen',
499
- '#800000' => 'maroon',
500
- '#000080' => 'navy',
501
- '#808000' => 'olive',
502
- '#CD853F' => 'peru',
503
- '#FFC0CB' => 'pink',
504
- '#DDA0DD' => 'plum',
505
- '#800080' => 'purple',
506
- '#F00' => 'red',
507
- '#FA8072' => 'salmon',
508
- '#A0522D' => 'sienna',
509
- '#C0C0C0' => 'silver',
510
- '#FFFAFA' => 'snow',
511
- '#D2B48C' => 'tan',
512
- '#FF6347' => 'tomato',
513
- '#EE82EE' => 'violet',
514
- '#F5DEB3' => 'wheat',
515
- );
516
-
517
- return preg_replace_callback(
518
- '/(?<=[: ])('.implode(array_keys($colors), '|').')(?=[; }])/i',
519
- function ($match) use ($colors) {
520
- return $colors[strtoupper($match[0])];
521
- },
522
- $content
523
- );
524
- }
525
-
526
- /**
527
- * Shorten CSS font weights.
528
- *
529
- * @param string $content The CSS content to shorten the font weights for
530
- *
531
- * @return string
532
- */
533
- protected function shortenFontWeights($content)
534
- {
535
- $weights = array(
536
- 'normal' => 400,
537
- 'bold' => 700,
538
- );
539
-
540
- $callback = function ($match) use ($weights) {
541
- return $match[1].$weights[$match[2]];
542
- };
543
-
544
- return preg_replace_callback('/(font-weight\s*:\s*)('.implode('|', array_keys($weights)).')(?=[;}])/', $callback, $content);
545
- }
546
-
547
- /**
548
- * Shorthand 0 values to plain 0, instead of e.g. -0em.
549
- *
550
- * @param string $content The CSS content to shorten the zero values for
551
- *
552
- * @return string
553
- */
554
- protected function shortenZeroes($content)
555
- {
556
- // we don't want to strip units in `calc()` expressions:
557
- // `5px - 0px` is valid, but `5px - 0` is not
558
- // `10px * 0` is valid (equates to 0), and so is `10 * 0px`, but
559
- // `10 * 0` is invalid
560
- // best to just leave `calc()`s alone, even if they could be optimized
561
- // (which is a whole other undertaking, where units & order of
562
- // operations all need to be considered...)
563
- $calcs = $this->findCalcs($content);
564
- $content = str_replace($calcs, array_keys($calcs), $content);
565
-
566
- // reusable bits of code throughout these regexes:
567
- // before & after are used to make sure we don't match lose unintended
568
- // 0-like values (e.g. in #000, or in http://url/1.0)
569
- // units can be stripped from 0 values, or used to recognize non 0
570
- // values (where wa may be able to strip a .0 suffix)
571
- $before = '(?<=[:(, ])';
572
- $after = '(?=[ ,);}])';
573
- $units = '(em|ex|%|px|cm|mm|in|pt|pc|ch|rem|vh|vw|vmin|vmax|vm)';
574
-
575
- // strip units after zeroes (0px -> 0)
576
- // NOTE: it should be safe to remove all units for a 0 value, but in
577
- // practice, Webkit (especially Safari) seems to stumble over at least
578
- // 0%, potentially other units as well. Only stripping 'px' for now.
579
- // @see https://github.com/matthiasmullie/minify/issues/60
580
- $content = preg_replace('/'.$before.'(-?0*(\.0+)?)(?<=0)px'.$after.'/', '\\1', $content);
581
-
582
- // strip 0-digits (.0 -> 0)
583
- $content = preg_replace('/'.$before.'\.0+'.$units.'?'.$after.'/', '0\\1', $content);
584
- // strip trailing 0: 50.10 -> 50.1, 50.10px -> 50.1px
585
- $content = preg_replace('/'.$before.'(-?[0-9]+\.[0-9]+)0+'.$units.'?'.$after.'/', '\\1\\2', $content);
586
- // strip trailing 0: 50.00 -> 50, 50.00px -> 50px
587
- $content = preg_replace('/'.$before.'(-?[0-9]+)\.0+'.$units.'?'.$after.'/', '\\1\\2', $content);
588
- // strip leading 0: 0.1 -> .1, 01.1 -> 1.1
589
- $content = preg_replace('/'.$before.'(-?)0+([0-9]*\.[0-9]+)'.$units.'?'.$after.'/', '\\1\\2\\3', $content);
590
-
591
- // strip negative zeroes (-0 -> 0) & truncate zeroes (00 -> 0)
592
- $content = preg_replace('/'.$before.'-?0+'.$units.'?'.$after.'/', '0\\1', $content);
593
-
594
- // IE doesn't seem to understand a unitless flex-basis value (correct -
595
- // it goes against the spec), so let's add it in again (make it `%`,
596
- // which is only 1 char: 0%, 0px, 0 anything, it's all just the same)
597
- // @see https://developer.mozilla.org/nl/docs/Web/CSS/flex
598
- $content = preg_replace('/flex:([0-9]+\s[0-9]+\s)0([;\}])/', 'flex:${1}0%${2}', $content);
599
- $content = preg_replace('/flex-basis:0([;\}])/', 'flex-basis:0%${1}', $content);
600
-
601
- // restore `calc()` expressions
602
- $content = str_replace(array_keys($calcs), $calcs, $content);
603
-
604
- return $content;
605
- }
606
-
607
- /**
608
- * Strip empty tags from source code.
609
- *
610
- * @param string $content
611
- *
612
- * @return string
613
- */
614
- protected function stripEmptyTags($content)
615
- {
616
- $content = preg_replace('/(?<=^)[^\{\};]+\{\s*\}/', '', $content);
617
- $content = preg_replace('/(?<=(\}|;))[^\{\};]+\{\s*\}/', '', $content);
618
-
619
- return $content;
620
- }
621
-
622
- /**
623
- * Strip comments from source code.
624
- */
625
- protected function stripComments()
626
- {
627
- $this->registerPattern('/\/\*.*?\*\//s', '');
628
- }
629
-
630
- /**
631
- * Strip whitespace.
632
- *
633
- * @param string $content The CSS content to strip the whitespace for
634
- *
635
- * @return string
636
- */
637
- protected function stripWhitespace($content)
638
- {
639
- // remove leading & trailing whitespace
640
- $content = preg_replace('/^\s*/m', '', $content);
641
- $content = preg_replace('/\s*$/m', '', $content);
642
-
643
- // replace newlines with a single space
644
- $content = preg_replace('/\s+/', ' ', $content);
645
-
646
- // remove whitespace around meta characters
647
- // inspired by stackoverflow.com/questions/15195750/minify-compress-css-with-regex
648
- $content = preg_replace('/\s*([\*$~^|]?+=|[{};,>~]|!important\b)\s*/', '$1', $content);
649
- $content = preg_replace('/([\[(:])\s+/', '$1', $content);
650
- $content = preg_replace('/\s+([\]\)])/', '$1', $content);
651
- $content = preg_replace('/\s+(:)(?![^\}]*\{)/', '$1', $content);
652
-
653
- // whitespace around + and - can only be stripped inside some pseudo-
654
- // classes, like `:nth-child(3+2n)`
655
- // not in things like `calc(3px + 2px)`, shorthands like `3px -2px`, or
656
- // selectors like `div.weird- p`
657
- $pseudos = array('nth-child', 'nth-last-child', 'nth-last-of-type', 'nth-of-type');
658
- $content = preg_replace('/:('.implode('|', $pseudos).')\(\s*([+-]?)\s*(.+?)\s*([+-]?)\s*(.*?)\s*\)/', ':$1($2$3$4$5)', $content);
659
-
660
- // remove semicolon/whitespace followed by closing bracket
661
- $content = str_replace(';}', '}', $content);
662
-
663
- return trim($content);
664
- }
665
-
666
- /**
667
- * Find all `calc()` occurrences.
668
- *
669
- * @param string $content The CSS content to find `calc()`s in.
670
- *
671
- * @return string[]
672
- */
673
- protected function findCalcs($content)
674
- {
675
- $results = array();
676
- preg_match_all('/calc(\(.+?)(?=$|;|calc\()/', $content, $matches, PREG_SET_ORDER);
677
-
678
- foreach ($matches as $match) {
679
- $length = strlen($match[1]);
680
- $expr = '';
681
- $opened = 0;
682
-
683
- for ($i = 0; $i < $length; $i++) {
684
- $char = $match[1][$i];
685
- $expr .= $char;
686
- if ($char === '(') {
687
- $opened++;
688
- } elseif ($char === ')' && --$opened === 0) {
689
- break;
690
- }
691
- }
692
-
693
- $results['calc('.count($results).')'] = 'calc'.$expr;
694
- }
695
-
696
- return $results;
697
- }
698
-
699
- /**
700
- * Check if file is small enough to be imported.
701
- *
702
- * @param string $path The path to the file
703
- *
704
- * @return bool
705
- */
706
- protected function canImportBySize($path)
707
- {
708
- return ($size = @filesize($path)) && $size <= $this->maxImportSize * 1024;
709
- }
710
-
711
- /**
712
- * Check if file a file can be imported, going by the path.
713
- *
714
- * @param string $path
715
- *
716
- * @return bool
717
- */
718
- protected function canImportByPath($path)
719
- {
720
- return preg_match('/^(data:|https?:|\\/)/', $path) === 0;
721
- }
722
-
723
- /**
724
- * Return a converter to update relative paths to be relative to the new
725
- * destination.
726
- *
727
- * @param string $source
728
- * @param string $target
729
- *
730
- * @return ConverterInterface
731
- */
732
- protected function getPathConverter($source, $target)
733
- {
734
- return new Converter($source, $target);
735
- }
736
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * CSS Minifier
4
+ *
5
+ * Please report bugs on https://github.com/matthiasmullie/minify/issues
6
+ *
7
+ * @author Matthias Mullie <minify@mullie.eu>
8
+ * @copyright Copyright (c) 2012, Matthias Mullie. All rights reserved
9
+ * @license MIT License
10
+ */
11
+
12
+ namespace MatthiasMullie\Minify;
13
+
14
+ use MatthiasMullie\Minify\Exceptions\FileImportException;
15
+ use MatthiasMullie\PathConverter\ConverterInterface;
16
+ use MatthiasMullie\PathConverter\Converter;
17
+
18
+ /**
19
+ * CSS minifier
20
+ *
21
+ * Please report bugs on https://github.com/matthiasmullie/minify/issues
22
+ *
23
+ * @package Minify
24
+ * @author Matthias Mullie <minify@mullie.eu>
25
+ * @author Tijs Verkoyen <minify@verkoyen.eu>
26
+ * @copyright Copyright (c) 2012, Matthias Mullie. All rights reserved
27
+ * @license MIT License
28
+ */
29
+ class CSS extends Minify
30
+ {
31
+ /**
32
+ * @var int maximum inport size in kB
33
+ */
34
+ protected $maxImportSize = 5;
35
+
36
+ /**
37
+ * @var string[] valid import extensions
38
+ */
39
+ protected $importExtensions = array(
40
+ 'gif' => 'data:image/gif',
41
+ 'png' => 'data:image/png',
42
+ 'jpe' => 'data:image/jpeg',
43
+ 'jpg' => 'data:image/jpeg',
44
+ 'jpeg' => 'data:image/jpeg',
45
+ 'svg' => 'data:image/svg+xml',
46
+ 'woff' => 'data:application/x-font-woff',
47
+ 'tif' => 'image/tiff',
48
+ 'tiff' => 'image/tiff',
49
+ 'xbm' => 'image/x-xbitmap',
50
+ );
51
+
52
+ /**
53
+ * Set the maximum size if files to be imported.
54
+ *
55
+ * Files larger than this size (in kB) will not be imported into the CSS.
56
+ * Importing files into the CSS as data-uri will save you some connections,
57
+ * but we should only import relatively small decorative images so that our
58
+ * CSS file doesn't get too bulky.
59
+ *
60
+ * @param int $size Size in kB
61
+ */
62
+ public function setMaxImportSize($size)
63
+ {
64
+ $this->maxImportSize = $size;
65
+ }
66
+
67
+ /**
68
+ * Set the type of extensions to be imported into the CSS (to save network
69
+ * connections).
70
+ * Keys of the array should be the file extensions & respective values
71
+ * should be the data type.
72
+ *
73
+ * @param string[] $extensions Array of file extensions
74
+ */
75
+ public function setImportExtensions(array $extensions)
76
+ {
77
+ $this->importExtensions = $extensions;
78
+ }
79
+
80
+ /**
81
+ * Move any import statements to the top.
82
+ *
83
+ * @param string $content Nearly finished CSS content
84
+ *
85
+ * @return string
86
+ */
87
+ protected function moveImportsToTop($content)
88
+ {
89
+ if (preg_match_all('/(;?)(@import (?<url>url\()?(?P<quotes>["\']?).+?(?P=quotes)(?(url)\)));?/', $content, $matches)) {
90
+ // remove from content
91
+ foreach ($matches[0] as $import) {
92
+ $content = str_replace($import, '', $content);
93
+ }
94
+
95
+ // add to top
96
+ $content = implode(';', $matches[2]).';'.trim($content, ';');
97
+ }
98
+
99
+ return $content;
100
+ }
101
+
102
+ /**
103
+ * Combine CSS from import statements.
104
+ *
105
+ * @import's will be loaded and their content merged into the original file,
106
+ * to save HTTP requests.
107
+ *
108
+ * @param string $source The file to combine imports for
109
+ * @param string $content The CSS content to combine imports for
110
+ * @param string[] $parents Parent paths, for circular reference checks
111
+ *
112
+ * @return string
113
+ *
114
+ * @throws FileImportException
115
+ */
116
+ protected function combineImports($source, $content, $parents)
117
+ {
118
+ $importRegexes = array(
119
+ // @import url(xxx)
120
+ '/
121
+ # import statement
122
+ @import
123
+
124
+ # whitespace
125
+ \s+
126
+
127
+ # open url()
128
+ url\(
129
+
130
+ # (optional) open path enclosure
131
+ (?P<quotes>["\']?)
132
+
133
+ # fetch path
134
+ (?P<path>.+?)
135
+
136
+ # (optional) close path enclosure
137
+ (?P=quotes)
138
+
139
+ # close url()
140
+ \)
141
+
142
+ # (optional) trailing whitespace
143
+ \s*
144
+
145
+ # (optional) media statement(s)
146
+ (?P<media>[^;]*)
147
+
148
+ # (optional) trailing whitespace
149
+ \s*
150
+
151
+ # (optional) closing semi-colon
152
+ ;?
153
+
154
+ /ix',
155
+
156
+ // @import 'xxx'
157
+ '/
158
+
159
+ # import statement
160
+ @import
161
+
162
+ # whitespace
163
+ \s+
164
+
165
+ # open path enclosure
166
+ (?P<quotes>["\'])
167
+
168
+ # fetch path
169
+ (?P<path>.+?)
170
+
171
+ # close path enclosure
172
+ (?P=quotes)
173
+
174
+ # (optional) trailing whitespace
175
+ \s*
176
+
177
+ # (optional) media statement(s)
178
+ (?P<media>[^;]*)
179
+
180
+ # (optional) trailing whitespace
181
+ \s*
182
+
183
+ # (optional) closing semi-colon
184
+ ;?
185
+
186
+ /ix',
187
+ );
188
+
189
+ // find all relative imports in css
190
+ $matches = array();
191
+ foreach ($importRegexes as $importRegex) {
192
+ if (preg_match_all($importRegex, $content, $regexMatches, PREG_SET_ORDER)) {
193
+ $matches = array_merge($matches, $regexMatches);
194
+ }
195
+ }
196
+
197
+ $search = array();
198
+ $replace = array();
199
+
200
+ // loop the matches
201
+ foreach ($matches as $match) {
202
+ // get the path for the file that will be imported
203
+ $importPath = dirname($source).'/'.$match['path'];
204
+
205
+ // only replace the import with the content if we can grab the
206
+ // content of the file
207
+ if (!$this->canImportByPath($match['path']) || !$this->canImportFile($importPath)) {
208
+ continue;
209
+ }
210
+
211
+ // check if current file was not imported previously in the same
212
+ // import chain.
213
+ if (in_array($importPath, $parents)) {
214
+ throw new FileImportException('Failed to import file "'.$importPath.'": circular reference detected.');
215
+ }
216
+
217
+ // grab referenced file & minify it (which may include importing
218
+ // yet other @import statements recursively)
219
+ $minifier = new static($importPath);
220
+ $minifier->setMaxImportSize($this->maxImportSize);
221
+ $minifier->setImportExtensions($this->importExtensions);
222
+ $importContent = $minifier->execute($source, $parents);
223
+
224
+ // check if this is only valid for certain media
225
+ if (!empty($match['media'])) {
226
+ $importContent = '@media '.$match['media'].'{'.$importContent.'}';
227
+ }
228
+
229
+ // add to replacement array
230
+ $search[] = $match[0];
231
+ $replace[] = $importContent;
232
+ }
233
+
234
+ // replace the import statements
235
+ return str_replace($search, $replace, $content);
236
+ }
237
+
238
+ /**
239
+ * Import files into the CSS, base64-ized.
240
+ *
241
+ * @url(image.jpg) images will be loaded and their content merged into the
242
+ * original file, to save HTTP requests.
243
+ *
244
+ * @param string $source The file to import files for
245
+ * @param string $content The CSS content to import files for
246
+ *
247
+ * @return string
248
+ */
249
+ protected function importFiles($source, $content)
250
+ {
251
+ $regex = '/url\((["\']?)(.+?)\\1\)/i';
252
+ if ($this->importExtensions && preg_match_all($regex, $content, $matches, PREG_SET_ORDER)) {
253
+ $search = array();
254
+ $replace = array();
255
+
256
+ // loop the matches
257
+ foreach ($matches as $match) {
258
+ $extension = substr(strrchr($match[2], '.'), 1);
259
+ if ($extension && !array_key_exists($extension, $this->importExtensions)) {
260
+ continue;
261
+ }
262
+
263
+ // get the path for the file that will be imported
264
+ $path = $match[2];
265
+ $path = dirname($source).'/'.$path;
266
+
267
+ // only replace the import with the content if we're able to get
268
+ // the content of the file, and it's relatively small
269
+ if ($this->canImportFile($path) && $this->canImportBySize($path)) {
270
+ // grab content && base64-ize
271
+ $importContent = $this->load($path);
272
+ $importContent = base64_encode($importContent);
273
+
274
+ // build replacement
275
+ $search[] = $match[0];
276
+ $replace[] = 'url('.$this->importExtensions[$extension].';base64,'.$importContent.')';
277
+ }
278
+ }
279
+
280
+ // replace the import statements
281
+ $content = str_replace($search, $replace, $content);
282
+ }
283
+
284
+ return $content;
285
+ }
286
+
287
+ /**
288
+ * Minify the data.
289
+ * Perform CSS optimizations.
290
+ *
291
+ * @param string[optional] $path Path to write the data to
292
+ * @param string[] $parents Parent paths, for circular reference checks
293
+ *
294
+ * @return string The minified data
295
+ */
296
+ public function execute($path = null, $parents = array())
297
+ {
298
+ $content = '';
299
+
300
+ // loop CSS data (raw data and files)
301
+ foreach ($this->data as $source => $css) {
302
+ /*
303
+ * Let's first take out strings & comments, since we can't just
304
+ * remove whitespace anywhere. If whitespace occurs inside a string,
305
+ * we should leave it alone. E.g.:
306
+ * p { content: "a test" }
307
+ */
308
+ $this->extractStrings();
309
+ $this->stripComments();
310
+ $this->extractCalcs();
311
+ $css = $this->replace($css);
312
+
313
+ $css = $this->stripWhitespace($css);
314
+ $css = $this->shortenColors($css);
315
+ $css = $this->shortenZeroes($css);
316
+ $css = $this->shortenFontWeights($css);
317
+ $css = $this->stripEmptyTags($css);
318
+
319
+ // restore the string we've extracted earlier
320
+ $css = $this->restoreExtractedData($css);
321
+
322
+ $source = is_int($source) ? '' : $source;
323
+ $parents = $source ? array_merge($parents, array($source)) : $parents;
324
+ $css = $this->combineImports($source, $css, $parents);
325
+ $css = $this->importFiles($source, $css);
326
+
327
+ /*
328
+ * If we'll save to a new path, we'll have to fix the relative paths
329
+ * to be relative no longer to the source file, but to the new path.
330
+ * If we don't write to a file, fall back to same path so no
331
+ * conversion happens (because we still want it to go through most
332
+ * of the move code, which also addresses url() & @import syntax...)
333
+ */
334
+ $converter = $this->getPathConverter($source, $path ?: $source);
335
+ $css = $this->move($converter, $css);
336
+
337
+ // combine css
338
+ $content .= $css;
339
+ }
340
+
341
+ $content = $this->moveImportsToTop($content);
342
+
343
+ return $content;
344
+ }
345
+
346
+ /**
347
+ * Moving a css file should update all relative urls.
348
+ * Relative references (e.g. ../images/image.gif) in a certain css file,
349
+ * will have to be updated when a file is being saved at another location
350
+ * (e.g. ../../images/image.gif, if the new CSS file is 1 folder deeper).
351
+ *
352
+ * @param ConverterInterface $converter Relative path converter
353
+ * @param string $content The CSS content to update relative urls for
354
+ *
355
+ * @return string
356
+ */
357
+ protected function move(ConverterInterface $converter, $content)
358
+ {
359
+ /*
360
+ * Relative path references will usually be enclosed by url(). @import
361
+ * is an exception, where url() is not necessary around the path (but is
362
+ * allowed).
363
+ * This *could* be 1 regular expression, where both regular expressions
364
+ * in this array are on different sides of a |. But we're using named
365
+ * patterns in both regexes, the same name on both regexes. This is only
366
+ * possible with a (?J) modifier, but that only works after a fairly
367
+ * recent PCRE version. That's why I'm doing 2 separate regular
368
+ * expressions & combining the matches after executing of both.
369
+ */
370
+ $relativeRegexes = array(
371
+ // url(xxx)
372
+ '/
373
+ # open url()
374
+ url\(
375
+
376
+ \s*
377
+
378
+ # open path enclosure
379
+ (?P<quotes>["\'])?
380
+
381
+ # fetch path
382
+ (?P<path>.+?)
383
+
384
+ # close path enclosure
385
+ (?(quotes)(?P=quotes))
386
+
387
+ \s*
388
+
389
+ # close url()
390
+ \)
391
+
392
+ /ix',
393
+
394
+ // @import "xxx"
395
+ '/
396
+ # import statement
397
+ @import
398
+
399
+ # whitespace
400
+ \s+
401
+
402
+ # we don\'t have to check for @import url(), because the
403
+ # condition above will already catch these
404
+
405
+ # open path enclosure
406
+ (?P<quotes>["\'])
407
+
408
+ # fetch path
409
+ (?P<path>.+?)
410
+
411
+ # close path enclosure
412
+ (?P=quotes)
413
+
414
+ /ix',
415
+ );
416
+
417
+ // find all relative urls in css
418
+ $matches = array();
419
+ foreach ($relativeRegexes as $relativeRegex) {
420
+ if (preg_match_all($relativeRegex, $content, $regexMatches, PREG_SET_ORDER)) {
421
+ $matches = array_merge($matches, $regexMatches);
422
+ }
423
+ }
424
+
425
+ $search = array();
426
+ $replace = array();
427
+
428
+ // loop all urls
429
+ foreach ($matches as $match) {
430
+ // determine if it's a url() or an @import match
431
+ $type = (strpos($match[0], '@import') === 0 ? 'import' : 'url');
432
+
433
+ $url = $match['path'];
434
+ if ($this->canImportByPath($url)) {
435
+ // attempting to interpret GET-params makes no sense, so let's discard them for awhile
436
+ $params = strrchr($url, '?');
437
+ $url = $params ? substr($url, 0, -strlen($params)) : $url;
438
+
439
+ // fix relative url
440
+ $url = $converter->convert($url);
441
+
442
+ // now that the path has been converted, re-apply GET-params
443
+ $url .= $params;
444
+ }
445
+
446
+ /*
447
+ * Urls with control characters above 0x7e should be quoted.
448
+ * According to Mozilla's parser, whitespace is only allowed at the
449
+ * end of unquoted urls.
450
+ * Urls with `)` (as could happen with data: uris) should also be
451
+ * quoted to avoid being confused for the url() closing parentheses.
452
+ * And urls with a # have also been reported to cause issues.
453
+ * Urls with quotes inside should also remain escaped.
454
+ *
455
+ * @see https://developer.mozilla.org/nl/docs/Web/CSS/url#The_url()_functional_notation
456
+ * @see https://hg.mozilla.org/mozilla-central/rev/14abca4e7378
457
+ * @see https://github.com/matthiasmullie/minify/issues/193
458
+ */
459
+ $url = trim($url);
460
+ if (preg_match('/[\s\)\'"#\x{7f}-\x{9f}]/u', $url)) {
461
+ $url = $match['quotes'] . $url . $match['quotes'];
462
+ }
463
+
464
+ // build replacement
465
+ $search[] = $match[0];
466
+ if ($type === 'url') {
467
+ $replace[] = 'url('.$url.')';
468
+ } elseif ($type === 'import') {
469
+ $replace[] = '@import "'.$url.'"';
470
+ }
471
+ }
472
+
473
+ // replace urls
474
+ return str_replace($search, $replace, $content);
475
+ }
476
+
477
+ /**
478
+ * Shorthand hex color codes.
479
+ * #FF0000 -> #F00.
480
+ *
481
+ * @param string $content The CSS content to shorten the hex color codes for
482
+ *
483
+ * @return string
484
+ */
485
+ protected function shortenColors($content)
486
+ {
487
+ $content = preg_replace('/(?<=[: ])#([0-9a-z])\\1([0-9a-z])\\2([0-9a-z])\\3(?:([0-9a-z])\\4)?(?=[; }])/i', '#$1$2$3$4', $content);
488
+
489
+ // remove alpha channel if it's pointless...
490
+ $content = preg_replace('/(?<=[: ])#([0-9a-z]{6})ff?(?=[; }])/i', '#$1', $content);
491
+ $content = preg_replace('/(?<=[: ])#([0-9a-z]{3})f?(?=[; }])/i', '#$1', $content);
492
+
493
+ $colors = array(
494
+ // we can shorten some even more by replacing them with their color name
495
+ '#F0FFFF' => 'azure',
496
+ '#F5F5DC' => 'beige',
497
+ '#A52A2A' => 'brown',
498
+ '#FF7F50' => 'coral',
499
+ '#FFD700' => 'gold',
500
+ '#808080' => 'gray',
501
+ '#008000' => 'green',
502
+ '#4B0082' => 'indigo',
503
+ '#FFFFF0' => 'ivory',
504
+ '#F0E68C' => 'khaki',
505
+ '#FAF0E6' => 'linen',
506
+ '#800000' => 'maroon',
507
+ '#000080' => 'navy',
508
+ '#808000' => 'olive',
509
+ '#CD853F' => 'peru',
510
+ '#FFC0CB' => 'pink',
511
+ '#DDA0DD' => 'plum',
512
+ '#800080' => 'purple',
513
+ '#F00' => 'red',
514
+ '#FA8072' => 'salmon',
515
+ '#A0522D' => 'sienna',
516
+ '#C0C0C0' => 'silver',
517
+ '#FFFAFA' => 'snow',
518
+ '#D2B48C' => 'tan',
519
+ '#FF6347' => 'tomato',
520
+ '#EE82EE' => 'violet',
521
+ '#F5DEB3' => 'wheat',
522
+ // or the other way around
523
+ 'WHITE' => '#fff',
524
+ 'BLACK' => '#000',
525
+ );
526
+
527
+ return preg_replace_callback(
528
+ '/(?<=[: ])('.implode('|', array_keys($colors)).')(?=[; }])/i',
529
+ function ($match) use ($colors) {
530
+ return $colors[strtoupper($match[0])];
531
+ },
532
+ $content
533
+ );
534
+ }
535
+
536
+ /**
537
+ * Shorten CSS font weights.
538
+ *
539
+ * @param string $content The CSS content to shorten the font weights for
540
+ *
541
+ * @return string
542
+ */
543
+ protected function shortenFontWeights($content)
544
+ {
545
+ $weights = array(
546
+ 'normal' => 400,
547
+ 'bold' => 700,
548
+ );
549
+
550
+ $callback = function ($match) use ($weights) {
551
+ return $match[1].$weights[$match[2]];
552
+ };
553
+
554
+ return preg_replace_callback('/(font-weight\s*:\s*)('.implode('|', array_keys($weights)).')(?=[;}])/', $callback, $content);
555
+ }
556
+
557
+ /**
558
+ * Shorthand 0 values to plain 0, instead of e.g. -0em.
559
+ *
560
+ * @param string $content The CSS content to shorten the zero values for
561
+ *
562
+ * @return string
563
+ */
564
+ protected function shortenZeroes($content)
565
+ {
566
+ // we don't want to strip units in `calc()` expressions:
567
+ // `5px - 0px` is valid, but `5px - 0` is not
568
+ // `10px * 0` is valid (equates to 0), and so is `10 * 0px`, but
569
+ // `10 * 0` is invalid
570
+ // we've extracted calcs earlier, so we don't need to worry about this
571
+
572
+ // reusable bits of code throughout these regexes:
573
+ // before & after are used to make sure we don't match lose unintended
574
+ // 0-like values (e.g. in #000, or in http://url/1.0)
575
+ // units can be stripped from 0 values, or used to recognize non 0
576
+ // values (where wa may be able to strip a .0 suffix)
577
+ $before = '(?<=[:(, ])';
578
+ $after = '(?=[ ,);}])';
579
+ $units = '(em|ex|%|px|cm|mm|in|pt|pc|ch|rem|vh|vw|vmin|vmax|vm)';
580
+
581
+ // strip units after zeroes (0px -> 0)
582
+ // NOTE: it should be safe to remove all units for a 0 value, but in
583
+ // practice, Webkit (especially Safari) seems to stumble over at least
584
+ // 0%, potentially other units as well. Only stripping 'px' for now.
585
+ // @see https://github.com/matthiasmullie/minify/issues/60
586
+ $content = preg_replace('/'.$before.'(-?0*(\.0+)?)(?<=0)px'.$after.'/', '\\1', $content);
587
+
588
+ // strip 0-digits (.0 -> 0)
589
+ $content = preg_replace('/'.$before.'\.0+'.$units.'?'.$after.'/', '0\\1', $content);
590
+ // strip trailing 0: 50.10 -> 50.1, 50.10px -> 50.1px
591
+ $content = preg_replace('/'.$before.'(-?[0-9]+\.[0-9]+)0+'.$units.'?'.$after.'/', '\\1\\2', $content);
592
+ // strip trailing 0: 50.00 -> 50, 50.00px -> 50px
593
+ $content = preg_replace('/'.$before.'(-?[0-9]+)\.0+'.$units.'?'.$after.'/', '\\1\\2', $content);
594
+ // strip leading 0: 0.1 -> .1, 01.1 -> 1.1
595
+ $content = preg_replace('/'.$before.'(-?)0+([0-9]*\.[0-9]+)'.$units.'?'.$after.'/', '\\1\\2\\3', $content);
596
+
597
+ // strip negative zeroes (-0 -> 0) & truncate zeroes (00 -> 0)
598
+ $content = preg_replace('/'.$before.'-?0+'.$units.'?'.$after.'/', '0\\1', $content);
599
+
600
+ // IE doesn't seem to understand a unitless flex-basis value (correct -
601
+ // it goes against the spec), so let's add it in again (make it `%`,
602
+ // which is only 1 char: 0%, 0px, 0 anything, it's all just the same)
603
+ // @see https://developer.mozilla.org/nl/docs/Web/CSS/flex
604
+ $content = preg_replace('/flex:([0-9]+\s[0-9]+\s)0([;\}])/', 'flex:${1}0%${2}', $content);
605
+ $content = preg_replace('/flex-basis:0([;\}])/', 'flex-basis:0%${1}', $content);
606
+
607
+ return $content;
608
+ }
609
+
610
+ /**
611
+ * Strip empty tags from source code.
612
+ *
613
+ * @param string $content
614
+ *
615
+ * @return string
616
+ */
617
+ protected function stripEmptyTags($content)
618
+ {
619
+ $content = preg_replace('/(?<=^)[^\{\};]+\{\s*\}/', '', $content);
620
+ $content = preg_replace('/(?<=(\}|;))[^\{\};]+\{\s*\}/', '', $content);
621
+
622
+ return $content;
623
+ }
624
+
625
+ /**
626
+ * Strip comments from source code.
627
+ */
628
+ protected function stripComments()
629
+ {
630
+ // PHP only supports $this inside anonymous functions since 5.4
631
+ $minifier = $this;
632
+ $callback = function ($match) use ($minifier) {
633
+ $count = count($minifier->extracted);
634
+ $placeholder = '/*'.$count.'*/';
635
+ $minifier->extracted[$placeholder] = $match[0];
636
+
637
+ return $placeholder;
638
+ };
639
+ $this->registerPattern('/\n?\/\*(!|.*?@license|.*?@preserve).*?\*\/\n?/s', $callback);
640
+
641
+ $this->registerPattern('/\/\*.*?\*\//s', '');
642
+ }
643
+
644
+ /**
645
+ * Strip whitespace.
646
+ *
647
+ * @param string $content The CSS content to strip the whitespace for
648
+ *
649
+ * @return string
650
+ */
651
+ protected function stripWhitespace($content)
652
+ {
653
+ // remove leading & trailing whitespace
654
+ $content = preg_replace('/^\s*/m', '', $content);
655
+ $content = preg_replace('/\s*$/m', '', $content);
656
+
657
+ // replace newlines with a single space
658
+ $content = preg_replace('/\s+/', ' ', $content);
659
+
660
+ // remove whitespace around meta characters
661
+ // inspired by stackoverflow.com/questions/15195750/minify-compress-css-with-regex
662
+ $content = preg_replace('/\s*([\*$~^|]?+=|[{};,>~]|!important\b)\s*/', '$1', $content);
663
+ $content = preg_replace('/([\[(:>\+])\s+/', '$1', $content);
664
+ $content = preg_replace('/\s+([\]\)>\+])/', '$1', $content);
665
+ $content = preg_replace('/\s+(:)(?![^\}]*\{)/', '$1', $content);
666
+
667
+ // whitespace around + and - can only be stripped inside some pseudo-
668
+ // classes, like `:nth-child(3+2n)`
669
+ // not in things like `calc(3px + 2px)`, shorthands like `3px -2px`, or
670
+ // selectors like `div.weird- p`
671
+ $pseudos = array('nth-child', 'nth-last-child', 'nth-last-of-type', 'nth-of-type');
672
+ $content = preg_replace('/:('.implode('|', $pseudos).')\(\s*([+-]?)\s*(.+?)\s*([+-]?)\s*(.*?)\s*\)/', ':$1($2$3$4$5)', $content);
673
+
674
+ // remove semicolon/whitespace followed by closing bracket
675
+ $content = str_replace(';}', '}', $content);
676
+
677
+ return trim($content);
678
+ }
679
+
680
+ /**
681
+ * Replace all `calc()` occurrences.
682
+ */
683
+ protected function extractCalcs()
684
+ {
685
+ // PHP only supports $this inside anonymous functions since 5.4
686
+ $minifier = $this;
687
+ $callback = function ($match) use ($minifier) {
688
+ $length = strlen($match[1]);
689
+ $expr = '';
690
+ $opened = 0;
691
+
692
+ for ($i = 0; $i < $length; $i++) {
693
+ $char = $match[1][$i];
694
+ $expr .= $char;
695
+ if ($char === '(') {
696
+ $opened++;
697
+ } elseif ($char === ')' && --$opened === 0) {
698
+ break;
699
+ }
700
+ }
701
+ $rest = str_replace($expr, '', $match[1]);
702
+ $expr = trim(substr($expr, 1, -1));
703
+
704
+ $count = count($minifier->extracted);
705
+ $placeholder = 'calc('.$count.')';
706
+ $minifier->extracted[$placeholder] = 'calc('.$expr.')';
707
+
708
+ return $placeholder.$rest;
709
+ };
710
+
711
+ $this->registerPattern('/calc(\(.+?)(?=$|;|calc\()/', $callback);
712
+ }
713
+
714
+ /**
715
+ * Check if file is small enough to be imported.
716
+ *
717
+ * @param string $path The path to the file
718
+ *
719
+ * @return bool
720
+ */
721
+ protected function canImportBySize($path)
722
+ {
723
+ return ($size = @filesize($path)) && $size <= $this->maxImportSize * 1024;
724
+ }
725
+
726
+ /**
727
+ * Check if file a file can be imported, going by the path.
728
+ *
729
+ * @param string $path
730
+ *
731
+ * @return bool
732
+ */
733
+ protected function canImportByPath($path)
734
+ {
735
+ return preg_match('/^(data:|https?:|\\/)/', $path) === 0;
736
+ }
737
+
738
+ /**
739
+ * Return a converter to update relative paths to be relative to the new
740
+ * destination.
741
+ *
742
+ * @param string $source
743
+ * @param string $target
744
+ *
745
+ * @return ConverterInterface
746
+ */
747
+ protected function getPathConverter($source, $target)
748
+ {
749
+ return new Converter($source, $target);
750
+ }
751
+ }
libs/matthiasmullie/minify/src/Exception.php CHANGED
@@ -1,20 +1,20 @@
1
- <?php
2
- /**
3
- * Base Exception
4
- *
5
- * @deprecated Use Exceptions\BasicException instead
6
- *
7
- * @author Matthias Mullie <minify@mullie.eu>
8
- */
9
- namespace MatthiasMullie\Minify;
10
-
11
- /**
12
- * Base Exception Class
13
- * @deprecated Use Exceptions\BasicException instead
14
- *
15
- * @package Minify
16
- * @author Matthias Mullie <minify@mullie.eu>
17
- */
18
- abstract class Exception extends \Exception
19
- {
20
- }
1
+ <?php
2
+ /**
3
+ * Base Exception
4
+ *
5
+ * @deprecated Use Exceptions\BasicException instead
6
+ *
7
+ * @author Matthias Mullie <minify@mullie.eu>
8
+ */
9
+ namespace MatthiasMullie\Minify;
10
+
11
+ /**
12
+ * Base Exception Class
13
+ * @deprecated Use Exceptions\BasicException instead
14
+ *
15
+ * @package Minify
16
+ * @author Matthias Mullie <minify@mullie.eu>
17
+ */
18
+ abstract class Exception extends \Exception
19
+ {
20
+ }
libs/matthiasmullie/minify/src/Exceptions/BasicException.php CHANGED
@@ -1,23 +1,23 @@
1
- <?php
2
- /**
3
- * Basic exception
4
- *
5
- * Please report bugs on https://github.com/matthiasmullie/minify/issues
6
- *
7
- * @author Matthias Mullie <minify@mullie.eu>
8
- * @copyright Copyright (c) 2012, Matthias Mullie. All rights reserved
9
- * @license MIT License
10
- */
11
- namespace MatthiasMullie\Minify\Exceptions;
12
-
13
- use MatthiasMullie\Minify\Exception;
14
-
15
- /**
16
- * Basic Exception Class
17
- *
18
- * @package Minify\Exception
19
- * @author Matthias Mullie <minify@mullie.eu>
20
- */
21
- abstract class BasicException extends Exception
22
- {
23
- }
1
+ <?php
2
+ /**
3
+ * Basic exception
4
+ *
5
+ * Please report bugs on https://github.com/matthiasmullie/minify/issues
6
+ *
7
+ * @author Matthias Mullie <minify@mullie.eu>
8
+ * @copyright Copyright (c) 2012, Matthias Mullie. All rights reserved
9
+ * @license MIT License
10
+ */
11
+ namespace MatthiasMullie\Minify\Exceptions;
12
+
13
+ use MatthiasMullie\Minify\Exception;
14
+
15
+ /**
16
+ * Basic Exception Class
17
+ *
18
+ * @package Minify\Exception
19
+ * @author Matthias Mullie <minify@mullie.eu>
20
+ */
21
+ abstract class BasicException extends Exception
22
+ {
23
+ }
libs/matthiasmullie/minify/src/Exceptions/FileImportException.php CHANGED
@@ -1,21 +1,21 @@
1
- <?php
2
- /**
3
- * File Import Exception
4
- *
5
- * Please report bugs on https://github.com/matthiasmullie/minify/issues
6
- *
7
- * @author Matthias Mullie <minify@mullie.eu>
8
- * @copyright Copyright (c) 2012, Matthias Mullie. All rights reserved
9
- * @license MIT License
10
- */
11
- namespace MatthiasMullie\Minify\Exceptions;
12
-
13
- /**
14
- * File Import Exception Class
15
- *
16
- * @package Minify\Exception
17
- * @author Matthias Mullie <minify@mullie.eu>
18
- */
19
- class FileImportException extends BasicException
20
- {
21
- }
1
+ <?php
2
+ /**
3
+ * File Import Exception
4
+ *
5
+ * Please report bugs on https://github.com/matthiasmullie/minify/issues
6
+ *
7
+ * @author Matthias Mullie <minify@mullie.eu>
8
+ * @copyright Copyright (c) 2012, Matthias Mullie. All rights reserved
9
+ * @license MIT License
10
+ */
11
+ namespace MatthiasMullie\Minify\Exceptions;
12
+
13
+ /**
14
+ * File Import Exception Class
15
+ *
16
+ * @package Minify\Exception
17
+ * @author Matthias Mullie <minify@mullie.eu>
18
+ */
19
+ class FileImportException extends BasicException
20
+ {
21
+ }
libs/matthiasmullie/minify/src/Exceptions/IOException.php CHANGED
@@ -1,21 +1,21 @@
1
- <?php
2
- /**
3
- * IO Exception
4
- *
5
- * Please report bugs on https://github.com/matthiasmullie/minify/issues
6
- *
7
- * @author Matthias Mullie <minify@mullie.eu>
8
- * @copyright Copyright (c) 2012, Matthias Mullie. All rights reserved
9
- * @license MIT License
10
- */
11
- namespace MatthiasMullie\Minify\Exceptions;
12
-
13
- /**
14
- * IO Exception Class
15
- *
16
- * @package Minify\Exception
17
- * @author Matthias Mullie <minify@mullie.eu>
18
- */
19
- class IOException extends BasicException
20
- {
21
- }
1
+ <?php
2
+ /**
3
+ * IO Exception
4
+ *
5
+ * Please report bugs on https://github.com/matthiasmullie/minify/issues
6
+ *
7
+ * @author Matthias Mullie <minify@mullie.eu>
8
+ * @copyright Copyright (c) 2012, Matthias Mullie. All rights reserved
9
+ * @license MIT License
10
+ */
11
+ namespace MatthiasMullie\Minify\Exceptions;
12
+
13
+ /**
14
+ * IO Exception Class
15
+ *
16
+ * @package Minify\Exception
17
+ * @author Matthias Mullie <minify@mullie.eu>
18
+ */
19
+ class IOException extends BasicException
20
+ {
21
+ }
libs/matthiasmullie/minify/src/JS.php CHANGED
@@ -1,598 +1,612 @@
1
- <?php
2
- /**
3
- * JavaScript minifier
4
- *
5
- * Please report bugs on https://github.com/matthiasmullie/minify/issues
6
- *
7
- * @author Matthias Mullie <minify@mullie.eu>
8
- * @copyright Copyright (c) 2012, Matthias Mullie. All rights reserved
9
- * @license MIT License
10
- */
11
- namespace MatthiasMullie\Minify;
12
-
13
- /**
14
- * JavaScript Minifier Class
15
- *
16
- * Please report bugs on https://github.com/matthiasmullie/minify/issues
17
- *
18
- * @package Minify
19
- * @author Matthias Mullie <minify@mullie.eu>
20
- * @author Tijs Verkoyen <minify@verkoyen.eu>
21
- * @copyright Copyright (c) 2012, Matthias Mullie. All rights reserved
22
- * @license MIT License
23
- */
24
- class JS extends Minify
25
- {
26
- /**
27
- * Var-matching regex based on http://stackoverflow.com/a/9337047/802993.
28
- *
29
- * Note that regular expressions using that bit must have the PCRE_UTF8
30
- * pattern modifier (/u) set.
31
- *
32
- * @var string
33
- */
34
- const REGEX_VARIABLE = '\b[$A-Z\_a-z\xaa\xb5\xba\xc0-\xd6\xd8-\xf6\xf8-\x{02c1}\x{02c6}-\x{02d1}\x{02e0}-\x{02e4}\x{02ec}\x{02ee}\x{0370}-\x{0374}\x{0376}\x{0377}\x{037a}-\x{037d}\x{0386}\x{0388}-\x{038a}\x{038c}\x{038e}-\x{03a1}\x{03a3}-\x{03f5}\x{03f7}-\x{0481}\x{048a}-\x{0527}\x{0531}-\x{0556}\x{0559}\x{0561}-\x{0587}\x{05d0}-\x{05ea}\x{05f0}-\x{05f2}\x{0620}-\x{064a}\x{066e}\x{066f}\x{0671}-\x{06d3}\x{06d5}\x{06e5}\x{06e6}\x{06ee}\x{06ef}\x{06fa}-\x{06fc}\x{06ff}\x{0710}\x{0712}-\x{072f}\x{074d}-\x{07a5}\x{07b1}\x{07ca}-\x{07ea}\x{07f4}\x{07f5}\x{07fa}\x{0800}-\x{0815}\x{081a}\x{0824}\x{0828}\x{0840}-\x{0858}\x{08a0}\x{08a2}-\x{08ac}\x{0904}-\x{0939}\x{093d}\x{0950}\x{0958}-\x{0961}\x{0971}-\x{0977}\x{0979}-\x{097f}\x{0985}-\x{098c}\x{098f}\x{0990}\x{0993}-\x{09a8}\x{09aa}-\x{09b0}\x{09b2}\x{09b6}-\x{09b9}\x{09bd}\x{09ce}\x{09dc}\x{09dd}\x{09df}-\x{09e1}\x{09f0}\x{09f1}\x{0a05}-\x{0a0a}\x{0a0f}\x{0a10}\x{0a13}-\x{0a28}\x{0a2a}-\x{0a30}\x{0a32}\x{0a33}\x{0a35}\x{0a36}\x{0a38}\x{0a39}\x{0a59}-\x{0a5c}\x{0a5e}\x{0a72}-\x{0a74}\x{0a85}-\x{0a8d}\x{0a8f}-\x{0a91}\x{0a93}-\x{0aa8}\x{0aaa}-\x{0ab0}\x{0ab2}\x{0ab3}\x{0ab5}-\x{0ab9}\x{0abd}\x{0ad0}\x{0ae0}\x{0ae1}\x{0b05}-\x{0b0c}\x{0b0f}\x{0b10}\x{0b13}-\x{0b28}\x{0b2a}-\x{0b30}\x{0b32}\x{0b33}\x{0b35}-\x{0b39}\x{0b3d}\x{0b5c}\x{0b5d}\x{0b5f}-\x{0b61}\x{0b71}\x{0b83}\x{0b85}-\x{0b8a}\x{0b8e}-\x{0b90}\x{0b92}-\x{0b95}\x{0b99}\x{0b9a}\x{0b9c}\x{0b9e}\x{0b9f}\x{0ba3}\x{0ba4}\x{0ba8}-\x{0baa}\x{0bae}-\x{0bb9}\x{0bd0}\x{0c05}-\x{0c0c}\x{0c0e}-\x{0c10}\x{0c12}-\x{0c28}\x{0c2a}-\x{0c33}\x{0c35}-\x{0c39}\x{0c3d}\x{0c58}\x{0c59}\x{0c60}\x{0c61}\x{0c85}-\x{0c8c}\x{0c8e}-\x{0c90}\x{0c92}-\x{0ca8}\x{0caa}-\x{0cb3}\x{0cb5}-\x{0cb9}\x{0cbd}\x{0cde}\x{0ce0}\x{0ce1}\x{0cf1}\x{0cf2}\x{0d05}-\x{0d0c}\x{0d0e}-\x{0d10}\x{0d12}-\x{0d3a}\x{0d3d}\x{0d4e}\x{0d60}\x{0d61}\x{0d7a}-\x{0d7f}\x{0d85}-\x{0d96}\x{0d9a}-\x{0db1}\x{0db3}-\x{0dbb}\x{0dbd}\x{0dc0}-\x{0dc6}\x{0e01}-\x{0e30}\x{0e32}\x{0e33}\x{0e40}-\x{0e46}\x{0e81}\x{0e82}\x{0e84}\x{0e87}\x{0e88}\x{0e8a}\x{0e8d}\x{0e94}-\x{0e97}\x{0e99}-\x{0e9f}\x{0ea1}-\x{0ea3}\x{0ea5}\x{0ea7}\x{0eaa}\x{0eab}\x{0ead}-\x{0eb0}\x{0eb2}\x{0eb3}\x{0ebd}\x{0ec0}-\x{0ec4}\x{0ec6}\x{0edc}-\x{0edf}\x{0f00}\x{0f40}-\x{0f47}\x{0f49}-\x{0f6c}\x{0f88}-\x{0f8c}\x{1000}-\x{102a}\x{103f}\x{1050}-\x{1055}\x{105a}-\x{105d}\x{1061}\x{1065}\x{1066}\x{106e}-\x{1070}\x{1075}-\x{1081}\x{108e}\x{10a0}-\x{10c5}\x{10c7}\x{10cd}\x{10d0}-\x{10fa}\x{10fc}-\x{1248}\x{124a}-\x{124d}\x{1250}-\x{1256}\x{1258}\x{125a}-\x{125d}\x{1260}-\x{1288}\x{128a}-\x{128d}\x{1290}-\x{12b0}\x{12b2}-\x{12b5}\x{12b8}-\x{12be}\x{12c0}\x{12c2}-\x{12c5}\x{12c8}-\x{12d6}\x{12d8}-\x{1310}\x{1312}-\x{1315}\x{1318}-\x{135a}\x{1380}-\x{138f}\x{13a0}-\x{13f4}\x{1401}-\x{166c}\x{166f}-\x{167f}\x{1681}-\x{169a}\x{16a0}-\x{16ea}\x{16ee}-\x{16f0}\x{1700}-\x{170c}\x{170e}-\x{1711}\x{1720}-\x{1731}\x{1740}-\x{1751}\x{1760}-\x{176c}\x{176e}-\x{1770}\x{1780}-\x{17b3}\x{17d7}\x{17dc}\x{1820}-\x{1877}\x{1880}-\x{18a8}\x{18aa}\x{18b0}-\x{18f5}\x{1900}-\x{191c}\x{1950}-\x{196d}\x{1970}-\x{1974}\x{1980}-\x{19ab}\x{19c1}-\x{19c7}\x{1a00}-\x{1a16}\x{1a20}-\x{1a54}\x{1aa7}\x{1b05}-\x{1b33}\x{1b45}-\x{1b4b}\x{1b83}-\x{1ba0}\x{1bae}\x{1baf}\x{1bba}-\x{1be5}\x{1c00}-\x{1c23}\x{1c4d}-\x{1c4f}\x{1c5a}-\x{1c7d}\x{1ce9}-\x{1cec}\x{1cee}-\x{1cf1}\x{1cf5}\x{1cf6}\x{1d00}-\x{1dbf}\x{1e00}-\x{1f15}\x{1f18}-\x{1f1d}\x{1f20}-\x{1f45}\x{1f48}-\x{1f4d}\x{1f50}-\x{1f57}\x{1f59}\x{1f5b}\x{1f5d}\x{1f5f}-\x{1f7d}\x{1f80}-\x{1fb4}\x{1fb6}-\x{1fbc}\x{1fbe}\x{1fc2}-\x{1fc4}\x{1fc6}-\x{1fcc}\x{1fd0}-\x{1fd3}\x{1fd6}-\x{1fdb}\x{1fe0}-\x{1fec}\x{1ff2}-\x{1ff4}\x{1ff6}-\x{1ffc}\x{2071}\x{207f}\x{2090}-\x{209c}\x{2102}\x{2107}\x{210a}-\x{2113}\x{2115}\x{2119}-\x{211d}\x{2124}\x{2126}\x{2128}\x{212a}-\x{212d}\x{212f}-\x{2139}\x{213c}-\x{213f}\x{2145}-\x{2149}\x{214e}\x{2160}-\x{2188}\x{2c00}-\x{2c2e}\x{2c30}-\x{2c5e}\x{2c60}-\x{2ce4}\x{2ceb}-\x{2cee}\x{2cf2}\x{2cf3}\x{2d00}-\x{2d25}\x{2d27}\x{2d2d}\x{2d30}-\x{2d67}\x{2d6f}\x{2d80}-\x{2d96}\x{2da0}-\x{2da6}\x{2da8}-\x{2dae}\x{2db0}-\x{2db6}\x{2db8}-\x{2dbe}\x{2dc0}-\x{2dc6}\x{2dc8}-\x{2dce}\x{2dd0}-\x{2dd6}\x{2dd8}-\x{2dde}\x{2e2f}\x{3005}-\x{3007}\x{3021}-\x{3029}\x{3031}-\x{3035}\x{3038}-\x{303c}\x{3041}-\x{3096}\x{309d}-\x{309f}\x{30a1}-\x{30fa}\x{30fc}-\x{30ff}\x{3105}-\x{312d}\x{3131}-\x{318e}\x{31a0}-\x{31ba}\x{31f0}-\x{31ff}\x{3400}-\x{4db5}\x{4e00}-\x{9fcc}\x{a000}-\x{a48c}\x{a4d0}-\x{a4fd}\x{a500}-\x{a60c}\x{a610}-\x{a61f}\x{a62a}\x{a62b}\x{a640}-\x{a66e}\x{a67f}-\x{a697}\x{a6a0}-\x{a6ef}\x{a717}-\x{a71f}\x{a722}-\x{a788}\x{a78b}-\x{a78e}\x{a790}-\x{a793}\x{a7a0}-\x{a7aa}\x{a7f8}-\x{a801}\x{a803}-\x{a805}\x{a807}-\x{a80a}\x{a80c}-\x{a822}\x{a840}-\x{a873}\x{a882}-\x{a8b3}\x{a8f2}-\x{a8f7}\x{a8fb}\x{a90a}-\x{a925}\x{a930}-\x{a946}\x{a960}-\x{a97c}\x{a984}-\x{a9b2}\x{a9cf}\x{aa00}-\x{aa28}\x{aa40}-\x{aa42}\x{aa44}-\x{aa4b}\x{aa60}-\x{aa76}\x{aa7a}\x{aa80}-\x{aaaf}\x{aab1}\x{aab5}\x{aab6}\x{aab9}-\x{aabd}\x{aac0}\x{aac2}\x{aadb}-\x{aadd}\x{aae0}-\x{aaea}\x{aaf2}-\x{aaf4}\x{ab01}-\x{ab06}\x{ab09}-\x{ab0e}\x{ab11}-\x{ab16}\x{ab20}-\x{ab26}\x{ab28}-\x{ab2e}\x{abc0}-\x{abe2}\x{ac00}-\x{d7a3}\x{d7b0}-\x{d7c6}\x{d7cb}-\x{d7fb}\x{f900}-\x{fa6d}\x{fa70}-\x{fad9}\x{fb00}-\x{fb06}\x{fb13}-\x{fb17}\x{fb1d}\x{fb1f}-\x{fb28}\x{fb2a}-\x{fb36}\x{fb38}-\x{fb3c}\x{fb3e}\x{fb40}\x{fb41}\x{fb43}\x{fb44}\x{fb46}-\x{fbb1}\x{fbd3}-\x{fd3d}\x{fd50}-\x{fd8f}\x{fd92}-\x{fdc7}\x{fdf0}-\x{fdfb}\x{fe70}-\x{fe74}\x{fe76}-\x{fefc}\x{ff21}-\x{ff3a}\x{ff41}-\x{ff5a}\x{ff66}-\x{ffbe}\x{ffc2}-\x{ffc7}\x{ffca}-\x{ffcf}\x{ffd2}-\x{ffd7}\x{ffda}-\x{ffdc}][$A-Z\_a-z\xaa\xb5\xba\xc0-\xd6\xd8-\xf6\xf8-\x{02c1}\x{02c6}-\x{02d1}\x{02e0}-\x{02e4}\x{02ec}\x{02ee}\x{0370}-\x{0374}\x{0376}\x{0377}\x{037a}-\x{037d}\x{0386}\x{0388}-\x{038a}\x{038c}\x{038e}-\x{03a1}\x{03a3}-\x{03f5}\x{03f7}-\x{0481}\x{048a}-\x{0527}\x{0531}-\x{0556}\x{0559}\x{0561}-\x{0587}\x{05d0}-\x{05ea}\x{05f0}-\x{05f2}\x{0620}-\x{064a}\x{066e}\x{066f}\x{0671}-\x{06d3}\x{06d5}\x{06e5}\x{06e6}\x{06ee}\x{06ef}\x{06fa}-\x{06fc}\x{06ff}\x{0710}\x{0712}-\x{072f}\x{074d}-\x{07a5}\x{07b1}\x{07ca}-\x{07ea}\x{07f4}\x{07f5}\x{07fa}\x{0800}-\x{0815}\x{081a}\x{0824}\x{0828}\x{0840}-\x{0858}\x{08a0}\x{08a2}-\x{08ac}\x{0904}-\x{0939}\x{093d}\x{0950}\x{0958}-\x{0961}\x{0971}-\x{0977}\x{0979}-\x{097f}\x{0985}-\x{098c}\x{098f}\x{0990}\x{0993}-\x{09a8}\x{09aa}-\x{09b0}\x{09b2}\x{09b6}-\x{09b9}\x{09bd}\x{09ce}\x{09dc}\x{09dd}\x{09df}-\x{09e1}\x{09f0}\x{09f1}\x{0a05}-\x{0a0a}\x{0a0f}\x{0a10}\x{0a13}-\x{0a28}\x{0a2a}-\x{0a30}\x{0a32}\x{0a33}\x{0a35}\x{0a36}\x{0a38}\x{0a39}\x{0a59}-\x{0a5c}\x{0a5e}\x{0a72}-\x{0a74}\x{0a85}-\x{0a8d}\x{0a8f}-\x{0a91}\x{0a93}-\x{0aa8}\x{0aaa}-\x{0ab0}\x{0ab2}\x{0ab3}\x{0ab5}-\x{0ab9}\x{0abd}\x{0ad0}\x{0ae0}\x{0ae1}\x{0b05}-\x{0b0c}\x{0b0f}\x{0b10}\x{0b13}-\x{0b28}\x{0b2a}-\x{0b30}\x{0b32}\x{0b33}\x{0b35}-\x{0b39}\x{0b3d}\x{0b5c}\x{0b5d}\x{0b5f}-\x{0b61}\x{0b71}\x{0b83}\x{0b85}-\x{0b8a}\x{0b8e}-\x{0b90}\x{0b92}-\x{0b95}\x{0b99}\x{0b9a}\x{0b9c}\x{0b9e}\x{0b9f}\x{0ba3}\x{0ba4}\x{0ba8}-\x{0baa}\x{0bae}-\x{0bb9}\x{0bd0}\x{0c05}-\x{0c0c}\x{0c0e}-\x{0c10}\x{0c12}-\x{0c28}\x{0c2a}-\x{0c33}\x{0c35}-\x{0c39}\x{0c3d}\x{0c58}\x{0c59}\x{0c60}\x{0c61}\x{0c85}-\x{0c8c}\x{0c8e}-\x{0c90}\x{0c92}-\x{0ca8}\x{0caa}-\x{0cb3}\x{0cb5}-\x{0cb9}\x{0cbd}\x{0cde}\x{0ce0}\x{0ce1}\x{0cf1}\x{0cf2}\x{0d05}-\x{0d0c}\x{0d0e}-\x{0d10}\x{0d12}-\x{0d3a}\x{0d3d}\x{0d4e}\x{0d60}\x{0d61}\x{0d7a}-\x{0d7f}\x{0d85}-\x{0d96}\x{0d9a}-\x{0db1}\x{0db3}-\x{0dbb}\x{0dbd}\x{0dc0}-\x{0dc6}\x{0e01}-\x{0e30}\x{0e32}\x{0e33}\x{0e40}-\x{0e46}\x{0e81}\x{0e82}\x{0e84}\x{0e87}\x{0e88}\x{0e8a}\x{0e8d}\x{0e94}-\x{0e97}\x{0e99}-\x{0e9f}\x{0ea1}-\x{0ea3}\x{0ea5}\x{0ea7}\x{0eaa}\x{0eab}\x{0ead}-\x{0eb0}\x{0eb2}\x{0eb3}\x{0ebd}\x{0ec0}-\x{0ec4}\x{0ec6}\x{0edc}-\x{0edf}\x{0f00}\x{0f40}-\x{0f47}\x{0f49}-\x{0f6c}\x{0f88}-\x{0f8c}\x{1000}-\x{102a}\x{103f}\x{1050}-\x{1055}\x{105a}-\x{105d}\x{1061}\x{1065}\x{1066}\x{106e}-\x{1070}\x{1075}-\x{1081}\x{108e}\x{10a0}-\x{10c5}\x{10c7}\x{10cd}\x{10d0}-\x{10fa}\x{10fc}-\x{1248}\x{124a}-\x{124d}\x{1250}-\x{1256}\x{1258}\x{125a}-\x{125d}\x{1260}-\x{1288}\x{128a}-\x{128d}\x{1290}-\x{12b0}\x{12b2}-\x{12b5}\x{12b8}-\x{12be}\x{12c0}\x{12c2}-\x{12c5}\x{12c8}-\x{12d6}\x{12d8}-\x{1310}\x{1312}-\x{1315}\x{1318}-\x{135a}\x{1380}-\x{138f}\x{13a0}-\x{13f4}\x{1401}-\x{166c}\x{166f}-\x{167f}\x{1681}-\x{169a}\x{16a0}-\x{16ea}\x{16ee}-\x{16f0}\x{1700}-\x{170c}\x{170e}-\x{1711}\x{1720}-\x{1731}\x{1740}-\x{1751}\x{1760}-\x{176c}\x{176e}-\x{1770}\x{1780}-\x{17b3}\x{17d7}\x{17dc}\x{1820}-\x{1877}\x{1880}-\x{18a8}\x{18aa}\x{18b0}-\x{18f5}\x{1900}-\x{191c}\x{1950}-\x{196d}\x{1970}-\x{1974}\x{1980}-\x{19ab}\x{19c1}-\x{19c7}\x{1a00}-\x{1a16}\x{1a20}-\x{1a54}\x{1aa7}\x{1b05}-\x{1b33}\x{1b45}-\x{1b4b}\x{1b83}-\x{1ba0}\x{1bae}\x{1baf}\x{1bba}-\x{1be5}\x{1c00}-\x{1c23}\x{1c4d}-\x{1c4f}\x{1c5a}-\x{1c7d}\x{1ce9}-\x{1cec}\x{1cee}-\x{1cf1}\x{1cf5}\x{1cf6}\x{1d00}-\x{1dbf}\x{1e00}-\x{1f15}\x{1f18}-\x{1f1d}\x{1f20}-\x{1f45}\x{1f48}-\x{1f4d}\x{1f50}-\x{1f57}\x{1f59}\x{1f5b}\x{1f5d}\x{1f5f}-\x{1f7d}\x{1f80}-\x{1fb4}\x{1fb6}-\x{1fbc}\x{1fbe}\x{1fc2}-\x{1fc4}\x{1fc6}-\x{1fcc}\x{1fd0}-\x{1fd3}\x{1fd6}-\x{1fdb}\x{1fe0}-\x{1fec}\x{1ff2}-\x{1ff4}\x{1ff6}-\x{1ffc}\x{2071}\x{207f}\x{2090}-\x{209c}\x{2102}\x{2107}\x{210a}-\x{2113}\x{2115}\x{2119}-\x{211d}\x{2124}\x{2126}\x{2128}\x{212a}-\x{212d}\x{212f}-\x{2139}\x{213c}-\x{213f}\x{2145}-\x{2149}\x{214e}\x{2160}-\x{2188}\x{2c00}-\x{2c2e}\x{2c30}-\x{2c5e}\x{2c60}-\x{2ce4}\x{2ceb}-\x{2cee}\x{2cf2}\x{2cf3}\x{2d00}-\x{2d25}\x{2d27}\x{2d2d}\x{2d30}-\x{2d67}\x{2d6f}\x{2d80}-\x{2d96}\x{2da0}-\x{2da6}\x{2da8}-\x{2dae}\x{2db0}-\x{2db6}\x{2db8}-\x{2dbe}\x{2dc0}-\x{2dc6}\x{2dc8}-\x{2dce}\x{2dd0}-\x{2dd6}\x{2dd8}-\x{2dde}\x{2e2f}\x{3005}-\x{3007}\x{3021}-\x{3029}\x{3031}-\x{3035}\x{3038}-\x{303c}\x{3041}-\x{3096}\x{309d}-\x{309f}\x{30a1}-\x{30fa}\x{30fc}-\x{30ff}\x{3105}-\x{312d}\x{3131}-\x{318e}\x{31a0}-\x{31ba}\x{31f0}-\x{31ff}\x{3400}-\x{4db5}\x{4e00}-\x{9fcc}\x{a000}-\x{a48c}\x{a4d0}-\x{a4fd}\x{a500}-\x{a60c}\x{a610}-\x{a61f}\x{a62a}\x{a62b}\x{a640}-\x{a66e}\x{a67f}-\x{a697}\x{a6a0}-\x{a6ef}\x{a717}-\x{a71f}\x{a722}-\x{a788}\x{a78b}-\x{a78e}\x{a790}-\x{a793}\x{a7a0}-\x{a7aa}\x{a7f8}-\x{a801}\x{a803}-\x{a805}\x{a807}-\x{a80a}\x{a80c}-\x{a822}\x{a840}-\x{a873}\x{a882}-\x{a8b3}\x{a8f2}-\x{a8f7}\x{a8fb}\x{a90a}-\x{a925}\x{a930}-\x{a946}\x{a960}-\x{a97c}\x{a984}-\x{a9b2}\x{a9cf}\x{aa00}-\x{aa28}\x{aa40}-\x{aa42}\x{aa44}-\x{aa4b}\x{aa60}-\x{aa76}\x{aa7a}\x{aa80}-\x{aaaf}\x{aab1}\x{aab5}\x{aab6}\x{aab9}-\x{aabd}\x{aac0}\x{aac2}\x{aadb}-\x{aadd}\x{aae0}-\x{aaea}\x{aaf2}-\x{aaf4}\x{ab01}-\x{ab06}\x{ab09}-\x{ab0e}\x{ab11}-\x{ab16}\x{ab20}-\x{ab26}\x{ab28}-\x{ab2e}\x{abc0}-\x{abe2}\x{ac00}-\x{d7a3}\x{d7b0}-\x{d7c6}\x{d7cb}-\x{d7fb}\x{f900}-\x{fa6d}\x{fa70}-\x{fad9}\x{fb00}-\x{fb06}\x{fb13}-\x{fb17}\x{fb1d}\x{fb1f}-\x{fb28}\x{fb2a}-\x{fb36}\x{fb38}-\x{fb3c}\x{fb3e}\x{fb40}\x{fb41}\x{fb43}\x{fb44}\x{fb46}-\x{fbb1}\x{fbd3}-\x{fd3d}\x{fd50}-\x{fd8f}\x{fd92}-\x{fdc7}\x{fdf0}-\x{fdfb}\x{fe70}-\x{fe74}\x{fe76}-\x{fefc}\x{ff21}-\x{ff3a}\x{ff41}-\x{ff5a}\x{ff66}-\x{ffbe}\x{ffc2}-\x{ffc7}\x{ffca}-\x{ffcf}\x{ffd2}-\x{ffd7}\x{ffda}-\x{ffdc}0-9\x{0300}-\x{036f}\x{0483}-\x{0487}\x{0591}-\x{05bd}\x{05bf}\x{05c1}\x{05c2}\x{05c4}\x{05c5}\x{05c7}\x{0610}-\x{061a}\x{064b}-\x{0669}\x{0670}\x{06d6}-\x{06dc}\x{06df}-\x{06e4}\x{06e7}\x{06e8}\x{06ea}-\x{06ed}\x{06f0}-\x{06f9}\x{0711}\x{0730}-\x{074a}\x{07a6}-\x{07b0}\x{07c0}-\x{07c9}\x{07eb}-\x{07f3}\x{0816}-\x{0819}\x{081b}-\x{0823}\x{0825}-\x{0827}\x{0829}-\x{082d}\x{0859}-\x{085b}\x{08e4}-\x{08fe}\x{0900}-\x{0903}\x{093a}-\x{093c}\x{093e}-\x{094f}\x{0951}-\x{0957}\x{0962}\x{0963}\x{0966}-\x{096f}\x{0981}-\x{0983}\x{09bc}\x{09be}-\x{09c4}\x{09c7}\x{09c8}\x{09cb}-\x{09cd}\x{09d7}\x{09e2}\x{09e3}\x{09e6}-\x{09ef}\x{0a01}-\x{0a03}\x{0a3c}\x{0a3e}-\x{0a42}\x{0a47}\x{0a48}\x{0a4b}-\x{0a4d}\x{0a51}\x{0a66}-\x{0a71}\x{0a75}\x{0a81}-\x{0a83}\x{0abc}\x{0abe}-\x{0ac5}\x{0ac7}-\x{0ac9}\x{0acb}-\x{0acd}\x{0ae2}\x{0ae3}\x{0ae6}-\x{0aef}\x{0b01}-\x{0b03}\x{0b3c}\x{0b3e}-\x{0b44}\x{0b47}\x{0b48}\x{0b4b}-\x{0b4d}\x{0b56}\x{0b57}\x{0b62}\x{0b63}\x{0b66}-\x{0b6f}\x{0b82}\x{0bbe}-\x{0bc2}\x{0bc6}-\x{0bc8}\x{0bca}-\x{0bcd}\x{0bd7}\x{0be6}-\x{0bef}\x{0c01}-\x{0c03}\x{0c3e}-\x{0c44}\x{0c46}-\x{0c48}\x{0c4a}-\x{0c4d}\x{0c55}\x{0c56}\x{0c62}\x{0c63}\x{0c66}-\x{0c6f}\x{0c82}\x{0c83}\x{0cbc}\x{0cbe}-\x{0cc4}\x{0cc6}-\x{0cc8}\x{0cca}-\x{0ccd}\x{0cd5}\x{0cd6}\x{0ce2}\x{0ce3}\x{0ce6}-\x{0cef}\x{0d02}\x{0d03}\x{0d3e}-\x{0d44}\x{0d46}-\x{0d48}\x{0d4a}-\x{0d4d}\x{0d57}\x{0d62}\x{0d63}\x{0d66}-\x{0d6f}\x{0d82}\x{0d83}\x{0dca}\x{0dcf}-\x{0dd4}\x{0dd6}\x{0dd8}-\x{0ddf}\x{0df2}\x{0df3}\x{0e31}\x{0e34}-\x{0e3a}\x{0e47}-\x{0e4e}\x{0e50}-\x{0e59}\x{0eb1}\x{0eb4}-\x{0eb9}\x{0ebb}\x{0ebc}\x{0ec8}-\x{0ecd}\x{0ed0}-\x{0ed9}\x{0f18}\x{0f19}\x{0f20}-\x{0f29}\x{0f35}\x{0f37}\x{0f39}\x{0f3e}\x{0f3f}\x{0f71}-\x{0f84}\x{0f86}\x{0f87}\x{0f8d}-\x{0f97}\x{0f99}-\x{0fbc}\x{0fc6}\x{102b}-\x{103e}\x{1040}-\x{1049}\x{1056}-\x{1059}\x{105e}-\x{1060}\x{1062}-\x{1064}\x{1067}-\x{106d}\x{1071}-\x{1074}\x{1082}-\x{108d}\x{108f}-\x{109d}\x{135d}-\x{135f}\x{1712}-\x{1714}\x{1732}-\x{1734}\x{1752}\x{1753}\x{1772}\x{1773}\x{17b4}-\x{17d3}\x{17dd}\x{17e0}-\x{17e9}\x{180b}-\x{180d}\x{1810}-\x{1819}\x{18a9}\x{1920}-\x{192b}\x{1930}-\x{193b}\x{1946}-\x{194f}\x{19b0}-\x{19c0}\x{19c8}\x{19c9}\x{19d0}-\x{19d9}\x{1a17}-\x{1a1b}\x{1a55}-\x{1a5e}\x{1a60}-\x{1a7c}\x{1a7f}-\x{1a89}\x{1a90}-\x{1a99}\x{1b00}-\x{1b04}\x{1b34}-\x{1b44}\x{1b50}-\x{1b59}\x{1b6b}-\x{1b73}\x{1b80}-\x{1b82}\x{1ba1}-\x{1bad}\x{1bb0}-\x{1bb9}\x{1be6}-\x{1bf3}\x{1c24}-\x{1c37}\x{1c40}-\x{1c49}\x{1c50}-\x{1c59}\x{1cd0}-\x{1cd2}\x{1cd4}-\x{1ce8}\x{1ced}\x{1cf2}-\x{1cf4}\x{1dc0}-\x{1de6}\x{1dfc}-\x{1dff}\x{200c}\x{200d}\x{203f}\x{2040}\x{2054}\x{20d0}-\x{20dc}\x{20e1}\x{20e5}-\x{20f0}\x{2cef}-\x{2cf1}\x{2d7f}\x{2de0}-\x{2dff}\x{302a}-\x{302f}\x{3099}\x{309a}\x{a620}-\x{a629}\x{a66f}\x{a674}-\x{a67d}\x{a69f}\x{a6f0}\x{a6f1}\x{a802}\x{a806}\x{a80b}\x{a823}-\x{a827}\x{a880}\x{a881}\x{a8b4}-\x{a8c4}\x{a8d0}-\x{a8d9}\x{a8e0}-\x{a8f1}\x{a900}-\x{a909}\x{a926}-\x{a92d}\x{a947}-\x{a953}\x{a980}-\x{a983}\x{a9b3}-\x{a9c0}\x{a9d0}-\x{a9d9}\x{aa29}-\x{aa36}\x{aa43}\x{aa4c}\x{aa4d}\x{aa50}-\x{aa59}\x{aa7b}\x{aab0}\x{aab2}-\x{aab4}\x{aab7}\x{aab8}\x{aabe}\x{aabf}\x{aac1}\x{aaeb}-\x{aaef}\x{aaf5}\x{aaf6}\x{abe3}-\x{abea}\x{abec}\x{abed}\x{abf0}-\x{abf9}\x{fb1e}\x{fe00}-\x{fe0f}\x{fe20}-\x{fe26}\x{fe33}\x{fe34}\x{fe4d}-\x{fe4f}\x{ff10}-\x{ff19}\x{ff3f}]*\b';
35
-
36
- /**
37
- * Full list of JavaScript reserved words.
38
- * Will be loaded from /data/js/keywords_reserved.txt.
39
- *
40
- * @see https://mathiasbynens.be/notes/reserved-keywords
41
- *
42
- * @var string[]
43
- */
44
- protected $keywordsReserved = array();
45
-
46
- /**
47
- * List of JavaScript reserved words that accept a <variable, value, ...>
48
- * after them. Some end of lines are not the end of a statement, like with
49
- * these keywords.
50
- *
51
- * E.g.: we shouldn't insert a ; after this else
52
- * else
53
- * console.log('this is quite fine')
54
- *
55
- * Will be loaded from /data/js/keywords_before.txt
56
- *
57
- * @var string[]
58
- */
59
- protected $keywordsBefore = array();
60
-
61
- /**
62
- * List of JavaScript reserved words that accept a <variable, value, ...>
63
- * before them. Some end of lines are not the end of a statement, like when
64
- * continued by one of these keywords on the newline.
65
- *
66
- * E.g.: we shouldn't insert a ; before this instanceof
67
- * variable
68
- * instanceof String
69
- *
70
- * Will be loaded from /data/js/keywords_after.txt
71
- *
72
- * @var string[]
73
- */
74
- protected $keywordsAfter = array();
75
-
76
- /**
77
- * List of all JavaScript operators.
78
- *
79
- * Will be loaded from /data/js/operators.txt
80
- *
81
- * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Expressions_and_Operators
82
- *
83
- * @var string[]
84
- */
85
- protected $operators = array();
86
-
87
- /**
88
- * List of JavaScript operators that accept a <variable, value, ...> after
89
- * them. Some end of lines are not the end of a statement, like with these
90
- * operators.
91
- *
92
- * Note: Most operators are fine, we've only removed ++ and --.
93
- * ++ & -- have to be joined with the value they're in-/decrementing.
94
- *
95
- * Will be loaded from /data/js/operators_before.txt
96
- *
97
- * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Expressions_and_Operators
98
- *
99
- * @var string[]
100
- */
101
- protected $operatorsBefore = array();
102
-
103
- /**
104
- * List of JavaScript operators that accept a <variable, value, ...> before
105
- * them. Some end of lines are not the end of a statement, like when
106
- * continued by one of these operators on the newline.
107
- *
108
- * Note: Most operators are fine, we've only removed ), ], ++, --, ! and ~.
109
- * There can't be a newline separating ! or ~ and whatever it is negating.
110
- * ++ & -- have to be joined with the value they're in-/decrementing.
111
- * ) & ] are "special" in that they have lots or usecases. () for example
112
- * is used for function calls, for grouping, in if () and for (), ...
113
- *
114
- * Will be loaded from /data/js/operators_after.txt
115
- *
116
- * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Expressions_and_Operators
117
- *
118
- * @var string[]
119
- */
120
- protected $operatorsAfter = array();
121
-
122
- /**
123
- * {@inheritdoc}
124
- */
125
- public function __construct()
126
- {
127
- call_user_func_array(array('parent', '__construct'), func_get_args());
128
-
129
- $dataDir = __DIR__.'/../data/js/';
130
- $options = FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES;
131
- $this->keywordsReserved = file($dataDir.'keywords_reserved.txt', $options);
132
- $this->keywordsBefore = file($dataDir.'keywords_before.txt', $options);
133
- $this->keywordsAfter = file($dataDir.'keywords_after.txt', $options);
134
- $this->operators = file($dataDir.'operators.txt', $options);
135
- $this->operatorsBefore = file($dataDir.'operators_before.txt', $options);
136
- $this->operatorsAfter = file($dataDir.'operators_after.txt', $options);
137
- }
138
-
139
- /**
140
- * Minify the data.
141
- * Perform JS optimizations.
142
- *
143
- * @param string[optional] $path Path to write the data to
144
- *
145
- * @return string The minified data
146
- */
147
- public function execute($path = null)
148
- {
149
- $content = '';
150
-
151
- /*
152
- * Let's first take out strings, comments and regular expressions.
153
- * All of these can contain JS code-like characters, and we should make
154
- * sure any further magic ignores anything inside of these.
155
- *
156
- * Consider this example, where we should not strip any whitespace:
157
- * var str = "a test";
158
- *
159
- * Comments will be removed altogether, strings and regular expressions
160
- * will be replaced by placeholder text, which we'll restore later.
161
- */
162
- $this->extractStrings('\'"`');
163
- $this->stripComments();
164
- $this->extractRegex();
165
-
166
- // loop files
167
- foreach ($this->data as $source => $js) {
168
- // take out strings, comments & regex (for which we've registered
169
- // the regexes just a few lines earlier)
170
- $js = $this->replace($js);
171
-
172
- $js = $this->propertyNotation($js);
173
- $js = $this->shortenBools($js);
174
- $js = $this->stripWhitespace($js);
175
-
176
- // combine js: separating the scripts by a ;
177
- $content .= $js.";";
178
- }
179
-
180
- // clean up leftover `;`s from the combination of multiple scripts
181
- $content = ltrim($content, ';');
182
- $content = (string) substr($content, 0, -1);
183
-
184
- /*
185
- * Earlier, we extracted strings & regular expressions and replaced them
186
- * with placeholder text. This will restore them.
187
- */
188
- $content = $this->restoreExtractedData($content);
189
-
190
- return $content;
191
- }
192
-
193
- /**
194
- * Strip comments from source code.
195
- */
196
- protected function stripComments()
197
- {
198
- // single-line comments
199
- $this->registerPattern('/\/\/.*$/m', '');
200
-
201
- // multi-line comments
202
- $this->registerPattern('/\/\*.*?\*\//s', '');
203
- }
204
-
205
- /**
206
- * JS can have /-delimited regular expressions, like: /ab+c/.match(string).
207
- *
208
- * The content inside the regex can contain characters that may be confused
209
- * for JS code: e.g. it could contain whitespace it needs to match & we
210
- * don't want to strip whitespace in there.
211
- *
212
- * The regex can be pretty simple: we don't have to care about comments,
213
- * (which also use slashes) because stripComments() will have stripped those
214
- * already.
215
- *
216
- * This method will replace all string content with simple REGEX#
217
- * placeholder text, so we've rid all regular expressions from characters
218
- * that may be misinterpreted. Original regex content will be saved in
219
- * $this->extracted and after doing all other minifying, we can restore the
220
- * original content via restoreRegex()
221
- */
222
- protected function extractRegex()
223
- {
224
- // PHP only supports $this inside anonymous functions since 5.4
225
- $minifier = $this;
226
- $callback = function ($match) use ($minifier) {
227
- $count = count($minifier->extracted);
228
- $placeholder = '"'.$count.'"';
229
- $minifier->extracted[$placeholder] = $match[0];
230
-
231
- return $placeholder;
232
- };
233
-
234
- // match all chars except `/` and `\`
235
- // `\` is allowed though, along with whatever char follows (which is the
236
- // one being escaped)
237
- // this should allow all chars, except for an unescaped `/` (= the one
238
- // closing the regex)
239
- // then also ignore bare `/` inside `[]`, where they don't need to be
240
- // escaped: anything inside `[]` can be ignored safely
241
- $pattern = '\\/(?:[^\\[\\/\\\\\n\r]+|(?:\\\\.)+|(?:\\[(?:[^\\]\\\\\n\r]+|(?:\\\\.)+)+\\])+)++\\/[gimuy]*';
242
-
243
- // a regular expression can only be followed by a few operators or some
244
- // of the RegExp methods (a `\` followed by a variable or value is
245
- // likely part of a division, not a regex)
246
- $keywords = array('do', 'in', 'new', 'else', 'throw', 'yield', 'delete', 'return', 'typeof');
247
- $before = '([=:,;\+\-\*\/\}\(\{\[&\|!]|^|'.implode('|', $keywords).')\s*';
248
- $propertiesAndMethods = array(
249
- // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp#Properties_2
250
- 'constructor',
251
- 'flags',
252
- 'global',
253
- 'ignoreCase',
254
- 'multiline',
255
- 'source',
256
- 'sticky',
257
- 'unicode',
258
- // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp#Methods_2
259
- 'compile(',
260
- 'exec(',
261
- 'test(',
262
- 'toSource(',
263
- 'toString(',
264
- );
265
- $delimiters = array_fill(0, count($propertiesAndMethods), '/');
266
- $propertiesAndMethods = array_map('preg_quote', $propertiesAndMethods, $delimiters);
267
- $after = '(?=\s*([\.,;\)\}&\|+]|\/\/|$|\.('.implode('|', $propertiesAndMethods).')))';
268
- $this->registerPattern('/'.$before.'\K'.$pattern.$after.'/', $callback);
269
-
270
- // regular expressions following a `)` are rather annoying to detect...
271
- // quite often, `/` after `)` is a division operator & if it happens to
272
- // be followed by another one (or a comment), it is likely to be
273
- // confused for a regular expression
274
- // however, it's perfectly possible for a regex to follow a `)`: after
275
- // a single-line `if()`, `while()`, ... statement, for example
276
- // since, when they occur like that, they're always the start of a
277
- // statement, there's only a limited amount of ways they can be useful:
278
- // by calling the regex methods directly
279
- // if a regex following `)` is not followed by `.<property or method>`,
280
- // it's quite likely not a regex
281
- $before = '\)\s*';
282
- $after = '(?=\s*\.('.implode('|', $propertiesAndMethods).'))';
283
- $this->registerPattern('/'.$before.'\K'.$pattern.$after.'/', $callback);
284
-
285
- // 1 more edge case: a regex can be followed by a lot more operators or
286
- // keywords if there's a newline (ASI) in between, where the operator
287
- // actually starts a new statement
288
- // (https://github.com/matthiasmullie/minify/issues/56)
289
- $operators = $this->getOperatorsForRegex($this->operatorsBefore, '/');
290
- $operators += $this->getOperatorsForRegex($this->keywordsReserved, '/');
291
- $after = '(?=\s*\n\s*('.implode('|', $operators).'))';
292
- $this->registerPattern('/'.$pattern.$after.'/', $callback);
293
- }
294
-
295
- /**
296
- * Strip whitespace.
297
- *
298
- * We won't strip *all* whitespace, but as much as possible. The thing that
299
- * we'll preserve are newlines we're unsure about.
300
- * JavaScript doesn't require statements to be terminated with a semicolon.
301
- * It will automatically fix missing semicolons with ASI (automatic semi-
302
- * colon insertion) at the end of line causing errors (without semicolon.)
303
- *
304
- * Because it's sometimes hard to tell if a newline is part of a statement
305
- * that should be terminated or not, we'll just leave some of them alone.
306
- *
307
- * @param string $content The content to strip the whitespace for
308
- *
309
- * @return string
310
- */
311
- protected function stripWhitespace($content)
312
- {
313
- // uniform line endings, make them all line feed
314
- $content = str_replace(array("\r\n", "\r"), "\n", $content);
315
-
316
- // collapse all non-line feed whitespace into a single space
317
- $content = preg_replace('/[^\S\n]+/', ' ', $content);
318
-
319
- // strip leading & trailing whitespace
320
- $content = str_replace(array(" \n", "\n "), "\n", $content);
321
-
322
- // collapse consecutive line feeds into just 1
323
- $content = preg_replace('/\n+/', "\n", $content);
324
-
325
- $operatorsBefore = $this->getOperatorsForRegex($this->operatorsBefore, '/');
326
- $operatorsAfter = $this->getOperatorsForRegex($this->operatorsAfter, '/');
327
- $operators = $this->getOperatorsForRegex($this->operators, '/');
328
- $keywordsBefore = $this->getKeywordsForRegex($this->keywordsBefore, '/');
329
- $keywordsAfter = $this->getKeywordsForRegex($this->keywordsAfter, '/');
330
-
331
- // strip whitespace that ends in (or next line begin with) an operator
332
- // that allows statements to be broken up over multiple lines
333
- unset($operatorsBefore['+'], $operatorsBefore['-'], $operatorsAfter['+'], $operatorsAfter['-']);
334
- $content = preg_replace(
335
- array(
336
- '/('.implode('|', $operatorsBefore).')\s+/',
337
- '/\s+('.implode('|', $operatorsAfter).')/',
338
- ), '\\1', $content
339
- );
340
-
341
- // make sure + and - can't be mistaken for, or joined into ++ and --
342
- $content = preg_replace(
343
- array(
344
- '/(?<![\+\-])\s*([\+\-])(?![\+\-])/',
345
- '/(?<![\+\-])([\+\-])\s*(?![\+\-])/',
346
- ), '\\1', $content
347
- );
348
-
349
- // collapse whitespace around reserved words into single space
350
- $content = preg_replace('/(^|[;\}\s])\K('.implode('|', $keywordsBefore).')\s+/', '\\2 ', $content);
351
- $content = preg_replace('/\s+('.implode('|', $keywordsAfter).')(?=([;\{\s]|$))/', ' \\1', $content);
352
-
353
- /*
354
- * We didn't strip whitespace after a couple of operators because they
355
- * could be used in different contexts and we can't be sure it's ok to
356
- * strip the newlines. However, we can safely strip any non-line feed
357
- * whitespace that follows them.
358
- */
359
- $operatorsDiffBefore = array_diff($operators, $operatorsBefore);
360
- $operatorsDiffAfter = array_diff($operators, $operatorsAfter);
361
- $content = preg_replace('/('.implode('|', $operatorsDiffBefore).')[^\S\n]+/', '\\1', $content);
362
- $content = preg_replace('/[^\S\n]+('.implode('|', $operatorsDiffAfter).')/', '\\1', $content);
363
-
364
- /*
365
- * Whitespace after `return` can be omitted in a few occasions
366
- * (such as when followed by a string or regex)
367
- * Same for whitespace in between `)` and `{`, or between `{` and some
368
- * keywords.
369
- */
370
- $content = preg_replace('/\breturn\s+(["\'\/\+\-])/', 'return$1', $content);
371
- $content = preg_replace('/\)\s+\{/', '){', $content);
372
- $content = preg_replace('/}\n(else|catch|finally)\b/', '}$1', $content);
373
-
374
- /*
375
- * Get rid of double semicolons, except where they can be used like:
376
- * "for(v=1,_=b;;)", "for(v=1;;v++)" or "for(;;ja||(ja=true))".
377
- * I'll safeguard these double semicolons inside for-loops by
378
- * temporarily replacing them with an invalid condition: they won't have
379
- * a double semicolon and will be easy to spot to restore afterwards.
380
- */
381
- $content = preg_replace('/\bfor\(([^;]*);;([^;]*)\)/', 'for(\\1;-;\\2)', $content);
382
- $content = preg_replace('/;+/', ';', $content);
383
- $content = preg_replace('/\bfor\(([^;]*);-;([^;]*)\)/', 'for(\\1;;\\2)', $content);
384
-
385
- /*
386
- * Next, we'll be removing all semicolons where ASI kicks in.
387
- * for-loops however, can have an empty body (ending in only a
388
- * semicolon), like: `for(i=1;i<3;i++);`, of `for(i in list);`
389
- * Here, nothing happens during the loop; it's just used to keep
390
- * increasing `i`. With that ; omitted, the next line would be expected
391
- * to be the for-loop's body... Same goes for while loops.
392
- * I'm going to double that semicolon (if any) so after the next line,
393
- * which strips semicolons here & there, we're still left with this one.
394
- */
395
- $content = preg_replace('/(for\([^;\{]*;[^;\{]*;[^;\{]*\));(\}|$)/s', '\\1;;\\2', $content);
396
- $content = preg_replace('/(for\([^;\{]+\s+in\s+[^;\{]+\));(\}|$)/s', '\\1;;\\2', $content);
397
- /*
398
- * Below will also keep `;` after a `do{}while();` along with `while();`
399
- * While these could be stripped after do-while, detecting this
400
- * distinction is cumbersome, so I'll play it safe and make sure `;`
401
- * after any kind of `while` is kept.
402
- */
403
- $content = preg_replace('/(while\([^;\{]+\));(\}|$)/s', '\\1;;\\2', $content);
404
-
405
- /*
406
- * We also can't strip empty else-statements. Even though they're
407
- * useless and probably shouldn't be in the code in the first place, we
408
- * shouldn't be stripping the `;` that follows it as it breaks the code.
409
- * We can just remove those useless else-statements completely.
410
- *
411
- * @see https://github.com/matthiasmullie/minify/issues/91
412
- */
413
- $content = preg_replace('/else;/s', '', $content);
414
-
415
- /*
416
- * We also don't really want to terminate statements followed by closing
417
- * curly braces (which we've ignored completely up until now) or end-of-
418
- * script: ASI will kick in here & we're all about minifying.
419
- * Semicolons at beginning of the file don't make any sense either.
420
- */
421
- $content = preg_replace('/;(\}|$)/s', '\\1', $content);
422
- $content = ltrim($content, ';');
423
-
424
- // get rid of remaining whitespace af beginning/end
425
- return trim($content);
426
- }
427
-
428
- /**
429
- * We'll strip whitespace around certain operators with regular expressions.
430
- * This will prepare the given array by escaping all characters.
431
- *
432
- * @param string[] $operators
433
- * @param string $delimiter
434
- *
435
- * @return string[]
436
- */
437
- protected function getOperatorsForRegex(array $operators, $delimiter = '/')
438
- {
439
- // escape operators for use in regex
440
- $delimiters = array_fill(0, count($operators), $delimiter);
441
- $escaped = array_map('preg_quote', $operators, $delimiters);
442
-
443
- $operators = array_combine($operators, $escaped);
444
-
445
- // ignore + & - for now, they'll get special treatment
446
- unset($operators['+'], $operators['-']);
447
-
448
- // dot can not just immediately follow a number; it can be confused for
449
- // decimal point, or calling a method on it, e.g. 42 .toString()
450
- $operators['.'] = '(?<![0-9]\s)\.';
451
-
452
- // don't confuse = with other assignment shortcuts (e.g. +=)
453
- $chars = preg_quote('+-*\=<>%&|', $delimiter);
454
- $operators['='] = '(?<!['.$chars.'])\=';
455
-
456
- return $operators;
457
- }
458
-
459
- /**
460
- * We'll strip whitespace around certain keywords with regular expressions.
461
- * This will prepare the given array by escaping all characters.
462
- *
463
- * @param string[] $keywords
464
- * @param string $delimiter
465
- *
466
- * @return string[]
467
- */
468
- protected function getKeywordsForRegex(array $keywords, $delimiter = '/')
469
- {
470
- // escape keywords for use in regex
471
- $delimiter = array_fill(0, count($keywords), $delimiter);
472
- $escaped = array_map('preg_quote', $keywords, $delimiter);
473
-
474
- // add word boundaries
475
- array_walk($keywords, function ($value) {
476
- return '\b'.$value.'\b';
477
- });
478
-
479
- $keywords = array_combine($keywords, $escaped);
480
-
481
- return $keywords;
482
- }
483
-
484
- /**
485
- * Replaces all occurrences of array['key'] by array.key.
486
- *
487
- * @param string $content
488
- *
489
- * @return string
490
- */
491
- protected function propertyNotation($content)
492
- {
493
- // PHP only supports $this inside anonymous functions since 5.4
494
- $minifier = $this;
495
- $keywords = $this->keywordsReserved;
496
- $callback = function ($match) use ($minifier, $keywords) {
497
- $property = trim($minifier->extracted[$match[1]], '\'"');
498
-
499
- /*
500
- * Check if the property is a reserved keyword. In this context (as
501
- * property of an object literal/array) it shouldn't matter, but IE8
502
- * freaks out with "Expected identifier".
503
- */
504
- if (in_array($property, $keywords)) {
505
- return $match[0];
506
- }
507
-
508
- /*
509
- * See if the property is in a variable-like format (e.g.
510
- * array['key-here'] can't be replaced by array.key-here since '-'
511
- * is not a valid character there.
512
- */
513
- if (!preg_match('/^'.$minifier::REGEX_VARIABLE.'$/u', $property)) {
514
- return $match[0];
515
- }
516
-
517
- return '.'.$property;
518
- };
519
-
520
- /*
521
- * Figure out if previous character is a variable name (of the array
522
- * we want to use property notation on) - this is to make sure
523
- * standalone ['value'] arrays aren't confused for keys-of-an-array.
524
- * We can (and only have to) check the last character, because PHP's
525
- * regex implementation doesn't allow unfixed-length look-behind
526
- * assertions.
527
- */
528
- preg_match('/(\[[^\]]+\])[^\]]*$/', static::REGEX_VARIABLE, $previousChar);
529
- $previousChar = $previousChar[1];
530
-
531
- /*
532
- * Make sure word preceding the ['value'] is not a keyword, e.g.
533
- * return['x']. Because -again- PHP's regex implementation doesn't allow
534
- * unfixed-length look-behind assertions, I'm just going to do a lot of
535
- * separate look-behind assertions, one for each keyword.
536
- */
537
- $keywords = $this->getKeywordsForRegex($keywords);
538
- $keywords = '(?<!'.implode(')(?<!', $keywords).')';
539
-
540
- return preg_replace_callback('/(?<='.$previousChar.'|\])'.$keywords.'\[\s*(([\'"])[0-9]+\\2)\s*\]/u', $callback, $content);
541
- }
542
-
543
- /**
544
- * Replaces true & false by !0 and !1.
545
- *
546
- * @param string $content
547
- *
548
- * @return string
549
- */
550
- protected function shortenBools($content)
551
- {
552
- /*
553
- * 'true' or 'false' could be used as property names (which may be
554
- * followed by whitespace) - we must not replace those!
555
- * Since PHP doesn't allow variable-length (to account for the
556
- * whitespace) lookbehind assertions, I need to capture the leading
557
- * character and check if it's a `.`
558
- */
559
- $callback = function ($match) {
560
- if (trim($match[1]) === '.') {
561
- return $match[0];
562
- }
563
-
564
- return $match[1].($match[2] === 'true' ? '!0' : '!1');
565
- };
566
- $content = preg_replace_callback('/(^|.\s*)\b(true|false)\b(?!:)/', $callback, $content);
567
-
568
- // for(;;) is exactly the same as while(true), but shorter :)
569
- $content = preg_replace('/\bwhile\(!0\){/', 'for(;;){', $content);
570
-
571
- // now make sure we didn't turn any do ... while(true) into do ... for(;;)
572
- preg_match_all('/\bdo\b/', $content, $dos, PREG_OFFSET_CAPTURE | PREG_SET_ORDER);
573
-
574
- // go backward to make sure positional offsets aren't altered when $content changes
575
- $dos = array_reverse($dos);
576
- foreach ($dos as $do) {
577
- $offsetDo = $do[0][1];
578
-
579
- // find all `while` (now `for`) following `do`: one of those must be
580
- // associated with the `do` and be turned back into `while`
581
- preg_match_all('/\bfor\(;;\)/', $content, $whiles, PREG_OFFSET_CAPTURE | PREG_SET_ORDER, $offsetDo);
582
- foreach ($whiles as $while) {
583
- $offsetWhile = $while[0][1];
584
-
585
- $open = substr_count($content, '{', $offsetDo, $offsetWhile - $offsetDo);
586
- $close = substr_count($content, '}', $offsetDo, $offsetWhile - $offsetDo);
587
- if ($open === $close) {
588
- // only restore `while` if amount of `{` and `}` are the same;
589
- // otherwise, that `for` isn't associated with this `do`
590
- $content = substr_replace($content, 'while(!0)', $offsetWhile, strlen('for(;;)'));
591
- break;
592
- }
593
- }
594
- }
595
-
596
- return $content;
597
- }
598
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * JavaScript minifier
4
+ *
5
+ * Please report bugs on https://github.com/matthiasmullie/minify/issues
6
+ *
7
+ * @author Matthias Mullie <minify@mullie.eu>
8
+ * @copyright Copyright (c) 2012, Matthias Mullie. All rights reserved
9
+ * @license MIT License
10
+ */
11
+ namespace MatthiasMullie\Minify;
12
+
13
+ /**
14
+ * JavaScript Minifier Class
15
+ *
16
+ * Please report bugs on https://github.com/matthiasmullie/minify/issues
17
+ *
18
+ * @package Minify
19
+ * @author Matthias Mullie <minify@mullie.eu>
20
+ * @author Tijs Verkoyen <minify@verkoyen.eu>
21
+ * @copyright Copyright (c) 2012, Matthias Mullie. All rights reserved
22
+ * @license MIT License
23
+ */
24
+ class JS extends Minify
25
+ {
26
+ /**
27
+ * Var-matching regex based on http://stackoverflow.com/a/9337047/802993.
28
+ *
29
+ * Note that regular expressions using that bit must have the PCRE_UTF8
30
+ * pattern modifier (/u) set.
31
+ *
32
+ * @var string
33
+ */
34
+ const REGEX_VARIABLE = '\b[$A-Z\_a-z\xaa\xb5\xba\xc0-\xd6\xd8-\xf6\xf8-\x{02c1}\x{02c6}-\x{02d1}\x{02e0}-\x{02e4}\x{02ec}\x{02ee}\x{0370}-\x{0374}\x{0376}\x{0377}\x{037a}-\x{037d}\x{0386}\x{0388}-\x{038a}\x{038c}\x{038e}-\x{03a1}\x{03a3}-\x{03f5}\x{03f7}-\x{0481}\x{048a}-\x{0527}\x{0531}-\x{0556}\x{0559}\x{0561}-\x{0587}\x{05d0}-\x{05ea}\x{05f0}-\x{05f2}\x{0620}-\x{064a}\x{066e}\x{066f}\x{0671}-\x{06d3}\x{06d5}\x{06e5}\x{06e6}\x{06ee}\x{06ef}\x{06fa}-\x{06fc}\x{06ff}\x{0710}\x{0712}-\x{072f}\x{074d}-\x{07a5}\x{07b1}\x{07ca}-\x{07ea}\x{07f4}\x{07f5}\x{07fa}\x{0800}-\x{0815}\x{081a}\x{0824}\x{0828}\x{0840}-\x{0858}\x{08a0}\x{08a2}-\x{08ac}\x{0904}-\x{0939}\x{093d}\x{0950}\x{0958}-\x{0961}\x{0971}-\x{0977}\x{0979}-\x{097f}\x{0985}-\x{098c}\x{098f}\x{0990}\x{0993}-\x{09a8}\x{09aa}-\x{09b0}\x{09b2}\x{09b6}-\x{09b9}\x{09bd}\x{09ce}\x{09dc}\x{09dd}\x{09df}-\x{09e1}\x{09f0}\x{09f1}\x{0a05}-\x{0a0a}\x{0a0f}\x{0a10}\x{0a13}-\x{0a28}\x{0a2a}-\x{0a30}\x{0a32}\x{0a33}\x{0a35}\x{0a36}\x{0a38}\x{0a39}\x{0a59}-\x{0a5c}\x{0a5e}\x{0a72}-\x{0a74}\x{0a85}-\x{0a8d}\x{0a8f}-\x{0a91}\x{0a93}-\x{0aa8}\x{0aaa}-\x{0ab0}\x{0ab2}\x{0ab3}\x{0ab5}-\x{0ab9}\x{0abd}\x{0ad0}\x{0ae0}\x{0ae1}\x{0b05}-\x{0b0c}\x{0b0f}\x{0b10}\x{0b13}-\x{0b28}\x{0b2a}-\x{0b30}\x{0b32}\x{0b33}\x{0b35}-\x{0b39}\x{0b3d}\x{0b5c}\x{0b5d}\x{0b5f}-\x{0b61}\x{0b71}\x{0b83}\x{0b85}-\x{0b8a}\x{0b8e}-\x{0b90}\x{0b92}-\x{0b95}\x{0b99}\x{0b9a}\x{0b9c}\x{0b9e}\x{0b9f}\x{0ba3}\x{0ba4}\x{0ba8}-\x{0baa}\x{0bae}-\x{0bb9}\x{0bd0}\x{0c05}-\x{0c0c}\x{0c0e}-\x{0c10}\x{0c12}-\x{0c28}\x{0c2a}-\x{0c33}\x{0c35}-\x{0c39}\x{0c3d}\x{0c58}\x{0c59}\x{0c60}\x{0c61}\x{0c85}-\x{0c8c}\x{0c8e}-\x{0c90}\x{0c92}-\x{0ca8}\x{0caa}-\x{0cb3}\x{0cb5}-\x{0cb9}\x{0cbd}\x{0cde}\x{0ce0}\x{0ce1}\x{0cf1}\x{0cf2}\x{0d05}-\x{0d0c}\x{0d0e}-\x{0d10}\x{0d12}-\x{0d3a}\x{0d3d}\x{0d4e}\x{0d60}\x{0d61}\x{0d7a}-\x{0d7f}\x{0d85}-\x{0d96}\x{0d9a}-\x{0db1}\x{0db3}-\x{0dbb}\x{0dbd}\x{0dc0}-\x{0dc6}\x{0e01}-\x{0e30}\x{0e32}\x{0e33}\x{0e40}-\x{0e46}\x{0e81}\x{0e82}\x{0e84}\x{0e87}\x{0e88}\x{0e8a}\x{0e8d}\x{0e94}-\x{0e97}\x{0e99}-\x{0e9f}\x{0ea1}-\x{0ea3}\x{0ea5}\x{0ea7}\x{0eaa}\x{0eab}\x{0ead}-\x{0eb0}\x{0eb2}\x{0eb3}\x{0ebd}\x{0ec0}-\x{0ec4}\x{0ec6}\x{0edc}-\x{0edf}\x{0f00}\x{0f40}-\x{0f47}\x{0f49}-\x{0f6c}\x{0f88}-\x{0f8c}\x{1000}-\x{102a}\x{103f}\x{1050}-\x{1055}\x{105a}-\x{105d}\x{1061}\x{1065}\x{1066}\x{106e}-\x{1070}\x{1075}-\x{1081}\x{108e}\x{10a0}-\x{10c5}\x{10c7}\x{10cd}\x{10d0}-\x{10fa}\x{10fc}-\x{1248}\x{124a}-\x{124d}\x{1250}-\x{1256}\x{1258}\x{125a}-\x{125d}\x{1260}-\x{1288}\x{128a}-\x{128d}\x{1290}-\x{12b0}\x{12b2}-\x{12b5}\x{12b8}-\x{12be}\x{12c0}\x{12c2}-\x{12c5}\x{12c8}-\x{12d6}\x{12d8}-\x{1310}\x{1312}-\x{1315}\x{1318}-\x{135a}\x{1380}-\x{138f}\x{13a0}-\x{13f4}\x{1401}-\x{166c}\x{166f}-\x{167f}\x{1681}-\x{169a}\x{16a0}-\x{16ea}\x{16ee}-\x{16f0}\x{1700}-\x{170c}\x{170e}-\x{1711}\x{1720}-\x{1731}\x{1740}-\x{1751}\x{1760}-\x{176c}\x{176e}-\x{1770}\x{1780}-\x{17b3}\x{17d7}\x{17dc}\x{1820}-\x{1877}\x{1880}-\x{18a8}\x{18aa}\x{18b0}-\x{18f5}\x{1900}-\x{191c}\x{1950}-\x{196d}\x{1970}-\x{1974}\x{1980}-\x{19ab}\x{19c1}-\x{19c7}\x{1a00}-\x{1a16}\x{1a20}-\x{1a54}\x{1aa7}\x{1b05}-\x{1b33}\x{1b45}-\x{1b4b}\x{1b83}-\x{1ba0}\x{1bae}\x{1baf}\x{1bba}-\x{1be5}\x{1c00}-\x{1c23}\x{1c4d}-\x{1c4f}\x{1c5a}-\x{1c7d}\x{1ce9}-\x{1cec}\x{1cee}-\x{1cf1}\x{1cf5}\x{1cf6}\x{1d00}-\x{1dbf}\x{1e00}-\x{1f15}\x{1f18}-\x{1f1d}\x{1f20}-\x{1f45}\x{1f48}-\x{1f4d}\x{1f50}-\x{1f57}\x{1f59}\x{1f5b}\x{1f5d}\x{1f5f}-\x{1f7d}\x{1f80}-\x{1fb4}\x{1fb6}-\x{1fbc}\x{1fbe}\x{1fc2}-\x{1fc4}\x{1fc6}-\x{1fcc}\x{1fd0}-\x{1fd3}\x{1fd6}-\x{1fdb}\x{1fe0}-\x{1fec}\x{1ff2}-\x{1ff4}\x{1ff6}-\x{1ffc}\x{2071}\x{207f}\x{2090}-\x{209c}\x{2102}\x{2107}\x{210a}-\x{2113}\x{2115}\x{2119}-\x{211d}\x{2124}\x{2126}\x{2128}\x{212a}-\x{212d}\x{212f}-\x{2139}\x{213c}-\x{213f}\x{2145}-\x{2149}\x{214e}\x{2160}-\x{2188}\x{2c00}-\x{2c2e}\x{2c30}-\x{2c5e}\x{2c60}-\x{2ce4}\x{2ceb}-\x{2cee}\x{2cf2}\x{2cf3}\x{2d00}-\x{2d25}\x{2d27}\x{2d2d}\x{2d30}-\x{2d67}\x{2d6f}\x{2d80}-\x{2d96}\x{2da0}-\x{2da6}\x{2da8}-\x{2dae}\x{2db0}-\x{2db6}\x{2db8}-\x{2dbe}\x{2dc0}-\x{2dc6}\x{2dc8}-\x{2dce}\x{2dd0}-\x{2dd6}\x{2dd8}-\x{2dde}\x{2e2f}\x{3005}-\x{3007}\x{3021}-\x{3029}\x{3031}-\x{3035}\x{3038}-\x{303c}\x{3041}-\x{3096}\x{309d}-\x{309f}\x{30a1}-\x{30fa}\x{30fc}-\x{30ff}\x{3105}-\x{312d}\x{3131}-\x{318e}\x{31a0}-\x{31ba}\x{31f0}-\x{31ff}\x{3400}-\x{4db5}\x{4e00}-\x{9fcc}\x{a000}-\x{a48c}\x{a4d0}-\x{a4fd}\x{a500}-\x{a60c}\x{a610}-\x{a61f}\x{a62a}\x{a62b}\x{a640}-\x{a66e}\x{a67f}-\x{a697}\x{a6a0}-\x{a6ef}\x{a717}-\x{a71f}\x{a722}-\x{a788}\x{a78b}-\x{a78e}\x{a790}-\x{a793}\x{a7a0}-\x{a7aa}\x{a7f8}-\x{a801}\x{a803}-\x{a805}\x{a807}-\x{a80a}\x{a80c}-\x{a822}\x{a840}-\x{a873}\x{a882}-\x{a8b3}\x{a8f2}-\x{a8f7}\x{a8fb}\x{a90a}-\x{a925}\x{a930}-\x{a946}\x{a960}-\x{a97c}\x{a984}-\x{a9b2}\x{a9cf}\x{aa00}-\x{aa28}\x{aa40}-\x{aa42}\x{aa44}-\x{aa4b}\x{aa60}-\x{aa76}\x{aa7a}\x{aa80}-\x{aaaf}\x{aab1}\x{aab5}\x{aab6}\x{aab9}-\x{aabd}\x{aac0}\x{aac2}\x{aadb}-\x{aadd}\x{aae0}-\x{aaea}\x{aaf2}-\x{aaf4}\x{ab01}-\x{ab06}\x{ab09}-\x{ab0e}\x{ab11}-\x{ab16}\x{ab20}-\x{ab26}\x{ab28}-\x{ab2e}\x{abc0}-\x{abe2}\x{ac00}-\x{d7a3}\x{d7b0}-\x{d7c6}\x{d7cb}-\x{d7fb}\x{f900}-\x{fa6d}\x{fa70}-\x{fad9}\x{fb00}-\x{fb06}\x{fb13}-\x{fb17}\x{fb1d}\x{fb1f}-\x{fb28}\x{fb2a}-\x{fb36}\x{fb38}-\x{fb3c}\x{fb3e}\x{fb40}\x{fb41}\x{fb43}\x{fb44}\x{fb46}-\x{fbb1}\x{fbd3}-\x{fd3d}\x{fd50}-\x{fd8f}\x{fd92}-\x{fdc7}\x{fdf0}-\x{fdfb}\x{fe70}-\x{fe74}\x{fe76}-\x{fefc}\x{ff21}-\x{ff3a}\x{ff41}-\x{ff5a}\x{ff66}-\x{ffbe}\x{ffc2}-\x{ffc7}\x{ffca}-\x{ffcf}\x{ffd2}-\x{ffd7}\x{ffda}-\x{ffdc}][$A-Z\_a-z\xaa\xb5\xba\xc0-\xd6\xd8-\xf6\xf8-\x{02c1}\x{02c6}-\x{02d1}\x{02e0}-\x{02e4}\x{02ec}\x{02ee}\x{0370}-\x{0374}\x{0376}\x{0377}\x{037a}-\x{037d}\x{0386}\x{0388}-\x{038a}\x{038c}\x{038e}-\x{03a1}\x{03a3}-\x{03f5}\x{03f7}-\x{0481}\x{048a}-\x{0527}\x{0531}-\x{0556}\x{0559}\x{0561}-\x{0587}\x{05d0}-\x{05ea}\x{05f0}-\x{05f2}\x{0620}-\x{064a}\x{066e}\x{066f}\x{0671}-\x{06d3}\x{06d5}\x{06e5}\x{06e6}\x{06ee}\x{06ef}\x{06fa}-\x{06fc}\x{06ff}\x{0710}\x{0712}-\x{072f}\x{074d}-\x{07a5}\x{07b1}\x{07ca}-\x{07ea}\x{07f4}\x{07f5}\x{07fa}\x{0800}-\x{0815}\x{081a}\x{0824}\x{0828}\x{0840}-\x{0858}\x{08a0}\x{08a2}-\x{08ac}\x{0904}-\x{0939}\x{093d}\x{0950}\x{0958}-\x{0961}\x{0971}-\x{0977}\x{0979}-\x{097f}\x{0985}-\x{098c}\x{098f}\x{0990}\x{0993}-\x{09a8}\x{09aa}-\x{09b0}\x{09b2}\x{09b6}-\x{09b9}\x{09bd}\x{09ce}\x{09dc}\x{09dd}\x{09df}-\x{09e1}\x{09f0}\x{09f1}\x{0a05}-\x{0a0a}\x{0a0f}\x{0a10}\x{0a13}-\x{0a28}\x{0a2a}-\x{0a30}\x{0a32}\x{0a33}\x{0a35}\x{0a36}\x{0a38}\x{0a39}\x{0a59}-\x{0a5c}\x{0a5e}\x{0a72}-\x{0a74}\x{0a85}-\x{0a8d}\x{0a8f}-\x{0a91}\x{0a93}-\x{0aa8}\x{0aaa}-\x{0ab0}\x{0ab2}\x{0ab3}\x{0ab5}-\x{0ab9}\x{0abd}\x{0ad0}\x{0ae0}\x{0ae1}\x{0b05}-\x{0b0c}\x{0b0f}\x{0b10}\x{0b13}-\x{0b28}\x{0b2a}-\x{0b30}\x{0b32}\x{0b33}\x{0b35}-\x{0b39}\x{0b3d}\x{0b5c}\x{0b5d}\x{0b5f}-\x{0b61}\x{0b71}\x{0b83}\x{0b85}-\x{0b8a}\x{0b8e}-\x{0b90}\x{0b92}-\x{0b95}\x{0b99}\x{0b9a}\x{0b9c}\x{0b9e}\x{0b9f}\x{0ba3}\x{0ba4}\x{0ba8}-\x{0baa}\x{0bae}-\x{0bb9}\x{0bd0}\x{0c05}-\x{0c0c}\x{0c0e}-\x{0c10}\x{0c12}-\x{0c28}\x{0c2a}-\x{0c33}\x{0c35}-\x{0c39}\x{0c3d}\x{0c58}\x{0c59}\x{0c60}\x{0c61}\x{0c85}-\x{0c8c}\x{0c8e}-\x{0c90}\x{0c92}-\x{0ca8}\x{0caa}-\x{0cb3}\x{0cb5}-\x{0cb9}\x{0cbd}\x{0cde}\x{0ce0}\x{0ce1}\x{0cf1}\x{0cf2}\x{0d05}-\x{0d0c}\x{0d0e}-\x{0d10}\x{0d12}-\x{0d3a}\x{0d3d}\x{0d4e}\x{0d60}\x{0d61}\x{0d7a}-\x{0d7f}\x{0d85}-\x{0d96}\x{0d9a}-\x{0db1}\x{0db3}-\x{0dbb}\x{0dbd}\x{0dc0}-\x{0dc6}\x{0e01}-\x{0e30}\x{0e32}\x{0e33}\x{0e40}-\x{0e46}\x{0e81}\x{0e82}\x{0e84}\x{0e87}\x{0e88}\x{0e8a}\x{0e8d}\x{0e94}-\x{0e97}\x{0e99}-\x{0e9f}\x{0ea1}-\x{0ea3}\x{0ea5}\x{0ea7}\x{0eaa}\x{0eab}\x{0ead}-\x{0eb0}\x{0eb2}\x{0eb3}\x{0ebd}\x{0ec0}-\x{0ec4}\x{0ec6}\x{0edc}-\x{0edf}\x{0f00}\x{0f40}-\x{0f47}\x{0f49}-\x{0f6c}\x{0f88}-\x{0f8c}\x{1000}-\x{102a}\x{103f}\x{1050}-\x{1055}\x{105a}-\x{105d}\x{1061}\x{1065}\x{1066}\x{106e}-\x{1070}\x{1075}-\x{1081}\x{108e}\x{10a0}-\x{10c5}\x{10c7}\x{10cd}\x{10d0}-\x{10fa}\x{10fc}-\x{1248}\x{124a}-\x{124d}\x{1250}-\x{1256}\x{1258}\x{125a}-\x{125d}\x{1260}-\x{1288}\x{128a}-\x{128d}\x{1290}-\x{12b0}\x{12b2}-\x{12b5}\x{12b8}-\x{12be}\x{12c0}\x{12c2}-\x{12c5}\x{12c8}-\x{12d6}\x{12d8}-\x{1310}\x{1312}-\x{1315}\x{1318}-\x{135a}\x{1380}-\x{138f}\x{13a0}-\x{13f4}\x{1401}-\x{166c}\x{166f}-\x{167f}\x{1681}-\x{169a}\x{16a0}-\x{16ea}\x{16ee}-\x{16f0}\x{1700}-\x{170c}\x{170e}-\x{1711}\x{1720}-\x{1731}\x{1740}-\x{1751}\x{1760}-\x{176c}\x{176e}-\x{1770}\x{1780}-\x{17b3}\x{17d7}\x{17dc}\x{1820}-\x{1877}\x{1880}-\x{18a8}\x{18aa}\x{18b0}-\x{18f5}\x{1900}-\x{191c}\x{1950}-\x{196d}\x{1970}-\x{1974}\x{1980}-\x{19ab}\x{19c1}-\x{19c7}\x{1a00}-\x{1a16}\x{1a20}-\x{1a54}\x{1aa7}\x{1b05}-\x{1b33}\x{1b45}-\x{1b4b}\x{1b83}-\x{1ba0}\x{1bae}\x{1baf}\x{1bba}-\x{1be5}\x{1c00}-\x{1c23}\x{1c4d}-\x{1c4f}\x{1c5a}-\x{1c7d}\x{1ce9}-\x{1cec}\x{1cee}-\x{1cf1}\x{1cf5}\x{1cf6}\x{1d00}-\x{1dbf}\x{1e00}-\x{1f15}\x{1f18}-\x{1f1d}\x{1f20}-\x{1f45}\x{1f48}-\x{1f4d}\x{1f50}-\x{1f57}\x{1f59}\x{1f5b}\x{1f5d}\x{1f5f}-\x{1f7d}\x{1f80}-\x{1fb4}\x{1fb6}-\x{1fbc}\x{1fbe}\x{1fc2}-\x{1fc4}\x{1fc6}-\x{1fcc}\x{1fd0}-\x{1fd3}\x{1fd6}-\x{1fdb}\x{1fe0}-\x{1fec}\x{1ff2}-\x{1ff4}\x{1ff6}-\x{1ffc}\x{2071}\x{207f}\x{2090}-\x{209c}\x{2102}\x{2107}\x{210a}-\x{2113}\x{2115}\x{2119}-\x{211d}\x{2124}\x{2126}\x{2128}\x{212a}-\x{212d}\x{212f}-\x{2139}\x{213c}-\x{213f}\x{2145}-\x{2149}\x{214e}\x{2160}-\x{2188}\x{2c00}-\x{2c2e}\x{2c30}-\x{2c5e}\x{2c60}-\x{2ce4}\x{2ceb}-\x{2cee}\x{2cf2}\x{2cf3}\x{2d00}-\x{2d25}\x{2d27}\x{2d2d}\x{2d30}-\x{2d67}\x{2d6f}\x{2d80}-\x{2d96}\x{2da0}-\x{2da6}\x{2da8}-\x{2dae}\x{2db0}-\x{2db6}\x{2db8}-\x{2dbe}\x{2dc0}-\x{2dc6}\x{2dc8}-\x{2dce}\x{2dd0}-\x{2dd6}\x{2dd8}-\x{2dde}\x{2e2f}\x{3005}-\x{3007}\x{3021}-\x{3029}\x{3031}-\x{3035}\x{3038}-\x{303c}\x{3041}-\x{3096}\x{309d}-\x{309f}\x{30a1}-\x{30fa}\x{30fc}-\x{30ff}\x{3105}-\x{312d}\x{3131}-\x{318e}\x{31a0}-\x{31ba}\x{31f0}-\x{31ff}\x{3400}-\x{4db5}\x{4e00}-\x{9fcc}\x{a000}-\x{a48c}\x{a4d0}-\x{a4fd}\x{a500}-\x{a60c}\x{a610}-\x{a61f}\x{a62a}\x{a62b}\x{a640}-\x{a66e}\x{a67f}-\x{a697}\x{a6a0}-\x{a6ef}\x{a717}-\x{a71f}\x{a722}-\x{a788}\x{a78b}-\x{a78e}\x{a790}-\x{a793}\x{a7a0}-\x{a7aa}\x{a7f8}-\x{a801}\x{a803}-\x{a805}\x{a807}-\x{a80a}\x{a80c}-\x{a822}\x{a840}-\x{a873}\x{a882}-\x{a8b3}\x{a8f2}-\x{a8f7}\x{a8fb}\x{a90a}-\x{a925}\x{a930}-\x{a946}\x{a960}-\x{a97c}\x{a984}-\x{a9b2}\x{a9cf}\x{aa00}-\x{aa28}\x{aa40}-\x{aa42}\x{aa44}-\x{aa4b}\x{aa60}-\x{aa76}\x{aa7a}\x{aa80}-\x{aaaf}\x{aab1}\x{aab5}\x{aab6}\x{aab9}-\x{aabd}\x{aac0}\x{aac2}\x{aadb}-\x{aadd}\x{aae0}-\x{aaea}\x{aaf2}-\x{aaf4}\x{ab01}-\x{ab06}\x{ab09}-\x{ab0e}\x{ab11}-\x{ab16}\x{ab20}-\x{ab26}\x{ab28}-\x{ab2e}\x{abc0}-\x{abe2}\x{ac00}-\x{d7a3}\x{d7b0}-\x{d7c6}\x{d7cb}-\x{d7fb}\x{f900}-\x{fa6d}\x{fa70}-\x{fad9}\x{fb00}-\x{fb06}\x{fb13}-\x{fb17}\x{fb1d}\x{fb1f}-\x{fb28}\x{fb2a}-\x{fb36}\x{fb38}-\x{fb3c}\x{fb3e}\x{fb40}\x{fb41}\x{fb43}\x{fb44}\x{fb46}-\x{fbb1}\x{fbd3}-\x{fd3d}\x{fd50}-\x{fd8f}\x{fd92}-\x{fdc7}\x{fdf0}-\x{fdfb}\x{fe70}-\x{fe74}\x{fe76}-\x{fefc}\x{ff21}-\x{ff3a}\x{ff41}-\x{ff5a}\x{ff66}-\x{ffbe}\x{ffc2}-\x{ffc7}\x{ffca}-\x{ffcf}\x{ffd2}-\x{ffd7}\x{ffda}-\x{ffdc}0-9\x{0300}-\x{036f}\x{0483}-\x{0487}\x{0591}-\x{05bd}\x{05bf}\x{05c1}\x{05c2}\x{05c4}\x{05c5}\x{05c7}\x{0610}-\x{061a}\x{064b}-\x{0669}\x{0670}\x{06d6}-\x{06dc}\x{06df}-\x{06e4}\x{06e7}\x{06e8}\x{06ea}-\x{06ed}\x{06f0}-\x{06f9}\x{0711}\x{0730}-\x{074a}\x{07a6}-\x{07b0}\x{07c0}-\x{07c9}\x{07eb}-\x{07f3}\x{0816}-\x{0819}\x{081b}-\x{0823}\x{0825}-\x{0827}\x{0829}-\x{082d}\x{0859}-\x{085b}\x{08e4}-\x{08fe}\x{0900}-\x{0903}\x{093a}-\x{093c}\x{093e}-\x{094f}\x{0951}-\x{0957}\x{0962}\x{0963}\x{0966}-\x{096f}\x{0981}-\x{0983}\x{09bc}\x{09be}-\x{09c4}\x{09c7}\x{09c8}\x{09cb}-\x{09cd}\x{09d7}\x{09e2}\x{09e3}\x{09e6}-\x{09ef}\x{0a01}-\x{0a03}\x{0a3c}\x{0a3e}-\x{0a42}\x{0a47}\x{0a48}\x{0a4b}-\x{0a4d}\x{0a51}\x{0a66}-\x{0a71}\x{0a75}\x{0a81}-\x{0a83}\x{0abc}\x{0abe}-\x{0ac5}\x{0ac7}-\x{0ac9}\x{0acb}-\x{0acd}\x{0ae2}\x{0ae3}\x{0ae6}-\x{0aef}\x{0b01}-\x{0b03}\x{0b3c}\x{0b3e}-\x{0b44}\x{0b47}\x{0b48}\x{0b4b}-\x{0b4d}\x{0b56}\x{0b57}\x{0b62}\x{0b63}\x{0b66}-\x{0b6f}\x{0b82}\x{0bbe}-\x{0bc2}\x{0bc6}-\x{0bc8}\x{0bca}-\x{0bcd}\x{0bd7}\x{0be6}-\x{0bef}\x{0c01}-\x{0c03}\x{0c3e}-\x{0c44}\x{0c46}-\x{0c48}\x{0c4a}-\x{0c4d}\x{0c55}\x{0c56}\x{0c62}\x{0c63}\x{0c66}-\x{0c6f}\x{0c82}\x{0c83}\x{0cbc}\x{0cbe}-\x{0cc4}\x{0cc6}-\x{0cc8}\x{0cca}-\x{0ccd}\x{0cd5}\x{0cd6}\x{0ce2}\x{0ce3}\x{0ce6}-\x{0cef}\x{0d02}\x{0d03}\x{0d3e}-\x{0d44}\x{0d46}-\x{0d48}\x{0d4a}-\x{0d4d}\x{0d57}\x{0d62}\x{0d63}\x{0d66}-\x{0d6f}\x{0d82}\x{0d83}\x{0dca}\x{0dcf}-\x{0dd4}\x{0dd6}\x{0dd8}-\x{0ddf}\x{0df2}\x{0df3}\x{0e31}\x{0e34}-\x{0e3a}\x{0e47}-\x{0e4e}\x{0e50}-\x{0e59}\x{0eb1}\x{0eb4}-\x{0eb9}\x{0ebb}\x{0ebc}\x{0ec8}-\x{0ecd}\x{0ed0}-\x{0ed9}\x{0f18}\x{0f19}\x{0f20}-\x{0f29}\x{0f35}\x{0f37}\x{0f39}\x{0f3e}\x{0f3f}\x{0f71}-\x{0f84}\x{0f86}\x{0f87}\x{0f8d}-\x{0f97}\x{0f99}-\x{0fbc}\x{0fc6}\x{102b}-\x{103e}\x{1040}-\x{1049}\x{1056}-\x{1059}\x{105e}-\x{1060}\x{1062}-\x{1064}\x{1067}-\x{106d}\x{1071}-\x{1074}\x{1082}-\x{108d}\x{108f}-\x{109d}\x{135d}-\x{135f}\x{1712}-\x{1714}\x{1732}-\x{1734}\x{1752}\x{1753}\x{1772}\x{1773}\x{17b4}-\x{17d3}\x{17dd}\x{17e0}-\x{17e9}\x{180b}-\x{180d}\x{1810}-\x{1819}\x{18a9}\x{1920}-\x{192b}\x{1930}-\x{193b}\x{1946}-\x{194f}\x{19b0}-\x{19c0}\x{19c8}\x{19c9}\x{19d0}-\x{19d9}\x{1a17}-\x{1a1b}\x{1a55}-\x{1a5e}\x{1a60}-\x{1a7c}\x{1a7f}-\x{1a89}\x{1a90}-\x{1a99}\x{1b00}-\x{1b04}\x{1b34}-\x{1b44}\x{1b50}-\x{1b59}\x{1b6b}-\x{1b73}\x{1b80}-\x{1b82}\x{1ba1}-\x{1bad}\x{1bb0}-\x{1bb9}\x{1be6}-\x{1bf3}\x{1c24}-\x{1c37}\x{1c40}-\x{1c49}\x{1c50}-\x{1c59}\x{1cd0}-\x{1cd2}\x{1cd4}-\x{1ce8}\x{1ced}\x{1cf2}-\x{1cf4}\x{1dc0}-\x{1de6}\x{1dfc}-\x{1dff}\x{200c}\x{200d}\x{203f}\x{2040}\x{2054}\x{20d0}-\x{20dc}\x{20e1}\x{20e5}-\x{20f0}\x{2cef}-\x{2cf1}\x{2d7f}\x{2de0}-\x{2dff}\x{302a}-\x{302f}\x{3099}\x{309a}\x{a620}-\x{a629}\x{a66f}\x{a674}-\x{a67d}\x{a69f}\x{a6f0}\x{a6f1}\x{a802}\x{a806}\x{a80b}\x{a823}-\x{a827}\x{a880}\x{a881}\x{a8b4}-\x{a8c4}\x{a8d0}-\x{a8d9}\x{a8e0}-\x{a8f1}\x{a900}-\x{a909}\x{a926}-\x{a92d}\x{a947}-\x{a953}\x{a980}-\x{a983}\x{a9b3}-\x{a9c0}\x{a9d0}-\x{a9d9}\x{aa29}-\x{aa36}\x{aa43}\x{aa4c}\x{aa4d}\x{aa50}-\x{aa59}\x{aa7b}\x{aab0}\x{aab2}-\x{aab4}\x{aab7}\x{aab8}\x{aabe}\x{aabf}\x{aac1}\x{aaeb}-\x{aaef}\x{aaf5}\x{aaf6}\x{abe3}-\x{abea}\x{abec}\x{abed}\x{abf0}-\x{abf9}\x{fb1e}\x{fe00}-\x{fe0f}\x{fe20}-\x{fe26}\x{fe33}\x{fe34}\x{fe4d}-\x{fe4f}\x{ff10}-\x{ff19}\x{ff3f}]*\b';
35
+
36
+ /**
37
+ * Full list of JavaScript reserved words.
38
+ * Will be loaded from /data/js/keywords_reserved.txt.
39
+ *
40
+ * @see https://mathiasbynens.be/notes/reserved-keywords
41
+ *
42
+ * @var string[]
43
+ */
44
+ protected $keywordsReserved = array();
45
+
46
+ /**
47
+ * List of JavaScript reserved words that accept a <variable, value, ...>
48
+ * after them. Some end of lines are not the end of a statement, like with
49
+ * these keywords.
50
+ *
51
+ * E.g.: we shouldn't insert a ; after this else
52
+ * else
53
+ * console.log('this is quite fine')
54
+ *
55
+ * Will be loaded from /data/js/keywords_before.txt
56
+ *
57
+ * @var string[]
58
+ */
59
+ protected $keywordsBefore = array();
60
+
61
+ /**
62
+ * List of JavaScript reserved words that accept a <variable, value, ...>
63
+ * before them. Some end of lines are not the end of a statement, like when
64
+ * continued by one of these keywords on the newline.
65
+ *
66
+ * E.g.: we shouldn't insert a ; before this instanceof
67
+ * variable
68
+ * instanceof String
69
+ *
70
+ * Will be loaded from /data/js/keywords_after.txt
71
+ *
72
+ * @var string[]
73
+ */
74
+ protected $keywordsAfter = array();
75
+
76
+ /**
77
+ * List of all JavaScript operators.
78
+ *
79
+ * Will be loaded from /data/js/operators.txt
80
+ *
81
+ * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Expressions_and_Operators
82
+ *
83
+ * @var string[]
84
+ */
85
+ protected $operators = array();
86
+
87
+ /**
88
+ * List of JavaScript operators that accept a <variable, value, ...> after
89
+ * them. Some end of lines are not the end of a statement, like with these
90
+ * operators.
91
+ *
92
+ * Note: Most operators are fine, we've only removed ++ and --.
93
+ * ++ & -- have to be joined with the value they're in-/decrementing.
94
+ *
95
+ * Will be loaded from /data/js/operators_before.txt
96
+ *
97
+ * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Expressions_and_Operators
98
+ *
99
+ * @var string[]
100
+ */
101
+ protected $operatorsBefore = array();
102
+
103
+ /**
104
+ * List of JavaScript operators that accept a <variable, value, ...> before
105
+ * them. Some end of lines are not the end of a statement, like when
106
+ * continued by one of these operators on the newline.
107
+ *
108
+ * Note: Most operators are fine, we've only removed ), ], ++, --, ! and ~.
109
+ * There can't be a newline separating ! or ~ and whatever it is negating.
110
+ * ++ & -- have to be joined with the value they're in-/decrementing.
111
+ * ) & ] are "special" in that they have lots or usecases. () for example
112
+ * is used for function calls, for grouping, in if () and for (), ...
113
+ *
114
+ * Will be loaded from /data/js/operators_after.txt
115
+ *
116
+ * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Expressions_and_Operators
117
+ *
118
+ * @var string[]
119
+ */
120
+ protected $operatorsAfter = array();
121
+
122
+ /**
123
+ * {@inheritdoc}
124
+ */
125
+ public function __construct()
126
+ {
127
+ call_user_func_array(array('parent', '__construct'), func_get_args());
128
+
129
+ $dataDir = __DIR__.'/../data/js/';
130
+ $options = FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES;
131
+ $this->keywordsReserved = file($dataDir.'keywords_reserved.txt', $options);
132
+ $this->keywordsBefore = file($dataDir.'keywords_before.txt', $options);
133
+ $this->keywordsAfter = file($dataDir.'keywords_after.txt', $options);
134
+ $this->operators = file($dataDir.'operators.txt', $options);
135
+ $this->operatorsBefore = file($dataDir.'operators_before.txt', $options);
136
+ $this->operatorsAfter = file($dataDir.'operators_after.txt', $options);
137
+ }
138
+
139
+ /**
140
+ * Minify the data.
141
+ * Perform JS optimizations.
142
+ *
143
+ * @param string[optional] $path Path to write the data to
144
+ *
145
+ * @return string The minified data
146
+ */
147
+ public function execute($path = null)
148
+ {
149
+ $content = '';
150
+
151
+ /*
152
+ * Let's first take out strings, comments and regular expressions.
153
+ * All of these can contain JS code-like characters, and we should make
154
+ * sure any further magic ignores anything inside of these.
155
+ *
156
+ * Consider this example, where we should not strip any whitespace:
157
+ * var str = "a test";
158
+ *
159
+ * Comments will be removed altogether, strings and regular expressions
160
+ * will be replaced by placeholder text, which we'll restore later.
161
+ */
162
+ $this->extractStrings('\'"`');
163
+ $this->stripComments();
164
+ $this->extractRegex();
165
+
166
+ // loop files
167
+ foreach ($this->data as $source => $js) {
168
+ // take out strings, comments & regex (for which we've registered
169
+ // the regexes just a few lines earlier)
170
+ $js = $this->replace($js);
171
+
172
+ $js = $this->propertyNotation($js);
173
+ $js = $this->shortenBools($js);
174
+ $js = $this->stripWhitespace($js);
175
+
176
+ // combine js: separating the scripts by a ;
177
+ $content .= $js.";";
178
+ }
179
+
180
+ // clean up leftover `;`s from the combination of multiple scripts
181
+ $content = ltrim($content, ';');
182
+ $content = (string) substr($content, 0, -1);
183
+
184
+ /*
185
+ * Earlier, we extracted strings & regular expressions and replaced them
186
+ * with placeholder text. This will restore them.
187
+ */
188
+ $content = $this->restoreExtractedData($content);
189
+
190
+ return $content;
191
+ }
192
+
193
+ /**
194
+ * Strip comments from source code.
195
+ */
196
+ protected function stripComments()
197
+ {
198
+ // PHP only supports $this inside anonymous functions since 5.4
199
+ $minifier = $this;
200
+ $callback = function ($match) use ($minifier) {
201
+ $count = count($minifier->extracted);
202
+ $placeholder = '/*'.$count.'*/';
203
+ $minifier->extracted[$placeholder] = $match[0];
204
+
205
+ return $placeholder;
206
+ };
207
+ // multi-line comments
208
+ $this->registerPattern('/\n?\/\*(!|.*?@license|.*?@preserve).*?\*\/\n?/s', $callback);
209
+ $this->registerPattern('/\/\*.*?\*\//s', '');
210
+
211
+ // single-line comments
212
+ $this->registerPattern('/\/\/.*$/m', '');
213
+ }
214
+
215
+ /**
216
+ * JS can have /-delimited regular expressions, like: /ab+c/.match(string).
217
+ *
218
+ * The content inside the regex can contain characters that may be confused
219
+ * for JS code: e.g. it could contain whitespace it needs to match & we
220
+ * don't want to strip whitespace in there.
221
+ *
222
+ * The regex can be pretty simple: we don't have to care about comments,
223
+ * (which also use slashes) because stripComments() will have stripped those
224
+ * already.
225
+ *
226
+ * This method will replace all string content with simple REGEX#
227
+ * placeholder text, so we've rid all regular expressions from characters
228
+ * that may be misinterpreted. Original regex content will be saved in
229
+ * $this->extracted and after doing all other minifying, we can restore the
230
+ * original content via restoreRegex()
231
+ */
232
+ protected function extractRegex()
233
+ {
234
+ // PHP only supports $this inside anonymous functions since 5.4
235
+ $minifier = $this;
236
+ $callback = function ($match) use ($minifier) {
237
+ $count = count($minifier->extracted);
238
+ $placeholder = '"'.$count.'"';
239
+ $minifier->extracted[$placeholder] = $match[0];
240
+
241
+ return $placeholder;
242
+ };
243
+
244
+ // match all chars except `/` and `\`
245
+ // `\` is allowed though, along with whatever char follows (which is the
246
+ // one being escaped)
247
+ // this should allow all chars, except for an unescaped `/` (= the one
248
+ // closing the regex)
249
+ // then also ignore bare `/` inside `[]`, where they don't need to be
250
+ // escaped: anything inside `[]` can be ignored safely
251
+ $pattern = '\\/(?!\*)(?:[^\\[\\/\\\\\n\r]++|(?:\\\\.)++|(?:\\[(?:[^\\]\\\\\n\r]++|(?:\\\\.)++)++\\])++)++\\/[gimuy]*';
252
+
253
+ // a regular expression can only be followed by a few operators or some
254
+ // of the RegExp methods (a `\` followed by a variable or value is
255
+ // likely part of a division, not a regex)
256
+ $keywords = array('do', 'in', 'new', 'else', 'throw', 'yield', 'delete', 'return', 'typeof');
257
+ $before = '([=:,;\+\-\*\/\}\(\{\[&\|!]|^|'.implode('|', $keywords).')\s*';
258
+ $propertiesAndMethods = array(
259
+ // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp#Properties_2
260
+ 'constructor',
261
+ 'flags',
262
+ 'global',
263
+ 'ignoreCase',
264
+ 'multiline',
265
+ 'source',
266
+ 'sticky',
267
+ 'unicode',
268
+ // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp#Methods_2
269
+ 'compile(',
270
+ 'exec(',
271
+ 'test(',
272
+ 'toSource(',
273
+ 'toString(',
274
+ );
275
+ $delimiters = array_fill(0, count($propertiesAndMethods), '/');
276
+ $propertiesAndMethods = array_map('preg_quote', $propertiesAndMethods, $delimiters);
277
+ $after = '(?=\s*([\.,;\)\}&\|+]|\/\/|$|\.('.implode('|', $propertiesAndMethods).')))';
278
+ $this->registerPattern('/'.$before.'\K'.$pattern.$after.'/', $callback);
279
+
280
+ // regular expressions following a `)` are rather annoying to detect...
281
+ // quite often, `/` after `)` is a division operator & if it happens to
282
+ // be followed by another one (or a comment), it is likely to be
283
+ // confused for a regular expression
284
+ // however, it's perfectly possible for a regex to follow a `)`: after
285
+ // a single-line `if()`, `while()`, ... statement, for example
286
+ // since, when they occur like that, they're always the start of a
287
+ // statement, there's only a limited amount of ways they can be useful:
288
+ // by calling the regex methods directly
289
+ // if a regex following `)` is not followed by `.<property or method>`,
290
+ // it's quite likely not a regex
291
+ $before = '\)\s*';
292
+ $after = '(?=\s*\.('.implode('|', $propertiesAndMethods).'))';
293
+ $this->registerPattern('/'.$before.'\K'.$pattern.$after.'/', $callback);
294
+
295
+ // 1 more edge case: a regex can be followed by a lot more operators or
296
+ // keywords if there's a newline (ASI) in between, where the operator
297
+ // actually starts a new statement
298
+ // (https://github.com/matthiasmullie/minify/issues/56)
299
+ $operators = $this->getOperatorsForRegex($this->operatorsBefore, '/');
300
+ $operators += $this->getOperatorsForRegex($this->keywordsReserved, '/');
301
+ $after = '(?=\s*\n\s*('.implode('|', $operators).'))';
302
+ $this->registerPattern('/'.$pattern.$after.'/', $callback);
303
+ }
304
+
305
+ /**
306
+ * Strip whitespace.
307
+ *
308
+ * We won't strip *all* whitespace, but as much as possible. The thing that
309
+ * we'll preserve are newlines we're unsure about.
310
+ * JavaScript doesn't require statements to be terminated with a semicolon.
311
+ * It will automatically fix missing semicolons with ASI (automatic semi-
312
+ * colon insertion) at the end of line causing errors (without semicolon.)
313
+ *
314
+ * Because it's sometimes hard to tell if a newline is part of a statement
315
+ * that should be terminated or not, we'll just leave some of them alone.
316
+ *
317
+ * @param string $content The content to strip the whitespace for
318
+ *
319
+ * @return string
320
+ */
321
+ protected function stripWhitespace($content)
322
+ {
323
+ // uniform line endings, make them all line feed
324
+ $content = str_replace(array("\r\n", "\r"), "\n", $content);
325
+
326
+ // collapse all non-line feed whitespace into a single space
327
+ $content = preg_replace('/[^\S\n]+/', ' ', $content);
328
+
329
+ // strip leading & trailing whitespace
330
+ $content = str_replace(array(" \n", "\n "), "\n", $content);
331
+
332
+ // collapse consecutive line feeds into just 1
333
+ $content = preg_replace('/\n+/', "\n", $content);
334
+
335
+ $operatorsBefore = $this->getOperatorsForRegex($this->operatorsBefore, '/');
336
+ $operatorsAfter = $this->getOperatorsForRegex($this->operatorsAfter, '/');
337
+ $operators = $this->getOperatorsForRegex($this->operators, '/');
338
+ $keywordsBefore = $this->getKeywordsForRegex($this->keywordsBefore, '/');
339
+ $keywordsAfter = $this->getKeywordsForRegex($this->keywordsAfter, '/');
340
+
341
+ // strip whitespace that ends in (or next line begin with) an operator
342
+ // that allows statements to be broken up over multiple lines
343
+ unset($operatorsBefore['+'], $operatorsBefore['-'], $operatorsAfter['+'], $operatorsAfter['-']);
344
+ $content = preg_replace(
345
+ array(
346
+ '/('.implode('|', $operatorsBefore).')\s+/',
347
+ '/\s+('.implode('|', $operatorsAfter).')/',
348
+ ),
349
+ '\\1',
350
+ $content
351
+ );
352
+
353
+ // make sure + and - can't be mistaken for, or joined into ++ and --
354
+ $content = preg_replace(
355
+ array(
356
+ '/(?<![\+\-])\s*([\+\-])(?![\+\-])/',
357
+ '/(?<![\+\-])([\+\-])\s*(?![\+\-])/',
358
+ ),
359
+ '\\1',
360
+ $content
361
+ );
362
+
363
+ // collapse whitespace around reserved words into single space
364
+ $content = preg_replace('/(^|[;\}\s])\K('.implode('|', $keywordsBefore).')\s+/', '\\2 ', $content);
365
+ $content = preg_replace('/\s+('.implode('|', $keywordsAfter).')(?=([;\{\s]|$))/', ' \\1', $content);
366
+
367
+ /*
368
+ * We didn't strip whitespace after a couple of operators because they
369
+ * could be used in different contexts and we can't be sure it's ok to
370
+ * strip the newlines. However, we can safely strip any non-line feed
371
+ * whitespace that follows them.
372
+ */
373
+ $operatorsDiffBefore = array_diff($operators, $operatorsBefore);
374
+ $operatorsDiffAfter = array_diff($operators, $operatorsAfter);
375
+ $content = preg_replace('/('.implode('|', $operatorsDiffBefore).')[^\S\n]+/', '\\1', $content);
376
+ $content = preg_replace('/[^\S\n]+('.implode('|', $operatorsDiffAfter).')/', '\\1', $content);
377
+
378
+ /*
379
+ * Whitespace after `return` can be omitted in a few occasions
380
+ * (such as when followed by a string or regex)
381
+ * Same for whitespace in between `)` and `{`, or between `{` and some
382
+ * keywords.
383
+ */
384
+ $content = preg_replace('/\breturn\s+(["\'\/\+\-])/', 'return$1', $content);
385
+ $content = preg_replace('/\)\s+\{/', '){', $content);
386
+ $content = preg_replace('/}\n(else|catch|finally)\b/', '}$1', $content);
387
+
388
+ /*
389
+ * Get rid of double semicolons, except where they can be used like:
390
+ * "for(v=1,_=b;;)", "for(v=1;;v++)" or "for(;;ja||(ja=true))".
391
+ * I'll safeguard these double semicolons inside for-loops by
392
+ * temporarily replacing them with an invalid condition: they won't have
393
+ * a double semicolon and will be easy to spot to restore afterwards.
394
+ */
395
+ $content = preg_replace('/\bfor\(([^;]*);;([^;]*)\)/', 'for(\\1;-;\\2)', $content);
396
+ $content = preg_replace('/;+/', ';', $content);
397
+ $content = preg_replace('/\bfor\(([^;]*);-;([^;]*)\)/', 'for(\\1;;\\2)', $content);
398
+
399
+ /*
400
+ * Next, we'll be removing all semicolons where ASI kicks in.
401
+ * for-loops however, can have an empty body (ending in only a
402
+ * semicolon), like: `for(i=1;i<3;i++);`, of `for(i in list);`
403
+ * Here, nothing happens during the loop; it's just used to keep
404
+ * increasing `i`. With that ; omitted, the next line would be expected
405
+ * to be the for-loop's body... Same goes for while loops.
406
+ * I'm going to double that semicolon (if any) so after the next line,
407
+ * which strips semicolons here & there, we're still left with this one.
408
+ */
409
+ $content = preg_replace('/(for\([^;\{]*;[^;\{]*;[^;\{]*\));(\}|$)/s', '\\1;;\\2', $content);
410
+ $content = preg_replace('/(for\([^;\{]+\s+in\s+[^;\{]+\));(\}|$)/s', '\\1;;\\2', $content);
411
+ /*
412
+ * Below will also keep `;` after a `do{}while();` along with `while();`
413
+ * While these could be stripped after do-while, detecting this
414
+ * distinction is cumbersome, so I'll play it safe and make sure `;`
415
+ * after any kind of `while` is kept.
416
+ */
417
+ $content = preg_replace('/(while\([^;\{]+\));(\}|$)/s', '\\1;;\\2', $content);
418
+
419
+ /*
420
+ * We also can't strip empty else-statements. Even though they're
421
+ * useless and probably shouldn't be in the code in the first place, we
422
+ * shouldn't be stripping the `;` that follows it as it breaks the code.
423
+ * We can just remove those useless else-statements completely.
424
+ *
425
+ * @see https://github.com/matthiasmullie/minify/issues/91
426
+ */
427
+ $content = preg_replace('/else;/s', '', $content);
428
+
429
+ /*
430
+ * We also don't really want to terminate statements followed by closing
431
+ * curly braces (which we've ignored completely up until now) or end-of-
432
+ * script: ASI will kick in here & we're all about minifying.
433
+ * Semicolons at beginning of the file don't make any sense either.
434
+ */
435
+ $content = preg_replace('/;(\}|$)/s', '\\1', $content);
436
+ $content = ltrim($content, ';');
437
+
438
+ // get rid of remaining whitespace af beginning/end
439
+ return trim($content);
440
+ }
441
+
442
+ /**
443
+ * We'll strip whitespace around certain operators with regular expressions.
444
+ * This will prepare the given array by escaping all characters.
445
+ *
446
+ * @param string[] $operators
447
+ * @param string $delimiter
448
+ *
449
+ * @return string[]
450
+ */
451
+ protected function getOperatorsForRegex(array $operators, $delimiter = '/')
452
+ {
453
+ // escape operators for use in regex
454
+ $delimiters = array_fill(0, count($operators), $delimiter);
455
+ $escaped = array_map('preg_quote', $operators, $delimiters);
456
+
457
+ $operators = array_combine($operators, $escaped);
458
+
459
+ // ignore + & - for now, they'll get special treatment
460
+ unset($operators['+'], $operators['-']);
461
+
462
+ // dot can not just immediately follow a number; it can be confused for
463
+ // decimal point, or calling a method on it, e.g. 42 .toString()
464
+ $operators['.'] = '(?<![0-9]\s)\.';
465
+
466
+ // don't confuse = with other assignment shortcuts (e.g. +=)
467
+ $chars = preg_quote('+-*\=<>%&|', $delimiter);
468
+ $operators['='] = '(?<!['.$chars.'])\=';
469
+
470
+ return $operators;
471
+ }
472
+
473
+ /**
474
+ * We'll strip whitespace around certain keywords with regular expressions.
475
+ * This will prepare the given array by escaping all characters.
476
+ *
477
+ * @param string[] $keywords
478
+ * @param string $delimiter
479
+ *
480
+ * @return string[]
481
+ */
482
+ protected function getKeywordsForRegex(array $keywords, $delimiter = '/')
483
+ {
484
+ // escape keywords for use in regex
485
+ $delimiter = array_fill(0, count($keywords), $delimiter);
486
+ $escaped = array_map('preg_quote', $keywords, $delimiter);
487
+
488
+ // add word boundaries
489
+ array_walk($keywords, function ($value) {
490
+ return '\b'.$value.'\b';
491
+ });
492
+
493
+ $keywords = array_combine($keywords, $escaped);
494
+
495
+ return $keywords;
496
+ }
497
+
498
+ /**
499
+ * Replaces all occurrences of array['key'] by array.key.
500
+ *
501
+ * @param string $content
502
+ *
503
+ * @return string
504
+ */
505
+ protected function propertyNotation($content)
506
+ {
507
+ // PHP only supports $this inside anonymous functions since 5.4
508
+ $minifier = $this;
509
+ $keywords = $this->keywordsReserved;
510
+ $callback = function ($match) use ($minifier, $keywords) {
511
+ $property = trim($minifier->extracted[$match[1]], '\'"');
512
+
513
+ /*
514
+ * Check if the property is a reserved keyword. In this context (as
515
+ * property of an object literal/array) it shouldn't matter, but IE8
516
+ * freaks out with "Expected identifier".
517
+ */
518
+ if (in_array($property, $keywords)) {
519
+ return $match[0];
520
+ }
521
+
522
+ /*
523
+ * See if the property is in a variable-like format (e.g.
524
+ * array['key-here'] can't be replaced by array.key-here since '-'
525
+ * is not a valid character there.
526
+ */
527
+ if (!preg_match('/^'.$minifier::REGEX_VARIABLE.'$/u', $property)) {
528
+ return $match[0];
529
+ }
530
+
531
+ return '.'.$property;
532
+ };
533
+
534
+ /*
535
+ * Figure out if previous character is a variable name (of the array
536
+ * we want to use property notation on) - this is to make sure
537
+ * standalone ['value'] arrays aren't confused for keys-of-an-array.
538
+ * We can (and only have to) check the last character, because PHP's
539
+ * regex implementation doesn't allow unfixed-length look-behind
540
+ * assertions.
541
+ */
542
+ preg_match('/(\[[^\]]+\])[^\]]*$/', static::REGEX_VARIABLE, $previousChar);
543
+ $previousChar = $previousChar[1];
544
+
545
+ /*
546
+ * Make sure word preceding the ['value'] is not a keyword, e.g.
547
+ * return['x']. Because -again- PHP's regex implementation doesn't allow
548
+ * unfixed-length look-behind assertions, I'm just going to do a lot of
549
+ * separate look-behind assertions, one for each keyword.
550
+ */
551
+ $keywords = $this->getKeywordsForRegex($keywords);
552
+ $keywords = '(?<!'.implode(')(?<!', $keywords).')';
553
+
554
+ return preg_replace_callback('/(?<='.$previousChar.'|\])'.$keywords.'\[\s*(([\'"])[0-9]+\\2)\s*\]/u', $callback, $content);
555
+ }
556
+
557
+ /**
558
+ * Replaces true & false by !0 and !1.
559
+ *
560
+ * @param string $content
561
+ *
562
+ * @return string
563
+ */
564
+ protected function shortenBools($content)
565
+ {
566
+ /*
567
+ * 'true' or 'false' could be used as property names (which may be
568
+ * followed by whitespace) - we must not replace those!
569
+ * Since PHP doesn't allow variable-length (to account for the
570
+ * whitespace) lookbehind assertions, I need to capture the leading
571
+ * character and check if it's a `.`
572
+ */
573
+ $callback = function ($match) {
574
+ if (trim($match[1]) === '.') {
575
+ return $match[0];
576
+ }
577
+
578
+ return $match[1].($match[2] === 'true' ? '!0' : '!1');
579
+ };
580
+ $content = preg_replace_callback('/(^|.\s*)\b(true|false)\b(?!:)/', $callback, $content);
581
+
582
+ // for(;;) is exactly the same as while(true), but shorter :)
583
+ $content = preg_replace('/\bwhile\(!0\){/', 'for(;;){', $content);
584
+
585
+ // now make sure we didn't turn any do ... while(true) into do ... for(;;)
586
+ preg_match_all('/\bdo\b/', $content, $dos, PREG_OFFSET_CAPTURE | PREG_SET_ORDER);
587
+
588
+ // go backward to make sure positional offsets aren't altered when $content changes
589
+ $dos = array_reverse($dos);
590
+ foreach ($dos as $do) {
591
+ $offsetDo = $do[0][1];
592
+
593
+ // find all `while` (now `for`) following `do`: one of those must be
594
+ // associated with the `do` and be turned back into `while`
595
+ preg_match_all('/\bfor\(;;\)/', $content, $whiles, PREG_OFFSET_CAPTURE | PREG_SET_ORDER, $offsetDo);
596
+ foreach ($whiles as $while) {
597
+ $offsetWhile = $while[0][1];
598
+
599
+ $open = substr_count($content, '{', $offsetDo, $offsetWhile - $offsetDo);
600
+ $close = substr_count($content, '}', $offsetDo, $offsetWhile - $offsetDo);
601
+ if ($open === $close) {
602
+ // only restore `while` if amount of `{` and `}` are the same;
603
+ // otherwise, that `for` isn't associated with this `do`
604
+ $content = substr_replace($content, 'while(!0)', $offsetWhile, strlen('for(;;)'));
605
+ break;
606
+ }
607
+ }
608
+ }
609
+
610
+ return $content;
611
+ }
612
+ }
libs/matthiasmullie/minify/src/Minify.php CHANGED
@@ -1,459 +1,459 @@
1
- <?php
2
- /**
3
- * Abstract minifier class
4
- *
5
- * Please report bugs on https://github.com/matthiasmullie/minify/issues
6
- *
7
- * @author Matthias Mullie <minify@mullie.eu>
8
- * @copyright Copyright (c) 2012, Matthias Mullie. All rights reserved
9
- * @license MIT License
10
- */
11
- namespace MatthiasMullie\Minify;
12
-
13
- use MatthiasMullie\Minify\Exceptions\IOException;
14
- use Psr\Cache\CacheItemInterface;
15
-
16
- /**
17
- * Abstract minifier class.
18
- *
19
- * Please report bugs on https://github.com/matthiasmullie/minify/issues
20
- *
21
- * @package Minify
22
- * @author Matthias Mullie <minify@mullie.eu>
23
- * @copyright Copyright (c) 2012, Matthias Mullie. All rights reserved
24
- * @license MIT License
25
- */
26
- abstract class Minify
27
- {
28
- /**
29
- * The data to be minified.
30
- *
31
- * @var string[]
32
- */
33
- protected $data = array();
34
-
35
- /**
36
- * Array of patterns to match.
37
- *
38
- * @var string[]
39
- */
40
- protected $patterns = array();
41
-
42
- /**
43
- * This array will hold content of strings and regular expressions that have
44
- * been extracted from the JS source code, so we can reliably match "code",
45
- * without having to worry about potential "code-like" characters inside.
46
- *
47
- * @var string[]
48
- */
49
- public $extracted = array();
50
-
51
- /**
52
- * Init the minify class - optionally, code may be passed along already.
53
- */
54
- public function __construct(/* $data = null, ... */)
55
- {
56
- // it's possible to add the source through the constructor as well ;)
57
- if (func_num_args()) {
58
- call_user_func_array(array($this, 'add'), func_get_args());
59
- }
60
- }
61
-
62
- /**
63
- * Add a file or straight-up code to be minified.
64
- *
65
- * @param string|string[] $data
66
- *
67
- * @return static
68
- */
69
- public function add($data /* $data = null, ... */)
70
- {
71
- // bogus "usage" of parameter $data: scrutinizer warns this variable is
72
- // not used (we're using func_get_args instead to support overloading),
73
- // but it still needs to be defined because it makes no sense to have
74
- // this function without argument :)
75
- $args = array($data) + func_get_args();
76
-
77
- // this method can be overloaded
78
- foreach ($args as $data) {
79
- if (is_array($data)) {
80
- call_user_func_array(array($this, 'add'), $data);
81
- continue;
82
- }
83
-
84
- // redefine var
85
- $data = (string) $data;
86
-
87
- // load data
88
- $value = $this->load($data);
89
- $key = ($data != $value) ? $data : count($this->data);
90
-
91
- // replace CR linefeeds etc.
92
- // @see https://github.com/matthiasmullie/minify/pull/139
93
- $value = str_replace(array("\r\n", "\r"), "\n", $value);
94
-
95
- // store data
96
- $this->data[$key] = $value;
97
- }
98
-
99
- return $this;
100
- }
101
-
102
- /**
103
- * Minify the data & (optionally) saves it to a file.
104
- *
105
- * @param string[optional] $path Path to write the data to
106
- *
107
- * @return string The minified data
108
- */
109
- public function minify($path = null)
110
- {
111
- $content = $this->execute($path);
112
-
113
- // save to path
114
- if ($path !== null) {
115
- $this->save($content, $path);
116
- }
117
-
118
- return $content;
119
- }
120
-
121
- /**
122
- * Minify & gzip the data & (optionally) saves it to a file.
123
- *
124
- * @param string[optional] $path Path to write the data to
125
- * @param int[optional] $level Compression level, from 0 to 9
126
- *
127
- * @return string The minified & gzipped data
128
- */
129
- public function gzip($path = null, $level = 9)
130
- {
131
- $content = $this->execute($path);
132
- $content = gzencode($content, $level, FORCE_GZIP);
133
-
134
- // save to path
135
- if ($path !== null) {
136
- $this->save($content, $path);
137
- }
138
-
139
- return $content;
140
- }
141
-
142
- /**
143
- * Minify the data & write it to a CacheItemInterface object.
144
- *
145
- * @param CacheItemInterface $item Cache item to write the data to
146
- *
147
- * @return CacheItemInterface Cache item with the minifier data
148
- */
149
- public function cache(CacheItemInterface $item)
150
- {
151
- $content = $this->execute();
152
- $item->set($content);
153
-
154
- return $item;
155
- }
156
-
157
- /**
158
- * Minify the data.
159
- *
160
- * @param string[optional] $path Path to write the data to
161
- *
162
- * @return string The minified data
163
- */
164
- abstract public function execute($path = null);
165
-
166
- /**
167
- * Load data.
168
- *
169
- * @param string $data Either a path to a file or the content itself
170
- *
171
- * @return string
172
- */
173
- protected function load($data)
174
- {
175
- // check if the data is a file
176
- if ($this->canImportFile($data)) {
177
- $data = file_get_contents($data);
178
-
179
- // strip BOM, if any
180
- if (substr($data, 0, 3) == "\xef\xbb\xbf") {
181
- $data = substr($data, 3);
182
- }
183
- }
184
-
185
- return $data;
186
- }
187
-
188
- /**
189
- * Save to file.
190
- *
191
- * @param string $content The minified data
192
- * @param string $path The path to save the minified data to
193
- *
194
- * @throws IOException
195
- */
196
- protected function save($content, $path)
197
- {
198
- $handler = $this->openFileForWriting($path);
199
-
200
- $this->writeToFile($handler, $content);
201
-
202
- @fclose($handler);
203
- }
204
-
205
- /**
206
- * Register a pattern to execute against the source content.
207
- *
208
- * @param string $pattern PCRE pattern
209
- * @param string|callable $replacement Replacement value for matched pattern
210
- */
211
- protected function registerPattern($pattern, $replacement = '')
212
- {
213
- // study the pattern, we'll execute it more than once
214
- $pattern .= 'S';
215
-
216
- $this->patterns[] = array($pattern, $replacement);
217
- }
218
-
219
- /**
220
- * We can't "just" run some regular expressions against JavaScript: it's a
221
- * complex language. E.g. having an occurrence of // xyz would be a comment,
222
- * unless it's used within a string. Of you could have something that looks
223
- * like a 'string', but inside a comment.
224
- * The only way to accurately replace these pieces is to traverse the JS one
225
- * character at a time and try to find whatever starts first.
226
- *
227
- * @param string $content The content to replace patterns in
228
- *
229
- * @return string The (manipulated) content
230
- */
231
- protected function replace($content)
232
- {
233
- $processed = '';
234
- $positions = array_fill(0, count($this->patterns), -1);
235
- $matches = array();
236
-
237
- while ($content) {
238
- // find first match for all patterns
239
- foreach ($this->patterns as $i => $pattern) {
240
- list($pattern, $replacement) = $pattern;
241
-
242
- // we can safely ignore patterns for positions we've unset earlier,
243
- // because we know these won't show up anymore
244
- if (!isset($positions[$i])) {
245
- continue;
246
- }
247
-
248
- // no need to re-run matches that are still in the part of the
249
- // content that hasn't been processed
250
- if ($positions[$i] >= 0) {
251
- continue;
252
- }
253
-
254
- $match = null;
255
- if (preg_match($pattern, $content, $match, PREG_OFFSET_CAPTURE)) {
256
- $matches[$i] = $match;
257
-
258
- // we'll store the match position as well; that way, we
259
- // don't have to redo all preg_matches after changing only
260
- // the first (we'll still know where those others are)
261
- $positions[$i] = $match[0][1];
262
- } else {
263
- // if the pattern couldn't be matched, there's no point in
264
- // executing it again in later runs on this same content;
265
- // ignore this one until we reach end of content
266
- unset($matches[$i], $positions[$i]);
267
- }
268
- }
269
-
270
- // no more matches to find: everything's been processed, break out
271
- if (!$matches) {
272
- $processed .= $content;
273
- break;
274
- }
275
-
276
- // see which of the patterns actually found the first thing (we'll
277
- // only want to execute that one, since we're unsure if what the
278
- // other found was not inside what the first found)
279
- $discardLength = min($positions);
280
- $firstPattern = array_search($discardLength, $positions);
281
- $match = $matches[$firstPattern][0][0];
282
-
283
- // execute the pattern that matches earliest in the content string
284
- list($pattern, $replacement) = $this->patterns[$firstPattern];
285
- $replacement = $this->replacePattern($pattern, $replacement, $content);
286
-
287
- // figure out which part of the string was unmatched; that's the
288
- // part we'll execute the patterns on again next
289
- $content = (string) substr($content, $discardLength);
290
- $unmatched = (string) substr($content, strpos($content, $match) + strlen($match));
291
-
292
- // move the replaced part to $processed and prepare $content to
293
- // again match batch of patterns against
294
- $processed .= substr($replacement, 0, strlen($replacement) - strlen($unmatched));
295
- $content = $unmatched;
296
-
297
- // first match has been replaced & that content is to be left alone,
298
- // the next matches will start after this replacement, so we should
299
- // fix their offsets
300
- foreach ($positions as $i => $position) {
301
- $positions[$i] -= $discardLength + strlen($match);
302
- }
303
- }
304
-
305
- return $processed;
306
- }
307
-
308
- /**
309
- * This is where a pattern is matched against $content and the matches
310
- * are replaced by their respective value.
311
- * This function will be called plenty of times, where $content will always
312
- * move up 1 character.
313
- *
314
- * @param string $pattern Pattern to match
315
- * @param string|callable $replacement Replacement value
316
- * @param string $content Content to match pattern against
317
- *
318
- * @return string
319
- */
320
- protected function replacePattern($pattern, $replacement, $content)
321
- {
322
- if (is_callable($replacement)) {
323
- return preg_replace_callback($pattern, $replacement, $content, 1, $count);
324
- } else {
325
- return preg_replace($pattern, $replacement, $content, 1, $count);
326
- }
327
- }
328
-
329
- /**
330
- * Strings are a pattern we need to match, in order to ignore potential
331
- * code-like content inside them, but we just want all of the string
332
- * content to remain untouched.
333
- *
334
- * This method will replace all string content with simple STRING#
335
- * placeholder text, so we've rid all strings from characters that may be
336
- * misinterpreted. Original string content will be saved in $this->extracted
337
- * and after doing all other minifying, we can restore the original content
338
- * via restoreStrings().
339
- *
340
- * @param string[optional] $chars
341
- * @param string[optional] $placeholderPrefix
342
- */
343
- protected function extractStrings($chars = '\'"', $placeholderPrefix = '')
344
- {
345
- // PHP only supports $this inside anonymous functions since 5.4
346
- $minifier = $this;
347
- $callback = function ($match) use ($minifier, $placeholderPrefix) {
348
- // check the second index here, because the first always contains a quote
349
- if ($match[2] === '') {
350
- /*
351
- * Empty strings need no placeholder; they can't be confused for
352
- * anything else anyway.
353
- * But we still needed to match them, for the extraction routine
354
- * to skip over this particular string.
355
- */
356
- return $match[0];
357
- }
358
-
359
- $count = count($minifier->extracted);
360
- $placeholder = $match[1].$placeholderPrefix.$count.$match[1];
361
- $minifier->extracted[$placeholder] = $match[1].$match[2].$match[1];
362
-
363
- return $placeholder;
364
- };
365
-
366
- /*
367
- * The \\ messiness explained:
368
- * * Don't count ' or " as end-of-string if it's escaped (has backslash
369
- * in front of it)
370
- * * Unless... that backslash itself is escaped (another leading slash),
371
- * in which case it's no longer escaping the ' or "
372
- * * So there can be either no backslash, or an even number
373
- * * multiply all of that times 4, to account for the escaping that has
374
- * to be done to pass the backslash into the PHP string without it being
375
- * considered as escape-char (times 2) and to get it in the regex,
376
- * escaped (times 2)
377
- */
378
- $this->registerPattern('/(['.$chars.'])(.*?(?<!\\\\)(\\\\\\\\)*+)\\1/s', $callback);
379
- }
380
-
381
- /**
382
- * This method will restore all extracted data (strings, regexes) that were
383
- * replaced with placeholder text in extract*(). The original content was
384
- * saved in $this->extracted.
385
- *
386
- * @param string $content
387
- *
388
- * @return string
389
- */
390
- protected function restoreExtractedData($content)
391
- {
392
- if (!$this->extracted) {
393
- // nothing was extracted, nothing to restore
394
- return $content;
395
- }
396
-
397
- $content = strtr($content, $this->extracted);
398
-
399
- $this->extracted = array();
400
-
401
- return $content;
402
- }
403
-
404
- /**
405
- * Check if the path is a regular file and can be read.
406
- *
407
- * @param string $path
408
- *
409
- * @return bool
410
- */
411
- protected function canImportFile($path)
412
- {
413
- $parsed = parse_url($path);
414
- if (
415
- // file is elsewhere
416
- isset($parsed['host']) ||
417
- // file responds to queries (may change, or need to bypass cache)
418
- isset($parsed['query'])
419
- ) {
420
- return false;
421
- }
422
-
423
- return strlen($path) < PHP_MAXPATHLEN && @is_file($path) && is_readable($path);
424
- }
425
-
426
- /**
427
- * Attempts to open file specified by $path for writing.
428
- *
429
- * @param string $path The path to the file
430
- *
431
- * @return resource Specifier for the target file
432
- *
433
- * @throws IOException
434
- */
435
- protected function openFileForWriting($path)
436
- {
437
- if (($handler = @fopen($path, 'w')) === false) {
438
- throw new IOException('The file "'.$path.'" could not be opened for writing. Check if PHP has enough permissions.');
439
- }
440
-
441
- return $handler;
442
- }
443
-
444
- /**
445
- * Attempts to write $content to the file specified by $handler. $path is used for printing exceptions.
446
- *
447
- * @param resource $handler The resource to write to
448
- * @param string $content The content to write
449
- * @param string $path The path to the file (for exception printing only)
450
- *
451
- * @throws IOException
452
- */
453
- protected function writeToFile($handler, $content, $path = '')
454
- {
455
- if (($result = @fwrite($handler, $content)) === false || ($result < strlen($content))) {
456
- throw new IOException('The file "'.$path.'" could not be written to. Check your disk space and file permissions.');
457
- }
458
- }
459
- }
1
+ <?php
2
+ /**
3
+ * Abstract minifier class
4
+ *
5
+ * Please report bugs on https://github.com/matthiasmullie/minify/issues
6
+ *
7
+ * @author Matthias Mullie <minify@mullie.eu>
8
+ * @copyright Copyright (c) 2012, Matthias Mullie. All rights reserved
9
+ * @license MIT License
10
+ */
11
+ namespace MatthiasMullie\Minify;
12
+
13
+ use MatthiasMullie\Minify\Exceptions\IOException;
14
+ use Psr\Cache\CacheItemInterface;
15
+
16
+ /**
17
+ * Abstract minifier class.
18
+ *
19
+ * Please report bugs on https://github.com/matthiasmullie/minify/issues
20
+ *
21
+ * @package Minify
22
+ * @author Matthias Mullie <minify@mullie.eu>
23
+ * @copyright Copyright (c) 2012, Matthias Mullie. All rights reserved
24
+ * @license MIT License
25
+ */
26
+ abstract class Minify
27
+ {
28
+ /**
29
+ * The data to be minified.
30
+ *
31
+ * @var string[]
32
+ */
33
+ protected $data = array();
34
+
35
+ /**
36
+ * Array of patterns to match.
37
+ *
38
+ * @var string[]
39
+ */
40
+ protected $patterns = array();
41
+
42
+ /**
43
+ * This array will hold content of strings and regular expressions that have
44
+ * been extracted from the JS source code, so we can reliably match "code",
45
+ * without having to worry about potential "code-like" characters inside.
46
+ *
47
+ * @var string[]
48
+ */
49
+ public $extracted = array();
50
+
51
+ /**
52
+ * Init the minify class - optionally, code may be passed along already.
53
+ */
54
+ public function __construct(/* $data = null, ... */)
55
+ {
56
+ // it's possible to add the source through the constructor as well ;)
57
+ if (func_num_args()) {
58
+ call_user_func_array(array($this, 'add'), func_get_args());
59
+ }
60
+ }
61
+
62
+ /**
63
+ * Add a file or straight-up code to be minified.
64
+ *
65
+ * @param string|string[] $data
66
+ *
67
+ * @return static
68
+ */
69
+ public function add($data /* $data = null, ... */)
70
+ {
71
+ // bogus "usage" of parameter $data: scrutinizer warns this variable is
72
+ // not used (we're using func_get_args instead to support overloading),
73
+ // but it still needs to be defined because it makes no sense to have
74
+ // this function without argument :)
75
+ $args = array($data) + func_get_args();
76
+
77
+ // this method can be overloaded
78
+ foreach ($args as $data) {
79
+ if (is_array($data)) {
80
+ call_user_func_array(array($this, 'add'), $data);
81
+ continue;
82
+ }
83
+
84
+ // redefine var
85
+ $data = (string) $data;
86
+
87
+ // load data
88
+ $value = $this->load($data);
89
+ $key = ($data != $value) ? $data : count($this->data);
90
+
91
+ // replace CR linefeeds etc.
92
+ // @see https://github.com/matthiasmullie/minify/pull/139
93
+ $value = str_replace(array("\r\n", "\r"), "\n", $value);
94
+
95
+ // store data
96
+ $this->data[$key] = $value;
97
+ }
98
+
99
+ return $this;
100
+ }
101
+
102
+ /**
103
+ * Minify the data & (optionally) saves it to a file.
104
+ *
105
+ * @param string[optional] $path Path to write the data to
106
+ *
107
+ * @return string The minified data
108
+ */
109
+ public function minify($path = null)
110
+ {
111
+ $content = $this->execute($path);
112
+
113
+ // save to path
114
+ if ($path !== null) {
115
+ $this->save($content, $path);
116
+ }
117
+
118
+ return $content;
119
+ }
120
+
121
+ /**
122
+ * Minify & gzip the data & (optionally) saves it to a file.
123
+ *
124
+ * @param string[optional] $path Path to write the data to
125
+ * @param int[optional] $level Compression level, from 0 to 9
126
+ *
127
+ * @return string The minified & gzipped data
128
+ */
129
+ public function gzip($path = null, $level = 9)
130
+ {
131
+ $content = $this->execute($path);
132
+ $content = gzencode($content, $level, FORCE_GZIP);
133
+
134
+ // save to path
135
+ if ($path !== null) {
136
+ $this->save($content, $path);
137
+ }
138
+
139
+ return $content;
140
+ }
141
+
142
+ /**
143
+ * Minify the data & write it to a CacheItemInterface object.
144
+ *
145
+ * @param CacheItemInterface $item Cache item to write the data to
146
+ *
147
+ * @return CacheItemInterface Cache item with the minifier data
148
+ */
149
+ public function cache(CacheItemInterface $item)
150
+ {
151
+ $content = $this->execute();
152
+ $item->set($content);
153
+
154
+ return $item;
155
+ }
156
+
157
+ /**
158
+ * Minify the data.
159
+ *
160
+ * @param string[optional] $path Path to write the data to
161
+ *
162
+ * @return string The minified data
163
+ */
164
+ abstract public function execute($path = null);
165
+
166
+ /**
167
+ * Load data.
168
+ *
169
+ * @param string $data Either a path to a file or the content itself
170
+ *
171
+ * @return string
172
+ */
173
+ protected function load($data)
174
+ {
175
+ // check if the data is a file
176
+ if ($this->canImportFile($data)) {
177
+ $data = file_get_contents($data);
178
+
179
+ // strip BOM, if any
180
+ if (substr($data, 0, 3) == "\xef\xbb\xbf") {
181
+ $data = substr($data, 3);
182
+ }
183
+ }
184
+
185
+ return $data;
186
+ }
187
+
188
+ /**
189
+ * Save to file.
190
+ *
191
+ * @param string $content The minified data
192
+ * @param string $path The path to save the minified data to
193
+ *
194
+ * @throws IOException
195
+ */
196
+ protected function save($content, $path)
197
+ {
198
+ $handler = $this->openFileForWriting($path);
199
+
200
+ $this->writeToFile($handler, $content);
201
+
202
+ @fclose($handler);
203
+ }
204
+
205
+ /**
206
+ * Register a pattern to execute against the source content.
207
+ *
208
+ * @param string $pattern PCRE pattern
209
+ * @param string|callable $replacement Replacement value for matched pattern
210
+ */
211
+ protected function registerPattern($pattern, $replacement = '')
212
+ {
213
+ // study the pattern, we'll execute it more than once
214
+ $pattern .= 'S';
215
+
216
+ $this->patterns[] = array($pattern, $replacement);
217
+ }
218
+
219
+ /**
220
+ * We can't "just" run some regular expressions against JavaScript: it's a
221
+ * complex language. E.g. having an occurrence of // xyz would be a comment,
222
+ * unless it's used within a string. Of you could have something that looks
223
+ * like a 'string', but inside a comment.
224
+ * The only way to accurately replace these pieces is to traverse the JS one
225
+ * character at a time and try to find whatever starts first.
226
+ *
227
+ * @param string $content The content to replace patterns in
228
+ *
229
+ * @return string The (manipulated) content
230
+ */
231
+ protected function replace($content)
232
+ {
233
+ $processed = '';
234
+ $positions = array_fill(0, count($this->patterns), -1);
235
+ $matches = array();
236
+
237
+ while ($content) {
238
+ // find first match for all patterns
239
+ foreach ($this->patterns as $i => $pattern) {
240
+ list($pattern, $replacement) = $pattern;
241
+
242
+ // we can safely ignore patterns for positions we've unset earlier,
243
+ // because we know these won't show up anymore
244
+ if (array_key_exists($i, $positions) == false) {
245
+ continue;
246
+ }
247
+
248
+ // no need to re-run matches that are still in the part of the
249
+ // content that hasn't been processed
250
+ if ($positions[$i] >= 0) {
251
+ continue;
252
+ }
253
+
254
+ $match = null;
255
+ if (preg_match($pattern, $content, $match, PREG_OFFSET_CAPTURE)) {
256
+ $matches[$i] = $match;
257
+
258
+ // we'll store the match position as well; that way, we
259
+ // don't have to redo all preg_matches after changing only
260
+ // the first (we'll still know where those others are)
261
+ $positions[$i] = $match[0][1];
262
+ } else {
263
+ // if the pattern couldn't be matched, there's no point in
264
+ // executing it again in later runs on this same content;
265
+ // ignore this one until we reach end of content
266
+ unset($matches[$i], $positions[$i]);
267
+ }
268
+ }
269
+
270
+ // no more matches to find: everything's been processed, break out
271
+ if (!$matches) {
272
+ $processed .= $content;
273
+ break;
274
+ }
275
+
276
+ // see which of the patterns actually found the first thing (we'll
277
+ // only want to execute that one, since we're unsure if what the
278
+ // other found was not inside what the first found)
279
+ $discardLength = min($positions);
280
+ $firstPattern = array_search($discardLength, $positions);
281
+ $match = $matches[$firstPattern][0][0];
282
+
283
+ // execute the pattern that matches earliest in the content string
284
+ list($pattern, $replacement) = $this->patterns[$firstPattern];
285
+ $replacement = $this->replacePattern($pattern, $replacement, $content);
286
+
287
+ // figure out which part of the string was unmatched; that's the
288
+ // part we'll execute the patterns on again next
289
+ $content = (string) substr($content, $discardLength);
290
+ $unmatched = (string) substr($content, strpos($content, $match) + strlen($match));
291
+
292
+ // move the replaced part to $processed and prepare $content to
293
+ // again match batch of patterns against
294
+ $processed .= substr($replacement, 0, strlen($replacement) - strlen($unmatched));
295
+ $content = $unmatched;
296
+
297
+ // first match has been replaced & that content is to be left alone,
298
+ // the next matches will start after this replacement, so we should
299
+ // fix their offsets
300
+ foreach ($positions as $i => $position) {
301
+ $positions[$i] -= $discardLength + strlen($match);
302
+ }
303
+ }
304
+
305
+ return $processed;
306
+ }
307
+
308
+ /**
309
+ * This is where a pattern is matched against $content and the matches
310
+ * are replaced by their respective value.
311
+ * This function will be called plenty of times, where $content will always
312
+ * move up 1 character.
313
+ *
314
+ * @param string $pattern Pattern to match
315
+ * @param string|callable $replacement Replacement value
316
+ * @param string $content Content to match pattern against
317
+ *
318
+ * @return string
319
+ */
320
+ protected function replacePattern($pattern, $replacement, $content)
321
+ {
322
+ if (is_callable($replacement)) {
323
+ return preg_replace_callback($pattern, $replacement, $content, 1, $count);
324
+ } else {
325
+ return preg_replace($pattern, $replacement, $content, 1, $count);
326
+ }
327
+ }
328
+
329
+ /**
330
+ * Strings are a pattern we need to match, in order to ignore potential
331
+ * code-like content inside them, but we just want all of the string
332
+ * content to remain untouched.
333
+ *
334
+ * This method will replace all string content with simple STRING#
335
+ * placeholder text, so we've rid all strings from characters that may be
336
+ * misinterpreted. Original string content will be saved in $this->extracted
337
+ * and after doing all other minifying, we can restore the original content
338
+ * via restoreStrings().
339
+ *
340
+ * @param string[optional] $chars
341
+ * @param string[optional] $placeholderPrefix
342
+ */
343
+ protected function extractStrings($chars = '\'"', $placeholderPrefix = '')
344
+ {
345
+ // PHP only supports $this inside anonymous functions since 5.4
346
+ $minifier = $this;
347
+ $callback = function ($match) use ($minifier, $placeholderPrefix) {
348
+ // check the second index here, because the first always contains a quote
349
+ if ($match[2] === '') {
350
+ /*
351
+ * Empty strings need no placeholder; they can't be confused for
352
+ * anything else anyway.
353
+ * But we still needed to match them, for the extraction routine
354
+ * to skip over this particular string.
355
+ */
356
+ return $match[0];
357
+ }
358
+
359
+ $count = count($minifier->extracted);
360
+ $placeholder = $match[1].$placeholderPrefix.$count.$match[1];
361
+ $minifier->extracted[$placeholder] = $match[1].$match[2].$match[1];
362
+
363
+ return $placeholder;
364
+ };
365
+
366
+ /*
367
+ * The \\ messiness explained:
368
+ * * Don't count ' or " as end-of-string if it's escaped (has backslash
369
+ * in front of it)
370
+ * * Unless... that backslash itself is escaped (another leading slash),
371
+ * in which case it's no longer escaping the ' or "
372
+ * * So there can be either no backslash, or an even number
373
+ * * multiply all of that times 4, to account for the escaping that has
374
+ * to be done to pass the backslash into the PHP string without it being
375
+ * considered as escape-char (times 2) and to get it in the regex,
376
+ * escaped (times 2)
377
+ */
378
+ $this->registerPattern('/(['.$chars.'])(.*?(?<!\\\\)(\\\\\\\\)*+)\\1/s', $callback);
379
+ }
380
+
381
+ /**
382
+ * This method will restore all extracted data (strings, regexes) that were
383
+ * replaced with placeholder text in extract*(). The original content was
384
+ * saved in $this->extracted.
385
+ *
386
+ * @param string $content
387
+ *
388
+ * @return string
389
+ */
390
+ protected function restoreExtractedData($content)
391
+ {
392
+ if (!$this->extracted) {
393
+ // nothing was extracted, nothing to restore
394
+ return $content;
395
+ }
396
+
397
+ $content = strtr($content, $this->extracted);
398
+
399
+ $this->extracted = array();
400
+
401
+ return $content;
402
+ }
403
+
404
+ /**
405
+ * Check if the path is a regular file and can be read.
406
+ *
407
+ * @param string $path
408
+ *
409
+ * @return bool
410
+ */
411
+ protected function canImportFile($path)
412
+ {
413
+ $parsed = parse_url($path);
414
+ if (
415
+ // file is elsewhere
416
+ isset($parsed['host']) ||
417
+ // file responds to queries (may change, or need to bypass cache)
418
+ isset($parsed['query'])
419
+ ) {
420
+ return false;
421
+ }
422
+
423
+ return strlen($path) < PHP_MAXPATHLEN && @is_file($path) && is_readable($path);
424
+ }
425
+
426
+ /**
427
+ * Attempts to open file specified by $path for writing.
428
+ *
429
+ * @param string $path The path to the file
430
+ *
431
+ * @return resource Specifier for the target file
432
+ *
433
+ * @throws IOException
434
+ */
435
+ protected function openFileForWriting($path)
436
+ {
437
+ if (($handler = @fopen($path, 'w')) === false) {
438
+ throw new IOException('The file "'.$path.'" could not be opened for writing. Check if PHP has enough permissions.');
439
+ }
440
+
441
+ return $handler;
442
+ }
443
+
444
+ /**
445
+ * Attempts to write $content to the file specified by $handler. $path is used for printing exceptions.
446
+ *
447
+ * @param resource $handler The resource to write to
448
+ * @param string $content The content to write
449
+ * @param string $path The path to the file (for exception printing only)
450
+ *
451
+ * @throws IOException
452
+ */
453
+ protected function writeToFile($handler, $content, $path = '')
454
+ {
455
+ if (($result = @fwrite($handler, $content)) === false || ($result < strlen($content))) {
456
+ throw new IOException('The file "'.$path.'" could not be written to. Check your disk space and file permissions.');
457
+ }
458
+ }
459
+ }
libs/matthiasmullie/path-converter/src/Converter.php CHANGED
@@ -1,196 +1,196 @@
1
- <?php
2
-
3
- namespace MatthiasMullie\PathConverter;
4
-
5
- /**
6
- * Convert paths relative from 1 file to another.
7
- *
8
- * E.g.
9
- * ../../images/icon.jpg relative to /css/imports/icons.css
10
- * becomes
11
- * ../images/icon.jpg relative to /css/minified.css
12
- *
13
- * Please report bugs on https://github.com/matthiasmullie/path-converter/issues
14
- *
15
- * @author Matthias Mullie <pathconverter@mullie.eu>
16
- * @copyright Copyright (c) 2015, Matthias Mullie. All rights reserved
17
- * @license MIT License
18
- */
19
- class Converter implements ConverterInterface
20
- {
21
- /**
22
- * @var string
23
- */
24
- protected $from;
25
-
26
- /**
27
- * @var string
28
- */
29
- protected $to;
30
-
31
- /**
32
- * @param string $from The original base path (directory, not file!)
33
- * @param string $to The new base path (directory, not file!)
34
- * @param string $root Root directory (defaults to `getcwd`)
35
- */
36
- public function __construct($from, $to, $root = '')
37
- {
38
- $shared = $this->shared($from, $to);
39
- if ($shared === '') {
40
- // when both paths have nothing in common, one of them is probably
41
- // absolute while the other is relative
42
- $root = $root ?: getcwd();
43
- $from = strpos($from, $root) === 0 ? $from : preg_replace('/\/+/', '/', $root.'/'.$from);
44
- $to = strpos($to, $root) === 0 ? $to : preg_replace('/\/+/', '/', $root.'/'.$to);
45
-
46
- // or traveling the tree via `..`
47
- // attempt to resolve path, or assume it's fine if it doesn't exist
48
- $from = @realpath($from) ?: $from;
49
- $to = @realpath($to) ?: $to;
50
- }
51
-
52
- $from = $this->dirname($from);
53
- $to = $this->dirname($to);
54
-
55
- $from = $this->normalize($from);
56
- $to = $this->normalize($to);
57
-
58
- $this->from = $from;
59
- $this->to = $to;
60
- }
61
-
62
- /**
63
- * Normalize path.
64
- *
65
- * @param string $path
66
- *
67
- * @return string
68
- */
69
- protected function normalize($path)
70
- {
71
- // deal with different operating systems' directory structure
72
- $path = rtrim(str_replace(DIRECTORY_SEPARATOR, '/', $path), '/');
73
-
74
- /*
75
- * Example:
76
- * /home/forkcms/frontend/cache/compiled_templates/../../core/layout/css/../images/img.gif
77
- * to
78
- * /home/forkcms/frontend/core/layout/images/img.gif
79
- */
80
- do {
81
- $path = preg_replace('/[^\/]+(?<!\.\.)\/\.\.\//', '', $path, -1, $count);
82
- } while ($count);
83
-
84
- return $path;
85
- }
86
-
87
- /**
88
- * Figure out the shared path of 2 locations.
89
- *
90
- * Example:
91
- * /home/forkcms/frontend/core/layout/images/img.gif
92
- * and
93
- * /home/forkcms/frontend/cache/minified_css
94
- * share
95
- * /home/forkcms/frontend
96
- *
97
- * @param string $path1
98
- * @param string $path2
99
- *
100
- * @return string
101
- */
102
- protected function shared($path1, $path2)
103
- {
104
- // $path could theoretically be empty (e.g. no path is given), in which
105
- // case it shouldn't expand to array(''), which would compare to one's
106
- // root /
107
- $path1 = $path1 ? explode('/', $path1) : array();
108
- $path2 = $path2 ? explode('/', $path2) : array();
109
-
110
- $shared = array();
111
-
112
- // compare paths & strip identical ancestors
113
- foreach ($path1 as $i => $chunk) {
114
- if (isset($path2[$i]) && $path1[$i] == $path2[$i]) {
115
- $shared[] = $chunk;
116
- } else {
117
- break;
118
- }
119
- }
120
-
121
- return implode('/', $shared);
122
- }
123
-
124
- /**
125
- * Convert paths relative from 1 file to another.
126
- *
127
- * E.g.
128
- * ../images/img.gif relative to /home/forkcms/frontend/core/layout/css
129
- * should become:
130
- * ../../core/layout/images/img.gif relative to
131
- * /home/forkcms/frontend/cache/minified_css
132
- *
133
- * @param string $path The relative path that needs to be converted
134
- *
135
- * @return string The new relative path
136
- */
137
- public function convert($path)
138
- {
139
- // quit early if conversion makes no sense
140
- if ($this->from === $this->to) {
141
- return $path;
142
- }
143
-
144
- $path = $this->normalize($path);
145
- // if we're not dealing with a relative path, just return absolute
146
- if (strpos($path, '/') === 0) {
147
- return $path;
148
- }
149
-
150
- // normalize paths
151
- $path = $this->normalize($this->from.'/'.$path);
152
-
153
- // strip shared ancestor paths
154
- $shared = $this->shared($path, $this->to);
155
- $path = mb_substr($path, mb_strlen($shared));
156
- $to = mb_substr($this->to, mb_strlen($shared));
157
-
158
- // add .. for every directory that needs to be traversed to new path
159
- $to = str_repeat('../', count(array_filter(explode('/', $to))));
160
-
161
- return $to.ltrim($path, '/');
162
- }
163
-
164
- /**
165
- * Attempt to get the directory name from a path.
166
- *
167
- * @param string $path
168
- *
169
- * @return string
170
- */
171
- protected function dirname($path)
172
- {
173
- if (@is_file($path)) {
174
- return dirname($path);
175
- }
176
-
177
- if (@is_dir($path)) {
178
- return rtrim($path, '/');
179
- }
180
-
181
- // no known file/dir, start making assumptions
182
-
183
- // ends in / = dir
184
- if (mb_substr($path, -1) === '/') {
185
- return rtrim($path, '/');
186
- }
187
-
188
- // has a dot in the name, likely a file
189
- if (preg_match('/.*\..*$/', basename($path)) !== 0) {
190
- return dirname($path);
191
- }
192
-
193
- // you're on your own here!
194
- return $path;
195
- }
196
- }
1
+ <?php
2
+
3
+ namespace MatthiasMullie\PathConverter;
4
+
5
+ /**
6
+ * Convert paths relative from 1 file to another.
7
+ *
8
+ * E.g.
9
+ * ../../images/icon.jpg relative to /css/imports/icons.css
10
+ * becomes
11
+ * ../images/icon.jpg relative to /css/minified.css
12
+ *
13
+ * Please report bugs on https://github.com/matthiasmullie/path-converter/issues
14
+ *
15
+ * @author Matthias Mullie <pathconverter@mullie.eu>
16
+ * @copyright Copyright (c) 2015, Matthias Mullie. All rights reserved
17
+ * @license MIT License
18
+ */
19
+ class Converter implements ConverterInterface
20
+ {
21
+ /**
22
+ * @var string
23
+ */
24
+ protected $from;
25
+
26
+ /**
27
+ * @var string
28
+ */
29
+ protected $to;
30
+
31
+ /**
32
+ * @param string $from The original base path (directory, not file!)
33
+ * @param string $to The new base path (directory, not file!)
34
+ * @param string $root Root directory (defaults to `getcwd`)
35
+ */
36
+ public function __construct($from, $to, $root = '')
37
+ {
38
+ $shared = $this->shared($from, $to);
39
+ if ($shared === '') {
40
+ // when both paths have nothing in common, one of them is probably
41
+ // absolute while the other is relative
42
+ $root = $root ?: getcwd();
43
+ $from = strpos($from, $root) === 0 ? $from : preg_replace('/\/+/', '/', $root.'/'.$from);
44
+ $to = strpos($to, $root) === 0 ? $to : preg_replace('/\/+/', '/', $root.'/'.$to);
45
+
46
+ // or traveling the tree via `..`
47
+ // attempt to resolve path, or assume it's fine if it doesn't exist
48
+ $from = @realpath($from) ?: $from;
49
+ $to = @realpath($to) ?: $to;
50
+ }
51
+
52
+ $from = $this->dirname($from);
53
+ $to = $this->dirname($to);
54
+
55
+ $from = $this->normalize($from);
56
+ $to = $this->normalize($to);
57
+
58
+ $this->from = $from;
59
+ $this->to = $to;
60
+ }
61
+
62
+ /**
63
+ * Normalize path.
64
+ *
65
+ * @param string $path
66
+ *
67
+ * @return string
68
+ */
69
+ protected function normalize($path)
70
+ {
71
+ // deal with different operating systems' directory structure
72
+ $path = rtrim(str_replace(DIRECTORY_SEPARATOR, '/', $path), '/');
73
+
74
+ /*
75
+ * Example:
76
+ * /home/forkcms/frontend/cache/compiled_templates/../../core/layout/css/../images/img.gif
77
+ * to
78
+ * /home/forkcms/frontend/core/layout/images/img.gif
79
+ */
80
+ do {
81
+ $path = preg_replace('/[^\/]+(?<!\.\.)\/\.\.\//', '', $path, -1, $count);
82
+ } while ($count);
83
+
84
+ return $path;
85
+ }
86
+
87
+ /**
88
+ * Figure out the shared path of 2 locations.
89
+ *
90
+ * Example:
91
+ * /home/forkcms/frontend/core/layout/images/img.gif
92
+ * and
93
+ * /home/forkcms/frontend/cache/minified_css
94
+ * share
95
+ * /home/forkcms/frontend
96
+ *
97
+ * @param string $path1
98
+ * @param string $path2
99
+ *
100
+ * @return string
101
+ */
102
+ protected function shared($path1, $path2)
103
+ {
104
+ // $path could theoretically be empty (e.g. no path is given), in which
105
+ // case it shouldn't expand to array(''), which would compare to one's
106
+ // root /
107
+ $path1 = $path1 ? explode('/', $path1) : array();
108
+ $path2 = $path2 ? explode('/', $path2) : array();
109
+
110
+ $shared = array();
111
+
112
+ // compare paths & strip identical ancestors
113
+ foreach ($path1 as $i => $chunk) {
114
+ if (isset($path2[$i]) && $path1[$i] == $path2[$i]) {
115
+ $shared[] = $chunk;
116
+ } else {
117
+ break;
118
+ }
119
+ }
120
+
121
+ return implode('/', $shared);
122
+ }
123
+
124
+ /**
125
+ * Convert paths relative from 1 file to another.
126
+ *
127
+ * E.g.
128
+ * ../images/img.gif relative to /home/forkcms/frontend/core/layout/css
129
+ * should become:
130
+ * ../../core/layout/images/img.gif relative to
131
+ * /home/forkcms/frontend/cache/minified_css
132
+ *
133
+ * @param string $path The relative path that needs to be converted
134
+ *
135
+ * @return string The new relative path
136
+ */
137
+ public function convert($path)
138
+ {
139
+ // quit early if conversion makes no sense
140
+ if ($this->from === $this->to) {
141
+ return $path;
142
+ }
143
+
144
+ $path = $this->normalize($path);
145
+ // if we're not dealing with a relative path, just return absolute
146
+ if (strpos($path, '/') === 0) {
147
+ return $path;
148
+ }
149
+
150
+ // normalize paths
151
+ $path = $this->normalize($this->from.'/'.$path);
152
+
153
+ // strip shared ancestor paths
154
+ $shared = $this->shared($path, $this->to);
155
+ $path = mb_substr($path, mb_strlen($shared));
156
+ $to = mb_substr($this->to, mb_strlen($shared));
157
+
158
+ // add .. for every directory that needs to be traversed to new path
159
+ $to = str_repeat('../', count(array_filter(explode('/', $to))));
160
+
161
+ return $to.ltrim($path, '/');
162
+ }
163
+
164
+ /**
165
+ * Attempt to get the directory name from a path.
166
+ *
167
+ * @param string $path
168
+ *
169
+ * @return string
170
+ */
171
+ protected function dirname($path)
172
+ {
173
+ if (@is_file($path)) {
174
+ return dirname($path);
175
+ }
176
+
177
+ if (@is_dir($path)) {
178
+ return rtrim($path, '/');
179
+ }
180
+
181
+ // no known file/dir, start making assumptions
182
+
183
+ // ends in / = dir
184
+ if (mb_substr($path, -1) === '/') {
185
+ return rtrim($path, '/');
186
+ }
187
+
188
+ // has a dot in the name, likely a file
189
+ if (preg_match('/.*\..*$/', basename($path)) !== 0) {
190
+ return dirname($path);
191
+ }
192
+
193
+ // you're on your own here!
194
+ return $path;
195
+ }
196
+ }
libs/matthiasmullie/path-converter/src/ConverterInterface.php CHANGED
@@ -1,24 +1,24 @@
1
- <?php
2
-
3
- namespace MatthiasMullie\PathConverter;
4
-
5
- /**
6
- * Convert file paths.
7
- *
8
- * Please report bugs on https://github.com/matthiasmullie/path-converter/issues
9
- *
10
- * @author Matthias Mullie <pathconverter@mullie.eu>
11
- * @copyright Copyright (c) 2015, Matthias Mullie. All rights reserved
12
- * @license MIT License
13
- */
14
- interface ConverterInterface
15
- {
16
- /**
17
- * Convert file paths.
18
- *
19
- * @param string $path The path to be converted
20
- *
21
- * @return string The new path
22
- */
23
- public function convert($path);
24
- }
1
+ <?php
2
+
3
+ namespace MatthiasMullie\PathConverter;
4
+
5
+ /**
6
+ * Convert file paths.
7
+ *
8
+ * Please report bugs on https://github.com/matthiasmullie/path-converter/issues
9
+ *
10
+ * @author Matthias Mullie <pathconverter@mullie.eu>
11
+ * @copyright Copyright (c) 2015, Matthias Mullie. All rights reserved
12
+ * @license MIT License
13
+ */
14
+ interface ConverterInterface
15
+ {
16
+ /**
17
+ * Convert file paths.
18
+ *
19
+ * @param string $path The path to be converted
20
+ *
21
+ * @return string The new path
22
+ */
23
+ public function convert($path);
24
+ }
libs/matthiasmullie/path-converter/src/NoConverter.php CHANGED
@@ -1,23 +1,23 @@
1
- <?php
2
-
3
- namespace MatthiasMullie\PathConverter;
4
-
5
- /**
6
- * Don't convert paths.
7
- *
8
- * Please report bugs on https://github.com/matthiasmullie/path-converter/issues
9
- *
10
- * @author Matthias Mullie <pathconverter@mullie.eu>
11
- * @copyright Copyright (c) 2015, Matthias Mullie. All rights reserved
12
- * @license MIT License
13
- */
14
- class NoConverter implements ConverterInterface
15
- {
16
- /**
17
- * {@inheritdoc}
18
- */
19
- public function convert($path)
20
- {
21
- return $path;
22
- }
23
- }
1
+ <?php
2
+
3
+ namespace MatthiasMullie\PathConverter;
4
+
5
+ /**
6
+ * Don't convert paths.
7
+ *
8
+ * Please report bugs on https://github.com/matthiasmullie/path-converter/issues
9
+ *
10
+ * @author Matthias Mullie <pathconverter@mullie.eu>
11
+ * @copyright Copyright (c) 2015, Matthias Mullie. All rights reserved
12
+ * @license MIT License
13
+ */
14
+ class NoConverter implements ConverterInterface
15
+ {
16
+ /**
17
+ * {@inheritdoc}
18
+ */
19
+ public function convert($path)
20
+ {
21
+ return $path;
22
+ }
23
+ }
readme.txt CHANGED
@@ -1,10 +1,10 @@
1
  === Fast Velocity Minify ===
2
  Contributors: Alignak
3
- Tags: PHP Minify, Lighthouse, GTmetrix, Pingdom, Pagespeed, CSS Merging, JS Merging, CSS Minification, JS Minification, Speed Optimization, HTML Minification, Performance, Optimization, Speed, Fast
4
- Requires at least: 4.5
5
- Requires PHP: 5.5
6
- Stable tag: 2.7.4
7
- Tested up to: 5.2.2
8
  License: GPLv3 or later
9
  License URI: http://www.gnu.org/licenses/gpl-3.0.html
10
 
@@ -198,8 +198,23 @@ Please backup your site before updating. Version 3.0 will have a major code rewr
198
 
199
  == Changelog ==
200
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
201
  = 2.7.4 [2019.08.18] =
202
- * hange to open JS/CSS files suspected of having PHP code via HTTP request, instead of reading the file directly from disk
203
 
204
  = 2.7.3 [2019.07.29] =
205
  * Beaver Builder compatibility fix
@@ -256,297 +271,8 @@ Please backup your site before updating. Version 3.0 will have a major code rewr
256
  * improved compatibility with page cache plugins and servers (purging FVM without purging the page cache should be fine now)
257
  * added a daily cronjob, to delete public invalid cache files that are older than 3 months (your page cache should expire before this)
258
 
259
- = 2.5.9 [2019.02.19] =
260
- * fixed some PHP notices, when wordpress fails to download a missing js/css file
261
-
262
- = 2.5.8 [2019.02.06] =
263
- * minor bug fix with the defer for pagespeed option
264
-
265
- = 2.5.7 [2019.02.04] =
266
- * reverted back the css merging method to version 2.5.2 due to some compatibility issues
267
-
268
- = 2.5.6 [2019.01.18] =
269
- * fixed some php notices
270
- * disabled FVM on amp pages
271
- * expected to be the last update on the 2.x branch, before 3.0 major release
272
-
273
- = 2.5.5 [2019.01.12] =
274
- * fixed the dynamic urls being forced as http://
275
- * fixed the inlined styles being stripped when the inline all CSS option is enabled
276
- * added option to disable merging of inlined css code (for when you have dynamic inline css code)
277
- * other minor bug fixes
278
-
279
- = 2.5.4 [2019.01.11] =
280
- * css merging bug fixes
281
-
282
- = 2.5.3 [2019.01.11] =
283
- * fixed inlined css code being minified, even when minification is off
284
- * compatibility and performance improvements for the CSS merging and inlining functionality
285
-
286
- = 2.5.2 [2019.01.11] =
287
- * removed CURL as a fallback method (CURL is already a fallback on the WP HTTP API) as per WP recommendation
288
- * fixed a query monitor notice about mkdir
289
- * removed some legacy code + some minor performance improvements on the code
290
- * improvement for the defer for pagespeed option and ignore list
291
- * improvement for the loadCSS functionality
292
- * improvements for merging the google fonts option
293
-
294
- = 2.5.1 [2018.12.17] =
295
- * minor bug fix related to the font awesome option
296
- * added cache purging support to Breeze (Cloudways)
297
-
298
- = 2.5.0 [2018.12.13] =
299
- * bug fixes with the google fonts merging option
300
- * better default settings
301
-
302
- = 2.4.9 [2018.12.13] =
303
- * improved the google fonts merging to only allow existing google fonts (no more google fonts 404 errors due to the merging of the fonts)
304
- * downgraded PHP Minify to version 1.3.60 due to someone reporting a server error with the new version
305
-
306
- = 2.4.8 [2018.12.07] =
307
- * changed the file permissions for the generated cache directory and files, to match the uploads directory
308
- * added some extra checks for when PHP is running in safe mode
309
-
310
- = 2.4.7 [2018.12.06] =
311
- * added better default options after new install
312
- * added option to preserve FVM settings on deactivation
313
- * added an HTML comment to the frontend with the path and notification, when FVM cannot generate the cache files (ie: wrong file permissions)
314
- * added a notification on wp-admin when FVM cannot generate the cache files due to wrong permissions
315
- * added an option to force "chmod 0777" on FVM cache files, to avoid errors on servers that need more than "chmod 0755"
316
- * improved the google fonts merging option when the enqueue is faulty (ie: incomplete google font urls )
317
- * fixed the cache purge notifications not showing on wp-admin
318
-
319
- = 2.4.6 [2018.12.05] =
320
- * fixed a bug that could cause an error 500 if an enqueued CSS or JS file was not found
321
- * added brotli_static support, if you have the php-ext-brotli extension installed - https://github.com/kjdev/php-ext-brotli
322
-
323
- = 2.4.5 [2018.12.04] =
324
- * fixed a bug, where it may show a warning during cache purge on wp-admin
325
- * exclude footer FVM generated files from the HTTP header preload option (footer files are not in the critical path)
326
-
327
- = 2.4.4 [2018.12.03] =
328
- * added option to inline CSS in the footer, while still preserving the merged file in the header
329
- * improvements for the google fonts merging option
330
- * fixed double notification, when purging caches without a cache plugin
331
-
332
- = 2.4.3 [2018.12.03] =
333
- * added font-display, to ensure text remains visible during webfont load for inlined google fonts and font-awesome
334
- * added automatic removal of "source mappings" from JS files during merging or minification
335
- * added font awesome async and exclusion from PSI options, as well as merging and inlining when the url matches "font-awesome" (ie: cdnjs)
336
- * added automatically inline of small CSS code (up to 20KB) for any FVM CSS files in the footer (requests reduction)
337
- * added automatically inline of small CSS code (up to 20KB) for any FVM CSS files in the header, which are not of mediatype "all"
338
- * improved the cache purge button (no more redirect from frontend to backend)
339
- * updated PHP Minify and Path Converter from master
340
- * bug fixes related to "Exclude JS files from PSI" option
341
-
342
- = 2.4.2 [2018.11.29] =
343
- * fixed a bug with the "Exclude JS files in the ignore list from PSI" option (it wasn't excluding properly)
344
- * improved functionality with the "Exclude CSS files from PSI" option (now works with both inline and link stylesheets)
345
- * added an option to automatically preload the CSS and JS files generated by FVM (beware that some server caches like Pantheon may push old css and js files even after purging caches on FVM)
346
- * improved JavaScript minification
347
-
348
- = 2.4.1 [2018.11.27] =
349
- * better FVM default settings
350
-
351
- = 2.4.0 [2018.11.26] =
352
- * bug fixes related to the inline css option
353
- * changed a few options and added better descriptions to the admin options
354
-
355
- = 2.3.9 [2018.11.24] =
356
- * there was an error on my end while pushing 2.3.8... this is a version bump
357
-
358
- = 2.3.8 [2018.11.24] =
359
- * removed the dynamic protocol in favour of auto detecting HTTP(s)
360
- * fixed a bug where some CSS files were being removed with the latest CSS inline method
361
- * fixed a bug where the wrong file path was being generated for fonts and some static assets (when the plugin or theme, uses relative paths and the Inline CSS option is enabled)
362
-
363
- = 2.3.7 [2018.11.24] =
364
- * bug fixes and performance improvements
365
- * changed a few "options" location to other tabs
366
- * changed the "Inline CSS" method to inline each file right separatly, instead of merging it and then inline it (also improves compatibility)
367
- * added option to exclude JS and CSS files from PSI separatly (will load them Async, so make sure to read the instructions for each)
368
- * added a dedicated Critical Path CSS for "is_front_page" (more conditionals on the roadmap)
369
- * renamed some labels to be more explicit regardless of what they do
370
-
371
- = 2.3.6 [2018.11.20] =
372
- * added better header preloader and preconnect headers for css and js files
373
- * added support to automatically purge the cache enabler plugin
374
- * added option to reload the cache, while preserving the old static files
375
- * added better default options after first install
376
- * added and reorganized some options
377
- * added a new developers tab
378
- * removed the YUI compressor option (defaults to PHP Minify)
379
- * readme and screenshots update
380
- * tested up to WP 5.0 tag
381
-
382
- = 2.3.5 [2018.08.27] =
383
- * added thinkwithgoogle support for the defer for insights option
384
- * added HyperCache support, thanks to @erich_k4wp
385
- * added suport for wp_add_inline_script, thanks to @yuriipavlov
386
- * fixed a bug where some inlined css was missing if not attached to a parent css file
387
- * the ignore list now also supports CSS handle names (no JS yet)
388
- * updated PHP Minify from master on github
389
- * improved performance for gtmetrix tests
390
-
391
- = 2.3.4 [2018.06.30] =
392
- * bug fix
393
-
394
- = 2.3.3 [2018.06.30] =
395
- * added a check to prevent creating an empty js or css file
396
- * added an option to force the CDN option when using the defer for insights option
397
- * removed the alternative HTML minification method
398
- * minor performance and bug fixes
399
-
400
- = 2.3.2 [2018.06.03] =
401
- * added some compatibility fixes when merging and minifying JS files
402
- * added an option to enable an "FVM Purge" button on the admin bar
403
- * moved all large transients (cached css or js code) to temporary disk files to reduce the database load
404
-
405
- = 2.3.1 [2018.06.01] =
406
- * bug fixes and performance tweaks for the "fix page editors" option
407
-
408
- = 2.3.0 [2018.05.24] =
409
- * added wp cli support for purge cache (usage: wp fvm purge)
410
- * added wp cli support for getting the cache size (usage: wp fvm stats)
411
-
412
- = 2.2.9 [2018.05.23] =
413
- * fixed several bugs related to notices, css minification and file paths
414
- * added more pcre.backtrack_limit and pcre.recursion_limit to avoid blank pages on some servers
415
- * added new option to defer the ignore list for pagespeed
416
-
417
- = 2.2.8 [2018.01.21] =
418
- * rollback to 2.2.6 + bugfixes
419
-
420
- = 2.2.7 [2018.02.19] =
421
- * fixed a bug with the blacklist functionality
422
- * replaced PHP Minify with JSMin as the default JS minification
423
- * replaced PHP Minify with CSSTidy as the default CSS minification
424
- * replaced PHP Minify with Minify HTML as the default HTML minification
425
- * moved the intermediary cache from transients to disk files
426
-
427
- = 2.2.6 [2018.01.06] =
428
- * fixed a bug with html minification on some files that should not be minified
429
- * fixed a bug with the defer for pagespeed insights
430
- * updated the default blacklist (delete all entries and save again, to restore)
431
-
432
- = 2.2.5 [2017.12.18] =
433
- * fixed a fatal error reported on the support forum
434
-
435
- = 2.2.4 [2017.12.17] =
436
- * added custom cache directory and url support
437
- * cleaned up some old unused code
438
- * updated to the latest PHP Minify version
439
- * added better descriptions and labels for some options
440
- * added auto exclusion for js and css files when defer for pagespeed is enabled
441
-
442
- = 2.2.3 [2017.12.16] =
443
- * added robots.txt and ajax requests to the exclusion list
444
- * added some cdn fixes
445
- * added a new Pro tab
446
- * added a global critical path css section
447
- * added an option to dequeue all css files
448
- * added an option to load CSS Async with LoadCSS (experimental)
449
- * added an option to merge external resources together
450
- * added the possibility to manage the default ignore list (reported files that cause conflicts when merged)
451
- * added the possibility to manage the blacklist (files that cannot be merged with normal files)
452
- * added better descriptions and labels for some options
453
-
454
- = 2.2.2 [2017.11.12] =
455
- * fixed the current cdn option box
456
- * fixed some other minor bugs and notices
457
- * added option to remove all enqueued google fonts (so you can use your own CSS @fontfaces manually)
458
- * added font hinting for the "Inline Google Fonts CSS" option, so it looks better on Windows
459
-
460
- = 2.2.1 [2017.08.21] =
461
- * added unicode support to the alternative html minification option
462
- * improved some options description
463
-
464
- = 2.2.0 [2017.08.13] =
465
- * fixed some debug notices
466
- * fixed the alternative html minification option
467
-
468
- = 2.1.9 [2017.08.11] =
469
- * fixed a development bug
470
-
471
- = 2.1.8 [2017.08.11] =
472
- * fixed the html minification not working
473
- * added support for the cdn enabler plugin (force http or https method)
474
-
475
- = 2.1.7 [2017.07.17] =
476
- * improved html minification speed and response time to the first byte
477
- * fixed a random bug with the html minification library on large html pages (white pages)
478
- * added support for the "Nginx Cache" plugin purge, by Till Krüss
479
-
480
- = 2.1.6 [2017.07.17] =
481
- * fixed a php notice in debug mode
482
- * children styles (added with wp_add_inline_style) are now kept in order and merged together in place
483
- * added faqs for possible "visual composer" issues
484
-
485
- = 2.1.5 [2017.07.17] =
486
- * css bug fixes and performance improvements
487
- * added support for auto purging on WP Engine
488
-
489
- = 2.1.4 [2017.07.14] =
490
- * added compatibility with WP Engine.com and other providers that use a CNAME with their own subdomain
491
-
492
- = 2.1.3 [2017.07.11] =
493
- * updated PHP Minify for better compatibility
494
- * added an alternative mode for html minification (because PHP Minify sometimes breaks things)
495
- * css bug fixes and performance improvements
496
-
497
- = 2.1.2 [2017.06.27] =
498
- * fixed another error notice when debug mode is on
499
-
500
- = 2.1.1 [2017.06.24] =
501
- * fixed an error notice
502
-
503
- = 2.1.0 [2017.06.21] =
504
- * some performance improvements
505
-
506
- = 2.0.9 [2017.06.01] =
507
- * several bug and compatibility fixes
508
-
509
- = 2.0.8 [2017.05.28] =
510
- * fixed a notice alert on php for undefined function
511
-
512
- = 2.0.7 [2017.05.28] =
513
- * added support for auto purging of LiteSpeed Cache
514
- * added support for auto purging on Godaddy Managed WordPress Hosting
515
- * added the ie only blacklist, wich doesn't split merged files anymore, like the ignore list does
516
- * added auto updates for the default ignore list and blacklist from our api once every 24 hours
517
- * added cdn rewrite support for generated css and js files only
518
- * removed url protocol rewrites and set default to dynamic "//" protocols
519
- * updated the faqs
520
-
521
- = 2.0.6 [2017.05.22] =
522
- * added a "Troubleshooting" option to fix frontend editors for admin and editor level users
523
- * updated the faqs
524
-
525
- = 2.0.5 [2017.05.15] =
526
- * fixed preserving the SVG namespace definition "http://www.w3.org/2000/svg" used on Bootstrap 4
527
- * added some exclusions for Thrive and Visual Composer frontend preview and editors
528
-
529
- = 2.0.4 [2017.05.15] =
530
- * improved compatibility with Windows operating systems
531
-
532
- = 2.0.3 [2017.05.15] =
533
- * fixed an "undefined" notice
534
-
535
- = 2.0.2 [2017.05.14] =
536
- * improved compatibility on JS merging and minification
537
-
538
- = 2.0.1 [2017.05.11] =
539
- * fixed missing file that caused some errors on new installs
540
-
541
  = 2.0.0 [2017.05.11] =
542
- * moved the css and js merging base code back to 1.4.3 because it was better for compatibility
543
- * removed the font awesome optimization tweaks because people have multiple versions and requirements (but duplicate css and js files are always removed)
544
- * added all usable improvements and features up to 1.5.2, except for the "Defer CSS" and "Critical Path" features (will consider for the future)
545
- * added info to the FAQ's about our internal blacklist for known CSS or JS files that are always ignored by the plugin
546
- * changed the way CSS and JS files are fetched and merged to make use of the new improvements that were supposed to be on 1.4.4+
547
- * changed the advanced settings tab back to the settings page for quicker selection of options by the advanced users
548
- * changed the cache purging option to also delete our plugin transients via the API, rather than just let them expire
549
- * changed the "Inline all CSS" option into header and footer separately
550
 
551
  = 1.0 [2016.06.19] =
552
  * Initial Release
1
  === Fast Velocity Minify ===
2
  Contributors: Alignak
3
+ Tags: PHP Minify, Lighthouse, GTmetrix, Pingdom, Pagespeed, CSS Merging, JS Merging, CSS Minification, JS Minification, Speed Optimization, HTML Minification, Performance, Optimization, FVM
4
+ Requires at least: 4.7
5
+ Requires PHP: 5.6
6
+ Stable tag: 2.7.8
7
+ Tested up to: 5.3
8
  License: GPLv3 or later
9
  License URI: http://www.gnu.org/licenses/gpl-3.0.html
10
 
198
 
199
  == Changelog ==
200
 
201
+ = 2.7.8 [2020.02.06] =
202
+ * updated PHP Minify with full support for PHP 7.4
203
+ * added try, catch wrappers for merged javacript files with console log errors (instead of letting the browser stop execution on error)
204
+ * improved compatibility with windows servers
205
+ * improved compatibility for font paths with some themes
206
+
207
+ = 2.7.7 [2019.10.15] =
208
+ * added a capability check on the status page ajax request, which could show the cache file path when debug mode is enabled to subscribers
209
+
210
+ = 2.7.6 [2019.10.10] =
211
+ * bug fix release
212
+
213
+ = 2.7.5 [2019.10.09] =
214
+ * added support to "after" scripts added via wp_add_inline_script
215
+
216
  = 2.7.4 [2019.08.18] =
217
+ * change to open JS/CSS files suspected of having PHP code via HTTP request, instead of reading the file directly from disk
218
 
219
  = 2.7.3 [2019.07.29] =
220
  * Beaver Builder compatibility fix
271
  * improved compatibility with page cache plugins and servers (purging FVM without purging the page cache should be fine now)
272
  * added a daily cronjob, to delete public invalid cache files that are older than 3 months (your page cache should expire before this)
273
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
274
  = 2.0.0 [2017.05.11] =
275
+ * version 2.x branch release
 
 
 
 
 
 
 
276
 
277
  = 1.0 [2016.06.19] =
278
  * Initial Release