Autoptimize - Version 2.4.0

Version Description

  • refactored significantly (no more "classlesses", all is OO), classes are autoloaded, tests added (travis-ci) by zytzagoo who deserves praise for his hard work!
  • new: image optimization (see "Extra"-tab) using Shortpixel's smart image optimization proxy
  • new: you can now disable JS/ CSS-files being aggregated, having them minified individually instead
  • new: Google Fonts can now be "aggregated & preloaded", this uses CSS which is loaded non render-blocking
  • new: Autoptimize "listens" to page caches being cleared, upon which it purges it's own cache as well. Support depends on known action hooks firing by the page cache, supported by Hyper Cache, WP Rocket, W3 Total Cache, KeyCDN Cache Enabler, Swift, WP Super Cache, WP Fastest Cache and Comet Cache.
  • new: local JS/ CSS-files that are excluded from optimization are minified by default (can be overridden by filter)
  • improvement: updated minifiers (with very significant improvements for YUI CSS compressor PHP port)
  • improvement: "remove all Google Fonts" is now more careful (avoiding removing entire CSS blocks)
  • misc. under the hood improvements (e.g. more robust cache clearing, better support for multibyte character sets, improved CDN rewrite logic, avoid PHP warnings when writing files to cache, ...)
Download this release

Release Info

Developer futtta
Plugin Icon 128x128 Autoptimize
Version 2.4.0
Comparing to
See all releases

Code changes from version 2.3.4 to 2.4.0

Files changed (49) hide show
  1. autoptimize.php +61 -300
  2. autoptimize_helper.php_example +5 -5
  3. classes/autoptimizeBase.php +620 -336
  4. classes/autoptimizeCLI.php +26 -14
  5. classes/autoptimizeCSSmin.php +57 -0
  6. classes/autoptimizeCache.php +542 -151
  7. classes/autoptimizeCacheChecker.php +112 -0
  8. classes/autoptimizeConfig.php +372 -227
  9. classes/autoptimizeExtra.php +921 -0
  10. classes/autoptimizeHTML.php +98 -67
  11. classes/autoptimizeMain.php +528 -0
  12. classes/autoptimizePartners.php +145 -0
  13. classes/autoptimizeScripts.php +339 -242
  14. classes/autoptimizeSpeedupper.php +110 -0
  15. classes/autoptimizeStyles.php +1070 -701
  16. classes/autoptimizeToolbar.php +72 -56
  17. classes/autoptimizeUtils.php +424 -0
  18. classes/autoptimizeVersionUpdatesHandler.php +206 -0
  19. classes/external/index.html +1 -1
  20. classes/external/js/index.html +1 -1
  21. classes/external/php/index.html +1 -1
  22. classes/external/php/jsmin-1.1.1.php +0 -291
  23. classes/external/php/{minify-2.3.1-jsmin.php → jsmin.php} +7 -6
  24. classes/external/php/minify-2.1.7-html.php +0 -257
  25. classes/external/php/minify-2.1.7-jsmin.php +0 -447
  26. classes/external/php/minify-css-compressor.php +0 -250
  27. classes/external/php/minify-html.php +59 -57
  28. classes/external/php/persist-admin-notices-dismissal/README.md +108 -0
  29. classes/external/php/persist-admin-notices-dismissal/dismiss-notice.js +29 -0
  30. classes/external/php/persist-admin-notices-dismissal/persist-admin-notices-dismissal.php +119 -0
  31. classes/external/php/yui-php-cssmin-2.4.8-4_fgo.php +0 -790
  32. classes/external/php/yui-php-cssmin-2.4.8-p10/cssmin.php +0 -1112
  33. classes/external/php/yui-php-cssmin-2.4.8-p10/data/hex-to-named-color-map.php +0 -37
  34. classes/external/php/yui-php-cssmin-2.4.8-p10/data/named-to-hex-color-map.php +0 -108
  35. classes/external/php/yui-php-cssmin-bundled/Colors.php +155 -0
  36. classes/external/php/yui-php-cssmin-bundled/Minifier.php +895 -0
  37. classes/external/php/yui-php-cssmin-bundled/Utils.php +149 -0
  38. {classlesses → classes/external/php/yui-php-cssmin-bundled}/index.html +1 -1
  39. classes/static/toolbar.css +8 -8
  40. classlesses/autoptimizeCacheChecker.php +0 -65
  41. classlesses/autoptimizeExtra.php +0 -386
  42. classlesses/autoptimizePageCacheFlush.php +0 -56
  43. classlesses/autoptimizePartners.php +0 -122
  44. classlesses/autoptimizeSpeedupper.php +0 -105
  45. classlesses/autoptimizeUpdateCode.php +0 -106
  46. config/default.php +35 -13
  47. config/delayed.php +0 -86
  48. localization/index.html +0 -1
  49. readme.txt +60 -282
autoptimize.php CHANGED
@@ -2,334 +2,95 @@
2
  /*
3
  Plugin Name: Autoptimize
4
  Plugin URI: https://autoptimize.com/
5
- Description: Optimizes your website, concatenating the CSS and JavaScript code, and compressing it.
6
- Version: 2.3.4
7
  Author: Frank Goossens (futtta)
8
  Author URI: https://autoptimize.com/
9
- Domain Path: localization/
10
  Text Domain: autoptimize
11
  Released under the GNU General Public License (GPL)
12
  http://www.gnu.org/licenses/gpl.txt
13
  */
14
 
15
- if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
 
 
16
 
17
- define('AUTOPTIMIZE_PLUGIN_DIR',plugin_dir_path(__FILE__));
18
 
19
- // Load config class
20
- include(AUTOPTIMIZE_PLUGIN_DIR.'classes/autoptimizeConfig.php');
21
-
22
- // Load toolbar class
23
- include( AUTOPTIMIZE_PLUGIN_DIR.'classes/autoptimizeToolbar.php' );
24
-
25
- // Load partners tab if admin
26
- if (is_admin()) {
27
- include AUTOPTIMIZE_PLUGIN_DIR.'classlesses/autoptimizePartners.php';
28
- }
29
-
30
- // Do we gzip when caching (needed early to load autoptimizeCache.php)
31
- define('AUTOPTIMIZE_CACHE_NOGZIP',(bool) get_option('autoptimize_cache_nogzip'));
32
-
33
- // Load cache class
34
- include(AUTOPTIMIZE_PLUGIN_DIR.'classes/autoptimizeCache.php');
35
-
36
- // wp-content dir name (automagically set, should not be needed), dirname of AO cache dir and AO-prefix can be overridden in wp-config.php
37
- if (!defined('AUTOPTIMIZE_WP_CONTENT_NAME')) { define('AUTOPTIMIZE_WP_CONTENT_NAME','/'.wp_basename( WP_CONTENT_DIR )); }
38
- if (!defined('AUTOPTIMIZE_CACHE_CHILD_DIR')) { define('AUTOPTIMIZE_CACHE_CHILD_DIR','/cache/autoptimize/'); }
39
- if (!defined('AUTOPTIMIZE_CACHEFILE_PREFIX')) { define('AUTOPTIMIZE_CACHEFILE_PREFIX', 'autoptimize_'); }
40
-
41
- // Plugin dir constants (plugin url's defined later to accomodate domain mapped sites)
42
- if (!defined('AUTOPTIMIZE_CACHE_DIR')) {
43
- if (is_multisite() && apply_filters( 'autoptimize_separate_blog_caches' , true )) {
44
- $blog_id = get_current_blog_id();
45
- define('AUTOPTIMIZE_CACHE_DIR', WP_CONTENT_DIR.AUTOPTIMIZE_CACHE_CHILD_DIR.$blog_id.'/' );
46
- } else {
47
- define('AUTOPTIMIZE_CACHE_DIR', WP_CONTENT_DIR.AUTOPTIMIZE_CACHE_CHILD_DIR );
48
- }
49
- }
50
- define('AUTOPTIMIZE_CACHE_DELAY',true);
51
- define('WP_ROOT_DIR',substr(WP_CONTENT_DIR, 0, strlen(WP_CONTENT_DIR)-strlen(AUTOPTIMIZE_WP_CONTENT_NAME)));
52
-
53
- // WP CLI
54
- if ( defined( 'WP_CLI' ) && WP_CLI ) {
55
- require_once AUTOPTIMIZE_PLUGIN_DIR . 'classes/autoptimizeCLI.php';
56
- }
57
-
58
- // Initialize the cache at least once
59
- $conf = autoptimizeConfig::instance();
60
-
61
- /* Check if we're updating, in which case we might need to do stuff and flush the cache
62
- to avoid old versions of aggregated files lingering around */
63
-
64
- $autoptimize_version="2.3.0";
65
- $autoptimize_db_version=get_option('autoptimize_version','none');
66
-
67
- if ($autoptimize_db_version !== $autoptimize_version) {
68
- if ($autoptimize_db_version==="none") {
69
- add_action('admin_notices', 'autoptimize_install_config_notice');
70
- } else {
71
- // updating, include the update-code
72
- include(AUTOPTIMIZE_PLUGIN_DIR.'classlesses/autoptimizeUpdateCode.php');
73
- }
74
-
75
- update_option('autoptimize_version',$autoptimize_version);
76
- $autoptimize_db_version=$autoptimize_version;
77
- }
78
-
79
- // Load translations
80
- function autoptimize_load_plugin_textdomain() {
81
- load_plugin_textdomain('autoptimize',false,plugin_basename(dirname( __FILE__ )).'/localization');
82
- }
83
- add_action( 'init', 'autoptimize_load_plugin_textdomain' );
84
-
85
- function autoptimize_uninstall(){
86
- autoptimizeCache::clearall();
87
-
88
- $delete_options=array("autoptimize_cache_clean", "autoptimize_cache_nogzip", "autoptimize_css", "autoptimize_css_datauris", "autoptimize_css_justhead", "autoptimize_css_defer", "autoptimize_css_defer_inline", "autoptimize_css_inline", "autoptimize_css_exclude", "autoptimize_html", "autoptimize_html_keepcomments", "autoptimize_js", "autoptimize_js_exclude", "autoptimize_js_forcehead", "autoptimize_js_justhead", "autoptimize_js_trycatch", "autoptimize_version", "autoptimize_show_adv", "autoptimize_cdn_url", "autoptimize_cachesize_notice","autoptimize_css_include_inline","autoptimize_js_include_inline","autoptimize_optimize_logged","autoptimize_optimize_checkout","autoptimize_extra_settings");
89
-
90
- if ( !is_multisite() ) {
91
- foreach ($delete_options as $del_opt) { delete_option( $del_opt ); }
92
- } else {
93
- global $wpdb;
94
- $blog_ids = $wpdb->get_col( "SELECT blog_id FROM $wpdb->blogs" );
95
- $original_blog_id = get_current_blog_id();
96
- foreach ( $blog_ids as $blog_id ) {
97
- switch_to_blog( $blog_id );
98
- foreach ($delete_options as $del_opt) { delete_option( $del_opt ); }
99
- }
100
- switch_to_blog( $original_blog_id );
101
- }
102
-
103
- if ( wp_get_schedule( 'ao_cachechecker' ) ) {
104
- wp_clear_scheduled_hook( 'ao_cachechecker' );
105
- }
106
- }
107
-
108
- function autoptimize_install_config_notice() {
109
- echo '<div class="updated"><p>';
110
- _e('Thank you for installing and activating Autoptimize. Please configure it under "Settings" -> "Autoptimize" to start improving your site\'s performance.', 'autoptimize' );
111
- echo '</p></div>';
112
- }
113
-
114
- function autoptimize_update_config_notice() {
115
- echo '<div class="updated"><p>';
116
- _e('Autoptimize has just been updated. Please <strong>test your site now</strong> and adapt Autoptimize config if needed.', 'autoptimize' );
117
- echo '</p></div>';
118
  }
119
 
120
- function autoptimize_cache_unavailable_notice() {
121
- echo '<div class="error"><p>';
122
- printf( __( 'Autoptimize cannot write to the cache directory (%s), please fix to enable CSS/ JS optimization!', 'autoptimize' ), AUTOPTIMIZE_CACHE_DIR );
123
- echo '</p></div>';
124
- }
125
 
126
- // Set up the buffering
127
- function autoptimize_start_buffering() {
128
- $ao_noptimize = false;
129
 
130
- // noptimize in qs to get non-optimized page for debugging
131
- if (array_key_exists("ao_noptimize",$_GET) || array_key_exists("ao_noptirocket",$_GET)) {
132
- if ( ( ($_GET["ao_noptimize"]==="1") || ($_GET["ao_noptirocket"]==="1") ) && (apply_filters('autoptimize_filter_honor_qs_noptimize',true)) ) {
133
- $ao_noptimize = true;
 
 
134
  }
135
  }
136
-
137
- // check for DONOTMINIFY constant as used by e.g. WooCommerce POS
138
- if (defined('DONOTMINIFY') && (constant('DONOTMINIFY')===true || constant('DONOTMINIFY')==="true")) {
139
- $ao_noptimize = true;
140
- }
141
-
142
- // if setting says not to optimize logged in user and user is logged in
143
- if ( get_option('autoptimize_optimize_logged','on') !== 'on' && is_user_logged_in() && current_user_can('edit_posts') ) {
144
- $ao_noptimize = true;
145
- }
146
-
147
- // if setting says not to optimize cart/ checkout
148
- if ( get_option('autoptimize_optimize_checkout','on') !== 'on' ) {
149
- // checking for woocommerce, easy digital downloads and wp ecommerce
150
- foreach ( array("is_checkout","is_cart","edd_is_checkout","wpsc_is_cart","wpsc_is_checkout") as $shopCond ) {
151
- if ( function_exists($shopCond) && $shopCond() ) {
152
- $ao_noptimize = true;
153
- break;
154
- }
155
- }
156
- }
157
-
158
- // filter you can use to block autoptimization on your own terms
159
- $ao_noptimize = (bool) apply_filters( 'autoptimize_filter_noptimize', $ao_noptimize );
160
-
161
- if (!is_feed() && !$ao_noptimize && !is_admin() && ( !function_exists('is_customize_preview') || !is_customize_preview() ) ) {
162
- // load speedupper conditionally (true by default?)
163
- if ( apply_filters('autoptimize_filter_speedupper', true) ) {
164
- include(AUTOPTIMIZE_PLUGIN_DIR.'classlesses/autoptimizeSpeedupper.php');
165
- }
166
-
167
- // Config element
168
- $conf = autoptimizeConfig::instance();
169
-
170
- // Load our base class
171
- include(AUTOPTIMIZE_PLUGIN_DIR.'classes/autoptimizeBase.php');
172
-
173
- // Load extra classes and set some vars
174
- if($conf->get('autoptimize_html')) {
175
- include(AUTOPTIMIZE_PLUGIN_DIR.'classes/autoptimizeHTML.php');
176
- // BUG: new minify-html does not support keeping HTML comments, skipping for now
177
- // if (defined('AUTOPTIMIZE_LEGACY_MINIFIERS')) {
178
- @include(AUTOPTIMIZE_PLUGIN_DIR.'classes/external/php/minify-html.php');
179
- // } else {
180
- // @include(AUTOPTIMIZE_PLUGIN_DIR.'classes/external/php/minify-2.1.7-html.php');
181
- // }
182
- }
183
-
184
- if($conf->get('autoptimize_js')) {
185
- include(AUTOPTIMIZE_PLUGIN_DIR.'classes/autoptimizeScripts.php');
186
- if (!class_exists('JSMin')) {
187
- if (defined('AUTOPTIMIZE_LEGACY_MINIFIERS')) {
188
- @include(AUTOPTIMIZE_PLUGIN_DIR.'classes/external/php/jsmin-1.1.1.php');
189
- } else {
190
- @include(AUTOPTIMIZE_PLUGIN_DIR.'classes/external/php/minify-2.3.1-jsmin.php');
191
- }
192
- }
193
- if ( ! defined( 'CONCATENATE_SCRIPTS' )) {
194
- define('CONCATENATE_SCRIPTS',false);
195
- }
196
- if ( ! defined( 'COMPRESS_SCRIPTS' )) {
197
- define('COMPRESS_SCRIPTS',false);
198
- }
199
- }
200
-
201
- if($conf->get('autoptimize_css')) {
202
- include(AUTOPTIMIZE_PLUGIN_DIR.'classes/autoptimizeStyles.php');
203
- if (defined('AUTOPTIMIZE_LEGACY_MINIFIERS')) {
204
- if (!class_exists('Minify_CSS_Compressor')) {
205
- @include(AUTOPTIMIZE_PLUGIN_DIR.'classes/external/php/minify-css-compressor.php');
206
- }
207
- } else {
208
- if (!class_exists('CSSmin')) {
209
- @include(AUTOPTIMIZE_PLUGIN_DIR.'classes/external/php/yui-php-cssmin-2.4.8-4_fgo.php');
210
- }
211
- }
212
- if ( ! defined( 'COMPRESS_CSS' )) {
213
- define('COMPRESS_CSS',false);
214
- }
215
- }
216
-
217
- // filter to be used with care, kills all output buffers when true. use with extreme caution. you have been warned!
218
- if (apply_filters('autoptimize_filter_obkiller',false)) {
219
- while (ob_get_level() > 0) {
220
- ob_end_clean();
221
- }
222
- }
223
- // Now, start the real thing!
224
- ob_start('autoptimize_end_buffering');
225
  }
 
 
 
226
  }
227
 
228
- // Action on end, this is where the magic happens
229
- function autoptimize_end_buffering($content) {
230
- if ( ((stripos($content,"<html") === false) && (stripos($content,"<!DOCTYPE html") === false)) || preg_match('/<html[^>]*(?:amp|⚡)/',$content) === 1 || stripos($content,"<xsl:stylesheet") !== false ) { return $content; }
231
-
232
- // load URL constants as late as possible to allow domain mapper to kick in
233
- if (function_exists("domain_mapping_siteurl")) {
234
- define('AUTOPTIMIZE_WP_SITE_URL',domain_mapping_siteurl(get_current_blog_id()));
235
- define('AUTOPTIMIZE_WP_CONTENT_URL',str_replace(get_original_url(AUTOPTIMIZE_WP_SITE_URL),AUTOPTIMIZE_WP_SITE_URL,content_url()));
236
- } else {
237
- define('AUTOPTIMIZE_WP_SITE_URL',site_url());
238
- define('AUTOPTIMIZE_WP_CONTENT_URL',content_url());
 
 
 
 
 
 
 
 
239
  }
240
 
241
- if ( is_multisite() && apply_filters( 'autoptimize_separate_blog_caches' , true ) ) {
242
- $blog_id = get_current_blog_id();
243
- define('AUTOPTIMIZE_CACHE_URL',AUTOPTIMIZE_WP_CONTENT_URL.AUTOPTIMIZE_CACHE_CHILD_DIR.$blog_id.'/' );
244
- } else {
245
- define('AUTOPTIMIZE_CACHE_URL',AUTOPTIMIZE_WP_CONTENT_URL.AUTOPTIMIZE_CACHE_CHILD_DIR);
246
  }
247
- define('AUTOPTIMIZE_WP_ROOT_URL',str_replace(AUTOPTIMIZE_WP_CONTENT_NAME,'',AUTOPTIMIZE_WP_CONTENT_URL));
248
- define('AUTOPTIMIZE_HASH',wp_hash(AUTOPTIMIZE_CACHE_DIR));
249
-
250
- // Config element
251
- $conf = autoptimizeConfig::instance();
252
-
253
- // Choose the classes
254
- $classes = array();
255
- if($conf->get('autoptimize_js'))
256
- $classes[] = 'autoptimizeScripts';
257
- if($conf->get('autoptimize_css'))
258
- $classes[] = 'autoptimizeStyles';
259
- if($conf->get('autoptimize_html'))
260
- $classes[] = 'autoptimizeHTML';
261
-
262
- // Set some options
263
- $classoptions = array(
264
- 'autoptimizeScripts' => array(
265
- 'justhead' => $conf->get('autoptimize_js_justhead'),
266
- 'forcehead' => $conf->get('autoptimize_js_forcehead'),
267
- 'trycatch' => $conf->get('autoptimize_js_trycatch'),
268
- 'js_exclude' => $conf->get('autoptimize_js_exclude'),
269
- 'cdn_url' => $conf->get('autoptimize_cdn_url'),
270
- 'include_inline' => $conf->get('autoptimize_js_include_inline')
271
- ),
272
- 'autoptimizeStyles' => array(
273
- 'justhead' => $conf->get('autoptimize_css_justhead'),
274
- 'datauris' => $conf->get('autoptimize_css_datauris'),
275
- 'defer' => $conf->get('autoptimize_css_defer'),
276
- 'defer_inline' => $conf->get('autoptimize_css_defer_inline'),
277
- 'inline' => $conf->get('autoptimize_css_inline'),
278
- 'css_exclude' => $conf->get('autoptimize_css_exclude'),
279
- 'cdn_url' => $conf->get('autoptimize_cdn_url'),
280
- 'include_inline' => $conf->get('autoptimize_css_include_inline'),
281
- 'nogooglefont' => $conf->get('autoptimize_css_nogooglefont')
282
- ),
283
- 'autoptimizeHTML' => array(
284
- 'keepcomments' => $conf->get('autoptimize_html_keepcomments')
285
- )
286
- );
287
-
288
- $content = apply_filters( 'autoptimize_filter_html_before_minify', $content );
289
 
290
- // Run the classes
291
- foreach($classes as $name) {
292
- $instance = new $name($content);
293
- if($instance->read($classoptions[$name])) {
294
- $instance->minify();
295
- $instance->cache();
296
- $content = $instance->getcontent();
297
- }
298
- unset($instance);
299
- }
300
-
301
- $content = apply_filters( 'autoptimize_html_after_minify', $content );
302
- return $content;
303
  }
304
 
305
- if ( autoptimizeCache::cacheavail() ) {
306
- $conf = autoptimizeConfig::instance();
307
- if( $conf->get('autoptimize_html') || $conf->get('autoptimize_js') || $conf->get('autoptimize_css') ) {
308
- // Hook to wordpress
309
- if (defined('AUTOPTIMIZE_INIT_EARLIER')) {
310
- add_action('init','autoptimize_start_buffering',-1);
311
- } else {
312
- if (!defined('AUTOPTIMIZE_HOOK_INTO')) { define('AUTOPTIMIZE_HOOK_INTO', 'template_redirect'); }
313
- add_action(constant("AUTOPTIMIZE_HOOK_INTO"),'autoptimize_start_buffering',2);
314
- }
315
- }
316
- } else {
317
- add_action('admin_notices', 'autoptimize_cache_unavailable_notice');
318
- }
319
 
320
- function autoptimize_activate() {
321
- register_uninstall_hook( __FILE__, 'autoptimize_uninstall' );
 
322
  }
323
- register_activation_hook( __FILE__, 'autoptimize_activate' );
324
 
325
- include_once('classlesses/autoptimizeCacheChecker.php');
 
 
 
 
 
 
326
 
327
- add_action('plugins_loaded','includeAutoptimizeExtra');
328
- function includeAutoptimizeExtra() {
329
- if ( apply_filters('autoptimize_filter_extra_activate',true) ) {
330
- include_once('classlesses/autoptimizeExtra.php');
331
  }
 
 
332
  }
333
 
334
- // Do not pollute other plugins
335
- unset($conf);
2
  /*
3
  Plugin Name: Autoptimize
4
  Plugin URI: https://autoptimize.com/
5
+ Description: Optimize your website's performance: JS, CSS, HTML, images, Google Fonts and more!
6
+ Version: 2.4.0
7
  Author: Frank Goossens (futtta)
8
  Author URI: https://autoptimize.com/
 
9
  Text Domain: autoptimize
10
  Released under the GNU General Public License (GPL)
11
  http://www.gnu.org/licenses/gpl.txt
12
  */
13
 
14
+ /**
15
+ * Autoptimize main plugin file.
16
+ */
17
 
 
18
 
19
+ if ( ! defined( 'ABSPATH' ) ) {
20
+ exit;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
21
  }
22
 
23
+ define( 'AUTOPTIMIZE_PLUGIN_VERSION', '2.4.0' );
 
 
 
 
24
 
25
+ // plugin_dir_path() returns the trailing slash!
26
+ define( 'AUTOPTIMIZE_PLUGIN_DIR', plugin_dir_path( __FILE__ ) );
27
+ define( 'AUTOPTIMIZE_PLUGIN_FILE', __FILE__ );
28
 
29
+ // Bail early if attempting to run on non-supported php versions.
30
+ if ( version_compare( PHP_VERSION, '5.3', '<' ) ) {
31
+ function autoptimize_incompatible_admin_notice() {
32
+ echo '<div class="error"><p>' . __( 'Autoptimize requires PHP 5.3 (or higher) to function properly. Please upgrade PHP. The Plugin has been auto-deactivated.', 'autoptimize' ) . '</p></div>';
33
+ if ( isset( $_GET['activate'] ) ) {
34
+ unset( $_GET['activate'] );
35
  }
36
  }
37
+ function autoptimize_deactivate_self() {
38
+ deactivate_plugins( plugin_basename( AUTOPTIMIZE_PLUGIN_FILE ) );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
39
  }
40
+ add_action( 'admin_notices', 'autoptimize_incompatible_admin_notice' );
41
+ add_action( 'admin_init', 'autoptimize_deactivate_self' );
42
+ return;
43
  }
44
 
45
+ function autoptimize_autoload( $class_name ) {
46
+ if ( in_array( $class_name, array( 'Minify_HTML', 'JSMin' ) ) ) {
47
+ $file = strtolower( $class_name );
48
+ $file = str_replace( '_', '-', $file );
49
+ $path = dirname( __FILE__ ) . '/classes/external/php/';
50
+ $filepath = $path . $file . '.php';
51
+ } elseif ( false !== strpos( $class_name, 'Autoptimize\\tubalmartin\\CssMin' ) ) {
52
+ $file = str_replace( 'Autoptimize\\tubalmartin\\CssMin\\', '', $class_name );
53
+ $path = dirname( __FILE__ ) . '/classes/external/php/yui-php-cssmin-bundled/';
54
+ $filepath = $path . $file . '.php';
55
+ } elseif ( 'autoptimize' === substr( $class_name, 0, 11 ) ) {
56
+ // One of our "old" classes.
57
+ $file = $class_name;
58
+ $path = dirname( __FILE__ ) . '/classes/';
59
+ $filepath = $path . $file . '.php';
60
+ } elseif ( 'PAnD' === $class_name ) {
61
+ $file = 'persist-admin-notices-dismissal';
62
+ $path = dirname( __FILE__ ) . '/classes/external/php/persist-admin-notices-dismissal/';
63
+ $filepath = $path . $file . '.php';
64
  }
65
 
66
+ // If we didn't match one of our rules, bail!
67
+ if ( ! isset( $filepath ) ) {
68
+ return;
 
 
69
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
70
 
71
+ require $filepath;
 
 
 
 
 
 
 
 
 
 
 
 
72
  }
73
 
74
+ spl_autoload_register( 'autoptimize_autoload' );
 
 
 
 
 
 
 
 
 
 
 
 
 
75
 
76
+ // Load WP CLI command(s) on demand.
77
+ if ( defined( 'WP_CLI' ) && WP_CLI ) {
78
+ require AUTOPTIMIZE_PLUGIN_DIR . 'classes/autoptimizeCLI.php';
79
  }
 
80
 
81
+ /**
82
+ * Retrieve the instance of the main plugin class.
83
+ *
84
+ * @return autoptimizeMain
85
+ */
86
+ function autoptimize() {
87
+ static $plugin = null;
88
 
89
+ if ( null === $plugin ) {
90
+ $plugin = new autoptimizeMain( AUTOPTIMIZE_PLUGIN_VERSION, AUTOPTIMIZE_PLUGIN_FILE );
 
 
91
  }
92
+
93
+ return $plugin;
94
  }
95
 
96
+ autoptimize()->run();
 
autoptimize_helper.php_example CHANGED
@@ -5,7 +5,7 @@ Plugin URI: http://blog.futtta.be/autoptimize
5
  Description: Autoptimize Helper contains some helper functions to make Autoptimize even more flexible
6
  Author: Frank Goossens (futtta)
7
  Version: 0.2
8
- Author URI: http://blog.futtta.be/
9
  */
10
 
11
  /* autoptimize_filter_css_datauri_maxsize: change the threshold at which background images are turned into data uri's;
@@ -33,7 +33,7 @@ function my_ao_exclude_image($imageexcl) {
33
  // add_filter('autoptimize_filter_js_defer','my_ao_override_defer',10,1);
34
  function my_ao_override_defer($defer) {
35
  return $defer."async ";
36
- }
37
 
38
  /* autoptimize_filter_noptimize: stop autoptimize from optimizing, e.g. based on URL as in example
39
 
@@ -133,11 +133,11 @@ function my_ao_css_include_inline() {
133
  function my_ao_css_defer_inline($inlined) {
134
  return $inlined."h2,h1{color:red !important;}";
135
  }
136
-
137
  /* autoptimize_filter_css_fonts_cdn: do we want to move fonts to the CDN-url as well
138
 
139
- @return: false (default) or true */
140
  // add_filter('autoptimize_filter_css_fonts_cdn','my_css_cdnfont',10,0);
141
  function my_css_cdnfont(){
142
- return true;
143
  }
5
  Description: Autoptimize Helper contains some helper functions to make Autoptimize even more flexible
6
  Author: Frank Goossens (futtta)
7
  Version: 0.2
8
+ Author URI: http://blog.futtta.be/
9
  */
10
 
11
  /* autoptimize_filter_css_datauri_maxsize: change the threshold at which background images are turned into data uri's;
33
  // add_filter('autoptimize_filter_js_defer','my_ao_override_defer',10,1);
34
  function my_ao_override_defer($defer) {
35
  return $defer."async ";
36
+ }
37
 
38
  /* autoptimize_filter_noptimize: stop autoptimize from optimizing, e.g. based on URL as in example
39
 
133
  function my_ao_css_defer_inline($inlined) {
134
  return $inlined."h2,h1{color:red !important;}";
135
  }
136
+
137
  /* autoptimize_filter_css_fonts_cdn: do we want to move fonts to the CDN-url as well
138
 
139
+ @return: false (default) or true */
140
  // add_filter('autoptimize_filter_css_fonts_cdn','my_css_cdnfont',10,0);
141
  function my_css_cdnfont(){
142
+ return true;
143
  }
classes/autoptimizeBase.php CHANGED
@@ -1,82 +1,127 @@
1
  <?php
2
- if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
 
 
3
 
4
- abstract class autoptimizeBase {
 
 
 
 
 
 
 
 
 
 
5
  protected $content = '';
6
- protected $tagWarning = false;
7
-
8
- public function __construct($content) {
 
 
 
 
 
 
 
9
  $this->content = $content;
10
  }
11
-
12
- //Reads the page and collects tags
13
- abstract public function read($justhead);
14
-
15
- //Joins and optimizes collected things
 
 
 
 
 
 
 
 
 
 
16
  abstract public function minify();
17
-
18
- //Caches the things
 
 
 
 
19
  abstract public function cache();
20
-
21
- //Returns the content
 
 
 
 
22
  abstract public function getcontent();
23
-
24
- //Converts an URL to a full path
25
- protected function getpath($url) {
26
- $url=apply_filters( 'autoptimize_filter_cssjs_alter_url', $url);
27
-
28
- if (strpos($url,'%')!==false) {
29
- $url=urldecode($url);
 
 
 
 
 
 
 
 
30
  }
31
 
32
- $siteHost=parse_url(AUTOPTIMIZE_WP_SITE_URL,PHP_URL_HOST);
33
- $contentHost=parse_url(AUTOPTIMIZE_WP_ROOT_URL,PHP_URL_HOST);
34
-
35
- // normalize
36
- if (strpos($url,'//')===0) {
37
- if (is_ssl()) {
38
- $url = "https:".$url;
 
39
  } else {
40
- $url = "http:".$url;
41
  }
42
- } else if ((strpos($url,'//')===false) && (strpos($url,$siteHost)===false)) {
43
- if (AUTOPTIMIZE_WP_SITE_URL === $siteHost) {
44
- $url = AUTOPTIMIZE_WP_SITE_URL.$url;
45
  } else {
46
- $subdir_levels=substr_count(preg_replace("/https?:\/\//","",AUTOPTIMIZE_WP_SITE_URL),"/");
47
- $url = AUTOPTIMIZE_WP_SITE_URL.str_repeat("/..",$subdir_levels).$url;
48
  }
49
  }
50
-
51
- if ($siteHost !== $contentHost) {
52
- $url=str_replace(AUTOPTIMIZE_WP_CONTENT_URL,AUTOPTIMIZE_WP_SITE_URL.AUTOPTIMIZE_WP_CONTENT_NAME,$url);
53
  }
54
 
55
- // first check; hostname wp site should be hostname of url
56
- $thisHost=@parse_url($url,PHP_URL_HOST);
57
- if ($thisHost !== $siteHost) {
58
- /*
59
- * first try to get all domains from WPML (if available)
60
- * then explicitely declare $this->cdn_url as OK as well
61
- * then apply own filter autoptimize_filter_cssjs_multidomain takes an array of hostnames
62
- * each item in that array will be considered part of the same WP multisite installation
63
- */
64
  $multidomains = array();
65
-
66
- $multidomainsWPML = apply_filters('wpml_setting', array(), 'language_domains');
67
- if (!empty($multidomainsWPML)) {
68
- $multidomains = array_map(array($this,"ao_getDomain"),$multidomainsWPML);
69
  }
70
-
71
- if (!empty($this->cdn_url)) {
72
- $multidomains[]=parse_url($this->cdn_url,PHP_URL_HOST);
73
  }
74
-
75
- $multidomains = apply_filters('autoptimize_filter_cssjs_multidomain', $multidomains);
76
-
77
- if (!empty($multidomains)) {
78
- if (in_array($thisHost,$multidomains)) {
79
- $url=str_replace($thisHost, parse_url(AUTOPTIMIZE_WP_SITE_URL,PHP_URL_HOST), $url);
80
  } else {
81
  return false;
82
  }
@@ -84,337 +129,576 @@ abstract class autoptimizeBase {
84
  return false;
85
  }
86
  }
87
-
88
- // try to remove "wp root url" from url while not minding http<>https
89
- $tmp_ao_root = preg_replace('/https?:/','',AUTOPTIMIZE_WP_ROOT_URL);
90
- if ($siteHost !== $contentHost) {
91
- // as we replaced the content-domain with the site-domain, we should match against that
92
- $tmp_ao_root = preg_replace('/https?:/','',AUTOPTIMIZE_WP_SITE_URL);
 
93
  }
94
- $tmp_url = preg_replace('/https?:/','',$url);
95
- $path = str_replace($tmp_ao_root,'',$tmp_url);
96
-
97
- // if path starts with :// or //, this is not a URL in the WP context and we have to assume we can't aggregate
98
- if (preg_match('#^:?//#',$path)) {
99
- /** External script/css (adsense, etc) */
 
 
100
  return false;
101
  }
102
 
103
- // prepend with WP_ROOT_DIR to have full path to file
104
- $path = str_replace('//','/',WP_ROOT_DIR.$path);
105
-
106
- // final check: does file exist and is it readable
107
- if (file_exists($path) && is_file($path) && is_readable($path)) {
108
  return $path;
109
  } else {
110
  return false;
111
  }
112
  }
113
 
114
- // needed for WPML-filter
115
- protected function ao_getDomain($in) {
116
- // make sure the url starts with something vaguely resembling a protocol
117
- if ((strpos($in,"http")!==0) && (strpos($in,"//")!==0)) {
118
- $in="http://".$in;
 
 
 
 
 
 
 
 
 
 
119
  }
120
-
121
- // do the actual parse_url
122
- $out = parse_url($in,PHP_URL_HOST);
123
-
124
- // fallback if parse_url does not understand the url is in fact a url
125
- if (empty($out)) $out=$in;
126
-
127
- return $out;
128
- }
129
 
 
 
130
 
131
- // logger
132
- protected function ao_logger($logmsg,$appendHTML=true) {
133
- if ($appendHTML) {
134
- $logmsg="<!--noptimize--><!-- ".$logmsg." --><!--/noptimize-->";
135
- $this->content.=$logmsg;
136
- } else {
137
- error_log("Autoptimize: ".$logmsg);
138
  }
 
 
139
  }
140
 
141
- // hide everything between noptimize-comment tags
142
- protected function hide_noptimize($noptimize_in) {
143
- if ( preg_match( '/<!--\s?noptimize\s?-->/', $noptimize_in ) ) {
144
- $noptimize_out = preg_replace_callback(
145
- '#<!--\s?noptimize\s?-->.*?<!--\s?/\s?noptimize\s?-->#is',
146
- create_function(
147
- '$matches',
148
- 'return "%%NOPTIMIZE".AUTOPTIMIZE_HASH."%%".base64_encode($matches[0])."%%NOPTIMIZE%%";'
149
- ),
150
- $noptimize_in
151
- );
152
- } else {
153
- $noptimize_out = $noptimize_in;
154
- }
155
- return $noptimize_out;
156
  }
157
-
158
- // unhide noptimize-tags
159
- protected function restore_noptimize($noptimize_in) {
160
- if ( strpos( $noptimize_in, '%%NOPTIMIZE%%' ) !== false ) {
161
- $noptimize_out = preg_replace_callback(
162
- '#%%NOPTIMIZE'.AUTOPTIMIZE_HASH.'%%(.*?)%%NOPTIMIZE%%#is',
163
- create_function(
164
- '$matches',
165
- 'return base64_decode($matches[1]);'
166
- ),
167
- $noptimize_in
168
- );
169
- } else {
170
- $noptimize_out = $noptimize_in;
171
- }
172
- return $noptimize_out;
173
  }
174
 
175
- protected function hide_iehacks($iehacks_in) {
176
- if ( strpos( $iehacks_in, '<!--[if' ) !== false ) {
177
- $iehacks_out = preg_replace_callback(
178
- '#<!--\[if.*?\[endif\]-->#is',
179
- create_function(
180
- '$matches',
181
- 'return "%%IEHACK".AUTOPTIMIZE_HASH."%%".base64_encode($matches[0])."%%IEHACK%%";'
182
- ),
183
- $iehacks_in
184
- );
185
- } else {
186
- $iehacks_out = $iehacks_in;
187
- }
188
- return $iehacks_out;
 
189
  }
190
 
191
- protected function restore_iehacks($iehacks_in) {
192
- if ( strpos( $iehacks_in, '%%IEHACK%%' ) !== false ) {
193
- $iehacks_out = preg_replace_callback(
194
- '#%%IEHACK'.AUTOPTIMIZE_HASH.'%%(.*?)%%IEHACK%%#is',
195
- create_function(
196
- '$matches',
197
- 'return base64_decode($matches[1]);'
198
- ),
199
- $iehacks_in
200
- );
201
- } else {
202
- $iehacks_out=$iehacks_in;
203
- }
204
- return $iehacks_out;
205
  }
206
 
207
- protected function hide_comments($comments_in) {
208
- if ( strpos( $comments_in, '<!--' ) !== false ) {
209
- $comments_out = preg_replace_callback(
210
- '#<!--.*?-->#is',
211
- create_function(
212
- '$matches',
213
- 'return "%%COMMENTS".AUTOPTIMIZE_HASH."%%".base64_encode($matches[0])."%%COMMENTS%%";'
214
- ),
215
- $comments_in
216
- );
217
- } else {
218
- $comments_out = $comments_in;
219
- }
220
- return $comments_out;
 
 
 
221
  }
222
 
223
- protected function restore_comments($comments_in) {
224
- if ( strpos( $comments_in, '%%COMMENTS%%' ) !== false ) {
225
- $comments_out = preg_replace_callback(
226
- '#%%COMMENTS'.AUTOPTIMIZE_HASH.'%%(.*?)%%COMMENTS%%#is',
227
- create_function(
228
- '$matches',
229
- 'return base64_decode($matches[1]);'
230
- ),
231
- $comments_in
232
- );
233
- } else {
234
- $comments_out=$comments_in;
235
- }
236
- return $comments_out;
237
  }
238
 
239
- protected function url_replace_cdn( $url ) {
240
- // API filter to change base CDN URL
241
- $cdn_url = apply_filters( 'autoptimize_filter_base_cdnurl', $this->cdn_url );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
242
 
243
- if ( !empty($cdn_url) ) {
244
- // prepend domain-less absolute URL's
245
- if ( ( substr( $url, 0, 1 ) === '/' ) && ( substr( $url, 1, 1 ) !== '/' ) ) {
246
- $url = rtrim( $cdn_url, '/' ) . $url;
 
 
 
 
247
  } else {
248
- // get wordpress base URL
249
- $WPSiteBreakdown = parse_url( AUTOPTIMIZE_WP_SITE_URL );
250
- $WPBaseUrl = $WPSiteBreakdown['scheme'] . '://' . $WPSiteBreakdown['host'];
251
- if ( ! empty( $WPSiteBreakdown['port'] ) ) {
252
- $WPBaseUrl .= ":" . $WPSiteBreakdown['port'];
253
- }
254
- // replace full url's with scheme
255
- $tmp_url = str_replace( $WPBaseUrl, rtrim( $cdn_url, '/' ), $url );
256
- if ( $tmp_url === $url ) {
257
- // last attempt; replace scheme-less URL's
258
- $url = str_replace( preg_replace( '/https?:/', '', $WPBaseUrl ), rtrim( $cdn_url, '/' ), $url );
259
  } else {
260
- $url = $tmp_url;
261
  }
 
 
262
  }
 
 
263
  }
264
 
265
- // allow API filter to alter URL after CDN replacement
266
  $url = apply_filters( 'autoptimize_filter_base_replace_cdn', $url );
 
267
  return $url;
268
  }
269
 
270
- protected function inject_in_html($payload,$replaceTag) {
271
- if (strpos($this->content,$replaceTag[0])!== false) {
272
- if ($replaceTag[1]==="after") {
273
- $replaceBlock=$replaceTag[0].$payload;
274
- } else if ($replaceTag[1]==="replace"){
275
- $replaceBlock=$payload;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
276
  } else {
277
- $replaceBlock=$payload.$replaceTag[0];
278
  }
279
- $this->content = substr_replace($this->content,$replaceBlock,strpos($this->content,$replaceTag[0]),strlen($replaceTag[0]));
 
 
 
 
 
 
 
 
280
  } else {
 
281
  $this->content .= $payload;
282
- if (!$this->tagWarning) {
283
- $this->content .= "<!--noptimize--><!-- Autoptimize found a problem with the HTML in your Theme, tag \"".str_replace(array("<",">"),"",$replaceTag[0])."\" missing --><!--/noptimize-->";
284
- $this->tagWarning=true;
 
285
  }
286
  }
287
  }
288
-
289
- protected function isremovable($tag, $removables) {
290
- foreach ($removables as $match) {
291
- if (strpos($tag,$match)!==false) {
 
 
 
 
 
 
 
 
 
292
  return true;
293
  }
294
  }
 
295
  return false;
296
  }
297
-
298
- // inject already minified code in optimized JS/CSS
299
- protected function inject_minified($in) {
300
- if ( strpos( $in, '%%INJECTLATER%%' ) !== false ) {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
301
  $out = preg_replace_callback(
302
- '#\/\*\!%%INJECTLATER'.AUTOPTIMIZE_HASH.'%%(.*?)%%INJECTLATER%%\*\/#is',
303
- create_function(
304
- '$matches',
305
- '$filepath=base64_decode(strtok($matches[1],"|"));
306
- $filecontent=file_get_contents($filepath);
307
-
308
- // remove BOM
309
- $filecontent = preg_replace("#\x{EF}\x{BB}\x{BF}#","",$filecontent);
310
-
311
- // remove comments and blank lines
312
- if (substr($filepath,-3,3)===".js") {
313
- $filecontent=preg_replace("#^\s*\/\/.*$#Um","",$filecontent);
314
- }
315
-
316
- $filecontent=preg_replace("#^\s*\/\*[^!].*\*\/\s?#Um","",$filecontent);
317
- $filecontent=preg_replace("#(^[\r\n]*|[\r\n]+)[\s\t]*[\r\n]+#", "\n", $filecontent);
318
-
319
- // differentiate between JS, CSS and other files
320
- if (substr($filepath,-3,3)===".js") {
321
- if ((substr($filecontent,-1,1)!==";")&&(substr($filecontent,-1,1)!=="}")) {
322
- $filecontent.=";";
323
- }
324
-
325
- if (get_option("autoptimize_js_trycatch")==="on") {
326
- $filecontent="try{".$filecontent."}catch(e){}";
327
- }
328
- } else if ((substr($filepath,-4,4)===".css")) {
329
- $filecontent=autoptimizeStyles::fixurls($filepath,$filecontent);
330
- } else {
331
- $filecontent="";
332
- }
333
-
334
- // return
335
- return "\n".$filecontent;'
336
- ),
337
  $in
338
  );
339
- } else {
340
- $out = $in;
341
  }
 
342
  return $out;
343
  }
344
-
345
- protected function minify_single($pathIn) {
346
- // determine JS or CSS and set var (also mimetype), return false if neither
347
- if ( $this->str_ends_in($pathIn,".js") === true ) {
348
- $codeType="js";
349
- $codeMime="text/javascript";
350
- } else if ( $this->str_ends_in($pathIn,".css") === true ) {
351
- $codeType="css";
352
- $codeMime="text/css";
353
- } else {
354
- return false;
355
- }
356
-
357
- // if min.js or min.css return false
358
- if (( $this->str_ends_in($pathIn,"-min.".$codeType) === true ) || ( $this->str_ends_in($pathIn,".min.".$codeType) === true ) || ( $this->str_ends_in($pathIn,"js/jquery/jquery.js") === true ) ) {
359
- return false;
360
- }
361
-
362
- // read file, return false if empty
363
- $_toMinify = file_get_contents($pathIn);
364
- if ( empty($_toMinify) ) return false;
365
-
366
- // check cache
367
- $_md5hash = "single_".md5($_toMinify);
368
- $_cache = new autoptimizeCache($_md5hash,$codeType);
369
- if ($_cache->check() ) {
370
- $_CachedMinifiedUrl = AUTOPTIMIZE_CACHE_URL.$_cache->getname();
371
- } else {
372
- // if not in cache first minify
373
- $_Minified = $_toMinify;
374
- if ($codeType === "js") {
375
- if (class_exists('JSMin') && apply_filters( 'autoptimize_js_do_minify' , true)) {
376
- if (@is_callable(array("JSMin","minify"))) {
377
- $tmp_code = trim(JSMin::minify($_toMinify));
378
- }
379
- }
380
- } else if ($codeType === "css") {
381
- // make sure paths to background images/ imported css/ fonts .. are OK
382
- $_toMinify = autoptimizeStyles::fixurls($pathIn,$_toMinify);
383
- if (class_exists('Minify_CSS_Compressor')) {
384
- $tmp_code = trim(Minify_CSS_Compressor::process($_toMinify));
385
- } else if(class_exists('CSSmin')) {
386
- $cssmin = new CSSmin();
387
- if (method_exists($cssmin,"run")) {
388
- $tmp_code = trim($cssmin->run($_toMinify));
389
- } elseif (@is_callable(array($cssmin,"minify"))) {
390
- $tmp_code = trim(CssMin::minify($_toMinify));
391
- }
392
- }
393
- }
394
- if (!empty($tmp_code)) {
395
- $_Minified = $tmp_code;
396
- unset($tmp_code);
397
- }
398
- // and then cache
399
- $_cache->cache($_Minified,$codeMime);
400
- $_CachedMinifiedUrl = AUTOPTIMIZE_CACHE_URL.$_cache->getname();
401
- }
402
- unset($_cache);
403
-
404
- // if CDN, then CDN
405
- $_CachedMinifiedUrl = $this->url_replace_cdn($_CachedMinifiedUrl);
406
-
407
- return $_CachedMinifiedUrl;
408
- }
409
-
410
- protected function str_ends_in($haystack,$needle) {
411
- $needleLength = strlen($needle);
412
- $haystackLength = strlen($haystack);
413
- $lastPos=strrpos($haystack,$needle);
414
- if ($lastPos === $haystackLength - $needleLength) {
415
- return true;
416
- } else {
417
- return false;
418
- }
419
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
420
  }
1
  <?php
2
+ /**
3
+ * Base class other (more-specific) classes inherit from.
4
+ */
5
 
6
+ if ( ! defined( 'ABSPATH' ) ) {
7
+ exit;
8
+ }
9
+
10
+ abstract class autoptimizeBase
11
+ {
12
+ /**
13
+ * Holds content being processed (html, scripts, styles)
14
+ *
15
+ * @var string
16
+ */
17
  protected $content = '';
18
+
19
+ /**
20
+ * Controls debug logging.
21
+ *
22
+ * @var bool
23
+ */
24
+ public $debug_log = false;
25
+
26
+ public function __construct( $content )
27
+ {
28
  $this->content = $content;
29
  }
30
+
31
+ /**
32
+ * Reads the page and collects tags.
33
+ *
34
+ * @param array $options Options.
35
+ *
36
+ * @return bool
37
+ */
38
+ abstract public function read( $options );
39
+
40
+ /**
41
+ * Joins and optimizes collected things.
42
+ *
43
+ * @return bool
44
+ */
45
  abstract public function minify();
46
+
47
+ /**
48
+ * Caches the things.
49
+ *
50
+ * @return void
51
+ */
52
  abstract public function cache();
53
+
54
+ /**
55
+ * Returns the content
56
+ *
57
+ * @return string
58
+ */
59
  abstract public function getcontent();
60
+
61
+ /**
62
+ * Tranfsorms a given URL to a full local filepath if possible.
63
+ * Returns local filepath or false.
64
+ *
65
+ * @param string $url URL to transform.
66
+ *
67
+ * @return bool|string
68
+ */
69
+ public function getpath( $url )
70
+ {
71
+ $url = apply_filters( 'autoptimize_filter_cssjs_alter_url', $url );
72
+
73
+ if ( false !== strpos( $url, '%' ) ) {
74
+ $url = urldecode( $url );
75
  }
76
 
77
+ $site_host = parse_url( AUTOPTIMIZE_WP_SITE_URL, PHP_URL_HOST );
78
+ $content_host = parse_url( AUTOPTIMIZE_WP_ROOT_URL, PHP_URL_HOST );
79
+
80
+ // Normalizing attempts...
81
+ $double_slash_position = strpos( $url, '//' );
82
+ if ( 0 === $double_slash_position ) {
83
+ if ( is_ssl() ) {
84
+ $url = 'https:' . $url;
85
  } else {
86
+ $url = 'http:' . $url;
87
  }
88
+ } elseif ( ( false === $double_slash_position ) && ( false === strpos( $url, $site_host ) ) ) {
89
+ if ( AUTOPTIMIZE_WP_SITE_URL === $site_host ) {
90
+ $url = AUTOPTIMIZE_WP_SITE_URL . $url;
91
  } else {
92
+ $url = AUTOPTIMIZE_WP_SITE_URL . autoptimizeUtils::path_canonicalize( $url );
 
93
  }
94
  }
95
+
96
+ if ( $site_host !== $content_host ) {
97
+ $url = str_replace( AUTOPTIMIZE_WP_CONTENT_URL, AUTOPTIMIZE_WP_SITE_URL . AUTOPTIMIZE_WP_CONTENT_NAME, $url );
98
  }
99
 
100
+ // First check; hostname wp site should be hostname of url!
101
+ $url_host = @parse_url( $url, PHP_URL_HOST ); // @codingStandardsIgnoreLine
102
+ if ( $url_host !== $site_host ) {
103
+ /**
104
+ * First try to get all domains from WPML (if available)
105
+ * then explicitely declare $this->cdn_url as OK as well
106
+ * then apply own filter autoptimize_filter_cssjs_multidomain takes an array of hostnames
107
+ * each item in that array will be considered part of the same WP multisite installation
108
+ */
109
  $multidomains = array();
110
+
111
+ $multidomains_wpml = apply_filters( 'wpml_setting', array(), 'language_domains' );
112
+ if ( ! empty( $multidomains_wpml ) ) {
113
+ $multidomains = array_map( array( $this, 'get_url_hostname' ), $multidomains_wpml );
114
  }
115
+
116
+ if ( ! empty( $this->cdn_url ) ) {
117
+ $multidomains[] = parse_url( $this->cdn_url, PHP_URL_HOST );
118
  }
119
+
120
+ $multidomains = apply_filters( 'autoptimize_filter_cssjs_multidomain', $multidomains );
121
+
122
+ if ( ! empty( $multidomains ) ) {
123
+ if ( in_array( $url_host, $multidomains ) ) {
124
+ $url = str_replace( $url_host, $site_host, $url );
125
  } else {
126
  return false;
127
  }
129
  return false;
130
  }
131
  }
132
+
133
+ // Try to remove "wp root url" from url while not minding http<>https.
134
+ $tmp_ao_root = preg_replace( '/https?:/', '', AUTOPTIMIZE_WP_ROOT_URL );
135
+
136
+ if ( $site_host !== $content_host ) {
137
+ // As we replaced the content-domain with the site-domain, we should match against that.
138
+ $tmp_ao_root = preg_replace( '/https?:/', '', AUTOPTIMIZE_WP_SITE_URL );
139
  }
140
+
141
+ $tmp_url = preg_replace( '/https?:/', '', $url );
142
+ $path = str_replace( $tmp_ao_root, '', $tmp_url );
143
+
144
+ // If path starts with :// or //, this is not a URL in the WP context and
145
+ // we have to assume we can't aggregate.
146
+ if ( preg_match( '#^:?//#', $path ) ) {
147
+ // External script/css (adsense, etc).
148
  return false;
149
  }
150
 
151
+ // Prepend with WP_ROOT_DIR to have full path to file.
152
+ $path = str_replace( '//', '/', WP_ROOT_DIR . $path );
153
+
154
+ // Final check: does file exist and is it readable?
155
+ if ( file_exists( $path ) && is_file( $path ) && is_readable( $path ) ) {
156
  return $path;
157
  } else {
158
  return false;
159
  }
160
  }
161
 
162
+ /**
163
+ * Returns the hostname part of a given $url if we're able to parse it.
164
+ * If not, it returns the original url (prefixed with http:// scheme in case
165
+ * it was missing).
166
+ * Used as callback for WPML multidomains filter.
167
+ *
168
+ * @param string $url URL.
169
+ *
170
+ * @return string
171
+ */
172
+ protected function get_url_hostname( $url )
173
+ {
174
+ // Checking that the url starts with something vaguely resembling a protocol.
175
+ if ( ( 0 !== strpos( $url, 'http' ) ) && ( 0 !== strpos( $url, '//' ) ) ) {
176
+ $url = 'http://' . $url;
177
  }
 
 
 
 
 
 
 
 
 
178
 
179
+ // Grab the hostname.
180
+ $hostname = parse_url( $url, PHP_URL_HOST );
181
 
182
+ // Fallback when parse_url() fails.
183
+ if ( empty( $hostname ) ) {
184
+ $hostname = $url;
 
 
 
 
185
  }
186
+
187
+ return $hostname;
188
  }
189
 
190
+ /**
191
+ * Hides everything between noptimize-comment tags.
192
+ *
193
+ * @param string $markup Markup to process.
194
+ *
195
+ * @return string
196
+ */
197
+ protected function hide_noptimize( $markup )
198
+ {
199
+ return $this->replace_contents_with_marker_if_exists(
200
+ 'NOPTIMIZE',
201
+ '/<!--\s?noptimize\s?-->/',
202
+ '#<!--\s?noptimize\s?-->.*?<!--\s?/\s?noptimize\s?-->#is',
203
+ $markup
204
+ );
205
  }
206
+
207
+ /**
208
+ * Unhide noptimize-tags.
209
+ *
210
+ * @param string $markup Markup to process.
211
+ *
212
+ * @return string
213
+ */
214
+ protected function restore_noptimize( $markup )
215
+ {
216
+ return $this->restore_marked_content( 'NOPTIMIZE', $markup );
 
 
 
 
 
217
  }
218
 
219
+ /**
220
+ * Hides "iehacks" content.
221
+ *
222
+ * @param string $markup Markup to process.
223
+ *
224
+ * @return string
225
+ */
226
+ protected function hide_iehacks( $markup )
227
+ {
228
+ return $this->replace_contents_with_marker_if_exists(
229
+ 'IEHACK', // Marker name...
230
+ '<!--[if', // Invalid regex, will fallback to search using strpos()...
231
+ '#<!--\[if.*?\[endif\]-->#is', // Replacement regex...
232
+ $markup
233
+ );
234
  }
235
 
236
+ /**
237
+ * Restores "hidden" iehacks content.
238
+ *
239
+ * @param string $markup Markup to process.
240
+ *
241
+ * @return string
242
+ */
243
+ protected function restore_iehacks( $markup )
244
+ {
245
+ return $this->restore_marked_content( 'IEHACK', $markup );
 
 
 
 
246
  }
247
 
248
+ /**
249
+ * "Hides" content within HTML comments using a regex-based replacement
250
+ * if HTML comment markers are found.
251
+ * `<!--example-->` becomes `%%COMMENTS%%ZXhhbXBsZQ==%%COMMENTS%%`
252
+ *
253
+ * @param string $markup Markup to process.
254
+ *
255
+ * @return string
256
+ */
257
+ protected function hide_comments( $markup )
258
+ {
259
+ return $this->replace_contents_with_marker_if_exists(
260
+ 'COMMENTS',
261
+ '<!--',
262
+ '#<!--.*?-->#is',
263
+ $markup
264
+ );
265
  }
266
 
267
+ /**
268
+ * Restores original HTML comment markers inside a string whose HTML
269
+ * comments have been "hidden" by using `hide_comments()`.
270
+ *
271
+ * @param string $markup Markup to process.
272
+ *
273
+ * @return string
274
+ */
275
+ protected function restore_comments( $markup )
276
+ {
277
+ return $this->restore_marked_content( 'COMMENTS', $markup );
 
 
 
278
  }
279
 
280
+ /**
281
+ * Replaces the given URL with the CDN-version of it when CDN replacement
282
+ * is supposed to be done.
283
+ *
284
+ * @param string $url URL to process.
285
+ *
286
+ * @return string
287
+ */
288
+ public function url_replace_cdn( $url )
289
+ {
290
+ // For 2.3 back-compat in which cdn-ing appeared to be automatically
291
+ // including WP subfolder/subdirectory into account as part of cdn-ing,
292
+ // even though it might've caused serious troubles in certain edge-cases.
293
+ $cdn_url = autoptimizeUtils::tweak_cdn_url_if_needed( $this->cdn_url );
294
+
295
+ // Allows API/filter to further tweak the cdn url...
296
+ $cdn_url = apply_filters( 'autoptimize_filter_base_cdnurl', $cdn_url );
297
+ if ( ! empty( $cdn_url ) ) {
298
+ $this->debug_log( 'before=' . $url );
299
 
300
+ // Simple str_replace-based approach fails when $url is protocol-or-host-relative.
301
+ $is_protocol_relative = autoptimizeUtils::is_protocol_relative( $url );
302
+ $is_host_relative = ( ! $is_protocol_relative && ( '/' === $url{0} ) );
303
+ $cdn_url = rtrim( $cdn_url, '/' );
304
+
305
+ if ( $is_host_relative ) {
306
+ // Prepending host-relative urls with the cdn url.
307
+ $url = $cdn_url . $url;
308
  } else {
309
+ // Either a protocol-relative or "regular" url, replacing it either way.
310
+ if ( $is_protocol_relative ) {
311
+ // Massage $site_url so that simple str_replace() still "works" by
312
+ // searching for the protocol-relative version of AUTOPTIMIZE_WP_SITE_URL.
313
+ $site_url = str_replace( array( 'http:', 'https:' ), '', AUTOPTIMIZE_WP_SITE_URL );
 
 
 
 
 
 
314
  } else {
315
+ $site_url = AUTOPTIMIZE_WP_SITE_URL;
316
  }
317
+ $this->debug_log( '`' . $site_url . '` -> `' . $cdn_url . '` in `' . $url . '`' );
318
+ $url = str_replace( $site_url, $cdn_url, $url );
319
  }
320
+
321
+ $this->debug_log( 'after=' . $url );
322
  }
323
 
324
+ // Allow API filter to take further care of CDN replacement.
325
  $url = apply_filters( 'autoptimize_filter_base_replace_cdn', $url );
326
+
327
  return $url;
328
  }
329
 
330
+ /**
331
+ * Injects/replaces the given payload markup into `$this->content`
332
+ * at the specified location.
333
+ * If the specified tag cannot be found, the payload is appended into
334
+ * $this->content along with a warning wrapped inside <!--noptimize--> tags.
335
+ *
336
+ * @param string $payload Markup to inject.
337
+ * @param array $where Array specifying the tag name and method of injection.
338
+ * Index 0 is the tag name (i.e., `</body>`).
339
+ * Index 1 specifies ˛'before', 'after' or 'replace'. Defaults to 'before'.
340
+ *
341
+ * @return void
342
+ */
343
+ protected function inject_in_html( $payload, $where )
344
+ {
345
+ $warned = false;
346
+ $position = autoptimizeUtils::strpos( $this->content, $where[0] );
347
+ if ( false !== $position ) {
348
+ // Found the tag, setup content/injection as specified.
349
+ if ( 'after' === $where[1] ) {
350
+ $content = $where[0] . $payload;
351
+ } elseif ( 'replace' === $where[1] ) {
352
+ $content = $payload;
353
  } else {
354
+ $content = $payload . $where[0];
355
  }
356
+ // Place where specified.
357
+ $this->content = autoptimizeUtils::substr_replace(
358
+ $this->content,
359
+ $content,
360
+ $position,
361
+ // Using plain strlen() should be safe here for now, since
362
+ // we're not searching for multibyte chars here still...
363
+ strlen( $where[0] )
364
+ );
365
  } else {
366
+ // Couldn't find what was specified, just append and add a warning.
367
  $this->content .= $payload;
368
+ if ( ! $warned ) {
369
+ $tag_display = str_replace( array( '<', '>' ), '', $where[0] );
370
+ $this->content .= '<!--noptimize--><!-- Autoptimize found a problem with the HTML in your Theme, tag `' . $tag_display . '` missing --><!--/noptimize-->';
371
+ $warned = true;
372
  }
373
  }
374
  }
375
+
376
+ /**
377
+ * Returns true if given `$tag` is found in the list of `$removables`.
378
+ *
379
+ * @param string $tag Tag to search for.
380
+ * @param array $removables List of things considered completely removable.
381
+ *
382
+ * @return bool
383
+ */
384
+ protected function isremovable( $tag, $removables )
385
+ {
386
+ foreach ( $removables as $match ) {
387
+ if ( false !== strpos( $tag, $match ) ) {
388
  return true;
389
  }
390
  }
391
+
392
  return false;
393
  }
394
+
395
+ /**
396
+ * Callback used in `self::inject_minified()`.
397
+ *
398
+ * @param array $matches Regex matches.
399
+ *
400
+ * @return string
401
+ */
402
+ public function inject_minified_callback( $matches )
403
+ {
404
+ static $conf = null;
405
+ if ( null === $conf ) {
406
+ $conf = autoptimizeConfig::instance();
407
+ }
408
+
409
+ /**
410
+ * $matches[1] holds the whole match caught by regex in self::inject_minified(),
411
+ * so we take that and split the string on `|`.
412
+ * First element is the filepath, second is the md5 hash of contents
413
+ * the filepath had when it was being processed.
414
+ * If we don't have those, we'll bail out early.
415
+ */
416
+ $filepath = null;
417
+ $filehash = null;
418
+
419
+ // Grab the parts we need.
420
+ $parts = explode( $matches[1], '|' );
421
+ if ( ! empty( $parts ) ) {
422
+ $filepath = isset( $parts[0] ) ? $parts[0] : null;
423
+ $filehash = isset( $parts[1] ) ? $parts[1] : null;
424
+ }
425
+
426
+ // Bail early if something's not right...
427
+ if ( ! $filepath || ! $filehash ) {
428
+ return "\n";
429
+ }
430
+
431
+ $filecontent = file_get_contents( $filepath );
432
+
433
+ // Some things are differently handled for css/js...
434
+ $is_js_file = ( '.js' === substr( $filepath, -3, 3 ) );
435
+
436
+ $is_css_file = false;
437
+ if ( ! $is_js_file ) {
438
+ $is_css_file = ( '.css' === substr( $filepath, -4, 4 ) );
439
+ }
440
+
441
+ // BOMs being nuked here unconditionally (regardless of where they are)!
442
+ $filecontent = preg_replace( "#\x{EF}\x{BB}\x{BF}#", '', $filecontent );
443
+
444
+ // Remove comments and blank lines.
445
+ if ( $is_js_file ) {
446
+ $filecontent = preg_replace( '#^\s*\/\/.*$#Um', '', $filecontent );
447
+ }
448
+
449
+ // Nuke un-important comments.
450
+ $filecontent = preg_replace( '#^\s*\/\*[^!].*\*\/\s?#Um', '', $filecontent );
451
+
452
+ // Normalize newlines.
453
+ $filecontent = preg_replace( '#(^[\r\n]*|[\r\n]+)[\s\t]*[\r\n]+#', "\n", $filecontent );
454
+
455
+ // JS specifics.
456
+ if ( $is_js_file ) {
457
+ // Append a semicolon at the end of js files if it's missing.
458
+ $last_char = substr( $filecontent, -1, 1 );
459
+ if ( ';' !== $last_char && '}' !== $last_char ) {
460
+ $filecontent .= ';';
461
+ }
462
+ // Check if try/catch should be used.
463
+ $opt_js_try_catch = $conf->get( 'autoptimize_js_trycatch' );
464
+ if ( 'on' === $opt_js_try_catch ) {
465
+ // It should, wrap in try/catch.
466
+ $filecontent = 'try{' . $filecontent . '}catch(e){}';
467
+ }
468
+ } elseif ( $is_css_file ) {
469
+ $filecontent = autoptimizeStyles::fixurls( $filepath, $filecontent );
470
+ } else {
471
+ $filecontent = '';
472
+ }
473
+
474
+ // Return modified (or empty!) code/content.
475
+ return "\n" . $filecontent;
476
+ }
477
+
478
+ /**
479
+ * Inject already minified code in optimized JS/CSS.
480
+ *
481
+ * @param string $in Markup.
482
+ *
483
+ * @return string
484
+ */
485
+ protected function inject_minified( $in )
486
+ {
487
+ $out = $in;
488
+
489
+ if ( false !== strpos( $in, '%%INJECTLATER%%' ) ) {
490
  $out = preg_replace_callback(
491
+ '#\/\*\!%%INJECTLATER' . AUTOPTIMIZE_HASH . '%%(.*?)%%INJECTLATER%%\*\/#is',
492
+ array( $this, 'inject_minified_callback' ),
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
493
  $in
494
  );
 
 
495
  }
496
+
497
  return $out;
498
  }
499
+
500
+ /**
501
+ * Specialized method to create the INJECTLATER marker.
502
+ * These are somewhat "special", in the sense that they're additionally wrapped
503
+ * within an "exclamation mark style" comment, so that they're not stripped
504
+ * out by minifiers.
505
+ * They also currently contain the hash of the file's contents too (unlike other markers).
506
+ *
507
+ * @param string $filepath Filepath.
508
+ * @param string $hash Hash.
509
+ *
510
+ * @return string
511
+ */
512
+ public static function build_injectlater_marker( $filepath, $hash )
513
+ {
514
+ $contents = '/*!' . self::build_marker( 'INJECTLATER', $filepath, $hash ) . '*/';
515
+
516
+ return $contents;
517
+ }
518
+
519
+ /**
520
+ * Creates and returns a `%%`-style named marker which holds
521
+ * the base64 encoded `$data`.
522
+ * If `$hash` is provided, it's appended to the base64 encoded string
523
+ * using `|` as the separator (in order to support building the
524
+ * somewhat special/different INJECTLATER marker).
525
+ *
526
+ * @param string $name Marker name.
527
+ * @param string $data Marker data which will be base64-encoded.
528
+ * @param string|null $hash Optional.
529
+ *
530
+ * @return string
531
+ */
532
+ public static function build_marker( $name, $data, $hash = null )
533
+ {
534
+ // Start the marker, add the data.
535
+ $marker = '%%' . $name . AUTOPTIMIZE_HASH . '%%' . base64_encode( $data );
536
+
537
+ // Add the hash if provided.
538
+ if ( null !== $hash ) {
539
+ $marker .= '|' . $hash;
540
+ }
541
+
542
+ // Close the marker.
543
+ $marker .= '%%' . $name . '%%';
544
+
545
+ return $marker;
546
+ }
547
+
548
+ /**
549
+ * Searches for `$search` in `$content` (using either `preg_match()`
550
+ * or `strpos()`, depending on whether `$search` is a valid regex pattern or not).
551
+ * If something is found, it replaces `$content` using `$re_replace_pattern`,
552
+ * effectively creating our named markers (`%%{$marker}%%`.
553
+ * These are then at some point replaced back to their actual/original/modified
554
+ * contents using `autoptimizeBase::restore_marked_content()`.
555
+ *
556
+ * @param string $marker Marker name (without percent characters).
557
+ * @param string $search A string or full blown regex pattern to search for in $content. Uses `strpos()` or `preg_match()`.
558
+ * @param string $re_replace_pattern Regex pattern to use when replacing contents.
559
+ * @param string $content Content to work on.
560
+ *
561
+ * @return string
562
+ */
563
+ protected function replace_contents_with_marker_if_exists( $marker, $search, $re_replace_pattern, $content )
564
+ {
565
+ $found = false;
566
+
567
+ $is_regex = autoptimizeUtils::str_is_valid_regex( $search );
568
+ if ( $is_regex ) {
569
+ $found = preg_match( $search, $content );
570
+ } else {
571
+ $found = ( false !== strpos( $content, $search ) );
572
+ }
573
+
574
+ if ( $found ) {
575
+ $content = preg_replace_callback(
576
+ $re_replace_pattern,
577
+ function( $matches ) use ( $marker ) {
578
+ return autoptimizeBase::build_marker( $marker, $matches[0] );
579
+ },
580
+ $content
581
+ );
582
+ }
583
+
584
+ return $content;
585
+ }
586
+
587
+ /**
588
+ * Complements `autoptimizeBase::replace_contents_with_marker_if_exists()`.
589
+ *
590
+ * @param string $marker Marker.
591
+ * @param string $content Markup.
592
+ *
593
+ * @return string
594
+ */
595
+ protected function restore_marked_content( $marker, $content )
596
+ {
597
+ if ( false !== strpos( $content, $marker ) ) {
598
+ $content = preg_replace_callback(
599
+ '#%%' . $marker . AUTOPTIMIZE_HASH . '%%(.*?)%%' . $marker . '%%#is',
600
+ function ( $matches ) {
601
+ return base64_decode( $matches[1] );
602
+ },
603
+ $content
604
+ );
605
+ }
606
+
607
+ return $content;
608
+ }
609
+
610
+ /**
611
+ * Logs given `$data` for debugging purposes (when debug logging is on).
612
+ *
613
+ * @param mixed $data Data to log.
614
+ *
615
+ * @return void
616
+ */
617
+ protected function debug_log( $data )
618
+ {
619
+ if ( ! isset( $this->debug_log ) || ! $this->debug_log ) {
620
+ return;
621
+ }
622
+
623
+ if ( ! is_string( $data ) && ! is_resource( $data ) ) {
624
+ $data = var_export( $data, true );
625
+ }
626
+
627
+ error_log( $data );
628
+ }
629
+
630
+ /**
631
+ * Checks if a single local css/js file can be minified and returns source if so.
632
+ *
633
+ * @param string $filepath Filepath.
634
+ *
635
+ * @return bool|string to be minified code or false.
636
+ */
637
+ protected function prepare_minify_single( $filepath )
638
+ {
639
+ // Decide what we're dealing with, return false if we don't know.
640
+ if ( $this->str_ends_in( $filepath, '.js' ) ) {
641
+ $type = 'js';
642
+ } elseif ( $this->str_ends_in( $filepath, '.css' ) ) {
643
+ $type = 'css';
644
+ } else {
645
+ return false;
646
+ }
647
+
648
+ // Bail if it looks like its already minifed (by having -min or .min
649
+ // in filename) or if it looks like WP jquery.js (which is minified).
650
+ $minified_variants = array(
651
+ '-min.' . $type,
652
+ '.min.' . $type,
653
+ 'js/jquery/jquery.js',
654
+ );
655
+ foreach ( $minified_variants as $ending ) {
656
+ if ( $this->str_ends_in( $filepath, $ending ) ) {
657
+ return false;
658
+ }
659
+ }
660
+
661
+ // Get file contents, bail if empty.
662
+ $contents = file_get_contents( $filepath );
663
+
664
+ return $contents;
665
+ }
666
+
667
+ /**
668
+ * Given an autoptimizeCache instance returns the (maybe cdn-ed) url of
669
+ * the cached file.
670
+ *
671
+ * @param autoptimizeCache $cache autoptimizeCache instance.
672
+ *
673
+ * @return string
674
+ */
675
+ protected function build_minify_single_url( autoptimizeCache $cache )
676
+ {
677
+ $url = AUTOPTIMIZE_CACHE_URL . $cache->getname();
678
+
679
+ // CDN-replace the resulting URL if needed...
680
+ $url = $this->url_replace_cdn( $url );
681
+
682
+ return $url;
683
+ }
684
+
685
+ /**
686
+ * Returns true if given $str ends with given $test.
687
+ *
688
+ * @param string $str String to check.
689
+ * @param string $test Ending to match.
690
+ *
691
+ * @return bool
692
+ */
693
+ protected function str_ends_in( $str, $test )
694
+ {
695
+ // @codingStandardsIgnoreStart
696
+ // substr_compare() is bugged on 5.5.11: https://3v4l.org/qGYBH
697
+ // return ( 0 === substr_compare( $str, $test, -strlen( $test ) ) );
698
+ // @codingStandardsIgnoreEnd
699
+
700
+ $length = strlen( $test );
701
+
702
+ return ( substr( $str, -$length, $length ) === $test );
703
+ }
704
  }
classes/autoptimizeCLI.php CHANGED
@@ -1,22 +1,34 @@
1
  <?php
2
- // Exit if accessed directly
 
 
 
3
  if ( ! defined( 'ABSPATH' ) ) {
4
- exit;
5
  }
6
 
7
- class autoptimizeCLI extends WP_CLI_Command {
8
-
9
- /**
10
- * Clears the cache.
11
- *
12
- * @subcommand clear
13
- */
14
- public function clear( $args, $args_assoc ) {
15
- WP_CLI::line( esc_html__( 'Flushing the cache...', 'autoptimize' ) );
16
- autoptimizeCache::clearall();
17
- WP_CLI::success( esc_html__( 'Cache flushed.', 'autoptimize' ) );
18
- }
19
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
20
  }
21
 
22
  WP_CLI::add_command( 'autoptimize', 'autoptimizeCLI' );
1
  <?php
2
+ /**
3
+ * WP-CLI commands for Autoptimize.
4
+ */
5
+
6
  if ( ! defined( 'ABSPATH' ) ) {
7
+ exit;
8
  }
9
 
10
+ // This is a WP-CLI command, so bail if it's not available.
11
+ if ( ! defined( 'WP_CLI' ) ) {
12
+ return;
13
+ }
 
 
 
 
 
 
 
 
14
 
15
+ class autoptimizeCLI extends \WP_CLI_Command
16
+ {
17
+ /**
18
+ * Clears the cache.
19
+ *
20
+ * @subcommand clear
21
+ *
22
+ * @param array $args Args.
23
+ * @param array $args_assoc Associative args.
24
+ *
25
+ * @return void
26
+ */
27
+ public function clear( $args, $args_assoc ) {
28
+ WP_CLI::line( esc_html__( 'Flushing the cache...', 'autoptimize' ) );
29
+ autoptimizeCache::clearall();
30
+ WP_CLI::success( esc_html__( 'Cache flushed.', 'autoptimize' ) );
31
+ }
32
  }
33
 
34
  WP_CLI::add_command( 'autoptimize', 'autoptimizeCLI' );
classes/autoptimizeCSSmin.php ADDED
@@ -0,0 +1,57 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Thin wrapper around css minifiers to avoid rewriting a bunch of existing code.
4
+ */
5
+
6
+ if ( ! defined( 'ABSPATH' ) ) {
7
+ exit;
8
+ }
9
+
10
+ class autoptimizeCSSmin
11
+ {
12
+ /**
13
+ * Minifier instance.
14
+ *
15
+ * @var Autoptimize\tubalmartin\CssMin\Minifier|null
16
+ */
17
+ protected $minifier = null;
18
+
19
+ /**
20
+ * Construtor.
21
+ *
22
+ * @param bool $raise_limits Whether to raise memory limits or not. Default true.
23
+ */
24
+ public function __construct( $raise_limits = true )
25
+ {
26
+ $this->minifier = new Autoptimize\tubalmartin\CssMin\Minifier( $raise_limits );
27
+ }
28
+
29
+ /**
30
+ * Runs the minifier on given string of $css.
31
+ * Returns the minified css.
32
+ *
33
+ * @param string $css CSS to minify.
34
+ *
35
+ * @return string
36
+ */
37
+ public function run( $css )
38
+ {
39
+ $result = $this->minifier->run( $css );
40
+
41
+ return $result;
42
+ }
43
+
44
+ /**
45
+ * Static helper.
46
+ *
47
+ * @param string $css CSS to minify.
48
+ *
49
+ * @return string
50
+ */
51
+ public static function minify( $css )
52
+ {
53
+ $minifier = new self();
54
+
55
+ return $minifier->run( $css );
56
+ }
57
+ }
classes/autoptimizeCache.php CHANGED
@@ -1,192 +1,508 @@
1
  <?php
2
- if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
 
 
3
 
4
- class autoptimizeCache {
 
 
 
 
 
 
 
 
 
 
5
  private $filename;
6
- private $mime;
 
 
 
 
 
7
  private $cachedir;
8
- private $delayed;
9
-
10
- public function __construct($md5,$ext='php') {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
11
  $this->cachedir = AUTOPTIMIZE_CACHE_DIR;
12
- $this->delayed = AUTOPTIMIZE_CACHE_DELAY;
13
- $this->nogzip = AUTOPTIMIZE_CACHE_NOGZIP;
14
- if($this->nogzip == false) {
15
- $this->filename = AUTOPTIMIZE_CACHEFILE_PREFIX.$md5.'.php';
16
  } else {
17
- if (in_array($ext, array("js","css"))) {
18
- $this->filename = $ext.'/'.AUTOPTIMIZE_CACHEFILE_PREFIX.$md5.'.'.$ext;
19
  } else {
20
- $this->filename = AUTOPTIMIZE_CACHEFILE_PREFIX.$md5.'.'.$ext;
21
  }
22
  }
23
  }
24
-
25
- public function check() {
26
- if(!file_exists($this->cachedir.$this->filename)) {
27
- // No cached file, sorry
28
- return false;
29
- }
30
- // Cache exists!
31
- return true;
 
32
  }
33
-
34
- public function retrieve() {
35
- if($this->check()) {
36
- if($this->nogzip == false) {
37
- return file_get_contents($this->cachedir.$this->filename.'.none');
 
 
 
 
 
 
38
  } else {
39
- return file_get_contents($this->cachedir.$this->filename);
40
  }
41
  }
42
  return false;
43
  }
44
-
45
- public function cache($code,$mime) {
46
- if($this->nogzip == false) {
47
- $file = ($this->delayed ? 'delayed.php' : 'default.php');
48
- $phpcode = file_get_contents(AUTOPTIMIZE_PLUGIN_DIR.'/config/'.$file);
49
- $phpcode = str_replace(array('%%CONTENT%%','exit;'),array($mime,''),$phpcode);
50
- file_put_contents($this->cachedir.$this->filename,$phpcode, LOCK_EX);
51
- file_put_contents($this->cachedir.$this->filename.'.none',$code, LOCK_EX);
52
- if(!$this->delayed) {
53
- // Compress now!
54
- file_put_contents($this->cachedir.$this->filename.'.deflate',gzencode($code,9,FORCE_DEFLATE), LOCK_EX);
55
- file_put_contents($this->cachedir.$this->filename.'.gzip',gzencode($code,9,FORCE_GZIP), LOCK_EX);
56
- }
 
 
 
 
 
 
57
  } else {
58
- // Write code to cache without doing anything else
59
- file_put_contents($this->cachedir.$this->filename,$code, LOCK_EX);
60
- if (apply_filters('autoptimize_filter_cache_create_static_gzip', false)) {
61
- // Create an additional cached gzip file
62
- file_put_contents($this->cachedir.$this->filename.'.gz', gzencode($code,9,FORCE_GZIP), LOCK_EX);
63
  }
64
  }
65
  }
66
-
67
- public function getname() {
68
- apply_filters('autoptimize_filter_cache_getname',AUTOPTIMIZE_CACHE_URL.$this->filename);
 
 
 
 
 
 
 
 
 
 
 
 
69
  return $this->filename;
70
  }
71
-
72
- static function clearall() {
73
- if(!autoptimizeCache::cacheavail()) {
74
- return false;
75
- }
76
-
77
- // scan the cachedirs
78
- foreach (array("","js","css") as $scandirName) {
79
- $scan[$scandirName] = scandir(AUTOPTIMIZE_CACHE_DIR.$scandirName);
 
 
 
 
 
 
 
 
80
  }
81
-
82
- // clear the cachedirs
83
- foreach ($scan as $scandirName=>$scanneddir) {
84
- $thisAoCacheDir=rtrim(AUTOPTIMIZE_CACHE_DIR.$scandirName,"/")."/";
85
- foreach($scanneddir as $file) {
86
- if(!in_array($file,array('.','..')) && strpos($file,AUTOPTIMIZE_CACHEFILE_PREFIX) !== false && is_file($thisAoCacheDir.$file)) {
87
- @unlink($thisAoCacheDir.$file);
 
 
 
 
 
 
 
 
 
 
 
88
  }
89
  }
90
  }
91
 
92
- @unlink(AUTOPTIMIZE_CACHE_DIR."/.htaccess");
93
- delete_transient("autoptimize_stats");
94
 
95
- // add cachepurged action
96
- if (!function_exists('autoptimize_do_cachepurged_action')) {
97
- function autoptimize_do_cachepurged_action() {
98
- do_action("autoptimize_action_cachepurged");
 
 
 
 
 
 
 
 
 
 
 
 
 
99
  }
100
  }
101
- add_action("shutdown","autoptimize_do_cachepurged_action",11);
102
-
103
- // try to purge caching plugins cache-files?
104
- include_once(AUTOPTIMIZE_PLUGIN_DIR.'classlesses/autoptimizePageCacheFlush.php');
105
- add_action("autoptimize_action_cachepurged","autoptimize_flush_pagecache",10,0);
106
-
107
- // warm cache (part of speedupper)?
108
- if ( apply_filters('autoptimize_filter_speedupper', true) ) {
109
- $warmCacheUrl = site_url()."/?ao_speedup_cachebuster=".rand(1,100000);
110
- $warmCache = @wp_remote_get($warmCacheUrl);
111
- unset($warmCache);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
112
  }
113
 
114
  return true;
115
  }
116
 
117
- static function stats() {
118
- $AOstats=get_transient("autoptimize_stats");
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
119
 
120
- if (empty($AOstats)) {
121
- // Cache not available :(
122
- if(!autoptimizeCache::cacheavail()) {
123
  return 0;
124
  }
125
-
126
- // Count cached info
127
- $count = 0;
128
- $size = 0;
129
-
130
- // scan the cachedirs
131
- foreach (array("","js","css") as $scandirName) {
132
- $scan[$scandirName] = scandir(AUTOPTIMIZE_CACHE_DIR.$scandirName);
 
133
  }
134
-
135
- foreach ($scan as $scandirName=>$scanneddir) {
136
- $thisAoCacheDir=rtrim(AUTOPTIMIZE_CACHE_DIR.$scandirName,"/")."/";
137
- foreach($scanneddir as $file) {
138
- if(!in_array($file,array('.','..')) && strpos($file,AUTOPTIMIZE_CACHEFILE_PREFIX) !== false) {
139
- if(is_file($thisAoCacheDir.$file)) {
140
- if(AUTOPTIMIZE_CACHE_NOGZIP && (strpos($file,'.js') !== false || strpos($file,'.css') !== false || strpos($file,'.img') !== false || strpos($file,'.txt') !== false )) {
141
- $count++;
142
- } elseif(!AUTOPTIMIZE_CACHE_NOGZIP && strpos($file,'.none') !== false) {
143
- $count++;
144
- }
145
- $size+=filesize($thisAoCacheDir.$file);
146
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
147
  }
 
148
  }
149
  }
150
- $AOstats=array($count,$size,time());
151
- if ($count>100) {
152
- set_transient( "autoptimize_stats", $AOstats, apply_filters( 'autoptimize_filter_cache_statsexpiry', HOUR_IN_SECONDS ) );
153
- }
154
  }
155
- // print the number of instances
156
- return $AOstats;
157
- }
158
 
159
- static function cacheavail() {
160
- if(!defined('AUTOPTIMIZE_CACHE_DIR')) {
161
- // We didn't set a cache
 
 
 
 
 
 
 
 
 
 
 
 
 
162
  return false;
163
  }
164
-
165
- foreach (array("","js","css") as $checkDir) {
166
- if(!autoptimizeCache::checkCacheDir(AUTOPTIMIZE_CACHE_DIR.$checkDir)) {
167
  return false;
168
  }
169
  }
170
-
171
- /** write index.html here to avoid prying eyes */
172
- $indexFile=AUTOPTIMIZE_CACHE_DIR.'/index.html';
173
- if(!is_file($indexFile)) {
174
- @file_put_contents($indexFile,'<html><head><meta name="robots" content="noindex, nofollow"></head><body>Generated by <a href="http://wordpress.org/extend/plugins/autoptimize/" rel="nofollow">Autoptimize</a></body></html>');
175
- }
176
 
177
- /** write .htaccess here to overrule wp_super_cache */
178
- $htAccess=AUTOPTIMIZE_CACHE_DIR.'/.htaccess';
179
- if(!is_file($htAccess)) {
180
- /**
181
- * create wp-content/AO_htaccess_tmpl with
182
  * whatever htaccess rules you might need
183
  * if you want to override default AO htaccess
184
  */
185
- $htaccess_tmpl=WP_CONTENT_DIR."/AO_htaccess_tmpl";
186
- if (is_file($htaccess_tmpl)) {
187
- $htAccessContent=file_get_contents($htaccess_tmpl);
188
- } else if (is_multisite() || AUTOPTIMIZE_CACHE_NOGZIP == false) {
189
- $htAccessContent='<IfModule mod_expires.c>
190
  ExpiresActive On
191
  ExpiresByType text/css A30672000
192
  ExpiresByType text/javascript A30672000
@@ -212,7 +528,7 @@ class autoptimizeCache {
212
  </Files>
213
  </IfModule>';
214
  } else {
215
- $htAccessContent='<IfModule mod_expires.c>
216
  ExpiresActive On
217
  ExpiresByType text/css A30672000
218
  ExpiresByType text/javascript A30672000
@@ -238,33 +554,108 @@ class autoptimizeCache {
238
  </Files>
239
  </IfModule>';
240
  }
241
- @file_put_contents($htAccess,$htAccessContent);
242
  }
243
 
244
- // All OK
245
  return true;
246
  }
247
 
248
- static function checkCacheDir($dir) {
249
- // Check and create if not exists
250
- if(!file_exists($dir)) {
251
- @mkdir($dir,0775,true);
252
- if(!file_exists($dir)) {
 
 
 
 
 
 
 
 
 
253
  return false;
254
  }
255
  }
256
 
257
- // check if we can now write
258
- if(!is_writable($dir)) {
259
  return false;
260
  }
261
 
262
- // and write index.html here to avoid prying eyes
263
- $indexFile=$dir.'/index.html';
264
- if(!is_file($indexFile)) {
265
- @file_put_contents($indexFile,'<html><head><meta name="robots" content="noindex, nofollow"></head><body>Generated by <a href="http://wordpress.org/extend/plugins/autoptimize/" rel="nofollow">Autoptimize</a></body></html>');
266
  }
267
-
268
  return true;
269
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
270
  }
1
  <?php
2
+ /**
3
+ * Handles disk-cache-related operations.
4
+ */
5
 
6
+ if ( ! defined( 'ABSPATH' ) ) {
7
+ exit;
8
+ }
9
+
10
+ class autoptimizeCache
11
+ {
12
+ /**
13
+ * Cache filename.
14
+ *
15
+ * @var string
16
+ */
17
  private $filename;
18
+
19
+ /**
20
+ * Cache directory path (with a trailing slash).
21
+ *
22
+ * @var string
23
+ */
24
  private $cachedir;
25
+
26
+ /**
27
+ * Whether gzipping is done by the web server or us.
28
+ * True => we don't gzip, the web server does it.
29
+ * False => we do it ourselves.
30
+ *
31
+ * @var bool
32
+ */
33
+ private $nogzip;
34
+
35
+ /**
36
+ * Ctor.
37
+ *
38
+ * @param string $md5 Hash.
39
+ * @param string $ext Extension.
40
+ */
41
+ public function __construct( $md5, $ext = 'php' )
42
+ {
43
  $this->cachedir = AUTOPTIMIZE_CACHE_DIR;
44
+ $this->nogzip = AUTOPTIMIZE_CACHE_NOGZIP;
45
+ if ( ! $this->nogzip ) {
46
+ $this->filename = AUTOPTIMIZE_CACHEFILE_PREFIX . $md5 . '.php';
 
47
  } else {
48
+ if ( in_array( $ext, array( 'js', 'css' ) ) ) {
49
+ $this->filename = $ext . '/' . AUTOPTIMIZE_CACHEFILE_PREFIX . $md5 . '.' . $ext;
50
  } else {
51
+ $this->filename = AUTOPTIMIZE_CACHEFILE_PREFIX . $md5 . '.' . $ext;
52
  }
53
  }
54
  }
55
+
56
+ /**
57
+ * Returns true if the cached file exists on disk.
58
+ *
59
+ * @return bool
60
+ */
61
+ public function check()
62
+ {
63
+ return file_exists( $this->cachedir . $this->filename );
64
  }
65
+
66
+ /**
67
+ * Returns cache contents if they exist, false otherwise.
68
+ *
69
+ * @return string|false
70
+ */
71
+ public function retrieve()
72
+ {
73
+ if ( $this->check() ) {
74
+ if ( false == $this->nogzip ) {
75
+ return file_get_contents( $this->cachedir . $this->filename . '.none' );
76
  } else {
77
+ return file_get_contents( $this->cachedir . $this->filename );
78
  }
79
  }
80
  return false;
81
  }
82
+
83
+ /**
84
+ * Stores given $data in cache.
85
+ *
86
+ * @param string $data Data to cache.
87
+ * @param string $mime Mimetype.
88
+ *
89
+ * @return void
90
+ */
91
+ public function cache( $data, $mime )
92
+ {
93
+ if ( false === $this->nogzip ) {
94
+ // We handle gzipping ourselves.
95
+ $file = 'default.php';
96
+ $phpcode = file_get_contents( AUTOPTIMIZE_PLUGIN_DIR . 'config/' . $file );
97
+ $phpcode = str_replace( array( '%%CONTENT%%', 'exit;' ), array( $mime, '' ), $phpcode );
98
+
99
+ file_put_contents( $this->cachedir . $this->filename, $phpcode );
100
+ file_put_contents( $this->cachedir . $this->filename . '.none', $data );
101
  } else {
102
+ // Write code to cache without doing anything else.
103
+ file_put_contents( $this->cachedir . $this->filename, $data );
104
+ if ( apply_filters( 'autoptimize_filter_cache_create_static_gzip', false ) ) {
105
+ // Create an additional cached gzip file.
106
+ file_put_contents( $this->cachedir . $this->filename . '.gz', gzencode( $data, 9, FORCE_GZIP ) );
107
  }
108
  }
109
  }
110
+
111
+ /**
112
+ * Get cache filename.
113
+ *
114
+ * @return string
115
+ */
116
+ public function getname()
117
+ {
118
+ // NOTE: This could've maybe been a do_action() instead, however,
119
+ // that ship has sailed.
120
+ // The original idea here was to provide 3rd party code a hook so that
121
+ // it can "listen" to all the complete autoptimized-urls that the page
122
+ // will emit... Or something to that effect I think?
123
+ apply_filters( 'autoptimize_filter_cache_getname', AUTOPTIMIZE_CACHE_URL . $this->filename );
124
+
125
  return $this->filename;
126
  }
127
+
128
+ /**
129
+ * Returns true if given `$file` is considered a valid Autoptimize cache file,
130
+ * false otherwise.
131
+ *
132
+ * @param string $dir Directory name (with a trailing slash).
133
+ * @param string $file Filename.
134
+ * @return bool
135
+ */
136
+ protected static function is_valid_cache_file( $dir, $file )
137
+ {
138
+ if ( '.' !== $file && '..' !== $file &&
139
+ false !== strpos( $file, AUTOPTIMIZE_CACHEFILE_PREFIX ) &&
140
+ is_file( $dir . $file ) ) {
141
+
142
+ // It's a valid file!
143
+ return true;
144
  }
145
+
146
+ // Everything else is considered invalid!
147
+ return false;
148
+ }
149
+
150
+ /**
151
+ * Clears contents of AUTOPTIMIZE_CACHE_DIR.
152
+ *
153
+ * @return void
154
+ */
155
+ protected static function clear_cache_classic()
156
+ {
157
+ $contents = self::get_cache_contents();
158
+ foreach ( $contents as $name => $files ) {
159
+ $dir = rtrim( AUTOPTIMIZE_CACHE_DIR . $name, '/' ) . '/';
160
+ foreach ( $files as $file ) {
161
+ if ( self::is_valid_cache_file( $dir, $file ) ) {
162
+ @unlink( $dir . $file ); // @codingStandardsIgnoreLine
163
  }
164
  }
165
  }
166
 
167
+ @unlink( AUTOPTIMIZE_CACHE_DIR . '/.htaccess' ); // @codingStandardsIgnoreLine
168
+ }
169
 
170
+ /**
171
+ * Recursively deletes the specified pathname (file/directory) if possible.
172
+ * Returns true on success, false otherwise.
173
+ *
174
+ * @param string $pathname Pathname to remove.
175
+ *
176
+ * @return bool
177
+ */
178
+ protected static function rmdir( $pathname )
179
+ {
180
+ $files = self::get_dir_contents( $pathname );
181
+ foreach ( $files as $file ) {
182
+ $path = $pathname . '/' . $file;
183
+ if ( is_dir( $path ) ) {
184
+ self::rmdir( $path );
185
+ } else {
186
+ unlink( $path );
187
  }
188
  }
189
+
190
+ return rmdir( $pathname );
191
+ }
192
+
193
+ /**
194
+ * Clears contents of AUTOPTIMIZE_CACHE_DIR by renaming the current
195
+ * cache directory into a new one with a unique name and then
196
+ * re-creating the default (empty) cache directory.
197
+ *
198
+ * @return bool Returns true when everything is done successfully, false otherwise.
199
+ */
200
+ protected static function clear_cache_via_rename()
201
+ {
202
+ $ok = false;
203
+ $dir = self::get_pathname_base();
204
+ $new_name = self::get_unique_name();
205
+
206
+ // Makes sure the new pathname is on the same level...
207
+ $new_pathname = dirname( $dir ) . '/' . $new_name;
208
+ $renamed = @rename( $dir, $new_pathname ); // @codingStandardsIgnoreLine
209
+
210
+ // When renamed, re-create the default cache directory back so it's
211
+ // available again...
212
+ if ( $renamed ) {
213
+ $ok = self::cacheavail();
214
+ }
215
+
216
+ return $ok;
217
+ }
218
+
219
+ /**
220
+ * Returns true when advanced cache clearing is enabled.
221
+ *
222
+ * @return bool
223
+ */
224
+ public static function advanced_cache_clear_enabled()
225
+ {
226
+ return apply_filters( 'autoptimize_filter_cache_clear_advanced', false );
227
+ }
228
+
229
+ /**
230
+ * Returns a (hopefully) unique new cache folder name for renaming purposes.
231
+ *
232
+ * @return string
233
+ */
234
+ protected static function get_unique_name()
235
+ {
236
+ $prefix = self::get_advanced_cache_clear_prefix();
237
+ $new_name = uniqid( $prefix, true );
238
+
239
+ return $new_name;
240
+ }
241
+
242
+ /**
243
+ * Get cache prefix name used in advanced cache clearing mode.
244
+ *
245
+ * @return string
246
+ */
247
+ protected static function get_advanced_cache_clear_prefix()
248
+ {
249
+ $pathname = self::get_pathname_base();
250
+ $basename = basename( $pathname );
251
+ $prefix = $basename . '-';
252
+
253
+ return $prefix;
254
+ }
255
+
256
+ /**
257
+ * Returns an array of file and directory names found within
258
+ * the given $pathname without '.' and '..' elements.
259
+ *
260
+ * @param string $pathname Pathname.
261
+ *
262
+ * @return array
263
+ */
264
+ protected static function get_dir_contents( $pathname )
265
+ {
266
+ return array_slice( scandir( $pathname ), 2 );
267
+ }
268
+
269
+ /**
270
+ * Wipes directories which were created as part of the fast cache clearing
271
+ * routine (which renames the current cache directory into a new one with
272
+ * a custom-prefixed unique name).
273
+ *
274
+ * @return bool
275
+ */
276
+ public static function delete_advanced_cache_clear_artifacts()
277
+ {
278
+ $dir = self::get_pathname_base();
279
+ $prefix = self::get_advanced_cache_clear_prefix();
280
+ $parent = dirname( $dir );
281
+ $ok = false;
282
+
283
+ // Returns the list of files without '.' and '..' elements.
284
+ $files = self::get_dir_contents( $parent );
285
+ foreach ( $files as $file ) {
286
+ $path = $parent . '/' . $file;
287
+ $prefixed = ( false !== strpos( $path, $prefix ) );
288
+ // Removing only our own (prefixed) directories...
289
+ if ( is_dir( $path ) && $prefixed ) {
290
+ $ok = self::rmdir( $path );
291
+ }
292
+ }
293
+
294
+ return $ok;
295
+ }
296
+
297
+ /**
298
+ * Returns the cache directory pathname used.
299
+ * Done as a function so we canSlightly different
300
+ * if multisite is used and `autoptimize_separate_blog_caches` filter
301
+ * is used.
302
+ *
303
+ * @return string
304
+ */
305
+ public static function get_pathname()
306
+ {
307
+ $pathname = self::get_pathname_base();
308
+
309
+ if ( is_multisite() && apply_filters( 'autoptimize_separate_blog_caches', true ) ) {
310
+ $blog_id = get_current_blog_id();
311
+ $pathname .= $blog_id . '/';
312
+ }
313
+
314
+ return $pathname;
315
+ }
316
+
317
+ /**
318
+ * Returns the base path of our cache directory.
319
+ *
320
+ * @return string
321
+ */
322
+ protected static function get_pathname_base()
323
+ {
324
+ $pathname = WP_CONTENT_DIR . AUTOPTIMIZE_CACHE_CHILD_DIR;
325
+
326
+ return $pathname;
327
+ }
328
+
329
+ /**
330
+ * Deletes everything from the cache directories.
331
+ *
332
+ * @param bool $propagate Whether to trigger additional actions when cache is purged.
333
+ *
334
+ * @return bool
335
+ */
336
+ public static function clearall( $propagate = true )
337
+ {
338
+ if ( ! self::cacheavail() ) {
339
+ return false;
340
+ }
341
+
342
+ // TODO/FIXME: If cache is big, switch to advanced/new cache clearing automatically?
343
+ if ( self::advanced_cache_clear_enabled() ) {
344
+ self::clear_cache_via_rename();
345
+ } else {
346
+ self::clear_cache_classic();
347
+ }
348
+
349
+ // Remove the transient so it gets regenerated...
350
+ delete_transient( 'autoptimize_stats' );
351
+
352
+ // Cache was just purged, clear page cache and allow others to hook into our purging...
353
+ if ( true === $propagate ) {
354
+ if ( ! function_exists( 'autoptimize_do_cachepurged_action' ) ) {
355
+ function autoptimize_do_cachepurged_action() {
356
+ do_action( 'autoptimize_action_cachepurged' );
357
+ }
358
+ }
359
+ add_action( 'shutdown', 'autoptimize_do_cachepurged_action', 11 );
360
+ add_action( 'autoptimize_action_cachepurged', array( 'autoptimizeCache', 'flushPageCache' ), 10, 0 );
361
+ }
362
+
363
+ // Warm cache (part of speedupper)!
364
+ if ( apply_filters( 'autoptimize_filter_speedupper', true ) ) {
365
+ $url = site_url() . '/?ao_speedup_cachebuster=' . rand( 1, 100000 );
366
+ $cache = @wp_remote_get( $url ); // @codingStandardsIgnoreLine
367
+ unset( $cache );
368
  }
369
 
370
  return true;
371
  }
372
 
373
+ /**
374
+ * Wrapper for clearall but with false param
375
+ * to ensure the event is not propagated to others
376
+ * through our own hooks (to avoid infinite loops).
377
+ *
378
+ * @return bool
379
+ */
380
+ public static function clearall_actionless()
381
+ {
382
+ return self::clearall( false );
383
+ }
384
+
385
+ /**
386
+ * Returns the contents of our cache dirs.
387
+ *
388
+ * @return array
389
+ */
390
+ protected static function get_cache_contents()
391
+ {
392
+ $contents = array();
393
+
394
+ foreach ( array( '', 'js', 'css' ) as $dir ) {
395
+ $contents[ $dir ] = scandir( AUTOPTIMIZE_CACHE_DIR . $dir );
396
+ }
397
+
398
+ return $contents;
399
+ }
400
+
401
+ /**
402
+ * Returns stats about cached contents.
403
+ *
404
+ * @return array
405
+ */
406
+ public static function stats()
407
+ {
408
+ $stats = get_transient( 'autoptimize_stats' );
409
 
410
+ // If no transient, do the actual scan!
411
+ if ( ! is_array( $stats ) ) {
412
+ if ( ! self::cacheavail() ) {
413
  return 0;
414
  }
415
+ $stats = self::stats_scan();
416
+ $count = $stats[0];
417
+ if ( $count > 100 ) {
418
+ // Store results in transient.
419
+ set_transient(
420
+ 'autoptimize_stats',
421
+ $stats,
422
+ apply_filters( 'autoptimize_filter_cache_statsexpiry', HOUR_IN_SECONDS )
423
+ );
424
  }
425
+ }
426
+
427
+ return $stats;
428
+ }
429
+
430
+ /**
431
+ * Performs a scan of cache directory contents and returns an array
432
+ * with 3 values: count, size, timestamp.
433
+ * count = total number of found files
434
+ * size = total filesize (in bytes) of found files
435
+ * timestamp = unix timestamp when the scan was last performed/finished.
436
+ *
437
+ * @return array
438
+ */
439
+ protected static function stats_scan()
440
+ {
441
+ $count = 0;
442
+ $size = 0;
443
+
444
+ // Scan everything in our cache directories.
445
+ foreach ( self::get_cache_contents() as $name => $files ) {
446
+ $dir = rtrim( AUTOPTIMIZE_CACHE_DIR . $name, '/' ) . '/';
447
+ foreach ( $files as $file ) {
448
+ if ( self::is_valid_cache_file( $dir, $file ) ) {
449
+ if ( AUTOPTIMIZE_CACHE_NOGZIP &&
450
+ (
451
+ false !== strpos( $file, '.js' ) ||
452
+ false !== strpos( $file, '.css' ) ||
453
+ false !== strpos( $file, '.img' ) ||
454
+ false !== strpos( $file, '.txt' )
455
+ )
456
+ ) {
457
+ // Web server is gzipping, we count .js|.css|.img|.txt files.
458
+ $count++;
459
+ } elseif ( ! AUTOPTIMIZE_CACHE_NOGZIP && false !== strpos( $file, '.none' ) ) {
460
+ // We are gzipping ourselves via php, counting only .none files.
461
+ $count++;
462
  }
463
+ $size += filesize( $dir . $file );
464
  }
465
  }
 
 
 
 
466
  }
 
 
 
467
 
468
+ $stats = array( $count, $size, time() );
469
+
470
+ return $stats;
471
+ }
472
+
473
+ /**
474
+ * Ensures the cache directory exists, is writeable and contains the
475
+ * required .htaccess files.
476
+ * Returns false in case it fails to ensure any of those things.
477
+ *
478
+ * @return bool
479
+ */
480
+ public static function cacheavail()
481
+ {
482
+ if ( ! defined( 'AUTOPTIMIZE_CACHE_DIR' ) ) {
483
+ // We didn't set a cache.
484
  return false;
485
  }
486
+
487
+ foreach ( array( '', 'js', 'css' ) as $dir ) {
488
+ if ( ! self::check_cache_dir( AUTOPTIMIZE_CACHE_DIR . $dir ) ) {
489
  return false;
490
  }
491
  }
 
 
 
 
 
 
492
 
493
+ // Using .htaccess inside our cache folder to overrule wp-super-cache.
494
+ $htaccess = AUTOPTIMIZE_CACHE_DIR . '/.htaccess';
495
+ if ( ! is_file( $htaccess ) ) {
496
+ /**
497
+ * Create `wp-content/AO_htaccess_tmpl` file with
498
  * whatever htaccess rules you might need
499
  * if you want to override default AO htaccess
500
  */
501
+ $htaccess_tmpl = WP_CONTENT_DIR . '/AO_htaccess_tmpl';
502
+ if ( is_file( $htaccess_tmpl ) ) {
503
+ $content = file_get_contents( $htaccess_tmpl );
504
+ } elseif ( is_multisite() || ! AUTOPTIMIZE_CACHE_NOGZIP ) {
505
+ $content = '<IfModule mod_expires.c>
506
  ExpiresActive On
507
  ExpiresByType text/css A30672000
508
  ExpiresByType text/javascript A30672000
528
  </Files>
529
  </IfModule>';
530
  } else {
531
+ $content = '<IfModule mod_expires.c>
532
  ExpiresActive On
533
  ExpiresByType text/css A30672000
534
  ExpiresByType text/javascript A30672000
554
  </Files>
555
  </IfModule>';
556
  }
557
+ @file_put_contents( $htaccess, $content ); // @codingStandardsIgnoreLine
558
  }
559
 
560
+ // All OK!
561
  return true;
562
  }
563
 
564
+ /**
565
+ * Ensures the specified `$dir` exists and is writeable.
566
+ * Returns false if that's not the case.
567
+ *
568
+ * @param string $dir Directory to check/create.
569
+ *
570
+ * @return bool
571
+ */
572
+ protected static function check_cache_dir( $dir )
573
+ {
574
+ // Try creating the dir if it doesn't exist.
575
+ if ( ! file_exists( $dir ) ) {
576
+ @mkdir( $dir, 0775, true ); // @codingStandardsIgnoreLine
577
+ if ( ! file_exists( $dir ) ) {
578
  return false;
579
  }
580
  }
581
 
582
+ // If we still cannot write, bail.
583
+ if ( ! is_writable( $dir ) ) {
584
  return false;
585
  }
586
 
587
+ // Create an index.html in there to avoid prying eyes!
588
+ $idx_file = rtrim( $dir, '/\\' ) . '/index.html';
589
+ if ( ! is_file( $idx_file ) ) {
590
+ @file_put_contents( $idx_file, '<html><head><meta name="robots" content="noindex, nofollow"></head><body>Generated by <a href="http://wordpress.org/extend/plugins/autoptimize/" rel="nofollow">Autoptimize</a></body></html>' ); // @codingStandardsIgnoreLine
591
  }
592
+
593
  return true;
594
  }
595
+
596
+ /**
597
+ * Flushes as many page cache plugin's caches as possible.
598
+ *
599
+ * @return void
600
+ */
601
+ // @codingStandardsIgnoreStart
602
+ public static function flushPageCache()
603
+ {
604
+ if ( function_exists( 'wp_cache_clear_cache' ) ) {
605
+ if ( is_multisite() ) {
606
+ $blog_id = get_current_blog_id();
607
+ wp_cache_clear_cache( $blog_id );
608
+ } else {
609
+ wp_cache_clear_cache();
610
+ }
611
+ } elseif ( has_action( 'cachify_flush_cache' ) ) {
612
+ do_action( 'cachify_flush_cache' );
613
+ } elseif ( function_exists( 'w3tc_pgcache_flush' ) ) {
614
+ w3tc_pgcache_flush();
615
+ } elseif ( function_exists( 'wp_fast_cache_bulk_delete_all' ) ) {
616
+ wp_fast_cache_bulk_delete_all();
617
+ } elseif ( class_exists( 'WpFastestCache' ) ) {
618
+ $wpfc = new WpFastestCache();
619
+ $wpfc->deleteCache();
620
+ } elseif ( class_exists( 'c_ws_plugin__qcache_purging_routines' ) ) {
621
+ c_ws_plugin__qcache_purging_routines::purge_cache_dir(); // quick cache
622
+ } elseif ( class_exists( 'zencache' ) ) {
623
+ zencache::clear();
624
+ } elseif ( class_exists( 'comet_cache' ) ) {
625
+ comet_cache::clear();
626
+ } elseif ( class_exists( 'WpeCommon' ) ) {
627
+ // WPEngine cache purge/flush methods to call by default
628
+ $wpe_methods = array(
629
+ 'purge_varnish_cache',
630
+ );
631
+
632
+ // More agressive clear/flush/purge behind a filter
633
+ if ( apply_filters( 'autoptimize_flush_wpengine_aggressive', false ) ) {
634
+ $wpe_methods = array_merge( $wpe_methods, array( 'purge_memcached', 'clear_maxcdn_cache' ) );
635
+ }
636
+
637
+ // Filtering the entire list of WpeCommon methods to be called (for advanced usage + easier testing)
638
+ $wpe_methods = apply_filters( 'autoptimize_flush_wpengine_methods', $wpe_methods );
639
+
640
+ foreach ( $wpe_methods as $wpe_method ) {
641
+ if ( method_exists( 'WpeCommon', $wpe_method ) ) {
642
+ WpeCommon::$wpe_method();
643
+ }
644
+ }
645
+ } elseif ( function_exists( 'sg_cachepress_purge_cache' ) ) {
646
+ sg_cachepress_purge_cache();
647
+ } elseif ( file_exists( WP_CONTENT_DIR . '/wp-cache-config.php' ) && function_exists( 'prune_super_cache' ) ) {
648
+ // fallback for WP-Super-Cache
649
+ global $cache_path;
650
+ if ( is_multisite() ) {
651
+ $blog_id = get_current_blog_id();
652
+ prune_super_cache( get_supercache_dir( $blog_id ), true );
653
+ prune_super_cache( $cache_path . 'blogs/', true );
654
+ } else {
655
+ prune_super_cache( $cache_path . 'supercache/', true );
656
+ prune_super_cache( $cache_path, true );
657
+ }
658
+ }
659
+ }
660
+ // @codingStandardsIgnoreEnd
661
  }
classes/autoptimizeCacheChecker.php ADDED
@@ -0,0 +1,112 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * CacheChecker - new in AO 2.0
4
+ *
5
+ * Daily cronned job (filter to change freq. + filter to disable).
6
+ * Checks if cachesize is > 0.5GB (size is filterable), if so, an option is set which controls showing an admin notice.
7
+ */
8
+
9
+ if ( ! defined( 'ABSPATH' ) ) {
10
+ exit;
11
+ }
12
+
13
+ class autoptimizeCacheChecker
14
+ {
15
+ const SCHEDULE_HOOK = 'ao_cachechecker';
16
+
17
+ public function __construct()
18
+ {
19
+ }
20
+
21
+ public function run()
22
+ {
23
+ $this->add_hooks();
24
+ }
25
+
26
+ public function add_hooks()
27
+ {
28
+ if ( is_admin() ) {
29
+ add_action( 'plugins_loaded', array( $this, 'setup' ) );
30
+ }
31
+ add_action( self::SCHEDULE_HOOK, array( $this, 'cronjob' ) );
32
+ add_action( 'admin_notices', array( $this, 'show_admin_notice' ) );
33
+ }
34
+
35
+ public function setup()
36
+ {
37
+ $do_cache_check = (bool) apply_filters( 'autoptimize_filter_cachecheck_do', true );
38
+ $schedule = wp_get_schedule( self::SCHEDULE_HOOK );
39
+ $frequency = apply_filters( 'autoptimize_filter_cachecheck_frequency', 'daily' );
40
+ if ( ! in_array( $frequency, array( 'hourly', 'daily', 'weekly', 'monthly' ) ) ) {
41
+ $frequency = 'daily';
42
+ }
43
+ if ( $do_cache_check && ( ! $schedule || $schedule !== $frequency ) ) {
44
+ wp_schedule_event( time(), $frequency, self::SCHEDULE_HOOK );
45
+ } elseif ( $schedule && ! $do_cache_check ) {
46
+ wp_clear_scheduled_hook( self::SCHEDULE_HOOK );
47
+ }
48
+ }
49
+
50
+ public function cronjob()
51
+ {
52
+ // Check cachesize and act accordingly.
53
+ $max_size = (int) apply_filters( 'autoptimize_filter_cachecheck_maxsize', 536870912 );
54
+ $do_cache_check = (bool) apply_filters( 'autoptimize_filter_cachecheck_do', true );
55
+ $stat_array = autoptimizeCache::stats();
56
+ $cache_size = round( $stat_array[1] );
57
+ if ( ( $cache_size > $max_size ) && ( $do_cache_check ) ) {
58
+ update_option( 'autoptimize_cachesize_notice', true );
59
+ if ( apply_filters( 'autoptimize_filter_cachecheck_sendmail', true ) ) {
60
+ $site_url = esc_url( site_url() );
61
+ $ao_mailto = apply_filters( 'autoptimize_filter_cachecheck_mailto', get_option( 'admin_email', '' ) );
62
+
63
+ $ao_mailsubject = __( 'Autoptimize cache size warning', 'autoptimize' ) . ' (' . $site_url . ')';
64
+ $ao_mailbody = __( 'Autoptimize\'s cache size is getting big, consider purging the cache. Have a look at https://wordpress.org/plugins/autoptimize/faq/ to see how you can keep the cache size under control.', 'autoptimize' ) . ' (site: ' . $site_url . ')';
65
+
66
+ if ( ! empty( $ao_mailto ) ) {
67
+ $ao_mailresult = wp_mail( $ao_mailto, $ao_mailsubject, $ao_mailbody );
68
+ if ( ! $ao_mailresult ) {
69
+ error_log( 'Autoptimize could not send cache size warning mail.' );
70
+ }
71
+ }
72
+ }
73
+ }
74
+
75
+ // Check if 3rd party services (e.g. image proxy) are up.
76
+ autoptimizeUtils::check_service_availability();
77
+
78
+ // Check image optimization stats.
79
+ autoptimizeExtra::get_img_provider_stats();
80
+
81
+ // Nukes advanced cache clearing artifacts if they exists...
82
+ autoptimizeCache::delete_advanced_cache_clear_artifacts();
83
+ }
84
+
85
+ public function show_admin_notice()
86
+ {
87
+ // fixme: make notices dismissable.
88
+ if ( (bool) get_option( 'autoptimize_cachesize_notice', false ) ) {
89
+ echo '<div class="notice notice-warning"><p>';
90
+ _e( '<strong>Autoptimize\'s cache size is getting big</strong>, consider purging the cache. Have a look at <a href="https://wordpress.org/plugins/autoptimize/faq/" target="_blank" rel="noopener noreferrer">the Autoptimize FAQ</a> to see how you can keep the cache size under control.', 'autoptimize' );
91
+ echo '</p></div>';
92
+ update_option( 'autoptimize_cachesize_notice', false );
93
+ }
94
+
95
+ // Notice for image proxy usage.
96
+ $_imgopt_notice = autoptimizeExtra::get_imgopt_status_notice_wrapper();
97
+ if ( is_array( $_imgopt_notice ) && array_key_exists( 'status', $_imgopt_notice ) && in_array( $_imgopt_notice['status'], array( 1, -1 ) ) ) {
98
+ $_dismissible = 'ao-img-opt-notice-';
99
+ $_hide_notice = '7';
100
+
101
+ if ( -1 == $_imgopt_notice['status'] ) {
102
+ $_hide_notice = '1';
103
+ }
104
+
105
+ $_imgopt_notice_dismissible = apply_filters( 'autoptimize_filter_imgopt_notice_dismissable', $_dismissible . $_hide_notice );
106
+
107
+ if ( $_imgopt_notice && PAnD::is_admin_notice_active( $_imgopt_notice_dismissible ) ) {
108
+ echo '<div class="notice notice-warning is-dismissible" data-dismissible="' . $_imgopt_notice_dismissible . '"><p>' . $_imgopt_notice['notice'] . '</p></div>';
109
+ }
110
+ }
111
+ }
112
+ }
classes/autoptimizeConfig.php CHANGED
@@ -1,49 +1,63 @@
1
  <?php
2
- if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
3
 
4
- class autoptimizeConfig {
5
- private $config = null;
 
6
  static private $instance = null;
7
 
8
- //Singleton: private construct
9
- private function __construct() {
10
- if( is_admin() ) {
11
- //Add the admin page and settings
12
- add_action('admin_menu',array($this,'addmenu'));
13
- add_action('admin_init',array($this,'registersettings'));
14
-
15
- //Set meta info
16
- if(function_exists('plugin_row_meta')) {
17
- //2.8+
18
- add_filter('plugin_row_meta',array($this,'setmeta'),10,2);
19
- } elseif(function_exists('post_class')) {
20
- //2.7
21
- $plugin = plugin_basename(AUTOPTIMIZE_PLUGIN_DIR.'autoptimize.php');
22
- add_filter('plugin_action_links_'.$plugin,array($this,'setmeta'));
 
 
 
 
 
23
  }
24
 
25
- //Clean cache?
26
- if(get_option('autoptimize_cache_clean')) {
27
  autoptimizeCache::clearall();
28
- update_option('autoptimize_cache_clean',0);
29
  }
 
 
30
  }
31
 
32
- //Add the Autoptimize Toolbar to the Admin bar
33
- //(we loaded outside the verification of is_admin to also be displayed on the frontend toolbar)
34
  $toolbar = new autoptimizeToolbar();
35
  }
36
-
37
- static public function instance() {
38
- //Only one instance
39
- if (self::$instance == null) {
 
 
 
 
40
  self::$instance = new autoptimizeConfig();
41
  }
42
-
43
  return self::$instance;
44
- }
45
-
46
- public function show() {
 
 
47
  ?>
48
  <style>
49
  /* title and button */
@@ -88,12 +102,13 @@ class autoptimizeConfig {
88
  .itemTitle {
89
  margin-top: 0;
90
  }
91
- input[type=url]:invalid {color: red; border-color:red;} .form-table th{font-weight:100;}
 
92
  #autoptimize_main .cb_label {display: block; padding-left: 25px; text-indent: -25px;}
93
 
94
  /* rss block */
95
  #futtta_feed ul{list-style:outside;}
96
- #futtta_feed {font-size:medium; margin:0px 20px;}
97
 
98
  /* banner + unslider */
99
  .autoptimize_banner {
@@ -138,6 +153,7 @@ input[type=url]:invalid {color: red; border-color:red;} .form-table th{font-weig
138
  .unslider-arrow.next::before {
139
  content: "\f345";
140
  }
 
141
  /* responsive stuff: hide admin-feed on smaller screens */
142
  @media (min-width: 961px) {
143
  #autoptimize_main {float:left;width:69%;}
@@ -158,42 +174,38 @@ input[type=url]:invalid {color: red; border-color:red;} .form-table th{font-weig
158
 
159
  <div class="wrap">
160
 
161
- <?php if (version_compare(PHP_VERSION, '5.3.0') < 0) { ?>
162
- <div class="notice-error notice"><?php echo '<p>' . sprintf( __('<strong>You are using a very old version of PHP</strong> which will not be supported as from the upcoming Autoptimize 2.4 any more, cfr. <a href=%s>this blogpost for more info</a>. please ask your hoster to provide you with an upgrade path to 7.x.','autoptimize'), '"https://blog.futtta.be/2018/02/13/introducing-zytzagoos-major-changes-for-autoptimize-2-4/" target="_blank"' ) . '</p>'; ?></div>
163
  <?php } ?>
164
 
165
- <?php if (defined('AUTOPTIMIZE_LEGACY_MINIFIERS')) { ?>
166
- <div class="notice-error notice"><?php echo '<p>' . sprintf( __('You are forcing Autoptimize to use the "legacy minifiers" by setting the AUTOPTIMIZE_LEGACY_MINIFIERS constant in /wp-config.php. The "legacy minifiers" will not be supported as from the upcoming Autoptimize 2.4 any more, cfr. <a href=%s>this blogpost for more info</a>.','autoptimize'), '"https://blog.futtta.be/2018/02/13/introducing-zytzagoos-major-changes-for-autoptimize-2-4/" target="_blank"') . '</p>'; ?></div>
 
 
167
  <?php } ?>
168
 
169
  <div id="autoptimize_main">
170
- <div id="ao_title_and_button">
171
- <h1 id="ao_title"><?php _e('Autoptimize Settings','autoptimize'); ?>
172
- <span id="ao_adv_button">
173
- <?php
174
- if (get_option('autoptimize_show_adv','0')=='1') {
175
- ?>
176
- <a href="javascript:void(0);" id="ao_show_adv" class="button" style="display:none;"><span><?php _e("Show advanced settings","autoptimize") ?></span></a>
177
- <a href="javascript:void(0);" id="ao_hide_adv" class="button"><span><?php _e("Hide advanced settings","autoptimize") ?></span></a>
178
- <style>tr.ao_adv{display:table-row;} li.ao_adv{display:list-item;}</style>
179
- <?php
180
- $hiddenClass="";
181
- } else {
182
- ?>
183
- <a href="javascript:void(0);" id="ao_show_adv" class="button"><span><?php _e("Show advanced settings","autoptimize") ?></span></a>
184
- <a href="javascript:void(0);" id="ao_hide_adv" class="button" style="display:none;"><span><?php _e("Hide advanced settings","autoptimize") ?></span></a>
185
- <?php
186
- $hiddenClass="hidden ";
187
- }
188
- ?>
189
- </span>
190
- </h1>
191
- </div>
192
 
193
- <?php echo $this->ao_admin_tabs(); ?>
194
 
195
  <form method="post" action="options.php">
196
- <?php settings_fields('autoptimize'); ?>
197
 
198
  <ul>
199
 
@@ -214,34 +226,39 @@ input[type=url]:invalid {color: red; border-color:red;} .form-table th{font-weig
214
 
215
  <li class="itemDetail">
216
  <h2 class="itemTitle"><?php _e('JavaScript Options','autoptimize'); ?></h2>
217
- <table class="form-table">
218
  <tr valign="top">
219
  <th scope="row"><?php _e('Optimize JavaScript Code?','autoptimize'); ?></th>
220
  <td><input type="checkbox" id="autoptimize_js" name="autoptimize_js" <?php echo get_option('autoptimize_js')?'checked="checked" ':''; ?>/></td>
221
  </tr>
222
  <tr valign="top" class="<?php echo $hiddenClass;?>js_sub ao_adv">
 
 
 
 
 
 
 
 
 
 
223
  <th scope="row"><?php _e('Force JavaScript in &lt;head&gt;?','autoptimize'); ?></th>
224
  <td><label class="cb_label"><input type="checkbox" name="autoptimize_js_forcehead" <?php echo get_option('autoptimize_js_forcehead')?'checked="checked" ':''; ?>/>
225
  <?php _e('Load JavaScript early, this can potentially fix some JS-errors, but makes the JS render blocking.','autoptimize'); ?></label></td>
226
  </tr>
227
  <?php if (get_option('autoptimize_js_justhead')) { ?>
228
- <tr valign="top" class="<?php echo $hiddenClass;?>js_sub ao_adv">
229
  <th scope="row"><?php _e('Look for scripts only in &lt;head&gt;?','autoptimize'); echo ' <i>'. __('(deprecated)','autoptimize') . '</i>'; ?></th>
230
  <td><label class="cb_label"><input type="checkbox" name="autoptimize_js_justhead" <?php echo get_option('autoptimize_js_justhead')?'checked="checked" ':''; ?>/>
231
  <?php _e('Mostly useful in combination with previous option when using jQuery-based templates, but might help keeping cache size under control.','autoptimize'); ?></label></td>
232
  </tr>
233
  <?php } ?>
234
- <tr valign="top" class="<?php echo $hiddenClass;?>js_sub ao_adv">
235
- <th scope="row"><?php _e('Also aggregate inline JS?','autoptimize'); ?></th>
236
- <td><label class="cb_label"><input type="checkbox" name="autoptimize_js_include_inline" <?php echo get_option('autoptimize_js_include_inline')?'checked="checked" ':''; ?>/>
237
- <?php _e('Let Autoptimize also extract JS from the HTML. <strong>Warning</strong>: this can make Autoptimize\'s cache size grow quickly, so only enable this if you know what you\'re doing.','autoptimize'); ?></label></td>
238
- </tr>
239
- <tr valign="top" class="<?php echo $hiddenClass;?>js_sub ao_adv">
240
  <th scope="row"><?php _e('Exclude scripts from Autoptimize:','autoptimize'); ?></th>
241
  <td><label><input type="text" style="width:100%;" name="autoptimize_js_exclude" value="<?php echo get_option('autoptimize_js_exclude',"seal.js, js/jquery/jquery.js"); ?>"/><br />
242
- <?php _e('A comma-separated list of scripts you want to exclude from being optimized, for example \'whatever.js, another.js\' (without the quotes) to exclude those scripts from being aggregated and minimized by Autoptimize.','autoptimize'); ?></label></td>
243
  </tr>
244
- <tr valign="top" class="<?php echo $hiddenClass;?>js_sub ao_adv">
245
  <th scope="row"><?php _e('Add try-catch wrapping?','autoptimize'); ?></th>
246
  <td><label class="cb_label"><input type="checkbox" name="autoptimize_js_trycatch" <?php echo get_option('autoptimize_js_trycatch')?'checked="checked" ':''; ?>/>
247
  <?php _e('If your scripts break because of a JS-error, you might want to try this.','autoptimize'); ?></label></td>
@@ -251,43 +268,56 @@ input[type=url]:invalid {color: red; border-color:red;} .form-table th{font-weig
251
 
252
  <li class="itemDetail">
253
  <h2 class="itemTitle"><?php _e('CSS Options','autoptimize'); ?></h2>
254
- <table class="form-table">
255
  <tr valign="top">
256
  <th scope="row"><?php _e('Optimize CSS Code?','autoptimize'); ?></th>
257
  <td><input type="checkbox" id="autoptimize_css" name="autoptimize_css" <?php echo get_option('autoptimize_css')?'checked="checked" ':''; ?>/></td>
258
  </tr>
259
  <tr class="<?php echo $hiddenClass;?>css_sub ao_adv" valign="top">
 
 
 
 
 
 
 
 
 
 
260
  <th scope="row"><?php _e('Generate data: URIs for images?','autoptimize'); ?></th>
261
  <td><label class="cb_label"><input type="checkbox" name="autoptimize_css_datauris" <?php echo get_option('autoptimize_css_datauris')?'checked="checked" ':''; ?>/>
262
  <?php _e('Enable this to include small background-images in the CSS itself instead of as separate downloads.','autoptimize'); ?></label></td>
263
  </tr>
264
  <?php if (get_option('autoptimize_css_justhead')) { ?>
265
- <tr valign="top" class="<?php echo $hiddenClass;?>css_sub ao_adv">
266
  <th scope="row"><?php _e('Look for styles only in &lt;head&gt;?','autoptimize'); echo ' <i>'. __('(deprecated)','autoptimize') . '</i>'; ?></th>
267
  <td><label class="cb_label"><input type="checkbox" name="autoptimize_css_justhead" <?php echo get_option('autoptimize_css_justhead')?'checked="checked" ':''; ?>/>
268
  <?php _e('Don\'t autoptimize CSS outside the head-section. If the cache gets big, you might want to enable this.','autoptimize'); ?></label></td>
269
  </tr>
270
  <?php } ?>
271
  <tr valign="top" class="<?php echo $hiddenClass;?>css_sub ao_adv">
272
- <th scope="row"><?php _e('Also aggregate inline CSS?','autoptimize'); ?></th>
273
- <td><label class="cb_label"><input type="checkbox" name="autoptimize_css_include_inline" <?php echo get_option('autoptimize_css_include_inline','1')?'checked="checked" ':''; ?>/>
274
- <?php _e('Check this option for Autoptimize to also aggregate CSS in the HTML.','autoptimize'); ?></label></td>
275
- </tr>
276
- <tr valign="top" class="<?php echo $hiddenClass;?>css_sub ao_adv">
277
  <th scope="row"><?php _e('Inline and Defer CSS?','autoptimize'); ?></th>
278
  <td><label class="cb_label"><input type="checkbox" name="autoptimize_css_defer" id="autoptimize_css_defer" <?php echo get_option('autoptimize_css_defer')?'checked="checked" ':''; ?>/>
279
- <?php _e('Inline "above the fold CSS" while loading the main autoptimized CSS only after page load. <a href="http://wordpress.org/plugins/autoptimize/faq/" target="_blank">Check the FAQ</a> before activating this option!','autoptimize'); ?></label></td>
 
 
 
 
 
 
 
 
280
  </tr>
281
  <tr valign="top" class="<?php echo $hiddenClass;?>css_sub ao_adv" id="autoptimize_css_defer_inline">
282
  <th scope="row"></th>
283
  <td><label><textarea rows="10" cols="10" style="width:100%;" placeholder="<?php _e('Paste the above the fold CSS here.','autoptimize'); ?>" name="autoptimize_css_defer_inline"><?php echo get_option('autoptimize_css_defer_inline'); ?></textarea></label></td>
284
  </tr>
285
- <tr valign="top" class="<?php echo $hiddenClass;?>ao_adv css_sub">
286
  <th scope="row"><?php _e('Inline all CSS?','autoptimize'); ?></th>
287
  <td><label class="cb_label"><input type="checkbox" id="autoptimize_css_inline" name="autoptimize_css_inline" <?php echo get_option('autoptimize_css_inline')?'checked="checked" ':''; ?>/>
288
  <?php _e('Inlining all CSS can improve performance for sites with a low pageviews/ visitor-rate, but may slow down performance otherwise.','autoptimize'); ?></label></td>
289
  </tr>
290
- <tr valign="top" class="<?php echo $hiddenClass;?>ao_adv css_sub">
291
  <th scope="row"><?php _e('Exclude CSS from Autoptimize:','autoptimize'); ?></th>
292
  <td><label><input type="text" style="width:100%;" name="autoptimize_css_exclude" value="<?php echo get_option('autoptimize_css_exclude','wp-content/cache/, wp-content/uploads/, admin-bar.min.css, dashicons.min.css'); ?>"/><br />
293
  <?php _e('A comma-separated list of CSS you want to exclude from being optimized.','autoptimize'); ?></label></td>
@@ -297,7 +327,7 @@ input[type=url]:invalid {color: red; border-color:red;} .form-table th{font-weig
297
 
298
  <li class="itemDetail">
299
  <h2 class="itemTitle"><?php _e('CDN Options','autoptimize'); ?></h2>
300
- <table class="form-table">
301
  <tr valign="top">
302
  <th scope="row"><?php _e('CDN Base URL','autoptimize'); ?></th>
303
  <td><label><input id="cdn_url" type="text" name="autoptimize_cdn_url" pattern="^(https?:)?\/\/([\da-z\.-]+)\.([\da-z\.]{2,6})([\/\w \.-]*)*(:\d{2,5})?\/?$" style="width:100%" value="<?php echo esc_url(get_option('autoptimize_cdn_url',''),array("http","https")); ?>" /><br />
@@ -308,7 +338,7 @@ input[type=url]:invalid {color: red; border-color:red;} .form-table th{font-weig
308
 
309
  <li class="<?php echo $hiddenClass;?>itemDetail ao_adv">
310
  <h2 class="itemTitle"><?php _e('Cache Info','autoptimize'); ?></h2>
311
- <table class="form-table" >
312
  <tr valign="top" class="<?php echo $hiddenClass;?>ao_adv">
313
  <th scope="row"><?php _e('Cache folder','autoptimize'); ?></th>
314
  <td><?php echo htmlentities(AUTOPTIMIZE_CACHE_DIR); ?></td>
@@ -320,9 +350,15 @@ input[type=url]:invalid {color: red; border-color:red;} .form-table th{font-weig
320
  <tr valign="top" class="<?php echo $hiddenClass;?>ao_adv">
321
  <th scope="row"><?php _e('Cached styles and scripts','autoptimize'); ?></th>
322
  <td><?php
323
- $AOstatArr=autoptimizeCache::stats();
324
- $AOcacheSize=round($AOstatArr[1]/1024);
325
- printf( __( '%1$s files, totalling %2$s Kbytes (calculated at %3$s)', 'autoptimize'), $AOstatArr[0], $AOcacheSize, date("H:i e", $AOstatArr[2]) );
 
 
 
 
 
 
326
  ?></td>
327
  </tr>
328
  </table>
@@ -330,31 +366,27 @@ input[type=url]:invalid {color: red; border-color:red;} .form-table th{font-weig
330
 
331
  <li class="<?php echo $hiddenClass;?>itemDetail ao_adv">
332
  <h2 class="itemTitle"><?php _e('Misc Options','autoptimize'); ?></h2>
333
- <table class="form-table">
334
- <tr valign="top" class="<?php echo $hiddenClass;?>ao_adv">
335
- <th scope="row"><?php _e('Save aggregated script/css as static files?','autoptimize'); ?></th>
336
- <td><label class="cb_label"><input type="checkbox" name="autoptimize_cache_nogzip" <?php echo get_option('autoptimize_cache_nogzip','1')?'checked="checked" ':''; ?>/>
337
- <?php _e('By default files saved are static css/js, uncheck this option if your webserver doesn\'t properly handle the compression and expiry.','autoptimize'); ?></label>
338
- </td>
339
- </tr>
340
- <tr valign="top" class="<?php echo $hiddenClass;?>ao_adv">
341
- <th scope="row"><?php _e('Also optimize for logged in users?','autoptimize'); ?></th>
342
- <td><label class="cb_label"><input type="checkbox" name="autoptimize_optimize_logged" <?php echo get_option('autoptimize_optimize_logged','1')?'checked="checked" ':''; ?>/>
343
- <?php _e('By default Autoptimize is also active for logged on users, uncheck not to optimize when logged in e.g. to use a pagebuilder.','autoptimize'); ?></label>
344
- </td>
345
- </tr>
346
- <?php
347
- if ( function_exists("is_checkout") || function_exists("is_cart") || function_exists("edd_is_checkout") || function_exists("wpsc_is_cart") || function_exists("wpsc_is_checkout") ) {
348
- ?>
349
- <tr valign="top" class="<?php echo $hiddenClass;?>ao_adv">
350
- <th scope="row"><?php _e('Also optimize shop cart/ checkout?','autoptimize'); ?></th>
351
- <td><label class="cb_label"><input type="checkbox" name="autoptimize_optimize_checkout" <?php echo get_option('autoptimize_optimize_checkout','1')?'checked="checked" ':''; ?>/>
352
- <?php _e('By default Autoptimize is also active on your shop\'s cart/ checkout, uncheck not to optimize those.','autoptimize'); ?></label>
353
- </td>
354
- </tr>
355
- <?php
356
- }
357
- ?>
358
  </table>
359
  </li>
360
 
@@ -371,25 +403,25 @@ input[type=url]:invalid {color: red; border-color:red;} .form-table th{font-weig
371
  </div>
372
  <div id="autoptimize_admin_feed" class="hidden">
373
  <div class="autoptimize_banner hidden">
374
- <ul>
375
- <?php
376
- if (apply_filters('autoptimize_settingsscreen_remotehttp',true)) {
377
- $AO_banner=get_transient("autoptimize_banner");
378
- if (empty($AO_banner)) {
379
- $banner_resp = wp_remote_get("http://misc.optimizingmatters.com/autoptimize_news.html");
380
- if (!is_wp_error($banner_resp)) {
381
- if (wp_remote_retrieve_response_code($banner_resp)=="200") {
382
- $AO_banner = wp_kses_post(wp_remote_retrieve_body($banner_resp));
383
- set_transient("autoptimize_banner",$AO_banner,DAY_IN_SECONDS);
384
- }
385
  }
386
  }
387
- echo $AO_banner;
388
  }
389
- ?>
 
 
390
  <li><?php _e("Need help? <a href='https://wordpress.org/plugins/autoptimize/faq/'>Check out the FAQ here</a>.","autoptimize"); ?></li>
391
  <li><?php _e("Happy with Autoptimize?","autoptimize"); ?><br /><a href="<?php echo network_admin_url(); ?>plugin-install.php?tab=search&type=author&s=optimizingmatters"><?php _e("Try my other plugins!","autoptimize"); ?></a></li>
392
- </ul>
393
  </div>
394
  <div style="margin-left:10px;margin-top:-5px;">
395
  <h2>
@@ -424,7 +456,8 @@ input[type=url]:invalid {color: red; border-color:red;} .form-table th{font-weig
424
 
425
  jQuery(document).ready(function() {
426
  check_ini_state();
427
- jQuery('#autoptimize_admin_feed').fadeTo("slow",1).show();
 
428
  jQuery('.autoptimize_banner').unslider({autoplay:true, delay:3500, infinite: false, arrows:{prev:'<a class="unslider-arrow prev"></a>', next:'<a class="unslider-arrow next"></a>'}}).fadeTo("slow",1).show();
429
 
430
  jQuery( "#feed_dropdown" ).change(function() {
@@ -446,7 +479,7 @@ input[type=url]:invalid {color: red; border-color:red;} .form-table th{font-weig
446
  if (jQuery("#autoptimize_js").attr('checked')) {
447
  jQuery(".js_sub:visible").fadeTo("fast",1);
448
  }
449
- check_ini_state()
450
  jQuery( "input#autoptimize_show_adv" ).val("1");
451
  });
452
 
@@ -461,7 +494,7 @@ input[type=url]:invalid {color: red; border-color:red;} .form-table th{font-weig
461
  if (!jQuery("#autoptimize_js").attr('checked')) {
462
  jQuery(".js_sub:visible").fadeTo("fast",.33);
463
  }
464
- check_ini_state()
465
  jQuery( "input#autoptimize_show_adv" ).val("0");
466
  });
467
 
@@ -481,6 +514,14 @@ input[type=url]:invalid {color: red; border-color:red;} .form-table th{font-weig
481
  }
482
  });
483
 
 
 
 
 
 
 
 
 
484
  jQuery( "#autoptimize_css" ).change(function() {
485
  if (this.checked) {
486
  jQuery(".css_sub:visible").fadeTo("fast",1);
@@ -489,6 +530,14 @@ input[type=url]:invalid {color: red; border-color:red;} .form-table th{font-weig
489
  }
490
  });
491
 
 
 
 
 
 
 
 
 
492
  jQuery( "#autoptimize_css_inline" ).change(function() {
493
  if (this.checked) {
494
  jQuery("#autoptimize_css_defer").prop("checked",false);
@@ -511,7 +560,7 @@ input[type=url]:invalid {color: red; border-color:red;} .form-table th{font-weig
511
  show_feed(feedid);
512
  })
513
 
514
- // validate cdn_url
515
  var cdn_url=document.getElementById("cdn_url");
516
  cdn_url_baseCSS=cdn_url.style.cssText;
517
  if ("validity" in cdn_url) {
@@ -533,9 +582,15 @@ input[type=url]:invalid {color: red; border-color:red;} .form-table th{font-weig
533
  if (!jQuery("#autoptimize_css").attr('checked')) {
534
  jQuery(".css_sub:visible").fadeTo('fast',.33);
535
  }
 
 
 
536
  if (!jQuery("#autoptimize_js").attr('checked')) {
537
  jQuery(".js_sub:visible").fadeTo('fast',.33);
538
  }
 
 
 
539
  }
540
 
541
  function show_feed(id) {
@@ -550,122 +605,186 @@ input[type=url]:invalid {color: red; border-color:red;} .form-table th{font-weig
550
  <?php
551
  }
552
 
553
- public function addmenu() {
554
- $hook=add_options_page(__('Autoptimize Options','autoptimize'),'Autoptimize','manage_options','autoptimize',array($this,'show'));
555
- add_action( 'admin_print_scripts-'.$hook,array($this,'autoptimize_admin_scripts'));
556
- add_action( 'admin_print_styles-'.$hook,array($this,'autoptimize_admin_styles'));
 
557
  }
558
 
559
- public function autoptimize_admin_scripts() {
560
- wp_enqueue_script('jqcookie', plugins_url('/external/js/jquery.cookie.min.js', __FILE__), array('jquery'),null,true);
561
- wp_enqueue_script('unslider', plugins_url('/external/js/unslider-min.js', __FILE__), array('jquery'),null,true);
 
562
  }
563
 
564
- public function autoptimize_admin_styles() {
565
- wp_enqueue_style('unslider', plugins_url('/external/js/unslider.css', __FILE__));
566
- wp_enqueue_style('unslider-dots', plugins_url('/external/js/unslider-dots.css', __FILE__));
 
567
  }
568
 
569
  public function registersettings() {
570
- register_setting('autoptimize','autoptimize_html');
571
- register_setting('autoptimize','autoptimize_html_keepcomments');
572
- register_setting('autoptimize','autoptimize_js');
573
- register_setting('autoptimize','autoptimize_js_exclude');
574
- register_setting('autoptimize','autoptimize_js_trycatch');
575
- register_setting('autoptimize','autoptimize_js_justhead');
576
- register_setting('autoptimize','autoptimize_js_forcehead');
577
- register_setting('autoptimize','autoptimize_js_include_inline');
578
- register_setting('autoptimize','autoptimize_css');
579
- register_setting('autoptimize','autoptimize_css_exclude');
580
- register_setting('autoptimize','autoptimize_css_justhead');
581
- register_setting('autoptimize','autoptimize_css_datauris');
582
- register_setting('autoptimize','autoptimize_css_defer');
583
- register_setting('autoptimize','autoptimize_css_defer_inline');
584
- register_setting('autoptimize','autoptimize_css_inline');
585
- register_setting('autoptimize','autoptimize_css_include_inline');
586
- register_setting('autoptimize','autoptimize_cdn_url');
587
- register_setting('autoptimize','autoptimize_cache_clean');
588
- register_setting('autoptimize','autoptimize_cache_nogzip');
589
- register_setting('autoptimize','autoptimize_show_adv');
590
- register_setting('autoptimize','autoptimize_optimize_logged');
591
- register_setting('autoptimize','autoptimize_optimize_checkout');
 
 
592
  }
593
 
594
- public function setmeta($links,$file=null) {
595
- //Inspired on http://wpengineer.com/meta-links-for-wordpress-plugins/
596
- //Do it only once - saves time
 
597
  static $plugin;
598
- if(empty($plugin))
599
- $plugin = plugin_basename(AUTOPTIMIZE_PLUGIN_DIR.'autoptimize.php');
600
-
601
- if($file===null) {
602
- //2.7
603
- $settings_link = sprintf('<a href="options-general.php?page=autoptimize">%s</a>', __('Settings'));
604
- array_unshift($links,$settings_link);
 
605
  } else {
606
- //2.8
607
- //If it's us, add the link
608
- if($file === $plugin) {
609
- $newlink = array(sprintf('<a href="options-general.php?page=autoptimize">%s</a>',__('Settings')));
610
- $links = array_merge($links,$newlink);
611
  }
612
  }
613
 
614
  return $links;
615
  }
616
 
617
- public function get($key) {
618
- if(!is_array($this->config)) {
619
- //Default config
620
- $config = array('autoptimize_html' => 0,
621
- 'autoptimize_html_keepcomments' => 0,
622
- 'autoptimize_js' => 0,
623
- 'autoptimize_js_exclude' => "seal.js, js/jquery/jquery.js",
624
- 'autoptimize_js_trycatch' => 0,
625
- 'autoptimize_js_justhead' => 0,
626
- 'autoptimize_js_include_inline' => 0,
627
- 'autoptimize_js_forcehead' => 0,
628
- 'autoptimize_css' => 0,
629
- 'autoptimize_css_exclude' => "admin-bar.min.css, dashicons.min.css, wp-content/cache/, wp-content/uploads/",
630
- 'autoptimize_css_justhead' => 0,
631
- 'autoptimize_css_include_inline' => 1,
632
- 'autoptimize_css_defer' => 0,
633
- 'autoptimize_css_defer_inline' => "",
634
- 'autoptimize_css_inline' => 0,
635
- 'autoptimize_css_datauris' => 0,
636
- 'autoptimize_cdn_url' => "",
637
- 'autoptimize_cache_nogzip' => 1,
638
- 'autoptimize_show_adv' => 0,
639
- 'autoptimize_optimize_logged' => 1,
640
- 'autoptimize_optimize_checkout' => 1
641
- );
642
-
643
- //Override with user settings
644
- foreach(array_keys($config) as $name) {
645
- $conf = get_option($name);
646
- if($conf!==false) {
647
- //It was set before!
648
- $config[$name] = $conf;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
649
  }
650
  }
651
 
652
- //Save for next question
653
  $this->config = apply_filters( 'autoptimize_filter_get_config', $config );
654
  }
655
 
656
- if(isset($this->config[$key]))
657
- return $this->config[$key];
 
658
 
659
  return false;
660
  }
661
 
662
  private function getFutttaFeeds($url) {
663
- if (apply_filters('autoptimize_settingsscreen_remotehttp',true)) {
664
  $rss = fetch_feed( $url );
665
  $maxitems = 0;
666
 
667
  if ( ! is_wp_error( $rss ) ) {
668
- $maxitems = $rss->get_item_quantity( 7 );
669
  $rss_items = $rss->get_items( 0, $maxitems );
670
  }
671
  ?>
@@ -683,34 +802,60 @@ input[type=url]:invalid {color: red; border-color:red;} .form-table th{font-weig
683
  <?php endforeach; ?>
684
  <?php endif; ?>
685
  </ul>
686
- <?php
687
  }
688
  }
689
 
690
  // based on http://wordpress.stackexchange.com/a/58826
691
- static function ao_admin_tabs(){
692
- $tabs = apply_filters('autoptimize_filter_settingsscreen_tabs',array('autoptimize' => __('Main','autoptimize')));
693
- $tabContent="";
694
- if (count($tabs)>1) {
695
- if(isset($_GET['page'])){
 
 
696
  $currentId = $_GET['page'];
697
  } else {
698
  $currentId = "autoptimize";
699
  }
700
- $tabContent .= "<h2 class=\"nav-tab-wrapper\">";
701
- foreach($tabs as $tabId => $tabName){
702
- if($currentId == $tabId){
703
- $class = " nav-tab-active";
704
  } else{
705
- $class = "";
706
  }
707
- $tabContent .= '<a class="nav-tab'.$class.'" href="?page='.$tabId.'">'.$tabName.'</a>';
708
  }
709
- $tabContent .= "</h2>";
710
  } else {
711
- $tabContent = "<hr/>";
712
  }
713
 
714
  return $tabContent;
715
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
716
  }
1
  <?php
2
+ if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly.
3
 
4
+ class autoptimizeConfig
5
+ {
6
+ private $config = null;
7
  static private $instance = null;
8
 
9
+ private $settings_screen_do_remote_http = true;
10
+
11
+ /**
12
+ * Singleton.
13
+ */
14
+ private function __construct()
15
+ {
16
+ if ( is_admin() ) {
17
+ // Add the admin page and settings.
18
+ add_action( 'admin_menu', array( $this, 'addmenu' ) );
19
+ add_action( 'admin_init', array( $this, 'registersettings' ) );
20
+
21
+ // Set meta info.
22
+ if ( function_exists( 'plugin_row_meta' ) ) {
23
+ // 2.8 and higher.
24
+ add_filter( 'plugin_row_meta', array( $this, 'setmeta' ), 10, 2 );
25
+ } elseif ( function_exists( 'post_class' ) ) {
26
+ // 2.7 and lower.
27
+ $plugin = plugin_basename( AUTOPTIMIZE_PLUGIN_DIR . 'autoptimize.php' );
28
+ add_filter( 'plugin_action_links_' . $plugin, array( $this, 'setmeta' ) );
29
  }
30
 
31
+ // Clean cache?
32
+ if ( get_option( 'autoptimize_cache_clean' ) ) {
33
  autoptimizeCache::clearall();
34
+ update_option( 'autoptimize_cache_clean', 0 );
35
  }
36
+
37
+ $this->settings_screen_do_remote_http = apply_filters( 'autoptimize_settingsscreen_remotehttp', $this->settings_screen_do_remote_http );
38
  }
39
 
40
+ // Adds the Autoptimize Toolbar to the Admin bar.
41
+ // (we load outside the is_admin check so it's also displayed on the frontend toolbar).
42
  $toolbar = new autoptimizeToolbar();
43
  }
44
+
45
+ /**
46
+ * @return autoptimizeConfig
47
+ */
48
+ static public function instance()
49
+ {
50
+ // Only one instance.
51
+ if ( null === self::$instance ) {
52
  self::$instance = new autoptimizeConfig();
53
  }
54
+
55
  return self::$instance;
56
+ }
57
+
58
+ public function show()
59
+ {
60
+ $conf = self::instance();
61
  ?>
62
  <style>
63
  /* title and button */
102
  .itemTitle {
103
  margin-top: 0;
104
  }
105
+
106
+ input[type=url]:invalid {color: red; border-color:red;} .form-table th{font-weight:normal;}
107
  #autoptimize_main .cb_label {display: block; padding-left: 25px; text-indent: -25px;}
108
 
109
  /* rss block */
110
  #futtta_feed ul{list-style:outside;}
111
+ #futtta_feed {font-size:medium; margin:0px 20px;}
112
 
113
  /* banner + unslider */
114
  .autoptimize_banner {
153
  .unslider-arrow.next::before {
154
  content: "\f345";
155
  }
156
+
157
  /* responsive stuff: hide admin-feed on smaller screens */
158
  @media (min-width: 961px) {
159
  #autoptimize_main {float:left;width:69%;}
174
 
175
  <div class="wrap">
176
 
177
+ <?php if ( version_compare( PHP_VERSION, '5.3.0' ) < 0 ) { ?>
178
+ <div class="notice-error notice"><?php echo '<p>' . sprintf( __( '<strong>You are using a very old version of PHP</strong> (5.2.x or older) which has <a href=%s>serious security and performance issues</a>. Support for PHP 5.5 and below will be removed in one of the next AO released, please ask your hoster to provide you with an upgrade path to 7.x.', 'autoptimize' ), '"http://blog.futtta.be/2016/03/15/why-would-you-still-be-on-php-5-2/" target="_blank"' ) . '</p>'; ?></div>
179
  <?php } ?>
180
 
181
+ <?php if ( defined( 'AUTOPTIMIZE_LEGACY_MINIFIERS' ) ) { ?>
182
+ <div class="notice-error notice"><p>
183
+ <?php _e( "You are using the (no longer supported) AUTOPTIMIZE_LEGACY_MINIFIERS constant. Ensure your site is working properly and remove the constant, it doesn't do anything any more.", 'autoptimize' ); ?>
184
+ </p></div>
185
  <?php } ?>
186
 
187
  <div id="autoptimize_main">
188
+ <div id="ao_title_and_button">
189
+ <h1 id="ao_title"><?php _e( 'Autoptimize Settings', 'autoptimize' ); ?>
190
+ <span id="ao_adv_button">
191
+ <?php if ( get_option( 'autoptimize_show_adv', '0' ) == '1' ) { ?>
192
+ <a href="javascript:void(0);" id="ao_show_adv" class="button" style="display:none;"><span><?php _e("Show advanced settings","autoptimize") ?></span></a>
193
+ <a href="javascript:void(0);" id="ao_hide_adv" class="button"><span><?php _e("Hide advanced settings","autoptimize") ?></span></a>
194
+ <style>tr.ao_adv{display:table-row;} li.ao_adv{display:list-item;}</style>
195
+ <?php $hiddenClass = ''; ?>
196
+ <?php } else { ?>
197
+ <a href="javascript:void(0);" id="ao_show_adv" class="button"><span><?php _e("Show advanced settings","autoptimize") ?></span></a>
198
+ <a href="javascript:void(0);" id="ao_hide_adv" class="button" style="display:none;"><span><?php _e("Hide advanced settings","autoptimize") ?></span></a>
199
+ <?php $hiddenClass = 'hidden '; ?>
200
+ <?php } ?>
201
+ </span>
202
+ </h1>
203
+ </div>
 
 
 
 
 
 
204
 
205
+ <?php echo $this->ao_admin_tabs(); ?>
206
 
207
  <form method="post" action="options.php">
208
+ <?php settings_fields( 'autoptimize' ); ?>
209
 
210
  <ul>
211
 
226
 
227
  <li class="itemDetail">
228
  <h2 class="itemTitle"><?php _e('JavaScript Options','autoptimize'); ?></h2>
229
+ <table class="form-table">
230
  <tr valign="top">
231
  <th scope="row"><?php _e('Optimize JavaScript Code?','autoptimize'); ?></th>
232
  <td><input type="checkbox" id="autoptimize_js" name="autoptimize_js" <?php echo get_option('autoptimize_js')?'checked="checked" ':''; ?>/></td>
233
  </tr>
234
  <tr valign="top" class="<?php echo $hiddenClass;?>js_sub ao_adv">
235
+ <th scope="row"><?php _e( 'Aggregate JS-files?', 'autoptimize' ); ?></th>
236
+ <td><label class="cb_label"><input type="checkbox" id="autoptimize_js_aggregate" name="autoptimize_js_aggregate" <?php echo $conf->get( 'autoptimize_js_aggregate' ) ? 'checked="checked" ':''; ?>/>
237
+ <?php _e( 'Aggregate all linked JS-files to have them loaded non-render blocking? If this option is off, the individual JS-files will remain in place but will be minified.', 'autoptimize' ); ?></label></td>
238
+ </tr>
239
+ <tr valign="top" class="<?php echo $hiddenClass;?>js_sub ao_adv js_aggregate">
240
+ <th scope="row"><?php _e('Also aggregate inline JS?','autoptimize'); ?></th>
241
+ <td><label class="cb_label"><input type="checkbox" name="autoptimize_js_include_inline" <?php echo get_option('autoptimize_js_include_inline')?'checked="checked" ':''; ?>/>
242
+ <?php _e('Let Autoptimize also extract JS from the HTML. <strong>Warning</strong>: this can make Autoptimize\'s cache size grow quickly, so only enable this if you know what you\'re doing.','autoptimize'); ?></label></td>
243
+ </tr>
244
+ <tr valign="top" class="<?php echo $hiddenClass;?>js_sub ao_adv js_aggregate">
245
  <th scope="row"><?php _e('Force JavaScript in &lt;head&gt;?','autoptimize'); ?></th>
246
  <td><label class="cb_label"><input type="checkbox" name="autoptimize_js_forcehead" <?php echo get_option('autoptimize_js_forcehead')?'checked="checked" ':''; ?>/>
247
  <?php _e('Load JavaScript early, this can potentially fix some JS-errors, but makes the JS render blocking.','autoptimize'); ?></label></td>
248
  </tr>
249
  <?php if (get_option('autoptimize_js_justhead')) { ?>
250
+ <tr valign="top" class="<?php echo $hiddenClass;?>js_sub ao_adv js_aggregate">
251
  <th scope="row"><?php _e('Look for scripts only in &lt;head&gt;?','autoptimize'); echo ' <i>'. __('(deprecated)','autoptimize') . '</i>'; ?></th>
252
  <td><label class="cb_label"><input type="checkbox" name="autoptimize_js_justhead" <?php echo get_option('autoptimize_js_justhead')?'checked="checked" ':''; ?>/>
253
  <?php _e('Mostly useful in combination with previous option when using jQuery-based templates, but might help keeping cache size under control.','autoptimize'); ?></label></td>
254
  </tr>
255
  <?php } ?>
256
+ <tr valign="top" class="<?php echo $hiddenClass;?>js_sub ao_adv js_aggregate">
 
 
 
 
 
257
  <th scope="row"><?php _e('Exclude scripts from Autoptimize:','autoptimize'); ?></th>
258
  <td><label><input type="text" style="width:100%;" name="autoptimize_js_exclude" value="<?php echo get_option('autoptimize_js_exclude',"seal.js, js/jquery/jquery.js"); ?>"/><br />
259
+ <?php _e('A comma-separated list of scripts you want to exclude from being optimized, for example \'whatever.js, another.js\' (without the quotes) to exclude those scripts from being aggregated by Autoptimize.','autoptimize'); ?></label></td>
260
  </tr>
261
+ <tr valign="top" class="<?php echo $hiddenClass;?>js_sub ao_adv js_aggregate">
262
  <th scope="row"><?php _e('Add try-catch wrapping?','autoptimize'); ?></th>
263
  <td><label class="cb_label"><input type="checkbox" name="autoptimize_js_trycatch" <?php echo get_option('autoptimize_js_trycatch')?'checked="checked" ':''; ?>/>
264
  <?php _e('If your scripts break because of a JS-error, you might want to try this.','autoptimize'); ?></label></td>
268
 
269
  <li class="itemDetail">
270
  <h2 class="itemTitle"><?php _e('CSS Options','autoptimize'); ?></h2>
271
+ <table class="form-table">
272
  <tr valign="top">
273
  <th scope="row"><?php _e('Optimize CSS Code?','autoptimize'); ?></th>
274
  <td><input type="checkbox" id="autoptimize_css" name="autoptimize_css" <?php echo get_option('autoptimize_css')?'checked="checked" ':''; ?>/></td>
275
  </tr>
276
  <tr class="<?php echo $hiddenClass;?>css_sub ao_adv" valign="top">
277
+ <th scope="row"><?php _e( 'Aggregate CSS-files?', 'autoptimize' ); ?></th>
278
+ <td><label class="cb_label"><input type="checkbox" id="autoptimize_css_aggregate" name="autoptimize_css_aggregate" <?php echo $conf->get( 'autoptimize_css_aggregate' ) ? 'checked="checked" ' : ''; ?>/>
279
+ <?php _e('Aggregate all linked CSS-files? If this option is off, the individual CSS-files will remain in place but will be minified.', 'autoptimize' ); ?></label></td>
280
+ </tr>
281
+ <tr valign="top" class="<?php echo $hiddenClass;?>css_sub ao_adv css_aggregate">
282
+ <th scope="row"><?php _e('Also aggregate inline CSS?','autoptimize'); ?></th>
283
+ <td><label class="cb_label"><input type="checkbox" name="autoptimize_css_include_inline" <?php echo get_option('autoptimize_css_include_inline','1')?'checked="checked" ':''; ?>/>
284
+ <?php _e('Check this option for Autoptimize to also aggregate CSS in the HTML.','autoptimize'); ?></label></td>
285
+ </tr>
286
+ <tr class="<?php echo $hiddenClass;?>css_sub ao_adv css_aggregate" valign="top">
287
  <th scope="row"><?php _e('Generate data: URIs for images?','autoptimize'); ?></th>
288
  <td><label class="cb_label"><input type="checkbox" name="autoptimize_css_datauris" <?php echo get_option('autoptimize_css_datauris')?'checked="checked" ':''; ?>/>
289
  <?php _e('Enable this to include small background-images in the CSS itself instead of as separate downloads.','autoptimize'); ?></label></td>
290
  </tr>
291
  <?php if (get_option('autoptimize_css_justhead')) { ?>
292
+ <tr valign="top" class="<?php echo $hiddenClass;?>css_sub ao_adv css_aggregate">
293
  <th scope="row"><?php _e('Look for styles only in &lt;head&gt;?','autoptimize'); echo ' <i>'. __('(deprecated)','autoptimize') . '</i>'; ?></th>
294
  <td><label class="cb_label"><input type="checkbox" name="autoptimize_css_justhead" <?php echo get_option('autoptimize_css_justhead')?'checked="checked" ':''; ?>/>
295
  <?php _e('Don\'t autoptimize CSS outside the head-section. If the cache gets big, you might want to enable this.','autoptimize'); ?></label></td>
296
  </tr>
297
  <?php } ?>
298
  <tr valign="top" class="<?php echo $hiddenClass;?>css_sub ao_adv">
 
 
 
 
 
299
  <th scope="row"><?php _e('Inline and Defer CSS?','autoptimize'); ?></th>
300
  <td><label class="cb_label"><input type="checkbox" name="autoptimize_css_defer" id="autoptimize_css_defer" <?php echo get_option('autoptimize_css_defer')?'checked="checked" ':''; ?>/>
301
+ <?php
302
+ _e( 'Inline "above the fold CSS" while loading the main autoptimized CSS only after page load. <a href="http://wordpress.org/plugins/autoptimize/faq/" target="_blank">Check the FAQ</a> for more info.', 'autoptimize' );
303
+ if ( function_exists( 'is_plugin_active' ) && ! is_plugin_active( 'autoptimize-criticalcss/ao_criticss_aas.php' ) ) {
304
+ echo ' ';
305
+ $critcss_install_url = network_admin_url() . 'plugin-install.php?s=autoptimize+criticalcss&tab=search&type=term';
306
+ echo sprintf( __( 'This can be fully automated for different types of pages with the %s.', 'autoptimize' ), '<a href="'.$critcss_install_url.'">Autoptimize CriticalCSS Power-Up</a>' );
307
+ }
308
+ ?>
309
+ </label></td>
310
  </tr>
311
  <tr valign="top" class="<?php echo $hiddenClass;?>css_sub ao_adv" id="autoptimize_css_defer_inline">
312
  <th scope="row"></th>
313
  <td><label><textarea rows="10" cols="10" style="width:100%;" placeholder="<?php _e('Paste the above the fold CSS here.','autoptimize'); ?>" name="autoptimize_css_defer_inline"><?php echo get_option('autoptimize_css_defer_inline'); ?></textarea></label></td>
314
  </tr>
315
+ <tr valign="top" class="<?php echo $hiddenClass;?>ao_adv css_sub css_aggregate">
316
  <th scope="row"><?php _e('Inline all CSS?','autoptimize'); ?></th>
317
  <td><label class="cb_label"><input type="checkbox" id="autoptimize_css_inline" name="autoptimize_css_inline" <?php echo get_option('autoptimize_css_inline')?'checked="checked" ':''; ?>/>
318
  <?php _e('Inlining all CSS can improve performance for sites with a low pageviews/ visitor-rate, but may slow down performance otherwise.','autoptimize'); ?></label></td>
319
  </tr>
320
+ <tr valign="top" class="<?php echo $hiddenClass;?>ao_adv css_sub css_aggregate">
321
  <th scope="row"><?php _e('Exclude CSS from Autoptimize:','autoptimize'); ?></th>
322
  <td><label><input type="text" style="width:100%;" name="autoptimize_css_exclude" value="<?php echo get_option('autoptimize_css_exclude','wp-content/cache/, wp-content/uploads/, admin-bar.min.css, dashicons.min.css'); ?>"/><br />
323
  <?php _e('A comma-separated list of CSS you want to exclude from being optimized.','autoptimize'); ?></label></td>
327
 
328
  <li class="itemDetail">
329
  <h2 class="itemTitle"><?php _e('CDN Options','autoptimize'); ?></h2>
330
+ <table class="form-table">
331
  <tr valign="top">
332
  <th scope="row"><?php _e('CDN Base URL','autoptimize'); ?></th>
333
  <td><label><input id="cdn_url" type="text" name="autoptimize_cdn_url" pattern="^(https?:)?\/\/([\da-z\.-]+)\.([\da-z\.]{2,6})([\/\w \.-]*)*(:\d{2,5})?\/?$" style="width:100%" value="<?php echo esc_url(get_option('autoptimize_cdn_url',''),array("http","https")); ?>" /><br />
338
 
339
  <li class="<?php echo $hiddenClass;?>itemDetail ao_adv">
340
  <h2 class="itemTitle"><?php _e('Cache Info','autoptimize'); ?></h2>
341
+ <table class="form-table" >
342
  <tr valign="top" class="<?php echo $hiddenClass;?>ao_adv">
343
  <th scope="row"><?php _e('Cache folder','autoptimize'); ?></th>
344
  <td><?php echo htmlentities(AUTOPTIMIZE_CACHE_DIR); ?></td>
350
  <tr valign="top" class="<?php echo $hiddenClass;?>ao_adv">
351
  <th scope="row"><?php _e('Cached styles and scripts','autoptimize'); ?></th>
352
  <td><?php
353
+ $AOstatArr = autoptimizeCache::stats();
354
+ if ( ! empty( $AOstatArr ) && is_array( $AOstatArr ) ) {
355
+ $AOcacheSize = size_format( $AOstatArr[1], 2 );
356
+ $details = '';
357
+ if ( $AOcacheSize > 0 ) {
358
+ $details = ', ~' . $AOcacheSize . ' total';
359
+ }
360
+ printf( __( '%1$s files, totalling %2$s Kbytes (calculated at %3$s)', 'autoptimize' ), $AOstatArr[0], $AOcacheSize, date( 'H:i e', $AOstatArr[2] ) );
361
+ }
362
  ?></td>
363
  </tr>
364
  </table>
366
 
367
  <li class="<?php echo $hiddenClass;?>itemDetail ao_adv">
368
  <h2 class="itemTitle"><?php _e('Misc Options','autoptimize'); ?></h2>
369
+ <table class="form-table">
370
+ <tr valign="top" class="<?php echo $hiddenClass;?>ao_adv">
371
+ <th scope="row"><?php _e('Save aggregated script/css as static files?','autoptimize'); ?></th>
372
+ <td><label class="cb_label"><input type="checkbox" name="autoptimize_cache_nogzip" <?php echo get_option('autoptimize_cache_nogzip','1')?'checked="checked" ':''; ?>/>
373
+ <?php _e('By default files saved are static css/js, uncheck this option if your webserver doesn\'t properly handle the compression and expiry.','autoptimize'); ?></label></td>
374
+ </tr>
375
+ <tr valign="top" class="<?php echo $hiddenClass;?>ao_adv">
376
+ <th scope="row"><?php _e('Also optimize for logged in users?','autoptimize'); ?></th>
377
+ <td><label class="cb_label"><input type="checkbox" name="autoptimize_optimize_logged" <?php echo get_option('autoptimize_optimize_logged','1')?'checked="checked" ':''; ?>/>
378
+ <?php _e('By default Autoptimize is also active for logged on users, uncheck not to optimize when logged in e.g. to use a pagebuilder.','autoptimize'); ?></label></td>
379
+ </tr>
380
+ <?php
381
+ if ( function_exists("is_checkout") || function_exists("is_cart") || function_exists("edd_is_checkout") || function_exists("wpsc_is_cart") || function_exists("wpsc_is_checkout") ) {
382
+ ?>
383
+ <tr valign="top" class="<?php echo $hiddenClass;?>ao_adv">
384
+ <th scope="row"><?php _e('Also optimize shop cart/ checkout?','autoptimize'); ?></th>
385
+ <td><label class="cb_label"><input type="checkbox" name="autoptimize_optimize_checkout" <?php echo get_option('autoptimize_optimize_checkout','1')?'checked="checked" ':''; ?>/>
386
+ <?php _e('By default Autoptimize is also active on your shop\'s cart/ checkout, uncheck not to optimize those.','autoptimize'); ?></label>
387
+ </td>
388
+ </tr>
389
+ <?php } ?>
 
 
 
 
390
  </table>
391
  </li>
392
 
403
  </div>
404
  <div id="autoptimize_admin_feed" class="hidden">
405
  <div class="autoptimize_banner hidden">
406
+ <ul>
407
+ <?php
408
+ if ( $this->settings_screen_do_remote_http ) {
409
+ $AO_banner = get_transient( 'autoptimize_banner' );
410
+ if ( empty( $AO_banner ) ) {
411
+ $banner_resp = wp_remote_get( 'https://misc.optimizingmatters.com/autoptimize_news.html?ao_ver='.AUTOPTIMIZE_PLUGIN_VERSION );
412
+ if ( ! is_wp_error( $banner_resp ) ) {
413
+ if ( '200' == wp_remote_retrieve_response_code( $banner_resp ) ) {
414
+ $AO_banner = wp_kses_post( wp_remote_retrieve_body( $banner_resp ) );
415
+ set_transient('autoptimize_banner', $AO_banner, DAY_IN_SECONDS);
 
416
  }
417
  }
 
418
  }
419
+ echo $AO_banner;
420
+ }
421
+ ?>
422
  <li><?php _e("Need help? <a href='https://wordpress.org/plugins/autoptimize/faq/'>Check out the FAQ here</a>.","autoptimize"); ?></li>
423
  <li><?php _e("Happy with Autoptimize?","autoptimize"); ?><br /><a href="<?php echo network_admin_url(); ?>plugin-install.php?tab=search&type=author&s=optimizingmatters"><?php _e("Try my other plugins!","autoptimize"); ?></a></li>
424
+ </ul>
425
  </div>
426
  <div style="margin-left:10px;margin-top:-5px;">
427
  <h2>
456
 
457
  jQuery(document).ready(function() {
458
  check_ini_state();
459
+
460
+ jQuery('#autoptimize_admin_feed').fadeTo("slow",1).show();
461
  jQuery('.autoptimize_banner').unslider({autoplay:true, delay:3500, infinite: false, arrows:{prev:'<a class="unslider-arrow prev"></a>', next:'<a class="unslider-arrow next"></a>'}}).fadeTo("slow",1).show();
462
 
463
  jQuery( "#feed_dropdown" ).change(function() {
479
  if (jQuery("#autoptimize_js").attr('checked')) {
480
  jQuery(".js_sub:visible").fadeTo("fast",1);
481
  }
482
+ check_ini_state();
483
  jQuery( "input#autoptimize_show_adv" ).val("1");
484
  });
485
 
494
  if (!jQuery("#autoptimize_js").attr('checked')) {
495
  jQuery(".js_sub:visible").fadeTo("fast",.33);
496
  }
497
+ check_ini_state();
498
  jQuery( "input#autoptimize_show_adv" ).val("0");
499
  });
500
 
514
  }
515
  });
516
 
517
+ jQuery( "#autoptimize_js_aggregate" ).change(function() {
518
+ if (this.checked && jQuery("#autoptimize_js").attr('checked')) {
519
+ jQuery(".js_aggregate:visible").fadeTo("fast",1);
520
+ } else {
521
+ jQuery(".js_aggregate:visible").fadeTo("fast",.33);
522
+ }
523
+ });
524
+
525
  jQuery( "#autoptimize_css" ).change(function() {
526
  if (this.checked) {
527
  jQuery(".css_sub:visible").fadeTo("fast",1);
530
  }
531
  });
532
 
533
+ jQuery( "#autoptimize_css_aggregate" ).change(function() {
534
+ if (this.checked && jQuery("#autoptimize_css").attr('checked')) {
535
+ jQuery(".css_aggregate:visible").fadeTo("fast",1);
536
+ } else {
537
+ jQuery(".css_aggregate:visible").fadeTo("fast",.33);
538
+ }
539
+ });
540
+
541
  jQuery( "#autoptimize_css_inline" ).change(function() {
542
  if (this.checked) {
543
  jQuery("#autoptimize_css_defer").prop("checked",false);
560
  show_feed(feedid);
561
  })
562
 
563
+ // validate cdn_url.
564
  var cdn_url=document.getElementById("cdn_url");
565
  cdn_url_baseCSS=cdn_url.style.cssText;
566
  if ("validity" in cdn_url) {
582
  if (!jQuery("#autoptimize_css").attr('checked')) {
583
  jQuery(".css_sub:visible").fadeTo('fast',.33);
584
  }
585
+ if (!jQuery("#autoptimize_css_aggregate").attr('checked')) {
586
+ jQuery(".css_aggregate:visible").fadeTo('fast',.33);
587
+ }
588
  if (!jQuery("#autoptimize_js").attr('checked')) {
589
  jQuery(".js_sub:visible").fadeTo('fast',.33);
590
  }
591
+ if (!jQuery("#autoptimize_js_aggregate").attr('checked')) {
592
+ jQuery(".js_aggregate:visible").fadeTo('fast',.33);
593
+ }
594
  }
595
 
596
  function show_feed(id) {
605
  <?php
606
  }
607
 
608
+ public function addmenu()
609
+ {
610
+ $hook = add_options_page( __( 'Autoptimize Options', 'autoptimize' ), 'Autoptimize', 'manage_options', 'autoptimize', array( $this, 'show' ) );
611
+ add_action( 'admin_print_scripts-' . $hook, array( $this, 'autoptimize_admin_scripts' ) );
612
+ add_action( 'admin_print_styles-' . $hook, array( $this, 'autoptimize_admin_styles' ) );
613
  }
614
 
615
+ public function autoptimize_admin_scripts()
616
+ {
617
+ wp_enqueue_script( 'jqcookie', plugins_url( '/external/js/jquery.cookie.min.js', __FILE__ ), array( 'jquery' ), null, true );
618
+ wp_enqueue_script( 'unslider', plugins_url( '/external/js/unslider-min.js', __FILE__ ), array( 'jquery' ), null, true );
619
  }
620
 
621
+ public function autoptimize_admin_styles()
622
+ {
623
+ wp_enqueue_style( 'unslider', plugins_url( '/external/js/unslider.css', __FILE__ ) );
624
+ wp_enqueue_style( 'unslider-dots', plugins_url( '/external/js/unslider-dots.css', __FILE__ ) );
625
  }
626
 
627
  public function registersettings() {
628
+ register_setting( 'autoptimize', 'autoptimize_html' );
629
+ register_setting( 'autoptimize', 'autoptimize_html_keepcomments' );
630
+ register_setting( 'autoptimize', 'autoptimize_js' );
631
+ register_setting( 'autoptimize', 'autoptimize_js_aggregate' );
632
+ register_setting( 'autoptimize', 'autoptimize_js_exclude' );
633
+ register_setting( 'autoptimize', 'autoptimize_js_trycatch' );
634
+ register_setting( 'autoptimize', 'autoptimize_js_justhead' );
635
+ register_setting( 'autoptimize', 'autoptimize_js_forcehead' );
636
+ register_setting( 'autoptimize', 'autoptimize_js_include_inline' );
637
+ register_setting( 'autoptimize', 'autoptimize_css' );
638
+ register_setting( 'autoptimize', 'autoptimize_css_aggregate' );
639
+ register_setting( 'autoptimize', 'autoptimize_css_exclude' );
640
+ register_setting( 'autoptimize', 'autoptimize_css_justhead' );
641
+ register_setting( 'autoptimize', 'autoptimize_css_datauris' );
642
+ register_setting( 'autoptimize', 'autoptimize_css_defer' );
643
+ register_setting( 'autoptimize', 'autoptimize_css_defer_inline' );
644
+ register_setting( 'autoptimize', 'autoptimize_css_inline' );
645
+ register_setting( 'autoptimize', 'autoptimize_css_include_inline' );
646
+ register_setting( 'autoptimize', 'autoptimize_cdn_url' );
647
+ register_setting( 'autoptimize', 'autoptimize_cache_clean' );
648
+ register_setting( 'autoptimize', 'autoptimize_cache_nogzip' );
649
+ register_setting( 'autoptimize', 'autoptimize_show_adv' );
650
+ register_setting( 'autoptimize', 'autoptimize_optimize_logged' );
651
+ register_setting( 'autoptimize', 'autoptimize_optimize_checkout' );
652
  }
653
 
654
+ public function setmeta($links, $file = null)
655
+ {
656
+ // Inspired on http://wpengineer.com/meta-links-for-wordpress-plugins/.
657
+ // Do it only once - saves time.
658
  static $plugin;
659
+ if ( empty( $plugin ) ) {
660
+ $plugin = plugin_basename( AUTOPTIMIZE_PLUGIN_DIR . 'autoptimize.php' );
661
+ }
662
+
663
+ if ( null === $file ) {
664
+ // 2.7 and lower.
665
+ $settings_link = sprintf( '<a href="options-general.php?page=autoptimize">%s</a>', __( 'Settings' ) );
666
+ array_unshift( $links, $settings_link );
667
  } else {
668
+ // 2.8 and higher.
669
+ // If it's us, add the link.
670
+ if ( $file === $plugin ) {
671
+ $newlink = array( sprintf( '<a href="options-general.php?page=autoptimize">%s</a>', __( 'Settings' ) ) );
672
+ $links = array_merge( $links, $newlink );
673
  }
674
  }
675
 
676
  return $links;
677
  }
678
 
679
+ /**
680
+ * @return array
681
+ */
682
+ public static function get_defaults()
683
+ {
684
+ static $config = array(
685
+ 'autoptimize_html' => 0,
686
+ 'autoptimize_html_keepcomments' => 0,
687
+ 'autoptimize_js' => 0,
688
+ 'autoptimize_js_aggregate' => 1,
689
+ 'autoptimize_js_exclude' => 'seal.js, js/jquery/jquery.js',
690
+ 'autoptimize_js_trycatch' => 0,
691
+ 'autoptimize_js_justhead' => 0,
692
+ 'autoptimize_js_include_inline' => 0,
693
+ 'autoptimize_js_forcehead' => 0,
694
+ 'autoptimize_css' => 0,
695
+ 'autoptimize_css_aggregate' => 1,
696
+ 'autoptimize_css_exclude' => 'admin-bar.min.css, dashicons.min.css, wp-content/cache/, wp-content/uploads/',
697
+ 'autoptimize_css_justhead' => 0,
698
+ 'autoptimize_css_include_inline' => 1,
699
+ 'autoptimize_css_defer' => 0,
700
+ 'autoptimize_css_defer_inline' => '',
701
+ 'autoptimize_css_inline' => 0,
702
+ 'autoptimize_css_datauris' => 0,
703
+ 'autoptimize_cdn_url' => '',
704
+ 'autoptimize_cache_nogzip' => 1,
705
+ 'autoptimize_show_adv' => 0,
706
+ 'autoptimize_optimize_logged' => 1,
707
+ 'autoptimize_optimize_checkout' => 1
708
+ );
709
+
710
+ return $config;
711
+ }
712
+
713
+ /**
714
+ * Returns default option values for autoptimizeExtra.
715
+ *
716
+ * @return array
717
+ */
718
+ public static function get_ao_extra_default_options()
719
+ {
720
+ $defaults = array(
721
+ 'autoptimize_extra_checkbox_field_1' => '0',
722
+ 'autoptimize_extra_checkbox_field_0' => '0',
723
+ 'autoptimize_extra_radio_field_4' => '1',
724
+ 'autoptimize_extra_text_field_2' => '',
725
+ 'autoptimize_extra_text_field_3' => '',
726
+ 'autoptimize_extra_checkbox_field_5' => '0',
727
+ 'autoptimize_extra_select_field_6' => '2',
728
+ );
729
+
730
+ return $defaults;
731
+ }
732
+
733
+ /**
734
+ * Returns preload polyfill JS.
735
+ *
736
+ * @return string
737
+ */
738
+ public static function get_ao_css_preload_polyfill()
739
+ {
740
+ $preload_poly = apply_filters('autoptimize_css_preload_polyfill','<script data-cfasync=\'false\'>!function(t){"use strict";t.loadCSS||(t.loadCSS=function(){});var e=loadCSS.relpreload={};if(e.support=function(){var e;try{e=t.document.createElement("link").relList.supports("preload")}catch(t){e=!1}return function(){return e}}(),e.bindMediaToggle=function(t){function e(){t.media=a}var a=t.media||"all";t.addEventListener?t.addEventListener("load",e):t.attachEvent&&t.attachEvent("onload",e),setTimeout(function(){t.rel="stylesheet",t.media="only x"}),setTimeout(e,3e3)},e.poly=function(){if(!e.support())for(var a=t.document.getElementsByTagName("link"),n=0;n<a.length;n++){var o=a[n];"preload"!==o.rel||"style"!==o.getAttribute("as")||o.getAttribute("data-loadcss")||(o.setAttribute("data-loadcss",!0),e.bindMediaToggle(o))}},!e.support()){e.poly();var a=t.setInterval(e.poly,500);t.addEventListener?t.addEventListener("load",function(){e.poly(),t.clearInterval(a)}):t.attachEvent&&t.attachEvent("onload",function(){e.poly(),t.clearInterval(a)})}"undefined"!=typeof exports?exports.loadCSS=loadCSS:t.loadCSS=loadCSS}("undefined"!=typeof global?global:this);</script>');
741
+ return $preload_poly;
742
+ }
743
+
744
+ /**
745
+ * Returns preload JS onload handler.
746
+ *
747
+ * @return string
748
+ */
749
+ public static function get_ao_css_preload_onload()
750
+ {
751
+ $preload_onload = apply_filters('autoptimize_filter_css_preload_onload',"this.onload=null;this.rel='stylesheet'");
752
+ return $preload_onload;
753
+ }
754
+
755
+ public function get($key)
756
+ {
757
+ if ( ! is_array( $this->config ) ) {
758
+ // Default config.
759
+ $config = self::get_defaults();
760
+
761
+ // Override with user settings.
762
+ foreach ( array_keys( $config ) as $name ) {
763
+ $conf = get_option( $name );
764
+ if ( false !== $conf ) {
765
+ // It was set before!
766
+ $config[ $name ] = $conf;
767
  }
768
  }
769
 
770
+ // Save for next call.
771
  $this->config = apply_filters( 'autoptimize_filter_get_config', $config );
772
  }
773
 
774
+ if ( isset( $this->config[ $key ] ) ) {
775
+ return $this->config[ $key ];
776
+ }
777
 
778
  return false;
779
  }
780
 
781
  private function getFutttaFeeds($url) {
782
+ if ( $this->settings_screen_do_remote_http ) {
783
  $rss = fetch_feed( $url );
784
  $maxitems = 0;
785
 
786
  if ( ! is_wp_error( $rss ) ) {
787
+ $maxitems = $rss->get_item_quantity( 7 );
788
  $rss_items = $rss->get_items( 0, $maxitems );
789
  }
790
  ?>
802
  <?php endforeach; ?>
803
  <?php endif; ?>
804
  </ul>
805
+ <?php
806
  }
807
  }
808
 
809
  // based on http://wordpress.stackexchange.com/a/58826
810
+ static function ao_admin_tabs()
811
+ {
812
+ $tabs = apply_filters( 'autoptimize_filter_settingsscreen_tabs' ,array( 'autoptimize' => __( 'Main', 'autoptimize' ) ) );
813
+ $tabContent = '';
814
+ $tabs_count = count($tabs);
815
+ if ( $tabs_count > 1 ) {
816
+ if ( isset( $_GET['page'] ) ) {
817
  $currentId = $_GET['page'];
818
  } else {
819
  $currentId = "autoptimize";
820
  }
821
+ $tabContent .= '<h2 class="nav-tab-wrapper">';
822
+ foreach ($tabs as $tabId => $tabName) {
823
+ if ( $currentId == $tabId ) {
824
+ $class = ' nav-tab-active';
825
  } else{
826
+ $class = '';
827
  }
828
+ $tabContent .= '<a class="nav-tab' . $class . '" href="?page=' . $tabId . '">' . $tabName . '</a>';
829
  }
830
+ $tabContent .= '</h2>';
831
  } else {
832
+ $tabContent = '<hr/>';
833
  }
834
 
835
  return $tabContent;
836
  }
837
+
838
+ /**
839
+ * Returns true if in admin (and not in admin-ajax.php!)
840
+ *
841
+ * @return bool
842
+ */
843
+ public static function is_admin_and_not_ajax()
844
+ {
845
+ return ( is_admin() && ! self::doing_ajax() );
846
+ }
847
+
848
+ /**
849
+ * Returns true if doing ajax.
850
+ *
851
+ * @return type
852
+ */
853
+ protected static function doing_ajax()
854
+ {
855
+ if ( function_exists( 'wp_doing_ajax' ) ) {
856
+ return wp_doing_ajax();
857
+ } else {
858
+ return ( defined( 'DOING_AJAX' ) && DOING_AJAX );
859
+ }
860
+ }
861
  }
classes/autoptimizeExtra.php ADDED
@@ -0,0 +1,921 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Handles autoptimizeExtra frontend features + admin options page
4
+ */
5
+
6
+ if ( ! defined( 'ABSPATH' ) ) {
7
+ exit;
8
+ }
9
+
10
+ class autoptimizeExtra
11
+ {
12
+ /**
13
+ * Options
14
+ *
15
+ * @var array
16
+ */
17
+ protected $options = array();
18
+
19
+ /**
20
+ * Creates an instance and calls run().
21
+ *
22
+ * @param array $options Optional. Allows overriding options without having to specify them via admin options page.
23
+ */
24
+ public function __construct( $options = array() )
25
+ {
26
+ if ( empty( $options ) ) {
27
+ $options = $this->fetch_options();
28
+ }
29
+
30
+ $this->options = $options;
31
+ }
32
+
33
+ public function run()
34
+ {
35
+ if ( is_admin() ) {
36
+ add_action( 'admin_menu', array( $this, 'admin_menu' ) );
37
+ add_filter( 'autoptimize_filter_settingsscreen_tabs', array( $this, 'add_extra_tab' ) );
38
+ } else {
39
+ $this->run_on_frontend();
40
+ }
41
+ }
42
+
43
+ protected function fetch_options()
44
+ {
45
+ $value = get_option( 'autoptimize_extra_settings' );
46
+ if ( empty( $value ) ) {
47
+ // Fallback to returning defaults when no stored option exists yet.
48
+ $value = autoptimizeConfig::get_ao_extra_default_options();
49
+ }
50
+
51
+ // get service availability.
52
+ $value['availabilities'] = get_option( 'autoptimize_service_availablity' );
53
+
54
+ if ( empty( $value['availabilities'] ) ) {
55
+ $value['availabilities'] = autoptimizeUtils::check_service_availability( true );
56
+ }
57
+
58
+ return $value;
59
+ }
60
+
61
+ public function disable_emojis()
62
+ {
63
+ // Removing all actions related to emojis!
64
+ remove_action( 'admin_print_styles', 'print_emoji_styles' );
65
+ remove_action( 'wp_head', 'print_emoji_detection_script', 7 );
66
+ remove_action( 'admin_print_scripts', 'print_emoji_detection_script' );
67
+ remove_action( 'wp_print_styles', 'print_emoji_styles' );
68
+ remove_filter( 'wp_mail', 'wp_staticize_emoji_for_email' );
69
+ remove_filter( 'the_content_feed', 'wp_staticize_emoji' );
70
+ remove_filter( 'comment_text_rss', 'wp_staticize_emoji' );
71
+
72
+ // Removes TinyMCE emojis.
73
+ add_filter( 'tiny_mce_plugins', array( $this, 'filter_disable_emojis_tinymce' ) );
74
+
75
+ // Removes emoji dns-preftech.
76
+ add_filter( 'wp_resource_hints', array( $this, 'filter_remove_emoji_dns_prefetch' ), 10, 2 );
77
+ }
78
+
79
+ public function filter_disable_emojis_tinymce( $plugins )
80
+ {
81
+ if ( is_array( $plugins ) ) {
82
+ return array_diff( $plugins, array( 'wpemoji' ) );
83
+ } else {
84
+ return array();
85
+ }
86
+ }
87
+
88
+ public function filter_remove_qs( $src ) {
89
+ if ( strpos( $src, '?ver=' ) ) {
90
+ $src = remove_query_arg( 'ver', $src );
91
+ }
92
+
93
+ return $src;
94
+ }
95
+
96
+ public function extra_async_js( $in )
97
+ {
98
+ $exclusions = array();
99
+ if ( ! empty( $in ) ) {
100
+ $exclusions = array_fill_keys( array_filter( array_map( 'trim', explode( ',', $in ) ) ), '' );
101
+ }
102
+
103
+ $settings = $this->options['autoptimize_extra_text_field_3'];
104
+ $async = array_fill_keys( array_filter( array_map( 'trim', explode( ',', $settings ) ) ), '' );
105
+ $attr = apply_filters( 'autoptimize_filter_extra_async', 'async' );
106
+ foreach ( $async as $k => $v ) {
107
+ $async[ $k ] = $attr;
108
+ }
109
+
110
+ // Merge exclusions & asyncs in one array and return to AO API.
111
+ $merged = array_merge( $exclusions, $async );
112
+
113
+ return $merged;
114
+ }
115
+
116
+ protected function run_on_frontend()
117
+ {
118
+ $options = $this->options;
119
+
120
+ // Disable emojis if specified.
121
+ if ( ! empty( $options['autoptimize_extra_checkbox_field_1'] ) ) {
122
+ $this->disable_emojis();
123
+ }
124
+
125
+ // Remove version query parameters.
126
+ if ( ! empty( $options['autoptimize_extra_checkbox_field_0'] ) ) {
127
+ add_filter( 'script_loader_src', array( $this, 'filter_remove_qs' ), 15, 1 );
128
+ add_filter( 'style_loader_src', array( $this, 'filter_remove_qs' ), 15, 1 );
129
+ }
130
+
131
+ // Making sure is_plugin_active() exists when we need it.
132
+ if ( ! function_exists( 'is_plugin_active' ) ) {
133
+ require_once ABSPATH . 'wp-admin/includes/plugin.php';
134
+ }
135
+ // Avoiding conflicts of interest when async-javascript plugin is active!
136
+ $async_js_plugin_active = false;
137
+ if ( function_exists( 'is_plugin_active' ) && is_plugin_active( 'async-javascript/async-javascript.php' ) ) {
138
+ $async_js_plugin_active = true;
139
+ }
140
+ if ( ! empty( $options['autoptimize_extra_text_field_3'] ) && ! $async_js_plugin_active ) {
141
+ add_filter( 'autoptimize_filter_js_exclude', array( $this, 'extra_async_js' ), 10, 1 );
142
+ }
143
+
144
+ // Optimize google fonts!
145
+ if ( ! empty( $options['autoptimize_extra_radio_field_4'] ) && ( '1' !== $options['autoptimize_extra_radio_field_4'] ) ) {
146
+ add_filter( 'wp_resource_hints', array( $this, 'filter_remove_gfonts_dnsprefetch' ), 10, 2 );
147
+ add_filter( 'autoptimize_html_after_minify', array( $this, 'filter_optimize_google_fonts' ), 10, 1 );
148
+ add_filter( 'autoptimize_extra_filter_tobepreconn', array( $this, 'filter_preconnect_google_fonts' ), 10, 1 );
149
+ }
150
+
151
+ // Preconnect!
152
+ if ( ! empty( $options['autoptimize_extra_text_field_2'] ) || has_filter( 'autoptimize_extra_filter_tobepreconn' ) ) {
153
+ add_filter( 'wp_resource_hints', array( $this, 'filter_preconnect' ), 10, 2 );
154
+ }
155
+
156
+ // Optimize Images!
157
+ if ( ! empty( $options['autoptimize_extra_checkbox_field_5'] ) && 'down' !== $options['availabilities']['extra_imgopt']['status'] && ( 'launch' !== $options['availabilities']['extra_imgopt']['status'] || $this->imgopt_launch_ok() ) ) {
158
+ if ( apply_filters( 'autoptimize_filter_extra_imgopt_do', true ) ) {
159
+ add_filter( 'autoptimize_html_after_minify', array( $this, 'filter_optimize_images' ), 10, 1 );
160
+ $_imgopt_active = true;
161
+ }
162
+ if ( apply_filters( 'autoptimize_filter_extra_imgopt_do_css', true ) ) {
163
+ add_filter( 'autoptimize_filter_base_replace_cdn', array( $this, 'filter_optimize_css_images' ), 10, 1 );
164
+ $_imgopt_active = true;
165
+ }
166
+ if ( $_imgopt_active ) {
167
+ add_filter( 'autoptimize_extra_filter_tobepreconn', array( $this, 'filter_preconnect_imgopt_url' ), 10, 1 );
168
+ }
169
+ }
170
+ }
171
+
172
+ public function filter_remove_emoji_dns_prefetch( $urls, $relation_type )
173
+ {
174
+ $emoji_svg_url = apply_filters( 'emoji_svg_url', 'https://s.w.org/images/core/emoji/' );
175
+
176
+ return $this->filter_remove_dns_prefetch( $urls, $relation_type, $emoji_svg_url );
177
+ }
178
+
179
+ public function filter_remove_gfonts_dnsprefetch( $urls, $relation_type )
180
+ {
181
+ return $this->filter_remove_dns_prefetch( $urls, $relation_type, 'fonts.googleapis.com' );
182
+ }
183
+
184
+ public function filter_remove_dns_prefetch( $urls, $relation_type, $url_to_remove )
185
+ {
186
+ if ( 'dns-prefetch' === $relation_type ) {
187
+ $cnt = 0;
188
+ foreach ( $urls as $url ) {
189
+ if ( false !== strpos( $url, $url_to_remove ) ) {
190
+ unset( $urls[ $cnt ] );
191
+ }
192
+ $cnt++;
193
+ }
194
+ }
195
+
196
+ return $urls;
197
+ }
198
+
199
+ public function filter_optimize_google_fonts( $in )
200
+ {
201
+ // Extract fonts, partly based on wp rocket's extraction code.
202
+ $markup = preg_replace( '/<!--(.*)-->/Uis', '', $in );
203
+ preg_match_all( '#<link(?:\s+(?:(?!href\s*=\s*)[^>])+)?(?:\s+href\s*=\s*([\'"])((?:https?:)?\/\/fonts\.googleapis\.com\/css(?:(?!\1).)+)\1)(?:\s+[^>]*)?>#iU', $markup, $matches );
204
+
205
+ $fonts_collection = array();
206
+ if ( ! $matches[2] ) {
207
+ return $in;
208
+ }
209
+
210
+ // Store them in $fonts array.
211
+ $i = 0;
212
+ foreach ( $matches[2] as $font ) {
213
+ if ( ! preg_match( '/rel=["\']dns-prefetch["\']/', $matches[0][ $i ] ) ) {
214
+ // Get fonts name.
215
+ $font = str_replace( array( '%7C', '%7c' ), '|', $font );
216
+ $font = explode( 'family=', $font );
217
+ $font = ( isset( $font[1] ) ) ? explode( '&', $font[1] ) : array();
218
+ // Add font to $fonts[$i] but make sure not to pollute with an empty family!
219
+ $_thisfont = array_values( array_filter( explode( '|', reset( $font ) ) ) );
220
+ if ( ! empty( $_thisfont ) ) {
221
+ $fonts_collection[ $i ]['fonts'] = $_thisfont;
222
+ // And add subset if any!
223
+ $subset = ( is_array( $font ) ) ? end( $font ) : '';
224
+ if ( false !== strpos( $subset, 'subset=' ) ) {
225
+ $subset = explode( 'subset=', $subset );
226
+ $fonts_collection[ $i ]['subsets'] = explode( ',', $subset[1] );
227
+ }
228
+ }
229
+ // And remove Google Fonts.
230
+ $in = str_replace( $matches[0][ $i ], '', $in );
231
+ }
232
+ $i++;
233
+ }
234
+
235
+ $options = $this->options;
236
+ $fonts_markup = '';
237
+ if ( '2' === $options['autoptimize_extra_radio_field_4'] ) {
238
+ // Remove Google Fonts.
239
+ unset( $fonts_collection );
240
+ return $in;
241
+ } elseif ( '3' === $options['autoptimize_extra_radio_field_4'] || '5' === $options['autoptimize_extra_radio_field_4'] ) {
242
+ // Aggregate & link!
243
+ $fonts_string = '';
244
+ $subset_string = '';
245
+ foreach ( $fonts_collection as $font ) {
246
+ $fonts_string .= '|' . trim( implode( '|', $font['fonts'] ), '|' );
247
+ if ( ! empty( $font['subsets'] ) ) {
248
+ $subset_string .= implode( ',', $font['subsets'] );
249
+ }
250
+ }
251
+
252
+ if ( ! empty( $subset_string ) ) {
253
+ $fonts_string = $fonts_string . '#038;subset=' . $subset_string;
254
+ }
255
+
256
+ $fonts_string = str_replace( '|', '%7C', ltrim( $fonts_string, '|' ) );
257
+
258
+ if ( ! empty( $fonts_string ) ) {
259
+ if ( '5' === $options['autoptimize_extra_radio_field_4'] ) {
260
+ $rel_string = 'rel="preload" as="style" onload="' . autoptimizeConfig::get_ao_css_preload_onload() . '"';
261
+ } else {
262
+ $rel_string = 'rel="stylesheet"';
263
+ }
264
+ $fonts_markup = '<link ' . $rel_string . ' id="ao_optimized_gfonts" href="https://fonts.googleapis.com/css?family=' . $fonts_string . '" />';
265
+ }
266
+ } elseif ( '4' === $options['autoptimize_extra_radio_field_4'] ) {
267
+ // Aggregate & load async (webfont.js impl.)!
268
+ $fonts_array = array();
269
+ foreach ( $fonts_collection as $_fonts ) {
270
+ if ( ! empty( $_fonts['subsets'] ) ) {
271
+ $_subset = implode( ',', $_fonts['subsets'] );
272
+ foreach ( $_fonts['fonts'] as $key => $_one_font ) {
273
+ $_one_font = $_one_font . ':' . $_subset;
274
+ $_fonts['fonts'][ $key ] = $_one_font;
275
+ }
276
+ }
277
+ $fonts_array = array_merge( $fonts_array, $_fonts['fonts'] );
278
+ }
279
+
280
+ $fonts_markup = '<script data-cfasync="false" id="ao_optimized_gfonts" type="text/javascript">WebFontConfig={google:{families:[\'';
281
+ foreach ( $fonts_array as $fnt ) {
282
+ $fonts_markup .= $fnt . "','";
283
+ }
284
+ $fonts_markup = trim( trim( $fonts_markup, "'" ), ',' );
285
+ $fonts_markup .= '] },classes:false, events:false, timeout:1500};(function() {var wf = document.createElement(\'script\');wf.src=\'https://ajax.googleapis.com/ajax/libs/webfont/1/webfont.js\';wf.type=\'text/javascript\';wf.async=\'true\';var s=document.getElementsByTagName(\'script\')[0];s.parentNode.insertBefore(wf, s);})();</script>';
286
+ }
287
+
288
+ // Replace back in markup.
289
+ $out = substr_replace( $in, $fonts_markup . '<link', strpos( $in, '<link' ), strlen( '<link' ) );
290
+ unset( $fonts_collection );
291
+
292
+ // and insert preload polyfill if "link preload" and if the polyfill isn't there yet (courtesy of inline&defer).
293
+ $preload_polyfill = autoptimizeConfig::get_ao_css_preload_polyfill();
294
+ if ( '5' === $options['autoptimize_extra_radio_field_4'] && strpos( $out, $preload_polyfill ) === false ) {
295
+ $out = str_replace( '</body>', $preload_polyfill . '</body>', $out );
296
+ }
297
+ return $out;
298
+ }
299
+
300
+ public function filter_preconnect( $hints, $relation_type )
301
+ {
302
+ $options = $this->options;
303
+
304
+ // Get settings and store in array.
305
+ $preconns = array_filter( array_map( 'trim', explode( ',', $options['autoptimize_extra_text_field_2'] ) ) );
306
+ $preconns = apply_filters( 'autoptimize_extra_filter_tobepreconn', $preconns );
307
+
308
+ // Walk array, extract domain and add to new array with crossorigin attribute.
309
+ foreach ( $preconns as $preconn ) {
310
+ $parsed = parse_url( $preconn );
311
+
312
+ if ( is_array( $parsed ) && empty( $parsed['scheme'] ) ) {
313
+ $domain = '//' . $parsed['host'];
314
+ } elseif ( is_array( $parsed ) ) {
315
+ $domain = $parsed['scheme'] . '://' . $parsed['host'];
316
+ }
317
+
318
+ if ( ! empty( $domain ) ) {
319
+ $hint = array( 'href' => $domain );
320
+ // Fonts don't get preconnected unless crossorigin flag is set, non-fonts don't get preconnected if origin flag is set
321
+ // so hardcode fonts.gstatic.com to come with crossorigin and have filter to add other domains if needed.
322
+ $crossorigins = apply_filters( 'autoptimize_extra_filter_preconn_crossorigin', array( 'https://fonts.gstatic.com' ) );
323
+ if ( in_array( $domain, $crossorigins ) ) {
324
+ $hint['crossorigin'] = 'anonymous';
325
+ }
326
+ $new_hints[] = $hint;
327
+ }
328
+ }
329
+
330
+ // Merge in WP's preconnect hints.
331
+ if ( 'preconnect' === $relation_type && ! empty( $new_hints ) ) {
332
+ $hints = array_merge( $hints, $new_hints );
333
+ }
334
+
335
+ return $hints;
336
+ }
337
+
338
+ public function filter_preconnect_google_fonts( $in )
339
+ {
340
+ if ( '2' !== $this->options['autoptimize_extra_radio_field_4'] ) {
341
+ // Preconnect to fonts.gstatic.com unless we remove gfonts.
342
+ $in[] = 'https://fonts.gstatic.com';
343
+ }
344
+
345
+ if ( '4' === $this->options['autoptimize_extra_radio_field_4'] ) {
346
+ // Preconnect even more hosts for webfont.js!
347
+ $in[] = 'https://ajax.googleapis.com';
348
+ $in[] = 'https://fonts.googleapis.com';
349
+ }
350
+
351
+ return $in;
352
+ }
353
+
354
+ public function filter_optimize_images( $in )
355
+ {
356
+ /*
357
+ * potential future functional improvements:
358
+ *
359
+ * picture element.
360
+ * filter for critical CSS.
361
+ */
362
+
363
+ $imgopt_base_url = $this->get_imgopt_base_url();
364
+ $to_replace = array();
365
+
366
+ // extract img tags.
367
+ if ( preg_match_all( '#<img[^>]*src[^>]*>#Usmi', $in, $matches ) ) {
368
+ foreach ( $matches[0] as $tag ) {
369
+ $orig_tag = $tag;
370
+
371
+ // first do (data-)srcsets.
372
+ if ( preg_match_all( '#srcset=("|\')(.*)("|\')#Usmi', $tag, $allsrcsets, PREG_SET_ORDER ) ) {
373
+ foreach ( $allsrcsets as $srcset ) {
374
+ $srcset = $srcset[2];
375
+ $srcsets = explode( ',', $srcset );
376
+ foreach ( $srcsets as $indiv_srcset ) {
377
+ $indiv_srcset_parts = explode( ' ', trim( $indiv_srcset ) );
378
+ if ( $indiv_srcset_parts[1] && rtrim( $indiv_srcset_parts[1], 'w' ) !== $indiv_srcset_parts[1] ) {
379
+ $imgopt_w = rtrim( $indiv_srcset_parts[1], 'w' );
380
+ }
381
+ if ( $this->can_optimize_image( $indiv_srcset_parts[0] ) ) {
382
+ $imgopt_url = $this->build_imgopt_url( $indiv_srcset_parts[0], $imgopt_w, '' );
383
+ $tag = str_replace( $indiv_srcset_parts[0], $imgopt_url, $tag );
384
+ $to_replace[ $orig_tag ] = $tag;
385
+ }
386
+ }
387
+ }
388
+ }
389
+
390
+ // proceed with img src.
391
+ // first get width and height and add to $imgopt_size.
392
+ if ( preg_match( '#width=("|\')(.*)("|\')#Usmi', $tag, $width ) ) {
393
+ $imgopt_w = $width[2];
394
+ }
395
+ if ( preg_match( '#height=("|\')(.*)("|\')#Usmi', $tag, $height ) ) {
396
+ $imgopt_h = $height[2];
397
+ }
398
+
399
+ // then start replacing images src.
400
+ if ( preg_match_all( '#src=(?:"|\')(?!data)(.*)(?:"|\')#Usmi', $tag, $urls, PREG_SET_ORDER ) ) {
401
+ foreach ( $urls as $url ) {
402
+ $full_src_orig = $url[0];
403
+ $url = $url[1];
404
+ if ( $this->can_optimize_image( $url ) ) {
405
+ $imgopt_url = $this->build_imgopt_url( $url, $imgopt_w, $imgopt_h );
406
+ $full_imgopt_src = str_replace( $url, $imgopt_url, $full_src_orig );
407
+ $tag = str_replace( $full_src_orig, $full_imgopt_src, $tag );
408
+ $to_replace[ $orig_tag ] = $tag;
409
+ }
410
+ }
411
+ }
412
+ }
413
+ }
414
+ $out = str_replace( array_keys( $to_replace ), array_values( $to_replace ), $in );
415
+
416
+ // img thumbnails in e.g. woocommerce.
417
+ if ( strpos( $out, 'data-thumb' ) !== false && apply_filters( 'autoptimize_filter_extra_imgopt_datathumbs', true ) ) {
418
+ $out = preg_replace_callback(
419
+ '/\<div(?:[^>]?)\sdata-thumb\=(?:\"|\')(.+?)(?:\"|\')(?:[^>]*)?\>/s',
420
+ array( $this, 'replace_data_thumbs' ),
421
+ $out
422
+ );
423
+ }
424
+
425
+ return $out;
426
+ }
427
+
428
+ public function filter_optimize_css_images( $in )
429
+ {
430
+ $imgopt_base_url = $this->get_imgopt_base_url();
431
+ $in = $this->normalize_img_urls( $in );
432
+
433
+ if ( $this->can_optimize_image( $in ) ) {
434
+ return $this->build_imgopt_url( $in, '', '' );
435
+ } else {
436
+ return $in;
437
+ }
438
+ }
439
+
440
+ private function get_imgopt_base_url()
441
+ {
442
+ static $imgopt_base_url = null;
443
+
444
+ if ( is_null( $imgopt_base_url ) ) {
445
+ $imgopt_host = $this->get_imgopt_host();
446
+ $quality = $this->get_img_quality_string();
447
+ $ret_val = apply_filters( 'autoptimize_filter_extra_imgopt_wait', 'ret_img' ); // values: ret_wait, ret_img, ret_json, ret_blank.
448
+ $imgopt_base_url = $imgopt_host . 'client/' . $quality . ',' . $ret_val;
449
+ $imgopt_base_url = apply_filters( 'autoptimize_filter_extra_imgopt_base_url', $imgopt_base_url );
450
+ }
451
+
452
+ return $imgopt_base_url;
453
+ }
454
+
455
+ private function can_optimize_image( $url )
456
+ {
457
+ static $cdn_url = null;
458
+ static $nopti_images = null;
459
+
460
+ if ( is_null( $cdn_url ) ) {
461
+ $cdn_url = apply_filters( 'autoptimize_filter_base_cdnurl', get_option( 'autoptimize_cdn_url', '' ) );
462
+ }
463
+
464
+ if ( is_null( $nopti_images ) ) {
465
+ $nopti_images = apply_filters( 'autoptimize_filter_extra_imgopt_noptimize', '' );
466
+ }
467
+
468
+ $imgopt_base_url = $this->get_imgopt_base_url();
469
+ $site_host = AUTOPTIMIZE_SITE_DOMAIN;
470
+ $url_parsed = parse_url( $url );
471
+
472
+ if ( $url_parsed['host'] !== $site_host && empty( $cdn_url ) ) {
473
+ return false;
474
+ } elseif ( ! empty( $cdn_url ) && strpos( $url, $cdn_url ) === false && $url_parsed['host'] !== $site_host ) {
475
+ return false;
476
+ } elseif ( strpos( $url, '.php' ) !== false ) {
477
+ return false;
478
+ } elseif ( str_ireplace( array( '.png', '.gif', '.jpg', '.jpeg' ), '', $url_parsed['path'] ) === $url_parsed['path'] ) {
479
+ // fixme: better check against end of string.
480
+ return false;
481
+ } elseif ( ! empty( $nopti_images ) ) {
482
+ $nopti_images_array = array_filter( array_map( 'trim', explode( ',', $nopti_images ) ) );
483
+ foreach ( $nopti_images_array as $nopti_image ) {
484
+ if ( strpos( $url, $nopti_image ) !== false ) {
485
+ return false;
486
+ }
487
+ }
488
+ }
489
+ return true;
490
+ }
491
+
492
+ private function build_imgopt_url( $orig_url, $width = 0, $height = 0 )
493
+ {
494
+ $filtered_url = apply_filters( 'autoptimize_filter_extra_imgopt_build_url', $orig_url, $width, $height );
495
+
496
+ if ( $filtered_url !== $orig_url ) {
497
+ return $filtered_url;
498
+ }
499
+
500
+ $orig_url = $this->normalize_img_urls( $orig_url );
501
+ $imgopt_base_url = $this->get_imgopt_base_url();
502
+ $imgopt_size = '';
503
+
504
+ if ( $width && 0 !== $width ) {
505
+ $imgopt_size = ',w_' . $width;
506
+ }
507
+
508
+ if ( $height && 0 !== $height ) {
509
+ $imgopt_size .= ',h_' . $height;
510
+ }
511
+
512
+ $url = $imgopt_base_url . $imgopt_size . '/' . $orig_url;
513
+
514
+ return $url;
515
+ }
516
+
517
+ public function replace_data_thumbs( $matches )
518
+ {
519
+ if ( $this->can_optimize_image( $matches[1] ) ) {
520
+ return str_replace( $matches[1], $this->build_imgopt_url( $matches[1], 150, 150 ), $matches[0] );
521
+ } else {
522
+ return $matches[0];
523
+ }
524
+ }
525
+
526
+ public function filter_preconnect_imgopt_url( $in )
527
+ {
528
+ $imgopt_url_array = parse_url( $this->get_imgopt_base_url() );
529
+ $in[] = $imgopt_url_array['scheme'] . '://' . $imgopt_url_array['host'];
530
+
531
+ return $in;
532
+ }
533
+
534
+ private function normalize_img_urls( $in )
535
+ {
536
+ $parsed_site_url = parse_url( site_url() );
537
+
538
+ if ( strpos( $in, 'http' ) !== 0 && strpos( $in, '//' ) === 0 ) {
539
+ $in = $parsed_site_url['scheme'] . ':' . $in;
540
+ } elseif ( strpos( $in, '/' ) === 0 ) {
541
+ $in = $parsed_site_url['scheme'] . '://' . $parsed_site_url['host'] . $in;
542
+ }
543
+
544
+ return apply_filters( 'autoptimize_filter_extra_imgopt_normalized_url', $in );
545
+ }
546
+
547
+ private function get_img_quality_array()
548
+ {
549
+ static $img_quality_array = null;
550
+
551
+ if ( is_null( $img_quality_array ) ) {
552
+ $img_quality_array = array(
553
+ '1' => 'lossy',
554
+ '2' => 'glossy',
555
+ '3' => 'lossless',
556
+ );
557
+ $img_quality_array = apply_filters( 'autoptimize_filter_extra_imgopt_quality_array', $img_quality_array );
558
+ }
559
+
560
+ return $img_quality_array;
561
+ }
562
+
563
+ private function get_img_quality_setting()
564
+ {
565
+ static $_img_q = null;
566
+
567
+ if ( is_null( $_img_q ) ) {
568
+ $_setting = $this->options['autoptimize_extra_select_field_6'];
569
+
570
+ if ( ! $_setting || empty( $_setting ) || ( '1' !== $_setting && '3' !== $_setting ) ) {
571
+ // default image opt. value is 2 ("glossy").
572
+ $_img_q = '2';
573
+ } else {
574
+ $_img_q = $_setting;
575
+ }
576
+ }
577
+
578
+ return $_img_q;
579
+ }
580
+
581
+ private function get_img_quality_string()
582
+ {
583
+ static $_img_q_string = null;
584
+
585
+ if ( is_null( $_img_q_string ) ) {
586
+ $_quality_array = $this->get_img_quality_array();
587
+ $_setting = $this->get_img_quality_setting();
588
+ $_img_q_string = apply_filters( 'autoptimize_filter_extra_imgopt_quality', 'q_' . $_quality_array[ $_setting ] );
589
+ }
590
+
591
+ return $_img_q_string;
592
+ }
593
+
594
+ public static function get_img_provider_stats()
595
+ {
596
+ // wrapper around query_img_provider_stats() so we can get to $this->options from cronjob() in autoptimizeCacheChecker.
597
+ $self = new self();
598
+ return $self->query_img_provider_stats();
599
+ }
600
+
601
+ public function query_img_provider_stats()
602
+ {
603
+ if ( ! empty( $this->options['autoptimize_extra_checkbox_field_5'] ) ) {
604
+ $_img_provider_stat_url = '';
605
+ $_img_provider_endpoint = $this->get_imgopt_host() . 'read-domain/';
606
+ $_site_host = AUTOPTIMIZE_SITE_DOMAIN;
607
+
608
+ // make sure parse_url result makes sense, keeping $_img_provider_stat_url empty if not.
609
+ if ( $_site_host && ! empty( $_site_host ) ) {
610
+ $_img_provider_stat_url = $_img_provider_endpoint . $_site_host;
611
+ }
612
+
613
+ $_img_provider_stat_url = apply_filters( 'autoptimize_filter_extra_imgopt_stat_url', $_img_provider_stat_url );
614
+
615
+ // only do the remote call if $_img_provider_stat_url is not empty to make sure no parse_url weirdness results in useless calls.
616
+ if ( ! empty( $_img_provider_stat_url ) ) {
617
+ $_img_stat_resp = wp_remote_get( $_img_provider_stat_url );
618
+ if ( ! is_wp_error( $_img_stat_resp ) ) {
619
+ if ( '200' == wp_remote_retrieve_response_code( $_img_stat_resp ) ) {
620
+ $_img_provider_stat = json_decode( wp_remote_retrieve_body( $_img_stat_resp ), true );
621
+ update_option( 'autoptimize_imgopt_provider_stat', $_img_provider_stat );
622
+ }
623
+ }
624
+ }
625
+ }
626
+ }
627
+
628
+ public function imgopt_launch_ok()
629
+ {
630
+ static $launch_status = null;
631
+
632
+ if ( is_null( $launch_status ) ) {
633
+ $avail_imgopt = $this->options['availabilities']['extra_imgopt'];
634
+ $magic_number = intval( substr( md5( parse_url( AUTOPTIMIZE_WP_SITE_URL, PHP_URL_HOST ) ), 0, 3 ), 16 );
635
+ $has_launched = get_option( 'autoptimize_imgopt_launched', '' );
636
+ if ( $has_launched || ( array_key_exists( 'launch-threshold', $avail_imgopt ) && $magic_number < $avail_imgopt['launch-threshold'] ) ) {
637
+ $launch_status = true;
638
+ if ( ! $has_launched ) {
639
+ update_option( 'autoptimize_imgopt_launched', 'on' );
640
+ }
641
+ } else {
642
+ $launch_status = false;
643
+ }
644
+ }
645
+
646
+ return $launch_status;
647
+ }
648
+
649
+ public function get_imgopt_host()
650
+ {
651
+ static $imgopt_host = null;
652
+
653
+ if ( is_null( $imgopt_host ) ) {
654
+ $avail_imgopt = $this->options['availabilities']['extra_imgopt'];
655
+ if ( ! empty( $avail_imgopt ) && array_key_exists( 'hosts', $avail_imgopt ) && is_array( $avail_imgopt['hosts'] ) ) {
656
+ $imgopt_host = array_rand( array_flip( $avail_imgopt['hosts'] ) );
657
+ } else {
658
+ $imgopt_host = 'https://cdn.shortpixel.ai/';
659
+ }
660
+ }
661
+
662
+ return $imgopt_host;
663
+ }
664
+
665
+ public static function get_imgopt_host_wrapper()
666
+ {
667
+ // needed for CI tests.
668
+ $self = new self();
669
+ return $self->get_imgopt_host();
670
+ }
671
+
672
+ public function get_imgopt_status_notice() {
673
+ $_extra_options = $this->options;
674
+ if ( ! empty( $_extra_options ) && is_array( $_extra_options ) && array_key_exists( 'autoptimize_extra_checkbox_field_5', $_extra_options ) && ! empty( $_extra_options['autoptimize_extra_checkbox_field_5'] ) ) {
675
+ $_imgopt_notice = '';
676
+ $_stat = get_option( 'autoptimize_imgopt_provider_stat', '' );
677
+ $_site_host = AUTOPTIMIZE_SITE_DOMAIN;
678
+ $_imgopt_upsell = 'https://shortpixel.com/aospai/af/GWRGFLW109483/' . $_site_host;
679
+
680
+ if ( is_array( $_stat ) ) {
681
+ if ( 1 == $_stat['Status'] ) {
682
+ // translators: "add more credits" will appear in a "a href".
683
+ $_imgopt_notice = sprintf( __( 'Your ShortPixel image optimization and CDN quota is almost used, make sure you %1$sadd more credits%2$s to avoid slowing down your website.', 'autoptimize' ), '<a href="' . $_imgopt_upsell . '" target="_blank">', '</a>' );
684
+ } elseif ( -1 == $_stat['Status'] ) {
685
+ // translators: "add more credits" will appear in a "a href".
686
+ $_imgopt_notice = sprintf( __( 'Your ShortPixel image optimization and CDN quota was used, %1$sadd more credits%2$s to keep fast serving optimized images on your site.', 'autoptimize' ), '<a href="' . $_imgopt_upsell . '" target="_blank">', '</a>' );
687
+ } else {
688
+ $_imgopt_upsell = 'https://shortpixel.com/g/af/GWRGFLW109483';
689
+ // translators: "log in to check your account" will appear in a "a href".
690
+ $_imgopt_notice = sprintf( __( 'Your ShortPixel image optimization and CDN quota are in good shape, %1$slog in to check your account%2$s.', 'autoptimize' ), '<a href="' . $_imgopt_upsell . '" target="_blank">', '</a>' );
691
+ }
692
+ $_imgopt_notice = apply_filters( 'autoptimize_filter_imgopt_notice', $_imgopt_notice );
693
+
694
+ return array(
695
+ 'status' => $_stat[ 'Status' ],
696
+ 'notice' => $_imgopt_notice,
697
+ );
698
+ }
699
+ }
700
+ return false;
701
+ }
702
+
703
+ public static function get_imgopt_status_notice_wrapper() {
704
+ // needed for notice being shown in autoptimizeCacheChecker.php.
705
+ $self = new self();
706
+ return $self->get_imgopt_status_notice();
707
+ }
708
+
709
+ public function admin_menu()
710
+ {
711
+ add_submenu_page( null, 'autoptimize_extra', 'autoptimize_extra', 'manage_options', 'autoptimize_extra', array( $this, 'options_page' ) );
712
+ register_setting( 'autoptimize_extra_settings', 'autoptimize_extra_settings' );
713
+ }
714
+
715
+ public function add_extra_tab( $in )
716
+ {
717
+ $in = array_merge( $in, array( 'autoptimize_extra' => __( 'Extra', 'autoptimize' ) ) );
718
+
719
+ return $in;
720
+ }
721
+
722
+ public function options_page()
723
+ {
724
+ // Working with actual option values from the database here.
725
+ // That way any saves are still processed as expected, but we can still
726
+ // override behavior by using `new autoptimizeExtra($custom_options)` and not have that custom
727
+ // behavior being persisted in the DB even if save is done here.
728
+ $options = $this->fetch_options();
729
+ $gfonts = $options['autoptimize_extra_radio_field_4'];
730
+ $sp_url_suffix = '/af/GWRGFLW109483/' . AUTOPTIMIZE_SITE_DOMAIN;
731
+ ?>
732
+ <style>
733
+ #ao_settings_form {background: white;border: 1px solid #ccc;padding: 1px 15px;margin: 15px 10px 10px 0;}
734
+ #ao_settings_form .form-table th {font-weight: normal;}
735
+ #autoptimize_extra_descr{font-size: 120%;}
736
+ </style>
737
+ <div class="wrap">
738
+ <h1><?php _e( 'Autoptimize Settings', 'autoptimize' ); ?></h1>
739
+ <?php echo autoptimizeConfig::ao_admin_tabs(); ?>
740
+ <?php
741
+ if ( 'on' !== get_option( 'autoptimize_js' ) && 'on' !== get_option( 'autoptimize_css' ) && 'on' !== get_option( 'autoptimize_html' ) ) {
742
+ ?>
743
+ <div class="notice-warning notice"><p>
744
+ <?php
745
+ _e( 'Most of below Extra optimizations require at least one of HTML, JS or CSS autoptimizations being active.', 'autoptimize' );
746
+ ?>
747
+ </p></div>
748
+ <?php
749
+ }
750
+
751
+ if ( 'down' === $options['availabilities']['extra_imgopt']['status'] ) {
752
+ ?>
753
+ <div class="notice-warning notice"><p>
754
+ <?php
755
+ // translators: "Autoptimize support forum" will appear in a "a href".
756
+ echo sprintf( __( 'The image optimization service is currently down, image optimization will be skipped until further notice. Check the %1$sAutoptimize support forum%2$s for more info.', 'autoptimize' ), '<a href="https://wordpress.org/support/plugin/autoptimize/" target="_blank">', '</a>' );
757
+ ?>
758
+ </p></div>
759
+ <?php
760
+ }
761
+
762
+ if ( 'launch' === $options['availabilities']['extra_imgopt']['status'] && ! $this->imgopt_launch_ok() ) {
763
+ ?>
764
+ <div class="notice-warning notice"><p>
765
+ <?php
766
+ _e( 'The image optimization service is launching, but not yet available for this domain, it should become available in the next couple of days.', 'autoptimize' );
767
+ ?>
768
+ </p></div>
769
+ <?php
770
+ }
771
+
772
+ ?>
773
+ <form id='ao_settings_form' action='options.php' method='post'>
774
+ <?php settings_fields( 'autoptimize_extra_settings' ); ?>
775
+ <h2><?php _e( 'Extra Auto-Optimizations', 'autoptimize' ); ?></h2>
776
+ <span id='autoptimize_extra_descr'><?php _e( 'The following settings can improve your site\'s performance even more.', 'autoptimize' ); ?></span>
777
+ <table class="form-table">
778
+ <tr>
779
+ <th scope="row"><?php _e( 'Google Fonts', 'autoptimize' ); ?></th>
780
+ <td>
781
+ <input type="radio" name="autoptimize_extra_settings[autoptimize_extra_radio_field_4]" value="1" <?php if ( ! in_array( $gfonts, array( 2, 3, 4, 5 ) ) ) { echo 'checked'; } ?> ><?php _e( 'Leave as is', 'autoptimize' ); ?><br/>
782
+ <input type="radio" name="autoptimize_extra_settings[autoptimize_extra_radio_field_4]" value="2" <?php checked( 2, $gfonts, true ); ?> ><?php _e( 'Remove Google Fonts', 'autoptimize' ); ?><br/>
783
+ <input type="radio" name="autoptimize_extra_settings[autoptimize_extra_radio_field_4]" value="3" <?php checked( 3, $gfonts, true ); ?> ><?php _e( 'Combine and link in head (fonts load fast but are render-blocking)', 'autoptimize' ); ?><br/>
784
+ <input type="radio" name="autoptimize_extra_settings[autoptimize_extra_radio_field_4]" value="5" <?php checked( 5, $gfonts, true ); ?> ><?php _e( 'Combine and preload in head (fonts load late, but are not render-blocking)', 'autoptimize' ); ?><br/>
785
+ <input type="radio" name="autoptimize_extra_settings[autoptimize_extra_radio_field_4]" value="4" <?php checked( 4, $gfonts, true ); ?> ><?php _e( 'Combine and load fonts asynchronously with <a href="https://github.com/typekit/webfontloader#readme" target="_blank">webfont.js</a>', 'autoptimize' ); ?><br/>
786
+ </td>
787
+ </tr>
788
+ <tr>
789
+ <th scope="row"><?php _e( 'Optimize Images', 'autoptimize' ); ?></th>
790
+ <td>
791
+ <label><input id='autoptimize_imgopt_checkbox' type='checkbox' name='autoptimize_extra_settings[autoptimize_extra_checkbox_field_5]' <?php if ( ! empty( $options['autoptimize_extra_checkbox_field_5'] ) && '1' === $options['autoptimize_extra_checkbox_field_5'] ) { echo 'checked="checked"'; } ?> value='1'><?php _e( 'Optimize images on the fly and serve them from a CDN.', 'autoptimize' ); ?></label>
792
+ <?php
793
+ // show shortpixel status.
794
+ $_notice = $this->get_imgopt_status_notice();
795
+ if ( $_notice ) {
796
+ switch ( $_notice['status'] ) {
797
+ case 2:
798
+ $_notice_color = 'green';
799
+ break;
800
+ case 1:
801
+ $_notice_color = 'orange';
802
+ break;
803
+ case -1:
804
+ $_notice_color = 'red';
805
+ break;
806
+ default:
807
+ $_notice_color = 'green';
808
+ }
809
+ echo apply_filters( 'autoptimize_filter_imgopt_settings_status', '<p><strong><span style="color:' . $_notice_color . ';">' . __( 'Shortpixel status: ', 'autoptimize' ) . '</span></strong>' . $_notice['notice'] . '</p>' );
810
+ } else {
811
+ $upsell_msg_1 = '<p>' . __( 'Get more Google love and improve your website\'s loading speed by having the images optimized on the fly by', 'autoptimize' );
812
+ $upsell_link = ' <a href="https://shortpixel.com/aospai' . $sp_url_suffix . '" target="_blank">ShortPixel</a> ';
813
+ $upsell_msg_2 = __( 'and then cached and served fast from a CDN.', 'autoptimize' ) . ' ';
814
+ if ( 'launch' === $options['availabilities']['extra_imgopt']['status'] ) {
815
+ $upsell_msg_3 = __( 'For a limited time only, this service is offered free-for-all, <b>don\'t miss the chance to test it</b> and see how much it could improve your site\'s speed.', 'autoptimize' );
816
+ } else {
817
+ $upsell_msg_3 = __( 'The service is offered for free for 100 images/month regardless of the traffic used. More image optimizations can be purchased starting with $4.99.', 'autoptimize' );
818
+ }
819
+ echo apply_filters( 'autoptimize_extra_imgopt_settings_copy', $upsell_msg_1 . $upsell_link . $upsell_msg_2 . $upsell_msg_3 . '</p>' );
820
+ }
821
+ echo '<p>' . __( 'Usage of this feature is subject to Shortpixel\'s', 'autoptimize' ) . ' <a href="https://shortpixel.com/tos' . $sp_url_suffix . '" target="_blank">' . __( 'Terms of Use', 'autoptimize' ) . '</a> ' . __( 'and', 'autoptimize' ) . ' <a href="https://shortpixel.com/pp' . $sp_url_suffix . '" target="_blank">Privacy policy</a>.</p>';
822
+ ?>
823
+ </td>
824
+ </tr>
825
+ <tr id='autoptimize_imgopt_quality' <?php if ( ! array_key_exists( 'autoptimize_extra_checkbox_field_5', $options ) || ( ! empty( $options['autoptimize_extra_checkbox_field_5'] ) && '1' !== $options['autoptimize_extra_checkbox_field_5'] ) ) { echo 'class="hidden"'; } ?>>
826
+ <th scope="row"><?php _e( 'Image Optimization quality', 'autoptimize' ); ?></th>
827
+ <td>
828
+ <label>
829
+ <select name='autoptimize_extra_settings[autoptimize_extra_select_field_6]'>
830
+ <?php
831
+ $_imgopt_array = $this->get_img_quality_array();
832
+ $_imgopt_val = $this->get_img_quality_setting();
833
+
834
+ foreach ( $_imgopt_array as $key => $value ) {
835
+ echo '<option value="' . $key . '"';
836
+ if ( $_imgopt_val == $key ) {
837
+ echo ' selected';
838
+ }
839
+ echo '>' . ucfirst( $value ) . '</option>';
840
+ }
841
+ echo "\n";
842
+ ?>
843
+ </select>
844
+ </label>
845
+ <p><?php echo apply_filters( 'autoptimize_extra_imgopt_quality_copy', __( 'You can', 'autoptimize' ) . ' <a href="https://shortpixel.com/oic' . $sp_url_suffix . '" target="_blank">' . __( 'test compression levels here', 'autoptimize' ) . '</a>.' ); ?></p>
846
+ </td>
847
+ </tr>
848
+ <tr>
849
+ <th scope="row"><?php _e( 'Remove emojis', 'autoptimize' ); ?></th>
850
+ <td>
851
+ <label><input type='checkbox' name='autoptimize_extra_settings[autoptimize_extra_checkbox_field_1]' <?php if ( ! empty( $options['autoptimize_extra_checkbox_field_1'] ) && '1' === $options['autoptimize_extra_checkbox_field_1'] ) { echo 'checked="checked"'; } ?> value='1'><?php _e( 'Removes WordPress\' core emojis\' inline CSS, inline JavaScript, and an otherwise un-autoptimized JavaScript file.', 'autoptimize' ); ?></label>
852
+ </td>
853
+ </tr>
854
+ <tr>
855
+ <th scope="row"><?php _e( 'Remove query strings from static resources', 'autoptimize' ); ?></th>
856
+ <td>
857
+ <label><input type='checkbox' name='autoptimize_extra_settings[autoptimize_extra_checkbox_field_0]' <?php if ( ! empty( $options['autoptimize_extra_checkbox_field_0'] ) && '1' === $options['autoptimize_extra_checkbox_field_0'] ) { echo 'checked="checked"'; } ?> value='1'><?php _e( 'Removing query strings (or more specifically the <code>ver</code> parameter) will not improve load time, but might improve performance scores.', 'autoptimize' ); ?></label>
858
+ </td>
859
+ </tr>
860
+ <tr>
861
+ <th scope="row"><?php _e( 'Preconnect to 3rd party domains <em>(advanced users)</em>', 'autoptimize' ); ?></th>
862
+ <td>
863
+ <label><input type='text' style='width:80%' name='autoptimize_extra_settings[autoptimize_extra_text_field_2]' value='<?php echo esc_attr( $options['autoptimize_extra_text_field_2'] ); ?>'><br /><?php _e( 'Add 3rd party domains you want the browser to <a href="https://www.keycdn.com/support/preconnect/#primary" target="_blank">preconnect</a> to, separated by comma\'s. Make sure to include the correct protocol (HTTP or HTTPS).', 'autoptimize' ); ?></label>
864
+ </td>
865
+ </tr>
866
+ <tr>
867
+ <th scope="row"><?php _e( 'Async Javascript-files <em>(advanced users)</em>', 'autoptimize' ); ?></th>
868
+ <td>
869
+ <?php
870
+ if ( function_exists( 'is_plugin_active' ) && is_plugin_active( 'async-javascript/async-javascript.php' ) ) {
871
+ _e( 'You have "Async JavaScript" installed,', 'autoptimize' );
872
+ $asj_config_url = 'options-general.php?page=async-javascript';
873
+ echo sprintf( ' <a href="' . $asj_config_url . '">%s</a>', __( 'configuration of async javascript is best done there.', 'autoptimize' ) );
874
+ } else {
875
+ ?>
876
+ <input type='text' style='width:80%' name='autoptimize_extra_settings[autoptimize_extra_text_field_3]' value='<?php echo esc_attr( $options['autoptimize_extra_text_field_3'] ); ?>'>
877
+ <br />
878
+ <?php
879
+ _e( 'Comma-separated list of local or 3rd party JS-files that should loaded with the <code>async</code> flag. JS-files from your own site will be automatically excluded if added here. ', 'autoptimize' );
880
+ // translators: %s will be replaced by a link to the "async javascript" plugin.
881
+ echo sprintf( __( 'Configuration of async javascript is easier and more flexible using the %s plugin.', 'autoptimize' ), '"<a href="https://wordpress.org/plugins/async-javascript" target="_blank">Async Javascript</a>"' );
882
+ $asj_install_url = network_admin_url() . 'plugin-install.php?s=async+javascript&tab=search&type=term';
883
+ echo sprintf( ' <a href="' . $asj_install_url . '">%s</a>', __( 'Click here to install and activate it.', 'autoptimize' ) );
884
+ }
885
+ ?>
886
+ </td>
887
+ </tr>
888
+ <tr>
889
+ <th scope="row"><?php _e( 'Optimize YouTube videos', 'autoptimize' ); ?></th>
890
+ <td>
891
+ <?php
892
+ if ( function_exists( 'is_plugin_active' ) && is_plugin_active( 'wp-youtube-lyte/wp-youtube-lyte.php' ) ) {
893
+ _e( 'Great, you have WP YouTube Lyte installed.', 'autoptimize' );
894
+ $lyte_config_url = 'options-general.php?page=lyte_settings_page';
895
+ echo sprintf( ' <a href="' . $lyte_config_url . '">%s</a>', __( 'Click here to configure it.', 'autoptimize' ) );
896
+ } else {
897
+ // translators: %s will be replaced by a link to "wp youtube lyte" plugin.
898
+ echo sprintf( __( '%s allows you to “lazy load” your videos, by inserting responsive “Lite YouTube Embeds". ', 'autoptimize' ), '<a href="https://wordpress.org/plugins/wp-youtube-lyte" target="_blank">WP YouTube Lyte</a>' );
899
+ $lyte_install_url = network_admin_url() . 'plugin-install.php?s=lyte&tab=search&type=term';
900
+ echo sprintf( ' <a href="' . $lyte_install_url . '">%s</a>', __( 'Click here to install and activate it.', 'autoptimize' ) );
901
+ }
902
+ ?>
903
+ </td>
904
+ </tr>
905
+ </table>
906
+ <p class="submit"><input type="submit" name="submit" id="submit" class="button button-primary" value="<?php _e( 'Save Changes', 'autoptimize' ); ?>" /></p>
907
+ </form>
908
+ <script>
909
+ jQuery(document).ready(function() {
910
+ jQuery( "#autoptimize_imgopt_checkbox" ).change(function() {
911
+ if (this.checked) {
912
+ jQuery("#autoptimize_imgopt_quality").show("slow");
913
+ } else {
914
+ jQuery("#autoptimize_imgopt_quality").hide("slow");
915
+ }
916
+ });
917
+ });
918
+ </script>
919
+ <?php
920
+ }
921
+ }
classes/autoptimizeHTML.php CHANGED
@@ -1,92 +1,123 @@
1
  <?php
2
- if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
 
 
3
 
4
- class autoptimizeHTML extends autoptimizeBase {
 
 
 
 
 
 
 
 
 
 
5
  private $keepcomments = false;
6
- private $exclude = array('<!-- ngg_resource_manager_marker -->', '<!--noindex-->', '<!--/noindex-->');
7
-
8
- public function read($options) {
 
 
 
 
 
 
 
 
 
 
 
9
  // Remove the HTML comments?
10
  $this->keepcomments = (bool) $options['keepcomments'];
11
-
12
- // filter to force xhtml
13
  $this->forcexhtml = (bool) apply_filters( 'autoptimize_filter_html_forcexhtml', false );
14
-
15
- // filter to add strings to be excluded from HTML minification
16
- $excludeHTML = apply_filters( 'autoptimize_filter_html_exclude','' );
17
- if ($excludeHTML!=="") {
18
- $exclHTMLArr = array_filter(array_map('trim',explode(",",$excludeHTML)));
19
- $this->exclude = array_merge($exclHTMLArr,$this->exclude);
20
  }
21
-
22
- // Nothing else for HTML
23
  return true;
24
  }
25
-
26
- //Joins and optimizes CSS
27
- public function minify() {
28
- $noptimizeHTML = apply_filters( 'autoptimize_filter_html_noptimize', false, $this->content );
29
- if ($noptimizeHTML)
 
 
 
 
 
30
  return false;
31
-
32
- if(class_exists('Minify_HTML')) {
33
- // wrap the to-be-excluded strings in noptimize tags
34
- foreach ($this->exclude as $exclString) {
35
- if (strpos($this->content,$exclString)!==false) {
36
- $replString="<!--noptimize-->".$exclString."<!--/noptimize-->";
37
- $this->content=str_replace($exclString,$replString,$this->content);
38
- }
39
  }
 
40
 
41
- // noptimize me
42
- $this->content = $this->hide_noptimize($this->content);
43
 
44
- // Minify html
45
- $options = array('keepComments' => $this->keepcomments);
46
- if ($this->forcexhtml) {
47
- $options['xhtml'] = true;
48
- }
49
 
50
- if (@is_callable(array("Minify_HTML","minify"))) {
51
- $tmp_content = Minify_HTML::minify($this->content,$options);
52
- if (!empty($tmp_content)) {
53
- $this->content = $tmp_content;
54
- unset($tmp_content);
55
- }
56
- }
57
 
58
- // restore noptimize
59
- $this->content = $this->restore_noptimize($this->content);
60
-
61
- // remove the noptimize-wrapper from around the excluded strings
62
- foreach ($this->exclude as $exclString) {
63
- $replString="<!--noptimize-->".$exclString."<!--/noptimize-->";
64
- if (strpos($this->content,$replString)!==false) {
65
- $this->content=str_replace($replString,$exclString,$this->content);
66
- }
67
- }
68
 
69
- // revslider data attribs somehow suffer from HTML optimization, this fixes that
70
- if ( class_exists('RevSlider') && apply_filters('autoptimize_filter_html_dataattrib_cleanup', false) ) {
71
- $this->content = preg_replace('#\n(data-.*$)\n#Um',' $1 ', $this->content);
72
- $this->content = preg_replace('#<[^>]*(=\"[^"\'<>\s]*\")(\w)#','$1 $2', $this->content);
 
73
  }
 
74
 
75
- return true;
 
 
 
76
  }
77
-
78
- // Didn't minify :(
79
- return false;
80
  }
81
-
82
- // Does nothing
83
- public function cache() {
84
- //No cache for HTML
 
 
 
 
85
  return true;
86
  }
87
-
88
- //Returns the content
89
- public function getcontent() {
 
 
 
 
 
90
  return $this->content;
91
  }
92
  }
1
  <?php
2
+ /**
3
+ * Handles minifying HTML markup.
4
+ */
5
 
6
+ if ( ! defined( 'ABSPATH' ) ) {
7
+ exit;
8
+ }
9
+
10
+ class autoptimizeHTML extends autoptimizeBase
11
+ {
12
+ /**
13
+ * Whether HTML comments are kept.
14
+ *
15
+ * @var bool
16
+ */
17
  private $keepcomments = false;
18
+
19
+ /**
20
+ * Things to exclude from being minifed.
21
+ *
22
+ * @var array
23
+ */
24
+ private $exclude = array(
25
+ '<!-- ngg_resource_manager_marker -->',
26
+ '<!--noindex-->',
27
+ '<!--/noindex-->',
28
+ );
29
+
30
+ public function read( $options )
31
+ {
32
  // Remove the HTML comments?
33
  $this->keepcomments = (bool) $options['keepcomments'];
34
+
35
+ // Filter to force xhtml.
36
  $this->forcexhtml = (bool) apply_filters( 'autoptimize_filter_html_forcexhtml', false );
37
+
38
+ // Filterable strings to be excluded from HTML minification.
39
+ $exclude = apply_filters( 'autoptimize_filter_html_exclude', '' );
40
+ if ( '' !== $exclude ) {
41
+ $exclude_arr = array_filter( array_map( 'trim', explode( ',', $exclude ) ) );
42
+ $this->exclude = array_merge( $exclude_arr, $this->exclude );
43
  }
44
+
45
+ // Nothing else for HTML!
46
  return true;
47
  }
48
+
49
+ /**
50
+ * Minifies HTML.
51
+ *
52
+ * @return bool
53
+ */
54
+ public function minify()
55
+ {
56
+ $noptimize = apply_filters( 'autoptimize_filter_html_noptimize', false, $this->content );
57
+ if ( $noptimize ) {
58
  return false;
59
+ }
60
+
61
+ // Wrap the to-be-excluded strings in noptimize tags.
62
+ foreach ( $this->exclude as $str ) {
63
+ if ( false !== strpos( $this->content, $str ) ) {
64
+ $replacement = '<!--noptimize-->' . $str . '<!--/noptimize-->';
65
+ $this->content = str_replace( $str, $replacement, $this->content );
 
66
  }
67
+ }
68
 
69
+ // Noptimize.
70
+ $this->content = $this->hide_noptimize( $this->content );
71
 
72
+ // Preparing options for Minify_HTML.
73
+ $options = array( 'keepComments' => $this->keepcomments );
74
+ if ( $this->forcexhtml ) {
75
+ $options['xhtml'] = true;
76
+ }
77
 
78
+ $tmp_content = Minify_HTML::minify( $this->content, $options );
79
+ if ( ! empty( $tmp_content ) ) {
80
+ $this->content = $tmp_content;
81
+ unset( $tmp_content );
82
+ }
 
 
83
 
84
+ // Restore noptimize.
85
+ $this->content = $this->restore_noptimize( $this->content );
 
 
 
 
 
 
 
 
86
 
87
+ // Remove the noptimize-wrapper from around the excluded strings.
88
+ foreach ( $this->exclude as $str ) {
89
+ $replacement = '<!--noptimize-->' . $str . '<!--/noptimize-->';
90
+ if ( false !== strpos( $this->content, $replacement ) ) {
91
+ $this->content = str_replace( $replacement, $str, $this->content );
92
  }
93
+ }
94
 
95
+ // Revslider data attribs somehow suffer from HTML optimization, this fixes that!
96
+ if ( class_exists( 'RevSlider' ) && apply_filters( 'autoptimize_filter_html_dataattrib_cleanup', false ) ) {
97
+ $this->content = preg_replace( '#\n(data-.*$)\n#Um', ' $1 ', $this->content );
98
+ $this->content = preg_replace( '#<[^>]*(=\"[^"\'<>\s]*\")(\w)#', '$1 $2', $this->content );
99
  }
100
+
101
+ return true;
 
102
  }
103
+
104
+ /**
105
+ * Doesn't do much in case of HTML (no cache in css/js sense there)
106
+ *
107
+ * @return true
108
+ */
109
+ public function cache()
110
+ {
111
  return true;
112
  }
113
+
114
+ /**
115
+ * Returns the HTML markup.
116
+ *
117
+ * @return string
118
+ */
119
+ public function getcontent()
120
+ {
121
  return $this->content;
122
  }
123
  }
classes/autoptimizeMain.php ADDED
@@ -0,0 +1,528 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Wraps base plugin logic/hooks and handles activation/deactivation/uninstall.
4
+ */
5
+
6
+ if ( ! defined( 'ABSPATH' ) ) {
7
+ exit;
8
+ }
9
+
10
+ class autoptimizeMain
11
+ {
12
+ const INIT_EARLIER_PRIORITY = -1;
13
+ const DEFAULT_HOOK_PRIORITY = 2;
14
+
15
+ /**
16
+ * Version string.
17
+ *
18
+ * @var string
19
+ */
20
+ protected $version = null;
21
+
22
+ /**
23
+ * Main plugin filepath.
24
+ * Used for activation/deactivation/uninstall hooks.
25
+ *
26
+ * @var string
27
+ */
28
+ protected $filepath = null;
29
+
30
+ /**
31
+ * Constructor.
32
+ *
33
+ * @param string $version Version.
34
+ * @param string $filepath Filepath. Needed for activation/deactivation/uninstall hooks.
35
+ */
36
+ public function __construct( $version, $filepath )
37
+ {
38
+ $this->version = $version;
39
+ $this->filepath = $filepath;
40
+ }
41
+
42
+ public function run()
43
+ {
44
+ $this->add_hooks();
45
+
46
+ // Runs cache size checker.
47
+ $checker = new autoptimizeCacheChecker();
48
+ $checker->run();
49
+ }
50
+
51
+ protected function add_hooks()
52
+ {
53
+ add_action( 'plugins_loaded', array( $this, 'setup' ) );
54
+
55
+ add_action( 'autoptimize_setup_done', array( $this, 'version_upgrades_check' ) );
56
+ add_action( 'autoptimize_setup_done', array( $this, 'check_cache_and_run' ) );
57
+ add_action( 'autoptimize_setup_done', array( $this, 'maybe_run_ao_extra' ) );
58
+ add_action( 'autoptimize_setup_done', array( $this, 'maybe_run_partners_tab' ) );
59
+
60
+ add_action( 'init', array( $this, 'load_textdomain' ) );
61
+ add_action( 'plugins_loaded', array( $this, 'hook_page_cache_purge' ) );
62
+ add_action( 'admin_init', array( 'PAnD', 'init' ) );
63
+
64
+ register_activation_hook( $this->filepath, array( $this, 'on_activate' ) );
65
+ }
66
+
67
+ public function on_activate()
68
+ {
69
+ register_uninstall_hook( $this->filepath, 'autoptimizeMain::on_uninstall' );
70
+ }
71
+
72
+ public function load_textdomain()
73
+ {
74
+ load_plugin_textdomain( 'autoptimize' );
75
+ }
76
+
77
+ public function setup()
78
+ {
79
+ // Do we gzip in php when caching or is the webserver doing it?
80
+ define( 'AUTOPTIMIZE_CACHE_NOGZIP', (bool) get_option( 'autoptimize_cache_nogzip' ) );
81
+
82
+ // These can be overridden by specifying them in wp-config.php or such.
83
+ if ( ! defined( 'AUTOPTIMIZE_WP_CONTENT_NAME' ) ) {
84
+ define( 'AUTOPTIMIZE_WP_CONTENT_NAME', '/' . wp_basename( WP_CONTENT_DIR ) );
85
+ }
86
+ if ( ! defined( 'AUTOPTIMIZE_CACHE_CHILD_DIR' ) ) {
87
+ define( 'AUTOPTIMIZE_CACHE_CHILD_DIR', '/cache/autoptimize/' );
88
+ }
89
+ if ( ! defined( 'AUTOPTIMIZE_CACHEFILE_PREFIX' ) ) {
90
+ define( 'AUTOPTIMIZE_CACHEFILE_PREFIX', 'autoptimize_' );
91
+ }
92
+ // Note: trailing slash is not optional!
93
+ if ( ! defined( 'AUTOPTIMIZE_CACHE_DIR' ) ) {
94
+ define( 'AUTOPTIMIZE_CACHE_DIR', autoptimizeCache::get_pathname() );
95
+ }
96
+
97
+ define( 'WP_ROOT_DIR', substr( WP_CONTENT_DIR, 0, strlen( WP_CONTENT_DIR ) - strlen( AUTOPTIMIZE_WP_CONTENT_NAME ) ) );
98
+
99
+ if ( ! defined( 'AUTOPTIMIZE_WP_SITE_URL' ) ) {
100
+ if ( function_exists( 'domain_mapping_siteurl' ) ) {
101
+ define( 'AUTOPTIMIZE_WP_SITE_URL', domain_mapping_siteurl( get_current_blog_id() ) );
102
+ } else {
103
+ define( 'AUTOPTIMIZE_WP_SITE_URL', site_url() );
104
+ }
105
+ }
106
+ if ( ! defined( 'AUTOPTIMIZE_WP_CONTENT_URL' ) ) {
107
+ if ( function_exists( 'domain_mapping_siteurl' ) ) {
108
+ define( 'AUTOPTIMIZE_WP_CONTENT_URL', str_replace( get_original_url( AUTOPTIMIZE_WP_SITE_URL ), AUTOPTIMIZE_WP_SITE_URL, content_url() ) );
109
+ } else {
110
+ define( 'AUTOPTIMIZE_WP_CONTENT_URL', content_url() );
111
+ }
112
+ }
113
+ if ( ! defined( 'AUTOPTIMIZE_CACHE_URL' ) ) {
114
+ if ( is_multisite() && apply_filters( 'autoptimize_separate_blog_caches', true ) ) {
115
+ $blog_id = get_current_blog_id();
116
+ define( 'AUTOPTIMIZE_CACHE_URL', AUTOPTIMIZE_WP_CONTENT_URL . AUTOPTIMIZE_CACHE_CHILD_DIR . $blog_id . '/' );
117
+ } else {
118
+ define( 'AUTOPTIMIZE_CACHE_URL', AUTOPTIMIZE_WP_CONTENT_URL . AUTOPTIMIZE_CACHE_CHILD_DIR );
119
+ }
120
+ }
121
+ if ( ! defined( 'AUTOPTIMIZE_WP_ROOT_URL' ) ) {
122
+ define( 'AUTOPTIMIZE_WP_ROOT_URL', str_replace( AUTOPTIMIZE_WP_CONTENT_NAME, '', AUTOPTIMIZE_WP_CONTENT_URL ) );
123
+ }
124
+ if ( ! defined( 'AUTOPTIMIZE_HASH' ) ) {
125
+ define( 'AUTOPTIMIZE_HASH', wp_hash( AUTOPTIMIZE_CACHE_URL ) );
126
+ }
127
+ if ( ! defined( 'AUTOPTIMIZE_SITE_DOMAIN' ) ) {
128
+ define( 'AUTOPTIMIZE_SITE_DOMAIN', parse_url( AUTOPTIMIZE_WP_SITE_URL, PHP_URL_HOST ) );
129
+ }
130
+ do_action( 'autoptimize_setup_done' );
131
+ }
132
+
133
+ /**
134
+ * Checks if there's a need to upgrade/update options and whatnot,
135
+ * in which case we might need to do stuff and flush the cache
136
+ * to avoid old versions of aggregated files lingering around.
137
+ */
138
+ public function version_upgrades_check()
139
+ {
140
+ autoptimizeVersionUpdatesHandler::check_installed_and_update( $this->version );
141
+ }
142
+
143
+ public function check_cache_and_run()
144
+ {
145
+ if ( autoptimizeCache::cacheavail() ) {
146
+ $conf = autoptimizeConfig::instance();
147
+ if ( $conf->get( 'autoptimize_html' ) || $conf->get( 'autoptimize_js' ) || $conf->get( 'autoptimize_css' ) ) {
148
+ // Hook into WordPress frontend.
149
+ if ( defined( 'AUTOPTIMIZE_INIT_EARLIER' ) ) {
150
+ add_action(
151
+ 'init',
152
+ array( $this, 'start_buffering' ),
153
+ self::INIT_EARLIER_PRIORITY
154
+ );
155
+ } else {
156
+ if ( ! defined( 'AUTOPTIMIZE_HOOK_INTO' ) ) {
157
+ define( 'AUTOPTIMIZE_HOOK_INTO', 'template_redirect' );
158
+ }
159
+ add_action(
160
+ constant( 'AUTOPTIMIZE_HOOK_INTO' ),
161
+ array( $this, 'start_buffering' ),
162
+ self::DEFAULT_HOOK_PRIORITY
163
+ );
164
+ }
165
+ }
166
+ } else {
167
+ add_action( 'admin_notices', 'autoptimizeMain::notice_cache_unavailable' );
168
+ }
169
+ }
170
+
171
+ public function maybe_run_ao_extra()
172
+ {
173
+ if ( apply_filters( 'autoptimize_filter_extra_activate', true ) ) {
174
+ $ao_extra = new autoptimizeExtra();
175
+ $ao_extra->run();
176
+ }
177
+ }
178
+
179
+ public function maybe_run_partners_tab()
180
+ {
181
+ // Loads partners tab code if in admin (and not in admin-ajax.php)!
182
+ if ( autoptimizeConfig::is_admin_and_not_ajax() ) {
183
+ new autoptimizePartners();
184
+ }
185
+ }
186
+
187
+ public function hook_page_cache_purge()
188
+ {
189
+ // hook into a collection of page cache purge actions if filter allows.
190
+ if ( apply_filters( 'autoptimize_filter_main_hookpagecachepurge', true ) ) {
191
+ $page_cache_purge_actions = array(
192
+ 'after_rocket_clean_domain', // exists.
193
+ 'hyper_cache_purged', // Stefano confirmed this will be added.
194
+ 'w3tc_flush_posts', // exits.
195
+ 'w3tc_flush_all', // exists.
196
+ 'ce_action_cache_cleared', // Sven confirmed this will be added.
197
+ 'comet_cache_wipe_cache', // still to be confirmed by Raam.
198
+ 'wp_cache_cleared', // cfr. https://github.com/Automattic/wp-super-cache/pull/537.
199
+ 'wpfc_delete_cache', // Emre confirmed this will be added this.
200
+ 'swift_performance_after_clear_all_cache', // swift perf. yeah!
201
+ );
202
+ $page_cache_purge_actions = apply_filters( 'autoptimize_filter_main_pagecachepurgeactions', $page_cache_purge_actions );
203
+ foreach ( $page_cache_purge_actions as $purge_action ) {
204
+ add_action( $purge_action, 'autoptimizeCache::clearall_actionless' );
205
+ }
206
+ }
207
+ }
208
+
209
+ /**
210
+ * Setup output buffering if needed.
211
+ *
212
+ * @return void
213
+ */
214
+ public function start_buffering()
215
+ {
216
+ if ( $this->should_buffer() ) {
217
+
218
+ // Load speedupper conditionally (true by default).
219
+ if ( apply_filters( 'autoptimize_filter_speedupper', true ) ) {
220
+ $ao_speedupper = new autoptimizeSpeedupper();
221
+ }
222
+
223
+ $conf = autoptimizeConfig::instance();
224
+
225
+ if ( $conf->get( 'autoptimize_js' ) ) {
226
+ if ( ! defined( 'CONCATENATE_SCRIPTS' ) ) {
227
+ define( 'CONCATENATE_SCRIPTS', false );
228
+ }
229
+ if ( ! defined( 'COMPRESS_SCRIPTS' ) ) {
230
+ define( 'COMPRESS_SCRIPTS', false );
231
+ }
232
+ }
233
+
234
+ if ( $conf->get( 'autoptimize_css' ) ) {
235
+ if ( ! defined( 'COMPRESS_CSS' ) ) {
236
+ define( 'COMPRESS_CSS', false );
237
+ }
238
+ }
239
+
240
+ if ( apply_filters( 'autoptimize_filter_obkiller', false ) ) {
241
+ while ( ob_get_level() > 0 ) {
242
+ ob_end_clean();
243
+ }
244
+ }
245
+
246
+ // Now, start the real thing!
247
+ ob_start( array( $this, 'end_buffering' ) );
248
+ }
249
+ }
250
+
251
+ /**
252
+ * Returns true if all the conditions to start output buffering are satisfied.
253
+ *
254
+ * @param bool $doing_tests Allows overriding the optimization of only
255
+ * deciding once per request (for use in tests).
256
+ * @return bool
257
+ */
258
+ public function should_buffer( $doing_tests = false )
259
+ {
260
+ static $do_buffering = null;
261
+
262
+ // Only check once in case we're called multiple times by others but
263
+ // still allows multiple calls when doing tests.
264
+ if ( null === $do_buffering || $doing_tests ) {
265
+
266
+ $ao_noptimize = false;
267
+
268
+ // Checking for DONOTMINIFY constant as used by e.g. WooCommerce POS.
269
+ if ( defined( 'DONOTMINIFY' ) && ( constant( 'DONOTMINIFY' ) === true || constant( 'DONOTMINIFY' ) === 'true' ) ) {
270
+ $ao_noptimize = true;
271
+ }
272
+
273
+ // Skip checking query strings if they're disabled.
274
+ if ( apply_filters( 'autoptimize_filter_honor_qs_noptimize', true ) ) {
275
+ // Check for `ao_noptimize` (and other) keys in the query string
276
+ // to get non-optimized page for debugging.
277
+ $keys = array(
278
+ 'ao_noptimize',
279
+ 'ao_noptirocket',
280
+ );
281
+ foreach ( $keys as $key ) {
282
+ if ( array_key_exists( $key, $_GET ) && '1' === $_GET[ $key ] ) {
283
+ $ao_noptimize = true;
284
+ break;
285
+ }
286
+ }
287
+ }
288
+
289
+ // If setting says not to optimize logged in user and user is logged in...
290
+ if ( 'on' !== get_option( 'autoptimize_optimize_logged', 'on' ) && is_user_logged_in() && current_user_can( 'edit_posts' ) ) {
291
+ $ao_noptimize = true;
292
+ }
293
+
294
+ // If setting says not to optimize cart/checkout.
295
+ if ( 'on' !== get_option( 'autoptimize_optimize_checkout', 'on' ) ) {
296
+ // Checking for woocommerce, easy digital downloads and wp ecommerce...
297
+ foreach ( array( 'is_checkout', 'is_cart', 'edd_is_checkout', 'wpsc_is_cart', 'wpsc_is_checkout' ) as $func ) {
298
+ if ( function_exists( $func ) && $func() ) {
299
+ $ao_noptimize = true;
300
+ break;
301
+ }
302
+ }
303
+ }
304
+
305
+ // Allows blocking of autoptimization on your own terms regardless of above decisions.
306
+ $ao_noptimize = (bool) apply_filters( 'autoptimize_filter_noptimize', $ao_noptimize );
307
+
308
+ // Check for site being previewed in the Customizer (available since WP 4.0).
309
+ $is_customize_preview = false;
310
+ if ( function_exists( 'is_customize_preview' ) && is_customize_preview() ) {
311
+ $is_customize_preview = is_customize_preview();
312
+ }
313
+
314
+ /**
315
+ * We only buffer the frontend requests (and then only if not a feed
316
+ * and not turned off explicitly and not when being previewed in Customizer)!
317
+ * NOTE: Tests throw a notice here due to is_feed() being called
318
+ * while the main query hasn't been ran yet. Thats why we use
319
+ * AUTOPTIMIZE_INIT_EARLIER in tests.
320
+ */
321
+ $do_buffering = ( ! is_admin() && ! is_feed() && ! $ao_noptimize && ! $is_customize_preview );
322
+ }
323
+
324
+ return $do_buffering;
325
+ }
326
+
327
+ /**
328
+ * Returns true if given markup is considered valid/processable/optimizable.
329
+ *
330
+ * @param string $content Markup.
331
+ *
332
+ * @return bool
333
+ */
334
+ public function is_valid_buffer( $content )
335
+ {
336
+ // Defaults to true.
337
+ $valid = true;
338
+
339
+ $has_no_html_tag = ( false === stripos( $content, '<html' ) );
340
+ $has_xsl_stylesheet = ( false !== stripos( $content, '<xsl:stylesheet' ) );
341
+ $has_html5_doctype = ( preg_match( '/^<!DOCTYPE.+html>/i', $content ) > 0 );
342
+
343
+ if ( $has_no_html_tag ) {
344
+ // Can't be valid amp markup without an html tag preceding it.
345
+ $is_amp_markup = false;
346
+ } else {
347
+ $is_amp_markup = self::is_amp_markup( $content );
348
+ }
349
+
350
+ // If it's not html, or if it's amp or contains xsl stylesheets we don't touch it.
351
+ if ( $has_no_html_tag && ! $has_html5_doctype || $is_amp_markup || $has_xsl_stylesheet ) {
352
+ $valid = false;
353
+ }
354
+
355
+ return $valid;
356
+ }
357
+
358
+ /**
359
+ * Returns true if given $content is considered to be AMP markup.
360
+ * This is far from actual validation against AMP spec, but it'll do for now.
361
+ *
362
+ * @param string $content Markup to check.
363
+ *
364
+ * @return bool
365
+ */
366
+ public static function is_amp_markup( $content )
367
+ {
368
+ $is_amp_markup = preg_match( '/<html[^>]*(?:amp|⚡)/i', $content );
369
+
370
+ return (bool) $is_amp_markup;
371
+ }
372
+
373
+ /**
374
+ * Processes/optimizes the output-buffered content and returns it.
375
+ * If the content is not processable, it is returned unmodified.
376
+ *
377
+ * @param string $content Buffered content.
378
+ *
379
+ * @return string
380
+ */
381
+ public function end_buffering( $content )
382
+ {
383
+ // Bail early without modifying anything if we can't handle the content.
384
+ if ( ! $this->is_valid_buffer( $content ) ) {
385
+ return $content;
386
+ }
387
+
388
+ $conf = autoptimizeConfig::instance();
389
+
390
+ // Determine what needs to be ran.
391
+ $classes = array();
392
+ if ( $conf->get( 'autoptimize_js' ) ) {
393
+ $classes[] = 'autoptimizeScripts';
394
+ }
395
+ if ( $conf->get( 'autoptimize_css' ) ) {
396
+ $classes[] = 'autoptimizeStyles';
397
+ }
398
+ if ( $conf->get( 'autoptimize_html' ) ) {
399
+ $classes[] = 'autoptimizeHTML';
400
+ }
401
+
402
+ $classoptions = array(
403
+ 'autoptimizeScripts' => array(
404
+ 'aggregate' => $conf->get( 'autoptimize_js_aggregate' ),
405
+ 'justhead' => $conf->get( 'autoptimize_js_justhead' ),
406
+ 'forcehead' => $conf->get( 'autoptimize_js_forcehead' ),
407
+ 'trycatch' => $conf->get( 'autoptimize_js_trycatch' ),
408
+ 'js_exclude' => $conf->get( 'autoptimize_js_exclude' ),
409
+ 'cdn_url' => $conf->get( 'autoptimize_cdn_url' ),
410
+ 'include_inline' => $conf->get( 'autoptimize_js_include_inline' ),
411
+ ),
412
+ 'autoptimizeStyles' => array(
413
+ 'aggregate' => $conf->get( 'autoptimize_css_aggregate' ),
414
+ 'justhead' => $conf->get( 'autoptimize_css_justhead' ),
415
+ 'datauris' => $conf->get( 'autoptimize_css_datauris' ),
416
+ 'defer' => $conf->get( 'autoptimize_css_defer' ),
417
+ 'defer_inline' => $conf->get( 'autoptimize_css_defer_inline' ),
418
+ 'inline' => $conf->get( 'autoptimize_css_inline' ),
419
+ 'css_exclude' => $conf->get( 'autoptimize_css_exclude' ),
420
+ 'cdn_url' => $conf->get( 'autoptimize_cdn_url' ),
421
+ 'include_inline' => $conf->get( 'autoptimize_css_include_inline' ),
422
+ 'nogooglefont' => $conf->get( 'autoptimize_css_nogooglefont' ),
423
+ ),
424
+ 'autoptimizeHTML' => array(
425
+ 'keepcomments' => $conf->get( 'autoptimize_html_keepcomments' ),
426
+ ),
427
+ );
428
+
429
+ $content = apply_filters( 'autoptimize_filter_html_before_minify', $content );
430
+
431
+ // Run the classes!
432
+ foreach ( $classes as $name ) {
433
+ $instance = new $name( $content );
434
+ if ( $instance->read( $classoptions[ $name ] ) ) {
435
+ $instance->minify();
436
+ $instance->cache();
437
+ $content = $instance->getcontent();
438
+ }
439
+ unset( $instance );
440
+ }
441
+
442
+ $content = apply_filters( 'autoptimize_html_after_minify', $content );
443
+
444
+ return $content;
445
+ }
446
+
447
+ public static function on_uninstall()
448
+ {
449
+ autoptimizeCache::clearall();
450
+
451
+ $delete_options = array(
452
+ 'autoptimize_cache_clean',
453
+ 'autoptimize_cache_nogzip',
454
+ 'autoptimize_css',
455
+ 'autoptimize_css_aggregate',
456
+ 'autoptimize_css_datauris',
457
+ 'autoptimize_css_justhead',
458
+ 'autoptimize_css_defer',
459
+ 'autoptimize_css_defer_inline',
460
+ 'autoptimize_css_inline',
461
+ 'autoptimize_css_exclude',
462
+ 'autoptimize_html',
463
+ 'autoptimize_html_keepcomments',
464
+ 'autoptimize_js',
465
+ 'autoptimize_js_aggregate',
466
+ 'autoptimize_js_exclude',
467
+ 'autoptimize_js_forcehead',
468
+ 'autoptimize_js_justhead',
469
+ 'autoptimize_js_trycatch',
470
+ 'autoptimize_version',
471
+ 'autoptimize_show_adv',
472
+ 'autoptimize_cdn_url',
473
+ 'autoptimize_cachesize_notice',
474
+ 'autoptimize_css_include_inline',
475
+ 'autoptimize_js_include_inline',
476
+ 'autoptimize_optimize_logged',
477
+ 'autoptimize_optimize_checkout',
478
+ 'autoptimize_extra_settings',
479
+ 'autoptimize_service_availablity',
480
+ 'autoptimize_imgopt_provider_stat',
481
+ 'autoptimize_imgopt_launched',
482
+ );
483
+
484
+ if ( ! is_multisite() ) {
485
+ foreach ( $delete_options as $del_opt ) {
486
+ delete_option( $del_opt );
487
+ }
488
+ } else {
489
+ global $wpdb;
490
+ $blog_ids = $wpdb->get_col( "SELECT blog_id FROM $wpdb->blogs" );
491
+ $original_blog_id = get_current_blog_id();
492
+ foreach ( $blog_ids as $blog_id ) {
493
+ switch_to_blog( $blog_id );
494
+ foreach ( $delete_options as $del_opt ) {
495
+ delete_option( $del_opt );
496
+ }
497
+ }
498
+ switch_to_blog( $original_blog_id );
499
+ }
500
+
501
+ if ( wp_get_schedule( 'ao_cachechecker' ) ) {
502
+ wp_clear_scheduled_hook( 'ao_cachechecker' );
503
+ }
504
+ }
505
+
506
+ public static function notice_cache_unavailable()
507
+ {
508
+ echo '<div class="error"><p>';
509
+ // Translators: %s is the cache directory location.
510
+ printf( __( 'Autoptimize cannot write to the cache directory (%s), please fix to enable CSS/ JS optimization!', 'autoptimize' ), AUTOPTIMIZE_CACHE_DIR );
511
+ echo '</p></div>';
512
+ }
513
+
514
+ public static function notice_installed()
515
+ {
516
+ echo '<div class="updated"><p>';
517
+ _e( 'Thank you for installing and activating Autoptimize. Please configure it under "Settings" -> "Autoptimize" to start improving your site\'s performance.', 'autoptimize' );
518
+ echo '</p></div>';
519
+ }
520
+
521
+ public static function notice_updated()
522
+ {
523
+ echo '<div class="updated"><p>';
524
+ _e( 'Autoptimize has just been updated. Please <strong>test your site now</strong> and adapt Autoptimize config if needed.', 'autoptimize' );
525
+ echo '</p></div>';
526
+ }
527
+
528
+ }
classes/autoptimizePartners.php ADDED
@@ -0,0 +1,145 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Handles adding "more tools" tab in AO admin settings page which promotes (future) AO
4
+ * addons and/or affiliate services.
5
+ */
6
+
7
+ if ( ! defined( 'ABSPATH' ) ) {
8
+ exit;
9
+ }
10
+
11
+ class autoptimizePartners
12
+ {
13
+ public function __construct()
14
+ {
15
+ $this->run();
16
+ }
17
+
18
+ public function run()
19
+ {
20
+ if ( $this->enabled() ) {
21
+ add_filter( 'autoptimize_filter_settingsscreen_tabs', array( $this, 'add_partner_tabs' ), 10, 1 );
22
+ }
23
+ add_action( 'admin_menu', array( $this, 'add_admin_menu' ) );
24
+ }
25
+
26
+ protected function enabled()
27
+ {
28
+ return apply_filters( 'autoptimize_filter_show_partner_tabs', true );
29
+ }
30
+
31
+ public function add_partner_tabs( $in )
32
+ {
33
+ $in = array_merge( $in, array(
34
+ 'ao_partners' => __( 'Optimize More!', 'autoptimize' ),
35
+ ) );
36
+
37
+ return $in;
38
+ }
39
+
40
+ public function add_admin_menu()
41
+ {
42
+ if ( $this->enabled() ) {
43
+ add_submenu_page( null, 'AO partner', 'AO partner', 'manage_options', 'ao_partners', array( $this, 'ao_partners_page' ) );
44
+ }
45
+ }
46
+
47
+ protected function get_ao_partner_feed_markup()
48
+ {
49
+ $no_feed_text = __( 'Have a look at <a href="http://optimizingmatters.com/">optimizingmatters.com</a> for Autoptimize power-ups!', 'autoptimize' );
50
+ $output = '';
51
+ if ( apply_filters( 'autoptimize_settingsscreen_remotehttp', true ) ) {
52
+ $rss = fetch_feed( 'http://feeds.feedburner.com/OptimizingMattersDownloads' );
53
+ $maxitems = 0;
54
+
55
+ if ( ! is_wp_error( $rss ) ) {
56
+ $maxitems = $rss->get_item_quantity( 20 );
57
+ $rss_items = $rss->get_items( 0, $maxitems );
58
+ }
59
+
60
+ if ( 0 == $maxitems ) {
61
+ $output .= $no_feed_text;
62
+ } else {
63
+ $output .= '<ul>';
64
+ foreach ( $rss_items as $item ) {
65
+ $item_url = esc_url( $item->get_permalink() );
66
+ $enclosure = $item->get_enclosure();
67
+
68
+ $output .= '<li class="itemDetail">';
69
+ $output .= '<h3 class="itemTitle"><a href="' . $item_url . '" target="_blank">' . esc_html( $item->get_title() ) . '</a></h3>';
70
+
71
+ if ( $enclosure && ( false !== strpos( $enclosure->get_type(), 'image' ) ) ) {
72
+ $img_url = esc_url( $enclosure->get_link() );
73
+ $output .= '<div class="itemImage"><a href="' . $item_url . '" target="_blank"><img src="' . $img_url . '"></a></div>';
74
+ }
75
+
76
+ $output .= '<div class="itemDescription">' . wp_kses_post( $item->get_description() ) . '</div>';
77
+ $output .= '<div class="itemButtonRow"><div class="itemButton button-secondary"><a href="' . $item_url . '" target="_blank">' . __( 'More info', 'autoptimize' ) . '</a></div></div>';
78
+ $output .= '</li>';
79
+ }
80
+ $output .= '</ul>';
81
+ }
82
+ } else {
83
+ $output .= $no_feed_text;
84
+ }
85
+
86
+ return $output;
87
+ }
88
+
89
+ public function ao_partners_page()
90
+ {
91
+ ?>
92
+ <style>
93
+ .itemDetail {
94
+ background: #fff;
95
+ width: 250px;
96
+ min-height: 290px;
97
+ border: 1px solid #ccc;
98
+ float: left;
99
+ padding: 15px;
100
+ position: relative;
101
+ margin: 0 10px 10px 0;
102
+ }
103
+ .itemTitle {
104
+ margin-top:0px;
105
+ margin-bottom:10px;
106
+ }
107
+ .itemImage {
108
+ text-align: center;
109
+ }
110
+ .itemImage img {
111
+ max-width: 95%;
112
+ max-height: 150px;
113
+ }
114
+ .itemDescription {
115
+ margin-bottom:30px;
116
+ }
117
+ .itemButtonRow {
118
+ position: absolute;
119
+ bottom: 10px;
120
+ right: 10px;
121
+ width:100%;
122
+ }
123
+ .itemButton {
124
+ float:right;
125
+ }
126
+ .itemButton a {
127
+ text-decoration: none;
128
+ color: #555;
129
+ }
130
+ .itemButton a:hover {
131
+ text-decoration: none;
132
+ color: #23282d;
133
+ }
134
+ </style>
135
+ <div class="wrap">
136
+ <h1><?php _e( 'Autoptimize Settings', 'autoptimize' ); ?></h1>
137
+ <?php echo autoptimizeConfig::ao_admin_tabs(); ?>
138
+ <?php echo '<h2>' . __( "These Autoptimize power-ups and related services will improve your site's performance even more!", 'autoptimize' ) . '</h2>'; ?>
139
+ <div>
140
+ <?php echo $this->get_ao_partner_feed_markup(); ?>
141
+ </div>
142
+ </div>
143
+ <?php
144
+ }
145
+ }
classes/autoptimizeScripts.php CHANGED
@@ -1,94 +1,130 @@
1
  <?php
2
- if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
3
 
4
- class autoptimizeScripts extends autoptimizeBase {
 
 
 
 
 
5
  private $scripts = array();
6
- private $dontmove = array('document.write','html5.js','show_ads.js','google_ad','histats.com/js','statcounter.com/counter/counter.js','ws.amazon.com/widgets','media.fastclick.net','/ads/','comment-form-quicktags/quicktags.php','edToolbar','intensedebate.com','scripts.chitika.net/','_gaq.push','jotform.com/','admin-bar.min.js','GoogleAnalyticsObject','plupload.full.min.js','syntaxhighlighter','adsbygoogle','gist.github.com','_stq','nonce','post_id','data-noptimize','wordfence_logHuman');
7
- private $domove = array('gaJsHost','load_cmc','jd.gallery.transitions.js','swfobject.embedSWF(','tiny_mce.js','tinyMCEPreInit.go');
8
- private $domovelast = array('addthis.com','/afsonline/show_afs_search.js','disqus.js','networkedblogs.com/getnetworkwidget','infolinks.com/js/','jd.gallery.js.php','jd.gallery.transitions.js','swfobject.embedSWF(','linkwithin.com/widget.js','tiny_mce.js','tinyMCEPreInit.go');
9
- private $trycatch = false;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
10
  private $alreadyminified = false;
11
- private $forcehead = true;
12
- private $include_inline = false;
13
- private $jscode = '';
14
- private $url = '';
15
- private $move = array('first' => array(), 'last' => array());
16
- private $restofcontent = '';
17
- private $md5hash = '';
18
- private $whitelist = '';
19
- private $jsremovables = array();
20
  private $inject_min_late = '';
21
-
22
- //Reads the page and collects script tags
23
- public function read($options) {
 
24
  $noptimizeJS = apply_filters( 'autoptimize_filter_js_noptimize', false, $this->content );
25
- if ($noptimizeJS) return false;
 
 
26
 
27
  // only optimize known good JS?
28
  $whitelistJS = apply_filters( 'autoptimize_filter_js_whitelist', '', $this->content );
29
- if (!empty($whitelistJS)) {
30
- $this->whitelist = array_filter(array_map('trim',explode(",",$whitelistJS)));
31
  }
32
 
33
  // is there JS we should simply remove
34
  $removableJS = apply_filters( 'autoptimize_filter_js_removables', '', $this->content );
35
  if (!empty($removableJS)) {
36
- $this->jsremovables = array_filter(array_map('trim',explode(",",$removableJS)));
37
  }
38
 
39
  // only header?
40
- if( apply_filters('autoptimize_filter_js_justhead', $options['justhead']) == true ) {
41
- $content = explode('</head>',$this->content,2);
42
- $this->content = $content[0].'</head>';
43
  $this->restofcontent = $content[1];
44
  }
45
-
 
 
 
 
 
 
 
 
 
46
  // include inline?
47
- if( apply_filters('autoptimize_js_include_inline', $options['include_inline']) == true ) {
48
  $this->include_inline = true;
49
  }
50
 
51
  // filter to "late inject minified JS", default to true for now (it is faster)
52
- $this->inject_min_late = apply_filters('autoptimize_filter_js_inject_min_late',true);
53
 
54
  // filters to override hardcoded do(nt)move(last) array contents (array in, array out!)
55
- $this->dontmove = apply_filters( 'autoptimize_filter_js_dontmove', $this->dontmove );
56
  $this->domovelast = apply_filters( 'autoptimize_filter_js_movelast', $this->domovelast );
57
  $this->domove = apply_filters( 'autoptimize_filter_js_domove', $this->domove );
58
 
59
  // get extra exclusions settings or filter
60
  $excludeJS = $options['js_exclude'];
61
  $excludeJS = apply_filters( 'autoptimize_filter_js_exclude', $excludeJS, $this->content );
62
- if ($excludeJS!=="") {
63
- if (is_array($excludeJS)) {
64
- if(($removeKeys = array_keys($excludeJS,"remove")) !== false) {
65
- foreach ($removeKeys as $removeKey) {
66
- unset($excludeJS[$removeKey]);
67
- $this->jsremovables[]=$removeKey;
 
68
  }
69
  }
70
- $exclJSArr = array_keys($excludeJS);
71
  } else {
72
- $exclJSArr = array_filter(array_map('trim',explode(",",$excludeJS)));
73
  }
74
- $this->dontmove = array_merge($exclJSArr,$this->dontmove);
75
  }
76
 
77
- //Should we add try-catch?
78
- if($options['trycatch'] == true)
79
  $this->trycatch = true;
 
80
 
81
- // force js in head?
82
- if($options['forcehead'] == true) {
83
  $this->forcehead = true;
84
  } else {
85
  $this->forcehead = false;
86
  }
 
87
  $this->forcehead = apply_filters( 'autoptimize_filter_js_forcehead', $this->forcehead );
88
 
89
  // get cdn url
90
  $this->cdn_url = $options['cdn_url'];
91
-
92
  // noptimize me
93
  $this->content = $this->hide_noptimize($this->content);
94
 
@@ -99,59 +135,59 @@ class autoptimizeScripts extends autoptimizeBase {
99
  $this->content = $this->hide_comments($this->content);
100
 
101
  // Get script files
102
- if (preg_match_all('#<script.*</script>#Usmi',$this->content,$matches)) {
103
- foreach($matches[0] as $tag) {
104
  // only consider script aggregation for types whitelisted in should_aggregate-function
105
- if( !$this->should_aggregate($tag) ) {
106
- $tag='';
 
107
  continue;
108
  }
109
 
110
- if (preg_match('#<script[^>]*src=("|\')([^>]*)("|\')#Usmi',$tag,$source)) {
111
  // non-inline script
112
- if ($this->isremovable($tag,$this->jsremovables)) {
113
- $this->content = str_replace($tag,'',$this->content);
114
  continue;
115
  }
116
- $explUrl = explode('?',$source[2],2);
117
- $url = $explUrl[0];
 
118
  $path = $this->getpath($url);
119
- if($path !== false && preg_match('#\.js$#',$path) && $this->ismergeable($tag)) {
120
  // ok to optimize, add to array
121
  $this->scripts[] = $path;
122
  } else {
123
  $origTag = $tag;
124
- $newTag = $tag;
125
-
126
  // non-mergeable script (excluded or dynamic or external)
127
- if (is_array($excludeJS)) {
128
  // should we add flags?
129
- foreach ($excludeJS as $exclTag => $exclFlags) {
130
- if ( strpos($origTag,$exclTag)!==false && in_array($exclFlags,array("async","defer")) ) {
131
- $newTag = str_replace('<script ','<script '.$exclFlags.' ',$newTag);
132
  }
133
  }
134
  }
135
-
136
- // should we minify the non-aggregated script?
137
- if ($path && apply_filters('autoptimize_filter_js_minify_excluded',false)) {
138
- $_CachedMinifiedUrl = $this->minify_single($path);
139
-
140
- // replace orig URL with minified URL from cache if so
141
- if (!empty($_CachedMinifiedUrl)) {
142
- $newTag = str_replace($url, $_CachedMinifiedUrl, $newTag);
143
- }
144
-
145
- // remove querystring from URL in newTag
146
- if ( !empty($explUrl[1]) ) {
147
- $newTag = str_replace("?".$explUrl[1],"",$newTag);
148
- }
149
- }
150
-
151
- // should we move the non-aggregated script?
152
- if( $this->ismovable($newTag) ) {
153
  // can be moved, flags and all
154
- if( $this->movetolast($newTag) ) {
155
  $this->move['last'][] = $newTag;
156
  } else {
157
  $this->move['first'][] = $newTag;
@@ -159,7 +195,7 @@ class autoptimizeScripts extends autoptimizeBase {
159
  } else {
160
  // cannot be moved, so if flag was added re-inject altered tag immediately
161
  if ( $origTag !== $newTag ) {
162
- $this->content = str_replace($origTag,$newTag,$this->content);
163
  $origTag = '';
164
  }
165
  // and forget about the $tag (not to be touched any more)
@@ -168,186 +204,229 @@ class autoptimizeScripts extends autoptimizeBase {
168
  }
169
  } else {
170
  // Inline script
171
- if ($this->isremovable($tag,$this->jsremovables)) {
172
- $this->content = str_replace($tag,'',$this->content);
173
  continue;
174
  }
175
-
176
  // unhide comments, as javascript may be wrapped in comment-tags for old times' sake
177
  $tag = $this->restore_comments($tag);
178
- if($this->ismergeable($tag) && ( $this->include_inline )) {
179
- preg_match('#<script.*>(.*)</script>#Usmi',$tag,$code);
180
- $code = preg_replace('#.*<!\[CDATA\[(?:\s*\*/)?(.*)(?://|/\*)\s*?\]\]>.*#sm','$1',$code[1]);
181
- $code = preg_replace('/(?:^\\s*<!--\\s*|\\s*(?:\\/\\/)?\\s*-->\\s*$)/','',$code);
182
- $this->scripts[] = 'INLINE;'.$code;
183
  } else {
184
  // Can we move this?
185
  $autoptimize_js_moveable = apply_filters( 'autoptimize_js_moveable', '', $tag );
186
- if( $this->ismovable($tag) || $autoptimize_js_moveable !== '' ) {
187
- if( $this->movetolast($tag) || $autoptimize_js_moveable === 'last' ) {
188
  $this->move['last'][] = $tag;
189
  } else {
190
  $this->move['first'][] = $tag;
191
  }
192
  } else {
193
- //We shouldn't touch this
194
  $tag = '';
195
  }
196
  }
197
- // re-hide comments to be able to do the removal based on tag from $this->content
198
  $tag = $this->hide_comments($tag);
199
  }
200
-
201
  //Remove the original script tag
202
- $this->content = str_replace($tag,'',$this->content);
203
  }
204
-
205
  return true;
206
  }
207
-
208
  // No script files, great ;-)
209
  return false;
210
  }
211
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
212
  //Joins and optimizes JS
213
- public function minify() {
214
- foreach($this->scripts as $script) {
215
- if(preg_match('#^INLINE;#',$script)) {
216
- //Inline script
217
- $script = preg_replace('#^INLINE;#','',$script);
 
 
218
  $script = rtrim( $script, ";\n\t\r" ) . ';';
219
- //Add try-catch?
220
- if($this->trycatch) {
221
- $script = 'try{'.$script.'}catch(e){}';
222
  }
223
  $tmpscript = apply_filters( 'autoptimize_js_individual_script', $script, '' );
224
- if ( has_filter('autoptimize_js_individual_script') && !empty($tmpscript) ) {
225
- $script=$tmpscript;
226
- $this->alreadyminified=true;
227
  }
228
  $this->jscode .= "\n" . $script;
229
  } else {
230
- //External script
231
- if($script !== false && file_exists($script) && is_readable($script)) {
232
- $scriptsrc = file_get_contents($script);
233
- $scripthash = md5($scriptsrc);
234
- $scriptsrc = preg_replace('/\x{EF}\x{BB}\x{BF}/','',$scriptsrc);
235
- $scriptsrc = rtrim($scriptsrc,";\n\t\r").';';
236
-
237
- //Add try-catch?
238
- if($this->trycatch) {
239
- $scriptsrc = 'try{'.$scriptsrc.'}catch(e){}';
240
  }
241
  $tmpscriptsrc = apply_filters( 'autoptimize_js_individual_script', $scriptsrc, $script );
242
- if ( has_filter('autoptimize_js_individual_script') && !empty($tmpscriptsrc) ) {
243
- $scriptsrc=$tmpscriptsrc;
244
- $this->alreadyminified=true;
245
- } else if ($this->can_inject_late($script)) {
246
- $scriptsrc="/*!%%INJECTLATER".AUTOPTIMIZE_HASH."%%".base64_encode($script)."|".$scripthash."%%INJECTLATER%%*/";
247
  }
248
- $this->jscode .= "\n".$scriptsrc;
249
  }/*else{
250
  //Couldn't read JS. Maybe getpath isn't working?
251
  }*/
252
  }
253
  }
254
 
255
- //Check for already-minified code
256
- $this->md5hash = md5($this->jscode);
257
- $ccheck = new autoptimizeCache($this->md5hash,'js');
258
- if($ccheck->check()) {
259
  $this->jscode = $ccheck->retrieve();
260
  return true;
261
  }
262
- unset($ccheck);
263
-
264
- //$this->jscode has all the uncompressed code now.
265
- if ($this->alreadyminified!==true) {
266
- if (class_exists('JSMin') && apply_filters( 'autoptimize_js_do_minify' , true)) {
267
- if (@is_callable(array("JSMin","minify"))) {
268
- $tmp_jscode = trim(JSMin::minify($this->jscode));
269
- if (!empty($tmp_jscode)) {
270
  $this->jscode = $tmp_jscode;
271
- unset($tmp_jscode);
272
  }
273
- $this->jscode = $this->inject_minified($this->jscode);
274
  $this->jscode = apply_filters( 'autoptimize_js_after_minify', $this->jscode );
275
  return true;
276
  } else {
277
- $this->jscode = $this->inject_minified($this->jscode);
278
  return false;
279
  }
280
- } else {
281
- $this->jscode = $this->inject_minified($this->jscode);
282
- return false;
283
- }
284
  }
 
285
  $this->jscode = apply_filters( 'autoptimize_js_after_minify', $this->jscode );
286
  return true;
287
  }
288
-
289
- //Caches the JS in uncompressed, deflated and gzipped form.
290
- public function cache() {
291
- $cache = new autoptimizeCache($this->md5hash,'js');
292
- if(!$cache->check()) {
293
- //Cache our code
294
- $cache->cache($this->jscode,'text/javascript');
 
295
  }
296
- $this->url = AUTOPTIMIZE_CACHE_URL.$cache->getname();
297
  $this->url = $this->url_replace_cdn($this->url);
298
  }
299
-
300
  // Returns the content
301
- public function getcontent() {
 
302
  // Restore the full content
303
- if(!empty($this->restofcontent)) {
304
  $this->content .= $this->restofcontent;
305
  $this->restofcontent = '';
306
  }
307
-
308
  // Add the scripts taking forcehead/ deferred (default) into account
309
- if($this->forcehead == true) {
310
- $replaceTag=array("</head>","before");
311
- $defer="";
312
  } else {
313
- $replaceTag=array("</body>","before");
314
- $defer="defer ";
315
  }
316
-
317
  $defer = apply_filters( 'autoptimize_filter_js_defer', $defer );
318
- $bodyreplacementpayload = '<script type="text/javascript" '.$defer.'src="'.$this->url.'"></script>';
319
- $bodyreplacementpayload = apply_filters('autoptimize_filter_js_bodyreplacementpayload',$bodyreplacementpayload);
320
 
321
- $bodyreplacement = implode('',$this->move['first']);
 
 
 
322
  $bodyreplacement .= $bodyreplacementpayload;
323
- $bodyreplacement .= implode('',$this->move['last']);
324
 
325
  $replaceTag = apply_filters( 'autoptimize_filter_js_replacetag', $replaceTag );
326
 
327
- if (strlen($this->jscode)>0) {
328
- $this->inject_in_html($bodyreplacement,$replaceTag);
329
  }
330
-
331
- // restore comments
332
- $this->content = $this->restore_comments($this->content);
333
 
334
- // Restore IE hacks
335
- $this->content = $this->restore_iehacks($this->content);
336
-
337
- // Restore noptimize
338
- $this->content = $this->restore_noptimize($this->content);
339
 
340
- // Return the modified HTML
 
 
 
341
  return $this->content;
342
  }
343
-
344
  // Checks against the white- and blacklists
345
- private function ismergeable($tag) {
346
- if (apply_filters('autoptimize_filter_js_dontaggregate',false)) {
347
- return false;
348
- } else if (!empty($this->whitelist)) {
349
- foreach ($this->whitelist as $match) {
350
- if(strpos($tag,$match)!==false) {
 
 
 
351
  return true;
352
  }
353
  }
@@ -355,93 +434,69 @@ class autoptimizeScripts extends autoptimizeBase {
355
  return false;
356
  } else {
357
  foreach($this->domove as $match) {
358
- if(strpos($tag,$match)!==false) {
359
  // Matched something
360
  return false;
361
  }
362
  }
363
-
364
- if ($this->movetolast($tag)) {
365
  return false;
366
  }
367
-
368
- foreach($this->dontmove as $match) {
369
- if(strpos($tag,$match)!==false) {
370
- //Matched something
371
  return false;
372
  }
373
  }
374
-
375
  // If we're here it's safe to merge
376
  return true;
377
  }
378
  }
379
-
380
- // Checks againstt the blacklist
381
- private function ismovable($tag) {
382
- if ($this->include_inline !== true || apply_filters('autoptimize_filter_js_unmovable',true)) {
 
383
  return false;
384
  }
385
-
386
- foreach($this->domove as $match) {
387
- if(strpos($tag,$match)!==false) {
388
  // Matched something
389
  return true;
390
  }
391
  }
392
-
393
- if ($this->movetolast($tag)) {
394
  return true;
395
  }
396
-
397
- foreach($this->dontmove as $match) {
398
- if(strpos($tag,$match)!==false) {
399
  // Matched something
400
  return false;
401
  }
402
  }
403
-
404
  // If we're here it's safe to move
405
  return true;
406
  }
407
-
408
- private function movetolast($tag) {
409
- foreach($this->domovelast as $match) {
410
- if(strpos($tag,$match)!==false) {
 
411
  // Matched, return true
412
  return true;
413
  }
414
  }
415
-
416
  // Should be in 'first'
417
  return false;
418
  }
419
-
420
- /**
421
- * Determines wheter a <script> $tag should be aggregated or not.
422
- *
423
- * We consider these as "aggregation-safe" currently:
424
- * - script tags without a `type` attribute
425
- * - script tags with an explicit `type` of `text/javascript`, 'text/ecmascript',
426
- * 'application/javascript' or 'application/ecmascript'
427
- *
428
- * Everything else should return false.
429
- *
430
- * @param string $tag
431
- * @return bool
432
- *
433
- * original function by https://github.com/zytzagoo/ on his AO fork, thanks Tomas!
434
- */
435
- public function should_aggregate($tag) {
436
- preg_match('#<(script[^>]*)>#i',$tag,$scripttag);
437
- if ( strpos($scripttag[1], 'type')===false ) {
438
- return true;
439
- } else if ( preg_match('/type\s*=\s*["\']?(?:text|application)\/(?:javascript|ecmascript)["\']?/i', $scripttag[1]) ) {
440
- return true;
441
- } else {
442
- return false;
443
- }
444
- }
445
 
446
  /**
447
  * Determines wheter a <script> $tag can be excluded from minification (as already minified) based on:
@@ -449,21 +504,63 @@ class autoptimizeScripts extends autoptimizeBase {
449
  * - filename ending in `min.js`
450
  * - filename matching `js/jquery/jquery.js` (wordpress core jquery, is minified)
451
  * - filename matching one passed in the consider minified filter
452
- *
453
  * @param string $jsPath
454
  * @return bool
455
- */
456
- private function can_inject_late($jsPath) {
457
- $consider_minified_array = apply_filters('autoptimize_filter_js_consider_minified',false);
458
- if ( $this->inject_min_late !== true ) {
459
  // late-inject turned off
460
  return false;
461
- } else if ( (strpos($jsPath,"min.js") === false) && ( strpos($jsPath,"wp-includes/js/jquery/jquery.js") === false ) && ( str_replace($consider_minified_array, '', $jsPath) === $jsPath ) ) {
462
- // file not minified based on filename & filter
463
- return false;
464
  } else {
465
  // phew, all is safe, we can late-inject
466
  return true;
467
  }
468
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
469
  }
1
  <?php
 
2
 
3
+ if ( ! defined( 'ABSPATH' ) ) {
4
+ exit;
5
+ }
6
+
7
+ class autoptimizeScripts extends autoptimizeBase
8
+ {
9
  private $scripts = array();
10
+ private $move = array(
11
+ 'first' => array(),
12
+ 'last' => array()
13
+ );
14
+
15
+ private $dontmove = array(
16
+ 'document.write','html5.js','show_ads.js','google_ad','histats.com/js','statcounter.com/counter/counter.js',
17
+ 'ws.amazon.com/widgets','media.fastclick.net','/ads/','comment-form-quicktags/quicktags.php','edToolbar',
18
+ 'intensedebate.com','scripts.chitika.net/','_gaq.push','jotform.com/','admin-bar.min.js','GoogleAnalyticsObject',
19
+ 'plupload.full.min.js','syntaxhighlighter','adsbygoogle','gist.github.com','_stq','nonce','post_id','data-noptimize'
20
+ ,'logHuman'
21
+ );
22
+ private $domove = array(
23
+ 'gaJsHost','load_cmc','jd.gallery.transitions.js','swfobject.embedSWF(','tiny_mce.js','tinyMCEPreInit.go'
24
+ );
25
+ private $domovelast = array(
26
+ 'addthis.com','/afsonline/show_afs_search.js','disqus.js','networkedblogs.com/getnetworkwidget','infolinks.com/js/',
27
+ 'jd.gallery.js.php','jd.gallery.transitions.js','swfobject.embedSWF(','linkwithin.com/widget.js','tiny_mce.js','tinyMCEPreInit.go'
28
+ );
29
+
30
+ private $aggregate = true;
31
+ private $trycatch = false;
32
  private $alreadyminified = false;
33
+ private $forcehead = true;
34
+ private $include_inline = false;
35
+ private $jscode = '';
36
+ private $url = '';
37
+ private $restofcontent = '';
38
+ private $md5hash = '';
39
+ private $whitelist = '';
40
+ private $jsremovables = array();
 
41
  private $inject_min_late = '';
42
+
43
+ // Reads the page and collects script tags
44
+ public function read($options)
45
+ {
46
  $noptimizeJS = apply_filters( 'autoptimize_filter_js_noptimize', false, $this->content );
47
+ if ( $noptimizeJS ) {
48
+ return false;
49
+ }
50
 
51
  // only optimize known good JS?
52
  $whitelistJS = apply_filters( 'autoptimize_filter_js_whitelist', '', $this->content );
53
+ if ( ! empty( $whitelistJS ) ) {
54
+ $this->whitelist = array_filter( array_map( 'trim', explode( ',', $whitelistJS ) ) );
55
  }
56
 
57
  // is there JS we should simply remove
58
  $removableJS = apply_filters( 'autoptimize_filter_js_removables', '', $this->content );
59
  if (!empty($removableJS)) {
60
+ $this->jsremovables = array_filter( array_map( 'trim', explode( ',', $removableJS ) ) );
61
  }
62
 
63
  // only header?
64
+ if ( apply_filters( 'autoptimize_filter_js_justhead', $options['justhead'] ) ) {
65
+ $content = explode( '</head>', $this->content, 2 );
66
+ $this->content = $content[0] . '</head>';
67
  $this->restofcontent = $content[1];
68
  }
69
+
70
+ // Determine whether we're doing JS-files aggregation or not.
71
+ if ( ! $options['aggregate'] ) {
72
+ $this->aggregate = false;
73
+ }
74
+ // Returning true for "dontaggregate" turns off aggregation.
75
+ if ( $this->aggregate && apply_filters( 'autoptimize_filter_js_dontaggregate', false ) ) {
76
+ $this->aggregate = false;
77
+ }
78
+
79
  // include inline?
80
+ if ( apply_filters( 'autoptimize_js_include_inline', $options['include_inline'] ) ) {
81
  $this->include_inline = true;
82
  }
83
 
84
  // filter to "late inject minified JS", default to true for now (it is faster)
85
+ $this->inject_min_late = apply_filters( 'autoptimize_filter_js_inject_min_late', true );
86
 
87
  // filters to override hardcoded do(nt)move(last) array contents (array in, array out!)
88
+ $this->dontmove = apply_filters( 'autoptimize_filter_js_dontmove', $this->dontmove );
89
  $this->domovelast = apply_filters( 'autoptimize_filter_js_movelast', $this->domovelast );
90
  $this->domove = apply_filters( 'autoptimize_filter_js_domove', $this->domove );
91
 
92
  // get extra exclusions settings or filter
93
  $excludeJS = $options['js_exclude'];
94
  $excludeJS = apply_filters( 'autoptimize_filter_js_exclude', $excludeJS, $this->content );
95
+
96
+ if ( '' !== $excludeJS ) {
97
+ if ( is_array( $excludeJS ) ) {
98
+ if ( ( $removeKeys = array_keys( $excludeJS, 'remove' ) ) !== false ) {
99
+ foreach ( $removeKeys as $removeKey ) {
100
+ unset( $excludeJS[$removeKey] );
101
+ $this->jsremovables[] = $removeKey;
102
  }
103
  }
104
+ $exclJSArr = array_keys( $excludeJS );
105
  } else {
106
+ $exclJSArr = array_filter( array_map( 'trim', explode( ',', $excludeJS ) ) );
107
  }
108
+ $this->dontmove = array_merge( $exclJSArr, $this->dontmove );
109
  }
110
 
111
+ // Should we add try-catch?
112
+ if ( $options['trycatch'] ) {
113
  $this->trycatch = true;
114
+ }
115
 
116
+ // force js in head?
117
+ if ( $options['forcehead'] ) {
118
  $this->forcehead = true;
119
  } else {
120
  $this->forcehead = false;
121
  }
122
+
123
  $this->forcehead = apply_filters( 'autoptimize_filter_js_forcehead', $this->forcehead );
124
 
125
  // get cdn url
126
  $this->cdn_url = $options['cdn_url'];
127
+
128
  // noptimize me
129
  $this->content = $this->hide_noptimize($this->content);
130
 
135
  $this->content = $this->hide_comments($this->content);
136
 
137
  // Get script files
138
+ if ( preg_match_all( '#<script.*</script>#Usmi', $this->content, $matches ) ) {
139
+ foreach( $matches[0] as $tag ) {
140
  // only consider script aggregation for types whitelisted in should_aggregate-function
141
+ $should_aggregate = $this->should_aggregate($tag);
142
+ if ( ! $should_aggregate ) {
143
+ $tag = '';
144
  continue;
145
  }
146
 
147
+ if ( preg_match( '#<script[^>]*src=("|\')([^>]*)("|\')#Usmi', $tag, $source ) ) {
148
  // non-inline script
149
+ if ( $this->isremovable($tag, $this->jsremovables) ) {
150
+ $this->content = str_replace( $tag, '', $this->content );
151
  continue;
152
  }
153
+
154
+ $origTag = null;
155
+ $url = current( explode( '?', $source[2], 2 ) );
156
  $path = $this->getpath($url);
157
+ if ( false !== $path && preg_match( '#\.js$#', $path ) && $this->ismergeable($tag) ) {
158
  // ok to optimize, add to array
159
  $this->scripts[] = $path;
160
  } else {
161
  $origTag = $tag;
162
+ $newTag = $tag;
163
+
164
  // non-mergeable script (excluded or dynamic or external)
165
+ if ( is_array( $excludeJS ) ) {
166
  // should we add flags?
167
+ foreach ( $excludeJS as $exclTag => $exclFlags) {
168
+ if ( false !== strpos( $origTag, $exclTag ) && in_array( $exclFlags, array( 'async', 'defer' ) ) ) {
169
+ $newTag = str_replace( '<script ', '<script ' . $exclFlags . ' ', $newTag );
170
  }
171
  }
172
  }
173
+
174
+ // Should we minify the non-aggregated script?
175
+ if ( $path && apply_filters( 'autoptimize_filter_js_minify_excluded', true, $url ) ) {
176
+ $minified_url = $this->minify_single( $path );
177
+ // replace orig URL with minified URL from cache if so
178
+ if ( ! empty( $minified_url ) ) {
179
+ $newTag = str_replace( $url, $minified_url, $newTag );
180
+ }
181
+
182
+ // remove querystring from URL in newTag
183
+ if ( ! empty( $explUrl[1] ) ) {
184
+ $newTag = str_replace( '?' . $explUrl[1], '', $newTag );
185
+ }
186
+ }
187
+
188
+ if ( $this->ismovable($newTag) ) {
 
 
189
  // can be moved, flags and all
190
+ if ( $this->movetolast($newTag) ) {
191
  $this->move['last'][] = $newTag;
192
  } else {
193
  $this->move['first'][] = $newTag;
195
  } else {
196
  // cannot be moved, so if flag was added re-inject altered tag immediately
197
  if ( $origTag !== $newTag ) {
198
+ $this->content = str_replace( $origTag, $newTag, $this->content );
199
  $origTag = '';
200
  }
201
  // and forget about the $tag (not to be touched any more)
204
  }
205
  } else {
206
  // Inline script
207
+ if ( $this->isremovable($tag, $this->jsremovables) ) {
208
+ $this->content = str_replace( $tag, '', $this->content );
209
  continue;
210
  }
211
+
212
  // unhide comments, as javascript may be wrapped in comment-tags for old times' sake
213
  $tag = $this->restore_comments($tag);
214
+ if ( $this->ismergeable($tag) && $this->include_inline ) {
215
+ preg_match( '#<script.*>(.*)</script>#Usmi', $tag , $code );
216
+ $code = preg_replace('#.*<!\[CDATA\[(?:\s*\*/)?(.*)(?://|/\*)\s*?\]\]>.*#sm', '$1', $code[1] );
217
+ $code = preg_replace('/(?:^\\s*<!--\\s*|\\s*(?:\\/\\/)?\\s*-->\\s*$)/', '', $code );
218
+ $this->scripts[] = 'INLINE;' . $code;
219
  } else {
220
  // Can we move this?
221
  $autoptimize_js_moveable = apply_filters( 'autoptimize_js_moveable', '', $tag );
222
+ if ( $this->ismovable($tag) || '' !== $autoptimize_js_moveable ) {
223
+ if ( $this->movetolast($tag) || 'last' === $autoptimize_js_moveable ) {
224
  $this->move['last'][] = $tag;
225
  } else {
226
  $this->move['first'][] = $tag;
227
  }
228
  } else {
229
+ // We shouldn't touch this
230
  $tag = '';
231
  }
232
  }
233
+ // Re-hide comments to be able to do the removal based on tag from $this->content
234
  $tag = $this->hide_comments($tag);
235
  }
236
+
237
  //Remove the original script tag
238
+ $this->content = str_replace( $tag, '', $this->content );
239
  }
240
+
241
  return true;
242
  }
243
+
244
  // No script files, great ;-)
245
  return false;
246
  }
247
+
248
+ /**
249
+ * Determines wheter a certain `<script>` $tag should be aggregated or not.
250
+ *
251
+ * We consider these as "aggregation-safe" currently:
252
+ * - script tags without a `type` attribute
253
+ * - script tags with these `type` attribute values: `text/javascript`, `text/ecmascript`, `application/javascript`,
254
+ * and `application/ecmascript`
255
+ *
256
+ * Everything else should return false.
257
+ *
258
+ * @link https://developer.mozilla.org/en/docs/Web/HTML/Element/script#attr-type
259
+ *
260
+ * @param string $tag
261
+ * @return bool
262
+ */
263
+ public function should_aggregate($tag)
264
+ {
265
+ // We're only interested in the type attribute of the <script> tag itself, not any possible
266
+ // inline code that might just contain the 'type=' string...
267
+ $tag_parts = array();
268
+ preg_match( '#<(script[^>]*)>#i', $tag, $tag_parts);
269
+ $tag_without_contents = null;
270
+ if ( ! empty( $tag_parts[1] ) ) {
271
+ $tag_without_contents = $tag_parts[1];
272
+ }
273
+
274
+ $has_type = ( strpos( $tag_without_contents, 'type' ) !== false );
275
+
276
+ $type_valid = false;
277
+ if ( $has_type ) {
278
+ $type_valid = (bool) preg_match( '/type\s*=\s*[\'"]?(?:text|application)\/(?:javascript|ecmascript)[\'"]?/i', $tag_without_contents );
279
+ }
280
+
281
+ $should_aggregate = false;
282
+ if ( ! $has_type || $type_valid ) {
283
+ $should_aggregate = true;
284
+ }
285
+
286
+ return $should_aggregate;
287
+ }
288
+
289
  //Joins and optimizes JS
290
+ public function minify()
291
+ {
292
+ foreach ( $this->scripts as $script ) {
293
+ // TODO/FIXME: some duplicate code here, can be reduced/simplified
294
+ if ( preg_match( '#^INLINE;#', $script ) ) {
295
+ // Inline script
296
+ $script = preg_replace( '#^INLINE;#', '', $script );
297
  $script = rtrim( $script, ";\n\t\r" ) . ';';
298
+ // Add try-catch?
299
+ if ( $this->trycatch ) {
300
+ $script = 'try{' . $script . '}catch(e){}';
301
  }
302
  $tmpscript = apply_filters( 'autoptimize_js_individual_script', $script, '' );
303
+ if ( has_filter( 'autoptimize_js_individual_script' ) && ! empty( $tmpscript ) ) {
304
+ $script = $tmpscript;
305
+ $this->alreadyminified = true;
306
  }
307
  $this->jscode .= "\n" . $script;
308
  } else {
309
+ // External script
310
+ if ( false !== $script && file_exists( $script ) && is_readable( $script ) ) {
311
+ $scriptsrc = file_get_contents( $script );
312
+ $scriptsrc = preg_replace( '/\x{EF}\x{BB}\x{BF}/', '', $scriptsrc );
313
+ $scriptsrc = rtrim( $scriptsrc, ";\n\t\r" ) . ';';
314
+ // Add try-catch?
315
+ if ( $this->trycatch ) {
316
+ $scriptsrc = 'try{' . $scriptsrc . '}catch(e){}';
 
 
317
  }
318
  $tmpscriptsrc = apply_filters( 'autoptimize_js_individual_script', $scriptsrc, $script );
319
+ if ( has_filter( 'autoptimize_js_individual_script' ) && ! empty( $tmpscriptsrc ) ) {
320
+ $scriptsrc = $tmpscriptsrc;
321
+ $this->alreadyminified = true;
322
+ } else if ( $this->can_inject_late($script) ) {
323
+ $scriptsrc = self::build_injectlater_marker($script, md5($scriptsrc));
324
  }
325
+ $this->jscode .= "\n" . $scriptsrc;
326
  }/*else{
327
  //Couldn't read JS. Maybe getpath isn't working?
328
  }*/
329
  }
330
  }
331
 
332
+ // Check for already-minified code
333
+ $this->md5hash = md5( $this->jscode );
334
+ $ccheck = new autoptimizeCache($this->md5hash, 'js');
335
+ if ( $ccheck->check() ) {
336
  $this->jscode = $ccheck->retrieve();
337
  return true;
338
  }
339
+ unset( $ccheck );
340
+
341
+ // $this->jscode has all the uncompressed code now.
342
+ if ( true !== $this->alreadyminified ) {
343
+ if ( apply_filters( 'autoptimize_js_do_minify', true ) ) {
344
+ $tmp_jscode = trim( JSMin::minify( $this->jscode ) );
345
+ if ( ! empty( $tmp_jscode ) ) {
 
346
  $this->jscode = $tmp_jscode;
347
+ unset( $tmp_jscode );
348
  }
349
+ $this->jscode = $this->inject_minified( $this->jscode );
350
  $this->jscode = apply_filters( 'autoptimize_js_after_minify', $this->jscode );
351
  return true;
352
  } else {
353
+ $this->jscode = $this->inject_minified( $this->jscode );
354
  return false;
355
  }
 
 
 
 
356
  }
357
+
358
  $this->jscode = apply_filters( 'autoptimize_js_after_minify', $this->jscode );
359
  return true;
360
  }
361
+
362
+ // Caches the JS in uncompressed, deflated and gzipped form.
363
+ public function cache()
364
+ {
365
+ $cache = new autoptimizeCache($this->md5hash, 'js');
366
+ if ( ! $cache->check() ) {
367
+ // Cache our code
368
+ $cache->cache($this->jscode, 'text/javascript');
369
  }
370
+ $this->url = AUTOPTIMIZE_CACHE_URL . $cache->getname();
371
  $this->url = $this->url_replace_cdn($this->url);
372
  }
373
+
374
  // Returns the content
375
+ public function getcontent()
376
+ {
377
  // Restore the full content
378
+ if ( ! empty( $this->restofcontent ) ) {
379
  $this->content .= $this->restofcontent;
380
  $this->restofcontent = '';
381
  }
382
+
383
  // Add the scripts taking forcehead/ deferred (default) into account
384
+ if ( $this->forcehead ) {
385
+ $replaceTag = array( '</head>', 'before' );
386
+ $defer = '';
387
  } else {
388
+ $replaceTag = array( '</body>', 'before' );
389
+ $defer = 'defer ';
390
  }
391
+
392
  $defer = apply_filters( 'autoptimize_filter_js_defer', $defer );
 
 
393
 
394
+ $bodyreplacementpayload = '<script type="text/javascript" ' . $defer . 'src="' . $this->url . '"></script>';
395
+ $bodyreplacementpayload = apply_filters( 'autoptimize_filter_js_bodyreplacementpayload', $bodyreplacementpayload );
396
+
397
+ $bodyreplacement = implode( '', $this->move['first'] );
398
  $bodyreplacement .= $bodyreplacementpayload;
399
+ $bodyreplacement .= implode( '', $this->move['last'] );
400
 
401
  $replaceTag = apply_filters( 'autoptimize_filter_js_replacetag', $replaceTag );
402
 
403
+ if ( strlen( $this->jscode ) > 0 ) {
404
+ $this->inject_in_html( $bodyreplacement, $replaceTag );
405
  }
 
 
 
406
 
407
+ // Restore comments.
408
+ $this->content = $this->restore_comments( $this->content );
409
+
410
+ // Restore IE hacks.
411
+ $this->content = $this->restore_iehacks( $this->content );
412
 
413
+ // Restore noptimize.
414
+ $this->content = $this->restore_noptimize( $this->content );
415
+
416
+ // Return the modified HTML.
417
  return $this->content;
418
  }
419
+
420
  // Checks against the white- and blacklists
421
+ private function ismergeable($tag)
422
+ {
423
+ if ( ! $this->aggregate ) {
424
+ return false;
425
+ }
426
+
427
+ if ( ! empty( $this->whitelist ) ) {
428
+ foreach ( $this->whitelist as $match ) {
429
+ if (false !== strpos( $tag, $match ) ) {
430
  return true;
431
  }
432
  }
434
  return false;
435
  } else {
436
  foreach($this->domove as $match) {
437
+ if ( false !== strpos( $tag, $match ) ) {
438
  // Matched something
439
  return false;
440
  }
441
  }
442
+
443
+ if ( $this->movetolast($tag) ) {
444
  return false;
445
  }
446
+
447
+ foreach( $this->dontmove as $match ) {
448
+ if ( false !== strpos( $tag, $match ) ) {
449
+ // Matched something
450
  return false;
451
  }
452
  }
453
+
454
  // If we're here it's safe to merge
455
  return true;
456
  }
457
  }
458
+
459
+ // Checks agains the blacklist
460
+ private function ismovable($tag)
461
+ {
462
+ if ( true !== $this->include_inline || apply_filters( 'autoptimize_filter_js_unmovable', true ) ) {
463
  return false;
464
  }
465
+
466
+ foreach ( $this->domove as $match ) {
467
+ if ( false !== strpos( $tag, $match ) ) {
468
  // Matched something
469
  return true;
470
  }
471
  }
472
+
473
+ if ( $this->movetolast($tag) ) {
474
  return true;
475
  }
476
+
477
+ foreach ( $this->dontmove as $match ) {
478
+ if ( false !== strpos( $tag, $match ) ) {
479
  // Matched something
480
  return false;
481
  }
482
  }
483
+
484
  // If we're here it's safe to move
485
  return true;
486
  }
487
+
488
+ private function movetolast($tag)
489
+ {
490
+ foreach ( $this->domovelast as $match ) {
491
+ if ( false !== strpos( $tag, $match ) ) {
492
  // Matched, return true
493
  return true;
494
  }
495
  }
496
+
497
  // Should be in 'first'
498
  return false;
499
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
500
 
501
  /**
502
  * Determines wheter a <script> $tag can be excluded from minification (as already minified) based on:
504
  * - filename ending in `min.js`
505
  * - filename matching `js/jquery/jquery.js` (wordpress core jquery, is minified)
506
  * - filename matching one passed in the consider minified filter
507
+ *
508
  * @param string $jsPath
509
  * @return bool
510
+ */
511
+ private function can_inject_late($jsPath) {
512
+ $consider_minified_array = apply_filters( 'autoptimize_filter_js_consider_minified', false );
513
+ if ( true !== $this->inject_min_late ) {
514
  // late-inject turned off
515
  return false;
516
+ } else if ( ( false === strpos( $jsPath, 'min.js' ) ) && ( false === strpos( $jsPath, 'wp-includes/js/jquery/jquery.js' ) ) && ( str_replace( $consider_minified_array, '', $jsPath ) === $jsPath ) ) {
517
+ // file not minified based on filename & filter
518
+ return false;
519
  } else {
520
  // phew, all is safe, we can late-inject
521
  return true;
522
  }
523
  }
524
+
525
+ /**
526
+ * Returns whether we're doing aggregation or not.
527
+ *
528
+ * @return bool
529
+ */
530
+ public function aggregating()
531
+ {
532
+ return $this->aggregate;
533
+ }
534
+
535
+ /**
536
+ * Minifies a single local js file and returns its (cached) url.
537
+ *
538
+ * @param string $filepath Filepath.
539
+ * @param bool $cache_miss Optional. Force a cache miss. Default false.
540
+ *
541
+ * @return bool|string Url pointing to the minified js file or false.
542
+ */
543
+ public function minify_single( $filepath, $cache_miss = false )
544
+ {
545
+ $contents = $this->prepare_minify_single( $filepath );
546
+
547
+ if ( empty( $contents ) ) {
548
+ return false;
549
+ }
550
+
551
+ // Check cache.
552
+ $hash = 'single_' . md5( $contents );
553
+ $cache = new autoptimizeCache( $hash, 'js' );
554
+
555
+ // If not in cache already, minify...
556
+ if ( ! $cache->check() || $cache_miss ) {
557
+ $contents = trim( JSMin::minify( $contents ) );
558
+ // Store in cache.
559
+ $cache->cache( $contents, 'text/javascript' );
560
+ }
561
+
562
+ $url = $this->build_minify_single_url( $cache );
563
+
564
+ return $url;
565
+ }
566
  }
classes/autoptimizeSpeedupper.php ADDED
@@ -0,0 +1,110 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Autoptimize SpeedUp; minify & cache each JS/ CSS separately
4
+ * new in Autoptimize 2.2
5
+ */
6
+
7
+ if ( ! defined( 'ABSPATH' ) ) {
8
+ exit;
9
+ }
10
+
11
+ class autoptimizeSpeedupper
12
+ {
13
+ public function __construct()
14
+ {
15
+ $this->add_hooks();
16
+ }
17
+
18
+ public function add_hooks()
19
+ {
20
+ if ( apply_filters( 'autoptimize_js_do_minify', true ) ) {
21
+ add_filter( 'autoptimize_js_individual_script', array( $this, 'js_snippetcacher' ), 10, 2 );
22
+ add_filter( 'autoptimize_js_after_minify', array( $this, 'js_cleanup' ), 10, 1 );
23
+ }
24
+ if ( apply_filters( 'autoptimize_css_do_minify', true ) ) {
25
+ add_filter( 'autoptimize_css_individual_style', array( $this, 'css_snippetcacher' ), 10, 2 );
26
+ add_filter( 'autoptimize_css_after_minify', array( $this, 'css_cleanup' ), 10, 1 );
27
+ }
28
+ }
29
+
30
+ public function js_snippetcacher( $jsin, $jsfilename )
31
+ {
32
+ $md5hash = 'snippet_' . md5( $jsin );
33
+ $ccheck = new autoptimizeCache( $md5hash, 'js' );
34
+ if ( $ccheck->check() ) {
35
+ $scriptsrc = $ccheck->retrieve();
36
+ } else {
37
+ if ( false === ( strpos( $jsfilename, 'min.js' ) ) && ( false === strpos( $jsfilename, 'js/jquery/jquery.js' ) ) && ( str_replace( apply_filters( 'autoptimize_filter_js_consider_minified', false ), '', $jsfilename ) === $jsfilename ) ) {
38
+ $tmp_jscode = trim( JSMin::minify( $jsin ) );
39
+ if ( ! empty( $tmp_jscode ) ) {
40
+ $scriptsrc = $tmp_jscode;
41
+ unset( $tmp_jscode );
42
+ } else {
43
+ $scriptsrc = $jsin;
44
+ }
45
+ } else {
46
+ // Removing comments, linebreaks and stuff!
47
+ $scriptsrc = preg_replace( '#^\s*\/\/.*$#Um', '', $jsin );
48
+ $scriptsrc = preg_replace( '#^\s*\/\*[^!].*\*\/\s?#Us', '', $scriptsrc );
49
+ $scriptsrc = preg_replace( "#(^[\r\n]*|[\r\n]+)[\s\t]*[\r\n]+#", "\n", $scriptsrc );
50
+ }
51
+
52
+ $last_char = substr( $scriptsrc, -1, 1 );
53
+ if ( ';' !== $last_char && '}' !== $last_char ) {
54
+ $scriptsrc .= ';';
55
+ }
56
+
57
+ if ( ! empty( $jsfilename ) && str_replace( apply_filters( 'autoptimize_filter_js_speedup_cache', false ), '', $jsfilename ) === $jsfilename ) {
58
+ // Don't cache inline CSS or if filter says no!
59
+ $ccheck->cache( $scriptsrc, 'text/javascript' );
60
+ }
61
+ }
62
+ unset( $ccheck );
63
+
64
+ return $scriptsrc;
65
+ }
66
+
67
+ public function css_snippetcacher( $cssin, $cssfilename )
68
+ {
69
+ $md5hash = 'snippet_' . md5( $cssin );
70
+ $ccheck = new autoptimizeCache( $md5hash, 'css' );
71
+ if ( $ccheck->check() ) {
72
+ $stylesrc = $ccheck->retrieve();
73
+ } else {
74
+ if ( ( false === strpos( $cssfilename, 'min.css' ) ) && ( str_replace( apply_filters( 'autoptimize_filter_css_consider_minified', false ), '', $cssfilename ) === $cssfilename ) ) {
75
+ $cssmin = new autoptimizeCSSmin();
76
+ $tmp_code = trim( $cssmin->run( $cssin ) );
77
+
78
+ if ( ! empty( $tmp_code ) ) {
79
+ $stylesrc = $tmp_code;
80
+ unset( $tmp_code );
81
+ } else {
82
+ $stylesrc = $cssin;
83
+ }
84
+ } else {
85
+ // .min.css -> no heavy-lifting, just some cleanup!
86
+ $stylesrc = preg_replace( '#^\s*\/\*[^!].*\*\/\s?#Us', '', $cssin );
87
+ $stylesrc = preg_replace( "#(^[\r\n]*|[\r\n]+)[\s\t]*[\r\n]+#", "\n", $stylesrc );
88
+ $stylesrc = autoptimizeStyles::fixurls( $cssfilename, $stylesrc );
89
+ }
90
+ if ( ! empty( $cssfilename ) && ( str_replace( apply_filters( 'autoptimize_filter_css_speedup_cache', false ), '', $cssfilename ) === $cssfilename ) ) {
91
+ // Only caching CSS if it's not inline and is allowed by filter!
92
+ $ccheck->cache( $stylesrc, 'text/css' );
93
+ }
94
+ }
95
+ unset( $ccheck );
96
+
97
+ return $stylesrc;
98
+ }
99
+
100
+ public function css_cleanup( $cssin )
101
+ {
102
+ // Speedupper results in aggregated CSS not being minified, so the filestart-marker AO adds when aggregating needs to be removed.
103
+ return trim( str_replace( array( '/*FILESTART*/', '/*FILESTART2*/' ), '', $cssin ) );
104
+ }
105
+
106
+ public function js_cleanup( $jsin )
107
+ {
108
+ return trim( $jsin );
109
+ }
110
+ }
classes/autoptimizeStyles.php CHANGED
@@ -1,701 +1,1070 @@
1
- <?php
2
- if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
3
-
4
- class autoptimizeStyles extends autoptimizeBase {
5
-
6
- const ASSETS_REGEX = '/url\s*\(\s*(?!["\']?data:)(?![\'|\"]?[\#|\%|])([^)]+)\s*\)([^;},\s]*)/i';
7
-
8
- private $css = array();
9
- private $csscode = array();
10
- private $url = array();
11
- private $restofcontent = '';
12
- private $datauris = false;
13
- private $hashmap = array();
14
- private $alreadyminified = false;
15
- private $inline = false;
16
- private $defer = false;
17
- private $defer_inline = false;
18
- private $whitelist = '';
19
- private $cssinlinesize = '';
20
- private $cssremovables = array();
21
- private $include_inline = false;
22
- private $inject_min_late = '';
23
-
24
- //Reads the page and collects style tags
25
- public function read($options) {
26
- $noptimizeCSS = apply_filters( 'autoptimize_filter_css_noptimize', false, $this->content );
27
- if ($noptimizeCSS) return false;
28
-
29
- $whitelistCSS = apply_filters( 'autoptimize_filter_css_whitelist', '', $this->content );
30
- if (!empty($whitelistCSS)) {
31
- $this->whitelist = array_filter(array_map('trim',explode(",",$whitelistCSS)));
32
- }
33
-
34
- $removableCSS = apply_filters( 'autoptimize_filter_css_removables','' );
35
- if (!empty($removableCSS)) {
36
- $this->cssremovables = array_filter(array_map('trim',explode(",",$removableCSS)));
37
- }
38
-
39
- $this->cssinlinesize = apply_filters('autoptimize_filter_css_inlinesize',256);
40
-
41
- // filter to "late inject minified CSS", default to true for now (it is faster)
42
- $this->inject_min_late = apply_filters('autoptimize_filter_css_inject_min_late',true);
43
-
44
- // Remove everything that's not the header
45
- if ( apply_filters('autoptimize_filter_css_justhead',$options['justhead']) == true ) {
46
- $content = explode('</head>',$this->content,2);
47
- $this->content = $content[0].'</head>';
48
- $this->restofcontent = $content[1];
49
- }
50
-
51
- // include inline?
52
- if( apply_filters('autoptimize_css_include_inline',$options['include_inline']) == true ) {
53
- $this->include_inline = true;
54
- }
55
-
56
- // what CSS shouldn't be autoptimized
57
- $excludeCSS = $options['css_exclude'];
58
- $excludeCSS = apply_filters( 'autoptimize_filter_css_exclude', $excludeCSS, $this->content );
59
- if ($excludeCSS!=="") {
60
- $this->dontmove = array_filter(array_map('trim',explode(",",$excludeCSS)));
61
- } else {
62
- $this->dontmove = array();
63
- }
64
-
65
- // forcefully exclude CSS with data-noptimize attrib
66
- $this->dontmove[]="data-noptimize";
67
-
68
- // should we defer css?
69
- // value: true/ false
70
- $this->defer = $options['defer'];
71
- $this->defer = apply_filters( 'autoptimize_filter_css_defer', $this->defer, $this->content );
72
-
73
- // should we inline while deferring?
74
- // value: inlined CSS
75
- $this->defer_inline = $options['defer_inline'];
76
- $this->defer_inline = apply_filters( 'autoptimize_filter_css_defer_inline', $this->defer_inline, $this->content );
77
-
78
- // should we inline?
79
- // value: true/ false
80
- $this->inline = $options['inline'];
81
- $this->inline = apply_filters( 'autoptimize_filter_css_inline', $this->inline, $this->content );
82
-
83
- // get cdn url
84
- $this->cdn_url = $options['cdn_url'];
85
-
86
- // Store data: URIs setting for later use
87
- $this->datauris = $options['datauris'];
88
-
89
- // noptimize me
90
- $this->content = $this->hide_noptimize($this->content);
91
-
92
- // exclude (no)script, as those may contain CSS which should be left as is
93
- if ( strpos( $this->content, '<script' ) !== false ) {
94
- $this->content = preg_replace_callback(
95
- '#<(?:no)?script.*?<\/(?:no)?script>#is',
96
- create_function(
97
- '$matches',
98
- 'return "%%SCRIPT".AUTOPTIMIZE_HASH."%%".base64_encode($matches[0])."%%SCRIPT%%";'
99
- ),
100
- $this->content
101
- );
102
- }
103
-
104
- // Save IE hacks
105
- $this->content = $this->hide_iehacks($this->content);
106
-
107
- // hide comments
108
- $this->content = $this->hide_comments($this->content);
109
-
110
- // Get <style> and <link>
111
- if(preg_match_all('#(<style[^>]*>.*</style>)|(<link[^>]*stylesheet[^>]*>)#Usmi',$this->content,$matches)) {
112
- foreach($matches[0] as $tag) {
113
- if ($this->isremovable($tag,$this->cssremovables)) {
114
- $this->content = str_replace($tag,'',$this->content);
115
- } else if ($this->ismovable($tag)) {
116
- // Get the media
117
- if(strpos($tag,'media=')!==false) {
118
- preg_match('#media=(?:"|\')([^>]*)(?:"|\')#Ui',$tag,$medias);
119
- $medias = explode(',',$medias[1]);
120
- $media = array();
121
- foreach($medias as $elem) {
122
- if (empty($elem)) { $elem="all"; }
123
- $media[] = $elem;
124
- }
125
- } else {
126
- // No media specified - applies to all
127
- $media = array('all');
128
- }
129
- $media = apply_filters( 'autoptimize_filter_css_tagmedia',$media,$tag );
130
-
131
- if(preg_match('#<link.*href=("|\')(.*)("|\')#Usmi',$tag,$source)) {
132
- // <link>
133
- $explUrl = explode('?',$source[2],2);
134
- $url = $explUrl[0];
135
- $path = $this->getpath($url);
136
-
137
- if($path!==false && preg_match('#\.css$#',$path)) {
138
- // Good link
139
- $this->css[] = array($media,$path);
140
- }else{
141
- // Link is dynamic (.php etc)
142
- $tag = '';
143
- }
144
- } else {
145
- // inline css in style tags can be wrapped in comment tags, so restore comments
146
- $tag = $this->restore_comments($tag);
147
- preg_match('#<style.*>(.*)</style>#Usmi',$tag,$code);
148
-
149
- // and re-hide them to be able to to the removal based on tag
150
- $tag = $this->hide_comments($tag);
151
-
152
- if ( $this->include_inline ) {
153
- $code = preg_replace('#^.*<!\[CDATA\[(?:\s*\*/)?(.*)(?://|/\*)\s*?\]\]>.*$#sm','$1',$code[1]);
154
- $this->css[] = array($media,'INLINE;'.$code);
155
- } else {
156
- $tag = '';
157
- }
158
- }
159
-
160
- // Remove the original style tag
161
- $this->content = str_replace($tag,'',$this->content);
162
- } else {
163
- // excluded CSS, minify if getpath
164
- if (preg_match('#<link.*href=("|\')(.*)("|\')#Usmi',$tag,$source)) {
165
- $explUrl = explode('?',$source[2],2);
166
- $url = $explUrl[0];
167
- $path = $this->getpath($url);
168
-
169
- if ($path && apply_filters('autoptimize_filter_css_minify_excluded',false)) {
170
- $_CachedMinifiedUrl = $this->minify_single($path);
171
-
172
- if (!empty($_CachedMinifiedUrl)) {
173
- // replace orig URL with URL to cache
174
- $newTag = str_replace($url, $_CachedMinifiedUrl, $tag);
175
- } else {
176
- $newTag = $tag;
177
- }
178
-
179
- // remove querystring from URL
180
- if ( !empty($explUrl[1]) ) {
181
- $newTag = str_replace("?".$explUrl[1],"",$newTag);
182
- }
183
-
184
- // and replace
185
- $this->content = str_replace($tag,$newTag,$this->content);
186
- }
187
- }
188
- }
189
- }
190
- return true;
191
- }
192
- // Really, no styles?
193
- return false;
194
- }
195
-
196
- // Joins and optimizes CSS
197
- public function minify() {
198
- foreach($this->css as $group) {
199
- list($media,$css) = $group;
200
- if(preg_match('#^INLINE;#',$css)) {
201
- // <style>
202
- $css = preg_replace('#^INLINE;#','',$css);
203
- $css = $this->fixurls(ABSPATH.'/index.php',$css);
204
- $tmpstyle = apply_filters( 'autoptimize_css_individual_style', $css, "" );
205
- if ( has_filter('autoptimize_css_individual_style') && !empty($tmpstyle) ) {
206
- $css=$tmpstyle;
207
- $this->alreadyminified=true;
208
- }
209
- } else {
210
- //<link>
211
- if($css !== false && file_exists($css) && is_readable($css)) {
212
- $cssPath = $css;
213
- $cssContents = file_get_contents($cssPath);
214
- $cssHash = md5($cssContents);
215
- $css = $this->fixurls($cssPath,$cssContents);
216
- $css = preg_replace('/\x{EF}\x{BB}\x{BF}/','',$css);
217
- $tmpstyle = apply_filters( 'autoptimize_css_individual_style', $css, $cssPath );
218
- if (has_filter('autoptimize_css_individual_style') && !empty($tmpstyle)) {
219
- $css=$tmpstyle;
220
- $this->alreadyminified=true;
221
- } else if ($this->can_inject_late($cssPath,$css)) {
222
- $css="/*!%%INJECTLATER%%".base64_encode($cssPath)."|".$cssHash."%%INJECTLATER%%*/";
223
- }
224
- } else {
225
- // Couldn't read CSS. Maybe getpath isn't working?
226
- $css = '';
227
- }
228
- }
229
-
230
- foreach($media as $elem) {
231
- if(!isset($this->csscode[$elem]))
232
- $this->csscode[$elem] = '';
233
- $this->csscode[$elem] .= "\n/*FILESTART*/".$css;
234
- }
235
- }
236
-
237
- // Check for duplicate code
238
- $md5list = array();
239
- $tmpcss = $this->csscode;
240
- foreach($tmpcss as $media => $code) {
241
- $md5sum = md5($code);
242
- $medianame = $media;
243
- foreach($md5list as $med => $sum) {
244
- // If same code
245
- if($sum === $md5sum) {
246
- //Add the merged code
247
- $medianame = $med.', '.$media;
248
- $this->csscode[$medianame] = $code;
249
- $md5list[$medianame] = $md5list[$med];
250
- unset($this->csscode[$med], $this->csscode[$media]);
251
- unset($md5list[$med]);
252
- }
253
- }
254
- $md5list[$medianame] = $md5sum;
255
- }
256
- unset($tmpcss);
257
-
258
- // Manage @imports, while is for recursive import management
259
- foreach ($this->csscode as &$thiscss) {
260
- // Flag to trigger import reconstitution and var to hold external imports
261
- $fiximports = false;
262
- $external_imports = "";
263
-
264
- // remove comments to avoid importing commented-out imports
265
- $thiscss_nocomments = preg_replace('#/\*.*\*/#Us','',$thiscss);
266
-
267
- while(preg_match_all('#@import +(?:url)?(?:(?:\\(([\"\']?)(?:[^\"\')]+)\\1\\)|([\"\'])(?:[^\"\']+)\\2)(?:[^,;\"\']+(?:,[^,;\"\']+)*)?)(?:;)#m',$thiscss_nocomments,$matches)) {
268
- foreach($matches[0] as $import) {
269
- if ($this->isremovable($import,$this->cssremovables)) {
270
- $thiscss = str_replace($import,'',$thiscss);
271
- $import_ok = true;
272
- } else {
273
- $url = trim(preg_replace('#^.*((?:https?:|ftp:)?//.*\.css).*$#','$1',trim($import))," \t\n\r\0\x0B\"'");
274
- $path = $this->getpath($url);
275
- $import_ok = false;
276
- if (file_exists($path) && is_readable($path)) {
277
- $code = addcslashes($this->fixurls($path,file_get_contents($path)),"\\");
278
- $code = preg_replace('/\x{EF}\x{BB}\x{BF}/','',$code);
279
- $tmpstyle = apply_filters( 'autoptimize_css_individual_style', $code, "" );
280
- if ( has_filter('autoptimize_css_individual_style') && !empty($tmpstyle)) {
281
- $code=$tmpstyle;
282
- $this->alreadyminified=true;
283
- } else if ($this->can_inject_late($path,$code)) {
284
- $code="/*!%%INJECTLATER".AUTOPTIMIZE_HASH."%%".base64_encode($path)."|".md5($code)."%%INJECTLATER%%*/";
285
- }
286
-
287
- if(!empty($code)) {
288
- $tmp_thiscss = preg_replace('#(/\*FILESTART\*/.*)'.preg_quote($import,'#').'#Us','/*FILESTART2*/'.$code.'$1',$thiscss);
289
- if (!empty($tmp_thiscss)) {
290
- $thiscss = $tmp_thiscss;
291
- $import_ok = true;
292
- unset($tmp_thiscss);
293
- }
294
- unset($code);
295
- }
296
- }
297
- }
298
-
299
- if (!$import_ok) {
300
- // external imports and general fall-back
301
- $external_imports .= $import;
302
- $thiscss = str_replace($import,'',$thiscss);
303
- $fiximports = true;
304
- }
305
- }
306
- $thiscss = preg_replace('#/\*FILESTART\*/#','',$thiscss);
307
- $thiscss = preg_replace('#/\*FILESTART2\*/#','/*FILESTART*/',$thiscss);
308
-
309
- // and update $thiscss_nocomments before going into next iteration in while loop
310
- $thiscss_nocomments=preg_replace('#/\*.*\*/#Us','',$thiscss);
311
- }
312
- unset($thiscss_nocomments);
313
-
314
- // add external imports to top of aggregated CSS
315
- if($fiximports) {
316
- $thiscss=$external_imports.$thiscss;
317
- }
318
- }
319
- unset($thiscss);
320
-
321
- // $this->csscode has all the uncompressed code now.
322
- foreach($this->csscode as &$code) {
323
- // Check for already-minified code
324
- $hash = md5($code);
325
- do_action( 'autoptimize_action_css_hash', $hash );
326
- $ccheck = new autoptimizeCache($hash,'css');
327
- if($ccheck->check()) {
328
- $code = $ccheck->retrieve();
329
- $this->hashmap[md5($code)] = $hash;
330
- continue;
331
- }
332
- unset($ccheck);
333
-
334
- // Do the imaging!
335
- $imgreplace = array();
336
- preg_match_all( self::ASSETS_REGEX, $code, $matches );
337
-
338
- if ( ($this->datauris == true) && (function_exists('base64_encode')) && (is_array($matches)) ) {
339
- foreach($matches[1] as $count => $quotedurl) {
340
- $iurl = trim($quotedurl," \t\n\r\0\x0B\"'");
341
-
342
- // if querystring, remove it from url
343
- if (strpos($iurl,'?') !== false) { $iurl = strtok($iurl,'?'); }
344
-
345
- $ipath = $this->getpath($iurl);
346
-
347
- $datauri_max_size = 4096;
348
- $datauri_max_size = (int) apply_filters( 'autoptimize_filter_css_datauri_maxsize', $datauri_max_size );
349
- $datauri_exclude = apply_filters( 'autoptimize_filter_css_datauri_exclude', "");
350
- if (!empty($datauri_exclude)) {
351
- $no_datauris=array_filter(array_map('trim',explode(",",$datauri_exclude)));
352
- foreach ($no_datauris as $no_datauri) {
353
- if (strpos($iurl,$no_datauri)!==false) {
354
- $ipath=false;
355
- break;
356
- }
357
- }
358
- }
359
-
360
- if($ipath != false && preg_match('#\.(jpe?g|png|gif|bmp)$#i',$ipath) && file_exists($ipath) && is_readable($ipath) && filesize($ipath) <= $datauri_max_size) {
361
- $ihash=md5($ipath);
362
- $icheck = new autoptimizeCache($ihash,'img');
363
- if($icheck->check()) {
364
- // we have the base64 image in cache
365
- $headAndData=$icheck->retrieve();
366
- $_base64data=explode(";base64,",$headAndData);
367
- $base64data=$_base64data[1];
368
- } else {
369
- // It's an image and we don't have it in cache, get the type
370
- $explA=explode('.',$ipath);
371
- $type=end($explA);
372
-
373
- switch($type) {
374
- case 'jpeg':
375
- $dataurihead = 'data:image/jpeg;base64,';
376
- break;
377
- case 'jpg':
378
- $dataurihead = 'data:image/jpeg;base64,';
379
- break;
380
- case 'gif':
381
- $dataurihead = 'data:image/gif;base64,';
382
- break;
383
- case 'png':
384
- $dataurihead = 'data:image/png;base64,';
385
- break;
386
- case 'bmp':
387
- $dataurihead = 'data:image/bmp;base64,';
388
- break;
389
- default:
390
- $dataurihead = 'data:application/octet-stream;base64,';
391
- }
392
-
393
- // Encode the data
394
- $base64data = base64_encode(file_get_contents($ipath));
395
- $headAndData=$dataurihead.$base64data;
396
-
397
- // Save in cache
398
- $icheck->cache($headAndData,"text/plain");
399
- }
400
- unset($icheck);
401
-
402
- // Add it to the list for replacement
403
- $imgreplace[$matches[0][$count]] = str_replace($quotedurl,$headAndData,$matches[0][$count]);
404
- } else {
405
- // just cdn the URL if applicable
406
- if (!empty($this->cdn_url)) {
407
- $imgreplace[$matches[0][$count]] = str_replace($quotedurl,$this->maybe_cdn_urls($quotedurl),$matches[0][$count]);
408
- }
409
- }
410
- }
411
- } else if ((is_array($matches)) && (!empty($this->cdn_url))) {
412
- // change urls to cdn-url
413
- foreach($matches[1] as $count => $quotedurl) {
414
- $imgreplace[$matches[0][$count]] = str_replace($quotedurl,$this->maybe_cdn_urls($quotedurl),$matches[0][$count]);
415
- }
416
- }
417
-
418
- if(!empty($imgreplace)) {
419
- $code = str_replace(array_keys($imgreplace),array_values($imgreplace),$code);
420
- }
421
-
422
- // Minify
423
- if (($this->alreadyminified!==true) && (apply_filters( "autoptimize_css_do_minify", true))) {
424
- if (class_exists('Minify_CSS_Compressor')) {
425
- $tmp_code = trim(Minify_CSS_Compressor::process($code));
426
- } else if(class_exists('CSSmin')) {
427
- $cssmin = new CSSmin();
428
- if (method_exists($cssmin,"run")) {
429
- $tmp_code = trim($cssmin->run($code));
430
- } elseif (@is_callable(array($cssmin,"minify"))) {
431
- $tmp_code = trim(CssMin::minify($code));
432
- }
433
- }
434
- if (!empty($tmp_code)) {
435
- $code = $tmp_code;
436
- unset($tmp_code);
437
- }
438
- }
439
-
440
- $code = $this->inject_minified($code);
441
-
442
- $tmp_code = apply_filters( 'autoptimize_css_after_minify', $code );
443
- if (!empty($tmp_code)) {
444
- $code = $tmp_code;
445
- unset($tmp_code);
446
- }
447
-
448
- $this->hashmap[md5($code)] = $hash;
449
- }
450
- unset($code);
451
- return true;
452
- }
453
-
454
- //Caches the CSS in uncompressed, deflated and gzipped form.
455
- public function cache() {
456
- // CSS cache
457
- foreach($this->csscode as $media => $code) {
458
- $md5 = $this->hashmap[md5($code)];
459
-
460
- $cache = new autoptimizeCache($md5,'css');
461
- if(!$cache->check()) {
462
- // Cache our code
463
- $cache->cache($code,'text/css');
464
- }
465
- $this->url[$media] = AUTOPTIMIZE_CACHE_URL.$cache->getname();
466
- }
467
- }
468
-
469
- //Returns the content
470
- public function getcontent() {
471
- // restore IE hacks
472
- $this->content = $this->restore_iehacks($this->content);
473
-
474
- // restore comments
475
- $this->content = $this->restore_comments($this->content);
476
-
477
- // restore (no)script
478
- if ( strpos( $this->content, '%%SCRIPT%%' ) !== false ) {
479
- $this->content = preg_replace_callback(
480
- '#%%SCRIPT'.AUTOPTIMIZE_HASH.'%%(.*?)%%SCRIPT%%#is',
481
- create_function(
482
- '$matches',
483
- 'return base64_decode($matches[1]);'
484
- ),
485
- $this->content
486
- );
487
- }
488
-
489
- // restore noptimize
490
- $this->content = $this->restore_noptimize($this->content);
491
-
492
- //Restore the full content
493
- if(!empty($this->restofcontent)) {
494
- $this->content .= $this->restofcontent;
495
- $this->restofcontent = '';
496
- }
497
-
498
- // Inject the new stylesheets
499
- $replaceTag = array("<title","before");
500
- $replaceTag = apply_filters( 'autoptimize_filter_css_replacetag', $replaceTag, $this->content );
501
-
502
- if ($this->inline == true) {
503
- foreach($this->csscode as $media => $code) {
504
- $this->inject_in_html('<style type="text/css" media="'.$media.'">'.$code.'</style>',$replaceTag);
505
- }
506
- } else {
507
- if ($this->defer == true) {
508
- $preloadCssBlock = "";
509
- $noScriptCssBlock = "<noscript id=\"aonoscrcss\">";
510
- $defer_inline_code=$this->defer_inline;
511
- if(!empty($defer_inline_code)){
512
- if ( apply_filters( 'autoptimize_filter_css_critcss_minify', true ) ) {
513
- $iCssHash = md5($defer_inline_code);
514
- $iCssCache = new autoptimizeCache($iCssHash,'css');
515
- if($iCssCache->check()) {
516
- // we have the optimized inline CSS in cache
517
- $defer_inline_code=$iCssCache->retrieve();
518
- } else {
519
- if (class_exists('Minify_CSS_Compressor')) {
520
- $tmp_code = trim(Minify_CSS_Compressor::process($defer_inline_code));
521
- } else if(class_exists('CSSmin')) {
522
- $cssmin = new CSSmin();
523
- $tmp_code = trim($cssmin->run($defer_inline_code));
524
- }
525
- if (!empty($tmp_code)) {
526
- $defer_inline_code = $tmp_code;
527
- $iCssCache->cache($defer_inline_code,"text/css");
528
- unset($tmp_code);
529
- }
530
- }
531
- }
532
- $code_out='<style type="text/css" id="aoatfcss" media="all">'.$defer_inline_code.'</style>';
533
- $this->inject_in_html($code_out,$replaceTag);
534
- }
535
- }
536
-
537
- foreach($this->url as $media => $url) {
538
- $url = $this->url_replace_cdn($url);
539
-
540
- //Add the stylesheet either deferred (import at bottom) or normal links in head
541
- if($this->defer == true) {
542
-
543
- // Filter to modify the onload attribute - passes value and the stylesheet url
544
- $preloadOnLoad = apply_filters('autoptimize_filter_css_preload_onload', "this.onload=null;this.rel='stylesheet'", $url);
545
-
546
- $preloadCssBlock .= '<link rel="preload" as="style" media="'.$media.'" href="'.$url.'" onload="'.$preloadOnLoad.'" />';
547
- $noScriptCssBlock .= '<link type="text/css" media="'.$media.'" href="'.$url.'" rel="stylesheet" />';
548
-
549
- } else {
550
- if (strlen($this->csscode[$media]) > $this->cssinlinesize) {
551
- $this->inject_in_html('<link type="text/css" media="'.$media.'" href="'.$url.'" rel="stylesheet" />',$replaceTag);
552
- } else if (strlen($this->csscode[$media])>0) {
553
- $this->inject_in_html('<style type="text/css" media="'.$media.'">'.$this->csscode[$media].'</style>',$replaceTag);
554
- }
555
- }
556
- }
557
-
558
- if($this->defer == true) {
559
- $preloadPolyfill = '<script data-cfasync=\'false\'>!function(t){"use strict";t.loadCSS||(t.loadCSS=function(){});var e=loadCSS.relpreload={};if(e.support=function(){var e;try{e=t.document.createElement("link").relList.supports("preload")}catch(t){e=!1}return function(){return e}}(),e.bindMediaToggle=function(t){function e(){t.media=a}var a=t.media||"all";t.addEventListener?t.addEventListener("load",e):t.attachEvent&&t.attachEvent("onload",e),setTimeout(function(){t.rel="stylesheet",t.media="only x"}),setTimeout(e,3e3)},e.poly=function(){if(!e.support())for(var a=t.document.getElementsByTagName("link"),n=0;n<a.length;n++){var o=a[n];"preload"!==o.rel||"style"!==o.getAttribute("as")||o.getAttribute("data-loadcss")||(o.setAttribute("data-loadcss",!0),e.bindMediaToggle(o))}},!e.support()){e.poly();var a=t.setInterval(e.poly,500);t.addEventListener?t.addEventListener("load",function(){e.poly(),t.clearInterval(a)}):t.attachEvent&&t.attachEvent("onload",function(){e.poly(),t.clearInterval(a)})}"undefined"!=typeof exports?exports.loadCSS=loadCSS:t.loadCSS=loadCSS}("undefined"!=typeof global?global:this);</script>';
560
- $noScriptCssBlock .= "</noscript>";
561
- $this->inject_in_html($preloadCssBlock.$noScriptCssBlock,$replaceTag);
562
-
563
- // Adds preload polyfill at end of body tag
564
- $this->inject_in_html(
565
- apply_filters('autoptimize_css_preload_polyfill', $preloadPolyfill),
566
- array('</body>','before')
567
- );
568
- }
569
- }
570
-
571
- //Return the modified stylesheet
572
- return $this->content;
573
- }
574
-
575
- static function fixurls($file, $code) {
576
- // Switch all imports to the url() syntax
577
- $code = preg_replace( '#@import ("|\')(.+?)\.css.*?("|\')#', '@import url("${2}.css")', $code );
578
-
579
- if ( preg_match_all( self::ASSETS_REGEX, $code, $matches ) ) {
580
- $file = str_replace( WP_ROOT_DIR, '/', $file );
581
- $dir = dirname( $file ); // Like /themes/expound/css
582
-
583
- // $dir should not contain backslashes, since it's used to replace
584
- // urls, but it can contain them when running on Windows because
585
- // fixurls() is sometimes called with `ABSPATH . 'index.php'`
586
- $dir = str_replace( '\\', '/', $dir );
587
- unset( $file ); // not used below at all
588
-
589
- $replace = array();
590
- foreach ( $matches[1] as $k => $url ) {
591
- // Remove quotes
592
- $url = trim( $url," \t\n\r\0\x0B\"'" );
593
- $noQurl = trim( $url, "\"'" );
594
- if ( $url !== $noQurl ) {
595
- $removedQuotes = true;
596
- } else {
597
- $removedQuotes = false;
598
- }
599
-
600
- if ( '' === $noQurl ) {
601
- continue;
602
- }
603
-
604
- $url = $noQurl;
605
- if ( '/' === $url{0} || preg_match( '#^(https?://|ftp://|data:)#i', $url ) ) {
606
- // URL is protocol-relative, host-relative or something we don't touch
607
- continue;
608
- } else {
609
- // Relative URL
610
- $newurl = preg_replace( '/https?:/', '', str_replace( ' ', '%20', AUTOPTIMIZE_WP_ROOT_URL . str_replace( '//', '/', $dir . '/' . $url ) ) );
611
-
612
- // Hash the url + whatever was behind potentially for replacement
613
- // We must do this, or different css classes referencing the same bg image (but
614
- // different parts of it, say, in sprites and such) loose their stuff...
615
- $hash = md5( $url . $matches[2][$k] );
616
- $code = str_replace( $matches[0][$k], $hash, $code );
617
-
618
- if ( $removedQuotes ) {
619
- $replace[$hash] = "url('" . $newurl . "')" . $matches[2][$k];
620
- } else {
621
- $replace[$hash] = 'url(' . $newurl . ')' . $matches[2][$k];
622
- }
623
- }
624
- }
625
-
626
- if ( ! empty( $replace ) ) {
627
- // Sort the replacements array by key length in desc order (so that the longest strings are replaced first)
628
- $keys = array_map( 'strlen', array_keys( $replace ) );
629
- array_multisort( $keys, SORT_DESC, $replace );
630
-
631
- // Replace URLs found within $code
632
- $code = str_replace( array_keys( $replace ), array_values( $replace ), $code );
633
- }
634
- }
635
-
636
- return $code;
637
- }
638
-
639
- private function ismovable($tag) {
640
- if ( apply_filters('autoptimize_filter_css_dontaggregate', false) ) {
641
- return false;
642
- } else if (!empty($this->whitelist)) {
643
- foreach ($this->whitelist as $match) {
644
- if(strpos($tag,$match)!==false) {
645
- return true;
646
- }
647
- }
648
- // no match with whitelist
649
- return false;
650
- } else {
651
- if (is_array($this->dontmove)) {
652
- foreach($this->dontmove as $match) {
653
- if(strpos($tag,$match)!==false) {
654
- //Matched something
655
- return false;
656
- }
657
- }
658
- }
659
-
660
- //If we're here it's safe to move
661
- return true;
662
- }
663
- }
664
-
665
- private function can_inject_late($cssPath,$css) {
666
- $consider_minified_array = apply_filters('autoptimize_filter_css_consider_minified', false, $cssPath);
667
- if ( $this->inject_min_late !== true ) {
668
- // late-inject turned off
669
- return false;
670
- } else if ( (strpos($cssPath,"min.css") === false) && ( str_replace($consider_minified_array, '', $cssPath) === $cssPath ) ) {
671
- // file not minified based on filename & filter
672
- return false;
673
- } else if ( strpos($css,"@import") !== false ) {
674
- // can't late-inject files with imports as those need to be aggregated
675
- return false;
676
- } else if ( (strpos($css,"@font-face")!==false ) && ( apply_filters("autoptimize_filter_css_fonts_cdn",false)===true) && (!empty($this->cdn_url)) ) {
677
- // don't late-inject CSS with font-src's if fonts are set to be CDN'ed
678
- return false;
679
- } else if ( (($this->datauris == true) || (!empty($this->cdn_url))) && preg_match("#background[^;}]*url\(#Ui",$css) ) {
680
- // don't late-inject CSS with images if CDN is set OR is image inlining is on
681
- return false;
682
- } else {
683
- // phew, all is safe, we can late-inject
684
- return true;
685
- }
686
- }
687
-
688
- private function maybe_cdn_urls($inUrl) {
689
- $url = trim($inUrl," \t\n\r\0\x0B\"'");
690
- $urlPath = parse_url($url,PHP_URL_PATH);
691
-
692
- // exclude fonts from CDN except if filter returns true
693
- if ( !preg_match('#\.(woff2?|eot|ttf|otf)$#i',$urlPath) || apply_filters('autoptimize_filter_css_fonts_cdn',false) ) {
694
- $cdn_url = $this->url_replace_cdn($url);
695
- } else {
696
- $cdn_url = $url;
697
- }
698
-
699
- return $cdn_url;
700
- }
701
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Class for CSS optimization.
4
+ */
5
+
6
+ if ( ! defined( 'ABSPATH' ) ) {
7
+ exit;
8
+ }
9
+
10
+ class autoptimizeStyles extends autoptimizeBase
11
+ {
12
+ const ASSETS_REGEX = '/url\s*\(\s*(?!["\']?data:)(?![\'|\"]?[\#|\%|])([^)]+)\s*\)([^;},\s]*)/i';
13
+
14
+ /**
15
+ * Font-face regex-fu from HamZa at: https://stackoverflow.com/a/21395083
16
+ * ~
17
+ * @font-face\s* # Match @font-face and some spaces
18
+ * ( # Start group 1
19
+ * \{ # Match {
20
+ * (?: # A non-capturing group
21
+ * [^{}]+ # Match anything except {} one or more times
22
+ * | # Or
23
+ * (?1) # Recurse/rerun the expression of group 1
24
+ * )* # Repeat 0 or more times
25
+ * \} # Match }
26
+ * ) # End group 1
27
+ * ~xs';
28
+ */
29
+ const FONT_FACE_REGEX = '~@font-face\s*(\{(?:[^{}]+|(?1))*\})~xsi'; // added `i` flag for case-insensitivity.
30
+
31
+ private $css = array();
32
+ private $csscode = array();
33
+ private $url = array();
34
+ private $restofcontent = '';
35
+ private $datauris = false;
36
+ private $hashmap = array();
37
+ private $alreadyminified = false;
38
+ private $aggregate = true;
39
+ private $inline = false;
40
+ private $defer = false;
41
+ private $defer_inline = false;
42
+ private $whitelist = '';
43
+ private $cssinlinesize = '';
44
+ private $cssremovables = array();
45
+ private $include_inline = false;
46
+ private $inject_min_late = '';
47
+
48
+ // public $cdn_url; // Used all over the place implicitly, so will have to be either public or protected :/ .
49
+
50
+ // Reads the page and collects style tags.
51
+ public function read( $options )
52
+ {
53
+ $noptimizeCSS = apply_filters( 'autoptimize_filter_css_noptimize', false, $this->content );
54
+ if ( $noptimizeCSS ) {
55
+ return false;
56
+ }
57
+
58
+ $whitelistCSS = apply_filters( 'autoptimize_filter_css_whitelist', '', $this->content );
59
+ if ( ! empty( $whitelistCSS ) ) {
60
+ $this->whitelist = array_filter( array_map( 'trim', explode( ',', $whitelistCSS ) ) );
61
+ }
62
+
63
+ $removableCSS = apply_filters( 'autoptimize_filter_css_removables', '' );
64
+ if ( ! empty( $removableCSS ) ) {
65
+ $this->cssremovables = array_filter( array_map( 'trim', explode( ',', $removableCSS ) ) );
66
+ }
67
+
68
+ $this->cssinlinesize = apply_filters( 'autoptimize_filter_css_inlinesize', 256 );
69
+
70
+ // filter to "late inject minified CSS", default to true for now (it is faster).
71
+ $this->inject_min_late = apply_filters( 'autoptimize_filter_css_inject_min_late', true );
72
+
73
+ // Remove everything that's not the header.
74
+ if ( apply_filters( 'autoptimize_filter_css_justhead', $options['justhead'] ) ) {
75
+ $content = explode( '</head>', $this->content, 2 );
76
+ $this->content = $content[0] . '</head>';
77
+ $this->restofcontent = $content[1];
78
+ }
79
+
80
+ // Determine whether we're doing CSS-files aggregation or not.
81
+ if ( isset( $options['aggregate'] ) && ! $options['aggregate'] ) {
82
+ $this->aggregate = false;
83
+ }
84
+ // Returning true for "dontaggregate" turns off aggregation.
85
+ if ( $this->aggregate && apply_filters( 'autoptimize_filter_css_dontaggregate', false ) ) {
86
+ $this->aggregate = false;
87
+ }
88
+
89
+ // include inline?
90
+ if ( apply_filters( 'autoptimize_css_include_inline', $options['include_inline'] ) ) {
91
+ $this->include_inline = true;
92
+ }
93
+
94
+ // List of CSS strings which are excluded from autoptimization.
95
+ $excludeCSS = apply_filters( 'autoptimize_filter_css_exclude', $options['css_exclude'], $this->content );
96
+ if ( '' !== $excludeCSS ) {
97
+ $this->dontmove = array_filter( array_map( 'trim', explode( ',', $excludeCSS ) ) );
98
+ } else {
99
+ $this->dontmove = array();
100
+ }
101
+
102
+ // forcefully exclude CSS with data-noptimize attrib.
103
+ $this->dontmove[] = 'data-noptimize';
104
+
105
+ // Should we defer css?
106
+ // value: true / false.
107
+ $this->defer = $options['defer'];
108
+ $this->defer = apply_filters( 'autoptimize_filter_css_defer', $this->defer, $this->content );
109
+
110
+ // Should we inline while deferring?
111
+ // value: inlined CSS.
112
+ $this->defer_inline = apply_filters( 'autoptimize_filter_css_defer_inline', $options['defer_inline'], $this->content );
113
+
114
+ // Should we inline?
115
+ // value: true / false.
116
+ $this->inline = $options['inline'];
117
+ $this->inline = apply_filters( 'autoptimize_filter_css_inline', $this->inline, $this->content );
118
+
119
+ // Store cdn url.
120
+ $this->cdn_url = $options['cdn_url'];
121
+
122
+ // Store data: URIs setting for later use.
123
+ $this->datauris = $options['datauris'];
124
+
125
+ // noptimize me.
126
+ $this->content = $this->hide_noptimize( $this->content );
127
+
128
+ // Exclude (no)script, as those may contain CSS which should be left as is.
129
+ $this->content = $this->replace_contents_with_marker_if_exists(
130
+ 'SCRIPT',
131
+ '<script',
132
+ '#<(?:no)?script.*?<\/(?:no)?script>#is',
133
+ $this->content
134
+ );
135
+
136
+ // Save IE hacks.
137
+ $this->content = $this->hide_iehacks( $this->content );
138
+
139
+ // Hide HTML comments.
140
+ $this->content = $this->hide_comments( $this->content );
141
+
142
+ // Get <style> and <link>.
143
+ if ( preg_match_all( '#(<style[^>]*>.*</style>)|(<link[^>]*stylesheet[^>]*>)#Usmi', $this->content, $matches ) ) {
144
+
145
+ foreach ( $matches[0] as $tag ) {
146
+ if ( $this->isremovable( $tag, $this->cssremovables ) ) {
147
+ $this->content = str_replace( $tag, '', $this->content );
148
+ } elseif ( $this->ismovable( $tag ) ) {
149
+ // Get the media.
150
+ if ( false !== strpos( $tag, 'media=' ) ) {
151
+ preg_match( '#media=(?:"|\')([^>]*)(?:"|\')#Ui', $tag, $medias );
152
+ $medias = explode( ',', $medias[1] );
153
+ $media = array();
154
+ foreach ( $medias as $elem ) {
155
+ /* $media[] = current(explode(' ',trim($elem),2)); */
156
+ if ( empty( $elem ) ) {
157
+ $elem = 'all';
158
+ }
159
+
160
+ $media[] = $elem;
161
+ }
162
+ } else {
163
+ // No media specified - applies to all.
164
+ $media = array( 'all' );
165
+ }
166
+
167
+ $media = apply_filters( 'autoptimize_filter_css_tagmedia', $media, $tag );
168
+
169
+ if ( preg_match( '#<link.*href=("|\')(.*)("|\')#Usmi', $tag, $source ) ) {
170
+ // <link>.
171
+ $url = current( explode( '?', $source[2], 2 ) );
172
+ $path = $this->getpath( $url );
173
+
174
+ if ( false !== $path && preg_match( '#\.css$#', $path ) ) {
175
+ // Good link.
176
+ $this->css[] = array( $media, $path );
177
+ } else {
178
+ // Link is dynamic (.php etc).
179
+ $tag = '';
180
+ }
181
+ } else {
182
+ // Inline css in style tags can be wrapped in comment tags, so restore comments.
183
+ $tag = $this->restore_comments( $tag );
184
+ preg_match( '#<style.*>(.*)</style>#Usmi', $tag, $code );
185
+
186
+ // And re-hide them to be able to to the removal based on tag.
187
+ $tag = $this->hide_comments( $tag );
188
+
189
+ if ( $this->include_inline ) {
190
+ $code = preg_replace( '#^.*<!\[CDATA\[(?:\s*\*/)?(.*)(?://|/\*)\s*?\]\]>.*$#sm', '$1', $code[1] );
191
+ $this->css[] = array( $media, 'INLINE;' . $code );
192
+ } else {
193
+ $tag = '';
194
+ }
195
+ }
196
+
197
+ // Remove the original style tag.
198
+ $this->content = str_replace( $tag, '', $this->content );
199
+ } else {
200
+ // Excluded CSS, minify if getpath and filter says so...
201
+ if ( preg_match( '#<link.*href=("|\')(.*)("|\')#Usmi', $tag, $source ) ) {
202
+ $exploded_url = explode( '?', $source[2], 2 );
203
+ $url = $exploded_url[0];
204
+ $path = $this->getpath( $url );
205
+
206
+ if ( $path && apply_filters( 'autoptimize_filter_css_minify_excluded', true, $url ) ) {
207
+ $minified_url = $this->minify_single( $path );
208
+ if ( ! empty( $minified_url ) ) {
209
+ // Replace orig URL with cached minified URL.
210
+ $new_tag = str_replace( $url, $minified_url, $tag );
211
+ } else {
212
+ $new_tag = $tag;
213
+ }
214
+
215
+ // Removes querystring from URL.
216
+ if ( ! empty( $exploded_url[1] ) ) {
217
+ $new_tag = str_replace( '?' . $exploded_url[1], '', $new_tag );
218
+ }
219
+
220
+ // Defer single CSS if "inline & defer" is ON and there is inline CSS.
221
+ if ( $this->defer && ! empty( $this->defer_inline ) ) {
222
+ // Get/ set (via filter) the JS to be triggers onload of the preloaded CSS.
223
+ $_preload_onload = apply_filters(
224
+ 'autoptimize_filter_css_preload_onload',
225
+ "this.onload=null;this.rel='stylesheet'",
226
+ $url
227
+ );
228
+ // Adapt original <link> element for CSS to be preloaded and add <noscript>-version for fallback.
229
+ $new_tag = '<noscript>' . $new_tag . '</noscript>' . str_replace(
230
+ array(
231
+ "rel='stylesheet'",
232
+ 'rel="stylesheet"',
233
+ ),
234
+ "rel='preload' as='style' onload=\"" . $_preload_onload . "\"",
235
+ $new_tag
236
+ );
237
+ }
238
+
239
+ // And replace!
240
+ $this->content = str_replace( $tag, $new_tag, $this->content );
241
+ }
242
+ }
243
+ }
244
+ }
245
+ return true;
246
+ }
247
+
248
+ // Really, no styles?
249
+ return false;
250
+ }
251
+
252
+ /**
253
+ * Checks if the local file referenced by $path is a valid
254
+ * candidate for being inlined into a data: URI
255
+ *
256
+ * @param string $path
257
+ * @return boolean
258
+ */
259
+ private function is_datauri_candidate( $path )
260
+ {
261
+ // Call only once since it's called from a loop.
262
+ static $max_size = null;
263
+ if ( null === $max_size ) {
264
+ $max_size = $this->get_datauri_maxsize();
265
+ }
266
+
267
+ if ( $path && preg_match( '#\.(jpe?g|png|gif|webp|bmp)$#i', $path ) &&
268
+ file_exists( $path ) && is_readable( $path ) && filesize( $path ) <= $max_size ) {
269
+
270
+ // Seems we have a candidate.
271
+ $is_candidate = true;
272
+ } else {
273
+ // Filter allows overriding default decision (which checks for local file existence).
274
+ $is_candidate = apply_filters( 'autoptimize_filter_css_is_datauri_candidate', false, $path );
275
+ }
276
+
277
+ return $is_candidate;
278
+ }
279
+
280
+ /**
281
+ * Returns the amount of bytes that shouldn't be exceeded if a file is to
282
+ * be inlined into a data: URI. Defaults to 4096, passed through
283
+ * `autoptimize_filter_css_datauri_maxsize` filter.
284
+ *
285
+ * @return mixed
286
+ */
287
+ private function get_datauri_maxsize()
288
+ {
289
+ static $max_size = null;
290
+
291
+ /**
292
+ * No need to apply the filter multiple times in case the
293
+ * method itself is invoked multiple times during a single request.
294
+ * This prevents some wild stuff like having different maxsizes
295
+ * for different files/site-sections etc. But if you're into that sort
296
+ * of thing you're probably better of building assets completely
297
+ * outside of WordPress anyway.
298
+ */
299
+ if ( null === $max_size ) {
300
+ $max_size = (int) apply_filters( 'autoptimize_filter_css_datauri_maxsize', 4096 );
301
+ }
302
+
303
+ return $max_size;
304
+ }
305
+
306
+ private function check_datauri_exclude_list( $url )
307
+ {
308
+ static $exclude_list = null;
309
+ $no_datauris = array();
310
+
311
+ // Again, skip doing certain stuff repeatedly when loop-called.
312
+ if ( null === $exclude_list ) {
313
+ $exclude_list = apply_filters( 'autoptimize_filter_css_datauri_exclude', '' );
314
+ $no_datauris = array_filter( array_map( 'trim', explode( ',', $exclude_list ) ) );
315
+ }
316
+
317
+ $matched = false;
318
+
319
+ if ( ! empty( $exclude_list ) ) {
320
+ foreach ( $no_datauris as $no_datauri ) {
321
+ if ( false !== strpos( $url, $no_datauri ) ) {
322
+ $matched = true;
323
+ break;
324
+ }
325
+ }
326
+ }
327
+
328
+ return $matched;
329
+ }
330
+
331
+ private function build_or_get_datauri_image( $path )
332
+ {
333
+ /**
334
+ * TODO/FIXME: document the required return array format, or better yet,
335
+ * use a string, since we don't really need an array for this. That would, however,
336
+ * require changing even more code, which is not happening right now...
337
+ */
338
+
339
+ // Allows short-circuiting datauri generation for an image.
340
+ $result = apply_filters( 'autoptimize_filter_css_datauri_image', array(), $path );
341
+ if ( ! empty( $result ) ) {
342
+ if ( is_array( $result ) && isset( $result['full'] ) && isset( $result['base64data'] ) ) {
343
+ return $result;
344
+ }
345
+ }
346
+
347
+ $hash = md5( $path );
348
+ $check = new autoptimizeCache( $hash, 'img' );
349
+ if ( $check->check() ) {
350
+ // we have the base64 image in cache.
351
+ $headAndData = $check->retrieve();
352
+ $_base64data = explode( ';base64,', $headAndData );
353
+ $base64data = $_base64data[1];
354
+ unset( $_base64data );
355
+ } else {
356
+ // It's an image and we don't have it in cache, get the type by extension.
357
+ $exploded_path = explode( '.', $path );
358
+ $type = end( $exploded_path );
359
+
360
+ switch ( $type ) {
361
+ case 'jpg':
362
+ case 'jpeg':
363
+ $dataurihead = 'data:image/jpeg;base64,';
364
+ break;
365
+ case 'gif':
366
+ $dataurihead = 'data:image/gif;base64,';
367
+ break;
368
+ case 'png':
369
+ $dataurihead = 'data:image/png;base64,';
370
+ break;
371
+ case 'bmp':
372
+ $dataurihead = 'data:image/bmp;base64,';
373
+ break;
374
+ case 'webp':
375
+ $dataurihead = 'data:image/webp;base64,';
376
+ break;
377
+ default:
378
+ $dataurihead = 'data:application/octet-stream;base64,';
379
+ }
380
+
381
+ // Encode the data.
382
+ $base64data = base64_encode( file_get_contents( $path ) );
383
+ $headAndData = $dataurihead . $base64data;
384
+
385
+ // Save in cache.
386
+ $check->cache( $headAndData, 'text/plain' );
387
+ }
388
+ unset( $check );
389
+
390
+ return array( 'full' => $headAndData, 'base64data' => $base64data );
391
+ }
392
+
393
+ /**
394
+ * Given an array of key/value pairs to replace in $string,
395
+ * it does so by replacing the longest-matching strings first.
396
+ *
397
+ * @param string $string
398
+ * @param array $replacements
399
+ *
400
+ * @return string
401
+ */
402
+ protected static function replace_longest_matches_first( $string, $replacements = array() )
403
+ {
404
+ if ( ! empty( $replacements ) ) {
405
+ // Sort the replacements array by key length in desc order (so that the longest strings are replaced first).
406
+ $keys = array_map( 'strlen', array_keys( $replacements ) );
407
+ array_multisort( $keys, SORT_DESC, $replacements );
408
+ $string = str_replace( array_keys( $replacements ), array_values( $replacements ), $string );
409
+ }
410
+
411
+ return $string;
412
+ }
413
+
414
+ /**
415
+ * Rewrites/Replaces any ASSETS_REGEX-matching urls in a string.
416
+ * Removes quotes/cruft around each one and passes it through to
417
+ * `autoptimizeBase::url_replace_cdn()`.
418
+ * Replacements are performed in a `longest-match-replaced-first` way.
419
+ *
420
+ * @param string $code CSS code.
421
+ *
422
+ * @return string
423
+ */
424
+ public function replace_urls( $code = '' )
425
+ {
426
+ $replacements = array();
427
+
428
+ preg_match_all( self::ASSETS_REGEX, $code, $url_src_matches );
429
+ if ( is_array( $url_src_matches ) && ! empty( $url_src_matches ) ) {
430
+ foreach ( $url_src_matches[1] as $count => $original_url ) {
431
+ // Removes quotes and other cruft.
432
+ $url = trim( $original_url, " \t\n\r\0\x0B\"'" );
433
+
434
+ /**
435
+ * TODO/FIXME: Add a way for other code / callable to be called here
436
+ * and provide it's own results for the $replacements array
437
+ * for the "current" key.
438
+ * If such a result is returned/provided, we sholud then avoid
439
+ * calling url_replace_cdn() here for the current iteration.
440
+ *
441
+ * This would maybe allow the inlining logic currently present
442
+ * in `autoptimizeStyles::rewrite_assets()` to be "pulled out"
443
+ * and given as a callable to this method or something... and
444
+ * then we could "just" call `replace_urls()` from within
445
+ * `autoptimizeStyles::rewrite_assets()` and avoid some
446
+ * (currently present) code/logic duplication.
447
+ */
448
+
449
+ // Do CDN replacement if needed.
450
+ if ( ! empty( $this->cdn_url ) ) {
451
+ $replacement_url = $this->url_replace_cdn( $url );
452
+ // Prepare replacements array.
453
+ $replacements[ $url_src_matches[1][ $count ] ] = str_replace(
454
+ $original_url, $replacement_url, $url_src_matches[1][$count]
455
+ );
456
+ }
457
+ }
458
+ }
459
+
460
+ $code = self::replace_longest_matches_first( $code, $replacements );
461
+
462
+ return $code;
463
+ }
464
+
465
+ /**
466
+ * "Hides" @font-face declarations by replacing them with `%%FONTFACE%%` markers.
467
+ * Also does CDN replacement of any font-urls within those declarations if the `autoptimize_filter_css_fonts_cdn`
468
+ * filter is used.
469
+ *
470
+ * @param string $code
471
+ * @return string
472
+ */
473
+ public function hide_fontface_and_maybe_cdn( $code )
474
+ {
475
+ // Proceed only if @font-face declarations exist within $code.
476
+ preg_match_all( self::FONT_FACE_REGEX, $code, $fontfaces );
477
+ if ( isset( $fontfaces[0] ) ) {
478
+ // Check if we need to cdn fonts or not.
479
+ $do_font_cdn = apply_filters( 'autoptimize_filter_css_fonts_cdn', false );
480
+
481
+ foreach ( $fontfaces[0] as $full_match ) {
482
+ // Keep original match so we can search/replace it.
483
+ $match_search = $full_match;
484
+
485
+ // Do font cdn if needed.
486
+ if ( $do_font_cdn ) {
487
+ $full_match = $this->replace_urls( $full_match );
488
+ }
489
+
490
+ // Replace declaration with its base64 encoded string.
491
+ $replacement = self::build_marker( 'FONTFACE', $full_match );
492
+ $code = str_replace( $match_search, $replacement, $code );
493
+ }
494
+ }
495
+
496
+ return $code;
497
+ }
498
+
499
+ /**
500
+ * Restores original @font-face declarations that have been "hidden"
501
+ * using `hide_fontface_and_maybe_cdn()`.
502
+ *
503
+ * @param string $code
504
+ * @return string
505
+ */
506
+ public function restore_fontface( $code )
507
+ {
508
+ return $this->restore_marked_content( 'FONTFACE', $code );
509
+ }
510
+
511
+ // Re-write (and/or inline) referenced assets.
512
+ public function rewrite_assets( $code )
513
+ {
514
+ // Handle @font-face rules by hiding and processing them separately.
515
+ $code = $this->hide_fontface_and_maybe_cdn( $code );
516
+
517
+ /**
518
+ * TODO/FIXME:
519
+ * Certain code parts below are kind-of repeated now in `replace_urls()`, which is not ideal.
520
+ * There is maybe a way to separate/refactor things and then be able to keep
521
+ * the ASSETS_REGEX rewriting/handling logic in a single place (along with removing quotes/cruft from matched urls).
522
+ * See comments in `replace_urls()` regarding this. The idea is to extract the inlining
523
+ * logic out (which is the only real difference between replace_urls() and the code below), but still
524
+ * achieve identical results as before.
525
+ */
526
+
527
+ // Re-write (and/or inline) URLs to point them to the CDN host.
528
+ $url_src_matches = array();
529
+ $imgreplace = array();
530
+ // Matches and captures anything specified within the literal `url()` and excludes those containing data: URIs.
531
+ preg_match_all( self::ASSETS_REGEX, $code, $url_src_matches );
532
+ if ( is_array( $url_src_matches ) && ! empty( $url_src_matches ) ) {
533
+ foreach ( $url_src_matches[1] as $count => $original_url ) {
534
+ // Removes quotes and other cruft.
535
+ $url = trim( $original_url, " \t\n\r\0\x0B\"'" );
536
+
537
+ // If datauri inlining is turned on, do it.
538
+ $inlined = false;
539
+ if ( $this->datauris ) {
540
+ $iurl = $url;
541
+ if ( false !== strpos( $iurl, '?' ) ) {
542
+ $iurl = strtok( $iurl, '?' );
543
+ }
544
+
545
+ $ipath = $this->getpath( $iurl );
546
+
547
+ $excluded = $this->check_datauri_exclude_list( $ipath );
548
+ if ( ! $excluded ) {
549
+ $is_datauri_candidate = $this->is_datauri_candidate( $ipath );
550
+ if ( $is_datauri_candidate ) {
551
+ $datauri = $this->build_or_get_datauri_image( $ipath );
552
+ $base64data = $datauri['base64data'];
553
+ // Add it to the list for replacement.
554
+ $imgreplace[ $url_src_matches[1][ $count ] ] = str_replace(
555
+ $original_url,
556
+ $datauri['full'],
557
+ $url_src_matches[1][$count]
558
+ );
559
+ $inlined = true;
560
+ }
561
+ }
562
+ }
563
+
564
+ /**
565
+ * Doing CDN URL replacement for every found match (if CDN is
566
+ * specified). This way we make sure to do it even if
567
+ * inlining isn't turned on, or if a resource is skipped from
568
+ * being inlined for whatever reason above.
569
+ */
570
+ if ( ! $inlined && ( ! empty( $this->cdn_url ) || has_filter( 'autoptimize_filter_base_replace_cdn' ) ) ) {
571
+ // Just do the "simple" CDN replacement.
572
+ $replacement_url = $this->url_replace_cdn( $url );
573
+ $imgreplace[ $url_src_matches[1][ $count ] ] = str_replace(
574
+ $original_url, $replacement_url, $url_src_matches[1][$count]
575
+ );
576
+ }
577
+ }
578
+ }
579
+
580
+ $code = self::replace_longest_matches_first( $code, $imgreplace );
581
+
582
+ // Replace back font-face markers with actual font-face declarations.
583
+ $code = $this->restore_fontface( $code );
584
+
585
+ return $code;
586
+ }
587
+
588
+ // Joins and optimizes CSS.
589
+ public function minify()
590
+ {
591
+ foreach ( $this->css as $group ) {
592
+ list( $media, $css ) = $group;
593
+ if ( preg_match( '#^INLINE;#', $css ) ) {
594
+ // <style>.
595
+ $css = preg_replace( '#^INLINE;#', '', $css );
596
+ $css = self::fixurls( ABSPATH . 'index.php', $css ); // ABSPATH already contains a trailing slash.
597
+ $tmpstyle = apply_filters( 'autoptimize_css_individual_style', $css, '' );
598
+ if ( has_filter( 'autoptimize_css_individual_style' ) && ! empty( $tmpstyle ) ) {
599
+ $css = $tmpstyle;
600
+ $this->alreadyminified = true;
601
+ }
602
+ } else {
603
+ // <link>
604
+ if ( false !== $css && file_exists( $css ) && is_readable( $css ) ) {
605
+ $cssPath = $css;
606
+ $css = self::fixurls( $cssPath, file_get_contents( $cssPath ) );
607
+ $css = preg_replace( '/\x{EF}\x{BB}\x{BF}/', '', $css );
608
+ $tmpstyle = apply_filters( 'autoptimize_css_individual_style', $css, $cssPath );
609
+ if ( has_filter( 'autoptimize_css_individual_style' ) && ! empty( $tmpstyle ) ) {
610
+ $css = $tmpstyle;
611
+ $this->alreadyminified = true;
612
+ } elseif ( $this->can_inject_late( $cssPath, $css ) ) {
613
+ $css = self::build_injectlater_marker( $cssPath, md5( $css ) );
614
+ }
615
+ } else {
616
+ // Couldn't read CSS. Maybe getpath isn't working?
617
+ $css = '';
618
+ }
619
+ }
620
+
621
+ foreach ( $media as $elem ) {
622
+ if ( ! empty( $css ) ) {
623
+ if ( ! isset( $this->csscode[$elem] ) ) {
624
+ $this->csscode[$elem] = '';
625
+ }
626
+ $this->csscode[$elem] .= "\n/*FILESTART*/" . $css;
627
+ }
628
+ }
629
+ }
630
+
631
+ // Check for duplicate code.
632
+ $md5list = array();
633
+ $tmpcss = $this->csscode;
634
+ foreach ( $tmpcss as $media => $code ) {
635
+ $md5sum = md5( $code );
636
+ $medianame = $media;
637
+ foreach ( $md5list as $med => $sum ) {
638
+ // If same code.
639
+ if ( $sum === $md5sum ) {
640
+ // Add the merged code.
641
+ $medianame = $med . ', ' . $media;
642
+ $this->csscode[$medianame] = $code;
643
+ $md5list[$medianame] = $md5list[$med];
644
+ unset( $this->csscode[$med], $this->csscode[$media], $med5list[$med] );
645
+ }
646
+ }
647
+ $md5list[$medianame] = $md5sum;
648
+ }
649
+ unset( $tmpcss );
650
+
651
+ // Manage @imports, while is for recursive import management.
652
+ foreach ( $this->csscode as &$thiscss ) {
653
+ // Flag to trigger import reconstitution and var to hold external imports.
654
+ $fiximports = false;
655
+ $external_imports = '';
656
+
657
+ // remove comments to avoid importing commented-out imports.
658
+ $thiscss_nocomments = preg_replace( '#/\*.*\*/#Us', '', $thiscss );
659
+ while ( preg_match_all( '#@import +(?:url)?(?:(?:\((["\']?)(?:[^"\')]+)\1\)|(["\'])(?:[^"\']+)\2)(?:[^,;"\']+(?:,[^,;"\']+)*)?)(?:;)#mi', $thiscss_nocomments, $matches ) ) {
660
+ foreach ( $matches[0] as $import ) {
661
+ if ( $this->isremovable( $import, $this->cssremovables ) ) {
662
+ $thiscss = str_replace( $import, '', $thiscss );
663
+ $import_ok = true;
664
+ } else {
665
+ $url = trim( preg_replace( '#^.*((?:https?:|ftp:)?//.*\.css).*$#', '$1', trim( $import ) ), " \t\n\r\0\x0B\"'" );
666
+ $path = $this->getpath( $url );
667
+ $import_ok = false;
668
+ if ( file_exists( $path ) && is_readable( $path ) ) {
669
+ $code = addcslashes( self::fixurls( $path, file_get_contents( $path ) ), "\\" );
670
+ $code = preg_replace( '/\x{EF}\x{BB}\x{BF}/', '', $code );
671
+ $tmpstyle = apply_filters( 'autoptimize_css_individual_style', $code, '' );
672
+ if ( has_filter( 'autoptimize_css_individual_style' ) && ! empty( $tmpstyle ) ) {
673
+ $code = $tmpstyle;
674
+ $this->alreadyminified = true;
675
+ } elseif ( $this->can_inject_late( $path, $code ) ) {
676
+ $code = self::build_injectlater_marker( $path, md5( $code ) );
677
+ }
678
+
679
+ if ( ! empty( $code ) ) {
680
+ $tmp_thiscss = preg_replace( '#(/\*FILESTART\*/.*)' . preg_quote( $import, '#' ) . '#Us', '/*FILESTART2*/' . $code . '$1', $thiscss );
681
+ if ( ! empty( $tmp_thiscss ) ) {
682
+ $thiscss = $tmp_thiscss;
683
+ $import_ok = true;
684
+ unset( $tmp_thiscss );
685
+ }
686
+ }
687
+ unset( $code );
688
+ }
689
+ }
690
+ if ( ! $import_ok ) {
691
+ // External imports and general fall-back.
692
+ $external_imports .= $import;
693
+
694
+ $thiscss = str_replace( $import, '', $thiscss );
695
+ $fiximports = true;
696
+ }
697
+ }
698
+ $thiscss = preg_replace( '#/\*FILESTART\*/#', '', $thiscss );
699
+ $thiscss = preg_replace( '#/\*FILESTART2\*/#', '/*FILESTART*/', $thiscss );
700
+
701
+ // and update $thiscss_nocomments before going into next iteration in while loop.
702
+ $thiscss_nocomments = preg_replace( '#/\*.*\*/#Us', '', $thiscss );
703
+ }
704
+ unset( $thiscss_nocomments );
705
+
706
+ // Add external imports to top of aggregated CSS.
707
+ if ( $fiximports ) {
708
+ $thiscss = $external_imports . $thiscss;
709
+ }
710
+ }
711
+ unset( $thiscss );
712
+
713
+ // $this->csscode has all the uncompressed code now.
714
+ foreach ( $this->csscode as &$code ) {
715
+ // Check for already-minified code.
716
+ $hash = md5( $code );
717
+ do_action( 'autoptimize_action_css_hash', $hash );
718
+ $ccheck = new autoptimizeCache( $hash, 'css' );
719
+ if ( $ccheck->check() ) {
720
+ $code = $ccheck->retrieve();
721
+ $this->hashmap[md5( $code )] = $hash;
722
+ continue;
723
+ }
724
+ unset( $ccheck );
725
+
726
+ // Rewrite and/or inline referenced assets.
727
+ $code = $this->rewrite_assets( $code );
728
+
729
+ // Minify.
730
+ $code = $this->run_minifier_on( $code );
731
+
732
+ // Bring back INJECTLATER stuff.
733
+ $code = $this->inject_minified( $code );
734
+
735
+ // Filter results.
736
+ $tmp_code = apply_filters( 'autoptimize_css_after_minify', $code );
737
+ if ( ! empty( $tmp_code ) ) {
738
+ $code = $tmp_code;
739
+ unset( $tmp_code );
740
+ }
741
+
742
+ $this->hashmap[md5( $code )] = $hash;
743
+ }
744
+
745
+ unset( $code );
746
+ return true;
747
+ }
748
+
749
+ public function run_minifier_on( $code )
750
+ {
751
+ if ( ! $this->alreadyminified ) {
752
+ $do_minify = apply_filters( 'autoptimize_css_do_minify', true );
753
+
754
+ if ( $do_minify ) {
755
+ $cssmin = new autoptimizeCSSmin();
756
+ $tmp_code = trim( $cssmin->run( $code ) );
757
+
758
+ if ( ! empty( $tmp_code ) ) {
759
+ $code = $tmp_code;
760
+ unset( $tmp_code );
761
+ }
762
+ }
763
+ }
764
+
765
+ return $code;
766
+ }
767
+
768
+ // Caches the CSS in uncompressed, deflated and gzipped form.
769
+ public function cache()
770
+ {
771
+ // CSS cache.
772
+ foreach ( $this->csscode as $media => $code ) {
773
+ $md5 = $this->hashmap[md5( $code )];
774
+ $cache = new autoptimizeCache( $md5, 'css' );
775
+ if ( ! $cache->check() ) {
776
+ // Cache our code.
777
+ $cache->cache( $code, 'text/css' );
778
+ }
779
+ $this->url[$media] = AUTOPTIMIZE_CACHE_URL . $cache->getname();
780
+ }
781
+ }
782
+
783
+ // Returns the content.
784
+ public function getcontent()
785
+ {
786
+ // restore comments.
787
+ $this->content = $this->restore_comments( $this->content );
788
+
789
+ // restore IE hacks.
790
+ $this->content = $this->restore_iehacks( $this->content );
791
+
792
+ // restore (no)script.
793
+ $this->content = $this->restore_marked_content( 'SCRIPT', $this->content );
794
+
795
+ // Restore noptimize.
796
+ $this->content = $this->restore_noptimize( $this->content );
797
+
798
+ // Restore the full content.
799
+ if ( ! empty( $this->restofcontent ) ) {
800
+ $this->content .= $this->restofcontent;
801
+ $this->restofcontent = '';
802
+ }
803
+
804
+ // Inject the new stylesheets.
805
+ $replaceTag = array( '<title', 'before' );
806
+ $replaceTag = apply_filters( 'autoptimize_filter_css_replacetag', $replaceTag, $this->content );
807
+
808
+ if ( $this->inline ) {
809
+ foreach ( $this->csscode as $media => $code ) {
810
+ $this->inject_in_html( '<style type="text/css" media="' . $media . '">' . $code . '</style>', $replaceTag );
811
+ }
812
+ } else {
813
+ if ( $this->defer ) {
814
+ $preloadCssBlock = '';
815
+ $noScriptCssBlock = "<noscript id=\"aonoscrcss\">";
816
+
817
+ $defer_inline_code = $this->defer_inline;
818
+ if ( ! empty( $defer_inline_code ) ) {
819
+ if ( apply_filters( 'autoptimize_filter_css_critcss_minify', true ) ) {
820
+ $iCssHash = md5( $defer_inline_code );
821
+ $iCssCache = new autoptimizeCache( $iCssHash, 'css' );
822
+ if ( $iCssCache->check() ) {
823
+ // we have the optimized inline CSS in cache.
824
+ $defer_inline_code = $iCssCache->retrieve();
825
+ } else {
826
+ $cssmin = new autoptimizeCSSmin();
827
+ $tmp_code = trim( $cssmin->run( $defer_inline_code ) );
828
+
829
+ if ( ! empty( $tmp_code ) ) {
830
+ $defer_inline_code = $tmp_code;
831
+ $iCssCache->cache( $defer_inline_code, 'text/css' );
832
+ unset( $tmp_code );
833
+ }
834
+ }
835
+ }
836
+ $code_out = '<style type="text/css" id="aoatfcss" media="all">' . $defer_inline_code . '</style>';
837
+ $this->inject_in_html( $code_out, $replaceTag );
838
+ }
839
+ }
840
+
841
+ foreach ( $this->url as $media => $url ) {
842
+ $url = $this->url_replace_cdn( $url );
843
+
844
+ // Add the stylesheet either deferred (import at bottom) or normal links in head.
845
+ if ( $this->defer ) {
846
+ $preloadOnLoad = autoptimizeConfig::get_ao_css_preload_onload();
847
+
848
+ $preloadCssBlock .= '<link rel="preload" as="style" media="' . $media . '" href="' . $url . '" onload="' . $preloadOnLoad . '" />';
849
+ $noScriptCssBlock .= '<link type="text/css" media="' . $media . '" href="' . $url . '" rel="stylesheet" />';
850
+ } else {
851
+ // $this->inject_in_html('<link type="text/css" media="' . $media . '" href="' . $url . '" rel="stylesheet" />', $replaceTag);
852
+ if ( strlen( $this->csscode[$media] ) > $this->cssinlinesize ) {
853
+ $this->inject_in_html( '<link type="text/css" media="' . $media . '" href="' . $url . '" rel="stylesheet" />', $replaceTag );
854
+ } elseif ( strlen( $this->csscode[$media] ) > 0 ) {
855
+ $this->inject_in_html( '<style type="text/css" media="' . $media . '">' . $this->csscode[$media] . '</style>', $replaceTag );
856
+ }
857
+ }
858
+ }
859
+
860
+ if ( $this->defer ) {
861
+ $preload_polyfill = autoptimizeConfig::get_ao_css_preload_polyfill();
862
+ $noScriptCssBlock .= '</noscript>';
863
+ $this->inject_in_html( $preloadCssBlock . $noScriptCssBlock, $replaceTag );
864
+
865
+ // Adds preload polyfill at end of body tag.
866
+ $this->inject_in_html(
867
+ apply_filters( 'autoptimize_css_preload_polyfill', $preload_polyfill ),
868
+ array( '</body>', 'before' )
869
+ );
870
+ }
871
+ }
872
+
873
+ // Return the modified stylesheet.
874
+ return $this->content;
875
+ }
876
+
877
+ static function fixurls( $file, $code )
878
+ {
879
+ // Switch all imports to the url() syntax.
880
+ $code = preg_replace( '#@import ("|\')(.+?)\.css.*?("|\')#', '@import url("${2}.css")', $code );
881
+
882
+ if ( preg_match_all( self::ASSETS_REGEX, $code, $matches ) ) {
883
+ $file = str_replace( WP_ROOT_DIR, '/', $file );
884
+ /**
885
+ * rollback as per https://github.com/futtta/autoptimize/issues/94
886
+ * $file = str_replace( AUTOPTIMIZE_WP_CONTENT_NAME, '', $file );
887
+ */
888
+ $dir = dirname( $file ); // Like /themes/expound/css.
889
+
890
+ /**
891
+ * $dir should not contain backslashes, since it's used to replace
892
+ * urls, but it can contain them when running on Windows because
893
+ * fixurls() is sometimes called with `ABSPATH . 'index.php'`
894
+ */
895
+ $dir = str_replace( '\\', '/', $dir );
896
+ unset( $file ); // not used below at all.
897
+
898
+ $replace = array();
899
+ foreach ( $matches[1] as $k => $url ) {
900
+ // Remove quotes.
901
+ $url = trim( $url, " \t\n\r\0\x0B\"'" );
902
+ $noQurl = trim( $url, "\"'" );
903
+ if ( $url !== $noQurl ) {
904
+ $removedQuotes = true;
905
+ } else {
906
+ $removedQuotes = false;
907
+ }
908
+
909
+ if ( '' === $noQurl ) {
910
+ continue;
911
+ }
912
+
913
+ $url = $noQurl;
914
+ if ( '/' === $url{0} || preg_match( '#^(https?://|ftp://|data:)#i', $url ) ) {
915
+ // URL is protocol-relative, host-relative or something we don't touch.
916
+ continue;
917
+ } else {
918
+ // Relative URL.
919
+ /**
920
+ * rollback as per https://github.com/futtta/autoptimize/issues/94
921
+ * $newurl = preg_replace( '/https?:/', '', str_replace( ' ', '%20', AUTOPTIMIZE_WP_CONTENT_URL . str_replace( '//', '/', $dir . '/' . $url ) ) );
922
+ */
923
+ $newurl = preg_replace( '/https?:/', '', str_replace( ' ', '%20', AUTOPTIMIZE_WP_ROOT_URL . str_replace( '//', '/', $dir . '/' . $url ) ) );
924
+ $newurl = apply_filters( 'autoptimize_filter_css_fixurl_newurl', $newurl );
925
+
926
+ /**
927
+ * Hash the url + whatever was behind potentially for replacement
928
+ * We must do this, or different css classes referencing the same bg image (but
929
+ * different parts of it, say, in sprites and such) loose their stuff...
930
+ */
931
+ $hash = md5( $url . $matches[2][$k] );
932
+ $code = str_replace( $matches[0][$k], $hash, $code );
933
+
934
+ if ( $removedQuotes ) {
935
+ $replace[$hash] = "url('" . $newurl . "')" . $matches[2][$k];
936
+ } else {
937
+ $replace[$hash] = 'url(' . $newurl . ')' . $matches[2][$k];
938
+ }
939
+ }
940
+ }
941
+
942
+ $code = self::replace_longest_matches_first( $code, $replace );
943
+ }
944
+
945
+ return $code;
946
+ }
947
+
948
+ private function ismovable( $tag )
949
+ {
950
+ if ( ! $this->aggregate ) {
951
+ return false;
952
+ }
953
+
954
+ if ( ! empty( $this->whitelist ) ) {
955
+ foreach ( $this->whitelist as $match ) {
956
+ if ( false !== strpos( $tag, $match ) ) {
957
+ return true;
958
+ }
959
+ }
960
+ // no match with whitelist.
961
+ return false;
962
+ } else {
963
+ if ( is_array( $this->dontmove ) && ! empty( $this->dontmove ) ) {
964
+ foreach ( $this->dontmove as $match ) {
965
+ if ( false !== strpos( $tag, $match ) ) {
966
+ // Matched something.
967
+ return false;
968
+ }
969
+ }
970
+ }
971
+
972
+ // If we're here it's safe to move.
973
+ return true;
974
+ }
975
+ }
976
+
977
+ private function can_inject_late( $cssPath, $css )
978
+ {
979
+ $consider_minified_array = apply_filters( 'autoptimize_filter_css_consider_minified', false, $cssPath );
980
+ if ( true !== $this->inject_min_late ) {
981
+ // late-inject turned off.
982
+ return false;
983
+ } elseif ( ( false === strpos( $cssPath, 'min.css' ) ) && ( str_replace( $consider_minified_array, '', $cssPath ) === $cssPath ) ) {
984
+ // file not minified based on filename & filter.
985
+ return false;
986
+ } elseif ( false !== strpos( $css, '@import' ) ) {
987
+ // can't late-inject files with imports as those need to be aggregated.
988
+ return false;
989
+ } elseif ( ( false !== strpos( $css, '@font-face' ) ) && ( apply_filters( 'autoptimize_filter_css_fonts_cdn', false ) === true ) && ( ! empty( $this->cdn_url ) ) ) {
990
+ // don't late-inject CSS with font-src's if fonts are set to be CDN'ed.
991
+ return false;
992
+ } elseif ( ( ( $this->datauris == true ) || ( ! empty( $this->cdn_url ) ) ) && preg_match( '#background[^;}]*url\(#Ui', $css ) ) {
993
+ // don't late-inject CSS with images if CDN is set OR if image inlining is on.
994
+ return false;
995
+ } else {
996
+ // phew, all is safe, we can late-inject.
997
+ return true;
998
+ }
999
+ }
1000
+
1001
+ /**
1002
+ * Minifies (and cdn-replaces) a single local css file
1003
+ * and returns its (cached) url.
1004
+ *
1005
+ * @param string $filepath Filepath.
1006
+ * @param bool $cache_miss Optional. Force a cache miss. Default false.
1007
+ *
1008
+ * @return bool|string Url pointing to the minified css file or false.
1009
+ */
1010
+ public function minify_single( $filepath, $cache_miss = false )
1011
+ {
1012
+ $contents = $this->prepare_minify_single( $filepath );
1013
+
1014
+ if ( empty( $contents ) ) {
1015
+ return false;
1016
+ }
1017
+
1018
+ // Check cache.
1019
+ $hash = 'single_' . md5( $contents );
1020
+ $cache = new autoptimizeCache( $hash, 'css' );
1021
+
1022
+ // If not in cache already, minify...
1023
+ if ( ! $cache->check() || $cache_miss ) {
1024
+ // Fixurls...
1025
+ $contents = self::fixurls( $filepath, $contents );
1026
+ // CDN-replace any referenced assets if needed...
1027
+ $contents = $this->replace_urls( $contents );
1028
+ // Now minify...
1029
+ $cssmin = new autoptimizeCSSmin();
1030
+ $contents = trim( $cssmin->run( $contents ) );
1031
+ // Store in cache.
1032
+ $cache->cache( $contents, 'text/css' );
1033
+ }
1034
+
1035
+ $url = $this->build_minify_single_url( $cache );
1036
+
1037
+ return $url;
1038
+ }
1039
+
1040
+ /**
1041
+ * Returns whether we're doing aggregation or not.
1042
+ *
1043
+ * @return bool
1044
+ */
1045
+ public function aggregating()
1046
+ {
1047
+ return $this->aggregate;
1048
+ }
1049
+
1050
+ public function getOptions()
1051
+ {
1052
+ return $this->options;
1053
+ }
1054
+
1055
+ public function replaceOptions( $options )
1056
+ {
1057
+ $this->options = $options;
1058
+ }
1059
+
1060
+ public function setOption( $name, $value )
1061
+ {
1062
+ $this->options[$name] = $value;
1063
+ $this->$name = $value;
1064
+ }
1065
+
1066
+ public function getOption( $name )
1067
+ {
1068
+ return $this->options[$name];
1069
+ }
1070
+ }
classes/autoptimizeToolbar.php CHANGED
@@ -1,34 +1,42 @@
1
  <?php
2
- if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
 
 
 
 
 
 
 
3
  class autoptimizeToolbar
4
  {
5
  public function __construct()
6
  {
7
- // If Cache is not available we don't add the Autoptimize Toolbar
8
- if( ! autoptimizeCache::cacheavail() ) {
9
  return;
10
  }
 
11
  // Load admin toolbar feature once WordPress, all plugins, and the theme are fully loaded and instantiated.
12
  add_action( 'wp_loaded', array( $this, 'load_toolbar' ) );
13
  }
14
 
15
  public function load_toolbar()
16
  {
17
- // Check permissions and that toolbar is not hidden via filter
18
  if ( current_user_can( 'manage_options' ) && apply_filters( 'autoptimize_filter_toolbar_show', true ) ) {
19
- // Create a handler for the AJAX toolbar requests
 
20
  add_action( 'wp_ajax_autoptimize_delete_cache', array( $this, 'delete_cache' ) );
21
 
22
- // Load custom styles, scripts and menu only when needed
23
  if ( is_admin_bar_showing() ) {
24
  if ( is_admin() ) {
25
- // in the case of back-end
26
  add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_scripts' ) );
27
  } else {
28
- // in the case of front-end
29
  add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_scripts' ) );
30
  }
31
- // Add the Autoptimize Toolbar to the Admin bar
 
32
  add_action( 'admin_bar_menu', array( $this, 'add_toolbar' ), 100 );
33
  }
34
  }
@@ -38,97 +46,105 @@ class autoptimizeToolbar
38
  {
39
  global $wp_admin_bar;
40
 
41
- // Retrieve the Autoptimize Cache Stats information
42
  $stats = autoptimizeCache::stats();
43
 
44
- // Set the Max Size recommended for cache files
45
  $max_size = apply_filters( 'autoptimize_filter_cachecheck_maxsize', 512 * 1024 * 1024 );
46
 
47
- // Retrieve the current Total Files in cache
48
  $files = $stats[0];
49
- // Retrieve the current Total Size of the cache
50
  $bytes = $stats[1];
 
51
 
52
- $size = $this->format_filesize($bytes);
53
-
54
- // We calculated the percentage of cache used
55
  $percentage = ceil( $bytes / $max_size * 100 );
56
  if ( $percentage > 100 ) {
57
  $percentage = 100;
58
  }
59
- // We define the type of color indicator for the current state of cache size.
60
- // "green" if the size is less than 80% of the total recommended
61
- // "orange" if over 80%
62
- // "red" if over 100%
63
- $color = ( $percentage == 100 ) ? 'red' : ( ( $percentage > 80 ) ? 'orange' : 'green' );
 
 
 
64
 
65
  // Create or add new items into the Admin Toolbar.
66
- // Main Autoptimize node
67
  $wp_admin_bar->add_node( array(
68
  'id' => 'autoptimize',
69
- 'title' => '<span class="ab-icon"></span><span class="ab-label">' . __( "Autoptimize", 'autoptimize' ) . '</span>',
70
  'href' => admin_url( 'options-general.php?page=autoptimize' ),
71
- 'meta' => array( 'class' => 'bullet-' . $color )
72
  ));
73
 
74
- // Cache Info node
75
  $wp_admin_bar->add_node( array(
76
  'id' => 'autoptimize-cache-info',
77
- 'title' => '<p>' . __( "Cache Info", 'autoptimize' ) . '</p>' .
78
- '<div class="autoptimize-radial-bar" percentage="' . $percentage . '">' .
79
- '<div class="circle">'.
80
- '<div class="mask full"><div class="fill bg-' . $color . '"></div></div>'.
81
- '<div class="mask half"><div class="fill bg-' . $color . '"></div></div>'.
82
- '<div class="shadow"></div>'.
83
- '</div>'.
84
- '<div class="inset"><div class="percentage"><div class="numbers ' . $color . '">' . $percentage . '%</div></div></div>'.
85
- '</div>' .
86
- '<table>' .
87
- '<tr><td>' . __( "Size", 'autoptimize' ) . ':</td><td class="size ' . $color . '">' . $size . '</td></tr>' .
88
- '<tr><td>' . __( "Files", 'autoptimize' ) . ':</td><td class="files white">' . $files . '</td></tr>' .
89
- '</table>',
90
- 'parent' => 'autoptimize'
91
  ));
92
 
93
- // Delete Cache node
94
  $wp_admin_bar->add_node( array(
95
  'id' => 'autoptimize-delete-cache',
96
- 'title' => __( "Delete Cache", 'autoptimize' ),
97
- 'parent' => 'autoptimize'
98
  ));
99
  }
100
 
101
  public function delete_cache()
102
  {
103
  check_ajax_referer( 'ao_delcache_nonce', 'nonce' );
 
104
  $result = false;
105
- if ( current_user_can( 'manage_options' ) )
106
- {
107
- // We call the function for cleaning the Autoptimize cache
108
  $result = autoptimizeCache::clearall();
109
  }
 
110
  wp_send_json( $result );
111
  }
112
 
113
  public function enqueue_scripts()
114
  {
115
- // Autoptimize Toolbar Styles
116
- wp_enqueue_style( 'autoptimize-toolbar', plugins_url( '/static/toolbar.css', __FILE__ ), array(), time(), 'all' );
117
- // Autoptimize Toolbar Javascript
118
- wp_enqueue_script( 'autoptimize-toolbar', plugins_url( '/static/toolbar.js', __FILE__ ), array( 'jquery' ), time(), true );
119
- // Localizes a registered script with data for a JavaScript variable. (We need this for the AJAX work properly in the front-end mode)
 
 
 
120
  wp_localize_script( 'autoptimize-toolbar', 'autoptimize_ajax_object', array(
121
- 'ajaxurl' => admin_url( 'admin-ajax.php' ),
122
- 'error_msg' => sprintf( __( 'Your Autoptimize cache might not have been purged successfully, please check on the <a href=%s>Autoptimize settings page</a>.', 'autoptimize' ), admin_url( 'options-general.php?page=autoptimize' ) . ' style="white-space:nowrap;"' ),
123
  'dismiss_msg' => __( 'Dismiss this notice.' ),
124
- 'nonce' => wp_create_nonce( 'ao_delcache_nonce' )
125
  ) );
126
  }
127
 
128
- public function format_filesize($bytes, $decimals = 2)
129
  {
130
  $units = array( 'B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB' );
131
- for ($i = 0; ($bytes / 1024) > 0.9; $i++, $bytes /= 1024) {}
132
- return sprintf( "%1.{$decimals}f %s", round( $bytes, $decimals ), $units[$i] );
 
 
133
  }
134
  }
1
  <?php
2
+ /**
3
+ * Handles toolbar-related stuff.
4
+ */
5
+
6
+ if ( ! defined( 'ABSPATH' ) ) {
7
+ exit;
8
+ }
9
+
10
  class autoptimizeToolbar
11
  {
12
  public function __construct()
13
  {
14
+ // If Cache is not available we don't add the toolbar.
15
+ if ( ! autoptimizeCache::cacheavail() ) {
16
  return;
17
  }
18
+
19
  // Load admin toolbar feature once WordPress, all plugins, and the theme are fully loaded and instantiated.
20
  add_action( 'wp_loaded', array( $this, 'load_toolbar' ) );
21
  }
22
 
23
  public function load_toolbar()
24
  {
25
+ // Check permissions and that toolbar is not hidden via filter.
26
  if ( current_user_can( 'manage_options' ) && apply_filters( 'autoptimize_filter_toolbar_show', true ) ) {
27
+
28
+ // Create a handler for the AJAX toolbar requests.
29
  add_action( 'wp_ajax_autoptimize_delete_cache', array( $this, 'delete_cache' ) );
30
 
31
+ // Load custom styles, scripts and menu only when needed.
32
  if ( is_admin_bar_showing() ) {
33
  if ( is_admin() ) {
 
34
  add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_scripts' ) );
35
  } else {
 
36
  add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_scripts' ) );
37
  }
38
+
39
+ // Add the Autoptimize Toolbar to the Admin bar.
40
  add_action( 'admin_bar_menu', array( $this, 'add_toolbar' ), 100 );
41
  }
42
  }
46
  {
47
  global $wp_admin_bar;
48
 
49
+ // Retrieve the Autoptimize Cache Stats information.
50
  $stats = autoptimizeCache::stats();
51
 
52
+ // Set the Max Size recommended for cache files.
53
  $max_size = apply_filters( 'autoptimize_filter_cachecheck_maxsize', 512 * 1024 * 1024 );
54
 
55
+ // Retrieve the current Total Files in cache.
56
  $files = $stats[0];
57
+ // Retrieve the current Total Size of the cache.
58
  $bytes = $stats[1];
59
+ $size = $this->format_filesize( $bytes );
60
 
61
+ // Calculate the percentage of cache used.
 
 
62
  $percentage = ceil( $bytes / $max_size * 100 );
63
  if ( $percentage > 100 ) {
64
  $percentage = 100;
65
  }
66
+
67
+ /**
68
+ * We define the type of color indicator for the current state of cache size:
69
+ * - "green" if the size is less than 80% of the total recommended.
70
+ * - "orange" if over 80%.
71
+ * - "red" if over 100%.
72
+ */
73
+ $color = ( 100 == $percentage ) ? 'red' : ( ( $percentage > 80 ) ? 'orange' : 'green' );
74
 
75
  // Create or add new items into the Admin Toolbar.
76
+ // Main "Autoptimize" node.
77
  $wp_admin_bar->add_node( array(
78
  'id' => 'autoptimize',
79
+ 'title' => '<span class="ab-icon"></span><span class="ab-label">' . __( 'Autoptimize', 'autoptimize' ) . '</span>',
80
  'href' => admin_url( 'options-general.php?page=autoptimize' ),
81
+ 'meta' => array( 'class' => 'bullet-' . $color ),
82
  ));
83
 
84
+ // "Cache Info" node.
85
  $wp_admin_bar->add_node( array(
86
  'id' => 'autoptimize-cache-info',
87
+ 'title' => '<p>' . __( 'Cache Info', 'autoptimize' ) . '</p>' .
88
+ '<div class="autoptimize-radial-bar" percentage="' . $percentage . '">' .
89
+ '<div class="autoptimize-circle">' .
90
+ '<div class="mask full"><div class="fill bg-' . $color . '"></div></div>' .
91
+ '<div class="mask half"><div class="fill bg-' . $color . '"></div></div>' .
92
+ '<div class="shadow"></div>' .
93
+ '</div>' .
94
+ '<div class="inset"><div class="percentage"><div class="numbers ' . $color . '">' . $percentage . '%</div></div></div>' .
95
+ '</div>' .
96
+ '<table>' .
97
+ '<tr><td>' . __( 'Size', 'autoptimize' ) . ':</td><td class="size ' . $color . '">' . $size . '</td></tr>' .
98
+ '<tr><td>' . __( 'Files', 'autoptimize' ) . ':</td><td class="files white">' . $files . '</td></tr>' .
99
+ '</table>',
100
+ 'parent' => 'autoptimize',
101
  ));
102
 
103
+ // "Delete Cache" node.
104
  $wp_admin_bar->add_node( array(
105
  'id' => 'autoptimize-delete-cache',
106
+ 'title' => __( 'Delete Cache', 'autoptimize' ),
107
+ 'parent' => 'autoptimize',
108
  ));
109
  }
110
 
111
  public function delete_cache()
112
  {
113
  check_ajax_referer( 'ao_delcache_nonce', 'nonce' );
114
+
115
  $result = false;
116
+ if ( current_user_can( 'manage_options' ) ) {
117
+ // We call the function for cleaning the Autoptimize cache.
 
118
  $result = autoptimizeCache::clearall();
119
  }
120
+
121
  wp_send_json( $result );
122
  }
123
 
124
  public function enqueue_scripts()
125
  {
126
+ // Autoptimize Toolbar Styles.
127
+ wp_enqueue_style( 'autoptimize-toolbar', plugins_url( '/static/toolbar.css', __FILE__ ), array(), AUTOPTIMIZE_PLUGIN_VERSION, 'all' );
128
+
129
+ // Autoptimize Toolbar Javascript.
130
+ wp_enqueue_script( 'autoptimize-toolbar', plugins_url( '/static/toolbar.js', __FILE__ ), array( 'jquery' ), AUTOPTIMIZE_PLUGIN_VERSION, true );
131
+
132
+ // Localizes a registered script with data for a JavaScript variable.
133
+ // Needed for the AJAX to work properly on the frontend.
134
  wp_localize_script( 'autoptimize-toolbar', 'autoptimize_ajax_object', array(
135
+ 'ajaxurl' => admin_url( 'admin-ajax.php' ),
136
+ 'error_msg' => sprintf( __( 'Your Autoptimize cache might not have been purged successfully, please check on the <a href=%s>Autoptimize settings page</a>.', 'autoptimize' ), admin_url( 'options-general.php?page=autoptimize' ) . ' style="white-space:nowrap;"' ),
137
  'dismiss_msg' => __( 'Dismiss this notice.' ),
138
+ 'nonce' => wp_create_nonce( 'ao_delcache_nonce' ),
139
  ) );
140
  }
141
 
142
+ public function format_filesize( $bytes, $decimals = 2 )
143
  {
144
  $units = array( 'B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB' );
145
+
146
+ for ( $i = 0; ( $bytes / 1024) > 0.9; $i++, $bytes /= 1024 ) {} // @codingStandardsIgnoreLine
147
+
148
+ return sprintf( "%1.{$decimals}f %s", round( $bytes, $decimals ), $units[ $i ] );
149
  }
150
  }
classes/autoptimizeUtils.php ADDED
@@ -0,0 +1,424 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * General helpers.
4
+ */
5
+
6
+ if ( ! defined( 'ABSPATH' ) ) {
7
+ exit;
8
+ }
9
+
10
+ class autoptimizeUtils
11
+ {
12
+ /**
13
+ * Returns true when mbstring is available.
14
+ *
15
+ * @param bool|null $override Allows overriding the decision.
16
+ *
17
+ * @return bool
18
+ */
19
+ public static function mbstring_available( $override = null )
20
+ {
21
+ static $available = null;
22
+
23
+ if ( null === $available ) {
24
+ $available = \extension_loaded( 'mbstring' );
25
+ }
26
+
27
+ if ( null !== $override ) {
28
+ $available = $override;
29
+ }
30
+
31
+ return $available;
32
+ }
33
+
34
+ /**
35
+ * Returns true when iconv is available.
36
+ *
37
+ * @param bool|null $override Allows overriding the decision.
38
+ *
39
+ * @return bool
40
+ */
41
+ public static function iconv_available( $override = null )
42
+ {
43
+ static $available = null;
44
+
45
+ if ( null === $available ) {
46
+ $available = \extension_loaded( 'iconv' );
47
+ }
48
+
49
+ if ( null !== $override ) {
50
+ $available = $override;
51
+ }
52
+
53
+ return $available;
54
+ }
55
+
56
+ /**
57
+ * Multibyte-capable strpos() if support is available on the server.
58
+ * If not, it falls back to using \strpos().
59
+ *
60
+ * @param string $haystack Haystack.
61
+ * @param string $needle Needle.
62
+ * @param int $offset Offset.
63
+ * @param string|null $encoding Encoding. Default null.
64
+ *
65
+ * @return int|false
66
+ */
67
+ public static function strpos( $haystack, $needle, $offset = 0, $encoding = null )
68
+ {
69
+ if ( self::mbstring_available() ) {
70
+ return ( null === $encoding ) ? \mb_strpos( $haystack, $needle, $offset ) : \mb_strlen( $haystack, $needle, $offset, $encoding );
71
+ } elseif ( self::iconv_available() ) {
72
+ return ( null === $encoding ) ? \iconv_strpos( $haystack, $needle, $offset ) : \iconv_strpos( $haystack, $needle, $offset, $encoding );
73
+ } else {
74
+ return \strpos( $haystack, $needle, $offset );
75
+ }
76
+ }
77
+
78
+ /**
79
+ * Attempts to return the number of characters in the given $string if
80
+ * mbstring or iconv is available. Returns the number of bytes
81
+ * (instead of characters) as fallback.
82
+ *
83
+ * @param string $string String.
84
+ * @param string|null $encoding Encoding.
85
+ *
86
+ * @return int Number of charcters or bytes in given $string
87
+ * (characters if/when supported, bytes otherwise).
88
+ */
89
+ public static function strlen( $string, $encoding = null )
90
+ {
91
+ if ( self::mbstring_available() ) {
92
+ return ( null === $encoding ) ? \mb_strlen( $string ) : \mb_strlen( $string, $encoding );
93
+ } elseif ( self::iconv_available() ) {
94
+ return ( null === $encoding ) ? @iconv_strlen( $string ) : @iconv_strlen( $string, $encoding );
95
+ } else {
96
+ return \strlen( $string );
97
+ }
98
+ }
99
+
100
+ /**
101
+ * Our wrapper around implementations of \substr_replace()
102
+ * that attempts to not break things horribly if at all possible.
103
+ * Uses mbstring and/or iconv if available, before falling back to regular
104
+ * substr_replace() (which works just fine in the majority of cases).
105
+ *
106
+ * @param string $string String.
107
+ * @param string $replacement Replacement.
108
+ * @param int $start Start offset.
109
+ * @param int|null $length Length.
110
+ * @param string|null $encoding Encoding.
111
+ *
112
+ * @return string
113
+ */
114
+ public static function substr_replace( $string, $replacement, $start, $length = null, $encoding = null )
115
+ {
116
+ if ( self::mbstring_available() ) {
117
+ $strlen = self::strlen( $string, $encoding );
118
+
119
+ if ( $start < 0 ) {
120
+ if ( -$start < $strlen ) {
121
+ $start = $strlen + $start;
122
+ } else {
123
+ $start = 0;
124
+ }
125
+ } elseif ( $start > $strlen ) {
126
+ $start = $strlen;
127
+ }
128
+
129
+ if ( null === $length || '' === $length ) {
130
+ $start2 = $strlen;
131
+ } elseif ( $length < 0 ) {
132
+ $start2 = $strlen + $length;
133
+ if ( $start2 < $start ) {
134
+ $start2 = $start;
135
+ }
136
+ } else {
137
+ $start2 = $start + $length;
138
+ }
139
+
140
+ if ( null === $encoding ) {
141
+ $leader = $start ? \mb_substr( $string, 0, $start ) : '';
142
+ $trailer = ( $start2 < $strlen ) ? \mb_substr( $string, $start2, null ) : '';
143
+ } else {
144
+ $leader = $start ? \mb_substr( $string, 0, $start, $encoding ) : '';
145
+ $trailer = ( $start2 < $strlen ) ? \mb_substr( $string, $start2, null, $encoding ) : '';
146
+ }
147
+
148
+ return "{$leader}{$replacement}{$trailer}";
149
+ } elseif ( self::iconv_available() ) {
150
+ $strlen = self::strlen( $string, $encoding );
151
+
152
+ if ( $start < 0 ) {
153
+ $start = \max( 0, $strlen + $start );
154
+ $start = $strlen + $start;
155
+ if ( $start < 0 ) {
156
+ $start = 0;
157
+ }
158
+ } elseif ( $start > $strlen ) {
159
+ $start = $strlen;
160
+ }
161
+
162
+ if ( $length < 0 ) {
163
+ $length = \max( 0, $strlen - $start + $length );
164
+ } elseif ( null === $length || ( $length > $strlen ) ) {
165
+ $length = $strlen;
166
+ }
167
+
168
+ if ( ( $start + $length ) > $strlen ) {
169
+ $length = $strlen - $start;
170
+ }
171
+
172
+ if ( null === $encoding ) {
173
+ return self::iconv_substr( $string, 0, $start ) . $replacement . self::iconv_substr( $string, $start + $length, $strlen - $start - $length );
174
+ }
175
+
176
+ return self::iconv_substr( $string, 0, $start, $encoding ) . $replacement . self::iconv_substr( $string, $start + $length, $strlen - $start - $length, $encoding );
177
+ }
178
+
179
+ return ( null === $length ) ? \substr_replace( $string, $replacement, $start ) : \substr_replace( $string, $replacement, $start, $length );
180
+ }
181
+
182
+ /**
183
+ * Wrapper around iconv_substr().
184
+ *
185
+ * @param string $s String.
186
+ * @param int $start Start offset.
187
+ * @param int|null $length Length.
188
+ * @param string|null $encoding Encoding.
189
+ *
190
+ * @return string
191
+ */
192
+ protected static function iconv_substr( $s, $start, $length = null, $encoding = null )
193
+ {
194
+ if ( $start < 0 ) {
195
+ $start = self::strlen( $s, $encoding ) + $start;
196
+ if ( $start < 0 ) {
197
+ $start = 0;
198
+ }
199
+ }
200
+
201
+ if ( null === $length ) {
202
+ $length = 2147483647;
203
+ } elseif ( $length < 0 ) {
204
+ $length = self::strlen( $s, $encoding ) + ( $length - $start );
205
+ if ( $length < 0 ) {
206
+ return '';
207
+ }
208
+ }
209
+
210
+ return (string) ( null === $encoding ) ? \iconv_substr( $s, $start, $length ) : \iconv_substr( $s, $start, $length, $encoding );
211
+ }
212
+
213
+ /**
214
+ * Decides whether this is a "subdirectory site" or not.
215
+ *
216
+ * @param bool $override Allows overriding the decision when needed.
217
+ *
218
+ * @return bool
219
+ */
220
+ public static function siteurl_not_root( $override = null )
221
+ {
222
+ static $subdir = null;
223
+
224
+ if ( null === $subdir ) {
225
+ $parts = self::get_ao_wp_site_url_parts();
226
+ $subdir = ( isset( $parts['path'] ) && ( '/' !== $parts['path'] ) );
227
+ }
228
+
229
+ if ( null !== $override ) {
230
+ $subdir = $override;
231
+ }
232
+
233
+ return $subdir;
234
+ }
235
+
236
+ /**
237
+ * Parse AUTOPTIMIZE_WP_SITE_URL into components using \parse_url(), but do
238
+ * so only once per request/lifecycle.
239
+ *
240
+ * @return array
241
+ */
242
+ public static function get_ao_wp_site_url_parts()
243
+ {
244
+ static $parts = array();
245
+
246
+ if ( empty( $parts ) ) {
247
+ $parts = \parse_url( AUTOPTIMIZE_WP_SITE_URL );
248
+ }
249
+
250
+ return $parts;
251
+ }
252
+
253
+ /**
254
+ * Modify given $cdn_url to include the site path when needed.
255
+ *
256
+ * @param string $cdn_url CDN URL to tweak.
257
+ * @param bool $force_cache_miss Force a cache miss in order to be able
258
+ * to re-run the filter.
259
+ *
260
+ * @return string
261
+ */
262
+ public static function tweak_cdn_url_if_needed( $cdn_url, $force_cache_miss = false )
263
+ {
264
+ static $results = array();
265
+
266
+ if ( ! isset( $results[ $cdn_url ] ) || $force_cache_miss ) {
267
+
268
+ // In order to return unmodified input when there's no need to tweak.
269
+ $results[ $cdn_url ] = $cdn_url;
270
+
271
+ // Behind a default true filter for backcompat, and only for sites
272
+ // in a subfolder/subdirectory, but still easily turned off if
273
+ // not wanted/needed...
274
+ if ( autoptimizeUtils::siteurl_not_root() ) {
275
+ $check = apply_filters( 'autoptimize_filter_cdn_magic_path_check', true, $cdn_url );
276
+ if ( $check ) {
277
+ $site_url_parts = autoptimizeUtils::get_ao_wp_site_url_parts();
278
+ $cdn_url_parts = \parse_url( $cdn_url );
279
+ $schemeless = self::is_protocol_relative( $cdn_url );
280
+ $cdn_url_parts = self::maybe_replace_cdn_path( $site_url_parts, $cdn_url_parts );
281
+ if ( false !== $cdn_url_parts ) {
282
+ $results[ $cdn_url ] = self::assemble_parsed_url( $cdn_url_parts, $schemeless );
283
+ }
284
+ }
285
+ }
286
+ }
287
+
288
+ return $results[ $cdn_url ];
289
+ }
290
+
291
+ /**
292
+ * When siteurl contans a path other than '/' and the CDN URL does not have
293
+ * a path or it's path is '/', this will modify the CDN URL's path component
294
+ * to match that of the siteurl.
295
+ * This is to support "magic" CDN urls that worked that way before v2.4...
296
+ *
297
+ * @param array $site_url_parts Site URL components array.
298
+ * @param array $cdn_url_parts CDN URL components array.
299
+ *
300
+ * @return array|false
301
+ */
302
+ public static function maybe_replace_cdn_path( array $site_url_parts, array $cdn_url_parts )
303
+ {
304
+ if ( isset( $site_url_parts['path'] ) && '/' !== $site_url_parts['path'] ) {
305
+ if ( ! isset( $cdn_url_parts['path'] ) || '/' === $cdn_url_parts['path'] ) {
306
+ $cdn_url_parts['path'] = $site_url_parts['path'];
307
+ return $cdn_url_parts;
308
+ }
309
+ }
310
+
311
+ return false;
312
+ }
313
+
314
+ /**
315
+ * Given an array or components returned from \parse_url(), assembles back
316
+ * the complete URL.
317
+ * If optional
318
+ *
319
+ * @param array $parsed_url URL components array.
320
+ * @param bool $schemeless Whether the assembled URL should be
321
+ * protocol-relative (schemeless) or not.
322
+ *
323
+ * @return string
324
+ */
325
+ public static function assemble_parsed_url( array $parsed_url, $schemeless = false )
326
+ {
327
+ $scheme = isset( $parsed_url['scheme'] ) ? $parsed_url['scheme'] . '://' : '';
328
+ if ( $schemeless ) {
329
+ $scheme = '//';
330
+ }
331
+ $host = isset( $parsed_url['host'] ) ? $parsed_url['host'] : '';
332
+ $port = isset( $parsed_url['port'] ) ? ':' . $parsed_url['port'] : '';
333
+ $user = isset( $parsed_url['user'] ) ? $parsed_url['user'] : '';
334
+ $pass = isset( $parsed_url['pass'] ) ? ':' . $parsed_url['pass'] : '';
335
+ $pass = ( $user || $pass ) ? "$pass@" : '';
336
+ $path = isset( $parsed_url['path'] ) ? $parsed_url['path'] : '';
337
+ $query = isset( $parsed_url['query'] ) ? '?' . $parsed_url['query'] : '';
338
+ $fragment = isset( $parsed_url['fragment'] ) ? '#' . $parsed_url['fragment'] : '';
339
+
340
+ return "$scheme$user$pass$host$port$path$query$fragment";
341
+ }
342
+
343
+ /**
344
+ * Returns true if given $url is protocol-relative.
345
+ *
346
+ * @param string $url URL to check.
347
+ *
348
+ * @return bool
349
+ */
350
+ public static function is_protocol_relative( $url )
351
+ {
352
+ $result = false;
353
+
354
+ if ( ! empty( $url ) ) {
355
+ $result = ( '/' === $url{1} ); // second char is `/`.
356
+ }
357
+
358
+ return $result;
359
+ }
360
+
361
+ /**
362
+ * Canonicalizes the given path regardless of it existing or not.
363
+ *
364
+ * @param string $path Path to normalize.
365
+ *
366
+ * @return string
367
+ */
368
+ public static function path_canonicalize( $path )
369
+ {
370
+ $patterns = array(
371
+ '~/{2,}~',
372
+ '~/(\./)+~',
373
+ '~([^/\.]+/(?R)*\.{2,}/)~',
374
+ '~\.\./~',
375
+ );
376
+ $replacements = array(
377
+ '/',
378
+ '/',
379
+ '',
380
+ '',
381
+ );
382
+
383
+ return preg_replace( $patterns, $replacements, $path );
384
+ }
385
+
386
+ /**
387
+ * Checks to see if 3rd party services are available and stores result in option
388
+ *
389
+ * @param string $return_result should we return resulting service status array (default no).
390
+ *
391
+ * @return none if $return_result is false (default), array if $return_result is true.
392
+ */
393
+ public static function check_service_availability( $return_result = false )
394
+ {
395
+ $service_availability_resp = wp_remote_get( 'https://misc.optimizingmatters.com/api/autoptimize_service_availablity.json?from=aomain&ver=' . AUTOPTIMIZE_PLUGIN_VERSION );
396
+ if ( ! is_wp_error( $service_availability_resp ) ) {
397
+ if ( '200' == wp_remote_retrieve_response_code( $service_availability_resp ) ) {
398
+ $availabilities = json_decode( wp_remote_retrieve_body( $service_availability_resp ), true );
399
+ if ( is_array( $availabilities ) ) {
400
+ update_option( 'autoptimize_service_availablity', $availabilities );
401
+ if ( $return_result ) {
402
+ return $availabilities;
403
+ }
404
+ }
405
+ }
406
+ }
407
+ }
408
+
409
+ /**
410
+ * Returns true if the string is a valid regex.
411
+ *
412
+ * @param string $string String, duh.
413
+ *
414
+ * @return bool
415
+ */
416
+ public static function str_is_valid_regex( $string )
417
+ {
418
+ set_error_handler( function() {}, E_WARNING );
419
+ $is_regex = ( false !== preg_match( $string, '' ) );
420
+ restore_error_handler();
421
+
422
+ return $is_regex;
423
+ }
424
+ }
classes/autoptimizeVersionUpdatesHandler.php ADDED
@@ -0,0 +1,206 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Handles version updates and should only be instantiated in autoptimize.php if/when needed.
4
+ */
5
+
6
+ if ( ! defined( 'ABSPATH' ) ) {
7
+ exit;
8
+ }
9
+
10
+ class autoptimizeVersionUpdatesHandler
11
+ {
12
+ /**
13
+ * The current major version string.
14
+ *
15
+ * @var string
16
+ */
17
+ protected $current_major_version = null;
18
+
19
+ public function __construct( $current_version )
20
+ {
21
+ $this->current_major_version = substr( $current_version, 0, 3 );
22
+ }
23
+
24
+ /**
25
+ * Runs all needed upgrade procedures (depending on the
26
+ * current major version specified during class instantiation)
27
+ */
28
+ public function run_needed_major_upgrades()
29
+ {
30
+ $major_update = false;
31
+
32
+ switch ( $this->current_major_version ) {
33
+ case '1.6':
34
+ $this->upgrade_from_1_6();
35
+ $major_update = true;
36
+ // No break, intentionally, so all upgrades are ran during a single request...
37
+ case '1.7':
38
+ $this->upgrade_from_1_7();
39
+ $major_update = true;
40
+ // No break, intentionally, so all upgrades are ran during a single request...
41
+ case '1.9':
42
+ $this->upgrade_from_1_9();
43
+ $major_update = true;
44
+ // No break, intentionally, so all upgrades are ran during a single request...
45
+ case '2.2':
46
+ $this->upgrade_from_2_2();
47
+ $major_update = true;
48
+ // No break, intentionally, so all upgrades are ran during a single request...
49
+ }
50
+
51
+ if ( true === $major_update ) {
52
+ $this->on_major_version_update();
53
+ }
54
+ }
55
+
56
+ /**
57
+ * Checks specified version against the one stored in the database under `autoptimize_version` and performs
58
+ * any major upgrade routines if needed.
59
+ * Updates the database version to the specified $target if it's different to the one currently stored there.
60
+ *
61
+ * @param string $target Target version to check against (ie., the currently running one).
62
+ */
63
+ public static function check_installed_and_update( $target )
64
+ {
65
+ $db_version = get_option( 'autoptimize_version', 'none' );
66
+ if ( $db_version !== $target ) {
67
+ if ( 'none' === $db_version ) {
68
+ add_action( 'admin_notices', 'autoptimizeMain::notice_installed' );
69
+ } else {
70
+ $updater = new self( $db_version );
71
+ $updater->run_needed_major_upgrades();
72
+ }
73
+
74
+ // Versions differed, upgrades happened if needed, store the new version.
75
+ update_option( 'autoptimize_version', $target );
76
+ }
77
+ }
78
+
79
+ /**
80
+ * Called after any major version update (and it's accompanying upgrade procedure)
81
+ * has happened. Clears cache and sets an admin notice.
82
+ */
83
+ protected function on_major_version_update()
84
+ {
85
+ // The transients guard here prevents stale object caches from busting the cache on every request.
86
+ if ( false == get_transient( 'autoptimize_stale_option_buster' ) ) {
87
+ set_transient( 'autoptimize_stale_option_buster', 'Mamsie & Liessie zehhe: ZWIJH!', HOUR_IN_SECONDS );
88
+ autoptimizeCache::clearall();
89
+ add_action( 'admin_notices', 'autoptimizeMain::notice_updated' );
90
+ }
91
+ }
92
+
93
+ /**
94
+ * From back in the days when I did not yet consider multisite.
95
+ */
96
+ private function upgrade_from_1_6()
97
+ {
98
+ // If user was on version 1.6.x, force advanced options to be shown by default.
99
+ update_option( 'autoptimize_show_adv', '1' );
100
+
101
+ // And remove old options.
102
+ $to_delete_options = array(
103
+ 'autoptimize_cdn_css',
104
+ 'autoptimize_cdn_css_url',
105
+ 'autoptimize_cdn_js',
106
+ 'autoptimize_cdn_js_url',
107
+ 'autoptimize_cdn_img',
108
+ 'autoptimize_cdn_img_url',
109
+ 'autoptimize_css_yui',
110
+ );
111
+ foreach ( $to_delete_options as $del_opt ) {
112
+ delete_option( $del_opt );
113
+ }
114
+ }
115
+
116
+ /**
117
+ * Forces WP 3.8 dashicons in CSS exclude options when upgrading from 1.7 to 1.8
118
+ *
119
+ * @global $wpdb
120
+ */
121
+ private function upgrade_from_1_7()
122
+ {
123
+ if ( ! is_multisite() ) {
124
+ $css_exclude = get_option( 'autoptimize_css_exclude' );
125
+ if ( empty( $css_exclude ) ) {
126
+ $css_exclude = 'admin-bar.min.css, dashicons.min.css';
127
+ } elseif ( false === strpos( $css_exclude, 'dashicons.min.css' ) ) {
128
+ $css_exclude .= ', dashicons.min.css';
129
+ }
130
+ update_option( 'autoptimize_css_exclude', $css_exclude );
131
+ } else {
132
+ global $wpdb;
133
+ $blog_ids = $wpdb->get_col( "SELECT blog_id FROM $wpdb->blogs" );
134
+ $original_blog_id = get_current_blog_id();
135
+ foreach ( $blog_ids as $blog_id ) {
136
+ switch_to_blog( $blog_id );
137
+ $css_exclude = get_option( 'autoptimize_css_exclude' );
138
+ if ( empty( $css_exclude ) ) {
139
+ $css_exclude = 'admin-bar.min.css, dashicons.min.css';
140
+ } elseif ( false === strpos( $css_exclude, 'dashicons.min.css' ) ) {
141
+ $css_exclude .= ', dashicons.min.css';
142
+ }
143
+ update_option( 'autoptimize_css_exclude', $css_exclude );
144
+ }
145
+ switch_to_blog( $original_blog_id );
146
+ }
147
+ }
148
+
149
+ /**
150
+ * 2.0 will not aggregate inline CSS/JS by default, but we want users
151
+ * upgrading from 1.9 to keep their inline code aggregated by default.
152
+ *
153
+ * @global $wpdb
154
+ */
155
+ private function upgrade_from_1_9()
156
+ {
157
+ if ( ! is_multisite() ) {
158
+ update_option( 'autoptimize_css_include_inline', 'on' );
159
+ update_option( 'autoptimize_js_include_inline', 'on' );
160
+ } else {
161
+ global $wpdb;
162
+ $blog_ids = $wpdb->get_col( "SELECT blog_id FROM $wpdb->blogs" );
163
+ $original_blog_id = get_current_blog_id();
164
+ foreach ( $blog_ids as $blog_id ) {
165
+ switch_to_blog( $blog_id );
166
+ update_option( 'autoptimize_css_include_inline', 'on' );
167
+ update_option( 'autoptimize_js_include_inline', 'on' );
168
+ }
169
+ switch_to_blog( $original_blog_id );
170
+ }
171
+ }
172
+
173
+ /**
174
+ * 2.3 has no "remove google fonts" in main screen, moved to "extra"
175
+ *
176
+ * @global $wpdb
177
+ */
178
+ private function upgrade_from_2_2()
179
+ {
180
+ if ( ! is_multisite() ) {
181
+ $this->do_2_2_settings_update();
182
+ } else {
183
+ global $wpdb;
184
+ $blog_ids = $wpdb->get_col( "SELECT blog_id FROM $wpdb->blogs" );
185
+ $original_blog_id = get_current_blog_id();
186
+ foreach ( $blog_ids as $blog_id ) {
187
+ switch_to_blog( $blog_id );
188
+ $this->do_2_2_settings_update();
189
+ }
190
+ switch_to_blog( $original_blog_id );
191
+ }
192
+ }
193
+
194
+ /**
195
+ * Helper for 2.2 autoptimize_extra_settings upgrade to avoid duplicate code
196
+ */
197
+ private function do_2_2_settings_update()
198
+ {
199
+ $nogooglefont = get_option( 'autoptimize_css_nogooglefont', '' );
200
+ $ao_extrasetting = get_option( 'autoptimize_extra_settings', '' );
201
+ if ( ( $nogooglefont ) && ( empty( $ao_extrasetting ) ) ) {
202
+ update_option( 'autoptimize_extra_settings', autoptimizeConfig::get_ao_extra_default_options() );
203
+ }
204
+ delete_option( 'autoptimize_css_nogooglefont' );
205
+ }
206
+ }
classes/external/index.html CHANGED
@@ -1 +1 @@
1
- <html><head><meta name="robots" content="noindex, nofollow"></head><body>Generated by <a href="http://wordpress.org/extend/plugins/autoptimize/" rel="nofollow">Autoptimize</a></body></html>
1
+ <html><head><meta name="robots" content="noindex, nofollow"></head><body>Generated by <a href="http://wordpress.org/extend/plugins/autoptimize/" rel="nofollow">Autoptimize</a></body></html>
classes/external/js/index.html CHANGED
@@ -1 +1 @@
1
- <html><head><meta name="robots" content="noindex, nofollow"></head><body>Generated by <a href="http://wordpress.org/extend/plugins/autoptimize/" rel="nofollow">Autoptimize</a></body></html>
1
+ <html><head><meta name="robots" content="noindex, nofollow"></head><body>Generated by <a href="http://wordpress.org/extend/plugins/autoptimize/" rel="nofollow">Autoptimize</a></body></html>
classes/external/php/index.html CHANGED
@@ -1 +1 @@
1
- <html><head><meta name="robots" content="noindex, nofollow"></head><body>Generated by <a href="http://wordpress.org/extend/plugins/autoptimize/" rel="nofollow">Autoptimize</a></body></html>
1
+ <html><head><meta name="robots" content="noindex, nofollow"></head><body>Generated by <a href="http://wordpress.org/extend/plugins/autoptimize/" rel="nofollow">Autoptimize</a></body></html>
classes/external/php/jsmin-1.1.1.php DELETED
@@ -1,291 +0,0 @@
1
- <?php
2
- /**
3
- * jsmin.php - PHP implementation of Douglas Crockford's JSMin.
4
- *
5
- * This is pretty much a direct port of jsmin.c to PHP with just a few
6
- * PHP-specific performance tweaks. Also, whereas jsmin.c reads from stdin and
7
- * outputs to stdout, this library accepts a string as input and returns another
8
- * string as output.
9
- *
10
- * PHP 5 or higher is required.
11
- *
12
- * Permission is hereby granted to use this version of the library under the
13
- * same terms as jsmin.c, which has the following license:
14
- *
15
- * --
16
- * Copyright (c) 2002 Douglas Crockford (www.crockford.com)
17
- *
18
- * Permission is hereby granted, free of charge, to any person obtaining a copy of
19
- * this software and associated documentation files (the "Software"), to deal in
20
- * the Software without restriction, including without limitation the rights to
21
- * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
22
- * of the Software, and to permit persons to whom the Software is furnished to do
23
- * so, subject to the following conditions:
24
- *
25
- * The above copyright notice and this permission notice shall be included in all
26
- * copies or substantial portions of the Software.
27
- *
28
- * The Software shall be used for Good, not Evil.
29
- *
30
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
31
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
32
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
33
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
34
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
35
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
36
- * SOFTWARE.
37
- * --
38
- *
39
- * @package JSMin
40
- * @author Ryan Grove <ryan@wonko.com>
41
- * @copyright 2002 Douglas Crockford <douglas@crockford.com> (jsmin.c)
42
- * @copyright 2008 Ryan Grove <ryan@wonko.com> (PHP port)
43
- * @license http://opensource.org/licenses/mit-license.php MIT License
44
- * @version 1.1.1 (2008-03-02)
45
- * @link http://code.google.com/p/jsmin-php/
46
- */
47
-
48
- class JSMin {
49
- const ORD_LF = 10;
50
- const ORD_SPACE = 32;
51
-
52
- protected $a = '';
53
- protected $b = '';
54
- protected $input = '';
55
- protected $inputIndex = 0;
56
- protected $inputLength = 0;
57
- protected $lookAhead = null;
58
- protected $output = '';
59
-
60
- // -- Public Static Methods --------------------------------------------------
61
-
62
- public static function minify($js) {
63
- $jsmin = new JSMin($js);
64
- return $jsmin->min();
65
- }
66
-
67
- // -- Public Instance Methods ------------------------------------------------
68
-
69
- public function __construct($input) {
70
- $this->input = str_replace("\r\n", "\n", $input);
71
- $this->inputLength = strlen($this->input);
72
- }
73
-
74
- // -- Protected Instance Methods ---------------------------------------------
75
-
76
- protected function action($d) {
77
- switch($d) {
78
- case 1:
79
- $this->output .= $this->a;
80
-
81
- case 2:
82
- $this->a = $this->b;
83
-
84
- if ($this->a === "'" || $this->a === '"') {
85
- for (;;) {
86
- $this->output .= $this->a;
87
- $this->a = $this->get();
88
-
89
- if ($this->a === $this->b) {
90
- break;
91
- }
92
-
93
- if (ord($this->a) <= self::ORD_LF) {
94
- throw new JSMinException('Unterminated string literal.');
95
- }
96
-
97
- if ($this->a === '\\') {
98
- $this->output .= $this->a;
99
- $this->a = $this->get();
100
- }
101
- }
102
- }
103
-
104
- case 3:
105
- $this->b = $this->next();
106
-
107
- if ($this->b === '/' && (
108
- $this->a === '(' || $this->a === ',' || $this->a === '=' ||
109
- $this->a === ':' || $this->a === '[' || $this->a === '!' ||
110
- $this->a === '&' || $this->a === '|' || $this->a === '?')) {
111
-
112
- $this->output .= $this->a . $this->b;
113
-
114
- for (;;) {
115
- $this->a = $this->get();
116
-
117
- if ($this->a === '/') {
118
- break;
119
- } elseif ($this->a === '\\') {
120
- $this->output .= $this->a;
121
- $this->a = $this->get();
122
- } elseif (ord($this->a) <= self::ORD_LF) {
123
- throw new JSMinException('Unterminated regular expression '.
124
- 'literal.');
125
- }
126
-
127
- $this->output .= $this->a;
128
- }
129
-
130
- $this->b = $this->next();
131
- }
132
- }
133
- }
134
-
135
- protected function get() {
136
- $c = $this->lookAhead;
137
- $this->lookAhead = null;
138
-
139
- if ($c === null) {
140
- if ($this->inputIndex < $this->inputLength) {
141
- $c = $this->input[$this->inputIndex];
142
- $this->inputIndex += 1;
143
- } else {
144
- $c = null;
145
- }
146
- }
147
-
148
- if ($c === "\r") {
149
- return "\n";
150
- }
151
-
152
- if ($c === null || $c === "\n" || ord($c) >= self::ORD_SPACE) {
153
- return $c;
154
- }
155
-
156
- return ' ';
157
- }
158
-
159
- protected function isAlphaNum($c) {
160
- return ord($c) > 126 || $c === '\\' || preg_match('/^[\w\$]$/', $c) === 1;
161
- }
162
-
163
- protected function min() {
164
- $this->a = "\n";
165
- $this->action(3);
166
-
167
- while ($this->a !== null) {
168
- switch ($this->a) {
169
- case ' ':
170
- if ($this->isAlphaNum($this->b)) {
171
- $this->action(1);
172
- } else {
173
- $this->action(2);
174
- }
175
- break;
176
-
177
- case "\n":
178
- switch ($this->b) {
179
- case '{':
180
- case '[':
181
- case '(':
182
- case '+':
183
- case '-':
184
- $this->action(1);
185
- break;
186
-
187
- case ' ':
188
- $this->action(3);
189
- break;
190
-
191
- default:
192
- if ($this->isAlphaNum($this->b)) {
193
- $this->action(1);
194
- }
195
- else {
196
- $this->action(2);
197
- }
198
- }
199
- break;
200
-
201
- default:
202
- switch ($this->b) {
203
- case ' ':
204
- if ($this->isAlphaNum($this->a)) {
205
- $this->action(1);
206
- break;
207
- }
208
-
209
- $this->action(3);
210
- break;
211
-
212
- case "\n":
213
- switch ($this->a) {
214
- case '}':
215
- case ']':
216
- case ')':
217
- case '+':
218
- case '-':
219
- case '"':
220
- case "'":
221
- $this->action(1);
222
- break;
223
-
224
- default:
225
- if ($this->isAlphaNum($this->a)) {
226
- $this->action(1);
227
- }
228
- else {
229
- $this->action(3);
230
- }
231
- }
232
- break;
233
-
234
- default:
235
- $this->action(1);
236
- break;
237
- }
238
- }
239
- }
240
-
241
- return $this->output;
242
- }
243
-
244
- protected function next() {
245
- $c = $this->get();
246
-
247
- if ($c === '/') {
248
- switch($this->peek()) {
249
- case '/':
250
- for (;;) {
251
- $c = $this->get();
252
-
253
- if (ord($c) <= self::ORD_LF) {
254
- return $c;
255
- }
256
- }
257
-
258
- case '*':
259
- $this->get();
260
-
261
- for (;;) {
262
- switch($this->get()) {
263
- case '*':
264
- if ($this->peek() === '/') {
265
- $this->get();
266
- return ' ';
267
- }
268
- break;
269
-
270
- case null:
271
- throw new JSMinException('Unterminated comment.');
272
- }
273
- }
274
-
275
- default:
276
- return $c;
277
- }
278
- }
279
-
280
- return $c;
281
- }
282
-
283
- protected function peek() {
284
- $this->lookAhead = $this->get();
285
- return $this->lookAhead;
286
- }
287
- }
288
-
289
- // -- Exceptions ---------------------------------------------------------------
290
- class JSMinException extends Exception {}
291
- ?>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
classes/external/php/{minify-2.3.1-jsmin.php → jsmin.php} RENAMED
@@ -54,6 +54,8 @@
54
  * @link http://code.google.com/p/jsmin-php/
55
  */
56
 
 
 
57
  class JSMin {
58
  const ORD_LF = 10;
59
  const ORD_SPACE = 32;
@@ -194,19 +196,17 @@ class JSMin {
194
  // fallthrough intentional
195
  case self::ACTION_DELETE_A: // 2
196
  $this->a = $this->b;
197
- if ($this->a === "'" || $this->a === '"' || $this->a === '`') { // string/template literal
198
- $delimiter = $this->a;
199
  $str = $this->a; // in case needed for exception
200
  for(;;) {
201
  $this->output .= $this->a;
202
  $this->lastByteOut = $this->a;
 
203
  $this->a = $this->get();
204
  if ($this->a === $this->b) { // end quote
205
  break;
206
  }
207
- if ($delimiter === '`' && $this->a === "\n") {
208
- // leave the newline
209
- } elseif ($this->isEOF($this->a)) {
210
  $byte = $this->inputIndex - 1;
211
  throw new JSMin_UnterminatedStringException(
212
  "JSMin: Unterminated String at byte {$byte}: {$str}");
@@ -215,7 +215,8 @@ class JSMin {
215
  if ($this->a === '\\') {
216
  $this->output .= $this->a;
217
  $this->lastByteOut = $this->a;
218
- $this->a = $this->get();
 
219
  $str .= $this->a;
220
  }
221
  }
54
  * @link http://code.google.com/p/jsmin-php/
55
  */
56
 
57
+ // This is from https://github.com/mrclay/jsmin-php 2.3.2
58
+
59
  class JSMin {
60
  const ORD_LF = 10;
61
  const ORD_SPACE = 32;
196
  // fallthrough intentional
197
  case self::ACTION_DELETE_A: // 2
198
  $this->a = $this->b;
199
+ if ($this->a === "'" || $this->a === '"') { // string literal
 
200
  $str = $this->a; // in case needed for exception
201
  for(;;) {
202
  $this->output .= $this->a;
203
  $this->lastByteOut = $this->a;
204
+
205
  $this->a = $this->get();
206
  if ($this->a === $this->b) { // end quote
207
  break;
208
  }
209
+ if ($this->isEOF($this->a)) {
 
 
210
  $byte = $this->inputIndex - 1;
211
  throw new JSMin_UnterminatedStringException(
212
  "JSMin: Unterminated String at byte {$byte}: {$str}");
215
  if ($this->a === '\\') {
216
  $this->output .= $this->a;
217
  $this->lastByteOut = $this->a;
218
+
219
+ $this->a = $this->get();
220
  $str .= $this->a;
221
  }
222
  }
classes/external/php/minify-2.1.7-html.php DELETED
@@ -1,257 +0,0 @@
1
- <?php
2
- /**
3
- * Class Minify_HTML
4
- * @package Minify
5
- */
6
-
7
- /**
8
- * Compress HTML
9
- *
10
- * This is a heavy regex-based removal of whitespace, unnecessary comments and
11
- * tokens. IE conditional comments are preserved. There are also options to have
12
- * STYLE and SCRIPT blocks compressed by callback functions.
13
- *
14
- * A test suite is available.
15
- *
16
- * @package Minify
17
- * @author Stephen Clay <steve@mrclay.org>
18
- */
19
- class Minify_HTML {
20
- /**
21
- * @var boolean
22
- */
23
- protected $_jsCleanComments = true;
24
-
25
- /**
26
- * "Minify" an HTML page
27
- *
28
- * @param string $html
29
- *
30
- * @param array $options
31
- *
32
- * 'cssMinifier' : (optional) callback function to process content of STYLE
33
- * elements.
34
- *
35
- * 'jsMinifier' : (optional) callback function to process content of SCRIPT
36
- * elements. Note: the type attribute is ignored.
37
- *
38
- * 'xhtml' : (optional boolean) should content be treated as XHTML1.0? If
39
- * unset, minify will sniff for an XHTML doctype.
40
- *
41
- * @return string
42
- */
43
- public static function minify($html, $options = array()) {
44
- $min = new self($html, $options);
45
- return $min->process();
46
- }
47
-
48
-
49
- /**
50
- * Create a minifier object
51
- *
52
- * @param string $html
53
- *
54
- * @param array $options
55
- *
56
- * 'cssMinifier' : (optional) callback function to process content of STYLE
57
- * elements.
58
- *
59
- * 'jsMinifier' : (optional) callback function to process content of SCRIPT
60
- * elements. Note: the type attribute is ignored.
61
- *
62
- * 'jsCleanComments' : (optional) whether to remove HTML comments beginning and end of script block
63
- *
64
- * 'xhtml' : (optional boolean) should content be treated as XHTML1.0? If
65
- * unset, minify will sniff for an XHTML doctype.
66
- *
67
- * @return null
68
- */
69
- public function __construct($html, $options = array())
70
- {
71
- $this->_html = str_replace("\r\n", "\n", trim($html));
72
- if (isset($options['xhtml'])) {
73
- $this->_isXhtml = (bool)$options['xhtml'];
74
- }
75
- if (isset($options['cssMinifier'])) {
76
- $this->_cssMinifier = $options['cssMinifier'];
77
- }
78
- if (isset($options['jsMinifier'])) {
79
- $this->_jsMinifier = $options['jsMinifier'];
80
- }
81
- if (isset($options['jsCleanComments'])) {
82
- $this->_jsCleanComments = (bool)$options['jsCleanComments'];
83
- }
84
- }
85
-
86
-
87
- /**
88
- * Minify the markeup given in the constructor
89
- *
90
- * @return string
91
- */
92
- public function process()
93
- {
94
- if ($this->_isXhtml === null) {
95
- $this->_isXhtml = (false !== strpos($this->_html, '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML'));
96
- }
97
-
98
- $this->_replacementHash = 'MINIFYHTML' . md5($_SERVER['REQUEST_TIME']);
99
- $this->_placeholders = array();
100
-
101
- // replace SCRIPTs (and minify) with placeholders
102
- $this->_html = preg_replace_callback(
103
- '/(\\s*)<script(\\b[^>]*?>)([\\s\\S]*?)<\\/script>(\\s*)/i'
104
- ,array($this, '_removeScriptCB')
105
- ,$this->_html);
106
-
107
- // replace STYLEs (and minify) with placeholders
108
- $this->_html = preg_replace_callback(
109
- '/\\s*<style(\\b[^>]*>)([\\s\\S]*?)<\\/style>\\s*/i'
110
- ,array($this, '_removeStyleCB')
111
- ,$this->_html);
112
-
113
- // remove HTML comments (not containing IE conditional comments).
114
- $this->_html = preg_replace_callback(
115
- '/<!--([\\s\\S]*?)-->/'
116
- ,array($this, '_commentCB')
117
- ,$this->_html);
118
-
119
- // replace PREs with placeholders
120
- $this->_html = preg_replace_callback('/\\s*<pre(\\b[^>]*?>[\\s\\S]*?<\\/pre>)\\s*/i'
121
- ,array($this, '_removePreCB')
122
- ,$this->_html);
123
-
124
- // replace TEXTAREAs with placeholders
125
- $this->_html = preg_replace_callback(
126
- '/\\s*<textarea(\\b[^>]*?>[\\s\\S]*?<\\/textarea>)\\s*/i'
127
- ,array($this, '_removeTextareaCB')
128
- ,$this->_html);
129
-
130
- // trim each line.
131
- // @todo take into account attribute values that span multiple lines.
132
- $this->_html = preg_replace('/^\\s+|\\s+$/m', '', $this->_html);
133
-
134
- // remove ws around block/undisplayed elements
135
- $this->_html = preg_replace('/\\s+(<\\/?(?:area|base(?:font)?|blockquote|body'
136
- .'|caption|center|cite|col(?:group)?|dd|dir|div|dl|dt|fieldset|form'
137
- .'|frame(?:set)?|h[1-6]|head|hr|html|legend|li|link|map|menu|meta'
138
- .'|ol|opt(?:group|ion)|p|param|t(?:able|body|head|d|h||r|foot|itle)'
139
- .'|ul)\\b[^>]*>)/i', '$1', $this->_html);
140
-
141
- // remove ws outside of all elements
142
- $this->_html = preg_replace(
143
- '/>(\\s(?:\\s*))?([^<]+)(\\s(?:\s*))?</'
144
- ,'>$1$2$3<'
145
- ,$this->_html);
146
-
147
- // use newlines before 1st attribute in open tags (to limit line lengths)
148
- $this->_html = preg_replace('/(<[a-z\\-]+)\\s+([^>]+>)/i', "$1\n$2", $this->_html);
149
-
150
- // fill placeholders
151
- $this->_html = str_replace(
152
- array_keys($this->_placeholders)
153
- ,array_values($this->_placeholders)
154
- ,$this->_html
155
- );
156
- // issue 229: multi-pass to catch scripts that didn't get replaced in textareas
157
- $this->_html = str_replace(
158
- array_keys($this->_placeholders)
159
- ,array_values($this->_placeholders)
160
- ,$this->_html
161
- );
162
- return $this->_html;
163
- }
164
-
165
- protected function _commentCB($m)
166
- {
167
- return (0 === strpos($m[1], '[') || false !== strpos($m[1], '<!['))
168
- ? $m[0]
169
- : '';
170
- }
171
-
172
- protected function _reservePlace($content)
173
- {
174
- $placeholder = '%' . $this->_replacementHash . count($this->_placeholders) . '%';
175
- $this->_placeholders[$placeholder] = $content;
176
- return $placeholder;
177
- }
178
-
179
- protected $_isXhtml = null;
180
- protected $_replacementHash = null;
181
- protected $_placeholders = array();
182
- protected $_cssMinifier = null;
183
- protected $_jsMinifier = null;
184
-
185
- protected function _removePreCB($m)
186
- {
187
- return $this->_reservePlace("<pre{$m[1]}");
188
- }
189
-
190
- protected function _removeTextareaCB($m)
191
- {
192
- return $this->_reservePlace("<textarea{$m[1]}");
193
- }
194
-
195
- protected function _removeStyleCB($m)
196
- {
197
- $openStyle = "<style{$m[1]}";
198
- $css = $m[2];
199
- // remove HTML comments
200
- $css = preg_replace('/(?:^\\s*<!--|-->\\s*$)/', '', $css);
201
-
202
- // remove CDATA section markers
203
- $css = $this->_removeCdata($css);
204
-
205
- // minify
206
- $minifier = $this->_cssMinifier
207
- ? $this->_cssMinifier
208
- : 'trim';
209
- $css = call_user_func($minifier, $css);
210
-
211
- return $this->_reservePlace($this->_needsCdata($css)
212
- ? "{$openStyle}/*<![CDATA[*/{$css}/*]]>*/</style>"
213
- : "{$openStyle}{$css}</style>"
214
- );
215
- }
216
-
217
- protected function _removeScriptCB($m)
218
- {
219
- $openScript = "<script{$m[2]}";
220
- $js = $m[3];
221
-
222
- // whitespace surrounding? preserve at least one space
223
- $ws1 = ($m[1] === '') ? '' : ' ';
224
- $ws2 = ($m[4] === '') ? '' : ' ';
225
-
226
- // remove HTML comments (and ending "//" if present)
227
- if ($this->_jsCleanComments) {
228
- $js = preg_replace('/(?:^\\s*<!--\\s*|\\s*(?:\\/\\/)?\\s*-->\\s*$)/', '', $js);
229
- }
230
-
231
- // remove CDATA section markers
232
- $js = $this->_removeCdata($js);
233
-
234
- // minify
235
- $minifier = $this->_jsMinifier
236
- ? $this->_jsMinifier
237
- : 'trim';
238
- $js = call_user_func($minifier, $js);
239
-
240
- return $this->_reservePlace($this->_needsCdata($js)
241
- ? "{$ws1}{$openScript}/*<![CDATA[*/{$js}/*]]>*/</script>{$ws2}"
242
- : "{$ws1}{$openScript}{$js}</script>{$ws2}"
243
- );
244
- }
245
-
246
- protected function _removeCdata($str)
247
- {
248
- return (false !== strpos($str, '<![CDATA['))
249
- ? str_replace(array('<![CDATA[', ']]>'), '', $str)
250
- : $str;
251
- }
252
-
253
- protected function _needsCdata($str)
254
- {
255
- return ($this->_isXhtml && preg_match('/(?:[<&]|\\-\\-|\\]\\]>)/', $str));
256
- }
257
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
classes/external/php/minify-2.1.7-jsmin.php DELETED
@@ -1,447 +0,0 @@
1
- <?php
2
- /**
3
- * JSMin.php - modified PHP implementation of Douglas Crockford's JSMin.
4
- *
5
- * <code>
6
- * $minifiedJs = JSMin::minify($js);
7
- * </code>
8
- *
9
- * This is a modified port of jsmin.c. Improvements:
10
- *
11
- * Does not choke on some regexp literals containing quote characters. E.g. /'/
12
- *
13
- * Spaces are preserved after some add/sub operators, so they are not mistakenly
14
- * converted to post-inc/dec. E.g. a + ++b -> a+ ++b
15
- *
16
- * Preserves multi-line comments that begin with /*!
17
- *
18
- * PHP 5 or higher is required.
19
- *
20
- * Permission is hereby granted to use this version of the library under the
21
- * same terms as jsmin.c, which has the following license:
22
- *
23
- * --
24
- * Copyright (c) 2002 Douglas Crockford (www.crockford.com)
25
- *
26
- * Permission is hereby granted, free of charge, to any person obtaining a copy of
27
- * this software and associated documentation files (the "Software"), to deal in
28
- * the Software without restriction, including without limitation the rights to
29
- * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
30
- * of the Software, and to permit persons to whom the Software is furnished to do
31
- * so, subject to the following conditions:
32
- *
33
- * The above copyright notice and this permission notice shall be included in all
34
- * copies or substantial portions of the Software.
35
- *
36
- * The Software shall be used for Good, not Evil.
37
- *
38
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
39
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
40
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
41
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
42
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
43
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
44
- * SOFTWARE.
45
- * --
46
- *
47
- * @package JSMin
48
- * @author Ryan Grove <ryan@wonko.com> (PHP port)
49
- * @author Steve Clay <steve@mrclay.org> (modifications + cleanup)
50
- * @author Andrea Giammarchi <http://www.3site.eu> (spaceBeforeRegExp)
51
- * @copyright 2002 Douglas Crockford <douglas@crockford.com> (jsmin.c)
52
- * @copyright 2008 Ryan Grove <ryan@wonko.com> (PHP port)
53
- * @license http://opensource.org/licenses/mit-license.php MIT License
54
- * @link http://code.google.com/p/jsmin-php/
55
- */
56
-
57
- class JSMin {
58
- const ORD_LF = 10;
59
- const ORD_SPACE = 32;
60
- const ACTION_KEEP_A = 1;
61
- const ACTION_DELETE_A = 2;
62
- const ACTION_DELETE_A_B = 3;
63
-
64
- protected $a = "\n";
65
- protected $b = '';
66
- protected $input = '';
67
- protected $inputIndex = 0;
68
- protected $inputLength = 0;
69
- protected $lookAhead = null;
70
- protected $output = '';
71
- protected $lastByteOut = '';
72
- protected $keptComment = '';
73
-
74
- /**
75
- * Minify Javascript.
76
- *
77
- * @param string $js Javascript to be minified
78
- *
79
- * @return string
80
- */
81
- public static function minify($js)
82
- {
83
- $jsmin = new JSMin($js);
84
- return $jsmin->min();
85
- }
86
-
87
- /**
88
- * @param string $input
89
- */
90
- public function __construct($input)
91
- {
92
- $this->input = $input;
93
- }
94
-
95
- /**
96
- * Perform minification, return result
97
- *
98
- * @return string
99
- */
100
- public function min()
101
- {
102
- if ($this->output !== '') { // min already run
103
- return $this->output;
104
- }
105
-
106
- $mbIntEnc = null;
107
- if (function_exists('mb_strlen') && ((int)ini_get('mbstring.func_overload') & 2)) {
108
- $mbIntEnc = mb_internal_encoding();
109
- mb_internal_encoding('8bit');
110
- }
111
- $this->input = str_replace("\r\n", "\n", $this->input);
112
- $this->inputLength = strlen($this->input);
113
-
114
- $this->action(self::ACTION_DELETE_A_B);
115
-
116
- while ($this->a !== null) {
117
- // determine next command
118
- $command = self::ACTION_KEEP_A; // default
119
- if ($this->a === ' ') {
120
- if (($this->lastByteOut === '+' || $this->lastByteOut === '-')
121
- && ($this->b === $this->lastByteOut)) {
122
- // Don't delete this space. If we do, the addition/subtraction
123
- // could be parsed as a post-increment
124
- } elseif (! $this->isAlphaNum($this->b)) {
125
- $command = self::ACTION_DELETE_A;
126
- }
127
- } elseif ($this->a === "\n") {
128
- if ($this->b === ' ') {
129
- $command = self::ACTION_DELETE_A_B;
130
-
131
- // in case of mbstring.func_overload & 2, must check for null b,
132
- // otherwise mb_strpos will give WARNING
133
- } elseif ($this->b === null
134
- || (false === strpos('{[(+-!~', $this->b)
135
- && ! $this->isAlphaNum($this->b))) {
136
- $command = self::ACTION_DELETE_A;
137
- }
138
- } elseif (! $this->isAlphaNum($this->a)) {
139
- if ($this->b === ' '
140
- || ($this->b === "\n"
141
- && (false === strpos('}])+-"\'', $this->a)))) {
142
- $command = self::ACTION_DELETE_A_B;
143
- }
144
- }
145
- $this->action($command);
146
- }
147
- $this->output = trim($this->output);
148
-
149
- if ($mbIntEnc !== null) {
150
- mb_internal_encoding($mbIntEnc);
151
- }
152
- return $this->output;
153
- }
154
-
155
- /**
156
- * ACTION_KEEP_A = Output A. Copy B to A. Get the next B.
157
- * ACTION_DELETE_A = Copy B to A. Get the next B.
158
- * ACTION_DELETE_A_B = Get the next B.
159
- *
160
- * @param int $command
161
- * @throws JSMin_UnterminatedRegExpException|JSMin_UnterminatedStringException
162
- */
163
- protected function action($command)
164
- {
165
- // make sure we don't compress "a + ++b" to "a+++b", etc.
166
- if ($command === self::ACTION_DELETE_A_B
167
- && $this->b === ' '
168
- && ($this->a === '+' || $this->a === '-')) {
169
- // Note: we're at an addition/substraction operator; the inputIndex
170
- // will certainly be a valid index
171
- if ($this->input[$this->inputIndex] === $this->a) {
172
- // This is "+ +" or "- -". Don't delete the space.
173
- $command = self::ACTION_KEEP_A;
174
- }
175
- }
176
-
177
- switch ($command) {
178
- case self::ACTION_KEEP_A: // 1
179
- $this->output .= $this->a;
180
-
181
- if ($this->keptComment) {
182
- $this->output = rtrim($this->output, "\n");
183
- $this->output .= $this->keptComment;
184
- $this->keptComment = '';
185
- }
186
-
187
- $this->lastByteOut = $this->a;
188
-
189
- // fallthrough intentional
190
- case self::ACTION_DELETE_A: // 2
191
- $this->a = $this->b;
192
- if ($this->a === "'" || $this->a === '"') { // string literal
193
- $str = $this->a; // in case needed for exception
194
- for(;;) {
195
- $this->output .= $this->a;
196
- $this->lastByteOut = $this->a;
197
-
198
- $this->a = $this->get();
199
- if ($this->a === $this->b) { // end quote
200
- break;
201
- }
202
- if ($this->isEOF($this->a)) {
203
- throw new JSMin_UnterminatedStringException(
204
- "JSMin: Unterminated String at byte {$this->inputIndex}: {$str}");
205
- }
206
- $str .= $this->a;
207
- if ($this->a === '\\') {
208
- $this->output .= $this->a;
209
- $this->lastByteOut = $this->a;
210
-
211
- $this->a = $this->get();
212
- $str .= $this->a;
213
- }
214
- }
215
- }
216
-
217
- // fallthrough intentional
218
- case self::ACTION_DELETE_A_B: // 3
219
- $this->b = $this->next();
220
- if ($this->b === '/' && $this->isRegexpLiteral()) {
221
- $this->output .= $this->a . $this->b;
222
- $pattern = '/'; // keep entire pattern in case we need to report it in the exception
223
- for(;;) {
224
- $this->a = $this->get();
225
- $pattern .= $this->a;
226
- if ($this->a === '[') {
227
- for(;;) {
228
- $this->output .= $this->a;
229
- $this->a = $this->get();
230
- $pattern .= $this->a;
231
- if ($this->a === ']') {
232
- break;
233
- }
234
- if ($this->a === '\\') {
235
- $this->output .= $this->a;
236
- $this->a = $this->get();
237
- $pattern .= $this->a;
238
- }
239
- if ($this->isEOF($this->a)) {
240
- throw new JSMin_UnterminatedRegExpException(
241
- "JSMin: Unterminated set in RegExp at byte "
242
- . $this->inputIndex .": {$pattern}");
243
- }
244
- }
245
- }
246
-
247
- if ($this->a === '/') { // end pattern
248
- break; // while (true)
249
- } elseif ($this->a === '\\') {
250
- $this->output .= $this->a;
251
- $this->a = $this->get();
252
- $pattern .= $this->a;
253
- } elseif ($this->isEOF($this->a)) {
254
- throw new JSMin_UnterminatedRegExpException(
255
- "JSMin: Unterminated RegExp at byte {$this->inputIndex}: {$pattern}");
256
- }
257
- $this->output .= $this->a;
258
- $this->lastByteOut = $this->a;
259
- }
260
- $this->b = $this->next();
261
- }
262
- // end case ACTION_DELETE_A_B
263
- }
264
- }
265
-
266
- /**
267
- * @return bool
268
- */
269
- protected function isRegexpLiteral()
270
- {
271
- if (false !== strpos("(,=:[!&|?+-~*{;", $this->a)) {
272
- // we obviously aren't dividing
273
- return true;
274
- }
275
-
276
- // we have to check for a preceding keyword, and we don't need to pattern
277
- // match over the whole output.
278
- $recentOutput = substr($this->output, -10);
279
-
280
- // check if return/typeof directly precede a pattern without a space
281
- foreach (array('return', 'typeof') as $keyword) {
282
- if ($this->a !== substr($keyword, -1)) {
283
- // certainly wasn't keyword
284
- continue;
285
- }
286
- if (preg_match("~(^|[\\s\\S])" . substr($keyword, 0, -1) . "$~", $recentOutput, $m)) {
287
- if ($m[1] === '' || !$this->isAlphaNum($m[1])) {
288
- return true;
289
- }
290
- }
291
- }
292
-
293
- // check all keywords
294
- if ($this->a === ' ' || $this->a === "\n") {
295
- if (preg_match('~(^|[\\s\\S])(?:case|else|in|return|typeof)$~', $recentOutput, $m)) {
296
- if ($m[1] === '' || !$this->isAlphaNum($m[1])) {
297
- return true;
298
- }
299
- }
300
- }
301
-
302
- return false;
303
- }
304
-
305
- /**
306
- * Return the next character from stdin. Watch out for lookahead. If the character is a control character,
307
- * translate it to a space or linefeed.
308
- *
309
- * @return string
310
- */
311
- protected function get()
312
- {
313
- $c = $this->lookAhead;
314
- $this->lookAhead = null;
315
- if ($c === null) {
316
- // getc(stdin)
317
- if ($this->inputIndex < $this->inputLength) {
318
- $c = $this->input[$this->inputIndex];
319
- $this->inputIndex += 1;
320
- } else {
321
- $c = null;
322
- }
323
- }
324
- if (ord($c) >= self::ORD_SPACE || $c === "\n" || $c === null) {
325
- return $c;
326
- }
327
- if ($c === "\r") {
328
- return "\n";
329
- }
330
- return ' ';
331
- }
332
-
333
- /**
334
- * Does $a indicate end of input?
335
- *
336
- * @param string $a
337
- * @return bool
338
- */
339
- protected function isEOF($a)
340
- {
341
- return ord($a) <= self::ORD_LF;
342
- }
343
-
344
- /**
345
- * Get next char (without getting it). If is ctrl character, translate to a space or newline.
346
- *
347
- * @return string
348
- */
349
- protected function peek()
350
- {
351
- $this->lookAhead = $this->get();
352
- return $this->lookAhead;
353
- }
354
-
355
- /**
356
- * Return true if the character is a letter, digit, underscore, dollar sign, or non-ASCII character.
357
- *
358
- * @param string $c
359
- *
360
- * @return bool
361
- */
362
- protected function isAlphaNum($c)
363
- {
364
- return (preg_match('/^[a-z0-9A-Z_\\$\\\\]$/', $c) || ord($c) > 126);
365
- }
366
-
367
- /**
368
- * Consume a single line comment from input (possibly retaining it)
369
- */
370
- protected function consumeSingleLineComment()
371
- {
372
- $comment = '';
373
- while (true) {
374
- $get = $this->get();
375
- $comment .= $get;
376
- if (ord($get) <= self::ORD_LF) { // end of line reached
377
- // if IE conditional comment
378
- if (preg_match('/^\\/@(?:cc_on|if|elif|else|end)\\b/', $comment)) {
379
- $this->keptComment .= "/{$comment}";
380
- }
381
- return;
382
- }
383
- }
384
- }
385
-
386
- /**
387
- * Consume a multiple line comment from input (possibly retaining it)
388
- *
389
- * @throws JSMin_UnterminatedCommentException
390
- */
391
- protected function consumeMultipleLineComment()
392
- {
393
- $this->get();
394
- $comment = '';
395
- for(;;) {
396
- $get = $this->get();
397
- if ($get === '*') {
398
- if ($this->peek() === '/') { // end of comment reached
399
- $this->get();
400
- if (0 === strpos($comment, '!')) {
401
- // preserved by YUI Compressor
402
- if (!$this->keptComment) {
403
- // don't prepend a newline if two comments right after one another
404
- $this->keptComment = "\n";
405
- }
406
- $this->keptComment .= "/*!" . substr($comment, 1) . "*/\n";
407
- } else if (preg_match('/^@(?:cc_on|if|elif|else|end)\\b/', $comment)) {
408
- // IE conditional
409
- $this->keptComment .= "/*{$comment}*/";
410
- }
411
- return;
412
- }
413
- } elseif ($get === null) {
414
- throw new JSMin_UnterminatedCommentException(
415
- "JSMin: Unterminated comment at byte {$this->inputIndex}: /*{$comment}");
416
- }
417
- $comment .= $get;
418
- }
419
- }
420
-
421
- /**
422
- * Get the next character, skipping over comments. Some comments may be preserved.
423
- *
424
- * @return string
425
- */
426
- protected function next()
427
- {
428
- $get = $this->get();
429
- if ($get === '/') {
430
- switch ($this->peek()) {
431
- case '/':
432
- $this->consumeSingleLineComment();
433
- $get = "\n";
434
- break;
435
- case '*':
436
- $this->consumeMultipleLineComment();
437
- $get = ' ';
438
- break;
439
- }
440
- }
441
- return $get;
442
- }
443
- }
444
-
445
- class JSMin_UnterminatedStringException extends Exception {}
446
- class JSMin_UnterminatedCommentException extends Exception {}
447
- class JSMin_UnterminatedRegExpException extends Exception {}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
classes/external/php/minify-css-compressor.php DELETED
@@ -1,250 +0,0 @@
1
- <?php
2
- /**
3
- * Class Minify_CSS_Compressor
4
- * @package Minify
5
- */
6
-
7
- /**
8
- * Compress CSS
9
- *
10
- * This is a heavy regex-based removal of whitespace, unnecessary
11
- * comments and tokens, and some CSS value minimization, where practical.
12
- * Many steps have been taken to avoid breaking comment-based hacks,
13
- * including the ie5/mac filter (and its inversion), but expect tricky
14
- * hacks involving comment tokens in 'content' value strings to break
15
- * minimization badly. A test suite is available.
16
- *
17
- * @package Minify
18
- * @author Stephen Clay <steve@mrclay.org>
19
- * @author http://code.google.com/u/1stvamp/ (Issue 64 patch)
20
- */
21
- class Minify_CSS_Compressor {
22
-
23
- /**
24
- * Minify a CSS string
25
- *
26
- * @param string $css
27
- *
28
- * @param array $options (currently ignored)
29
- *
30
- * @return string
31
- */
32
- public static function process($css, $options = array())
33
- {
34
- $obj = new Minify_CSS_Compressor($options);
35
- return $obj->_process($css);
36
- }
37
-
38
- /**
39
- * @var array options
40
- */
41
- protected $_options = null;
42
-
43
- /**
44
- * @var bool Are we "in" a hack?
45
- *
46
- * I.e. are some browsers targetted until the next comment?
47
- */
48
- protected $_inHack = false;
49
-
50
-
51
- /**
52
- * Constructor
53
- *
54
- * @param array $options (currently ignored)
55
- *
56
- * @return null
57
- */
58
- private function __construct($options) {
59
- $this->_options = $options;
60
- }
61
-
62
- /**
63
- * Minify a CSS string
64
- *
65
- * @param string $css
66
- *
67
- * @return string
68
- */
69
- protected function _process($css)
70
- {
71
- $css = str_replace("\r\n", "\n", $css);
72
-
73
- // preserve empty comment after '>'
74
- // http://www.webdevout.net/css-hacks#in_css-selectors
75
- $css = preg_replace('@>/\\*\\s*\\*/@', '>/*keep*/', $css);
76
-
77
- // preserve empty comment between property and value
78
- // http://css-discuss.incutio.com/?page=BoxModelHack
79
- $css = preg_replace('@/\\*\\s*\\*/\\s*:@', '/*keep*/:', $css);
80
- $css = preg_replace('@:\\s*/\\*\\s*\\*/@', ':/*keep*/', $css);
81
-
82
- // apply callback to all valid comments (and strip out surrounding ws
83
- $css = preg_replace_callback('@\\s*/\\*([\\s\\S]*?)\\*/\\s*@'
84
- ,array($this, '_commentCB'), $css);
85
-
86
- // remove ws around { } and last semicolon in declaration block
87
- $css = preg_replace('/\\s*{\\s*/', '{', $css);
88
- $css = preg_replace('/;?\\s*}\\s*/', '}', $css);
89
-
90
- // remove ws surrounding semicolons
91
- $css = preg_replace('/\\s*;\\s*/', ';', $css);
92
-
93
- // remove ws around urls
94
- $css = preg_replace('/
95
- url\\( # url(
96
- \\s*
97
- ([^\\)]+?) # 1 = the URL (really just a bunch of non right parenthesis)
98
- \\s*
99
- \\) # )
100
- /x', 'url($1)', $css);
101
-
102
- // remove ws between rules and colons
103
- $css = preg_replace('/
104
- \\s*
105
- ([{;]) # 1 = beginning of block or rule separator
106
- \\s*
107
- ([\\*_]?[\\w\\-]+) # 2 = property (and maybe IE filter)
108
- \\s*
109
- :
110
- \\s*
111
- (\\b|[#\'"]) # 3 = first character of a value
112
- /x', '$1$2:$3', $css);
113
-
114
- // remove ws in selectors
115
- $css = preg_replace_callback('/
116
- (?: # non-capture
117
- \\s*
118
- [^~>+,\\s]+ # selector part
119
- \\s*
120
- [,>+~] # combinators
121
- )+
122
- \\s*
123
- [^~>+,\\s]+ # selector part
124
- { # open declaration block
125
- /x'
126
- ,array($this, '_selectorsCB'), $css);
127
-
128
- // minimize hex colors
129
- $css = preg_replace('/([^=])#([a-f\\d])\\2([a-f\\d])\\3([a-f\\d])\\4([\\s;\\}])/i'
130
- , '$1#$2$3$4$5', $css);
131
-
132
- // remove spaces between font families
133
- $css = preg_replace_callback('/font-family:([^;}]+)([;}])/'
134
- ,array($this, '_fontFamilyCB'), $css);
135
-
136
- $css = preg_replace('/@import\\s+url/', '@import url', $css);
137
-
138
- // replace any ws involving newlines with a single newline
139
- $css = preg_replace('/[ \\t]*\\n+\\s*/', "\n", $css);
140
-
141
- // separate common descendent selectors w/ newlines (to limit line lengths)
142
- $css = preg_replace('/([\\w#\\.\\*]+)\\s+([\\w#\\.\\*]+){/', "$1\n$2{", $css);
143
-
144
- // Use newline after 1st numeric value (to limit line lengths).
145
- $css = preg_replace('/
146
- ((?:padding|margin|border|outline):\\d+(?:px|em)?) # 1 = prop : 1st numeric value
147
- \\s+
148
- /x'
149
- ,"$1\n", $css);
150
-
151
- // prevent triggering IE6 bug: http://www.crankygeek.com/ie6pebug/
152
- $css = preg_replace('/:first-l(etter|ine)\\{/', ':first-l$1 {', $css);
153
-
154
- return trim($css);
155
- }
156
-
157
- /**
158
- * Replace what looks like a set of selectors
159
- *
160
- * @param array $m regex matches
161
- *
162
- * @return string
163
- */
164
- protected function _selectorsCB($m)
165
- {
166
- // remove ws around the combinators
167
- return preg_replace('/\\s*([,>+~])\\s*/', '$1', $m[0]);
168
- }
169
-
170
- /**
171
- * Process a comment and return a replacement
172
- *
173
- * @param array $m regex matches
174
- *
175
- * @return string
176
- */
177
- protected function _commentCB($m)
178
- {
179
- $hasSurroundingWs = (trim($m[0]) !== $m[1]);
180
- $m = $m[1];
181
- // $m is the comment content w/o the surrounding tokens,
182
- // but the return value will replace the entire comment.
183
- if ($m === 'keep') {
184
- return '/**/';
185
- }
186
- if ($m === '" "') {
187
- // component of http://tantek.com/CSS/Examples/midpass.html
188
- return '/*" "*/';
189
- }
190
- if (preg_match('@";\\}\\s*\\}/\\*\\s+@', $m)) {
191
- // component of http://tantek.com/CSS/Examples/midpass.html
192
- return '/*";}}/* */';
193
- }
194
- if ($this->_inHack) {
195
- // inversion: feeding only to one browser
196
- if (preg_match('@
197
- ^/ # comment started like /*/
198
- \\s*
199
- (\\S[\\s\\S]+?) # has at least some non-ws content
200
- \\s*
201
- /\\* # ends like /*/ or /**/
202
- @x', $m, $n)) {
203
- // end hack mode after this comment, but preserve the hack and comment content
204
- $this->_inHack = false;
205
- return "/*/{$n[1]}/**/";
206
- }
207
- }
208
- if (substr($m, -1) === '\\') { // comment ends like \*/
209
- // begin hack mode and preserve hack
210
- $this->_inHack = true;
211
- return '/*\\*/';
212
- }
213
- if ($m !== '' && $m[0] === '/') { // comment looks like /*/ foo */
214
- // begin hack mode and preserve hack
215
- $this->_inHack = true;
216
- return '/*/*/';
217
- }
218
- if ($this->_inHack) {
219
- // a regular comment ends hack mode but should be preserved
220
- $this->_inHack = false;
221
- return '/**/';
222
- }
223
- // Issue 107: if there's any surrounding whitespace, it may be important, so
224
- // replace the comment with a single space
225
- return $hasSurroundingWs // remove all other comments
226
- ? ' '
227
- : '';
228
- }
229
-
230
- /**
231
- * Process a font-family listing and return a replacement
232
- *
233
- * @param array $m regex matches
234
- *
235
- * @return string
236
- */
237
- protected function _fontFamilyCB($m)
238
- {
239
- $m[1] = preg_replace('/
240
- \\s*
241
- (
242
- "[^"]+" # 1 = family in double qutoes
243
- |\'[^\']+\' # or 1 = family in single quotes
244
- |[\\w\\-]+ # or 1 = unquoted family
245
- )
246
- \\s*
247
- /x', '$1', $m[1]);
248
- return 'font-family:' . $m[1] . $m[2];
249
- }
250
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
classes/external/php/minify-html.php CHANGED
@@ -1,18 +1,18 @@
1
  <?php
2
  /**
3
- * Class Minify_HTML
4
  * @package Minify
5
  */
6
 
7
  /**
8
  * Compress HTML
9
  *
10
- * This is a heavy regex-based removal of whitespace, unnecessary comments and
11
  * tokens. IE conditional comments are preserved. There are also options to have
12
- * STYLE and SCRIPT blocks compressed by callback functions.
13
- *
14
  * A test suite is available.
15
- *
16
  * @package Minify
17
  * @author Stephen Clay <steve@mrclay.org>
18
  */
@@ -27,24 +27,24 @@ class Minify_HTML {
27
  *
28
  * 'cssMinifier' : (optional) callback function to process content of STYLE
29
  * elements.
30
- *
31
  * 'jsMinifier' : (optional) callback function to process content of SCRIPT
32
  * elements. Note: the type attribute is ignored.
33
- *
34
  * 'xhtml' : (optional boolean) should content be treated as XHTML1.0? If
35
  * unset, minify will sniff for an XHTML doctype.
36
- *
37
  * 'keepComments' : (optional boolean) should the HTML comments be kept
38
  * in the HTML Code?
39
- *
40
  * @return string
41
  */
42
  public static function minify($html, $options = array()) {
43
  $min = new Minify_HTML($html, $options);
44
  return $min->process();
45
  }
46
-
47
-
48
  /**
49
  * Create a minifier object
50
  *
@@ -54,16 +54,16 @@ class Minify_HTML {
54
  *
55
  * 'cssMinifier' : (optional) callback function to process content of STYLE
56
  * elements.
57
- *
58
  * 'jsMinifier' : (optional) callback function to process content of SCRIPT
59
  * elements. Note: the type attribute is ignored.
60
- *
61
  * 'xhtml' : (optional boolean) should content be treated as XHTML1.0? If
62
  * unset, minify will sniff for an XHTML doctype.
63
- *
64
  * 'xhtml' : (optional boolean) should content be treated as XHTML1.0? If
65
  * unset, minify will sniff for an XHTML doctype.
66
- *
67
  * @return null
68
  */
69
  public function __construct($html, $options = array())
@@ -82,11 +82,11 @@ class Minify_HTML {
82
  $this->_keepComments = $options['keepComments'];
83
  }
84
  }
85
-
86
-
87
  /**
88
  * Minify the markeup given in the constructor
89
- *
90
  * @return string
91
  */
92
  public function process()
@@ -94,67 +94,67 @@ class Minify_HTML {
94
  if ($this->_isXhtml === null) {
95
  $this->_isXhtml = (false !== strpos($this->_html, '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML'));
96
  }
97
-
98
  $this->_replacementHash = 'MINIFYHTML' . md5($_SERVER['REQUEST_TIME']);
99
  $this->_placeholders = array();
100
-
101
  // replace SCRIPTs (and minify) with placeholders
102
  $this->_html = preg_replace_callback(
103
  '/(\\s*)(<script\\b[^>]*?>)([\\s\\S]*?)<\\/script>(\\s*)/i'
104
  ,array($this, '_removeScriptCB')
105
  ,$this->_html);
106
-
107
  // replace STYLEs (and minify) with placeholders
108
  $this->_html = preg_replace_callback(
109
  '/\\s*(<style\\b[^>]*?>)([\\s\\S]*?)<\\/style>\\s*/i'
110
  ,array($this, '_removeStyleCB')
111
  ,$this->_html);
112
-
113
  // remove HTML comments (not containing IE conditional comments).
114
- if ($this->_keepComments == false) {
115
- $this->_html = preg_replace_callback(
116
- '/<!--([\\s\\S]*?)-->/'
117
- ,array($this, '_commentCB')
118
- ,$this->_html);
119
  }
120
-
121
  // replace PREs with placeholders
122
  $this->_html = preg_replace_callback('/\\s*(<pre\\b[^>]*?>[\\s\\S]*?<\\/pre>)\\s*/i'
123
  ,array($this, '_removePreCB')
124
  ,$this->_html);
125
-
126
  // replace TEXTAREAs with placeholders
127
  $this->_html = preg_replace_callback(
128
  '/\\s*(<textarea\\b[^>]*?>[\\s\\S]*?<\\/textarea>)\\s*/i'
129
  ,array($this, '_removeTextareaCB')
130
  ,$this->_html);
131
-
132
- // replace data: URIs with placeholders
133
  $this->_html = preg_replace_callback(
134
  '/(=("|\')data:.*\\2)/Ui'
135
  ,array($this, '_removeDataURICB')
136
  ,$this->_html);
137
-
138
  // trim each line.
139
  // replace by space instead of '' to avoid newline after opening tag getting zapped
140
  $this->_html = preg_replace('/^\s+|\s+$/m', ' ', $this->_html);
141
-
142
  // remove ws around block/undisplayed elements
143
  $this->_html = preg_replace('/\\s+(<\\/?(?:area|article|aside|base(?:font)?|blockquote|body'
144
  .'|canvas|caption|center|col(?:group)?|dd|dir|div|dl|dt|fieldset|figcaption|figure|footer|form'
145
  .'|frame(?:set)?|h[1-6]|head|header|hgroup|hr|html|legend|li|link|main|map|menu|meta|nav'
146
  .'|ol|opt(?:group|ion)|output|p|param|section|t(?:able|body|head|d|h||r|foot|itle)'
147
  .'|ul|video)\\b[^>]*>)/i', '$1', $this->_html);
148
-
149
  // remove ws outside of all elements
150
  $this->_html = preg_replace_callback(
151
  '/>([^<]+)</'
152
  ,array($this, '_outsideTagCB')
153
  ,$this->_html);
154
-
155
  // use newlines before 1st attribute in open tags (to limit line lengths)
156
  //$this->_html = preg_replace('/(<[a-z\\-]+)\\s+([^>]+>)/i', "$1\n$2", $this->_html);
157
-
158
  // fill placeholders
159
  $this->_html = str_replace(
160
  array_keys($this->_placeholders)
@@ -163,14 +163,14 @@ class Minify_HTML {
163
  );
164
  return $this->_html;
165
  }
166
-
167
  protected function _commentCB($m)
168
  {
169
  return (0 === strpos($m[1], '[') || false !== strpos($m[1], '<!['))
170
  ? $m[0]
171
  : '';
172
  }
173
-
174
  protected function _reservePlace($content)
175
  {
176
  $placeholder = '%' . $this->_replacementHash . count($this->_placeholders) . '%';
@@ -183,24 +183,24 @@ class Minify_HTML {
183
  protected $_placeholders = array();
184
  protected $_cssMinifier = null;
185
  protected $_jsMinifier = null;
186
- protected $_keepComments = false;
187
 
188
  protected function _outsideTagCB($m)
189
  {
190
  return '>' . preg_replace('/^\\s+|\\s+$/', ' ', $m[1]) . '<';
191
  }
192
-
193
  protected function _removePreCB($m)
194
  {
195
  return $this->_reservePlace($m[1]);
196
  }
197
-
198
  protected function _removeTextareaCB($m)
199
  {
200
  return $this->_reservePlace($m[1]);
201
  }
202
-
203
- protected function _removeDataURICB($m)
204
  {
205
  return $this->_reservePlace($m[1]);
206
  }
@@ -211,16 +211,16 @@ class Minify_HTML {
211
  $css = $m[2];
212
  // remove HTML comments
213
  $css = preg_replace('/(?:^\\s*<!--|-->\\s*$)/', '', $css);
214
-
215
  // remove CDATA section markers
216
  $css = $this->_removeCdata($css);
217
-
218
  // minify
219
  $minifier = $this->_cssMinifier
220
  ? $this->_cssMinifier
221
  : 'trim';
222
  $css = call_user_func($minifier, $css);
223
-
224
  return $this->_reservePlace($this->_needsCdata($css)
225
  ? "{$openStyle}/*<![CDATA[*/{$css}/*]]>*/</style>"
226
  : "{$openStyle}{$css}</style>"
@@ -231,23 +231,25 @@ class Minify_HTML {
231
  {
232
  $openScript = $m[2];
233
  $js = $m[3];
234
-
235
  // whitespace surrounding? preserve at least one space
236
  $ws1 = ($m[1] === '') ? '' : ' ';
237
  $ws2 = ($m[4] === '') ? '' : ' ';
238
-
239
- // remove HTML comments (and ending "//" if present)
240
- $js = preg_replace('/(?:^\\s*<!--\\s*|\\s*(?:\\/\\/)?\\s*-->\\s*$)/', '', $js);
241
-
242
- // remove CDATA section markers
243
- $js = $this->_removeCdata($js);
244
-
 
 
245
  // minify
246
  $minifier = $this->_jsMinifier
247
  ? $this->_jsMinifier
248
- : 'trim';
249
  $js = call_user_func($minifier, $js);
250
-
251
  return $this->_reservePlace($this->_needsCdata($js)
252
  ? "{$ws1}{$openScript}/*<![CDATA[*/{$js}/*]]>*/</script>{$ws2}"
253
  : "{$ws1}{$openScript}{$js}</script>{$ws2}"
@@ -260,7 +262,7 @@ class Minify_HTML {
260
  ? str_replace(array('/* <![CDATA[ */','/* ]]> */','/*<![CDATA[*/','/*]]>*/','<![CDATA[', ']]>'), '', $str)
261
  : $str;
262
  }
263
-
264
  protected function _needsCdata($str)
265
  {
266
  return ($this->_isXhtml && preg_match('/(?:[<&]|\\-\\-|\\]\\]>)/', $str));
1
  <?php
2
  /**
3
+ * Class Minify_HTML
4
  * @package Minify
5
  */
6
 
7
  /**
8
  * Compress HTML
9
  *
10
+ * This is a heavy regex-based removal of whitespace, unnecessary comments and
11
  * tokens. IE conditional comments are preserved. There are also options to have
12
+ * STYLE and SCRIPT blocks compressed by callback functions.
13
+ *
14
  * A test suite is available.
15
+ *
16
  * @package Minify
17
  * @author Stephen Clay <steve@mrclay.org>
18
  */
27
  *
28
  * 'cssMinifier' : (optional) callback function to process content of STYLE
29
  * elements.
30
+ *
31
  * 'jsMinifier' : (optional) callback function to process content of SCRIPT
32
  * elements. Note: the type attribute is ignored.
33
+ *
34
  * 'xhtml' : (optional boolean) should content be treated as XHTML1.0? If
35
  * unset, minify will sniff for an XHTML doctype.
36
+ *
37
  * 'keepComments' : (optional boolean) should the HTML comments be kept
38
  * in the HTML Code?
39
+ *
40
  * @return string
41
  */
42
  public static function minify($html, $options = array()) {
43
  $min = new Minify_HTML($html, $options);
44
  return $min->process();
45
  }
46
+
47
+
48
  /**
49
  * Create a minifier object
50
  *
54
  *
55
  * 'cssMinifier' : (optional) callback function to process content of STYLE
56
  * elements.
57
+ *
58
  * 'jsMinifier' : (optional) callback function to process content of SCRIPT
59
  * elements. Note: the type attribute is ignored.
60
+ *
61
  * 'xhtml' : (optional boolean) should content be treated as XHTML1.0? If
62
  * unset, minify will sniff for an XHTML doctype.
63
+ *
64
  * 'xhtml' : (optional boolean) should content be treated as XHTML1.0? If
65
  * unset, minify will sniff for an XHTML doctype.
66
+ *
67
  * @return null
68
  */
69
  public function __construct($html, $options = array())
82
  $this->_keepComments = $options['keepComments'];
83
  }
84
  }
85
+
86
+
87
  /**
88
  * Minify the markeup given in the constructor
89
+ *
90
  * @return string
91
  */
92
  public function process()
94
  if ($this->_isXhtml === null) {
95
  $this->_isXhtml = (false !== strpos($this->_html, '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML'));
96
  }
97
+
98
  $this->_replacementHash = 'MINIFYHTML' . md5($_SERVER['REQUEST_TIME']);
99
  $this->_placeholders = array();
100
+
101
  // replace SCRIPTs (and minify) with placeholders
102
  $this->_html = preg_replace_callback(
103
  '/(\\s*)(<script\\b[^>]*?>)([\\s\\S]*?)<\\/script>(\\s*)/i'
104
  ,array($this, '_removeScriptCB')
105
  ,$this->_html);
106
+
107
  // replace STYLEs (and minify) with placeholders
108
  $this->_html = preg_replace_callback(
109
  '/\\s*(<style\\b[^>]*?>)([\\s\\S]*?)<\\/style>\\s*/i'
110
  ,array($this, '_removeStyleCB')
111
  ,$this->_html);
112
+
113
  // remove HTML comments (not containing IE conditional comments).
114
+ if ($this->_keepComments == false) {
115
+ $this->_html = preg_replace_callback(
116
+ '/<!--([\\s\\S]*?)-->/'
117
+ ,array($this, '_commentCB')
118
+ ,$this->_html);
119
  }
120
+
121
  // replace PREs with placeholders
122
  $this->_html = preg_replace_callback('/\\s*(<pre\\b[^>]*?>[\\s\\S]*?<\\/pre>)\\s*/i'
123
  ,array($this, '_removePreCB')
124
  ,$this->_html);
125
+
126
  // replace TEXTAREAs with placeholders
127
  $this->_html = preg_replace_callback(
128
  '/\\s*(<textarea\\b[^>]*?>[\\s\\S]*?<\\/textarea>)\\s*/i'
129
  ,array($this, '_removeTextareaCB')
130
  ,$this->_html);
131
+
132
+ // replace data: URIs with placeholders
133
  $this->_html = preg_replace_callback(
134
  '/(=("|\')data:.*\\2)/Ui'
135
  ,array($this, '_removeDataURICB')
136
  ,$this->_html);
137
+
138
  // trim each line.
139
  // replace by space instead of '' to avoid newline after opening tag getting zapped
140
  $this->_html = preg_replace('/^\s+|\s+$/m', ' ', $this->_html);
141
+
142
  // remove ws around block/undisplayed elements
143
  $this->_html = preg_replace('/\\s+(<\\/?(?:area|article|aside|base(?:font)?|blockquote|body'
144
  .'|canvas|caption|center|col(?:group)?|dd|dir|div|dl|dt|fieldset|figcaption|figure|footer|form'
145
  .'|frame(?:set)?|h[1-6]|head|header|hgroup|hr|html|legend|li|link|main|map|menu|meta|nav'
146
  .'|ol|opt(?:group|ion)|output|p|param|section|t(?:able|body|head|d|h||r|foot|itle)'
147
  .'|ul|video)\\b[^>]*>)/i', '$1', $this->_html);
148
+
149
  // remove ws outside of all elements
150
  $this->_html = preg_replace_callback(
151
  '/>([^<]+)</'
152
  ,array($this, '_outsideTagCB')
153
  ,$this->_html);
154
+
155
  // use newlines before 1st attribute in open tags (to limit line lengths)
156
  //$this->_html = preg_replace('/(<[a-z\\-]+)\\s+([^>]+>)/i', "$1\n$2", $this->_html);
157
+
158
  // fill placeholders
159
  $this->_html = str_replace(
160
  array_keys($this->_placeholders)
163
  );
164
  return $this->_html;
165
  }
166
+
167
  protected function _commentCB($m)
168
  {
169
  return (0 === strpos($m[1], '[') || false !== strpos($m[1], '<!['))
170
  ? $m[0]
171
  : '';
172
  }
173
+
174
  protected function _reservePlace($content)
175
  {
176
  $placeholder = '%' . $this->_replacementHash . count($this->_placeholders) . '%';
183
  protected $_placeholders = array();
184
  protected $_cssMinifier = null;
185
  protected $_jsMinifier = null;
186
+ protected $_keepComments = false;
187
 
188
  protected function _outsideTagCB($m)
189
  {
190
  return '>' . preg_replace('/^\\s+|\\s+$/', ' ', $m[1]) . '<';
191
  }
192
+
193
  protected function _removePreCB($m)
194
  {
195
  return $this->_reservePlace($m[1]);
196
  }
197
+
198
  protected function _removeTextareaCB($m)
199
  {
200
  return $this->_reservePlace($m[1]);
201
  }
202
+
203
+ protected function _removeDataURICB($m)
204
  {
205
  return $this->_reservePlace($m[1]);
206
  }
211
  $css = $m[2];
212
  // remove HTML comments
213
  $css = preg_replace('/(?:^\\s*<!--|-->\\s*$)/', '', $css);
214
+
215
  // remove CDATA section markers
216
  $css = $this->_removeCdata($css);
217
+
218
  // minify
219
  $minifier = $this->_cssMinifier
220
  ? $this->_cssMinifier
221
  : 'trim';
222
  $css = call_user_func($minifier, $css);
223
+
224
  return $this->_reservePlace($this->_needsCdata($css)
225
  ? "{$openStyle}/*<![CDATA[*/{$css}/*]]>*/</style>"
226
  : "{$openStyle}{$css}</style>"
231
  {
232
  $openScript = $m[2];
233
  $js = $m[3];
234
+
235
  // whitespace surrounding? preserve at least one space
236
  $ws1 = ($m[1] === '') ? '' : ' ';
237
  $ws2 = ($m[4] === '') ? '' : ' ';
238
+
239
+ if ($this->_keepComments == false) {
240
+ // remove HTML comments (and ending "//" if present)
241
+ $js = preg_replace('/(?:^\\s*<!--\\s*|\\s*(?:\\/\\/)?\\s*-->\\s*$)/', '', $js);
242
+
243
+ // remove CDATA section markers
244
+ $js = $this->_removeCdata($js);
245
+ }
246
+
247
  // minify
248
  $minifier = $this->_jsMinifier
249
  ? $this->_jsMinifier
250
+ : 'trim';
251
  $js = call_user_func($minifier, $js);
252
+
253
  return $this->_reservePlace($this->_needsCdata($js)
254
  ? "{$ws1}{$openScript}/*<![CDATA[*/{$js}/*]]>*/</script>{$ws2}"
255
  : "{$ws1}{$openScript}{$js}</script>{$ws2}"
262
  ? str_replace(array('/* <![CDATA[ */','/* ]]> */','/*<![CDATA[*/','/*]]>*/','<![CDATA[', ']]>'), '', $str)
263
  : $str;
264
  }
265
+
266
  protected function _needsCdata($str)
267
  {
268
  return ($this->_isXhtml && preg_match('/(?:[<&]|\\-\\-|\\]\\]>)/', $str));
classes/external/php/persist-admin-notices-dismissal/README.md ADDED
@@ -0,0 +1,108 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Persist Admin notice Dismissals
2
+ [![Latest Stable Version](https://poser.pugx.org/collizo4sky/persist-admin-notices-dismissal/v/stable)](https://packagist.org/packages/collizo4sky/persist-admin-notices-dismissal)
3
+ [![Total Downloads](https://poser.pugx.org/collizo4sky/persist-admin-notices-dismissal/downloads)](https://packagist.org/packages/collizo4sky/persist-admin-notices-dismissal)
4
+
5
+ Simple framework library that persists the dismissal of admin notices across pages in WordPress dashboard.
6
+
7
+ ## Installation
8
+
9
+ Run `composer require collizo4sky/persist-admin-notices-dismissal`
10
+
11
+ Alternatively, clone or download this repo into the `vendor/` folder in your plugin, and include/require the `persist-admin-notices-dismissal.php` file like so
12
+
13
+ ```php
14
+ require __DIR__ . '/vendor/persist-admin-notices-dismissal/persist-admin-notices-dismissal.php';
15
+ add_action( 'admin_init', array( 'PAnD', 'init' ) );
16
+ ```
17
+
18
+ or let Composer's autoloader do the work.
19
+
20
+ ## How to Use
21
+ Firstly, install and activate this library within a plugin.
22
+
23
+ Say you have the following markup as your admin notice,
24
+
25
+
26
+ ```php
27
+ function sample_admin_notice__success() {
28
+ ?>
29
+ <div class="updated notice notice-success is-dismissible">
30
+ <p><?php _e( 'Done!', 'sample-text-domain' ); ?></p>
31
+ </div>
32
+ <?php
33
+ }
34
+ add_action( 'admin_notices', 'sample_admin_notice__success' );
35
+ ```
36
+
37
+ To make it hidden forever when dismissed, add the following data attribute `data-dismissible="disable-done-notice-forever"` to the div markup like so:
38
+
39
+
40
+ ```php
41
+ function sample_admin_notice__success() {
42
+ if ( ! PAnD::is_admin_notice_active( 'disable-done-notice-forever' ) ) {
43
+ return;
44
+ }
45
+
46
+ ?>
47
+ <div data-dismissible="disable-done-notice-forever" class="updated notice notice-success is-dismissible">
48
+ <p><?php _e( 'Done!', 'sample-text-domain' ); ?></p>
49
+ </div>
50
+ <?php
51
+ }
52
+ add_action( 'admin_init', array( 'PAnD', 'init' ) );
53
+ add_action( 'admin_notices', 'sample_admin_notice__success' );
54
+ ```
55
+
56
+ ## Autoloaders
57
+ When using the framework with an autoloader you **must** also load the class outside of the `admin_notices` or `network_admin_notices` hooks. The reason is that these hooks come after the `admin_enqueue_script` hook that loads the javascript.
58
+
59
+ Just add the following in your main plugin file.
60
+
61
+ ```php
62
+ add_action( 'admin_init', array( 'PAnD', 'init' ) );
63
+ ```
64
+
65
+ #### Usage Instructions and Examples
66
+ If you have two notices displayed when certain actions are triggered; firstly, choose a string to uniquely identify them, e.g. `notice-one` and `notice-two`
67
+
68
+ To make the first notice never appear once dismissed, its `data-dismissible` attribute will be `data-dismissible="notice-one-forever"` where `notice-one` is its unique identifier and `forever` is the dismissal time period.
69
+
70
+ To make the second notice only hidden for 2 days, its `data-dismissible` attribute will be `data-dismissible="notice-two-2"` where `notice-two` is its unique identifier and the `2`, the number of days it will be hidden is the dismissal time period.
71
+
72
+ You **must** append the dismissal time period to the end of your unique identifier with a hyphen (`-`) and this value must be an integer. The only exception is the string `forever`.
73
+
74
+ To actually make the dismissed admin notice not to appear, use the `is_admin_notice_active()` function like so:
75
+
76
+
77
+ ```php
78
+ function sample_admin_notice__success1() {
79
+ if ( ! PAnD::is_admin_notice_active( 'notice-one-forever' ) ) {
80
+ return;
81
+ }
82
+
83
+ ?>
84
+ <div data-dismissible="notice-one-forever" class="updated notice notice-success is-dismissible">
85
+ <p><?php _e( 'Done 1!', 'sample-text-domain' ); ?></p>
86
+ </div>
87
+ <?php
88
+ }
89
+
90
+ function sample_admin_notice__success2() {
91
+ if ( ! PAnD::is_admin_notice_active( 'notice-two-2' ) ) {
92
+ return;
93
+ }
94
+
95
+ ?>
96
+ <div data-dismissible="notice-two-2" class="updated notice notice-success is-dismissible">
97
+ <p><?php _e( 'Done 2!', 'sample-text-domain' ); ?></p>
98
+ </div>
99
+ <?php
100
+ }
101
+
102
+ add_action( 'admin_init', array( 'PAnD', 'init' ) );
103
+ add_action( 'admin_notices', 'sample_admin_notice__success1' );
104
+ add_action( 'admin_notices', 'sample_admin_notice__success2' );
105
+ ```
106
+
107
+
108
+ Cool beans. Isn't it?
classes/external/php/persist-admin-notices-dismissal/dismiss-notice.js ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ (function ($) {
2
+ //shorthand for ready event.
3
+ $(function () {
4
+ $('div[data-dismissible] button.notice-dismiss').click(function (event) {
5
+ event.preventDefault();
6
+ var $this = $(this);
7
+
8
+ var attr_value, option_name, dismissible_length, data;
9
+
10
+ attr_value = $this.parent().attr('data-dismissible').split('-');
11
+
12
+ // remove the dismissible length from the attribute value and rejoin the array.
13
+ dismissible_length = attr_value.pop();
14
+
15
+ option_name = attr_value.join('-');
16
+
17
+ data = {
18
+ 'action': 'dismiss_admin_notice',
19
+ 'option_name': option_name,
20
+ 'dismissible_length': dismissible_length,
21
+ 'nonce': dismissible_notice.nonce
22
+ };
23
+
24
+ // We can also pass the url value separately from ajaxurl for front end AJAX implementations
25
+ $.post(ajaxurl, data);
26
+ });
27
+ })
28
+
29
+ }(jQuery));
classes/external/php/persist-admin-notices-dismissal/persist-admin-notices-dismissal.php ADDED
@@ -0,0 +1,119 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * Persist Admin notices Dismissal
5
+ *
6
+ * Copyright (C) 2016 Agbonghama Collins <http://w3guy.com>
7
+ *
8
+ * This program is free software: you can redistribute it and/or modify
9
+ * it under the terms of the GNU General Public License as published by
10
+ * the Free Software Foundation, either version 3 of the License, or
11
+ * (at your option) any later version.
12
+ *
13
+ * This program is distributed in the hope that it will be useful,
14
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
15
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16
+ * GNU General Public License for more details.
17
+ *
18
+ * You should have received a copy of the GNU General Public License
19
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
20
+ *
21
+ * @package Persist Admin notices Dismissal
22
+ * @author Agbonghama Collins
23
+ * @author Andy Fragen
24
+ * @license http://www.gnu.org/licenses GNU General Public License
25
+ * @version 1.3.2
26
+ */
27
+
28
+ /**
29
+ * Exit if called directly.
30
+ */
31
+ if ( ! defined( 'ABSPATH' ) ) {
32
+ die;
33
+ }
34
+
35
+ if ( ! class_exists( 'PAnD' ) ) {
36
+
37
+ /**
38
+ * Class PAnD
39
+ */
40
+ class PAnD {
41
+
42
+ /**
43
+ * Init hooks.
44
+ */
45
+ public static function init() {
46
+ add_action( 'admin_enqueue_scripts', array( __CLASS__, 'load_script' ) );
47
+ add_action( 'wp_ajax_dismiss_admin_notice', array( __CLASS__, 'dismiss_admin_notice' ) );
48
+ }
49
+
50
+ /**
51
+ * Enqueue javascript and variables.
52
+ */
53
+ public static function load_script() {
54
+
55
+ if(is_customize_preview()) return;
56
+
57
+ wp_enqueue_script(
58
+ 'dismissible-notices',
59
+ plugins_url( 'dismiss-notice.js', __FILE__ ),
60
+ array( 'jquery', 'common' ),
61
+ false,
62
+ true
63
+ );
64
+
65
+ wp_localize_script(
66
+ 'dismissible-notices',
67
+ 'dismissible_notice',
68
+ array(
69
+ 'nonce' => wp_create_nonce( 'dismissible-notice' ),
70
+ )
71
+ );
72
+ }
73
+
74
+ /**
75
+ * Handles Ajax request to persist notices dismissal.
76
+ * Uses check_ajax_referer to verify nonce.
77
+ */
78
+ public static function dismiss_admin_notice() {
79
+ $option_name = sanitize_text_field( $_POST['option_name'] );
80
+ $dismissible_length = sanitize_text_field( $_POST['dismissible_length'] );
81
+ $transient = 0;
82
+
83
+ if ( 'forever' != $dismissible_length ) {
84
+ // If $dismissible_length is not an integer default to 1
85
+ $dismissible_length = ( 0 == absint( $dismissible_length ) ) ? 1 : $dismissible_length;
86
+ $transient = absint( $dismissible_length ) * DAY_IN_SECONDS;
87
+ $dismissible_length = strtotime( absint( $dismissible_length ) . ' days' );
88
+ }
89
+
90
+ check_ajax_referer( 'dismissible-notice', 'nonce' );
91
+ set_site_transient( $option_name, $dismissible_length, $transient );
92
+ wp_die();
93
+ }
94
+
95
+ /**
96
+ * Is admin notice active?
97
+ *
98
+ * @param string $arg data-dismissible content of notice.
99
+ *
100
+ * @return bool
101
+ */
102
+ public static function is_admin_notice_active( $arg ) {
103
+ $array = explode( '-', $arg );
104
+ $length = array_pop( $array );
105
+ $option_name = implode( '-', $array );
106
+ $db_record = get_site_transient( $option_name );
107
+
108
+ if ( 'forever' == $db_record ) {
109
+ return false;
110
+ } elseif ( absint( $db_record ) >= time() ) {
111
+ return false;
112
+ } else {
113
+ return true;
114
+ }
115
+ }
116
+
117
+ }
118
+
119
+ }
classes/external/php/yui-php-cssmin-2.4.8-4_fgo.php DELETED
@@ -1,790 +0,0 @@
1
- <?php
2
-
3
- /*!
4
- * cssmin.php v2.4.8-4
5
- * Author: Tubal Martin - http://tubalmartin.me/
6
- * Repo: https://github.com/tubalmartin/YUI-CSS-compressor-PHP-port
7
- *
8
- * This is a PHP port of the CSS minification tool distributed with YUICompressor,
9
- * itself a port of the cssmin utility by Isaac Schlueter - http://foohack.com/
10
- * Permission is hereby granted to use the PHP version under the same
11
- * conditions as the YUICompressor.
12
- */
13
-
14
- /*!
15
- * YUI Compressor
16
- * http://developer.yahoo.com/yui/compressor/
17
- * Author: Julien Lecomte - http://www.julienlecomte.net/
18
- * Copyright (c) 2013 Yahoo! Inc. All rights reserved.
19
- * The copyrights embodied in the content of this file are licensed
20
- * by Yahoo! Inc. under the BSD (revised) open source license.
21
- */
22
-
23
- class CSSmin
24
- {
25
- const NL = '___YUICSSMIN_PRESERVED_NL___';
26
- const TOKEN = '___YUICSSMIN_PRESERVED_TOKEN_';
27
- const COMMENT = '___YUICSSMIN_PRESERVE_CANDIDATE_COMMENT_';
28
- const CLASSCOLON = '___YUICSSMIN_PSEUDOCLASSCOLON___';
29
- const QUERY_FRACTION = '___YUICSSMIN_QUERY_FRACTION___';
30
-
31
- private $comments;
32
- private $preserved_tokens;
33
- private $memory_limit;
34
- private $max_execution_time;
35
- private $pcre_backtrack_limit;
36
- private $pcre_recursion_limit;
37
- private $raise_php_limits;
38
-
39
- /**
40
- * @param bool|int $raise_php_limits
41
- * If true, PHP settings will be raised if needed
42
- */
43
- public function __construct($raise_php_limits = TRUE)
44
- {
45
- // Set suggested PHP limits
46
- $this->memory_limit = 128 * 1048576; // 128MB in bytes
47
- $this->max_execution_time = 60; // 1 min
48
- $this->pcre_backtrack_limit = 1000 * 1000;
49
- $this->pcre_recursion_limit = 500 * 1000;
50
-
51
- $this->raise_php_limits = (bool) $raise_php_limits;
52
- }
53
-
54
- /**
55
- * Minify a string of CSS
56
- * @param string $css
57
- * @param int|bool $linebreak_pos
58
- * @return string
59
- */
60
- public function run($css = '', $linebreak_pos = FALSE)
61
- {
62
- if (empty($css)) {
63
- return '';
64
- }
65
-
66
- if ($this->raise_php_limits) {
67
- $this->do_raise_php_limits();
68
- }
69
-
70
- $this->comments = array();
71
- $this->preserved_tokens = array();
72
-
73
- $start_index = 0;
74
- $length = strlen($css);
75
-
76
- $css = $this->extract_data_urls($css);
77
-
78
- // collect all comment blocks...
79
- while (($start_index = $this->index_of($css, '/*', $start_index)) >= 0) {
80
- $end_index = $this->index_of($css, '*/', $start_index + 2);
81
- if ($end_index < 0) {
82
- $end_index = $length;
83
- }
84
- $comment_found = $this->str_slice($css, $start_index + 2, $end_index);
85
- $this->comments[] = $comment_found;
86
- $comment_preserve_string = self::COMMENT . (count($this->comments) - 1) . '___';
87
- $css = $this->str_slice($css, 0, $start_index + 2) . $comment_preserve_string . $this->str_slice($css, $end_index);
88
- // Set correct start_index: Fixes issue #2528130
89
- $start_index = $end_index + 2 + strlen($comment_preserve_string) - strlen($comment_found);
90
- }
91
-
92
- // preserve strings so their content doesn't get accidentally minified
93
- $css = preg_replace_callback('/(?:"(?:[^\\\\"]|\\\\.|\\\\)*")|'."(?:'(?:[^\\\\']|\\\\.|\\\\)*')/S", array($this, 'replace_string'), $css);
94
-
95
- // Let's divide css code in chunks of 5.000 chars aprox.
96
- // Reason: PHP's PCRE functions like preg_replace have a "backtrack limit"
97
- // of 100.000 chars by default (php < 5.3.7) so if we're dealing with really
98
- // long strings and a (sub)pattern matches a number of chars greater than
99
- // the backtrack limit number (i.e. /(.*)/s) PCRE functions may fail silently
100
- // returning NULL and $css would be empty.
101
- $charset = '';
102
- $charset_regexp = '/(@charset)( [^;]+;)/i';
103
- $css_chunks = array();
104
- $css_chunk_length = 5000; // aprox size, not exact
105
- $start_index = 0;
106
- $i = $css_chunk_length; // save initial iterations
107
- $l = strlen($css);
108
-
109
-
110
- // if the number of characters is 5000 or less, do not chunk
111
- if ($l <= $css_chunk_length) {
112
- $css_chunks[] = $css;
113
- } else {
114
- // chunk css code securely
115
- while ($i < $l) {
116
- $i += 50; // save iterations
117
- if ($l - $start_index <= $css_chunk_length || $i >= $l) {
118
- $css_chunks[] = $this->str_slice($css, $start_index);
119
- break;
120
- }
121
- if ($css[$i - 1] === '}' && $i - $start_index > $css_chunk_length) {
122
- // If there are two ending curly braces }} separated or not by spaces,
123
- // join them in the same chunk (i.e. @media blocks)
124
- $next_chunk = substr($css, $i);
125
- if (preg_match('/^\s*\}/', $next_chunk)) {
126
- $i = $i + $this->index_of($next_chunk, '}') + 1;
127
- }
128
-
129
- $css_chunks[] = $this->str_slice($css, $start_index, $i);
130
- $start_index = $i;
131
- }
132
- }
133
- }
134
-
135
- // Minify each chunk
136
- for ($i = 0, $n = count($css_chunks); $i < $n; $i++) {
137
- $css_chunks[$i] = $this->minify($css_chunks[$i], $linebreak_pos);
138
- // Keep the first @charset at-rule found
139
- if (empty($charset) && preg_match($charset_regexp, $css_chunks[$i], $matches)) {
140
- $charset = strtolower($matches[1]) . $matches[2];
141
- }
142
- // Delete all @charset at-rules
143
- $css_chunks[$i] = preg_replace($charset_regexp, '', $css_chunks[$i]);
144
- }
145
-
146
- // Update the first chunk and push the charset to the top of the file.
147
- $css_chunks[0] = $charset . $css_chunks[0];
148
-
149
- return implode('', $css_chunks);
150
- }
151
-
152
- /**
153
- * Sets the memory limit for this script
154
- * @param int|string $limit
155
- */
156
- public function set_memory_limit($limit)
157
- {
158
- $this->memory_limit = $this->normalize_int($limit);
159
- }
160
-
161
- /**
162
- * Sets the maximum execution time for this script
163
- * @param int|string $seconds
164
- */
165
- public function set_max_execution_time($seconds)
166
- {
167
- $this->max_execution_time = (int) $seconds;
168
- }
169
-
170
- /**
171
- * Sets the PCRE backtrack limit for this script
172
- * @param int $limit
173
- */
174
- public function set_pcre_backtrack_limit($limit)
175
- {
176
- $this->pcre_backtrack_limit = (int) $limit;
177
- }
178
-
179
- /**
180
- * Sets the PCRE recursion limit for this script
181
- * @param int $limit
182
- */
183
- public function set_pcre_recursion_limit($limit)
184
- {
185
- $this->pcre_recursion_limit = (int) $limit;
186
- }
187
-
188
- /**
189
- * Try to configure PHP to use at least the suggested minimum settings
190
- */
191
- private function do_raise_php_limits()
192
- {
193
- $php_limits = array(
194
- 'memory_limit' => $this->memory_limit,
195
- 'max_execution_time' => $this->max_execution_time,
196
- 'pcre.backtrack_limit' => $this->pcre_backtrack_limit,
197
- 'pcre.recursion_limit' => $this->pcre_recursion_limit
198
- );
199
-
200
- // If current settings are higher respect them.
201
- foreach ($php_limits as $name => $suggested) {
202
- $current = $this->normalize_int(ini_get($name));
203
- // memory_limit exception: allow -1 for "no memory limit".
204
- if ($current > -1 && ($suggested == -1 || $current < $suggested)) {
205
- ini_set($name, $suggested);
206
- }
207
- }
208
- }
209
-
210
- /**
211
- * Does bulk of the minification
212
- * @param string $css
213
- * @param int|bool $linebreak_pos
214
- * @return string
215
- */
216
- private function minify($css, $linebreak_pos)
217
- {
218
- // strings are safe, now wrestle the comments
219
- for ($i = 0, $max = count($this->comments); $i < $max; $i++) {
220
-
221
- $token = $this->comments[$i];
222
- $placeholder = '/' . self::COMMENT . $i . '___/';
223
-
224
- // ! in the first position of the comment means preserve
225
- // so push to the preserved tokens keeping the !
226
- if (substr($token, 0, 1) === '!') {
227
- $this->preserved_tokens[] = $token;
228
- $token_tring = self::TOKEN . (count($this->preserved_tokens) - 1) . '___';
229
- $css = preg_replace($placeholder, $token_tring, $css, 1);
230
- // Preserve new lines for /*! important comments
231
- $css = preg_replace('/\s*[\n\r\f]+\s*(\/\*'. $token_tring .')/S', self::NL.'$1', $css);
232
- $css = preg_replace('/('. $token_tring .'\*\/)\s*[\n\r\f]+\s*/', '$1'.self::NL, $css);
233
- continue;
234
- }
235
-
236
- // \ in the last position looks like hack for Mac/IE5
237
- // shorten that to /*\*/ and the next one to /**/
238
- if (substr($token, (strlen($token) - 1), 1) === '\\') {
239
- $this->preserved_tokens[] = '\\';
240
- $css = preg_replace($placeholder, self::TOKEN . (count($this->preserved_tokens) - 1) . '___', $css, 1);
241
- $i = $i + 1; // attn: advancing the loop
242
- $this->preserved_tokens[] = '';
243
- $css = preg_replace('/' . self::COMMENT . $i . '___/', self::TOKEN . (count($this->preserved_tokens) - 1) . '___', $css, 1);
244
- continue;
245
- }
246
-
247
- // keep empty comments after child selectors (IE7 hack)
248
- // e.g. html >/**/ body
249
- if (strlen($token) === 0) {
250
- $start_index = $this->index_of($css, $this->str_slice($placeholder, 1, -1));
251
- if ($start_index > 2) {
252
- if (substr($css, $start_index - 3, 1) === '>') {
253
- $this->preserved_tokens[] = '';
254
- $css = preg_replace($placeholder, self::TOKEN . (count($this->preserved_tokens) - 1) . '___', $css, 1);
255
- }
256
- }
257
- }
258
-
259
- // in all other cases kill the comment
260
- $css = preg_replace('/\/\*' . $this->str_slice($placeholder, 1, -1) . '\*\//', '', $css, 1);
261
- }
262
-
263
-
264
- // Normalize all whitespace strings to single spaces. Easier to work with that way.
265
- $css = preg_replace('/\s+/', ' ', $css);
266
-
267
- // preserve flex, keeping percentage even if 0
268
- $css = preg_replace_callback('/flex\s?:\s?((?:[0-9 ]*)\s?(?:px|em|auto|%)?(?:calc\(.*\))?)/i',array($this, 'replace_flex'),$css);
269
-
270
- // Fix IE7 issue on matrix filters which browser accept whitespaces between Matrix parameters
271
- $css = preg_replace_callback('/\s*filter\:\s*progid:DXImageTransform\.Microsoft\.Matrix\(([^\)]+)\)/', array($this, 'preserve_old_IE_specific_matrix_definition'), $css);
272
-
273
- // Shorten & preserve calculations calc(...) since spaces are important
274
- $css = preg_replace_callback('/calc(\(((?:[^\(\)]+|(?1))*)\))/i', array($this, 'replace_calc'), $css);
275
-
276
- // Replace positive sign from numbers preceded by : or a white-space before the leading space is removed
277
- // +1.2em to 1.2em, +.8px to .8px, +2% to 2%
278
- $css = preg_replace('/((?<!\\\\)\:|\s)\+(\.?\d+)/S', '$1$2', $css);
279
-
280
- // Remove leading zeros from integer and float numbers preceded by : or a white-space
281
- // 000.6 to .6, -0.8 to -.8, 0050 to 50, -01.05 to -1.05
282
- $css = preg_replace('/((?<!\\\\)\:|\s)(\-?)0+(\.?\d+)/S', '$1$2$3', $css);
283
-
284
- // Remove trailing zeros from float numbers preceded by : or a white-space
285
- // -6.0100em to -6.01em, .0100 to .01, 1.200px to 1.2px
286
- $css = preg_replace('/((?<!\\\\)\:|\s)(\-?)(\d?\.\d+?)0+([^\d])/S', '$1$2$3$4', $css);
287
-
288
- // Remove trailing .0 -> -9.0 to -9
289
- $css = preg_replace('/((?<!\\\\)\:|\s)(\-?\d+)\.0([^\d])/S', '$1$2$3', $css);
290
-
291
- // Replace 0 length numbers with 0
292
- $css = preg_replace('/((?<!\\\\)\:|\s)\-?\.?0+([^\d])/S', '${1}0$2', $css);
293
-
294
- // Remove the spaces before the things that should not have spaces before them.
295
- // But, be careful not to turn "p :link {...}" into "p:link{...}"
296
- // Swap out any pseudo-class colons with the token, and then swap back.
297
- $css = preg_replace_callback('/(?:^|\})[^\{]*\s+\:/', array($this, 'replace_colon'), $css);
298
-
299
- // Remove spaces before the things that should not have spaces before them.
300
- $css = preg_replace('/\s+([\!\{\}\;\:\>\+\(\)\]\~\=,])/', '$1', $css);
301
-
302
- // Restore spaces for !important
303
- $css = preg_replace('/\!important/i', ' !important', $css);
304
-
305
- // bring back the colon
306
- $css = preg_replace('/' . self::CLASSCOLON . '/', ':', $css);
307
-
308
- // retain space for special IE6 cases
309
- $css = preg_replace_callback('/\:first\-(line|letter)(\{|,)/i', array($this, 'lowercase_pseudo_first'), $css);
310
-
311
- // no space after the end of a preserved comment
312
- $css = preg_replace('/\*\/ /', '*/', $css);
313
-
314
- // lowercase some popular @directives
315
- $css = preg_replace_callback('/@(font-face|import|(?:-(?:atsc|khtml|moz|ms|o|wap|webkit)-)?keyframe|media|page|namespace)/i', array($this, 'lowercase_directives'), $css);
316
-
317
- // lowercase some more common pseudo-elements
318
- $css = preg_replace_callback('/:(active|after|before|checked|disabled|empty|enabled|first-(?:child|of-type)|focus|hover|last-(?:child|of-type)|link|only-(?:child|of-type)|root|:selection|target|visited)/i', array($this, 'lowercase_pseudo_elements'), $css);
319
-
320
- // lowercase some more common functions
321
- $css = preg_replace_callback('/:(lang|not|nth-child|nth-last-child|nth-last-of-type|nth-of-type|(?:-(?:moz|webkit)-)?any)\(/i', array($this, 'lowercase_common_functions'), $css);
322
-
323
- // lower case some common function that can be values
324
- // NOTE: rgb() isn't useful as we replace with #hex later, as well as and() is already done for us
325
- $css = preg_replace_callback('/([:,\( ]\s*)(attr|color-stop|from|rgba|to|url|(?:-(?:atsc|khtml|moz|ms|o|wap|webkit)-)?(?:calc|max|min|(?:repeating-)?(?:linear|radial)-gradient)|-webkit-gradient)/iS', array($this, 'lowercase_common_functions_values'), $css);
326
-
327
- // Put the space back in some cases, to support stuff like
328
- // @media screen and (-webkit-min-device-pixel-ratio:0){
329
- // based on regex from https://github.com/tubalmartin/YUI-CSS-compressor-PHP-port/blob/v3.2.0/src/Minifier.php#L479
330
- $css = preg_replace('/(\s|\)\s)(and|not|or)\(/i', '$1$2 (', $css);
331
-
332
- // Remove the spaces after the things that should not have spaces after them.
333
- $css = preg_replace('/([\!\{\}\:;\>\+\(\[\~\=,])\s+/S', '$1', $css);
334
-
335
- // remove unnecessary semicolons
336
- $css = preg_replace('/;+\}/', '}', $css);
337
-
338
- // Fix for issue: #2528146
339
- // Restore semicolon if the last property is prefixed with a `*` (lte IE7 hack)
340
- // to avoid issues on Symbian S60 3.x browsers.
341
- $css = preg_replace('/(\*[a-z0-9\-]+\s*\:[^;\}]+)(\})/', '$1;$2', $css);
342
-
343
- // Replace 0 <length> and 0 <percentage> values with 0.
344
- // <length> data type: https://developer.mozilla.org/en-US/docs/Web/CSS/length
345
- // <percentage> data type: https://developer.mozilla.org/en-US/docs/Web/CSS/percentage
346
- $css = preg_replace('/([^\\\\]\:|\s)0(?:em|ex|ch|rem|vw|vh|vm|vmin|cm|mm|in|px|pt|pc|%)/iS', '${1}0', $css);
347
-
348
- // 0% step in a keyframe? restore the % unit
349
- $css = preg_replace_callback('/(@[a-z\-]*?keyframes[^\{]+\{)(.*?)(\}\})/iS', array($this, 'replace_keyframe_zero'), $css);
350
-
351
- // Replace 0 0; or 0 0 0; or 0 0 0 0; with 0.
352
- $css = preg_replace('/\:0(?: 0){1,3}(;|\}| \!)/', ':0$1', $css);
353
-
354
- // Fix for issue: #2528142
355
- // Replace text-shadow:0; with text-shadow:0 0 0;
356
- $css = preg_replace('/(text-shadow\:0)(;|\}| \!)/i', '$1 0 0$2', $css);
357
-
358
- // Replace background-position:0; with background-position:0 0;
359
- // same for transform-origin
360
- // Changing -webkit-mask-position: 0 0 to just a single 0 will result in the second parameter defaulting to 50% (center)
361
- $css = preg_replace('/(background|background\-position|webkit-mask-position|(?:webkit|moz|o|ms|)\-?transform\-origin)\:0(;|\}| \!)/iS', '$1:0 0$2', $css);
362
-
363
- // Shorten colors from rgb(51,102,153) to #336699, rgb(100%,0%,0%) to #ff0000 (sRGB color space)
364
- // Shorten colors from hsl(0, 100%, 50%) to #ff0000 (sRGB color space)
365
- // This makes it more likely that it'll get further compressed in the next step.
366
- $css = preg_replace_callback('/rgb\s*\(\s*([0-9,\s\-\.\%]+)\s*\)(.{1})/i', array($this, 'rgb_to_hex'), $css);
367
- $css = preg_replace_callback('/hsl\s*\(\s*([0-9,\s\-\.\%]+)\s*\)(.{1})/i', array($this, 'hsl_to_hex'), $css);
368
-
369
- // Shorten colors from #AABBCC to #ABC or short color name.
370
- $css = $this->compress_hex_colors($css);
371
-
372
- // border: none to border:0, outline: none to outline:0
373
- $css = preg_replace('/(border\-?(?:top|right|bottom|left|)|outline)\:none(;|\}| \!)/iS', '$1:0$2', $css);
374
-
375
- // shorter opacity IE filter
376
- $css = preg_replace('/progid\:DXImageTransform\.Microsoft\.Alpha\(Opacity\=/i', 'alpha(opacity=', $css);
377
-
378
- // Find a fraction that is used for Opera's -o-device-pixel-ratio query
379
- // Add token to add the "\" back in later
380
- $css = preg_replace('/\(([a-z\-]+):([0-9]+)\/([0-9]+)\)/i', '($1:$2'. self::QUERY_FRACTION .'$3)', $css);
381
-
382
- // Remove empty rules.
383
- $css = preg_replace('/[^\};\{\/]+\{\}/S', '', $css);
384
-
385
- // Add "/" back to fix Opera -o-device-pixel-ratio query
386
- $css = preg_replace('/'. self::QUERY_FRACTION .'/', '/', $css);
387
-
388
- // Replace multiple semi-colons in a row by a single one
389
- // See SF bug #1980989
390
- $css = preg_replace('/;;+/', ';', $css);
391
-
392
- // Restore new lines for /*! important comments
393
- $css = preg_replace('/'. self::NL .'/', "\n", $css);
394
-
395
- // Lowercase all uppercase properties
396
- $css = preg_replace_callback('/(\{|\;)([A-Z\-]+)(\:)/', array($this, 'lowercase_properties'), $css);
397
-
398
- // Some source control tools don't like it when files containing lines longer
399
- // than, say 8000 characters, are checked in. The linebreak option is used in
400
- // that case to split long lines after a specific column.
401
- if ($linebreak_pos !== FALSE && (int) $linebreak_pos >= 0) {
402
- $linebreak_pos = (int) $linebreak_pos;
403
- $start_index = $i = 0;
404
- while ($i < strlen($css)) {
405
- $i++;
406
- if ($css[$i - 1] === '}' && $i - $start_index > $linebreak_pos) {
407
- $css = $this->str_slice($css, 0, $i) . "\n" . $this->str_slice($css, $i);
408
- $start_index = $i;
409
- }
410
- }
411
- }
412
-
413
- // restore preserved comments and strings in reverse order
414
- for ($i = count($this->preserved_tokens) - 1; $i >= 0; $i--) {
415
- $css = preg_replace('/' . self::TOKEN . $i . '___/', $this->preserved_tokens[$i], $css, 1);
416
- // $css.=$this->preserved_tokens[$i];
417
- }
418
-
419
- // Trim the final string (for any leading or trailing white spaces)
420
- return trim($css);
421
- }
422
-
423
- /**
424
- * Utility method to replace all data urls with tokens before we start
425
- * compressing, to avoid performance issues running some of the subsequent
426
- * regexes against large strings chunks.
427
- *
428
- * @param string $css
429
- * @return string
430
- */
431
- private function extract_data_urls($css)
432
- {
433
- // Leave data urls alone to increase parse performance.
434
- $max_index = strlen($css) - 1;
435
- $append_index = $index = $last_index = $offset = 0;
436
- $sb = array();
437
- $pattern = '/url\(\s*(["\']?)data\:/i';
438
-
439
- // Since we need to account for non-base64 data urls, we need to handle
440
- // ' and ) being part of the data string. Hence switching to indexOf,
441
- // to determine whether or not we have matching string terminators and
442
- // handling sb appends directly, instead of using matcher.append* methods.
443
-
444
- while (preg_match($pattern, $css, $m, 0, $offset)) {
445
- $index = $this->index_of($css, $m[0], $offset);
446
- $last_index = $index + strlen($m[0]);
447
- $start_index = $index + 4; // "url(".length()
448
- $end_index = $last_index - 1;
449
- $terminator = $m[1]; // ', " or empty (not quoted)
450
- $found_terminator = FALSE;
451
-
452
- if (strlen($terminator) === 0) {
453
- $terminator = ')';
454
- }
455
-
456
- while ($found_terminator === FALSE && $end_index+1 <= $max_index) {
457
- $end_index = $this->index_of($css, $terminator, $end_index + 1);
458
-
459
- // endIndex == 0 doesn't really apply here
460
- if ($end_index > 0 && substr($css, $end_index - 1, 1) !== '\\') {
461
- $found_terminator = TRUE;
462
- if (')' != $terminator) {
463
- $end_index = $this->index_of($css, ')', $end_index);
464
- }
465
- }
466
- }
467
-
468
- // Enough searching, start moving stuff over to the buffer
469
- $sb[] = $this->str_slice($css, $append_index, $index);
470
-
471
- if ($found_terminator) {
472
- $token = $this->str_slice($css, $start_index, $end_index);
473
- if (strpos($token,"<svg")===false && strpos($token,'svg+xml')===false) {
474
- $token = preg_replace('/\s+/', '', $token);
475
- }
476
- $this->preserved_tokens[] = $token;
477
-
478
- $preserver = 'url(' . self::TOKEN . (count($this->preserved_tokens) - 1) . '___)';
479
- $sb[] = $preserver;
480
-
481
- $append_index = $end_index + 1;
482
- } else {
483
- // No end terminator found, re-add the whole match. Should we throw/warn here?
484
- $sb[] = $this->str_slice($css, $index, $last_index);
485
- $append_index = $last_index;
486
- }
487
-
488
- $offset = $last_index;
489
- }
490
-
491
- $sb[] = $this->str_slice($css, $append_index);
492
-
493
- return implode('', $sb);
494
- }
495
-
496
- /**
497
- * Utility method to compress hex color values of the form #AABBCC to #ABC or short color name.
498
- *
499
- * DOES NOT compress CSS ID selectors which match the above pattern (which would break things).
500
- * e.g. #AddressForm { ... }
501
- *
502
- * DOES NOT compress IE filters, which have hex color values (which would break things).
503
- * e.g. filter: chroma(color="#FFFFFF");
504
- *
505
- * DOES NOT compress invalid hex values.
506
- * e.g. background-color: #aabbccdd
507
- *
508
- * @param string $css
509
- * @return string
510
- */
511
- private function compress_hex_colors($css)
512
- {
513
- // Look for hex colors inside { ... } (to avoid IDs) and which don't have a =, or a " in front of them (to avoid filters)
514
- $pattern = '/(\=\s*?["\']?)?#([0-9a-f])([0-9a-f])([0-9a-f])([0-9a-f])([0-9a-f])([0-9a-f])(\}|[^0-9a-f{][^{]*?\})/iS';
515
- $_index = $index = $last_index = $offset = 0;
516
- $sb = array();
517
- // See: http://ajaxmin.codeplex.com/wikipage?title=CSS%20Colors
518
- $short_safe = array(
519
- '#808080' => 'gray',
520
- '#008000' => 'green',
521
- '#800000' => 'maroon',
522
- '#000080' => 'navy',
523
- '#808000' => 'olive',
524
- '#ffa500' => 'orange',
525
- '#800080' => 'purple',
526
- '#c0c0c0' => 'silver',
527
- '#008080' => 'teal',
528
- '#f00' => 'red'
529
- );
530
-
531
- while (preg_match($pattern, $css, $m, 0, $offset)) {
532
- $index = $this->index_of($css, $m[0], $offset);
533
- $last_index = $index + strlen($m[0]);
534
- $is_filter = $m[1] !== null && $m[1] !== '';
535
-
536
- $sb[] = $this->str_slice($css, $_index, $index);
537
-
538
- if ($is_filter) {
539
- // Restore, maintain case, otherwise filter will break
540
- $sb[] = $m[1] . '#' . $m[2] . $m[3] . $m[4] . $m[5] . $m[6] . $m[7];
541
- } else {
542
- if (strtolower($m[2]) == strtolower($m[3]) &&
543
- strtolower($m[4]) == strtolower($m[5]) &&
544
- strtolower($m[6]) == strtolower($m[7])) {
545
- // Compress.
546
- $hex = '#' . strtolower($m[3] . $m[5] . $m[7]);
547
- } else {
548
- // Non compressible color, restore but lower case.
549
- $hex = '#' . strtolower($m[2] . $m[3] . $m[4] . $m[5] . $m[6] . $m[7]);
550
- }
551
- // replace Hex colors to short safe color names
552
- $sb[] = array_key_exists($hex, $short_safe) ? $short_safe[$hex] : $hex;
553
- }
554
-
555
- $_index = $offset = $last_index - strlen($m[8]);
556
- }
557
-
558
- $sb[] = $this->str_slice($css, $_index);
559
-
560
- return implode('', $sb);
561
- }
562
-
563
- /* CALLBACKS
564
- * ---------------------------------------------------------------------------------------------
565
- */
566
-
567
- private function replace_string($matches)
568
- {
569
- $match = $matches[0];
570
- $quote = substr($match, 0, 1);
571
- // Must use addcslashes in PHP to avoid parsing of backslashes
572
- $match = addcslashes($this->str_slice($match, 1, -1), '\\');
573
-
574
- // maybe the string contains a comment-like substring?
575
- // one, maybe more? put'em back then
576
- if (($pos = $this->index_of($match, self::COMMENT)) >= 0) {
577
- for ($i = 0, $max = count($this->comments); $i < $max; $i++) {
578
- $match = preg_replace('/' . self::COMMENT . $i . '___/', $this->comments[$i], $match, 1);
579
- }
580
- }
581
-
582
- // minify alpha opacity in filter strings
583
- $match = preg_replace('/progid\:DXImageTransform\.Microsoft\.Alpha\(Opacity\=/i', 'alpha(opacity=', $match);
584
-
585
- $this->preserved_tokens[] = $match;
586
- return $quote . self::TOKEN . (count($this->preserved_tokens) - 1) . '___' . $quote;
587
- }
588
-
589
- private function replace_colon($matches)
590
- {
591
- return preg_replace('/\:/', self::CLASSCOLON, $matches[0]);
592
- }
593
-
594
- private function replace_calc($matches)
595
- {
596
- $this->preserved_tokens[] = preg_replace('/\)([\+\-]{1})/',') $1',preg_replace('/([\+\-]{1})\(/','$1 (',trim(preg_replace('/\s*([\*\/\(\),])\s*/', '$1', $matches[2]))));
597
- return 'calc('. self::TOKEN . (count($this->preserved_tokens) - 1) . '___' . ')';
598
- }
599
-
600
- private function replace_flex($matches)
601
- {
602
- $this->preserved_tokens[] = trim($matches[1]);
603
- return 'flex:'.self::TOKEN . (count($this->preserved_tokens) - 1) . '___';
604
- }
605
-
606
- private function preserve_old_IE_specific_matrix_definition($matches)
607
- {
608
- $this->preserved_tokens[] = $matches[1];
609
- return 'filter:progid:DXImageTransform.Microsoft.Matrix(' . self::TOKEN . (count($this->preserved_tokens) - 1) . '___' . ')';
610
- }
611
-
612
- private function replace_keyframe_zero($matches)
613
- {
614
- return $matches[1] . preg_replace('/0(\{|,[^\)\{]+\{)/', '0%$1', $matches[2]) . $matches[3];
615
- }
616
-
617
- private function rgb_to_hex($matches)
618
- {
619
- // Support for percentage values rgb(100%, 0%, 45%);
620
- if ($this->index_of($matches[1], '%') >= 0){
621
- $rgbcolors = explode(',', str_replace('%', '', $matches[1]));
622
- for ($i = 0; $i < count($rgbcolors); $i++) {
623
- $rgbcolors[$i] = $this->round_number(floatval($rgbcolors[$i]) * 2.55);
624
- }
625
- } else {
626
- $rgbcolors = explode(',', $matches[1]);
627
- }
628
-
629
- // Values outside the sRGB color space should be clipped (0-255)
630
- for ($i = 0; $i < count($rgbcolors); $i++) {
631
- $rgbcolors[$i] = $this->clamp_number(intval($rgbcolors[$i], 10), 0, 255);
632
- $rgbcolors[$i] = sprintf("%02x", $rgbcolors[$i]);
633
- }
634
-
635
- // Fix for issue #2528093
636
- if (!preg_match('/[\s\,\);\}]/', $matches[2])){
637
- $matches[2] = ' ' . $matches[2];
638
- }
639
-
640
- return '#' . implode('', $rgbcolors) . $matches[2];
641
- }
642
-
643
- private function hsl_to_hex($matches)
644
- {
645
- $values = explode(',', str_replace('%', '', $matches[1]));
646
- $h = floatval($values[0]);
647
- $s = floatval($values[1]);
648
- $l = floatval($values[2]);
649
-
650
- // Wrap and clamp, then fraction!
651
- $h = ((($h % 360) + 360) % 360) / 360;
652
- $s = $this->clamp_number($s, 0, 100) / 100;
653
- $l = $this->clamp_number($l, 0, 100) / 100;
654
-
655
- if ($s == 0) {
656
- $r = $g = $b = $this->round_number(255 * $l);
657
- } else {
658
- $v2 = $l < 0.5 ? $l * (1 + $s) : ($l + $s) - ($s * $l);
659
- $v1 = (2 * $l) - $v2;
660
- $r = $this->round_number(255 * $this->hue_to_rgb($v1, $v2, $h + (1/3)));
661
- $g = $this->round_number(255 * $this->hue_to_rgb($v1, $v2, $h));
662
- $b = $this->round_number(255 * $this->hue_to_rgb($v1, $v2, $h - (1/3)));
663
- }
664
-
665
- return $this->rgb_to_hex(array('', $r.','.$g.','.$b, $matches[2]));
666
- }
667
-
668
- private function lowercase_pseudo_first($matches)
669
- {
670
- return ':first-'. strtolower($matches[1]) .' '. $matches[2];
671
- }
672
-
673
- private function lowercase_directives($matches)
674
- {
675
- return '@'. strtolower($matches[1]);
676
- }
677
-
678
- private function lowercase_pseudo_elements($matches)
679
- {
680
- return ':'. strtolower($matches[1]);
681
- }
682
-
683
- private function lowercase_common_functions($matches)
684
- {
685
- return ':'. strtolower($matches[1]) .'(';
686
- }
687
-
688
- private function lowercase_common_functions_values($matches)
689
- {
690
- return $matches[1] . strtolower($matches[2]);
691
- }
692
-
693
- private function lowercase_properties($matches)
694
- {
695
- return $matches[1].strtolower($matches[2]).$matches[3];
696
- }
697
-
698
- /* HELPERS
699
- * ---------------------------------------------------------------------------------------------
700
- */
701
-
702
- private function hue_to_rgb($v1, $v2, $vh)
703
- {
704
- $vh = $vh < 0 ? $vh + 1 : ($vh > 1 ? $vh - 1 : $vh);
705
- if ($vh * 6 < 1) return $v1 + ($v2 - $v1) * 6 * $vh;
706
- if ($vh * 2 < 1) return $v2;
707
- if ($vh * 3 < 2) return $v1 + ($v2 - $v1) * ((2/3) - $vh) * 6;
708
- return $v1;
709
- }
710
-
711
- private function round_number($n)
712
- {
713
- return intval(floor(floatval($n) + 0.5), 10);
714
- }
715
-
716
- private function clamp_number($n, $min, $max)
717
- {
718
- return min(max($n, $min), $max);
719
- }
720
-
721
- /**
722
- * PHP port of Javascript's "indexOf" function for strings only
723
- * Author: Tubal Martin http://blog.margenn.com
724
- *
725
- * @param string $haystack
726
- * @param string $needle
727
- * @param int $offset index (optional)
728
- * @return int
729
- */
730
- private function index_of($haystack, $needle, $offset = 0)
731
- {
732
- $index = strpos($haystack, $needle, $offset);
733
-
734
- return ($index !== FALSE) ? $index : -1;
735
- }
736
-
737
- /**
738
- * PHP port of Javascript's "slice" function for strings only
739
- * Author: Tubal Martin http://blog.margenn.com
740
- * Tests: http://margenn.com/tubal/str_slice/
741
- *
742
- * @param string $str
743
- * @param int $start index
744
- * @param int|bool $end index (optional)
745
- * @return string
746
- */
747
- private function str_slice($str, $start = 0, $end = FALSE)
748
- {
749
- if ($end !== FALSE && ($start < 0 || $end <= 0)) {
750
- $max = strlen($str);
751
-
752
- if ($start < 0) {
753
- if (($start = $max + $start) < 0) {
754
- return '';
755
- }
756
- }
757
-
758
- if ($end < 0) {
759
- if (($end = $max + $end) < 0) {
760
- return '';
761
- }
762
- }
763
-
764
- if ($end <= $start) {
765
- return '';
766
- }
767
- }
768
-
769
- $slice = ($end === FALSE) ? substr($str, $start) : substr($str, $start, $end - $start);
770
- return ($slice === FALSE) ? '' : $slice;
771
- }
772
-
773
- /**
774
- * Convert strings like "64M" or "30" to int values
775
- * @param mixed $size
776
- * @return int
777
- */
778
- private function normalize_int($size)
779
- {
780
- if (is_string($size)) {
781
- switch (substr($size, -1)) {
782
- case 'M': case 'm': return (int) $size * 1048576;
783
- case 'K': case 'k': return (int) $size * 1024;
784
- case 'G': case 'g': return (int) $size * 1073741824;
785
- }
786
- }
787
-
788
- return (int) $size;
789
- }
790
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
classes/external/php/yui-php-cssmin-2.4.8-p10/cssmin.php DELETED
@@ -1,1112 +0,0 @@
1
- <?php
2
-
3
- /*!
4
- * cssmin.php
5
- * Author: Tubal Martin - http://tubalmartin.me/
6
- * Repo: https://github.com/tubalmartin/YUI-CSS-compressor-PHP-port
7
- *
8
- * This is a PHP port of the CSS minification tool distributed with YUICompressor,
9
- * itself a port of the cssmin utility by Isaac Schlueter - http://foohack.com/
10
- * Permission is hereby granted to use the PHP version under the same
11
- * conditions as the YUICompressor.
12
- */
13
-
14
- /*!
15
- * YUI Compressor
16
- * http://developer.yahoo.com/yui/compressor/
17
- * Author: Julien Lecomte - http://www.julienlecomte.net/
18
- * Copyright (c) 2013 Yahoo! Inc. All rights reserved.
19
- * The copyrights embodied in the content of this file are licensed
20
- * by Yahoo! Inc. under the BSD (revised) open source license.
21
- */
22
-
23
- class CSSmin
24
- {
25
- const NL = '___YUICSSMIN_PRESERVED_NL___';
26
- const CLASSCOLON = '___YUICSSMIN_PSEUDOCLASSCOLON___';
27
- const QUERY_FRACTION = '___YUICSSMIN_QUERY_FRACTION___';
28
-
29
- const TOKEN = '___YUICSSMIN_PRESERVED_TOKEN_';
30
- const COMMENT = '___YUICSSMIN_PRESERVE_CANDIDATE_COMMENT_';
31
- const AT_RULE_BLOCK = '___YUICSSMIN_PRESERVE_AT_RULE_BLOCK_';
32
-
33
- private $comments;
34
- private $atRuleBlocks;
35
- private $preservedTokens;
36
- private $chunkLength = 5000;
37
- private $minChunkLength = 100;
38
- private $memoryLimit;
39
- private $maxExecutionTime = 60; // 1 min
40
- private $pcreBacktrackLimit;
41
- private $pcreRecursionLimit;
42
- private $raisePhpLimits;
43
-
44
- private $unitsGroupRegex = '(?:ch|cm|em|ex|gd|in|mm|px|pt|pc|q|rem|vh|vmax|vmin|vw|%)';
45
- private $numRegex;
46
-
47
- /**
48
- * @param bool|int $raisePhpLimits If true, PHP settings will be raised if needed
49
- */
50
- public function __construct($raisePhpLimits = true)
51
- {
52
- $this->memoryLimit = 128 * 1048576; // 128MB in bytes
53
- $this->pcreBacktrackLimit = 1000 * 1000;
54
- $this->pcreRecursionLimit = 500 * 1000;
55
-
56
- $this->raisePhpLimits = (bool) $raisePhpLimits;
57
-
58
- $this->numRegex = '(?:\+|-)?\d*\.?\d+' . $this->unitsGroupRegex .'?';
59
- }
60
-
61
- /**
62
- * Minifies a string of CSS
63
- * @param string $css
64
- * @param int|bool $linebreakPos
65
- * @return string
66
- */
67
- public function run($css = '', $linebreakPos = false)
68
- {
69
- if (empty($css)) {
70
- return '';
71
- }
72
-
73
- if ($this->raisePhpLimits) {
74
- $this->doRaisePhpLimits();
75
- }
76
-
77
- $this->comments = array();
78
- $this->atRuleBlocks = array();
79
- $this->preservedTokens = array();
80
-
81
- // process data urls
82
- $css = $this->processDataUrls($css);
83
-
84
- // process comments
85
- $css = preg_replace_callback('/(?<!\\\\)\/\*(.*?)\*(?<!\\\\)\//Ss', array($this, 'processComments'), $css);
86
-
87
- // process strings so their content doesn't get accidentally minified
88
- $css = preg_replace_callback(
89
- '/(?:"(?:[^\\\\"]|\\\\.|\\\\)*")|'."(?:'(?:[^\\\\']|\\\\.|\\\\)*')/S",
90
- array($this, 'processStrings'),
91
- $css
92
- );
93
-
94
- // Safe chunking: process at rule blocks so after chunking nothing gets stripped out
95
- $css = preg_replace_callback(
96
- '/@(?:document|(?:-(?:atsc|khtml|moz|ms|o|wap|webkit)-)?keyframes|media|supports).+?\}\s*\}/si',
97
- array($this, 'processAtRuleBlocks'),
98
- $css
99
- );
100
-
101
- // Let's divide css code in chunks of {$this->chunkLength} chars aprox.
102
- // Reason: PHP's PCRE functions like preg_replace have a "backtrack limit"
103
- // of 100.000 chars by default (php < 5.3.7) so if we're dealing with really
104
- // long strings and a (sub)pattern matches a number of chars greater than
105
- // the backtrack limit number (i.e. /(.*)/s) PCRE functions may fail silently
106
- // returning NULL and $css would be empty.
107
- $charset = '';
108
- $charsetRegexp = '/(@charset)( [^;]+;)/i';
109
- $cssChunks = array();
110
- $l = strlen($css);
111
-
112
- // if the number of characters is <= {$this->chunkLength}, do not chunk
113
- if ($l <= $this->chunkLength) {
114
- $cssChunks[] = $css;
115
- } else {
116
- // chunk css code securely
117
- for ($startIndex = 0, $i = $this->chunkLength; $i < $l; $i++) {
118
- if ($css[$i - 1] === '}' && $i - $startIndex >= $this->chunkLength) {
119
- $cssChunks[] = $this->strSlice($css, $startIndex, $i);
120
- $startIndex = $i;
121
- // Move forward saving iterations when possible!
122
- if ($startIndex + $this->chunkLength < $l) {
123
- $i += $this->chunkLength;
124
- }
125
- }
126
- }
127
-
128
- // Final chunk
129
- $cssChunks[] = $this->strSlice($css, $startIndex);
130
- }
131
-
132
- // Minify each chunk
133
- for ($i = 0, $n = count($cssChunks); $i < $n; $i++) {
134
- $cssChunks[$i] = $this->minify($cssChunks[$i], $linebreakPos);
135
- // Keep the first @charset at-rule found
136
- if (empty($charset) && preg_match($charsetRegexp, $cssChunks[$i], $matches)) {
137
- $charset = strtolower($matches[1]) . $matches[2];
138
- }
139
- // Delete all @charset at-rules
140
- $cssChunks[$i] = preg_replace($charsetRegexp, '', $cssChunks[$i]);
141
- }
142
-
143
- // Update the first chunk and push the charset to the top of the file.
144
- $cssChunks[0] = $charset . $cssChunks[0];
145
-
146
- return trim(implode('', $cssChunks));
147
- }
148
-
149
- /**
150
- * Sets the approximate number of characters to use when splitting a string in chunks.
151
- * @param int $length
152
- */
153
- public function set_chunk_length($length)
154
- {
155
- $length = (int) $length;
156
- $this->chunkLength = $length < $this->minChunkLength ? $this->minChunkLength : $length;
157
- }
158
-
159
- /**
160
- * Sets the memory limit for this script
161
- * @param int|string $limit
162
- */
163
- public function set_memory_limit($limit)
164
- {
165
- $this->memoryLimit = $this->normalizeInt($limit);
166
- }
167
-
168
- /**
169
- * Sets the maximum execution time for this script
170
- * @param int|string $seconds
171
- */
172
- public function set_max_execution_time($seconds)
173
- {
174
- $this->maxExecutionTime = (int) $seconds;
175
- }
176
-
177
- /**
178
- * Sets the PCRE backtrack limit for this script
179
- * @param int $limit
180
- */
181
- public function set_pcre_backtrack_limit($limit)
182
- {
183
- $this->pcreBacktrackLimit = (int) $limit;
184
- }
185
-
186
- /**
187
- * Sets the PCRE recursion limit for this script
188
- * @param int $limit
189
- */
190
- public function set_pcre_recursion_limit($limit)
191
- {
192
- $this->pcreRecursionLimit = (int) $limit;
193
- }
194
-
195
- /**
196
- * Tries to configure PHP to use at least the suggested minimum settings
197
- * @return void
198
- */
199
- private function doRaisePhpLimits()
200
- {
201
- $phpLimits = array(
202
- 'memory_limit' => $this->memoryLimit,
203
- 'max_execution_time' => $this->maxExecutionTime,
204
- 'pcre.backtrack_limit' => $this->pcreBacktrackLimit,
205
- 'pcre.recursion_limit' => $this->pcreRecursionLimit
206
- );
207
-
208
- // If current settings are higher respect them.
209
- foreach ($phpLimits as $name => $suggested) {
210
- $current = $this->normalizeInt(ini_get($name));
211
-
212
- if ($current > $suggested) {
213
- continue;
214
- }
215
-
216
- // memoryLimit exception: allow -1 for "no memory limit".
217
- if ($name === 'memory_limit' && $current === -1) {
218
- continue;
219
- }
220
-
221
- // maxExecutionTime exception: allow 0 for "no memory limit".
222
- if ($name === 'max_execution_time' && $current === 0) {
223
- continue;
224
- }
225
-
226
- ini_set($name, $suggested);
227
- }
228
- }
229
-
230
- /**
231
- * Registers a preserved token
232
- * @param $token
233
- * @return string The token ID string
234
- */
235
- private function registerPreservedToken($token)
236
- {
237
- $this->preservedTokens[] = $token;
238
- return self::TOKEN . (count($this->preservedTokens) - 1) .'___';
239
- }
240
-
241
- /**
242
- * Gets the regular expression to match the specified token ID string
243
- * @param $id
244
- * @return string
245
- */
246
- private function getPreservedTokenPlaceholderRegexById($id)
247
- {
248
- return '/'. self::TOKEN . $id .'___/';
249
- }
250
-
251
- /**
252
- * Registers a candidate comment token
253
- * @param $comment
254
- * @return string The comment token ID string
255
- */
256
- private function registerComment($comment)
257
- {
258
- $this->comments[] = $comment;
259
- return '/*'. self::COMMENT . (count($this->comments) - 1) .'___*/';
260
- }
261
-
262
- /**
263
- * Gets the candidate comment token ID string for the specified comment token ID
264
- * @param $id
265
- * @return string
266
- */
267
- private function getCommentPlaceholderById($id)
268
- {
269
- return self::COMMENT . $id .'___';
270
- }
271
-
272
- /**
273
- * Gets the regular expression to match the specified comment token ID string
274
- * @param $id
275
- * @return string
276
- */
277
- private function getCommentPlaceholderRegexById($id)
278
- {
279
- return '/'. $this->getCommentPlaceholderById($id) .'/';
280
- }
281
-
282
- /**
283
- * Registers an at rule block token
284
- * @param $block
285
- * @return string The comment token ID string
286
- */
287
- private function registerAtRuleBlock($block)
288
- {
289
- $this->atRuleBlocks[] = $block;
290
- return self::AT_RULE_BLOCK . (count($this->atRuleBlocks) - 1) .'___';
291
- }
292
-
293
- /**
294
- * Gets the regular expression to match the specified at rule block token ID string
295
- * @param $id
296
- * @return string
297
- */
298
- private function getAtRuleBlockPlaceholderRegexById($id)
299
- {
300
- return '/'. self::AT_RULE_BLOCK . $id .'___/';
301
- }
302
-
303
- /**
304
- * Minifies the given input CSS string
305
- * @param string $css
306
- * @param int|bool $linebreakPos
307
- * @return string
308
- */
309
- private function minify($css, $linebreakPos)
310
- {
311
- // Restore preserved at rule blocks
312
- for ($i = 0, $max = count($this->atRuleBlocks); $i < $max; $i++) {
313
- $css = preg_replace(
314
- $this->getAtRuleBlockPlaceholderRegexById($i),
315
- $this->escapeReplacementString($this->atRuleBlocks[$i]),
316
- $css,
317
- 1
318
- );
319
- }
320
-
321
- // strings are safe, now wrestle the comments
322
- for ($i = 0, $max = count($this->comments); $i < $max; $i++) {
323
- $comment = $this->comments[$i];
324
- $commentPlaceholder = $this->getCommentPlaceholderById($i);
325
- $commentPlaceholderRegex = $this->getCommentPlaceholderRegexById($i);
326
-
327
- // ! in the first position of the comment means preserve
328
- // so push to the preserved tokens keeping the !
329
- if (preg_match('/^!/', $comment)) {
330
- $preservedTokenPlaceholder = $this->registerPreservedToken($comment);
331
- $css = preg_replace($commentPlaceholderRegex, $preservedTokenPlaceholder, $css, 1);
332
- // Preserve new lines for /*! important comments
333
- $css = preg_replace('/\R+\s*(\/\*'. $preservedTokenPlaceholder .')/', self::NL.'$1', $css);
334
- $css = preg_replace('/('. $preservedTokenPlaceholder .'\*\/)\s*\R+/', '$1'.self::NL, $css);
335
- continue;
336
- }
337
-
338
- // \ in the last position looks like hack for Mac/IE5
339
- // shorten that to /*\*/ and the next one to /**/
340
- if (preg_match('/\\\\$/', $comment)) {
341
- $preservedTokenPlaceholder = $this->registerPreservedToken('\\');
342
- $css = preg_replace($commentPlaceholderRegex, $preservedTokenPlaceholder, $css, 1);
343
- $i = $i + 1; // attn: advancing the loop
344
- $preservedTokenPlaceholder = $this->registerPreservedToken('');
345
- $css = preg_replace($this->getCommentPlaceholderRegexById($i), $preservedTokenPlaceholder, $css, 1);
346
- continue;
347
- }
348
-
349
- // keep empty comments after child selectors (IE7 hack)
350
- // e.g. html >/**/ body
351
- if (strlen($comment) === 0) {
352
- $startIndex = $this->indexOf($css, $commentPlaceholder);
353
- if ($startIndex > 2) {
354
- if (substr($css, $startIndex - 3, 1) === '>') {
355
- $preservedTokenPlaceholder = $this->registerPreservedToken('');
356
- $css = preg_replace($commentPlaceholderRegex, $preservedTokenPlaceholder, $css, 1);
357
- continue;
358
- }
359
- }
360
- }
361
-
362
- // in all other cases kill the comment
363
- $css = preg_replace('/\/\*' . $commentPlaceholder . '\*\//', '', $css, 1);
364
- }
365
-
366
- // Normalize all whitespace strings to single spaces. Easier to work with that way.
367
- $css = preg_replace('/\s+/', ' ', $css);
368
-
369
- // Remove spaces before & after newlines
370
- $css = preg_replace('/\s*'. self::NL .'\s*/', self::NL, $css);
371
-
372
- // Fix IE7 issue on matrix filters which browser accept whitespaces between Matrix parameters
373
- $css = preg_replace_callback(
374
- '/\s*filter:\s*progid:DXImageTransform\.Microsoft\.Matrix\(([^)]+)\)/',
375
- array($this, 'processOldIeSpecificMatrixDefinition'),
376
- $css
377
- );
378
-
379
- // Shorten & preserve calculations calc(...) since spaces are important
380
- $css = preg_replace_callback('/calc(\(((?:[^()]+|(?1))*)\))/i', array($this, 'processCalc'), $css);
381
-
382
- // Replace positive sign from numbers preceded by : or a white-space before the leading space is removed
383
- // +1.2em to 1.2em, +.8px to .8px, +2% to 2%
384
- $css = preg_replace('/((?<!\\\\):|\s)\+(\.?\d+)/S', '$1$2', $css);
385
-
386
- // Remove leading zeros from integer and float numbers preceded by : or a white-space
387
- // 000.6 to .6, -0.8 to -.8, 0050 to 50, -01.05 to -1.05
388
- $css = preg_replace('/((?<!\\\\):|\s)(-?)0+(\.?\d+)/S', '$1$2$3', $css);
389
-
390
- // Remove trailing zeros from float numbers preceded by : or a white-space
391
- // -6.0100em to -6.01em, .0100 to .01, 1.200px to 1.2px
392
- $css = preg_replace('/((?<!\\\\):|\s)(-?)(\d?\.\d+?)0+([^\d])/S', '$1$2$3$4', $css);
393
-
394
- // Remove trailing .0 -> -9.0 to -9
395
- $css = preg_replace('/((?<!\\\\):|\s)(-?\d+)\.0([^\d])/S', '$1$2$3', $css);
396
-
397
- // Replace 0 length numbers with 0
398
- $css = preg_replace('/((?<!\\\\):|\s)-?\.?0+([^\d])/S', '${1}0$2', $css);
399
-
400
- // Remove the spaces before the things that should not have spaces before them.
401
- // But, be careful not to turn "p :link {...}" into "p:link{...}"
402
- // Swap out any pseudo-class colons with the token, and then swap back.
403
- $css = preg_replace_callback('/(?:^|\})[^{]*\s+:/', array($this, 'processColon'), $css);
404
-
405
- // Remove spaces before the things that should not have spaces before them.
406
- $css = preg_replace('/\s+([!{};:>+()\]~=,])/', '$1', $css);
407
-
408
- // Restore spaces for !important
409
- $css = preg_replace('/!important/i', ' !important', $css);
410
-
411
- // bring back the colon
412
- $css = preg_replace('/'. self::CLASSCOLON .'/', ':', $css);
413
-
414
- // retain space for special IE6 cases
415
- $css = preg_replace_callback('/:first-(line|letter)(\{|,)/i', array($this, 'lowercasePseudoFirst'), $css);
416
-
417
- // no space after the end of a preserved comment
418
- $css = preg_replace('/\*\/ /', '*/', $css);
419
-
420
- // lowercase some popular @directives
421
- $css = preg_replace_callback(
422
- '/@(document|font-face|import|(?:-(?:atsc|khtml|moz|ms|o|wap|webkit)-)?keyframes|media|namespace|page|' .
423
- 'supports|viewport)/i',
424
- array($this, 'lowercaseDirectives'),
425
- $css
426
- );
427
-
428
- // lowercase some more common pseudo-elements
429
- $css = preg_replace_callback(
430
- '/:(active|after|before|checked|disabled|empty|enabled|first-(?:child|of-type)|focus|hover|' .
431
- 'last-(?:child|of-type)|link|only-(?:child|of-type)|root|:selection|target|visited)/i',
432
- array($this, 'lowercasePseudoElements'),
433
- $css
434
- );
435
-
436
- // lowercase some more common functions
437
- $css = preg_replace_callback(
438
- '/:(lang|not|nth-child|nth-last-child|nth-last-of-type|nth-of-type|(?:-(?:moz|webkit)-)?any)\(/i',
439
- array($this, 'lowercaseCommonFunctions'),
440
- $css
441
- );
442
-
443
- // lower case some common function that can be values
444
- // NOTE: rgb() isn't useful as we replace with #hex later, as well as and() is already done for us
445
- $css = preg_replace_callback(
446
- '/([:,( ]\s*)(attr|color-stop|from|rgba|to|url|-webkit-gradient|' .
447
- '(?:-(?:atsc|khtml|moz|ms|o|wap|webkit)-)?(?:calc|max|min|(?:repeating-)?(?:linear|radial)-gradient))/iS',
448
- array($this, 'lowercaseCommonFunctionsValues'),
449
- $css
450
- );
451
-
452
- // Put the space back in some cases, to support stuff like
453
- // @media screen and (-webkit-min-device-pixel-ratio:0){
454
- $css = preg_replace_callback('/(\s|\)\s)(and|not|or)\(/i', array($this, 'processAtRulesOperators'), $css);
455
-
456
- // Remove the spaces after the things that should not have spaces after them.
457
- $css = preg_replace('/([!{}:;>+(\[~=,])\s+/S', '$1', $css);
458
-
459
- // remove unnecessary semicolons
460
- $css = preg_replace('/;+\}/', '}', $css);
461
-
462
- // Fix for issue: #2528146
463
- // Restore semicolon if the last property is prefixed with a `*` (lte IE7 hack)
464
- // to avoid issues on Symbian S60 3.x browsers.
465
- $css = preg_replace('/(\*[a-z0-9\-]+\s*:[^;}]+)(\})/', '$1;$2', $css);
466
-
467
- // Shorten zero values for safe properties only
468
- $css = $this->shortenZeroValues($css);
469
-
470
- // Shorten font-weight values
471
- $css = preg_replace('/(font-weight:)bold\b/i', '${1}700', $css);
472
- $css = preg_replace('/(font-weight:)normal\b/i', '${1}400', $css);
473
-
474
- // Shorten suitable shorthand properties with repeated non-zero values
475
- $css = preg_replace(
476
- '/(margin|padding):('.$this->numRegex.') ('.$this->numRegex.') (?:\2) (?:\3)(;|\}| !)/i',
477
- '$1:$2 $3$4',
478
- $css
479
- );
480
- $css = preg_replace(
481
- '/(margin|padding):('.$this->numRegex.') ('.$this->numRegex.') ('.$this->numRegex.') (?:\3)(;|\}| !)/i',
482
- '$1:$2 $3 $4$5',
483
- $css
484
- );
485
-
486
- // Shorten colors from rgb(51,102,153) to #336699, rgb(100%,0%,0%) to #ff0000 (sRGB color space)
487
- // Shorten colors from hsl(0, 100%, 50%) to #ff0000 (sRGB color space)
488
- // This makes it more likely that it'll get further compressed in the next step.
489
- $css = preg_replace_callback('/rgb\s*\(\s*([0-9,\s\-.%]+)\s*\)(.{1})/i', array($this, 'rgbToHex'), $css);
490
- $css = preg_replace_callback('/hsl\s*\(\s*([0-9,\s\-.%]+)\s*\)(.{1})/i', array($this, 'hslToHex'), $css);
491
-
492
- // Shorten colors from #AABBCC to #ABC or shorter color name.
493
- $css = $this->shortenHexColors($css);
494
-
495
- // Shorten long named colors: white -> #fff.
496
- $css = $this->shortenNamedColors($css);
497
-
498
- // shorter opacity IE filter
499
- $css = preg_replace('/progid:DXImageTransform\.Microsoft\.Alpha\(Opacity=/i', 'alpha(opacity=', $css);
500
-
501
- // Find a fraction that is used for Opera's -o-device-pixel-ratio query
502
- // Add token to add the "\" back in later
503
- $css = preg_replace('/\(([a-z\-]+):([0-9]+)\/([0-9]+)\)/i', '($1:$2'. self::QUERY_FRACTION .'$3)', $css);
504
-
505
- // Patch new lines to avoid being removed when followed by empty rules cases
506
- $css = preg_replace('/'. self::NL .'/', self::NL .'}', $css);
507
-
508
- // Remove empty rules.
509
- $css = preg_replace('/[^{};\/]+\{\}/S', '', $css);
510
-
511
- // Restore new lines for /*! important comments
512
- $css = preg_replace('/'. self::NL .'}/', "\n", $css);
513
-
514
- // Add "/" back to fix Opera -o-device-pixel-ratio query
515
- $css = preg_replace('/'. self::QUERY_FRACTION .'/', '/', $css);
516
-
517
- // Replace multiple semi-colons in a row by a single one
518
- // See SF bug #1980989
519
- $css = preg_replace('/;;+/', ';', $css);
520
-
521
- // Lowercase all uppercase properties
522
- $css = preg_replace_callback('/(\{|;)([A-Z\-]+)(:)/', array($this, 'lowercaseProperties'), $css);
523
-
524
- // Some source control tools don't like it when files containing lines longer
525
- // than, say 8000 characters, are checked in. The linebreak option is used in
526
- // that case to split long lines after a specific column.
527
- if ($linebreakPos !== false && (int) $linebreakPos >= 0) {
528
- $linebreakPos = (int) $linebreakPos;
529
- for ($startIndex = $i = 1, $l = strlen($css); $i < $l; $i++) {
530
- if ($css[$i - 1] === '}' && $i - $startIndex > $linebreakPos) {
531
- $css = $this->strSlice($css, 0, $i) . "\n" . $this->strSlice($css, $i);
532
- $l = strlen($css);
533
- $startIndex = $i;
534
- }
535
- }
536
- }
537
-
538
- // restore preserved comments and strings in reverse order
539
- for ($i = count($this->preservedTokens) - 1; $i >= 0; $i--) {
540
- $css = preg_replace(
541
- $this->getPreservedTokenPlaceholderRegexById($i),
542
- $this->escapeReplacementString($this->preservedTokens[$i]),
543
- $css,
544
- 1
545
- );
546
- }
547
-
548
- // Trim the final string for any leading or trailing white space but respect newlines!
549
- $css = preg_replace('/(^ | $)/', '', $css);
550
-
551
- return $css;
552
- }
553
-
554
- /**
555
- * Searches & replaces all data urls with tokens before we start compressing,
556
- * to avoid performance issues running some of the subsequent regexes against large string chunks.
557
- * @param string $css
558
- * @return string
559
- */
560
- private function processDataUrls($css)
561
- {
562
- // Leave data urls alone to increase parse performance.
563
- $maxIndex = strlen($css) - 1;
564
- $appenIndex = $index = $lastIndex = $offset = 0;
565
- $sb = array();
566
- $pattern = '/url\(\s*(["\']?)data:/i';
567
-
568
- // Since we need to account for non-base64 data urls, we need to handle
569
- // ' and ) being part of the data string. Hence switching to indexOf,
570
- // to determine whether or not we have matching string terminators and
571
- // handling sb appends directly, instead of using matcher.append* methods.
572
- while (preg_match($pattern, $css, $m, 0, $offset)) {
573
- $index = $this->indexOf($css, $m[0], $offset);
574
- $lastIndex = $index + strlen($m[0]);
575
- $startIndex = $index + 4; // "url(".length()
576
- $endIndex = $lastIndex - 1;
577
- $terminator = $m[1]; // ', " or empty (not quoted)
578
- $terminatorFound = false;
579
-
580
- if (strlen($terminator) === 0) {
581
- $terminator = ')';
582
- }
583
-
584
- while ($terminatorFound === false && $endIndex+1 <= $maxIndex) {
585
- $endIndex = $this->indexOf($css, $terminator, $endIndex + 1);
586
- // endIndex == 0 doesn't really apply here
587
- if ($endIndex > 0 && substr($css, $endIndex - 1, 1) !== '\\') {
588
- $terminatorFound = true;
589
- if (')' !== $terminator) {
590
- $endIndex = $this->indexOf($css, ')', $endIndex);
591
- }
592
- }
593
- }
594
-
595
- // Enough searching, start moving stuff over to the buffer
596
- $sb[] = $this->strSlice($css, $appenIndex, $index);
597
-
598
- if ($terminatorFound) {
599
- $token = $this->strSlice($css, $startIndex, $endIndex);
600
- // Remove all spaces only for base64 encoded URLs.
601
- $token = preg_replace_callback(
602
- '/.+base64,.+/s',
603
- array($this, 'removeSpacesFromDataUrls'),
604
- trim($token)
605
- );
606
- $preservedTokenPlaceholder = $this->registerPreservedToken($token);
607
- $sb[] = 'url('. $preservedTokenPlaceholder .')';
608
- $appenIndex = $endIndex + 1;
609
- } else {
610
- // No end terminator found, re-add the whole match. Should we throw/warn here?
611
- $sb[] = $this->strSlice($css, $index, $lastIndex);
612
- $appenIndex = $lastIndex;
613
- }
614
-
615
- $offset = $lastIndex;
616
- }
617
-
618
- $sb[] = $this->strSlice($css, $appenIndex);
619
-
620
- return implode('', $sb);
621
- }
622
-
623
- /**
624
- * Shortens all zero values for a set of safe properties
625
- * e.g. padding: 0px 1px; -> padding:0 1px
626
- * e.g. padding: 0px 0rem 0em 0.0pc; -> padding:0
627
- * @param string $css
628
- * @return string
629
- */
630
- private function shortenZeroValues($css)
631
- {
632
- $unitsGroupReg = $this->unitsGroupRegex;
633
- $numOrPosReg = '('. $this->numRegex .'|top|left|bottom|right|center)';
634
- $oneZeroSafeProperties = array(
635
- '(?:line-)?height',
636
- '(?:(?:min|max)-)?width',
637
- 'top',
638
- 'left',
639
- 'background-position',
640
- 'bottom',
641
- 'right',
642
- 'border(?:-(?:top|left|bottom|right))?(?:-width)?',
643
- 'border-(?:(?:top|bottom)-(?:left|right)-)?radius',
644
- 'column-(?:gap|width)',
645
- 'margin(?:-(?:top|left|bottom|right))?',
646
- 'outline-width',
647
- 'padding(?:-(?:top|left|bottom|right))?'
648
- );
649
- $nZeroSafeProperties = array(
650
- 'margin',
651
- 'padding',
652
- 'background-position'
653
- );
654
-
655
- $regStart = '/(;|\{)';
656
- $regEnd = '/i';
657
-
658
- // First zero regex start
659
- $oneZeroRegStart = $regStart .'('. implode('|', $oneZeroSafeProperties) .'):';
660
-
661
- // Multiple zeros regex start
662
- $nZerosRegStart = $regStart .'('. implode('|', $nZeroSafeProperties) .'):';
663
-
664
- $css = preg_replace(
665
- array(
666
- $oneZeroRegStart .'0'. $unitsGroupReg . $regEnd,
667
- $nZerosRegStart . $numOrPosReg .' 0'. $unitsGroupReg . $regEnd,
668
- $nZerosRegStart . $numOrPosReg .' '. $numOrPosReg .' 0'. $unitsGroupReg . $regEnd,
669
- $nZerosRegStart . $numOrPosReg .' '. $numOrPosReg .' '. $numOrPosReg .' 0'. $unitsGroupReg . $regEnd
670
- ),
671
- array(
672
- '$1$2:0',
673
- '$1$2:$3 0',
674
- '$1$2:$3 $4 0',
675
- '$1$2:$3 $4 $5 0'
676
- ),
677
- $css
678
- );
679
-
680
- // Remove background-position
681
- array_pop($nZeroSafeProperties);
682
-
683
- // Replace 0 0; or 0 0 0; or 0 0 0 0; with 0 for safe properties only.
684
- $css = preg_replace(
685
- '/('. implode('|', $nZeroSafeProperties) .'):0(?: 0){1,3}(;|\}| !)'. $regEnd,
686
- '$1:0$2',
687
- $css
688
- );
689
-
690
- // Replace 0 0 0; or 0 0 0 0; with 0 0 for background-position property.
691
- $css = preg_replace('/(background-position):0(?: 0){2,3}(;|\}| !)'. $regEnd, '$1:0 0$2', $css);
692
-
693
- return $css;
694
- }
695
-
696
- /**
697
- * Shortens all named colors with a shorter HEX counterpart for a set of safe properties
698
- * e.g. white -> #fff
699
- * @param string $css
700
- * @return string
701
- */
702
- private function shortenNamedColors($css)
703
- {
704
- $patterns = array();
705
- $replacements = array();
706
- $longNamedColors = include 'data/named-to-hex-color-map.php';
707
- $propertiesWithColors = array(
708
- 'color',
709
- 'background(?:-color)?',
710
- 'border(?:-(?:top|right|bottom|left|color)(?:-color)?)?',
711
- 'outline(?:-color)?',
712
- '(?:text|box)-shadow'
713
- );
714
-
715
- $regStart = '/(;|\{)('. implode('|', $propertiesWithColors) .'):([^;}]*)\b';
716
- $regEnd = '\b/iS';
717
-
718
- foreach ($longNamedColors as $colorName => $colorCode) {
719
- $patterns[] = $regStart . $colorName . $regEnd;
720
- $replacements[] = '$1$2:$3'. $colorCode;
721
- }
722
-
723
- // Run at least 4 times to cover most cases (same color used several times for the same property)
724
- for ($i = 0; $i < 4; $i++) {
725
- $css = preg_replace($patterns, $replacements, $css);
726
- }
727
-
728
- return $css;
729
- }
730
-
731
- /**
732
- * Compresses HEX color values of the form #AABBCC to #ABC or short color name.
733
- *
734
- * DOES NOT compress CSS ID selectors which match the above pattern (which would break things).
735
- * e.g. #AddressForm { ... }
736
- *
737
- * DOES NOT compress IE filters, which have hex color values (which would break things).
738
- * e.g. filter: chroma(color="#FFFFFF");
739
- *
740
- * DOES NOT compress invalid hex values.
741
- * e.g. background-color: #aabbccdd
742
- *
743
- * @param string $css
744
- * @return string
745
- */
746
- private function shortenHexColors($css)
747
- {
748
- // Look for hex colors inside { ... } (to avoid IDs) and
749
- // which don't have a =, or a " in front of them (to avoid filters)
750
- $pattern =
751
- '/(=\s*?["\']?)?#([0-9a-f])([0-9a-f])([0-9a-f])([0-9a-f])([0-9a-f])([0-9a-f])(\}|[^0-9a-f{][^{]*?\})/iS';
752
- $_index = $index = $lastIndex = $offset = 0;
753
- $longHexColors = include 'data/hex-to-named-color-map.php';
754
- $sb = array();
755
-
756
- while (preg_match($pattern, $css, $m, 0, $offset)) {
757
- $index = $this->indexOf($css, $m[0], $offset);
758
- $lastIndex = $index + strlen($m[0]);
759
- $isFilter = $m[1] !== null && $m[1] !== '';
760
-
761
- $sb[] = $this->strSlice($css, $_index, $index);
762
-
763
- if ($isFilter) {
764
- // Restore, maintain case, otherwise filter will break
765
- $sb[] = $m[1] .'#'. $m[2] . $m[3] . $m[4] . $m[5] . $m[6] . $m[7];
766
- } else {
767
- if (strtolower($m[2]) == strtolower($m[3]) &&
768
- strtolower($m[4]) == strtolower($m[5]) &&
769
- strtolower($m[6]) == strtolower($m[7])) {
770
- // Compress.
771
- $hex = '#'. strtolower($m[3] . $m[5] . $m[7]);
772
- } else {
773
- // Non compressible color, restore but lower case.
774
- $hex = '#'. strtolower($m[2] . $m[3] . $m[4] . $m[5] . $m[6] . $m[7]);
775
- }
776
- // replace Hex colors with shorter color names
777
- $sb[] = array_key_exists($hex, $longHexColors) ? $longHexColors[$hex] : $hex;
778
- }
779
-
780
- $_index = $offset = $lastIndex - strlen($m[8]);
781
- }
782
-
783
- $sb[] = $this->strSlice($css, $_index);
784
-
785
- return implode('', $sb);
786
- }
787
-
788
- // ---------------------------------------------------------------------------------------------
789
- // CALLBACKS
790
- // ---------------------------------------------------------------------------------------------
791
-
792
- private function processComments($matches)
793
- {
794
- $match = !empty($matches[1]) ? $matches[1] : '';
795
- return $this->registerComment($match);
796
- }
797
-
798
- private function processStrings($matches)
799
- {
800
- $match = $matches[0];
801
- $quote = substr($match, 0, 1);
802
- $match = $this->strSlice($match, 1, -1);
803
-
804
- // maybe the string contains a comment-like substring?
805
- // one, maybe more? put'em back then
806
- if (($pos = strpos($match, self::COMMENT)) !== false) {
807
- for ($i = 0, $max = count($this->comments); $i < $max; $i++) {
808
- $match = preg_replace(
809
- $this->getCommentPlaceholderRegexById($i),
810
- $this->escapeReplacementString($this->comments[$i]),
811
- $match,
812
- 1
813
- );
814
- }
815
- }
816
-
817
- // minify alpha opacity in filter strings
818
- $match = preg_replace('/progid:DXImageTransform\.Microsoft\.Alpha\(Opacity=/i', 'alpha(opacity=', $match);
819
-
820
- $preservedTokenPlaceholder = $this->registerPreservedToken($match);
821
- return $quote . $preservedTokenPlaceholder . $quote;
822
- }
823
-
824
- private function processAtRuleBlocks($matches)
825
- {
826
- return $this->registerAtRuleBlock($matches[0]);
827
- }
828
-
829
- private function processCalc($matches)
830
- {
831
- $token = preg_replace(
832
- '/\)([+\-]{1})/',
833
- ') $1',
834
- preg_replace(
835
- '/([+\-]{1})\(/',
836
- '$1 (',
837
- trim(preg_replace('/\s*([*\/(),])\s*/', '$1', $matches[2]))
838
- )
839
- );
840
- $preservedTokenPlaceholder = $this->registerPreservedToken($token);
841
- return 'calc('. $preservedTokenPlaceholder .')';
842
- }
843
-
844
- private function processOldIeSpecificMatrixDefinition($matches)
845
- {
846
- $preservedTokenPlaceholder = $this->registerPreservedToken($matches[1]);
847
- return 'filter:progid:DXImageTransform.Microsoft.Matrix('. $preservedTokenPlaceholder .')';
848
- }
849
-
850
- private function processColon($matches)
851
- {
852
- return preg_replace('/\:/', self::CLASSCOLON, $matches[0]);
853
- }
854
-
855
- private function removeSpacesFromDataUrls($matches)
856
- {
857
- return preg_replace('/\s+/', '', $matches[0]);
858
- }
859
-
860
- private function rgbToHex($matches)
861
- {
862
- $hexColors = array();
863
- $rgbColors = explode(',', $matches[1]);
864
-
865
- // Values outside the sRGB color space should be clipped (0-255)
866
- for ($i = 0, $l = count($rgbColors); $i < $l; $i++) {
867
- $hexColors[$i] = sprintf("%02x", $this->clampNumberSrgb($this->rgbPercentageToRgbInteger($rgbColors[$i])));
868
- }
869
-
870
- // Fix for issue #2528093
871
- if (!preg_match('/[\s,);}]/', $matches[2])) {
872
- $matches[2] = ' '. $matches[2];
873
- }
874
-
875
- return '#'. implode('', $hexColors) . $matches[2];
876
- }
877
-
878
- private function hslToHex($matches)
879
- {
880
- $hslValues = explode(',', $matches[1]);
881
-
882
- $rgbColors = $this->hslToRgb($hslValues);
883
-
884
- return $this->rgbToHex(array('', implode(',', $rgbColors), $matches[2]));
885
- }
886
-
887
- private function processAtRulesOperators($matches)
888
- {
889
- return $matches[1] . strtolower($matches[2]) .' (';
890
- }
891
-
892
- private function lowercasePseudoFirst($matches)
893
- {
894
- return ':first-'. strtolower($matches[1]) .' '. $matches[2];
895
- }
896
-
897
- private function lowercaseDirectives($matches)
898
- {
899
- return '@'. strtolower($matches[1]);
900
- }
901
-
902
- private function lowercasePseudoElements($matches)
903
- {
904
- return ':'. strtolower($matches[1]);
905
- }
906
-
907
- private function lowercaseCommonFunctions($matches)
908
- {
909
- return ':'. strtolower($matches[1]) .'(';
910
- }
911
-
912
- private function lowercaseCommonFunctionsValues($matches)
913
- {
914
- return $matches[1] . strtolower($matches[2]);
915
- }
916
-
917
- private function lowercaseProperties($matches)
918
- {
919
- return $matches[1] . strtolower($matches[2]) . $matches[3];
920
- }
921
-
922
- // ---------------------------------------------------------------------------------------------
923
- // HELPERS
924
- // ---------------------------------------------------------------------------------------------
925
-
926
- /**
927
- * Clamps a number between a minimum and a maximum value.
928
- * @param int|float $n the number to clamp
929
- * @param int|float $min the lower end number allowed
930
- * @param int|float $max the higher end number allowed
931
- * @return int|float
932
- */
933
- private function clampNumber($n, $min, $max)
934
- {
935
- return min(max($n, $min), $max);
936
- }
937
-
938
- /**
939
- * Clamps a RGB color number outside the sRGB color space
940
- * @param int|float $n the number to clamp
941
- * @return int|float
942
- */
943
- private function clampNumberSrgb($n)
944
- {
945
- return $this->clampNumber($n, 0, 255);
946
- }
947
-
948
- /**
949
- * Escapes backreferences such as \1 and $1 in a regular expression replacement string
950
- * @param $string
951
- * @return string
952
- */
953
- private function escapeReplacementString($string)
954
- {
955
- return addcslashes($string, '\\$');
956
- }
957
-
958
- /**
959
- * Converts a HSL color into a RGB color
960
- * @param array $hslValues
961
- * @return array
962
- */
963
- private function hslToRgb($hslValues)
964
- {
965
- $h = floatval($hslValues[0]);
966
- $s = floatval(str_replace('%', '', $hslValues[1]));
967
- $l = floatval(str_replace('%', '', $hslValues[2]));
968
-
969
- // Wrap and clamp, then fraction!
970
- $h = ((($h % 360) + 360) % 360) / 360;
971
- $s = $this->clampNumber($s, 0, 100) / 100;
972
- $l = $this->clampNumber($l, 0, 100) / 100;
973
-
974
- if ($s == 0) {
975
- $r = $g = $b = $this->roundNumber(255 * $l);
976
- } else {
977
- $v2 = $l < 0.5 ? $l * (1 + $s) : ($l + $s) - ($s * $l);
978
- $v1 = (2 * $l) - $v2;
979
- $r = $this->roundNumber(255 * $this->hueToRgb($v1, $v2, $h + (1/3)));
980
- $g = $this->roundNumber(255 * $this->hueToRgb($v1, $v2, $h));
981
- $b = $this->roundNumber(255 * $this->hueToRgb($v1, $v2, $h - (1/3)));
982
- }
983
-
984
- return array($r, $g, $b);
985
- }
986
-
987
- /**
988
- * Tests and selects the correct formula for each RGB color channel
989
- * @param $v1
990
- * @param $v2
991
- * @param $vh
992
- * @return mixed
993
- */
994
- private function hueToRgb($v1, $v2, $vh)
995
- {
996
- $vh = $vh < 0 ? $vh + 1 : ($vh > 1 ? $vh - 1 : $vh);
997
-
998
- if ($vh * 6 < 1) {
999
- return $v1 + ($v2 - $v1) * 6 * $vh;
1000
- }
1001
-
1002
- if ($vh * 2 < 1) {
1003
- return $v2;
1004
- }
1005
-
1006
- if ($vh * 3 < 2) {
1007
- return $v1 + ($v2 - $v1) * ((2 / 3) - $vh) * 6;
1008
- }
1009
-
1010
- return $v1;
1011
- }
1012
-
1013
- /**
1014
- * PHP port of Javascript's "indexOf" function for strings only
1015
- * Author: Tubal Martin
1016
- *
1017
- * @param string $haystack
1018
- * @param string $needle
1019
- * @param int $offset index (optional)
1020
- * @return int
1021
- */
1022
- private function indexOf($haystack, $needle, $offset = 0)
1023
- {
1024
- $index = strpos($haystack, $needle, $offset);
1025
-
1026
- return ($index !== false) ? $index : -1;
1027
- }
1028
-
1029
- /**
1030
- * Convert strings like "64M" or "30" to int values
1031
- * @param mixed $size
1032
- * @return int
1033
- */
1034
- private function normalizeInt($size)
1035
- {
1036
- if (is_string($size)) {
1037
- $letter = substr($size, -1);
1038
- $size = intval($size);
1039
- switch ($letter) {
1040
- case 'M':
1041
- case 'm':
1042
- return (int) $size * 1048576;
1043
- case 'K':
1044
- case 'k':
1045
- return (int) $size * 1024;
1046
- case 'G':
1047
- case 'g':
1048
- return (int) $size * 1073741824;
1049
- }
1050
- }
1051
- return (int) $size;
1052
- }
1053
-
1054
- /**
1055
- * Converts a string containing and RGB percentage value into a RGB integer value i.e. '90%' -> 229.5
1056
- * @param $rgbPercentage
1057
- * @return int
1058
- */
1059
- private function rgbPercentageToRgbInteger($rgbPercentage)
1060
- {
1061
- if (strpos($rgbPercentage, '%') !== false) {
1062
- $rgbPercentage = $this->roundNumber(floatval(str_replace('%', '', $rgbPercentage)) * 2.55);
1063
- }
1064
-
1065
- return intval($rgbPercentage, 10);
1066
- }
1067
-
1068
- /**
1069
- * Rounds a number to its closest integer
1070
- * @param $n
1071
- * @return int
1072
- */
1073
- private function roundNumber($n)
1074
- {
1075
- return intval(round(floatval($n)), 10);
1076
- }
1077
-
1078
- /**
1079
- * PHP port of Javascript's "slice" function for strings only
1080
- * Author: Tubal Martin
1081
- *
1082
- * @param string $str
1083
- * @param int $start index
1084
- * @param int|bool $end index (optional)
1085
- * @return string
1086
- */
1087
- private function strSlice($str, $start = 0, $end = false)
1088
- {
1089
- if ($end !== false && ($start < 0 || $end <= 0)) {
1090
- $max = strlen($str);
1091
-
1092
- if ($start < 0) {
1093
- if (($start = $max + $start) < 0) {
1094
- return '';
1095
- }
1096
- }
1097
-
1098
- if ($end < 0) {
1099
- if (($end = $max + $end) < 0) {
1100
- return '';
1101
- }
1102
- }
1103
-
1104
- if ($end <= $start) {
1105
- return '';
1106
- }
1107
- }
1108
-
1109
- $slice = ($end === false) ? substr($str, $start) : substr($str, $start, $end - $start);
1110
- return ($slice === false) ? '' : $slice;
1111
- }
1112
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
classes/external/php/yui-php-cssmin-2.4.8-p10/data/hex-to-named-color-map.php DELETED
@@ -1,37 +0,0 @@
1
- <?php
2
-
3
- // Hex colors longer than named counterpart
4
- return array(
5
- '#f0ffff' => 'azure',
6
- '#f5f5dc' => 'beige',
7
- '#ffe4c4' => 'bisque',
8
- '#a52a2a' => 'brown',
9
- '#ff7f50' => 'coral',
10
- '#ffd700' => 'gold',
11
- '#808080' => 'gray',
12
- '#008000' => 'green',
13
- '#4b0082' => 'indigo',
14
- '#fffff0' => 'ivory',
15
- '#f0e68c' => 'khaki',
16
- '#faf0e6' => 'linen',
17
- '#800000' => 'maroon',
18
- '#000080' => 'navy',
19
- '#fdf5e6' => 'oldlace',
20
- '#808000' => 'olive',
21
- '#ffa500' => 'orange',
22
- '#da70d6' => 'orchid',
23
- '#cd853f' => 'peru',
24
- '#ffc0cb' => 'pink',
25
- '#dda0dd' => 'plum',
26
- '#800080' => 'purple',
27
- '#f00' => 'red',
28
- '#fa8072' => 'salmon',
29
- '#a0522d' => 'sienna',
30
- '#c0c0c0' => 'silver',
31
- '#fffafa' => 'snow',
32
- '#d2b48c' => 'tan',
33
- '#008080' => 'teal',
34
- '#ff6347' => 'tomato',
35
- '#ee82ee' => 'violet',
36
- '#f5deb3' => 'wheat'
37
- );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
classes/external/php/yui-php-cssmin-2.4.8-p10/data/named-to-hex-color-map.php DELETED
@@ -1,108 +0,0 @@
1
- <?php
2
-
3
- // Named colors longer than hex counterpart
4
- return array(
5
- 'aliceblue' => '#f0f8ff',
6
- 'antiquewhite' => '#faebd7',
7
- 'aquamarine' => '#7fffd4',
8
- 'black' => '#000',
9
- 'blanchedalmond' => '#ffebcd',
10
- 'blueviolet' => '#8a2be2',
11
- 'burlywood' => '#deb887',
12
- 'cadetblue' => '#5f9ea0',
13
- 'chartreuse' => '#7fff00',
14
- 'chocolate' => '#d2691e',
15
- 'cornflowerblue' => '#6495ed',
16
- 'cornsilk' => '#fff8dc',
17
- 'darkblue' => '#00008b',
18
- 'darkcyan' => '#008b8b',
19
- 'darkgoldenrod' => '#b8860b',
20
- 'darkgray' => '#a9a9a9',
21
- 'darkgreen' => '#006400',
22
- 'darkgrey' => '#a9a9a9',
23
- 'darkkhaki' => '#bdb76b',
24
- 'darkmagenta' => '#8b008b',
25
- 'darkolivegreen' => '#556b2f',
26
- 'darkorange' => '#ff8c00',
27
- 'darkorchid' => '#9932cc',
28
- 'darksalmon' => '#e9967a',
29
- 'darkseagreen' => '#8fbc8f',
30
- 'darkslateblue' => '#483d8b',
31
- 'darkslategray' => '#2f4f4f',
32
- 'darkslategrey' => '#2f4f4f',
33
- 'darkturquoise' => '#00ced1',
34
- 'darkviolet' => '#9400d3',
35
- 'deeppink' => '#ff1493',
36
- 'deepskyblue' => '#00bfff',
37
- 'dodgerblue' => '#1e90ff',
38
- 'firebrick' => '#b22222',
39
- 'floralwhite' => '#fffaf0',
40
- 'forestgreen' => '#228b22',
41
- 'fuchsia' => '#f0f',
42
- 'gainsboro' => '#dcdcdc',
43
- 'ghostwhite' => '#f8f8ff',
44
- 'goldenrod' => '#daa520',
45
- 'greenyellow' => '#adff2f',
46
- 'honeydew' => '#f0fff0',
47
- 'indianred' => '#cd5c5c',
48
- 'lavender' => '#e6e6fa',
49
- 'lavenderblush' => '#fff0f5',
50
- 'lawngreen' => '#7cfc00',
51
- 'lemonchiffon' => '#fffacd',
52
- 'lightblue' => '#add8e6',
53
- 'lightcoral' => '#f08080',
54
- 'lightcyan' => '#e0ffff',
55
- 'lightgoldenrodyellow' => '#fafad2',
56
- 'lightgray' => '#d3d3d3',
57
- 'lightgreen' => '#90ee90',
58
- 'lightgrey' => '#d3d3d3',
59
- 'lightpink' => '#ffb6c1',
60
- 'lightsalmon' => '#ffa07a',
61
- 'lightseagreen' => '#20b2aa',
62
- 'lightskyblue' => '#87cefa',
63
- 'lightslategray' => '#778899',
64
- 'lightslategrey' => '#778899',
65
- 'lightsteelblue' => '#b0c4de',
66
- 'lightyellow' => '#ffffe0',
67
- 'limegreen' => '#32cd32',
68
- 'mediumaquamarine' => '#66cdaa',
69
- 'mediumblue' => '#0000cd',
70
- 'mediumorchid' => '#ba55d3',
71
- 'mediumpurple' => '#9370db',
72
- 'mediumseagreen' => '#3cb371',
73
- 'mediumslateblue' => '#7b68ee',
74
- 'mediumspringgreen' => '#00fa9a',
75
- 'mediumturquoise' => '#48d1cc',
76
- 'mediumvioletred' => '#c71585',
77
- 'midnightblue' => '#191970',
78
- 'mintcream' => '#f5fffa',
79
- 'mistyrose' => '#ffe4e1',
80
- 'moccasin' => '#ffe4b5',
81
- 'navajowhite' => '#ffdead',
82
- 'olivedrab' => '#6b8e23',
83
- 'orangered' => '#ff4500',
84
- 'palegoldenrod' => '#eee8aa',
85
- 'palegreen' => '#98fb98',
86
- 'paleturquoise' => '#afeeee',
87
- 'palevioletred' => '#db7093',
88
- 'papayawhip' => '#ffefd5',
89
- 'peachpuff' => '#ffdab9',
90
- 'powderblue' => '#b0e0e6',
91
- 'rebeccapurple' => '#663399',
92
- 'rosybrown' => '#bc8f8f',
93
- 'royalblue' => '#4169e1',
94
- 'saddlebrown' => '#8b4513',
95
- 'sandybrown' => '#f4a460',
96
- 'seagreen' => '#2e8b57',
97
- 'seashell' => '#fff5ee',
98
- 'slateblue' => '#6a5acd',
99
- 'slategray' => '#708090',
100
- 'slategrey' => '#708090',
101
- 'springgreen' => '#00ff7f',
102
- 'steelblue' => '#4682b4',
103
- 'turquoise' => '#40e0d0',
104
- 'white' => '#fff',
105
- 'whitesmoke' => '#f5f5f5',
106
- 'yellow' => '#ff0',
107
- 'yellowgreen' => '#9acd32'
108
- );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
classes/external/php/yui-php-cssmin-bundled/Colors.php ADDED
@@ -0,0 +1,155 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Autoptimize\tubalmartin\CssMin;
4
+
5
+ class Colors
6
+ {
7
+ public static function getHexToNamedMap()
8
+ {
9
+ // Hex colors longer than named counterpart
10
+ return array(
11
+ '#f0ffff' => 'azure',
12
+ '#f5f5dc' => 'beige',
13
+ '#ffe4c4' => 'bisque',
14
+ '#a52a2a' => 'brown',
15
+ '#ff7f50' => 'coral',
16
+ '#ffd700' => 'gold',
17
+ '#808080' => 'gray',
18
+ '#008000' => 'green',
19
+ '#4b0082' => 'indigo',
20
+ '#fffff0' => 'ivory',
21
+ '#f0e68c' => 'khaki',
22
+ '#faf0e6' => 'linen',
23
+ '#800000' => 'maroon',
24
+ '#000080' => 'navy',
25
+ '#fdf5e6' => 'oldlace',
26
+ '#808000' => 'olive',
27
+ '#ffa500' => 'orange',
28
+ '#da70d6' => 'orchid',
29
+ '#cd853f' => 'peru',
30
+ '#ffc0cb' => 'pink',
31
+ '#dda0dd' => 'plum',
32
+ '#800080' => 'purple',
33
+ '#f00' => 'red',
34
+ '#fa8072' => 'salmon',
35
+ '#a0522d' => 'sienna',
36
+ '#c0c0c0' => 'silver',
37
+ '#fffafa' => 'snow',
38
+ '#d2b48c' => 'tan',
39
+ '#008080' => 'teal',
40
+ '#ff6347' => 'tomato',
41
+ '#ee82ee' => 'violet',
42
+ '#f5deb3' => 'wheat'
43
+ );
44
+ }
45
+
46
+ public static function getNamedToHexMap()
47
+ {
48
+ // Named colors longer than hex counterpart
49
+ return array(
50
+ 'aliceblue' => '#f0f8ff',
51
+ 'antiquewhite' => '#faebd7',
52
+ 'aquamarine' => '#7fffd4',
53
+ 'black' => '#000',
54
+ 'blanchedalmond' => '#ffebcd',
55
+ 'blueviolet' => '#8a2be2',
56
+ 'burlywood' => '#deb887',
57
+ 'cadetblue' => '#5f9ea0',
58
+ 'chartreuse' => '#7fff00',
59
+ 'chocolate' => '#d2691e',
60
+ 'cornflowerblue' => '#6495ed',
61
+ 'cornsilk' => '#fff8dc',
62
+ 'darkblue' => '#00008b',
63
+ 'darkcyan' => '#008b8b',
64
+ 'darkgoldenrod' => '#b8860b',
65
+ 'darkgray' => '#a9a9a9',
66
+ 'darkgreen' => '#006400',
67
+ 'darkgrey' => '#a9a9a9',
68
+ 'darkkhaki' => '#bdb76b',
69
+ 'darkmagenta' => '#8b008b',
70
+ 'darkolivegreen' => '#556b2f',
71
+ 'darkorange' => '#ff8c00',
72
+ 'darkorchid' => '#9932cc',
73
+ 'darksalmon' => '#e9967a',
74
+ 'darkseagreen' => '#8fbc8f',
75
+ 'darkslateblue' => '#483d8b',
76
+ 'darkslategray' => '#2f4f4f',
77
+ 'darkslategrey' => '#2f4f4f',
78
+ 'darkturquoise' => '#00ced1',
79
+ 'darkviolet' => '#9400d3',
80
+ 'deeppink' => '#ff1493',
81
+ 'deepskyblue' => '#00bfff',
82
+ 'dodgerblue' => '#1e90ff',
83
+ 'firebrick' => '#b22222',
84
+ 'floralwhite' => '#fffaf0',
85
+ 'forestgreen' => '#228b22',
86
+ 'fuchsia' => '#f0f',
87
+ 'gainsboro' => '#dcdcdc',
88
+ 'ghostwhite' => '#f8f8ff',
89
+ 'goldenrod' => '#daa520',
90
+ 'greenyellow' => '#adff2f',
91
+ 'honeydew' => '#f0fff0',
92
+ 'indianred' => '#cd5c5c',
93
+ 'lavender' => '#e6e6fa',
94
+ 'lavenderblush' => '#fff0f5',
95
+ 'lawngreen' => '#7cfc00',
96
+ 'lemonchiffon' => '#fffacd',
97
+ 'lightblue' => '#add8e6',
98
+ 'lightcoral' => '#f08080',
99
+ 'lightcyan' => '#e0ffff',
100
+ 'lightgoldenrodyellow' => '#fafad2',
101
+ 'lightgray' => '#d3d3d3',
102
+ 'lightgreen' => '#90ee90',
103
+ 'lightgrey' => '#d3d3d3',
104
+ 'lightpink' => '#ffb6c1',
105
+ 'lightsalmon' => '#ffa07a',
106
+ 'lightseagreen' => '#20b2aa',
107
+ 'lightskyblue' => '#87cefa',
108
+ 'lightslategray' => '#778899',
109
+ 'lightslategrey' => '#778899',
110
+ 'lightsteelblue' => '#b0c4de',
111
+ 'lightyellow' => '#ffffe0',
112
+ 'limegreen' => '#32cd32',
113
+ 'mediumaquamarine' => '#66cdaa',
114
+ 'mediumblue' => '#0000cd',
115
+ 'mediumorchid' => '#ba55d3',
116
+ 'mediumpurple' => '#9370db',
117
+ 'mediumseagreen' => '#3cb371',
118
+ 'mediumslateblue' => '#7b68ee',
119
+ 'mediumspringgreen' => '#00fa9a',
120
+ 'mediumturquoise' => '#48d1cc',
121
+ 'mediumvioletred' => '#c71585',
122
+ 'midnightblue' => '#191970',
123
+ 'mintcream' => '#f5fffa',
124
+ 'mistyrose' => '#ffe4e1',
125
+ 'moccasin' => '#ffe4b5',
126
+ 'navajowhite' => '#ffdead',
127
+ 'olivedrab' => '#6b8e23',
128
+ 'orangered' => '#ff4500',
129
+ 'palegoldenrod' => '#eee8aa',
130
+ 'palegreen' => '#98fb98',
131
+ 'paleturquoise' => '#afeeee',
132
+ 'palevioletred' => '#db7093',
133
+ 'papayawhip' => '#ffefd5',
134
+ 'peachpuff' => '#ffdab9',
135
+ 'powderblue' => '#b0e0e6',
136
+ 'rebeccapurple' => '#663399',
137
+ 'rosybrown' => '#bc8f8f',
138
+ 'royalblue' => '#4169e1',
139
+ 'saddlebrown' => '#8b4513',
140
+ 'sandybrown' => '#f4a460',
141
+ 'seagreen' => '#2e8b57',
142
+ 'seashell' => '#fff5ee',
143
+ 'slateblue' => '#6a5acd',
144
+ 'slategray' => '#708090',
145
+ 'slategrey' => '#708090',
146
+ 'springgreen' => '#00ff7f',
147
+ 'steelblue' => '#4682b4',
148
+ 'turquoise' => '#40e0d0',
149
+ 'white' => '#fff',
150
+ 'whitesmoke' => '#f5f5f5',
151
+ 'yellow' => '#ff0',
152
+ 'yellowgreen' => '#9acd32'
153
+ );
154
+ }
155
+ }
classes/external/php/yui-php-cssmin-bundled/Minifier.php ADDED
@@ -0,0 +1,895 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*!
4
+ * CssMin
5
+ * Author: Tubal Martin - http://tubalmartin.me/
6
+ * Repo: https://github.com/tubalmartin/YUI-CSS-compressor-PHP-port
7
+ *
8
+ * This is a PHP port of the CSS minification tool distributed with YUICompressor,
9
+ * itself a port of the cssmin utility by Isaac Schlueter - http://foohack.com/
10
+ * Permission is hereby granted to use the PHP version under the same
11
+ * conditions as the YUICompressor.
12
+ */
13
+
14
+ /*!
15
+ * YUI Compressor
16
+ * http://developer.yahoo.com/yui/compressor/
17
+ * Author: Julien Lecomte - http://www.julienlecomte.net/
18
+ * Copyright (c) 2013 Yahoo! Inc. All rights reserved.
19
+ * The copyrights embodied in the content of this file are licensed
20
+ * by Yahoo! Inc. under the BSD (revised) open source license.
21
+ */
22
+
23
+ namespace Autoptimize\tubalmartin\CssMin;
24
+
25
+ class Minifier
26
+ {
27
+ const QUERY_FRACTION = '_CSSMIN_QF_';
28
+ const COMMENT_TOKEN = '_CSSMIN_CMT_%d_';
29
+ const COMMENT_TOKEN_START = '_CSSMIN_CMT_';
30
+ const RULE_BODY_TOKEN = '_CSSMIN_RBT_%d_';
31
+ const PRESERVED_TOKEN = '_CSSMIN_PTK_%d_';
32
+
33
+ // Token lists
34
+ private $comments = array();
35
+ private $ruleBodies = array();
36
+ private $preservedTokens = array();
37
+
38
+ // Output options
39
+ private $keepImportantComments = true;
40
+ private $keepSourceMapComment = false;
41
+ private $linebreakPosition = 0;
42
+
43
+ // PHP ini limits
44
+ private $raisePhpLimits;
45
+ private $memoryLimit;
46
+ private $maxExecutionTime = 60; // 1 min
47
+ private $pcreBacktrackLimit;
48
+ private $pcreRecursionLimit;
49
+
50
+ // Color maps
51
+ private $hexToNamedColorsMap;
52
+ private $namedToHexColorsMap;
53
+
54
+ // Regexes
55
+ private $numRegex;
56
+ private $charsetRegex = '/@charset [^;]+;/Si';
57
+ private $importRegex = '/@import [^;]+;/Si';
58
+ private $namespaceRegex = '/@namespace [^;]+;/Si';
59
+ private $namedToHexColorsRegex;
60
+ private $shortenOneZeroesRegex;
61
+ private $shortenTwoZeroesRegex;
62
+ private $shortenThreeZeroesRegex;
63
+ private $shortenFourZeroesRegex;
64
+ private $unitsGroupRegex = '(?:ch|cm|em|ex|gd|in|mm|px|pt|pc|q|rem|vh|vmax|vmin|vw|%)';
65
+
66
+ /**
67
+ * @param bool|int $raisePhpLimits If true, PHP settings will be raised if needed
68
+ */
69
+ public function __construct($raisePhpLimits = true)
70
+ {
71
+ $this->raisePhpLimits = (bool) $raisePhpLimits;
72
+ $this->memoryLimit = 128 * 1048576; // 128MB in bytes
73
+ $this->pcreBacktrackLimit = 1000 * 1000;
74
+ $this->pcreRecursionLimit = 500 * 1000;
75
+ $this->hexToNamedColorsMap = Colors::getHexToNamedMap();
76
+ $this->namedToHexColorsMap = Colors::getNamedToHexMap();
77
+ $this->namedToHexColorsRegex = sprintf(
78
+ '/([:,( ])(%s)( |,|\)|;|$)/Si',
79
+ implode('|', array_keys($this->namedToHexColorsMap))
80
+ );
81
+ $this->numRegex = sprintf('-?\d*\.?\d+%s?', $this->unitsGroupRegex);
82
+ $this->setShortenZeroValuesRegexes();
83
+ }
84
+
85
+ /**
86
+ * Parses & minifies the given input CSS string
87
+ * @param string $css
88
+ * @return string
89
+ */
90
+ public function run($css = '')
91
+ {
92
+ if (empty($css) || !is_string($css)) {
93
+ return '';
94
+ }
95
+
96
+ $this->resetRunProperties();
97
+
98
+ if ($this->raisePhpLimits) {
99
+ $this->doRaisePhpLimits();
100
+ }
101
+
102
+ return $this->minify($css);
103
+ }
104
+
105
+ /**
106
+ * Sets whether to keep or remove sourcemap special comment.
107
+ * Sourcemap comments are removed by default.
108
+ * @param bool $keepSourceMapComment
109
+ */
110
+ public function keepSourceMapComment($keepSourceMapComment = true)
111
+ {
112
+ $this->keepSourceMapComment = (bool) $keepSourceMapComment;
113
+ }
114
+
115
+ /**
116
+ * Sets whether to keep or remove important comments.
117
+ * Important comments outside of a declaration block are kept by default.
118
+ * @param bool $removeImportantComments
119
+ */
120
+ public function removeImportantComments($removeImportantComments = true)
121
+ {
122
+ $this->keepImportantComments = !(bool) $removeImportantComments;
123
+ }
124
+
125
+ /**
126
+ * Sets the approximate column after which long lines will be splitted in the output
127
+ * with a linebreak.
128
+ * @param int $position
129
+ */
130
+ public function setLineBreakPosition($position)
131
+ {
132
+ $this->linebreakPosition = (int) $position;
133
+ }
134
+
135
+ /**
136
+ * Sets the memory limit for this script
137
+ * @param int|string $limit
138
+ */
139
+ public function setMemoryLimit($limit)
140
+ {
141
+ $this->memoryLimit = Utils::normalizeInt($limit);
142
+ }
143
+
144
+ /**
145
+ * Sets the maximum execution time for this script
146
+ * @param int|string $seconds
147
+ */
148
+ public function setMaxExecutionTime($seconds)
149
+ {
150
+ $this->maxExecutionTime = (int) $seconds;
151
+ }
152
+
153
+ /**
154
+ * Sets the PCRE backtrack limit for this script
155
+ * @param int $limit
156
+ */
157
+ public function setPcreBacktrackLimit($limit)
158
+ {
159
+ $this->pcreBacktrackLimit = (int) $limit;
160
+ }
161
+
162
+ /**
163
+ * Sets the PCRE recursion limit for this script
164
+ * @param int $limit
165
+ */
166
+ public function setPcreRecursionLimit($limit)
167
+ {
168
+ $this->pcreRecursionLimit = (int) $limit;
169
+ }
170
+
171
+ /**
172
+ * Builds regular expressions needed for shortening zero values
173
+ */
174
+ private function setShortenZeroValuesRegexes()
175
+ {
176
+ $zeroRegex = '0'. $this->unitsGroupRegex;
177
+ $numOrPosRegex = '('. $this->numRegex .'|top|left|bottom|right|center) ';
178
+ $oneZeroSafeProperties = array(
179
+ '(?:line-)?height',
180
+ '(?:(?:min|max)-)?width',
181
+ 'top',
182
+ 'left',
183
+ 'background-position',
184
+ 'bottom',
185
+ 'right',
186
+ 'border(?:-(?:top|left|bottom|right))?(?:-width)?',
187
+ 'border-(?:(?:top|bottom)-(?:left|right)-)?radius',
188
+ 'column-(?:gap|width)',
189
+ 'margin(?:-(?:top|left|bottom|right))?',
190
+ 'outline-width',
191
+ 'padding(?:-(?:top|left|bottom|right))?'
192
+ );
193
+
194
+ // First zero regex
195
+ $regex = '/(^|;)('. implode('|', $oneZeroSafeProperties) .'):%s/Si';
196
+ $this->shortenOneZeroesRegex = sprintf($regex, $zeroRegex);
197
+
198
+ // Multiple zeroes regexes
199
+ $regex = '/(^|;)(margin|padding|border-(?:width|radius)|background-position):%s/Si';
200
+ $this->shortenTwoZeroesRegex = sprintf($regex, $numOrPosRegex . $zeroRegex);
201
+ $this->shortenThreeZeroesRegex = sprintf($regex, $numOrPosRegex . $numOrPosRegex . $zeroRegex);
202
+ $this->shortenFourZeroesRegex = sprintf($regex, $numOrPosRegex . $numOrPosRegex . $numOrPosRegex . $zeroRegex);
203
+ }
204
+
205
+ /**
206
+ * Resets properties whose value may change between runs
207
+ */
208
+ private function resetRunProperties()
209
+ {
210
+ $this->comments = array();
211
+ $this->ruleBodies = array();
212
+ $this->preservedTokens = array();
213
+ }
214
+
215
+ /**
216
+ * Tries to configure PHP to use at least the suggested minimum settings
217
+ * @return void
218
+ */
219
+ private function doRaisePhpLimits()
220
+ {
221
+ $phpLimits = array(
222
+ 'memory_limit' => $this->memoryLimit,
223
+ 'max_execution_time' => $this->maxExecutionTime,
224
+ 'pcre.backtrack_limit' => $this->pcreBacktrackLimit,
225
+ 'pcre.recursion_limit' => $this->pcreRecursionLimit
226
+ );
227
+
228
+ // If current settings are higher respect them.
229
+ foreach ($phpLimits as $name => $suggested) {
230
+ $current = Utils::normalizeInt(ini_get($name));
231
+
232
+ if ($current >= $suggested) {
233
+ continue;
234
+ }
235
+
236
+ // memoryLimit exception: allow -1 for "no memory limit".
237
+ if ($name === 'memory_limit' && $current === -1) {
238
+ continue;
239
+ }
240
+
241
+ // maxExecutionTime exception: allow 0 for "no memory limit".
242
+ if ($name === 'max_execution_time' && $current === 0) {
243
+ continue;
244
+ }
245
+
246
+ ini_set($name, $suggested);
247
+ }
248
+ }
249
+
250
+ /**
251
+ * Registers a preserved token
252
+ * @param string $token
253
+ * @return string The token ID string
254
+ */
255
+ private function registerPreservedToken($token)
256
+ {
257
+ $tokenId = sprintf(self::PRESERVED_TOKEN, count($this->preservedTokens));
258
+ $this->preservedTokens[$tokenId] = $token;
259
+ return $tokenId;
260
+ }
261
+
262
+ /**
263
+ * Registers a candidate comment token
264
+ * @param string $comment
265
+ * @return string The comment token ID string
266
+ */
267
+ private function registerCommentToken($comment)
268
+ {
269
+ $tokenId = sprintf(self::COMMENT_TOKEN, count($this->comments));
270
+ $this->comments[$tokenId] = $comment;
271
+ return $tokenId;
272
+ }
273
+
274
+ /**
275
+ * Registers a rule body token
276
+ * @param string $body the minified rule body
277
+ * @return string The rule body token ID string
278
+ */
279
+ private function registerRuleBodyToken($body)
280
+ {
281
+ if (empty($body)) {
282
+ return '';
283
+ }
284
+
285
+ $tokenId = sprintf(self::RULE_BODY_TOKEN, count($this->ruleBodies));
286
+ $this->ruleBodies[$tokenId] = $body;
287
+ return $tokenId;
288
+ }
289
+
290
+ /**
291
+ * Parses & minifies the given input CSS string
292
+ * @param string $css
293
+ * @return string
294
+ */
295
+ private function minify($css)
296
+ {
297
+ // Process data urls
298
+ $css = $this->processDataUrls($css);
299
+
300
+ // Process comments
301
+ $css = preg_replace_callback(
302
+ '/(?<!\\\\)\/\*(.*?)\*(?<!\\\\)\//Ss',
303
+ array($this, 'processCommentsCallback'),
304
+ $css
305
+ );
306
+
307
+ // IE7: Process Microsoft matrix filters (whitespaces between Matrix parameters). Can contain strings inside.
308
+ $css = preg_replace_callback(
309
+ '/filter:\s*progid:DXImageTransform\.Microsoft\.Matrix\(([^)]+)\)/Ss',
310
+ array($this, 'processOldIeSpecificMatrixDefinitionCallback'),
311
+ $css
312
+ );
313
+
314
+ // Process quoted unquotable attribute selectors to unquote them. Covers most common cases.
315
+ // Likelyhood of a quoted attribute selector being a substring in a string: Very very low.
316
+ $css = preg_replace(
317
+ '/\[\s*([a-z][a-z-]+)\s*([\*\|\^\$~]?=)\s*[\'"](-?[a-z_][a-z0-9-_]+)[\'"]\s*\]/Ssi',
318
+ '[$1$2$3]',
319
+ $css
320
+ );
321
+
322
+ // Process strings so their content doesn't get accidentally minified
323
+ $css = preg_replace_callback(
324
+ '/(?:"(?:[^\\\\"]|\\\\.|\\\\)*")|'."(?:'(?:[^\\\\']|\\\\.|\\\\)*')/S",
325
+ array($this, 'processStringsCallback'),
326
+ $css
327
+ );
328
+
329
+ // Normalize all whitespace strings to single spaces. Easier to work with that way.
330
+ $css = preg_replace('/\s+/S', ' ', $css);
331
+
332
+ // Process import At-rules with unquoted URLs so URI reserved characters such as a semicolon may be used safely.
333
+ $css = preg_replace_callback(
334
+ '/@import url\(([^\'"]+?)\)( |;)/Si',
335
+ array($this, 'processImportUnquotedUrlAtRulesCallback'),
336
+ $css
337
+ );
338
+
339
+ // Process comments
340
+ $css = $this->processComments($css);
341
+
342
+ // Process rule bodies
343
+ $css = $this->processRuleBodies($css);
344
+
345
+ // Process at-rules and selectors
346
+ $css = $this->processAtRulesAndSelectors($css);
347
+
348
+ // Restore preserved rule bodies before splitting
349
+ $css = strtr($css, $this->ruleBodies);
350
+
351
+ // Split long lines in output if required
352
+ $css = $this->processLongLineSplitting($css);
353
+
354
+ // Restore preserved comments and strings
355
+ $css = strtr($css, $this->preservedTokens);
356
+
357
+ return trim($css);
358
+ }
359
+
360
+ /**
361
+ * Searches & replaces all data urls with tokens before we start compressing,
362
+ * to avoid performance issues running some of the subsequent regexes against large string chunks.
363
+ * @param string $css
364
+ * @return string
365
+ */
366
+ private function processDataUrls($css)
367
+ {
368
+ $ret = '';
369
+ $searchOffset = $substrOffset = 0;
370
+
371
+ // Since we need to account for non-base64 data urls, we need to handle
372
+ // ' and ) being part of the data string.
373
+ while (preg_match('/url\(\s*(["\']?)data:/Si', $css, $m, PREG_OFFSET_CAPTURE, $searchOffset)) {
374
+ $matchStartIndex = $m[0][1];
375
+ $dataStartIndex = $matchStartIndex + 4; // url( length
376
+ $searchOffset = $matchStartIndex + strlen($m[0][0]);
377
+ $terminator = $m[1][0]; // ', " or empty (not quoted)
378
+ $terminatorRegex = '/(?<!\\\\)'. (strlen($terminator) === 0 ? '' : $terminator.'\s*') .'(\))/S';
379
+
380
+ $ret .= substr($css, $substrOffset, $matchStartIndex - $substrOffset);
381
+
382
+ // Terminator found
383
+ if (preg_match($terminatorRegex, $css, $matches, PREG_OFFSET_CAPTURE, $searchOffset)) {
384
+ $matchEndIndex = $matches[1][1];
385
+ $searchOffset = $matchEndIndex + 1;
386
+ $token = substr($css, $dataStartIndex, $matchEndIndex - $dataStartIndex);
387
+
388
+ // Remove all spaces only for base64 encoded URLs.
389
+ if (stripos($token, 'base64,') !== false) {
390
+ $token = preg_replace('/\s+/S', '', $token);
391
+ }
392
+
393
+ $ret .= 'url('. $this->registerPreservedToken(trim($token)) .')';
394
+ // No end terminator found, re-add the whole match. Should we throw/warn here?
395
+ } else {
396
+ $ret .= substr($css, $matchStartIndex, $searchOffset - $matchStartIndex);
397
+ }
398
+
399
+ $substrOffset = $searchOffset;
400
+ }
401
+
402
+ $ret .= substr($css, $substrOffset);
403
+
404
+ return $ret;
405
+ }
406
+
407
+ /**
408
+ * Registers all comments found as candidates to be preserved.
409
+ * @param array $matches
410
+ * @return string
411
+ */
412
+ private function processCommentsCallback($matches)
413
+ {
414
+ return '/*'. $this->registerCommentToken($matches[1]) .'*/';
415
+ }
416
+
417
+ /**
418
+ * Preserves old IE Matrix string definition
419
+ * @param array $matches
420
+ * @return string
421
+ */
422
+ private function processOldIeSpecificMatrixDefinitionCallback($matches)
423
+ {
424
+ return 'filter:progid:DXImageTransform.Microsoft.Matrix('. $this->registerPreservedToken($matches[1]) .')';
425
+ }
426
+
427
+ /**
428
+ * Preserves strings found
429
+ * @param array $matches
430
+ * @return string
431
+ */
432
+ private function processStringsCallback($matches)
433
+ {
434
+ $match = $matches[0];
435
+ $quote = substr($match, 0, 1);
436
+ $match = substr($match, 1, -1);
437
+
438
+ // maybe the string contains a comment-like substring?
439
+ // one, maybe more? put'em back then
440
+ if (strpos($match, self::COMMENT_TOKEN_START) !== false) {
441
+ $match = strtr($match, $this->comments);
442
+ }
443
+
444
+ // minify alpha opacity in filter strings
445
+ $match = str_ireplace('progid:DXImageTransform.Microsoft.Alpha(Opacity=', 'alpha(opacity=', $match);
446
+
447
+ return $quote . $this->registerPreservedToken($match) . $quote;
448
+ }
449
+
450
+ /**
451
+ * Searches & replaces all import at-rule unquoted urls with tokens so URI reserved characters such as a semicolon
452
+ * may be used safely in a URL.
453
+ * @param array $matches
454
+ * @return string
455
+ */
456
+ private function processImportUnquotedUrlAtRulesCallback($matches)
457
+ {
458
+ return '@import url('. $this->registerPreservedToken($matches[1]) .')'. $matches[2];
459
+ }
460
+
461
+ /**
462
+ * Preserves or removes comments found.
463
+ * @param string $css
464
+ * @return string
465
+ */
466
+ private function processComments($css)
467
+ {
468
+ foreach ($this->comments as $commentId => $comment) {
469
+ $commentIdString = '/*'. $commentId .'*/';
470
+
471
+ // ! in the first position of the comment means preserve
472
+ // so push to the preserved tokens keeping the !
473
+ if ($this->keepImportantComments && strpos($comment, '!') === 0) {
474
+ $preservedTokenId = $this->registerPreservedToken($comment);
475
+ // Put new lines before and after /*! important comments
476
+ $css = str_replace($commentIdString, "\n/*$preservedTokenId*/\n", $css);
477
+ continue;
478
+ }
479
+
480
+ // # sourceMappingURL= in the first position of the comment means sourcemap
481
+ // so push to the preserved tokens if {$this->keepSourceMapComment} is truthy.
482
+ if ($this->keepSourceMapComment && strpos($comment, '# sourceMappingURL=') === 0) {
483
+ $preservedTokenId = $this->registerPreservedToken($comment);
484
+ // Add new line before the sourcemap comment
485
+ $css = str_replace($commentIdString, "\n/*$preservedTokenId*/", $css);
486
+ continue;
487
+ }
488
+
489
+ // Keep empty comments after child selectors (IE7 hack)
490
+ // e.g. html >/**/ body
491
+ if (strlen($comment) === 0 && strpos($css, '>/*'.$commentId) !== false) {
492
+ $css = str_replace($commentId, $this->registerPreservedToken(''), $css);
493
+ continue;
494
+ }
495
+
496
+ // in all other cases kill the comment
497
+ $css = str_replace($commentIdString, '', $css);
498
+ }
499
+
500
+ // Normalize whitespace again
501
+ $css = preg_replace('/ +/S', ' ', $css);
502
+
503
+ return $css;
504
+ }
505
+
506
+ /**
507
+ * Finds, minifies & preserves all rule bodies.
508
+ * @param string $css the whole stylesheet.
509
+ * @return string
510
+ */
511
+ private function processRuleBodies($css)
512
+ {
513
+ $ret = '';
514
+ $searchOffset = $substrOffset = 0;
515
+
516
+ while (($blockStartPos = strpos($css, '{', $searchOffset)) !== false) {
517
+ $blockEndPos = strpos($css, '}', $blockStartPos);
518
+ $nextBlockStartPos = strpos($css, '{', $blockStartPos + 1);
519
+ $ret .= substr($css, $substrOffset, $blockStartPos - $substrOffset);
520
+
521
+ if ($nextBlockStartPos !== false && $nextBlockStartPos < $blockEndPos) {
522
+ $ret .= substr($css, $blockStartPos, $nextBlockStartPos - $blockStartPos);
523
+ $searchOffset = $nextBlockStartPos;
524
+ } else {
525
+ $ruleBody = substr($css, $blockStartPos + 1, $blockEndPos - $blockStartPos - 1);
526
+ $ruleBodyToken = $this->registerRuleBodyToken($this->processRuleBody($ruleBody));
527
+ $ret .= '{'. $ruleBodyToken .'}';
528
+ $searchOffset = $blockEndPos + 1;
529
+ }
530
+
531
+ $substrOffset = $searchOffset;
532
+ }
533
+
534
+ $ret .= substr($css, $substrOffset);
535
+
536
+ return $ret;
537
+ }
538
+
539
+ /**
540
+ * Compresses non-group rule bodies.
541
+ * @param string $body The rule body without curly braces
542
+ * @return string
543
+ */
544
+ private function processRuleBody($body)
545
+ {
546
+ $body = trim($body);
547
+
548
+ // Remove spaces before the things that should not have spaces before them.
549
+ $body = preg_replace('/ ([:=,)*\/;\n])/S', '$1', $body);
550
+
551
+ // Remove the spaces after the things that should not have spaces after them.
552
+ $body = preg_replace('/([:=,(*\/!;\n]) /S', '$1', $body);
553
+
554
+ // Replace multiple semi-colons in a row by a single one
555
+ $body = preg_replace('/;;+/S', ';', $body);
556
+
557
+ // Remove semicolon before closing brace except when:
558
+ // - The last property is prefixed with a `*` (lte IE7 hack) to avoid issues on Symbian S60 3.x browsers.
559
+ if (!preg_match('/\*[a-z0-9-]+:[^;]+;$/Si', $body)) {
560
+ $body = rtrim($body, ';');
561
+ }
562
+
563
+ // Remove important comments inside a rule body (because they make no sense here).
564
+ if (strpos($body, '/*') !== false) {
565
+ $body = preg_replace('/\n?\/\*[A-Z0-9_]+\*\/\n?/S', '', $body);
566
+ }
567
+
568
+ // Empty rule body? Exit :)
569
+ if (empty($body)) {
570
+ return '';
571
+ }
572
+
573
+ // Shorten font-weight values
574
+ $body = preg_replace(
575
+ array('/(font-weight:)bold\b/Si', '/(font-weight:)normal\b/Si'),
576
+ array('${1}700', '${1}400'),
577
+ $body
578
+ );
579
+
580
+ // Shorten background property
581
+ $body = preg_replace('/(background:)(?:none|transparent)( !|;|$)/Si', '${1}0 0$2', $body);
582
+
583
+ // Shorten opacity IE filter
584
+ $body = str_ireplace('progid:DXImageTransform.Microsoft.Alpha(Opacity=', 'alpha(opacity=', $body);
585
+
586
+ // Shorten colors from rgb(51,102,153) to #336699, rgb(100%,0%,0%) to #ff0000 (sRGB color space)
587
+ // Shorten colors from hsl(0, 100%, 50%) to #ff0000 (sRGB color space)
588
+ // This makes it more likely that it'll get further compressed in the next step.
589
+ $body = preg_replace_callback(
590
+ '/(rgb|hsl)\(([0-9,.% -]+)\)(.|$)/Si',
591
+ array($this, 'shortenHslAndRgbToHexCallback'),
592
+ $body
593
+ );
594
+
595
+ // Shorten colors from #AABBCC to #ABC or shorter color name:
596
+ // - Look for hex colors which don't have a "=" in front of them (to avoid MSIE filters)
597
+ $body = preg_replace_callback(
598
+ '/(?<!=)#([0-9a-f]{3,6})( |,|\)|;|$)/Si',
599
+ array($this, 'shortenHexColorsCallback'),
600
+ $body
601
+ );
602
+
603
+ // Shorten long named colors with a shorter HEX counterpart: white -> #fff.
604
+ // Run at least 2 times to cover most cases
605
+ $body = preg_replace_callback(
606
+ array($this->namedToHexColorsRegex, $this->namedToHexColorsRegex),
607
+ array($this, 'shortenNamedColorsCallback'),
608
+ $body
609
+ );
610
+
611
+ // Replace positive sign from numbers before the leading space is removed.
612
+ // +1.2em to 1.2em, +.8px to .8px, +2% to 2%
613
+ $body = preg_replace('/([ :,(])\+(\.?\d+)/S', '$1$2', $body);
614
+
615
+ // shorten ms to s
616
+ $body = preg_replace_callback('/([ :,(])(-?)(\d{3,})ms/Si', function ($matches) {
617
+ return $matches[1] . $matches[2] . ((int) $matches[3] / 1000) .'s';
618
+ }, $body);
619
+
620
+ // Remove leading zeros from integer and float numbers.
621
+ // 000.6 to .6, -0.8 to -.8, 0050 to 50, -01.05 to -1.05
622
+ $body = preg_replace('/([ :,(])(-?)0+([1-9]?\.?\d+)/S', '$1$2$3', $body);
623
+
624
+ // Remove trailing zeros from float numbers.
625
+ // -6.0100em to -6.01em, .0100 to .01, 1.200px to 1.2px
626
+ $body = preg_replace('/([ :,(])(-?\d?\.\d+?)0+([^\d])/S', '$1$2$3', $body);
627
+
628
+ // Remove trailing .0 -> -9.0 to -9
629
+ $body = preg_replace('/([ :,(])(-?\d+)\.0([^\d])/S', '$1$2$3', $body);
630
+
631
+ // Replace 0 length numbers with 0
632
+ $body = preg_replace('/([ :,(])-?\.?0+([^\d])/S', '${1}0$2', $body);
633
+
634
+ // Shorten zero values for safe properties only
635
+ $body = preg_replace(
636
+ array(
637
+ $this->shortenOneZeroesRegex,
638
+ $this->shortenTwoZeroesRegex,
639
+ $this->shortenThreeZeroesRegex,
640
+ $this->shortenFourZeroesRegex
641
+ ),
642
+ array(
643
+ '$1$2:0',
644
+ '$1$2:$3 0',
645
+ '$1$2:$3 $4 0',
646
+ '$1$2:$3 $4 $5 0'
647
+ ),
648
+ $body
649
+ );
650
+
651
+ // Replace 0 0 0; or 0 0 0 0; with 0 0 for background-position property.
652
+ $body = preg_replace('/(background-position):0(?: 0){2,3}( !|;|$)/Si', '$1:0 0$2', $body);
653
+
654
+ // Shorten suitable shorthand properties with repeated values
655
+ $body = preg_replace(
656
+ array(
657
+ '/(margin|padding|border-(?:width|radius)):('.$this->numRegex.')(?: \2)+( !|;|$)/Si',
658
+ '/(border-(?:style|color)):([#a-z0-9]+)(?: \2)+( !|;|$)/Si'
659
+ ),
660
+ '$1:$2$3',
661
+ $body
662
+ );
663
+ $body = preg_replace(
664
+ array(
665
+ '/(margin|padding|border-(?:width|radius)):'.
666
+ '('.$this->numRegex.') ('.$this->numRegex.') \2 \3( !|;|$)/Si',
667
+ '/(border-(?:style|color)):([#a-z0-9]+) ([#a-z0-9]+) \2 \3( !|;|$)/Si'
668
+ ),
669
+ '$1:$2 $3$4',
670
+ $body
671
+ );
672
+ $body = preg_replace(
673
+ array(
674
+ '/(margin|padding|border-(?:width|radius)):'.
675
+ '('.$this->numRegex.') ('.$this->numRegex.') ('.$this->numRegex.') \3( !|;|$)/Si',
676
+ '/(border-(?:style|color)):([#a-z0-9]+) ([#a-z0-9]+) ([#a-z0-9]+) \3( !|;|$)/Si'
677
+ ),
678
+ '$1:$2 $3 $4$5',
679
+ $body
680
+ );
681
+
682
+ // Lowercase some common functions that can be values
683
+ $body = preg_replace_callback(
684
+ '/(?:attr|blur|brightness|circle|contrast|cubic-bezier|drop-shadow|ellipse|from|grayscale|'.
685
+ 'hsla?|hue-rotate|inset|invert|local|minmax|opacity|perspective|polygon|rgba?|rect|repeat|saturate|sepia|'.
686
+ 'steps|to|url|var|-webkit-gradient|'.
687
+ '(?:-(?:atsc|khtml|moz|ms|o|wap|webkit)-)?(?:calc|(?:repeating-)?(?:linear|radial)-gradient))\(/Si',
688
+ array($this, 'strtolowerCallback'),
689
+ $body
690
+ );
691
+
692
+ // Lowercase all uppercase properties
693
+ $body = preg_replace_callback('/(?:^|;)[A-Z-]+:/S', array($this, 'strtolowerCallback'), $body);
694
+
695
+ return $body;
696
+ }
697
+
698
+ /**
699
+ * Compresses At-rules and selectors.
700
+ * @param string $css the whole stylesheet with rule bodies tokenized.
701
+ * @return string
702
+ */
703
+ private function processAtRulesAndSelectors($css)
704
+ {
705
+ $charset = '';
706
+ $imports = '';
707
+ $namespaces = '';
708
+
709
+ // Remove spaces before the things that should not have spaces before them.
710
+ $css = preg_replace('/ ([@{};>+)\]~=,\/\n])/S', '$1', $css);
711
+
712
+ // Remove the spaces after the things that should not have spaces after them.
713
+ $css = preg_replace('/([{}:;>+(\[~=,\/\n]) /S', '$1', $css);
714
+
715
+ // Shorten shortable double colon (CSS3) pseudo-elements to single colon (CSS2)
716
+ $css = preg_replace('/::(before|after|first-(?:line|letter))(\{|,)/Si', ':$1$2', $css);
717
+
718
+ // Retain space for special IE6 cases
719
+ $css = preg_replace_callback('/:first-(line|letter)(\{|,)/Si', function ($matches) {
720
+ return ':first-'. strtolower($matches[1]) .' '. $matches[2];
721
+ }, $css);
722
+
723
+ // Find a fraction that may used in some @media queries such as: (min-aspect-ratio: 1/1)
724
+ // Add token to add the "/" back in later
725
+ $css = preg_replace('/\(([a-z-]+):([0-9]+)\/([0-9]+)\)/Si', '($1:$2'. self::QUERY_FRACTION .'$3)', $css);
726
+
727
+ // Remove empty rule blocks up to 2 levels deep.
728
+ $css = preg_replace(array_fill(0, 2, '/(\{)[^{};\/\n]+\{\}/S'), '$1', $css);
729
+ $css = preg_replace('/[^{};\/\n]+\{\}/S', '', $css);
730
+
731
+ // Two important comments next to each other? Remove extra newline.
732
+ if ($this->keepImportantComments) {
733
+ $css = str_replace("\n\n", "\n", $css);
734
+ }
735
+
736
+ // Restore fraction
737
+ $css = str_replace(self::QUERY_FRACTION, '/', $css);
738
+
739
+ // Lowercase some popular @directives
740
+ $css = preg_replace_callback(
741
+ '/(?<!\\\\)@(?:charset|document|font-face|import|(?:-(?:atsc|khtml|moz|ms|o|wap|webkit)-)?keyframes|media|'.
742
+ 'namespace|page|supports|viewport)/Si',
743
+ array($this, 'strtolowerCallback'),
744
+ $css
745
+ );
746
+
747
+ // Lowercase some popular media types
748
+ $css = preg_replace_callback(
749
+ '/[ ,](?:all|aural|braille|handheld|print|projection|screen|tty|tv|embossed|speech)[ ,;{]/Si',
750
+ array($this, 'strtolowerCallback'),
751
+ $css
752
+ );
753
+
754
+ // Lowercase some common pseudo-classes & pseudo-elements
755
+ $css = preg_replace_callback(
756
+ '/(?<!\\\\):(?:active|after|before|checked|default|disabled|empty|enabled|first-(?:child|of-type)|'.
757
+ 'focus(?:-within)?|hover|indeterminate|in-range|invalid|lang\(|last-(?:child|of-type)|left|link|not\(|'.
758
+ 'nth-(?:child|of-type)\(|nth-last-(?:child|of-type)\(|only-(?:child|of-type)|optional|out-of-range|'.
759
+ 'read-(?:only|write)|required|right|root|:selection|target|valid|visited)/Si',
760
+ array($this, 'strtolowerCallback'),
761
+ $css
762
+ );
763
+
764
+ // @charset handling
765
+ if (preg_match($this->charsetRegex, $css, $matches)) {
766
+ // Keep the first @charset at-rule found
767
+ $charset = $matches[0];
768
+ // Delete all @charset at-rules
769
+ $css = preg_replace($this->charsetRegex, '', $css);
770
+ }
771
+
772
+ // @import handling
773
+ $css = preg_replace_callback($this->importRegex, function ($matches) use (&$imports) {
774
+ // Keep all @import at-rules found for later
775
+ $imports .= $matches[0];
776
+ // Delete all @import at-rules
777
+ return '';
778
+ }, $css);
779
+
780
+ // @namespace handling
781
+ $css = preg_replace_callback($this->namespaceRegex, function ($matches) use (&$namespaces) {
782
+ // Keep all @namespace at-rules found for later
783
+ $namespaces .= $matches[0];
784
+ // Delete all @namespace at-rules
785
+ return '';
786
+ }, $css);
787
+
788
+ // Order critical at-rules:
789
+ // 1. @charset first
790
+ // 2. @imports below @charset
791
+ // 3. @namespaces below @imports
792
+ $css = $charset . $imports . $namespaces . $css;
793
+
794
+ return $css;
795
+ }
796
+
797
+ /**
798
+ * Splits long lines after a specific column.
799
+ *
800
+ * Some source control tools don't like it when files containing lines longer
801
+ * than, say 8000 characters, are checked in. The linebreak option is used in
802
+ * that case to split long lines after a specific column.
803
+ *
804
+ * @param string $css the whole stylesheet.
805
+ * @return string
806
+ */
807
+ private function processLongLineSplitting($css)
808
+ {
809
+ if ($this->linebreakPosition > 0) {
810
+ $l = strlen($css);
811
+ $offset = $this->linebreakPosition;
812
+ while (preg_match('/(?<!\\\\)\}(?!\n)/S', $css, $matches, PREG_OFFSET_CAPTURE, $offset)) {
813
+ $matchIndex = $matches[0][1];
814
+ $css = substr_replace($css, "\n", $matchIndex + 1, 0);
815
+ $offset = $matchIndex + 2 + $this->linebreakPosition;
816
+ $l += 1;
817
+ if ($offset > $l) {
818
+ break;
819
+ }
820
+ }
821
+ }
822
+
823
+ return $css;
824
+ }
825
+
826
+ /**
827
+ * Converts hsl() & rgb() colors to HEX format.
828
+ * @param $matches
829
+ * @return string
830
+ */
831
+ private function shortenHslAndRgbToHexCallback($matches)
832
+ {
833
+ $type = $matches[1];
834
+ $values = explode(',', $matches[2]);
835
+ $terminator = $matches[3];
836
+
837
+ if ($type === 'hsl') {
838
+ $values = Utils::hslToRgb($values);
839
+ }
840
+
841
+ $hexColors = Utils::rgbToHex($values);
842
+
843
+ // Restore space after rgb() or hsl() function in some cases such as:
844
+ // background-image: linear-gradient(to bottom, rgb(210,180,140) 10%, rgb(255,0,0) 90%);
845
+ if (!empty($terminator) && !preg_match('/[ ,);]/S', $terminator)) {
846
+ $terminator = ' '. $terminator;
847
+ }
848
+
849
+ return '#'. implode('', $hexColors) . $terminator;
850
+ }
851
+
852
+ /**
853
+ * Compresses HEX color values of the form #AABBCC to #ABC or short color name.
854
+ * @param $matches
855
+ * @return string
856
+ */
857
+ private function shortenHexColorsCallback($matches)
858
+ {
859
+ $hex = $matches[1];
860
+
861
+ // Shorten suitable 6 chars HEX colors
862
+ if (strlen($hex) === 6 && preg_match('/^([0-9a-f])\1([0-9a-f])\2([0-9a-f])\3$/Si', $hex, $m)) {
863
+ $hex = $m[1] . $m[2] . $m[3];
864
+ }
865
+
866
+ // Lowercase
867
+ $hex = '#'. strtolower($hex);
868
+
869
+ // Replace Hex colors with shorter color names
870
+ $color = array_key_exists($hex, $this->hexToNamedColorsMap) ? $this->hexToNamedColorsMap[$hex] : $hex;
871
+
872
+ return $color . $matches[2];
873
+ }
874
+
875
+ /**
876
+ * Shortens all named colors with a shorter HEX counterpart for a set of safe properties
877
+ * e.g. white -> #fff
878
+ * @param array $matches
879
+ * @return string
880
+ */
881
+ private function shortenNamedColorsCallback($matches)
882
+ {
883
+ return $matches[1] . $this->namedToHexColorsMap[strtolower($matches[2])] . $matches[3];
884
+ }
885
+
886
+ /**
887
+ * Makes a string lowercase
888
+ * @param array $matches
889
+ * @return string
890
+ */
891
+ private function strtolowerCallback($matches)
892
+ {
893
+ return strtolower($matches[0]);
894
+ }
895
+ }
classes/external/php/yui-php-cssmin-bundled/Utils.php ADDED
@@ -0,0 +1,149 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Autoptimize\tubalmartin\CssMin;
4
+
5
+ class Utils
6
+ {
7
+ /**
8
+ * Clamps a number between a minimum and a maximum value.
9
+ * @param int|float $n the number to clamp
10
+ * @param int|float $min the lower end number allowed
11
+ * @param int|float $max the higher end number allowed
12
+ * @return int|float
13
+ */
14
+ public static function clampNumber($n, $min, $max)
15
+ {
16
+ return min(max($n, $min), $max);
17
+ }
18
+
19
+ /**
20
+ * Clamps a RGB color number outside the sRGB color space
21
+ * @param int|float $n the number to clamp
22
+ * @return int|float
23
+ */
24
+ public static function clampNumberSrgb($n)
25
+ {
26
+ return self::clampNumber($n, 0, 255);
27
+ }
28
+
29
+ /**
30
+ * Converts a HSL color into a RGB color
31
+ * @param array $hslValues
32
+ * @return array
33
+ */
34
+ public static function hslToRgb($hslValues)
35
+ {
36
+ $h = floatval($hslValues[0]);
37
+ $s = floatval(str_replace('%', '', $hslValues[1]));
38
+ $l = floatval(str_replace('%', '', $hslValues[2]));
39
+
40
+ // Wrap and clamp, then fraction!
41
+ $h = ((($h % 360) + 360) % 360) / 360;
42
+ $s = self::clampNumber($s, 0, 100) / 100;
43
+ $l = self::clampNumber($l, 0, 100) / 100;
44
+
45
+ if ($s == 0) {
46
+ $r = $g = $b = self::roundNumber(255 * $l);
47
+ } else {
48
+ $v2 = $l < 0.5 ? $l * (1 + $s) : ($l + $s) - ($s * $l);
49
+ $v1 = (2 * $l) - $v2;
50
+ $r = self::roundNumber(255 * self::hueToRgb($v1, $v2, $h + (1/3)));
51
+ $g = self::roundNumber(255 * self::hueToRgb($v1, $v2, $h));
52
+ $b = self::roundNumber(255 * self::hueToRgb($v1, $v2, $h - (1/3)));
53
+ }
54
+
55
+ return array($r, $g, $b);
56
+ }
57
+
58
+ /**
59
+ * Tests and selects the correct formula for each RGB color channel
60
+ * @param $v1
61
+ * @param $v2
62
+ * @param $vh
63
+ * @return mixed
64
+ */
65
+ public static function hueToRgb($v1, $v2, $vh)
66
+ {
67
+ $vh = $vh < 0 ? $vh + 1 : ($vh > 1 ? $vh - 1 : $vh);
68
+
69
+ if ($vh * 6 < 1) {
70
+ return $v1 + ($v2 - $v1) * 6 * $vh;
71
+ }
72
+
73
+ if ($vh * 2 < 1) {
74
+ return $v2;
75
+ }
76
+
77
+ if ($vh * 3 < 2) {
78
+ return $v1 + ($v2 - $v1) * ((2 / 3) - $vh) * 6;
79
+ }
80
+
81
+ return $v1;
82
+ }
83
+
84
+ /**
85
+ * Convert strings like "64M" or "30" to int values
86
+ * @param mixed $size
87
+ * @return int
88
+ */
89
+ public static function normalizeInt($size)
90
+ {
91
+ if (is_string($size)) {
92
+ $letter = substr($size, -1);
93
+ $size = intval($size);
94
+ switch ($letter) {
95
+ case 'M':
96
+ case 'm':
97
+ return (int) $size * 1048576;
98
+ case 'K':
99
+ case 'k':
100
+ return (int) $size * 1024;
101
+ case 'G':
102
+ case 'g':
103
+ return (int) $size * 1073741824;
104
+ }
105
+ }
106
+ return (int) $size;
107
+ }
108
+
109
+ /**
110
+ * Converts a string containing and RGB percentage value into a RGB integer value i.e. '90%' -> 229.5
111
+ * @param $rgbPercentage
112
+ * @return int
113
+ */
114
+ public static function rgbPercentageToRgbInteger($rgbPercentage)
115
+ {
116
+ if (strpos($rgbPercentage, '%') !== false) {
117
+ $rgbPercentage = self::roundNumber(floatval(str_replace('%', '', $rgbPercentage)) * 2.55);
118
+ }
119
+
120
+ return intval($rgbPercentage, 10);
121
+ }
122
+
123
+ /**
124
+ * Converts a RGB color into a HEX color
125
+ * @param array $rgbColors
126
+ * @return array
127
+ */
128
+ public static function rgbToHex($rgbColors)
129
+ {
130
+ $hexColors = array();
131
+
132
+ // Values outside the sRGB color space should be clipped (0-255)
133
+ for ($i = 0, $l = count($rgbColors); $i < $l; $i++) {
134
+ $hexColors[$i] = sprintf("%02x", self::clampNumberSrgb(self::rgbPercentageToRgbInteger($rgbColors[$i])));
135
+ }
136
+
137
+ return $hexColors;
138
+ }
139
+
140
+ /**
141
+ * Rounds a number to its closest integer
142
+ * @param $n
143
+ * @return int
144
+ */
145
+ public static function roundNumber($n)
146
+ {
147
+ return intval(round(floatval($n)), 10);
148
+ }
149
+ }
{classlesses → classes/external/php/yui-php-cssmin-bundled}/index.html RENAMED
@@ -1 +1 @@
1
- <html><head><meta name="robots" content="noindex, nofollow"></head><body>Generated by <a href="http://wordpress.org/extend/plugins/autoptimize/" rel="nofollow">Autoptimize</a></body></html>
1
+ <html><head><meta name="robots" content="noindex, nofollow"></head><body>Generated by <a href="http://wordpress.org/extend/plugins/autoptimize/" rel="nofollow">Autoptimize</a></body></html>
classes/static/toolbar.css CHANGED
@@ -80,9 +80,9 @@
80
  @-webkit-keyframes blink { 50% { visibility: hidden; }}
81
  @keyframes blink { 50% { visibility: hidden; }}
82
 
83
-
84
  /* Some cosmetic Toolbar things */
85
- #wp-admin-bar-autoptimize table, #wp-admin-bar-autoptimize th, #wp-admin-bar-autoptimize td
86
  {
87
  border: 0px !important;
88
  }
@@ -140,7 +140,7 @@
140
  line-height: 16px !important;
141
  }
142
 
143
- #wp-admin-bar-autoptimize-cache-info .ab-item table
144
  {
145
  display: inline-block !important;
146
  margin-left: 10px !important;
@@ -153,7 +153,7 @@
153
  margin-top: 5px !important;
154
  }
155
  .autoptimize-radial-bar,
156
- .autoptimize-radial-bar .mask,
157
  .autoptimize-radial-bar .fill,
158
  .autoptimize-radial-bar .shadow
159
  {
@@ -198,7 +198,7 @@
198
 
199
  top : 7px !important;
200
  left : 0px !important;
201
-
202
  overflow : hidden;
203
  }
204
  .autoptimize-radial-bar .numbers
@@ -208,7 +208,7 @@
208
  font-size : 9px !important;
209
 
210
  margin-top : -10px !important;
211
-
212
  display : inline-block;
213
  vertical-align : top;
214
  text-align : center;
@@ -223,7 +223,7 @@
223
  box-shadow : 3px 3px 5px rgba(0,0,0,0.3) inset !important;
224
  }
225
 
226
- .autoptimize-radial-bar .mask,
227
  .autoptimize-radial-bar .fill,
228
  .autoptimize-radial-bar .shadow,
229
  .autoptimize-radial-bar .inset,
@@ -243,4 +243,4 @@
243
 
244
  /* fixes for toolbar on frontend for other themes messing things up */
245
  #wp-admin-bar-autoptimize tr{border:0 !important}
246
- #wp-admin-bar-autoptimize td{background-color:#32373c !important}
80
  @-webkit-keyframes blink { 50% { visibility: hidden; }}
81
  @keyframes blink { 50% { visibility: hidden; }}
82
 
83
+
84
  /* Some cosmetic Toolbar things */
85
+ #wp-admin-bar-autoptimize table, #wp-admin-bar-autoptimize th, #wp-admin-bar-autoptimize td
86
  {
87
  border: 0px !important;
88
  }
140
  line-height: 16px !important;
141
  }
142
 
143
+ #wp-admin-bar-autoptimize-cache-info .ab-item table
144
  {
145
  display: inline-block !important;
146
  margin-left: 10px !important;
153
  margin-top: 5px !important;
154
  }
155
  .autoptimize-radial-bar,
156
+ .autoptimize-radial-bar .mask,
157
  .autoptimize-radial-bar .fill,
158
  .autoptimize-radial-bar .shadow
159
  {
198
 
199
  top : 7px !important;
200
  left : 0px !important;
201
+
202
  overflow : hidden;
203
  }
204
  .autoptimize-radial-bar .numbers
208
  font-size : 9px !important;
209
 
210
  margin-top : -10px !important;
211
+
212
  display : inline-block;
213
  vertical-align : top;
214
  text-align : center;
223
  box-shadow : 3px 3px 5px rgba(0,0,0,0.3) inset !important;
224
  }
225
 
226
+ .autoptimize-radial-bar .mask,
227
  .autoptimize-radial-bar .fill,
228
  .autoptimize-radial-bar .shadow,
229
  .autoptimize-radial-bar .inset,
243
 
244
  /* fixes for toolbar on frontend for other themes messing things up */
245
  #wp-admin-bar-autoptimize tr{border:0 !important}
246
+ #wp-admin-bar-autoptimize td{background-color:#32373c !important}
classlesses/autoptimizeCacheChecker.php DELETED
@@ -1,65 +0,0 @@
1
- <?php
2
- if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
3
-
4
- /*
5
- * cachechecker code
6
- * new in AO 2.0
7
- *
8
- * daily cronned job (filter to change freq. + filter to disable)
9
- * checks if cachesize is > 0.5GB (filter to change maxsize)
10
- * if so an option is set
11
- * if that option is set, notice on admin is shown
12
- *
13
- */
14
-
15
- if (is_admin()) {
16
- add_action('plugins_loaded','ao_cachechecker_setup');
17
- }
18
-
19
- function ao_cachechecker_setup() {
20
- $doCacheCheck = (bool) apply_filters( 'autoptimize_filter_cachecheck_do', true);
21
- $cacheCheckSchedule = wp_get_schedule( 'ao_cachechecker' );
22
- $AOCCfreq = apply_filters('autoptimize_filter_cachecheck_frequency','daily');
23
- if (!in_array($AOCCfreq,array('hourly','daily','monthly'))) {
24
- $AOCCfreq='daily';
25
- }
26
- if ( $doCacheCheck && ( !$cacheCheckSchedule || $cacheCheckSchedule !== $AOCCfreq ) ) {
27
- wp_schedule_event(time(), $AOCCfreq, 'ao_cachechecker');
28
- } else if ( $cacheCheckSchedule && !$doCacheCheck ) {
29
- wp_clear_scheduled_hook( 'ao_cachechecker' );
30
- }
31
- }
32
-
33
- add_action('ao_cachechecker', 'ao_cachechecker_cronjob');
34
- function ao_cachechecker_cronjob() {
35
- $maxSize = (int) apply_filters( "autoptimize_filter_cachecheck_maxsize", 536870912);
36
- $doCacheCheck = (bool) apply_filters( "autoptimize_filter_cachecheck_do", true);
37
- $statArr=autoptimizeCache::stats();
38
- $cacheSize=round($statArr[1]);
39
- if (($cacheSize>$maxSize) && ($doCacheCheck)) {
40
- update_option("autoptimize_cachesize_notice",true);
41
- if (apply_filters('autoptimize_filter_cachecheck_sendmail',true)) {
42
- $saniSiteUrl=esc_url(site_url());
43
- $ao_mailto=apply_filters('autoptimize_filter_cachecheck_mailto',get_option('admin_email',''));
44
- $ao_mailsubject=__('Autoptimize cache size warning','autoptimize')." (".$saniSiteUrl.")";
45
- $ao_mailbody=__('Autoptimize\'s cache size is getting big, consider purging the cache. Have a look at https://wordpress.org/plugins/autoptimize/faq/ to see how you can keep the cache size under control.', 'autoptimize')." (site: ".$saniSiteUrl.")";
46
-
47
- if (!empty($ao_mailto)) {
48
- $ao_mailresult=wp_mail($ao_mailto,$ao_mailsubject,$ao_mailbody);
49
- if (!$ao_mailresult) {
50
- error_log("Autoptimize could not send cache size warning mail.");
51
- }
52
- }
53
- }
54
- }
55
- }
56
-
57
- add_action('admin_notices', 'autoptimize_cachechecker_notice');
58
- function autoptimize_cachechecker_notice() {
59
- if ((bool) get_option("autoptimize_cachesize_notice",false)) {
60
- echo '<div class="notice notice-warning"><p>';
61
- _e('<strong>Autoptimize\'s cache size is getting big</strong>, consider purging the cache. Have a look at <a href="https://wordpress.org/plugins/autoptimize/faq/" target="_blank">the Autoptimize FAQ</a> to see how you can keep the cache size under control.', 'autoptimize' );
62
- echo '</p></div>';
63
- update_option("autoptimize_cachesize_notice",false);
64
- }
65
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
classlesses/autoptimizeExtra.php DELETED
@@ -1,386 +0,0 @@
1
- <?php
2
- if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
3
-
4
- // initialize
5
- if ( is_admin() ) {
6
- add_action( 'admin_menu', 'autoptimize_extra_admin' );
7
- add_filter( 'autoptimize_filter_settingsscreen_tabs','add_autoptimize_extra_tab' );
8
- } else {
9
- autoptimize_extra_init();
10
- }
11
-
12
- // get option
13
- function autoptimize_extra_get_options() {
14
- $_default_val = array("autoptimize_extra_checkbox_field_1"=>"0","autoptimize_extra_checkbox_field_0"=>"0","autoptimize_extra_radio_field_4"=>"1","autoptimize_extra_text_field_2"=>"","autoptimize_extra_text_field_3"=>"");
15
- $_option_val = get_option( 'autoptimize_extra_settings' );
16
- if (empty($_option_val)) {
17
- $_option_val = $_default_val;
18
- }
19
- return $_option_val;
20
- }
21
-
22
- // frontend init
23
- function autoptimize_extra_init() {
24
- $autoptimize_extra_options = autoptimize_extra_get_options();
25
-
26
- /* disable emojis */
27
- if ( !empty($autoptimize_extra_options['autoptimize_extra_checkbox_field_1']) ) {
28
- autoptimize_extra_disable_emojis();
29
- }
30
-
31
- /* remove version from query string */
32
- if ( !empty($autoptimize_extra_options['autoptimize_extra_checkbox_field_0']) ) {
33
- add_filter( 'script_loader_src', 'autoptimize_extra_remove_qs', 15, 1 );
34
- add_filter( 'style_loader_src', 'autoptimize_extra_remove_qs', 15, 1 );
35
- }
36
-
37
- /*
38
- * async JS
39
- *
40
- * is_plugin_active is not available in frontend by default
41
- * cfr. https://codex.wordpress.org/Function_Reference/is_plugin_active
42
- * so we need to source in wp-admin/includes/plugin.php
43
- */
44
- include_once( ABSPATH . 'wp-admin/includes/plugin.php' );
45
- $_asyncJSactive = false;
46
- if ( function_exists('is_plugin_active') && is_plugin_active('async-javascript/async-javascript.php') ) {
47
- $_asyncJSactive = true;
48
- }
49
- if ( !empty($autoptimize_extra_options['autoptimize_extra_text_field_3']) && $_asyncJSactive === false ) {
50
- add_filter('autoptimize_filter_js_exclude','autoptimize_extra_async_js',10,1);
51
- }
52
-
53
- /* optimize google fonts */
54
- if ( !empty( $autoptimize_extra_options['autoptimize_extra_radio_field_4'] ) && ( $autoptimize_extra_options['autoptimize_extra_radio_field_4'] != "1" ) ) {
55
- add_filter( 'wp_resource_hints', 'autoptimize_extra_gfonts_remove_dnsprefetch', 10, 2 );
56
- if ( $autoptimize_extra_options['autoptimize_extra_radio_field_4'] == "2" ) {
57
- add_filter('autoptimize_filter_css_removables','autoptimize_extra_remove_gfonts',10,1);
58
- } else {
59
- add_filter('autoptimize_html_after_minify','autoptimize_extra_gfonts',10,1);
60
- add_filter('autoptimize_extra_filter_tobepreconn','autoptimize_extra_preconnectgooglefonts',10,1);
61
- }
62
- }
63
-
64
- /* preconnect */
65
- if ( !empty($autoptimize_extra_options['autoptimize_extra_text_field_2']) || has_filter('autoptimize_extra_filter_tobepreconn') ) {
66
- add_filter( 'wp_resource_hints', 'autoptimize_extra_preconnect', 10, 2 );
67
- }
68
- }
69
-
70
- // disable emoji's functions
71
- function autoptimize_extra_disable_emojis() {
72
- // all actions related to emojis
73
- remove_action( 'admin_print_styles', 'print_emoji_styles' );
74
- remove_action( 'wp_head', 'print_emoji_detection_script', 7 );
75
- remove_action( 'admin_print_scripts', 'print_emoji_detection_script' );
76
- remove_action( 'wp_print_styles', 'print_emoji_styles' );
77
- remove_filter( 'wp_mail', 'wp_staticize_emoji_for_email' );
78
- remove_filter( 'the_content_feed', 'wp_staticize_emoji' );
79
- remove_filter( 'comment_text_rss', 'wp_staticize_emoji' );
80
-
81
- // filter to remove TinyMCE emojis
82
- add_filter( 'tiny_mce_plugins', 'autoptimize_extra_disable_emojis_tinymce' );
83
-
84
- // and remove dns-prefetch for emoji
85
- add_filter( 'wp_resource_hints', 'autoptimize_extra_emojis_remove_dns_prefetch', 10, 2 );
86
- }
87
-
88
- function autoptimize_extra_disable_emojis_tinymce( $plugins ) {
89
- if ( is_array( $plugins ) ) {
90
- return array_diff( $plugins, array( 'wpemoji' ) );
91
- } else {
92
- return array();
93
- }
94
- }
95
-
96
- function autoptimize_extra_emojis_remove_dns_prefetch( $urls, $relation_type ) {
97
- $_emoji_svg_url = apply_filters( 'emoji_svg_url', 'https://s.w.org/images/core/emoji/' );
98
-
99
- return autoptimize_extra_remove_dns_prefetch( $urls, $relation_type, $_emoji_svg_url );
100
- }
101
-
102
- // remove query string function
103
- function autoptimize_extra_remove_qs( $src ) {
104
- if ( strpos($src, '?ver=') ) {
105
- $src = remove_query_arg( 'ver', $src );
106
- }
107
- return $src;
108
- }
109
-
110
- // async function
111
- function autoptimize_extra_async_js($in) {
112
- $autoptimize_extra_options = autoptimize_extra_get_options();
113
-
114
- // get exclusions
115
- $AO_JSexclArrayIn = array();
116
- if (!empty($in)) {
117
- $AO_JSexclArrayIn = array_fill_keys(array_filter(array_map('trim',explode(",",$in))),"");
118
- }
119
-
120
- // get asyncs
121
- $_fromSetting = $autoptimize_extra_options['autoptimize_extra_text_field_3'];
122
- $AO_asynced_JS = array_fill_keys(array_filter(array_map('trim',explode(",",$_fromSetting))),"");
123
- $AO_async_flag = apply_filters('autoptimize_filter_extra_async',"async");
124
- foreach ($AO_asynced_JS as $JSkey => $JSvalue) {
125
- $AO_asynced_JS[$JSkey] = $AO_async_flag;
126
- }
127
-
128
- // merge exclusions & asyncs in one array and return to AO API
129
- $AO_excl_w_async = array_merge( $AO_JSexclArrayIn, $AO_asynced_JS );
130
- return $AO_excl_w_async;
131
- }
132
-
133
- // preconnect function
134
- function autoptimize_extra_preconnect($hints, $relation_type) {
135
- $autoptimize_extra_options = autoptimize_extra_get_options();
136
-
137
- // get setting and store in array
138
- $_to_be_preconnected = array_filter(array_map('trim',explode(",",$autoptimize_extra_options['autoptimize_extra_text_field_2'])));
139
- $_to_be_preconnected = apply_filters( 'autoptimize_extra_filter_tobepreconn', $_to_be_preconnected );
140
-
141
- // walk array, extract domain and add to new array with crossorigin attribute
142
- foreach ($_to_be_preconnected as $_preconn_single) {
143
- $_preconn_parsed = parse_url($_preconn_single);
144
-
145
- if ( is_array($_preconn_parsed) && empty($_preconn_parsed['scheme']) ) {
146
- $_preconn_domain = "//".$_preconn_parsed['host'];
147
- } else if ( is_array($_preconn_parsed) ) {
148
- $_preconn_domain = $_preconn_parsed['scheme']."://".$_preconn_parsed['host'];
149
- }
150
-
151
- if ( !empty($_preconn_domain) ) {
152
- $_preconn_hint = array('href' => $_preconn_domain);
153
- // fonts don't get preconnected unless crossorigin flag is set, non-fonts don't get preconnected if origin flag is set
154
- // so hardcode fonts.gstatic.com to come with crossorigin and have filter to add other domains if needed
155
- $_preconn_crossorigin = apply_filters( 'autoptimize_extra_filter_preconn_crossorigin', array('https://fonts.gstatic.com') );
156
- if ( in_array( $_preconn_domain, $_preconn_crossorigin ) ) {
157
- $_preconn_hint['crossorigin'] = 'anonymous';
158
- }
159
- $_new_hints[] = $_preconn_hint;
160
- }
161
- }
162
-
163
- // merge in wordpress' preconnect hints
164
- if ( 'preconnect' === $relation_type && !empty($_new_hints) ) {
165
- $hints = array_merge($hints, $_new_hints);
166
- }
167
-
168
- return $hints;
169
- }
170
-
171
- // google font functions
172
- function autoptimize_extra_gfonts_remove_dnsprefetch ( $urls, $relation_type ) {
173
- $_gfonts_url = "fonts.googleapis.com";
174
-
175
- return autoptimize_extra_remove_dns_prefetch( $urls, $relation_type, $_gfonts_url );
176
- }
177
-
178
- function autoptimize_extra_remove_gfonts($in) {
179
- // simply remove google fonts
180
- return $in.", fonts.googleapis.com";
181
- }
182
-
183
- function autoptimize_extra_gfonts($in) {
184
- $autoptimize_extra_options = autoptimize_extra_get_options();
185
-
186
- // extract fonts, partly based on wp rocket's extraction code
187
- $_without_comments = preg_replace( '/<!--(.*)-->/Uis', '', $in );
188
- preg_match_all( '#<link(?:\s+(?:(?!href\s*=\s*)[^>])+)?(?:\s+href\s*=\s*([\'"])((?:https?:)?\/\/fonts\.googleapis\.com\/css(?:(?!\1).)+)\1)(?:\s+[^>]*)?>#iU', $_without_comments, $matches );
189
-
190
- $i = 0;
191
- $fontsCollection = array();
192
- if ( ! $matches[2] ) {
193
- return $in;
194
- }
195
-
196
- // store them in $fonts array
197
- foreach ( $matches[2] as $font ) {
198
- if ( ! preg_match( '/rel=["\']dns-prefetch["\']/', $matches[0][ $i ] ) ) {
199
- // Get fonts name.
200
- $font = str_replace( array( '%7C', '%7c' ) , '|', $font );
201
- $font = explode( 'family=', $font );
202
- $font = ( isset( $font[1] ) ) ? explode( '&', $font[1] ) : array();
203
- // Add font to $fonts[$i] but make sure not to pollute with an empty family
204
- $_thisfont = array_values( array_filter( explode( '|', reset( $font ) ) ) );
205
- if ( !empty($_thisfont) ) {
206
- $fontsCollection[$i]["fonts"] = $_thisfont;
207
- // And add subset if any
208
- $subset = ( is_array( $font ) ) ? end( $font ) : '';
209
- if ( false !== strpos( $subset, 'subset=' ) ) {
210
- $subset = explode( 'subset=', $subset );
211
- $fontsCollection[$i]["subsets"] = explode( ',', $subset[1] );
212
- }
213
- }
214
- // And remove Google Fonts.
215
- $in = str_replace( $matches[0][ $i ], '', $in );
216
- }
217
- $i++;
218
- }
219
-
220
- $_fontsOut="";
221
- if ( $autoptimize_extra_options['autoptimize_extra_radio_field_4'] == "3" ) {
222
- // aggregate & link
223
- $_fontsString="";
224
- $_subsetString="";
225
- foreach ($fontsCollection as $font) {
226
- $_fontsString .= '|'.trim( implode( '|' , $font["fonts"] ), '|' );
227
- if ( !empty( $font["subsets"] ) ) {
228
- $_subsetString .= implode( ',', $font["subsets"] );
229
- }
230
- }
231
-
232
- if (!empty($_subsetString)) {
233
- $_fontsString = $_fontsString."#038;subset=".$_subsetString;
234
- }
235
-
236
- $_fontsString = str_replace( '|', '%7C', ltrim($_fontsString,'|') );
237
-
238
- if ( ! empty( $_fontsString ) ) {
239
- $_fontsOut = '<link rel="stylesheet" id="ao_optimized_gfonts" href="https://fonts.googleapis.com/css?family=' . $_fontsString . '" />';
240
- }
241
- } else if ( $autoptimize_extra_options['autoptimize_extra_radio_field_4'] == "4" ) {
242
- // aggregate & load async (webfont.js impl.)
243
- $_fontsArray = array();
244
- foreach ($fontsCollection as $_fonts) {
245
- if ( !empty( $_fonts["subsets"] ) ) {
246
- $_subset = implode(",",$_fonts["subsets"]);
247
- foreach ($_fonts["fonts"] as $key => $_one_font) {
248
- $_one_font = $_one_font.":".$_subset;
249
- $_fonts["fonts"][$key] = $_one_font;
250
- }
251
- }
252
- $_fontsArray = array_merge($_fontsArray, $_fonts["fonts"]);
253
- }
254
-
255
- $_fontsOut = '<script data-cfasync="false" type="text/javascript">WebFontConfig={google:{families:[\'';
256
- foreach ($_fontsArray as $_font) {
257
- $_fontsOut .= $_font."','";
258
- }
259
- $_fontsOut = trim(trim($_fontsOut,"'"),",");
260
- $_fontsOut .= '] },classes:false, events:false, timeout:1500};(function() {var wf = document.createElement(\'script\');wf.src=\'https://ajax.googleapis.com/ajax/libs/webfont/1/webfont.js\';wf.type=\'text/javascript\';wf.defer=\'true\';var s=document.getElementsByTagName(\'script\')[0];s.parentNode.insertBefore(wf, s);})();</script>';
261
- }
262
-
263
- // inject in HTML
264
- $out = substr_replace($in, $_fontsOut."<link", strpos($in, "<link"), strlen("<link"));
265
- return $out;
266
- }
267
-
268
- function autoptimize_extra_preconnectgooglefonts($in) {
269
- $autoptimize_extra_options = autoptimize_extra_get_options();
270
-
271
- // preconnect to fonts.gstatic.com speed up download of static font-files
272
- $in[] = "https://fonts.gstatic.com";
273
- if ( $autoptimize_extra_options['autoptimize_extra_radio_field_4'] == "4" ) {
274
- // and more preconnects for webfont.js
275
- $in[] = "https://ajax.googleapis.com";
276
- $in[] = "https://fonts.googleapis.com";
277
- }
278
- return $in;
279
- }
280
-
281
- function autoptimize_extra_remove_dns_prefetch( $urls, $relation_type, $_remove_url ) {
282
- if ( 'dns-prefetch' == $relation_type ) {
283
- $_count=0;
284
- foreach ($urls as $_url) {
285
- if ( strpos($_url, $_remove_url) !== false ) {
286
- unset($urls[$_count]);
287
- }
288
- $_count++;
289
- }
290
- }
291
-
292
- return $urls;
293
- }
294
-
295
- /* admin page functions */
296
- function autoptimize_extra_admin() {
297
- add_submenu_page( null, 'autoptimize_extra', 'autoptimize_extra', 'manage_options', 'autoptimize_extra', 'autoptimize_extra_options_page' );
298
- register_setting( 'autoptimize_extra_settings', 'autoptimize_extra_settings' );
299
- }
300
-
301
- function add_autoptimize_extra_tab($in) {
302
- $in=array_merge($in,array('autoptimize_extra' => __('Extra','autoptimize')));
303
- return $in;
304
- }
305
-
306
- function autoptimize_extra_options_page() {
307
- $autoptimize_extra_options = autoptimize_extra_get_options();
308
- $_googlef = $autoptimize_extra_options['autoptimize_extra_radio_field_4'];
309
- ?>
310
- <style>
311
- #ao_settings_form {background: white;border: 1px solid #ccc;padding: 1px 15px;margin: 15px 10px 10px 0;}
312
- #ao_settings_form .form-table th {font-weight: 100;}
313
- #autoptimize_extra_descr{font-size: 120%;}
314
- </style>
315
- <div class="wrap">
316
- <h1><?php _e('Autoptimize Settings','autoptimize'); ?></h1>
317
- <?php echo autoptimizeConfig::ao_admin_tabs(); ?>
318
- <form id='ao_settings_form' action='options.php' method='post'>
319
- <?php settings_fields('autoptimize_extra_settings'); ?>
320
- <h2><?php _e('Extra Auto-Optimizations','autoptimize'); ?></h2>
321
- <span id='autoptimize_extra_descr'><?php _e('The following settings can improve your site\'s performance even more.','autoptimize'); ?></span>
322
- <table class="form-table">
323
- <tr>
324
- <th scope="row"><?php _e('Remove emojis','autoptimize'); ?></th>
325
- <td>
326
- <label><input type='checkbox' name='autoptimize_extra_settings[autoptimize_extra_checkbox_field_1]' <?php if (!empty($autoptimize_extra_options['autoptimize_extra_checkbox_field_1']) && 1 == $autoptimize_extra_options['autoptimize_extra_checkbox_field_1']) echo 'checked="checked"'; ?> value='1'><?php _e('Removes WordPress\' core emojis\' inline CSS, inline JavaScript, and an otherwise un-autoptimized JavaScript file.','autoptimize'); ?></label>
327
- </td>
328
- </tr>
329
- <tr>
330
- <th scope="row"><?php _e('Remove query strings from static resources','autoptimize'); ?></th>
331
- <td>
332
- <label><input type='checkbox' name='autoptimize_extra_settings[autoptimize_extra_checkbox_field_0]' <?php if (!empty( $autoptimize_extra_options['autoptimize_extra_checkbox_field_0']) && 1 == $autoptimize_extra_options['autoptimize_extra_checkbox_field_0']) echo 'checked="checked"'; ?> value='1'><?php _e('Removing query strings (or more specificaly the <code>ver</code> parameter) will not improve load time, but might improve performance scores.','autoptimize'); ?></label>
333
- </td>
334
- </tr>
335
- <tr>
336
- <th scope="row"><?php _e('Google Fonts','autoptimize'); ?></th>
337
- <td>
338
- <input type="radio" name="autoptimize_extra_settings[autoptimize_extra_radio_field_4]" value="1" <?php if (!in_array($_googlef,array(2,3,4))) {echo "checked"; } ?> ><?php _e('Leave as is','autoptimize'); ?><br/>
339
- <input type="radio" name="autoptimize_extra_settings[autoptimize_extra_radio_field_4]" value="2" <?php checked(2, $_googlef, true); ?> ><?php _e('Remove Google Fonts','autoptimize'); ?><br/>
340
- <input type="radio" name="autoptimize_extra_settings[autoptimize_extra_radio_field_4]" value="3" <?php checked(3, $_googlef, true); ?> ><?php _e('Combine and link in head','autoptimize'); ?><br/>
341
- <input type="radio" name="autoptimize_extra_settings[autoptimize_extra_radio_field_4]" value="4" <?php checked(4, $_googlef, true); ?> ><?php _e('Combine and load fonts asynchronously with <a href="https://github.com/typekit/webfontloader#readme" target="_blank">webfont.js</a>','autoptimize'); ?><br/>
342
- </td>
343
- </tr>
344
- <tr>
345
- <th scope="row"><?php _e('Preconnect to 3rd party domains <em>(advanced users)</em>','autoptimize'); ?></th>
346
- <td>
347
- <label><input type='text' style='width:80%' name='autoptimize_extra_settings[autoptimize_extra_text_field_2]' value='<?php echo $autoptimize_extra_options['autoptimize_extra_text_field_2']; ?>'><br /><?php _e('Add 3rd party domains you want the browser to <a href="https://www.keycdn.com/support/preconnect/#primary" target="_blank">preconnect</a> to, separated by comma\'s. Make sure to include the correct protocol (HTTP or HTTPS).','autoptimize'); ?></label>
348
- </td>
349
- </tr>
350
- <tr>
351
- <th scope="row"><?php _e('Async Javascript-files <em>(advanced users)</em>','autoptimize'); ?></th>
352
- <td>
353
- <?php if ( function_exists('is_plugin_active') && is_plugin_active('async-javascript/async-javascript.php') ) {
354
- _e('You have "Async JavaScript" installed,','autoptimize');
355
- $asj_config_url="options-general.php?page=async-javascript";
356
- echo sprintf(' <a href="'.$asj_config_url.'">%s</a>', __('configuration of async javascript is best done there.','autoptimize'));
357
- } else { ?>
358
- <input type='text' style='width:80%' name='autoptimize_extra_settings[autoptimize_extra_text_field_3]' value='<?php echo $autoptimize_extra_options['autoptimize_extra_text_field_3']; ?>'>
359
- <br />
360
- <?php
361
- _e('Comma-separated list of local or 3rd party JS-files that should loaded with the <code>async</code> flag. JS-files from your own site will be automatically excluded if added here. ','autoptimize');
362
- echo sprintf( __('Configuration of async javascript is easier and more flexible using the %s plugin.','autoptimize'), '"<a href="https://wordpress.org/plugins/async-javascript" target="_blank">Async Javascript</a>"');
363
- $asj_install_url= network_admin_url()."plugin-install.php?s=async+javascript&tab=search&type=term";
364
- echo sprintf(' <a href="'.$asj_install_url.'">%s</a>', __('Click here to install and activate it.','autoptimize'));
365
- } ?>
366
- </td>
367
- </tr>
368
- <tr>
369
- <th scope="row"><?php _e('Optimize YouTube videos','autoptimize'); ?></th>
370
- <td>
371
- <?php if ( function_exists('is_plugin_active') && is_plugin_active('wp-youtube-lyte/wp-youtube-lyte.php') ) {
372
- _e('Great, you have WP YouTube Lyte installed.','autoptimize');
373
- $lyte_config_url="options-general.php?page=lyte_settings_page";
374
- echo sprintf(' <a href="'.$lyte_config_url.'">%s</a>', __('Click here to configure it.','autoptimize'));
375
- } else {
376
- echo sprintf( __('%s allows you to “lazy load” your videos, by inserting responsive “Lite YouTube Embeds". ','autoptimize'),'<a href="https://wordpress.org/plugins/wp-youtube-lyte" target="_blank">WP YouTube Lyte</a>');
377
- $lyte_install_url= network_admin_url()."plugin-install.php?s=lyte&tab=search&type=term";
378
- echo sprintf(' <a href="'.$lyte_install_url.'">%s</a>', __('Click here to install and activate it.','autoptimize'));
379
- } ?>
380
- </td>
381
- </tr>
382
- </table>
383
- <p class="submit"><input type="submit" name="submit" id="submit" class="button button-primary" value="<?php _e('Save Changes','autoptimize') ?>" /></p>
384
- </form>
385
- <?php
386
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
classlesses/autoptimizePageCacheFlush.php DELETED
@@ -1,56 +0,0 @@
1
- <?php
2
- // flush as many page cache plugin's caches as possible
3
- // hyper cache and gator cache hook into AO, so we don't need to :-)
4
-
5
- if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
6
-
7
- function autoptimize_flush_pagecache() {
8
- if(function_exists('wp_cache_clear_cache')) {
9
- if (is_multisite()) {
10
- $blog_id = get_current_blog_id();
11
- wp_cache_clear_cache($blog_id);
12
- } else {
13
- wp_cache_clear_cache();
14
- }
15
- } else if ( has_action('cachify_flush_cache') ) {
16
- do_action('cachify_flush_cache');
17
- } else if ( function_exists('w3tc_pgcache_flush') ) {
18
- w3tc_pgcache_flush();
19
- } else if ( function_exists('wp_fast_cache_bulk_delete_all') ) {
20
- wp_fast_cache_bulk_delete_all(); // still to retest
21
- } else if (class_exists("WpFastestCache")) {
22
- $wpfc = new WpFastestCache();
23
- $wpfc -> deleteCache();
24
- } else if ( class_exists("c_ws_plugin__qcache_purging_routines") ) {
25
- c_ws_plugin__qcache_purging_routines::purge_cache_dir(); // quick cache, still to retest
26
- } else if ( class_exists("zencache") ) {
27
- zencache::clear();
28
- } else if ( class_exists("comet_cache") ) {
29
- comet_cache::clear();
30
- } else if ( class_exists("WpeCommon") ) {
31
- if ( apply_filters('autoptimize_flush_wpengine_aggressive', false) ) {
32
- if ( method_exists( "WpeCommon", "purge_memcached" ) ) {
33
- WpeCommon::purge_memcached();
34
- }
35
- if ( method_exists( "WpeCommon", "clear_maxcdn_cache" ) ) {
36
- WpeCommon::clear_maxcdn_cache();
37
- }
38
- }
39
- if ( method_exists( "WpeCommon", "purge_varnish_cache" ) ) {
40
- WpeCommon::purge_varnish_cache();
41
- }
42
- } else if ( function_exists('sg_cachepress_purge_cache') ) {
43
- sg_cachepress_purge_cache();
44
- } else if(file_exists(WP_CONTENT_DIR.'/wp-cache-config.php') && function_exists('prune_super_cache')){
45
- // fallback for WP-Super-Cache
46
- global $cache_path;
47
- if (is_multisite()) {
48
- $blog_id = get_current_blog_id();
49
- prune_super_cache( get_supercache_dir( $blog_id ), true );
50
- prune_super_cache( $cache_path . 'blogs/', true );
51
- } else {
52
- prune_super_cache($cache_path.'supercache/',true);
53
- prune_super_cache($cache_path,true);
54
- }
55
- }
56
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
classlesses/autoptimizePartners.php DELETED
@@ -1,122 +0,0 @@
1
- <?php
2
- /*
3
- Classlessly add a "more tools" tab to promote (future) AO addons and/ or affiliate services
4
- */
5
-
6
- if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
7
-
8
- add_action('admin_init', 'ao_partner_tabs_preinit');
9
- function ao_partner_tabs_preinit() {
10
- if (apply_filters('autoptimize_filter_show_partner_tabs',true)) {
11
- add_filter('autoptimize_filter_settingsscreen_tabs','ao_add_partner_tabs');
12
- }
13
- }
14
-
15
- function ao_add_partner_tabs($in) {
16
- $in=array_merge($in,array('ao_partners' => __('Optimize More!','autoptimize')));
17
- return $in;
18
- }
19
-
20
- add_action('admin_menu','ao_partners_init');
21
- function ao_partners_init() {
22
- if (apply_filters('autoptimize_filter_show_partner_tabs',true)) {
23
- $hook=add_submenu_page(NULL,'AO partner','AO partner','manage_options','ao_partners','ao_partners');
24
- // register_settings here as well if needed
25
- }
26
- }
27
-
28
- function ao_partners() {
29
- ?>
30
- <style>
31
- .itemDetail {
32
- background: #fff;
33
- width: 250px;
34
- min-height: 290px;
35
- border: 1px solid #ccc;
36
- float: left;
37
- padding: 15px;
38
- position: relative;
39
- margin: 0 10px 10px 0;
40
- }
41
- .itemTitle {
42
- margin-top:0px;
43
- margin-bottom:10px;
44
- }
45
- .itemImage {
46
- text-align: center;
47
- }
48
- .itemImage img {
49
- max-width: 95%;
50
- max-height: 150px;
51
- }
52
- .itemDescription {
53
- margin-bottom:30px;
54
- }
55
- .itemButtonRow {
56
- position: absolute;
57
- bottom: 10px;
58
- right: 10px;
59
- width:100%;
60
- }
61
- .itemButton {
62
- float:right;
63
- }
64
- .itemButton a {
65
- text-decoration: none;
66
- color: #555;
67
- }
68
- .itemButton a:hover {
69
- text-decoration: none;
70
- color: #23282d;
71
- }
72
- </style>
73
- <div class="wrap">
74
- <h1><?php _e('Autoptimize Settings','autoptimize'); ?></h1>
75
- <?php echo autoptimizeConfig::ao_admin_tabs(); ?>
76
- <?php
77
- echo '<h2>'. __("These Autoptimize power-ups and related services will improve your site's performance even more!",'autoptimize') . '</h2>';
78
- ?>
79
- <div>
80
- <?php getAOPartnerFeed(); ?>
81
- </div>
82
- </div>
83
- <?php
84
- }
85
-
86
- function getAOPartnerFeed() {
87
- $noFeedText=__( 'Have a look at <a href="http://optimizingmatters.com/">optimizingmatters.com</a> for Autoptimize power-ups!', 'autoptimize' );
88
-
89
- if (apply_filters('autoptimize_settingsscreen_remotehttp',true)) {
90
- $rss = fetch_feed( "http://feeds.feedburner.com/OptimizingMattersDownloads" );
91
- $maxitems = 0;
92
-
93
- if ( ! is_wp_error( $rss ) ) {
94
- $maxitems = $rss->get_item_quantity( 20 );
95
- $rss_items = $rss->get_items( 0, $maxitems );
96
- } ?>
97
- <ul>
98
- <?php
99
- if ( $maxitems == 0 ) {
100
- echo $noFeedText;
101
- } else {
102
- foreach ( $rss_items as $item ) :
103
- $itemURL = esc_url( $item->get_permalink() ); ?>
104
- <li class="itemDetail">
105
- <h3 class="itemTitle"><a href="<?php echo $itemURL; ?>" target="_blank"><?php echo esc_html( $item->get_title() ); ?></a></h3>
106
- <?php
107
- if (($enclosure = $item->get_enclosure()) && (strpos($enclosure->get_type(),"image")!==false) ) {
108
- $itemImgURL=esc_url($enclosure->get_link());
109
- echo "<div class=\"itemImage\"><a href=\"".$itemURL."\" target=\"_blank\"><img src=\"".$itemImgURL."\"/></a></div>";
110
- }
111
- ?>
112
- <div class="itemDescription"><?php echo wp_kses_post($item -> get_description() ); ?></div>
113
- <div class="itemButtonRow"><div class="itemButton button-secondary"><a href="<?php echo $itemURL; ?>" target="_blank"><?php _e('More info','autoptimize'); ?></a></div></div>
114
- </li>
115
- <?php endforeach; ?>
116
- <?php } ?>
117
- </ul>
118
- <?php
119
- } else {
120
- echo $noFeedText;
121
- }
122
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
classlesses/autoptimizeSpeedupper.php DELETED
@@ -1,105 +0,0 @@
1
- <?php
2
- /*
3
- * Autoptimize SpeedUp; minify & cache each JS/ CSS separately
4
- * new in Autoptimize 2.2
5
- */
6
-
7
- if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
8
-
9
- function ao_js_snippetcacher($jsin,$jsfilename) {
10
- $md5hash = "snippet_".md5($jsin);
11
- $ccheck = new autoptimizeCache($md5hash,'js');
12
- if($ccheck->check()) {
13
- $scriptsrc = $ccheck->retrieve();
14
- } else {
15
- if ( (strpos($jsfilename,"min.js") === false) && ( strpos($jsfilename,"js/jquery/jquery.js") === false ) && ( str_replace(apply_filters('autoptimize_filter_js_consider_minified',false), '', $jsfilename) === $jsfilename ) ) {
16
- if(class_exists('JSMin')) {
17
- $tmp_jscode = trim(JSMin::minify($jsin));
18
- if (!empty($tmp_jscode)) {
19
- $scriptsrc = $tmp_jscode;
20
- unset($tmp_jscode);
21
- } else {
22
- $scriptsrc=$jsin;
23
- }
24
- } else {
25
- $scriptsrc=$jsin;
26
- }
27
- } else {
28
- // do some housekeeping here to remove comments & linebreaks and stuff
29
- $scriptsrc=preg_replace("#^\s*\/\/.*$#Um","",$jsin);
30
- $scriptsrc=preg_replace("#^\s*\/\*[^!].*\*\/\s?#Us","",$scriptsrc);
31
- $scriptsrc=preg_replace("#(^[\r\n]*|[\r\n]+)[\s\t]*[\r\n]+#", "\n", $scriptsrc);
32
- }
33
-
34
- if ( (substr($scriptsrc,-1,1)!==";") && (substr($scriptsrc,-1,1)!=="}") ) {
35
- $scriptsrc.=";";
36
- }
37
-
38
- if ( !empty($jsfilename) && str_replace( apply_filters('autoptimize_filter_js_speedup_cache',false), '', $jsfilename ) === $jsfilename ) {
39
- // don't cache inline CSS or if filter says no
40
- $ccheck->cache($scriptsrc,'text/javascript');
41
- }
42
- }
43
- unset($ccheck);
44
-
45
- return $scriptsrc;
46
- }
47
-
48
- function ao_css_snippetcacher($cssin,$cssfilename) {
49
- $md5hash = "snippet_".md5($cssin);
50
- $ccheck = new autoptimizeCache($md5hash,'css');
51
- if($ccheck->check()) {
52
- $stylesrc = $ccheck->retrieve();
53
- } else {
54
- if ( ( strpos($cssfilename,"min.css") === false ) && ( str_replace( apply_filters('autoptimize_filter_css_consider_minified',false), '', $cssfilename ) === $cssfilename ) ) {
55
- if (class_exists('Minify_CSS_Compressor')) {
56
- $tmp_code = trim(Minify_CSS_Compressor::process($cssin));
57
- } else if(class_exists('CSSmin')) {
58
- $cssmin = new CSSmin();
59
- if (method_exists($cssmin,"run")) {
60
- $tmp_code = trim($cssmin->run($cssin));
61
- } elseif (@is_callable(array($cssmin,"minify"))) {
62
- $tmp_code = trim(CssMin::minify($cssin));
63
- }
64
- }
65
-
66
- if (!empty($tmp_code)) {
67
- $stylesrc = $tmp_code;
68
- unset($tmp_code);
69
- } else {
70
- $stylesrc = $cssin;
71
- }
72
- } else {
73
- // .min.css -> no heavy-lifting, just some cleanup
74
- $stylesrc=preg_replace("#^\s*\/\*[^!].*\*\/\s?#Us","",$cssin);
75
- $stylesrc=preg_replace("#(^[\r\n]*|[\r\n]+)[\s\t]*[\r\n]+#", "\n", $stylesrc);
76
- $stylesrc=autoptimizeStyles::fixurls($cssfilename,$stylesrc);
77
- }
78
- if ( !empty($cssfilename) && ( str_replace( apply_filters('autoptimize_filter_css_speedup_cache',false), '', $cssfilename ) === $cssfilename ) ) {
79
- // only cache CSS if not inline and allowed by filter
80
- $ccheck->cache($stylesrc,'text/css');
81
- }
82
- }
83
- unset($ccheck);
84
- return $stylesrc;
85
- }
86
-
87
- function ao_css_speedup_cleanup($cssin) {
88
- // speedupper results in aggregated CSS not being minified, so the filestart-marker AO adds when aggregating need to be removed
89
- return trim(str_replace(array('/*FILESTART*/','/*FILESTART2*/'),'',$cssin));
90
- }
91
-
92
- function ao_js_speedup_cleanup($jsin) {
93
- // cleanup
94
- return trim($jsin);
95
- }
96
-
97
- // conditionally attach filters
98
- if ( apply_filters('autoptimize_css_do_minify',true) ) {
99
- add_filter('autoptimize_css_individual_style','ao_css_snippetcacher',10,2);
100
- add_filter('autoptimize_css_after_minify','ao_css_speedup_cleanup',10,1);
101
- }
102
- if ( apply_filters('autoptimize_js_do_minify',true) ) {
103
- add_filter('autoptimize_js_individual_script','ao_js_snippetcacher',10,2);
104
- add_filter('autoptimize_js_after_minify','ao_js_speedup_cleanup',10,1);
105
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
classlesses/autoptimizeUpdateCode.php DELETED
@@ -1,106 +0,0 @@
1
- <?php
2
- /*
3
- * below code handles updates and is only included by autoptimize.php if/ when needed
4
- */
5
-
6
- if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
7
-
8
- $majorUp = false;
9
- $autoptimize_major_version=substr($autoptimize_db_version,0,3);
10
-
11
- switch($autoptimize_major_version) {
12
- case "1.6":
13
- // from back in the days when I did not yet consider multisite
14
- // if user was on version 1.6.x, force advanced options to be shown by default
15
- update_option('autoptimize_show_adv','1');
16
-
17
- // and remove old options
18
- $to_delete_options=array("autoptimize_cdn_css","autoptimize_cdn_css_url","autoptimize_cdn_js","autoptimize_cdn_js_url","autoptimize_cdn_img","autoptimize_cdn_img_url","autoptimize_css_yui","autoptimize_js_yui");
19
- foreach ($to_delete_options as $del_opt) {
20
- delete_option( $del_opt );
21
- }
22
- $majorUp = true;
23
- case "1.7":
24
- // force 3.8 dashicons in CSS exclude options when upgrading from 1.7 to 1.8
25
- if ( !is_multisite() ) {
26
- $css_exclude = get_option('autoptimize_css_exclude');
27
- if (empty($css_exclude)) {
28
- $css_exclude = "admin-bar.min.css, dashicons.min.css";
29
- } else if (strpos($css_exclude,"dashicons.min.css")===false) {
30
- $css_exclude .= ", dashicons.min.css";
31
- }
32
- update_option('autoptimize_css_exclude',$css_exclude);
33
- } else {
34
- global $wpdb;
35
- $blog_ids = $wpdb->get_col( "SELECT blog_id FROM $wpdb->blogs" );
36
- $original_blog_id = get_current_blog_id();
37
- foreach ( $blog_ids as $blog_id ) {
38
- switch_to_blog( $blog_id );
39
- $css_exclude = get_option('autoptimize_css_exclude');
40
- if (empty($css_exclude)) {
41
- $css_exclude = "admin-bar.min.css, dashicons.min.css";
42
- } else if (strpos($css_exclude,"dashicons.min.css")===false) {
43
- $css_exclude .= ", dashicons.min.css";
44
- }
45
- update_option('autoptimize_css_exclude',$css_exclude);
46
- }
47
- switch_to_blog( $original_blog_id );
48
- }
49
- $majorUp = true;
50
- case "1.9":
51
- /*
52
- * 2.0 will not aggregate inline CSS/JS by default, but we want users
53
- * upgrading from 1.9 to keep their inline code aggregated by default.
54
- */
55
- if ( !is_multisite() ) {
56
- update_option('autoptimize_css_include_inline','on');
57
- update_option('autoptimize_js_include_inline','on');
58
- } else {
59
- global $wpdb;
60
- $blog_ids = $wpdb->get_col( "SELECT blog_id FROM $wpdb->blogs" );
61
- $original_blog_id = get_current_blog_id();
62
- foreach ( $blog_ids as $blog_id ) {
63
- switch_to_blog( $blog_id );
64
- update_option('autoptimize_css_include_inline','on');
65
- update_option('autoptimize_js_include_inline','on');
66
- }
67
- switch_to_blog( $original_blog_id );
68
- }
69
- $majorUp = true;
70
- case "2.2":
71
- /*
72
- * 2.3 has no "remove google fonts" in main screen, moved to "extra"
73
- */
74
- if ( !is_multisite() ) {
75
- $_nogooglefont = get_option('autoptimize_css_nogooglefont','');
76
- $_ao_extrasetting = get_option('autoptimize_extra_settings','');
77
- if ( ($_nogooglefont == 1) && ( empty($_ao_extrasetting) ) ) {
78
- $_aoextra_removegfonts = array("autoptimize_extra_checkbox_field_1"=>"0","autoptimize_extra_checkbox_field_0"=>"0","autoptimize_extra_radio_field_4"=>"1","autoptimize_extra_text_field_2"=>"","autoptimize_extra_text_field_3"=>"");
79
- update_option( 'autoptimize_extra_settings', $_aoextra_removegfonts );
80
- }
81
- delete_option('autoptimize_css_nogooglefont');
82
- } else {
83
- global $wpdb;
84
- $blog_ids = $wpdb->get_col( "SELECT blog_id FROM $wpdb->blogs" );
85
- $original_blog_id = get_current_blog_id();
86
- foreach ( $blog_ids as $blog_id ) {
87
- switch_to_blog( $blog_id );
88
- $_nogooglefont = get_option('autoptimize_css_nogooglefont','');
89
- $_ao_extrasetting = get_option('autoptimize_extra_settings','');
90
- if ( ($_nogooglefont == 1) && ( empty($_ao_extrasetting) ) ) {
91
- $_aoextra_removegfonts = array("autoptimize_extra_checkbox_field_1"=>"0","autoptimize_extra_checkbox_field_0"=>"0","autoptimize_extra_radio_field_4"=>"1","autoptimize_extra_text_field_2"=>"","autoptimize_extra_text_field_3"=>"");
92
- update_option( 'autoptimize_extra_settings', $_aoextra_removegfonts );
93
- }
94
- delete_option('autoptimize_css_nogooglefont');
95
- }
96
- switch_to_blog( $original_blog_id );
97
- }
98
- $majorUp = true;
99
- }
100
-
101
- if ( $majorUp === true && get_transient('autoptimize_stale_option_buster') == false ) {
102
- // clear cache and notify user to check result if major upgrade
103
- set_transient('autoptimize_stale_option_buster', 'Mamsie & Liessie zehhe: ZWIJH!', HOUR_IN_SECONDS);
104
- autoptimizeCache::clearall();
105
- add_action('admin_notices', 'autoptimize_update_config_notice');
106
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
config/default.php CHANGED
@@ -5,7 +5,7 @@ if(!isset($_SERVER['HTTP_ACCEPT_ENCODING']))
5
  $_SERVER['HTTP_ACCEPT_ENCODING'] = '';
6
  if(!isset($_SERVER['HTTP_USER_AGENT']))
7
  $_SERVER['HTTP_USER_AGENT'] = '';
8
-
9
  // Determine supported compression method
10
  $gzip = strstr($_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip');
11
  $deflate = strstr($_SERVER['HTTP_ACCEPT_ENCODING'], 'deflate');
@@ -14,15 +14,15 @@ $deflate = strstr($_SERVER['HTTP_ACCEPT_ENCODING'], 'deflate');
14
  $encoding = $gzip ? 'gzip' : ($deflate ? 'deflate' : 'none');
15
 
16
  // Check for buggy versions of Internet Explorer
17
- if (!strstr($_SERVER['HTTP_USER_AGENT'], 'Opera') &&
18
  preg_match('/^Mozilla\/4\.0 \(compatible; MSIE ([0-9]\.[0-9])/i', $_SERVER['HTTP_USER_AGENT'], $matches))
19
  {
20
  $version = floatval($matches[1]);
21
-
22
  if ($version < 6)
23
  $encoding = 'none';
24
-
25
- if ($version == 6 && !strstr($_SERVER['HTTP_USER_AGENT'], 'EV1'))
26
  $encoding = 'none';
27
  }
28
 
@@ -30,10 +30,20 @@ if (!strstr($_SERVER['HTTP_USER_AGENT'], 'Opera') &&
30
  if(ini_get('output_handler') == 'ob_gzhandler' || ini_get('zlib.output_compression') == 1)
31
  $encoding = 'none';
32
 
33
- //Get data
34
- $contents = file_get_contents(__FILE__.'.'.$encoding);
 
 
 
 
 
 
 
 
35
 
36
  // first check if we have to send 304
 
 
37
  $eTag=md5($contents);
38
  $modTime=filemtime(__FILE__.'.none');
39
 
@@ -46,20 +56,32 @@ if (($modTimeMatch)||($eTagMatch)) {
46
  header('Connection: close');
47
  } else {
48
  // send all sorts of headers
49
- $expireTime=60*60*24*356; // 1y max according to RFC
50
-
51
- if(isset($encoding) && $encoding != 'none')
52
- {
53
  header('Content-Encoding: '.$encoding);
54
  }
55
  header('Vary: Accept-Encoding');
56
  header('Content-Length: '.strlen($contents));
57
  header('Content-type: %%CONTENT%%; charset=utf-8');
 
58
  header('Cache-Control: max-age='.$expireTime.', public, immutable');
59
- header('Expires: '.gmdate('D, d M Y H:i:s', time() + $expireTime).' GMT'); //10 years
60
  header('ETag: ' . $eTag);
61
  header('Last-Modified: '.gmdate('D, d M Y H:i:s', $modTime).' GMT');
62
-
63
  // send output
64
  echo $contents;
 
 
 
 
 
 
 
 
 
 
 
 
 
65
  }
5
  $_SERVER['HTTP_ACCEPT_ENCODING'] = '';
6
  if(!isset($_SERVER['HTTP_USER_AGENT']))
7
  $_SERVER['HTTP_USER_AGENT'] = '';
8
+
9
  // Determine supported compression method
10
  $gzip = strstr($_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip');
11
  $deflate = strstr($_SERVER['HTTP_ACCEPT_ENCODING'], 'deflate');
14
  $encoding = $gzip ? 'gzip' : ($deflate ? 'deflate' : 'none');
15
 
16
  // Check for buggy versions of Internet Explorer
17
+ if (!strstr($_SERVER['HTTP_USER_AGENT'], 'Opera') &&
18
  preg_match('/^Mozilla\/4\.0 \(compatible; MSIE ([0-9]\.[0-9])/i', $_SERVER['HTTP_USER_AGENT'], $matches))
19
  {
20
  $version = floatval($matches[1]);
21
+
22
  if ($version < 6)
23
  $encoding = 'none';
24
+
25
+ if ($version == 6 && !strstr($_SERVER['HTTP_USER_AGENT'], 'EV1'))
26
  $encoding = 'none';
27
  }
28
 
30
  if(ini_get('output_handler') == 'ob_gzhandler' || ini_get('zlib.output_compression') == 1)
31
  $encoding = 'none';
32
 
33
+ $iscompressed = file_exists(__FILE__.'.'.$encoding);
34
+ if($encoding != 'none' && $iscompressed == false)
35
+ {
36
+ $flag = ($encoding == 'gzip' ? FORCE_GZIP : FORCE_DEFLATE);
37
+ $code = file_get_contents(__FILE__.'.none');
38
+ $contents = gzencode($code,9,$flag);
39
+ }else{
40
+ //Get data
41
+ $contents = file_get_contents(__FILE__.'.'.$encoding);
42
+ }
43
 
44
  // first check if we have to send 304
45
+ // inspired by http://www.jonasjohn.de/snippets/php/caching.htm
46
+
47
  $eTag=md5($contents);
48
  $modTime=filemtime(__FILE__.'.none');
49
 
56
  header('Connection: close');
57
  } else {
58
  // send all sorts of headers
59
+ $expireTime=60*60*24*355; // 1y max according to RFC
60
+ if ($encoding != 'none') {
 
 
61
  header('Content-Encoding: '.$encoding);
62
  }
63
  header('Vary: Accept-Encoding');
64
  header('Content-Length: '.strlen($contents));
65
  header('Content-type: %%CONTENT%%; charset=utf-8');
66
+ header('Cache-Control: max-age='.$expireTime.', public, must-revalidate');
67
  header('Cache-Control: max-age='.$expireTime.', public, immutable');
68
+ header('Expires: '.gmdate('D, d M Y H:i:s', time() + $expireTime).' GMT');
69
  header('ETag: ' . $eTag);
70
  header('Last-Modified: '.gmdate('D, d M Y H:i:s', $modTime).' GMT');
71
+
72
  // send output
73
  echo $contents;
74
+
75
+ //And write to filesystem cache if not done yet
76
+ if($encoding != 'none' && $iscompressed == false)
77
+ {
78
+ //Write the content we sent
79
+ file_put_contents(__FILE__.'.'.$encoding,$contents);
80
+
81
+ //And write the new content
82
+ $flag = ($encoding == 'gzip' ? FORCE_DEFLATE : FORCE_GZIP);
83
+ $ext = ($encoding == 'gzip' ? 'deflate' : 'gzip');
84
+ $contents = gzencode($code,9,$flag);
85
+ file_put_contents(__FILE__.'.'.$ext,$contents);
86
+ }
87
  }
config/delayed.php DELETED
@@ -1,86 +0,0 @@
1
- <?php exit;
2
-
3
- //Check everything exists before using it
4
- if(!isset($_SERVER['HTTP_ACCEPT_ENCODING']))
5
- $_SERVER['HTTP_ACCEPT_ENCODING'] = '';
6
- if(!isset($_SERVER['HTTP_USER_AGENT']))
7
- $_SERVER['HTTP_USER_AGENT'] = '';
8
-
9
- // Determine supported compression method
10
- $gzip = strstr($_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip');
11
- $deflate = strstr($_SERVER['HTTP_ACCEPT_ENCODING'], 'deflate');
12
-
13
- // Determine used compression method
14
- $encoding = $gzip ? 'gzip' : ($deflate ? 'deflate' : 'none');
15
-
16
- // Check for buggy versions of Internet Explorer
17
- if (!strstr($_SERVER['HTTP_USER_AGENT'], 'Opera') &&
18
- preg_match('/^Mozilla\/4\.0 \(compatible; MSIE ([0-9]\.[0-9])/i', $_SERVER['HTTP_USER_AGENT'], $matches))
19
- {
20
- $version = floatval($matches[1]);
21
-
22
- if ($version < 6)
23
- $encoding = 'none';
24
-
25
- if ($version == 6 && !strstr($_SERVER['HTTP_USER_AGENT'], 'EV1'))
26
- $encoding = 'none';
27
- }
28
-
29
- //Some servers compress the output of PHP - Don't break in those cases
30
- if(ini_get('output_handler') == 'ob_gzhandler' || ini_get('zlib.output_compression') == 1)
31
- $encoding = 'none';
32
-
33
- $iscompressed = file_exists(__FILE__.'.'.$encoding);
34
- if($encoding != 'none' && $iscompressed == false)
35
- {
36
- $flag = ($encoding == 'gzip' ? FORCE_GZIP : FORCE_DEFLATE);
37
- $code = file_get_contents(__FILE__.'.none');
38
- $contents = gzencode($code,9,$flag);
39
- }else{
40
- //Get data
41
- $contents = file_get_contents(__FILE__.'.'.$encoding);
42
- }
43
-
44
- // first check if we have to send 304
45
- // inspired by http://www.jonasjohn.de/snippets/php/caching.htm
46
-
47
- $eTag=md5($contents);
48
- $modTime=filemtime(__FILE__.'.none');
49
-
50
- date_default_timezone_set("UTC");
51
- $eTagMatch = (isset($_SERVER['HTTP_IF_NONE_MATCH']) && strpos($_SERVER['HTTP_IF_NONE_MATCH'],$eTag));
52
- $modTimeMatch = (isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) && strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']) === $modTime);
53
-
54
- if (($modTimeMatch)||($eTagMatch)) {
55
- header('HTTP/1.1 304 Not Modified');
56
- header('Connection: close');
57
- } else {
58
- // send all sorts of headers
59
- $expireTime=60*60*24*355; // 1y max according to RFC
60
- if ($encoding != 'none') {
61
- header('Content-Encoding: '.$encoding);
62
- }
63
- header('Vary: Accept-Encoding');
64
- header('Content-Length: '.strlen($contents));
65
- header('Content-type: %%CONTENT%%; charset=utf-8');
66
- header('Cache-Control: max-age='.$expireTime.', public, immutable');
67
- header('Expires: '.gmdate('D, d M Y H:i:s', time() + $expireTime).' GMT');
68
- header('ETag: ' . $eTag);
69
- header('Last-Modified: '.gmdate('D, d M Y H:i:s', $modTime).' GMT');
70
-
71
- // send output
72
- echo $contents;
73
-
74
- //And write to filesystem cache if not done yet
75
- if($encoding != 'none' && $iscompressed == false)
76
- {
77
- //Write the content we sent
78
- file_put_contents(__FILE__.'.'.$encoding,$contents);
79
-
80
- //And write the new content
81
- $flag = ($encoding == 'gzip' ? FORCE_DEFLATE : FORCE_GZIP);
82
- $ext = ($encoding == 'gzip' ? 'deflate' : 'gzip');
83
- $contents = gzencode($code,9,$flag);
84
- file_put_contents(__FILE__.'.'.$ext,$contents);
85
- }
86
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
localization/index.html DELETED
@@ -1 +0,0 @@
1
- <html><head><meta name="robots" content="noindex, nofollow"></head><body>Generated by <a href="http://wordpress.org/extend/plugins/autoptimize/" rel="nofollow">Autoptimize</a></body></html>
 
readme.txt CHANGED
@@ -1,21 +1,22 @@
1
  === Autoptimize ===
2
- Contributors: futtta, optimizingmatters, turl
3
- Tags: optimize, minify, performance, pagespeed, async
4
  Donate link: http://blog.futtta.be/2013/10/21/do-not-donate-to-me/
5
  Requires at least: 4.0
6
  Tested up to: 4.9
7
- Stable tag: 2.3.4
 
8
 
9
- Autoptimize speeds up your website by optimizing JS, CSS and HTML, async-ing JavaScript, removing emoji cruft, optimizing Google Fonts and more.
10
 
11
  == Description ==
12
 
13
- Autoptimize makes optimizing your site really easy. It can aggregate, minify and cache scripts and styles, injects CSS in the page head by default (but can also defer), moves and defers scripts to the footer and minifies HTML. The "Extra" options allow you to async non-aggregated JavaScript, remove WordPress core emoji cruft, optimize Google Fonts and more. As such it can improve your site's performance even when already on HTTP/2! There is extensive API available to enable you to tailor Autoptimize to each and every site's specific needs.
14
 
15
  If you consider performance important, you really should use one of the many caching plugins to do page caching. Some good candidates to complement Autoptimize that way are e.g. [WP Super Cache](http://wordpress.org/plugins/wp-super-cache/), [HyperCache](http://wordpress.org/plugins/hyper-cache/), [Comet Cache](https://wordpress.org/plugins/comet-cache/) or [KeyCDN's Cache Enabler](https://wordpress.org/plugins/cache-enabler).
16
 
17
  > <strong>Premium Support</strong><br>
18
- > We provide great [Autoptimize Pro Support and Web Performance Optimization services](http://autoptimize.com/), check out our offering on (http://autoptimize.com/)!
19
 
20
  (Speed-surfing image under creative commons [by LL Twistiti](https://www.flickr.com/photos/twistiti/818552808/))
21
 
@@ -35,7 +36,7 @@ It concatenates all scripts and styles, minifies and compresses them, adds expir
35
 
36
  = But I'm on HTTP/2, so I don't need Autoptimize? =
37
 
38
- HTTP/2 is a great step forward for sure, reducing the impact of multiple requests from the same server significantly by using the same connection to perform several concurrent requests. That being said, [concatenation of CSS/ JS can still make a lot of sense](http://engineering.khanacademy.org/posts/js-packaging-http2.htm), as described in [this css-tricks.com article](https://css-tricks.com/http2-real-world-performance-test-analysis/) and this [blogpost from one of the Ebay engineers](http://calendar.perfplanet.com/2015/packaging-for-performance/). The conclusion; configure, test, reconfigure, retest, tweak and look what works best in your context. Maybe it's just HTTP/2, maybe it's HTTP/2 + aggregation and minification, maybe it's HTTP/2 + minification (which AO can do as well).
39
 
40
  = Will this work with my blog? =
41
 
@@ -134,7 +135,7 @@ Both CSS and JS optimization can skip code from being aggregated and minimized b
134
 
135
  = Configuring & Troubleshooting Autoptimize =
136
 
137
- After having installed and activated the plugin, you'll have access to an admin page where you can to enable HTML, CSS and JavaScript optimization. According to your liking, you can start of just enabling all of them, or if you're more cautious one at a time.
138
 
139
  If your blog doesn't function normally after having turned on Autoptimize, here are some pointers to identify & solve such issues using "advanced settings":
140
 
@@ -147,13 +148,7 @@ If your blog doesn't function normally after having turned on Autoptimize, here
147
 
148
  = Help, I have a blank page or an internal server error after enabling Autoptimize!! =
149
 
150
- First of all make sure you're not running other HTML, CSS or JS minification plugins (BWP minify, WP minify, ...) simultaneously with Autoptimize or disable that functionality your page caching plugin (W3 Total Cache, WP Fastest Cache, ...).
151
-
152
- In some rare cases the [CSS minification component](https://github.com/tubalmartin/YUI-CSS-compressor-PHP-port/) currently used by Autoptimize crashes due to a lack of resources (see [detailed technical explanation here](http://blog.futtta.be/2014/01/14/irregular-expressions-have-your-stack-for-lunch/)). You can in that case either disable CSS optimization, try to exclude specific CSS from being aggregated or activate the legacy minifiers which don't have that problem. The latter can be accomplished by adding this to your wp-config.php:
153
-
154
- `define("AUTOPTIMIZE_LEGACY_MINIFIERS","true");`
155
-
156
- The "legacy minifiers" will remain in Autoptimize "for ever" and changes to wp-config.php are not affected by core-, theme- or plugin-upgrades so you should be good to go.
157
 
158
  = But I still have blank autoptimized CSS or JS-files! =
159
 
@@ -196,14 +191,13 @@ NextGen Galleries does some nifty stuff to add JavaScript. In order for Autoptim
196
  = What is noptimize? =
197
 
198
  Starting with version 1.6.6 Autoptimize excludes everything inside noptimize tags, e.g.:
199
-
200
- <!--noptimize--><script>alert('this will not get autoptimized')></script><!--/noptimize-->
201
 
202
  You can do this in your page/ post content, in widgets and in your theme files (consider creating [a child theme](http://codex.wordpress.org/Child_Themes) to avoid your work being overwritten by theme updates).
203
 
204
  = Can I change the directory & filename of cached autoptimize files? =
205
 
206
- Yes, if you want to serve files from e.g. /wp-content/resources/aggregated_12345.css instead of the default /wp-content/cache/autoptimize/autoptimize_12345.css, then add this to wp-config.php:
207
  `
208
  define('AUTOPTIMIZE_CACHE_CHILD_DIR','/resources/');
209
  define('AUTOPTIMIZE_CACHEFILE_PREFIX','aggregated_');
@@ -213,7 +207,7 @@ define('AUTOPTIMIZE_CACHEFILE_PREFIX','aggregated_');
213
 
214
  Yes, but this is off by default. You can enable this by passing ´true´ to ´autoptimize_filter_cache_create_static_gzip´. You'll obviously still have to configure your webserver to use these files instead of the non-gzipped ones to avoid the overhead of on-the-fly compression.
215
 
216
- = What does "remove emoji's" do? =
217
 
218
  This new option in Autoptimize 2.3 removes the inline CSS, inline JS and linked JS-file added by WordPress core. As such is can have a small positive impact on your site's performance.
219
 
@@ -223,7 +217,7 @@ Although some online performance assessement tools will single out "query string
223
 
224
  = (How) should I optimize Google Fonts? =
225
 
226
- Google Fonts are typically loaded by a "render blocking" linked CSS-file. If you have a theme and plugins that use Google Fonts, you might end up with multiple such CSS-files. Autoptimize (since version 2.3) now let's you lessen the impact of Google Fonts by either removing them alltogether or by optimizing the way they are loaded. There are two optimization-flavors; the first one is "combine and link", which replaces all requests for Google Fonts into one request, which will still be render-blocking but will allow the fonts to be loaded immediately (meaning you won't see fonts change while the page is loading). The alternative is "combine and load async" which uses JavaScript to load the fonts in a non-render blocking manner but which might cause a "flash of unstyled text".
227
 
228
  = Should I use "preconnect" =
229
 
@@ -233,6 +227,33 @@ Preconnect is a somewhat advanced feature to instruct browsers ([if they support
233
 
234
  JavaScript files that are not autoptimized (because they were excluded or because they are hosted elsewhere) are typically render-blocking. By adding them in the comma-separated "async JS" field, Autoptimize will add the async flag causing the browser to load those files asynchronously (i.e. non-render blocking). This can however break your site (page), e.g. if you async "js/jquery/jquery.js" you will very likely get "jQuery is not defined"-errors. Use with care.
235
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
236
  = Where can I get help? =
237
 
238
  You can get help on the [wordpress.org support forum](http://wordpress.org/support/plugin/autoptimize). If you are 100% sure this your problem cannot be solved using Autoptimize configuration and that you in fact discovered a bug in the code, you can [create an issue on GitHub](https://github.com/futtta/autoptimize/issues). If you're looking for premium support, check out our [Autoptimize Pro Support and Web Performance Optimization services](http://autoptimize.com/).
@@ -249,9 +270,20 @@ Just [fork Autoptimize on Github](https://github.com/futtta/autoptimize) and cod
249
 
250
  == Changelog ==
251
 
 
 
 
 
 
 
 
 
 
 
 
252
  = 2.3.4 =
253
  * bugfix: is_plugin_active causing errors in some cases as [reported by @iluminancia and @lozula](https://wordpress.org/support/topic/fatal-error-after-update-to-2-3-3/)
254
- * bugfix: added language domain to 4 __/_e functions, un grand merci à Guillaume Blet!
255
 
256
  = 2.3.3 =
257
  * improvement: updated to latest version of Filamentgroup's loadCSS
@@ -270,10 +302,10 @@ Just [fork Autoptimize on Github](https://github.com/futtta/autoptimize) and cod
270
  * fix for issue with update-code in some circumstances, thanks to [Rajendra Zore](https://rajendrazore.com/) to report & help fix!
271
 
272
  = 2.3.0 =
273
- * new: optimize Google fonts with combine & link and combine and load async (with webload.js), intelligently preconnecting to Googles domains to limit performance impact even further
274
  * new: Async JS, can be applied to local or 3rd party JS (if local it will be auto-excluded from autoptimization)
275
  * new: support to tell browsers to preconnect (= dns lookup + tcp/ip connection + ssl negotiation) to 3rd party domains (depends on browser support, works in Chrome & Firefox)
276
- * new: remove WordPress Core’s emoji CSS & JS
277
  * new: remove (version parameter from) Querystring
278
  * new: support to clear cache through WP CLI thanks to [junaidbhura](https://junaidbhura.com)
279
  * lots of [bugfixes and small improvements done by some seriously smart people via GitHub](https://github.com/futtta/autoptimize/commits/master) (thanks all!!), including [a fix for AO 2.2 which saw the HTML minifier go PacMan on spaces](https://github.com/futtta/autoptimize/commit/0f6ac683c35bc82d1ac2d496ae3b66bb53e49f88) in some circumstances.
@@ -284,7 +316,7 @@ Just [fork Autoptimize on Github](https://github.com/futtta/autoptimize) and cod
284
  * readme update
285
 
286
  = 2.2.1 =
287
- * fix for images being referenced in CSS not all being translated to correct path, leading to 404s as reported by Jeff Inho
288
  * fix for "[] operator not supported for strings" error in PHP7.1 as reported by falk-wussow.de
289
  * fix for security hash busting AO's cache in some cases (esp. in 2.1.1)
290
 
@@ -292,7 +324,7 @@ Just [fork Autoptimize on Github](https://github.com/futtta/autoptimize) and cod
292
  * new: Autoptimize minifies first (caching the individual snippets) and aggregrates the minified snippets, resulting in huge performance improvements for uncached JS/ CSS.
293
  * new: option to enable/ disable AO for logged in users (on by default)
294
  * new: option to enable/ disable AO on WooCommerce, Easy Digital Downloads or WP eCommerce cart/ checkout page (on by default)
295
- * improvement: switched to [rel=preload + Filamentgroups loadCSS for CSS deferring](http://blog.futtta.be/2017/02/24/autoptimize-css-defer-switching-to-loadcss-soon/)
296
  * improvement: switched to YUI CSS minifier PHP-port 2.8.4-p10 (so not to the 3.x branch yet)
297
  * improvements to the logic of which JS/ CSS can be optimized (getPath function) increasing reliability of the aggregation process
298
  * security: made placeholder replacement less naive to protect against XSS and LFI vulnerability as reported by Matthew Barry and fixed with great help from Matthew and Tomas Trkulja. Thanks guys!!
@@ -309,7 +341,7 @@ Just [fork Autoptimize on Github](https://github.com/futtta/autoptimize) and cod
309
 
310
  = 2.1.0 =
311
  * new: Autoptimize now appears in admin-toolbar with an easy view on cache size and the possibility to purge the cache (pass `false` to `autoptimize_filter_toolbar_show` filter to disable), a big thanks to [Pablo Custo](https://github.com/pablocusto) for his hard work on this nice feature!
312
- * new: An extra "More Optimization"-tab is shown (can be hidden with ´autoptimize_filter_show_partner_tabs´-filter) with information about related optimization tools- and services.
313
  * new: If cache size becomes too big, a mail will be sent to the site admin (pass `false` to `autoptimize_filter_cachecheck_sendmail` filter to disable or pass alternative email to the `autoptimize_filter_cachecheck_mailto` filter to change email-address)
314
  * new: power-users can enable Autoptimize to pre-gzip the autoptimized files by passing `true` to `autoptimize_filter_cache_create_static_gzip`, kudo's to (Draikin)[https://github.com/Draikin] for this!
315
  * improvement: admin GUI updated (again; thanks Pablo!) with some responsiveness added in the mix (not showing the right hand column on smaller screen-sizes)
@@ -347,259 +379,5 @@ Just [fork Autoptimize on Github](https://github.com/futtta/autoptimize) and cod
347
  * lots of small and bigger bugfixes, I won't bother you with a full list but have a look at [the commmit log on GitHub](https://github.com/futtta/autoptimize/commits/master).
348
  * tested and confirmed working with PHP7
349
 
350
- = 1.9.4 =
351
- * bugfix: make sure non-AO CSSmin doesn't get fed 2 parameters (as some only expect one, which resulted in an internal server error), based on [feedback from zerooverture and zamba](https://wordpress.org/support/topic/error-code-500internal-server-error?replies=7)
352
- * bugfix: make default add_action hook back into "template_redirect" instead of "init" to fix multiple problems as reported by [schecteracademicservices, bond138, rickenbacker](https://wordpress.org/support/topic/192-concatenated-js-but-193-does-not-for-me?replies=11), [Rick Sportel](https://wordpress.org/support/topic/version-193-made-plugin-wp-cdn-rewrite-crash?replies=3#post-6833159) and [wizray](https://wordpress.org/support/topic/the-page-loads-both-the-auto-combined-css-file-and-origin-raw-file?replies=11#post-6833146). If you do need Autoptimize to initialize earlier (e.g. when using Nextgen Galleries), then add this to your wp-config.php:
353
- `define("AUTOPTIMIZE_INIT_EARLIER","true");`
354
-
355
- = 1.9.3 =
356
- * improvement: more intelligent CDN-replacement logic, thanks [Squazz for reporting and testing](https://wordpress.org/support/topic/enable-cdn-for-images-referenced-in-the-css?replies=9)
357
- * improvement: allow strings (comments) to be excluded from HTML-optimization (comment removal)
358
- * improvement: changed priority with which AO gets triggered by WordPress, solving JS not being aggregated when NextGen Galleries is active, with great [help from msebald](https://wordpress.org/support/topic/js-options-dont-work-if-html-disabled/)
359
- * improvement: extra JS exclude-strings: gist.github.com, text/html, text/template, wp-slimstat.min.js, _stq, nonce, post_id (the latter two were removed from the "manual" exclude list on the settings-page)
360
- * new in API: autoptimize_filter_html_exclude, autoptimize_filter_css_defer, autoptimize_filter_css_inline, autoptimize_filter_base_replace_cdn, autopitmize_filter_js_noptimize, autopitmize_filter_css_noptimize, autopitmize_filter_html_noptimize
361
- * bugfix: remove some PHP notices, as [reported by dimitrov.adrian](https://wordpress.org/support/topic/php-errors-39)
362
- * bugfix: make sure HTML-optimalization does not gobble a space before a cite [as proposed by ecdltf](https://wordpress.org/support/topic/%E2%80%9Coptimize-html%E2%80%9D-is-gobbling-whitespace-before-cite-tag)
363
- * bugfix: cleaning the cache did not work on non-default directories as [encountered by NoahJ Champion](https://wordpress.org/support/topic/changing-the-wp-content-path-to-top-level?replies=10#post-6573657)
364
- * upgraded to [yui compressor php port 2.4.8-4](https://github.com/tubalmartin/YUI-CSS-compressor-PHP-port)
365
- * added arabic translation, thanks to the [ekleel team](http://www.ekleel.net)
366
- * tested with WordPress 4.2 beta 3
367
-
368
- = 1.9.2 =
369
- First of all; Happy holidays, all the best for 2015!!
370
-
371
- * New: support for alternative cache-directory and file-prefix as requested by a.o. [Jassi Bacha](https://wordpress.org/support/topic/requesthelp-add-ability-to-specify-cache-folder?replies=1#post-6300128), [Cluster666](https://wordpress.org/support/topic/rewrite-js-path?replies=6#post-6363535) and Baris Unver.
372
- * Improvement: hard-exclude all linked-data json objects (script type=application/ld+json)
373
- * Improvement: several filters added to the API, e.g. to alter optimized HTML, CSS or JS
374
- * Bugfix: set Autoptimize priority back from 11 to 2 (as previously) to avoid some pages not being optimized (thanks to [CaveatLector for investigating & reporting](https://wordpress.org/support/topic/wp-property-plugin-add_action-priority-incompatibility?replies=1))
375
- * Bugfix (in YUI-CSS-compressor-PHP-port): don't convert bools to percentages in rotate3D-transforms (cfr. [bugreport on Github](https://github.com/tubalmartin/YUI-CSS-compressor-PHP-port/issues/17))
376
- * Bugfix: background images with a space in the path didn't load, [reported by johnh10](https://wordpress.org/support/topic/optimize-css-code-error-with-background-image-elements?replies=6#post-6201582).
377
- * Bugfix: SVG image with fill:url broken after CSS optimization as [reported by Tkama](https://wordpress.org/support/topic/one-more-broblem-with-plugin?replies=2)
378
- * Updated translation for Swedish, new translation for Ukrainian by [Zanatoly of SebWeo.com](http://SebWeo.com)
379
- * Updated readme.txt
380
- * Confirmed working with WordPress 4.1
381
-
382
- = 1.9.1 =
383
- * hard-exclude [the sidelink-search-box introduced in WP SEO v1.6](http://wordpress.org/plugins/wordpress-seo/changelog/) from JS optimization (this [broke some JS-optimization badly](http://wordpress.org/support/topic/190-breaks-js?replies=4))
384
- * bugfix: first add semi-colon to inline script, only then add try-catch if required instead of the other way around.
385
-
386
- = 1.9.0 =
387
- * "Inline and defer CSS" allows one to specify which "above the fold CSS" should be inlined, while the normal optimized CSS is deferred.
388
- * Inlined Base64-encoded background Images will now be cached as well and the threshold for inlining these images has been bumped up to 4096 bytes (from 2560).
389
- * Separate cache-directories for CSS and JS in /wp-content/cache/autoptimize, which should result in faster cache pruning (and in some cases possibly faster serving of individual aggregated files).
390
- * Autoptimized CSS is now injected before the <title>-tag, JS before </body> (and after </title> when forced in head). This can be overridden in the API.
391
- * Some usability improvements of the administration-page
392
- * Multiple hooks added to the API a.o. filters to not aggregate inline CSS or JS and filters to aggregate but not minify CSS or JS.
393
- * Updated translations for Dutch, French, German, Persian and Polish and new translations for Brazilian Portuguese (thanks to [Leonardo Antonioli](http://tobeguarany.com/)) and Turkish (kudo's [Baris Unver](http://beyn.org/))
394
- * Multiple bugfixes & improvements
395
- * Tested with WordPress 4.0 rc3
396
-
397
- = 1.8.5 =
398
- * Updated to lastest version of [CSS minification component](https://github.com/tubalmartin/YUI-CSS-compressor-PHP-port/)
399
- * Improvement: for multi-sites the cache is now written to separate directories, avoiding one site to clean out the cache for the entire installation. Code [contributed by Joern Lund](http://wordpress.org/support/topic/multisite-blog-admin-can-delete-entire-network-cache), kudo's Joern!!
400
- * Improvement: add WordPress plugin header to autoptimize_helper.php_example to make it easier to enable it as a module
401
- * Improvement: nonce and post_id are added to default configuration for JS exclusion
402
- * Improvement: explicitely exclude wp-admin from being Autoptimized
403
- * Bugfix: plupload.min.js, syntaxhighlighter and "adsbygoogle" are excluded from JS aggregation.
404
- * Bugfix: avoid double closing body-tags when Autoptimize adds JS to HTML as [reported by Can](http://wordpress.org/support/topic/works-like-a-charm-but-i-have-two-problems)
405
- * Bugfix: make .htaccess compatible with both Apache 2.2 and 2.4 (http://wordpress.org/support/topic/feature-request-support-generating-htaccess-files-for-apache-24?replies=3)
406
-
407
- = 1.8.4 =
408
- * Bugfix: code in inline JS (or CSS) can be wrapped inside HTML-comments, but these got removed since 1.8.2 as part of a bugfix.
409
-
410
- = 1.8.3 =
411
- * Bugfix: avoid useless warnings on is_callable to flood php error log as [reported by Praveen Kumar](http://wordpress.org/support/topic/182-breaks-css-and-js?replies=14#post-5377604)
412
-
413
- = 1.8.2 =
414
- * Improvement: more graceful failure when minifier classes exist but method does not, based on [bug-report by Franck160](http://wordpress.org/support/topic/confict-with-dynamic-to-top)
415
- * Improvement: deferred CSS is also outputted in noscript-tags
416
- * Improvement: differentiate between Apache version in .htaccess file as suggested by [iMadalin](http://www.imadalin.ro/)
417
- * Improvement: also aggregate protocol-less CSS/JS URI's (as [suggested by Ross](http://wordpress.org/support/topic/protocol-less-url-support))
418
- * Improvement: disable autoptimization based on parameter in querystring (for debugging)
419
- * Bugfix: some CSS-imports were not being aggregated/ minified
420
- * Bugfix: add CSS before <title instead of <title> to avoid breakage when title includes other attributes (e.g. itemscope)
421
- * Bugfix: make sure javascript or css between comments is not aggregated as reported by [Milap Gajjar](http://wordpress.org/support/topic/the-optimized-css-contains-duplicate-classes)
422
- * Tested with WordPress 3.9 (beta 1)
423
- * Updates in FAQ
424
-
425
- = 1.8.1 =
426
- * bugfix: CSS in conditional comments was not excluded from aggregation as reported by [Rolf](http://www.finkbeiner-holz.de/) and [bottapress](http://www.wordpress-hebergement.fr/)
427
-
428
- = 1.8.0 =
429
- * New: Option to inline all CSS [as suggested by Hamed](http://wordpress.org/support/topic/make-style-sheet-inline)
430
- * New: set of filters to provide a simple API to change Autoptimize behavior (e.g. replace "defer" with "async", disabling Autoptimization on certain pages, specificy non-aggregatable script to be moved after aggregated one (cfr. http://wordpress.org/support/topic/feature-request-some-extra-options?replies=14), size of image to be data-urized). More info in the included autoptimize_helper.php_example.
431
- * Improvement: exclude (css in) noscript-tags as [proposed by belg4mit](http://wordpress.org/support/topic/feature-suggestion-noscript-for-css)
432
- * Improvement: switch default delivery of optimized CSS/JS-files from PHP to static files
433
- * Updated [upstream CSS minifier](https://github.com/tubalmartin/YUI-CSS-compressor-PHP-port/commit/fb33d2ffd0963692747101330b175a80173ce21b)
434
- * Improvement (force gzip of static files) and Bugfix (force expiry for dynamic files, thanks to [Willem Razenberg](http://www.column-razenberg.nl/) in .htaccess
435
- * Improvement: fail gracefully when things go wrong (e.g. CSS import resulting in empty aggregated CSS-files [reported by Danka](http://wordpress.org/support/topic/very-good-332) or when the theme is broken [as seen by Prateek Gupta](http://wordpress.org/support/topic/js-optimization-break-site-white-page-issue?replies=14#post-5038941))
436
- * Updated translations and Polish added (thanks to [Jakub Sierpinski](http://www.sierpinski.pl/)).
437
- * Bugfix: stop import-statements in CSS comments to be taken into acccount [hat tip to Josef from blog-it-solutions.de](http://www.blog-it-solutions.de/)
438
- * Bugfix: fix for blur in CSS breakeage as [reported by Chris of clickpanic.com](http://blog.clickpanic.com/)
439
-
440
- = 1.7.3 =
441
- * improvement: remove cache + options on uninstall as [requested by Gingerbreadmen](http://wordpress.org/support/topic/wp_options-entries)
442
- * improvement: set .htaccess to allow PHP execution in wp-content/cache/autoptimize when saving optimized files as PHP, as suggested by (David Mottershead of bermuda4u.com)[http://www.bermuda4u.com/] but forbid PHP execution when saving aggregated script/css as static files (except for multisite).
443
- * bugfix: avoid Yoast SEO sitemaps going blank (due optimization of Yoast's dynamically built XML/XSL) as reported by [Vance Hallman](http://www.icefishing.co) and [Armand Hadife](http://solar-flag-pole-lights.com/). More info on this issue [can be found on my blog](http://blog.futtta.be/2013/12/09/blank-yoast-seo-sitemaps-no-more/).
444
- * smaller changes to readme.txt
445
-
446
- = 1.7.2 =
447
- * improvement: extra checks in CSS @import-handling + move import rules to top of CSS if not imported successfully, based a.o. on bug reports [by ozum](http://wordpress.org/support/topic/zero-lenght-file-with-css-optimization) and by [Peter Stolwijk](http://wordpress.org/support/topic/cant-activate-plugin-22?replies=13#post-4891377)
448
- * improvement: check if JS and CSS minifier classes exist and only load if they don't to avoid possible conflicts with other themes or plugins that already loaded minifiers
449
- * tested and approved for WordPress 3.8 (beta1)
450
-
451
- = 1.7.1 =
452
- * New: support for mapped domains as suggested by [Michael for tiremoni.com](http://tiremoni.com/)
453
- * Added an .htaccess to wp-content/cache/autoptimize to overwrite other caching directives (fixing a problem with WP Super Cache's .htaccess really, [as reported](http://wordpress.org/support/topic/expiresmax-age-compatibility-with-supercache) by [Hugh of www.unitedworldschools.org](http://www.unitedworldschools.org/))
454
- * bugfix: Autoptimize broke data:uri's in CSS in some cases as reported by [Josef from blog-it-solutions.de](http://www.blog-it-solutions.de/)
455
- * bugfix: avoid PHP notice if CSS exclusion list is empty
456
- * moved "do not donate"-image into plugin
457
-
458
- = 1.7.0 =
459
- * New: exclude CSS
460
- * New: defer CSS
461
- * Updated minimizing components (JSMin & YUI PHP CSSMin)
462
- * Updated admin-page, hiding advanced configuration options
463
- * Updated CDN-support for added simplicity (code & UI-wise), including changing background image url in CSS
464
- * Updated/ new translations provided for [French: wordpress-hebergement.fr](http://www.wordpress-hebergement.fr/), [Persian: Hamed Irani](http://basics.ir/), [Swedish: Jonathan Sulo](http://sulo.se/), [German: blog-it-solutions.de](http://www.blog-it-solutions.de/) and Dutch
465
- * Removed support for YUI
466
- * Flush HTML caching plugin's cache when flushing Autoptimize's one
467
- * fix for BOM marker in CSS-files [as seen in Frontier theme](http://wordpress.org/support/topic/sidebar-problem-42), kudo's to [Download Converter](http://convertertoolz.com/) for reporting!
468
- * fix for [protocol-less 3rd party scripts disappearing](http://wordpress.org/support/topic/javascript-optimize-breaks-twentythirteen-mobile-menu), thanks for reporting p33t3r!
469
- * fix for stylesheets without type="text/css" not being autoptimized as reported by [renzo](http://cocobeanproductions.com/)
470
- * tested with WordPress 3.7 beta2 (admin-bar.min.js added to automatically excluded scripts)
471
-
472
- = 1.6.6 =
473
- * New: disable autoptimizatoin by putting part of your HTML, JS or CSS in between noptimize-tags, e.g.;
474
- `<!--noptimize--><script>alert('this will not get autoptimized');</script><!--/noptimize-->`
475
- * Added extra check to prevent plugin-files being called outside of WordPress as suggested in [this good article on security](http://mikejolley.com/2013/08/keeping-your-shit-secure-whilst-developing-for-wordpress/).
476
- * Added small notice to be displayed after installation/ activation to ask user to configure the plugin as well.
477
- * Added Persian translation, thanks to [Hamed T.](http://basics.ir/)
478
-
479
- = 1.6.5 =
480
- * new javascript-debug option to force the aggregated javascript file in the head-section of the HTML instead of at the bottom
481
- * YUI compression & CDN are now deprecated functionality that will be removed in 1.7.0
482
-
483
- = 1.6.4 =
484
- * fix for PHP notice about mfunc_functions
485
- * fix for strpos warnings due to empty values from the "Exclude scripts from autoptimize" configuration as [reported by CandleFOREX](http://wordpress.org/support/topic/empty-needle-warning)
486
- * fix for broken feeds as [reported by Dinata and talgalili](http://wordpress.org/support/topic/feed-issue-5)
487
-
488
- = 1.6.3 =
489
- * fix for IE-hacks with javascript inside, causing javascript breakage (as seen in Sampression theme) as reported by [Takahiro of hiskip.com](http://www.hiskip.com/wp/)
490
- * fix for escaping problem of imported css causing css breakage (as seen in Sampression theme) as reported by Takahiro as well
491
- * fix to parse imports with syntax @import 'custom.css' not being parsed (as seen in Arras theme), again as reported by Takahiro
492
- * fix for complex media types in media-attribute [as reported by jvwisssen](http://wordpress.org/support/topic/autoptimize-and-media-queries)
493
- * fix for disappearing background-images that were already datauri's [as reported by will.blaschko](http://wordpress.org/support/topic/data-uris)
494
- * fix not to strip out comments in HTML needed by WP Super Cache or W3 Total Cache (e.g. mfunc)
495
- * added check to clean cache on upgrade
496
- * updated FAQ in readme with information on troubleshooting and support
497
- * tested with WordPress 3.6 beta
498
-
499
- = 1.6.2 =
500
- * Yet another emergency bugfix I'm afraid: apache_request_headers (again in config/delayed.php) is only available on ... Apache (duh), breaking non-Apache systems such as ngnix, Lighttpd and MS IIS badly. Reported by multiple users, thanks all!
501
-
502
- = 1.6.1 =
503
- * fixed stupid typo in config/delayed.php which broke things badly (april fools-wise); strpos instead of str_pos as reported by Takahiro.
504
-
505
- = 1.6.0 =
506
- * You can now specify scripts that should not be Autoptimized in the admin page. Just add the names (or part of the path) of the scripts in a comma-separated list and that JavaScript-file will remain untouched by Autoptimize.
507
- * Added support for ETag and LastModified (essentially for a better pagespeed score, as the files are explicitely cacheable for 1 year)
508
- * Autoptimizing for logged in users is enabled again
509
- * Autoptimize now creates an index.html in wp-content/cache/autoptimize to prevent snooping (as [proposed by Chris](http://blog.futtta.be/2013/01/07/adopting-an-oss-orphan-autoptimize/#li-comment-36292))
510
- * bugfix: removed all deprecated functions ([reported by Hypolythe](http://wordpress.org/support/topic/many-deprecated-errors) and diff by Heiko Adams, thanks guys!)
511
- * bugfix for HTTPS-problem as [reported by dbs121](http://wordpress.org/support/topic/woocommerce-autoptimizer-https-issue)
512
- * bugfix for breakage with unusual WordPress directory layout as reported by [Josef from blog-it-solutions.de](http://www.blog-it-solutions.de/).
513
-
514
- = 1.5.1 =
515
- * bugfix: add CSS before opening title-tag instead of after closing title, to avoid CSS being loaded in wrong order, as reported by [fotofashion](http://fotoandfashion.de/) and [blogitsolutions](http://www.blog-it-solutions.de) (thanks guys)
516
-
517
- = 1.5 =
518
- * first bugfix release by [futtta](http://blog.futtta.be/2013/01/07/adopting-an-oss-orphan-autoptimize/), thanks for a great plugin Turl!
519
- * misc bug fixes, a.o. support for Twenty Twelve theme, admin bar problem in WP3.5, data-uri breaking CSS file naming
520
-
521
- = 1.4 =
522
- * Add support for inline style tags with CSS media
523
- * Fix Wordpress top bar
524
-
525
- = 1.3 =
526
- * Add workaround for TinyMCEComments
527
- * Add workaround for asynchronous Google Analytics
528
-
529
- = 1.2 =
530
- * Add workaround for Chitika ads.
531
- * Add workaround for LinkWithin widget.
532
- * Belorussian translation
533
-
534
- = 1.1 =
535
- * Add workarounds for amazon and fastclick
536
- * Add workaround for Comment Form Quicktags
537
- * Fix issue with Vipers Video Quicktags
538
- * Fix a bug in where some scripts that shouldn't be moved were moved
539
- * Fix a bug in where the config page wouldn't appear
540
- * Fix @import handling
541
- * Implement an option to disable js/css gzipping
542
- * Implement CDN functionality
543
- * Implement data: URI generation for images
544
- * Support YUI CSS/JS Compressor
545
- * Performance increases
546
- * Handle WP Super Cache's cache files better
547
- * Update translations
548
-
549
- = 1.0 =
550
- * Add workaround for whos.among.us
551
- * Support preserving HTML Comments.
552
- * Implement "delayed cache compression"
553
- * French translation
554
- * Update Spanish translation
555
-
556
- = 0.9 =
557
- * Add workaround for networkedblogs.
558
- * Add workarounds for histats and statscounter
559
- * Add workaround for smowtion and infolinks.
560
- * Add workaround for Featured Content Gallery
561
- * Simplified Chinese translation
562
- * Update Spanish Translation
563
- * Modify the cache system so it uses wp-content/cache/
564
- * Add a clear cache button
565
-
566
- = 0.8 =
567
- * Add workaround for Vipers Video Quicktags
568
- * Support <link> tags without media.
569
- * Take even more precautions so we don't break urls in CSS
570
- * Support adding try-catch wrappings to JavaScript code
571
- * Add workaround for Wordpress.com Stats
572
- * Fix a bug in where the tags wouldn't move
573
- * Update translation template
574
- * Update Spanish translation
575
-
576
- = 0.7 =
577
- * Add fix for DISQUS Comment System.
578
-
579
- = 0.6 =
580
- * Add workaround for mybloglog, blogcatalog, tweetmeme and Google CSE
581
-
582
- = 0.5 =
583
- * Support localization
584
- * Fix the move and don't move system (again)
585
- * Improve url detection in CSS
586
- * Support looking for scripts and styles on just the header
587
- * Fix an issue with data: uris getting modified
588
- * Spanish translation
589
-
590
- = 0.4 =
591
- * Write plugin description in English
592
- * Set default config to everything off
593
- * Add link from plugins page to options page
594
- * Fix problems with scripts that shouldn't be moved and were moved all the same
595
-
596
- = 0.3 =
597
- * Disable CSS media on @imports - caused an infinite loop
598
-
599
- = 0.2 =
600
- * Support CSS media
601
- * Fix an issue in the IE Hacks preservation mechanism
602
- * Fix an issue with some urls getting broken in CSS
603
-
604
- = 0.1 =
605
- * First released version.
1
  === Autoptimize ===
2
+ Contributors: futtta, optimizingmatters, zytzagoo, turl
3
+ Tags: optimize, minify, performance, pagespeed, image optimization
4
  Donate link: http://blog.futtta.be/2013/10/21/do-not-donate-to-me/
5
  Requires at least: 4.0
6
  Tested up to: 4.9
7
+ Requires PHP: 5.3
8
+ Stable tag: 2.4.0
9
 
10
+ Autoptimize speeds up your website by optimizing JS, CSS, HTML, Google Fonts and images, async-ing JS, removing emoji cruft and more.
11
 
12
  == Description ==
13
 
14
+ Autoptimize makes optimizing your site really easy. It can aggregate, minify and cache scripts and styles, injects CSS in the page head by default (but can also defer), moves and defers scripts to the footer and minifies HTML. The "Extra" options allow you to optimize Google Fonts and images, async non-aggregated JavaScript, remove WordPress core emoji cruft and more. As such it can improve your site's performance even when already on HTTP/2! There is extensive API available to enable you to tailor Autoptimize to each and every site's specific needs.
15
 
16
  If you consider performance important, you really should use one of the many caching plugins to do page caching. Some good candidates to complement Autoptimize that way are e.g. [WP Super Cache](http://wordpress.org/plugins/wp-super-cache/), [HyperCache](http://wordpress.org/plugins/hyper-cache/), [Comet Cache](https://wordpress.org/plugins/comet-cache/) or [KeyCDN's Cache Enabler](https://wordpress.org/plugins/cache-enabler).
17
 
18
  > <strong>Premium Support</strong><br>
19
+ > We provide great [Autoptimize Pro Support and Web Performance Optimization services](https://autoptimize.com/), check out our offering on [https://autoptimize.com/](https://autoptimize.com/)!
20
 
21
  (Speed-surfing image under creative commons [by LL Twistiti](https://www.flickr.com/photos/twistiti/818552808/))
22
 
36
 
37
  = But I'm on HTTP/2, so I don't need Autoptimize? =
38
 
39
+ HTTP/2 is a great step forward for sure, reducing the impact of multiple requests from the same server significantly by using the same connection to perform several concurrent requests. That being said, [concatenation of CSS/ JS can still make a lot of sense](http://engineering.khanacademy.org/posts/js-packaging-http2.htm), as described in [this css-tricks.com article](https://css-tricks.com/http2-real-world-performance-test-analysis/) and this [blogpost from one of the Ebay engineers](http://calendar.perfplanet.com/2015/packaging-for-performance/). The conclusion; configure, test, reconfigure, retest, tweak and look what works best in your context. Maybe it's just HTTP/2, maybe it's HTTP/2 + aggregation and minification, maybe it's HTTP/2 + minification (which AO can do as well, simply untick the "aggregate JS-files" and/ or "aggregate CSS-files" options).
40
 
41
  = Will this work with my blog? =
42
 
135
 
136
  = Configuring & Troubleshooting Autoptimize =
137
 
138
+ After having installed and activated the plugin, you'll have access to an admin page where you can to enable HTML, CSS and JavaScript optimization. According to your liking, you can start of just enabling all of them, or if you're more cautious one at a time.
139
 
140
  If your blog doesn't function normally after having turned on Autoptimize, here are some pointers to identify & solve such issues using "advanced settings":
141
 
148
 
149
  = Help, I have a blank page or an internal server error after enabling Autoptimize!! =
150
 
151
+ Make sure you're not running other HTML, CSS or JS minification plugins (BWP minify, WP minify, ...) simultaneously with Autoptimize or disable that functionality your page caching plugin (W3 Total Cache, WP Fastest Cache, ...). Try enabling only CSS or only JS optimization to see which one causes the server error and follow the generic troubleshooting steps to find a workaround.
 
 
 
 
 
 
152
 
153
  = But I still have blank autoptimized CSS or JS-files! =
154
 
191
  = What is noptimize? =
192
 
193
  Starting with version 1.6.6 Autoptimize excludes everything inside noptimize tags, e.g.:
194
+ `&lt;!--noptimize-->&lt;script>alert('this will not get autoptimized');&lt;/script>&lt;!--/noptimize-->`
 
195
 
196
  You can do this in your page/ post content, in widgets and in your theme files (consider creating [a child theme](http://codex.wordpress.org/Child_Themes) to avoid your work being overwritten by theme updates).
197
 
198
  = Can I change the directory & filename of cached autoptimize files? =
199
 
200
+ Yes, if you want to serve files from e.g. /wp-content/resources/aggregated_12345.css instead of the default /wp-content/cache/autoptimize/autoptimize_12345.css, then add this to wp-config.php:
201
  `
202
  define('AUTOPTIMIZE_CACHE_CHILD_DIR','/resources/');
203
  define('AUTOPTIMIZE_CACHEFILE_PREFIX','aggregated_');
207
 
208
  Yes, but this is off by default. You can enable this by passing ´true´ to ´autoptimize_filter_cache_create_static_gzip´. You'll obviously still have to configure your webserver to use these files instead of the non-gzipped ones to avoid the overhead of on-the-fly compression.
209
 
210
+ = What does "remove emojis" do? =
211
 
212
  This new option in Autoptimize 2.3 removes the inline CSS, inline JS and linked JS-file added by WordPress core. As such is can have a small positive impact on your site's performance.
213
 
217
 
218
  = (How) should I optimize Google Fonts? =
219
 
220
+ Google Fonts are typically loaded by a "render blocking" linked CSS-file. If you have a theme and plugins that use Google Fonts, you might end up with multiple such CSS-files. Autoptimize (since version 2.3) now let's you lessen the impact of Google Fonts by either removing them alltogether or by optimizing the way they are loaded. There are two optimization-flavors; the first one is "combine and link", which replaces all requests for Google Fonts into one request, which will still be render-blocking but will allow the fonts to be loaded immediately (meaning you won't see fonts change while the page is loading). The alternative is "combine and load async" which uses JavaScript to load the fonts in a non-render blocking manner but which might cause a "flash of unstyled text". Starting form Autopitimize 2.4 "aggregate & preload" allows to aggregate all Google Font-files in one CSS-file that is preloaded, which should not be considered render blocking, but the fonts are available sooner (so less of a flash of unstyled text).
221
 
222
  = Should I use "preconnect" =
223
 
227
 
228
  JavaScript files that are not autoptimized (because they were excluded or because they are hosted elsewhere) are typically render-blocking. By adding them in the comma-separated "async JS" field, Autoptimize will add the async flag causing the browser to load those files asynchronously (i.e. non-render blocking). This can however break your site (page), e.g. if you async "js/jquery/jquery.js" you will very likely get "jQuery is not defined"-errors. Use with care.
229
 
230
+ = How does image optimization work? =
231
+
232
+ When image optimization is on, Autoptimize will look for png, gif, jpeg (.jpg) files in image tags and in your CSS files that are loaded from your own domain and change the src (source) to the ShortPixel CDN for those.
233
+
234
+ = Where can I get more info on image optimization? =
235
+
236
+ Have a look at [Shortpixel's FAQ](https://shortpixel.helpscoutdocs.com/category/60-shortpixel-ai-cdn).
237
+
238
+ = Can I disable the minification of excluded JS/ CSS? =
239
+
240
+ As from AO 2.4 excluded JS/ CSS are minified if the (filename indicates the) file is not minified yet. You can disable this with these filters;
241
+
242
+ `
243
+ add_filter('autoptimize_filter_js_minify_excluded','__return_false');
244
+ add_filter('autoptimize_filter_css_minify_excluded','__return_false');`
245
+
246
+ = Can I disable AO listening to page cache purges? =
247
+
248
+ As from AO 2.4 AO "listens" to page cache purges to clear its own cache. You can disable this behavior with this filter;
249
+
250
+ `
251
+ add_filter('autoptimize_filter_main_hookpagecachepurge','__return_false');`
252
+
253
+ = Why can't I upgrade from 2.3.4 to 2.4.0 (or higher)? =
254
+
255
+ Main reason (apart from occasional hickups that seem to be inherent to plugin upgrades) is that AO 2.4 requires you to be running PHP 5.3 or higher. And let's face it; you should actually be running PHP 7.x if you value performance (and security and support), no?
256
+
257
  = Where can I get help? =
258
 
259
  You can get help on the [wordpress.org support forum](http://wordpress.org/support/plugin/autoptimize). If you are 100% sure this your problem cannot be solved using Autoptimize configuration and that you in fact discovered a bug in the code, you can [create an issue on GitHub](https://github.com/futtta/autoptimize/issues). If you're looking for premium support, check out our [Autoptimize Pro Support and Web Performance Optimization services](http://autoptimize.com/).
270
 
271
  == Changelog ==
272
 
273
+ = 2.4.0 =
274
+ * refactored significantly (no more "classlesses", all is OO), classes are autoloaded, tests added (travis-ci) by zytzagoo who deserves praise for his hard work!
275
+ * new: image optimization (see "Extra"-tab) using Shortpixel's smart image optimization proxy
276
+ * new: you can now disable JS/ CSS-files being aggregated, having them minified individually instead
277
+ * new: Google Fonts can now be "aggregated & preloaded", this uses CSS which is loaded non render-blocking
278
+ * new: Autoptimize "listens" to page caches being cleared, upon which it purges it's own cache as well. Support depends on known action hooks firing by the page cache, supported by Hyper Cache, WP Rocket, W3 Total Cache, KeyCDN Cache Enabler, Swift, WP Super Cache, WP Fastest Cache and Comet Cache.
279
+ * new: local JS/ CSS-files that are excluded from optimization are minified by default (can be overridden by filter)
280
+ * improvement: updated minifiers (with very significant improvements for YUI CSS compressor PHP port)
281
+ * improvement: "remove all Google Fonts" is now more careful (avoiding removing entire CSS blocks)
282
+ * misc. under the hood improvements (e.g. more robust cache clearing, better support for multibyte character sets, improved CDN rewrite logic, avoid PHP warnings when writing files to cache, ...)
283
+
284
  = 2.3.4 =
285
  * bugfix: is_plugin_active causing errors in some cases as [reported by @iluminancia and @lozula](https://wordpress.org/support/topic/fatal-error-after-update-to-2-3-3/)
286
+ * bugfix: added language domain to 4 __/_e functions, un grand merci à Guillaume Blet!
287
 
288
  = 2.3.3 =
289
  * improvement: updated to latest version of Filamentgroup's loadCSS
302
  * fix for issue with update-code in some circumstances, thanks to [Rajendra Zore](https://rajendrazore.com/) to report & help fix!
303
 
304
  = 2.3.0 =
305
+ * new: optimize Google fonts with "combine & link" and "combine and load async" (with webload.js), intelligently preconnecting to Google's domains to limit performance impact even further
306
  * new: Async JS, can be applied to local or 3rd party JS (if local it will be auto-excluded from autoptimization)
307
  * new: support to tell browsers to preconnect (= dns lookup + tcp/ip connection + ssl negotiation) to 3rd party domains (depends on browser support, works in Chrome & Firefox)
308
+ * new: remove WordPress' core's emoji CSS & JS
309
  * new: remove (version parameter from) Querystring
310
  * new: support to clear cache through WP CLI thanks to [junaidbhura](https://junaidbhura.com)
311
  * lots of [bugfixes and small improvements done by some seriously smart people via GitHub](https://github.com/futtta/autoptimize/commits/master) (thanks all!!), including [a fix for AO 2.2 which saw the HTML minifier go PacMan on spaces](https://github.com/futtta/autoptimize/commit/0f6ac683c35bc82d1ac2d496ae3b66bb53e49f88) in some circumstances.
316
  * readme update
317
 
318
  = 2.2.1 =
319
+ * fix for images being referenced in CSS not all being translated to correct path, leading to 404's as reported by Jeff Inho
320
  * fix for "[] operator not supported for strings" error in PHP7.1 as reported by falk-wussow.de
321
  * fix for security hash busting AO's cache in some cases (esp. in 2.1.1)
322
 
324
  * new: Autoptimize minifies first (caching the individual snippets) and aggregrates the minified snippets, resulting in huge performance improvements for uncached JS/ CSS.
325
  * new: option to enable/ disable AO for logged in users (on by default)
326
  * new: option to enable/ disable AO on WooCommerce, Easy Digital Downloads or WP eCommerce cart/ checkout page (on by default)
327
+ * improvement: switched to [rel=preload + Filamentgroup's loadCSS for CSS deferring](http://blog.futtta.be/2017/02/24/autoptimize-css-defer-switching-to-loadcss-soon/)
328
  * improvement: switched to YUI CSS minifier PHP-port 2.8.4-p10 (so not to the 3.x branch yet)
329
  * improvements to the logic of which JS/ CSS can be optimized (getPath function) increasing reliability of the aggregation process
330
  * security: made placeholder replacement less naive to protect against XSS and LFI vulnerability as reported by Matthew Barry and fixed with great help from Matthew and Tomas Trkulja. Thanks guys!!
341
 
342
  = 2.1.0 =
343
  * new: Autoptimize now appears in admin-toolbar with an easy view on cache size and the possibility to purge the cache (pass `false` to `autoptimize_filter_toolbar_show` filter to disable), a big thanks to [Pablo Custo](https://github.com/pablocusto) for his hard work on this nice feature!
344
+ * new: An extra "More Optimization"-tab is shown (can be hidden with ´autoptimize_filter_show_partner_tabs´-filter) with information about related optimization tools- and services.
345
  * new: If cache size becomes too big, a mail will be sent to the site admin (pass `false` to `autoptimize_filter_cachecheck_sendmail` filter to disable or pass alternative email to the `autoptimize_filter_cachecheck_mailto` filter to change email-address)
346
  * new: power-users can enable Autoptimize to pre-gzip the autoptimized files by passing `true` to `autoptimize_filter_cache_create_static_gzip`, kudo's to (Draikin)[https://github.com/Draikin] for this!
347
  * improvement: admin GUI updated (again; thanks Pablo!) with some responsiveness added in the mix (not showing the right hand column on smaller screen-sizes)
379
  * lots of small and bigger bugfixes, I won't bother you with a full list but have a look at [the commmit log on GitHub](https://github.com/futtta/autoptimize/commits/master).
380
  * tested and confirmed working with PHP7
381
 
382
+ = older =
383
+ * see [https://plugins.svn.wordpress.org/autoptimize/tags/2.3.4/readme.txt](https://plugins.svn.wordpress.org/autoptimize/tags/2.3.4/readme.txt)