PluginProbe
Autoptimize / 3.0.3
Autoptimize v3.0.3
2.2.2 2.3.0 2.3.1 2.3.2 2.3.3 2.3.4 2.4.0 2.4.1 2.4.2 2.4.3 2.4.4 2.5.0 2.5.1 2.6.0 2.6.1 2.6.2 2.7.0 2.7.1 2.7.2 2.7.3 2.7.4 2.7.5 2.7.6 2.7.7 2.7.8 All 107 releases
autoptimize / classes / autoptimizeCriticalCSSSettingsAjax.php

autoptimizeCriticalCSSSettingsAjax.php in Autoptimize 3.0.3, at classes/autoptimizeCriticalCSSSettingsAjax.php

539 lines 24.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Critical CSS settings AJAX logic.
4 */
5
6 if ( ! defined( 'ABSPATH' ) ) {
7 exit;
8 }
9
10 class autoptimizeCriticalCSSSettingsAjax {
11 public function __construct() {
12 $this->criticalcss = autoptimize()->criticalcss();
13 $this->run();
14 }
15
16 public function run() {
17 // add filters.
18 add_action( 'wp_ajax_fetch_critcss', array( $this, 'critcss_fetch_callback' ) );
19 add_action( 'wp_ajax_save_critcss', array( $this, 'critcss_save_callback' ) );
20 add_action( 'wp_ajax_rm_critcss', array( $this, 'critcss_rm_callback' ) );
21 add_action( 'wp_ajax_rm_critcss_all', array( $this, 'critcss_rm_all_callback' ) );
22 add_action( 'wp_ajax_ao_ccss_export', array( $this, 'ao_ccss_export_callback' ) );
23 add_action( 'wp_ajax_ao_ccss_import', array( $this, 'ao_ccss_import_callback' ) );
24 add_action( 'wp_ajax_ao_ccss_queuerunner', array( $this, 'ao_ccss_queuerunner_callback' ) );
25 add_action( 'wp_ajax_ao_ccss_saverules', array( $this, 'ao_ccss_saverules_callback' ) );
26 }
27
28 public function critcss_fetch_callback() {
29 // Ajax handler to obtain a critical CSS file from the filesystem.
30 // Check referer.
31 check_ajax_referer( 'fetch_critcss_nonce', 'critcss_fetch_nonce' );
32
33 // Initialize error flag.
34 $error = true;
35
36 // Allow no content for MANUAL rules (as they may not exist just yet).
37 if ( current_user_can( 'manage_options' ) && empty( $_POST['critcssfile'] ) ) {
38 $content = '';
39 $error = false;
40 } elseif ( current_user_can( 'manage_options' ) && $this->critcss_check_filename( $_POST['critcssfile'] ) ) {
41 // Or check user permissios and filename.
42 // Set file path and obtain its content.
43 $critcssfile = AO_CCSS_DIR . strip_tags( $_POST['critcssfile'] );
44 if ( file_exists( $critcssfile ) ) {
45 $content = file_get_contents( $critcssfile );
46 $error = false;
47 }
48 }
49
50 // Prepare response.
51 if ( $error ) {
52 $response['code'] = '500';
53 $response['string'] = 'Error reading file ' . $critcssfile . '.';
54 } else {
55 $response['code'] = '200';
56 $response['string'] = $content;
57 }
58
59 // Dispatch respose.
60 echo json_encode( $response );
61
62 // Close ajax request.
63 wp_die();
64 }
65
66 public function critcss_save_callback() {
67 $error = false;
68 $status = false;
69 $response = array();
70
71 // Ajax handler to write a critical CSS to the filesystem
72 // Check referer.
73 check_ajax_referer( 'save_critcss_nonce', 'critcss_save_nonce' );
74
75 // Allow empty contents for MANUAL rules (as they are fetched later).
76 if ( current_user_can( 'manage_options' ) && empty( $_POST['critcssfile'] ) ) {
77 $critcssfile = false;
78 $status = true;
79 } elseif ( current_user_can( 'manage_options' ) && $this->critcss_check_filename( $_POST['critcssfile'] ) ) {
80 // Or check user permissios and filename
81 // Set critical CSS content.
82 $critcsscontents = stripslashes( $_POST['critcsscontents'] );
83
84 // If there is content and it's valid, write the file.
85 if ( $critcsscontents && $this->criticalcss->check_contents( $critcsscontents ) ) {
86 // Set file path and status.
87 $critcssfile = AO_CCSS_DIR . strip_tags( $_POST['critcssfile'] );
88 $status = file_put_contents( $critcssfile, $critcsscontents, LOCK_EX );
89 // Or set as error.
90 } else {
91 $error = true;
92 }
93 // Or just set an error.
94 } else {
95 $error = true;
96 }
97
98 // Prepare response.
99 if ( ! $status || $error ) {
100 $response['code'] = '500';
101 $response['string'] = 'Error saving file ' . $critcssfile . '.';
102 } else {
103 $response['code'] = '200';
104 if ( $critcssfile ) {
105 $response['string'] = 'File ' . $critcssfile . ' saved.';
106 } else {
107 $response['string'] = 'Empty content do not need to be saved.';
108 }
109 }
110
111 // Dispatch respose.
112 echo json_encode( $response );
113
114 // Close ajax request.
115 wp_die();
116 }
117
118
119 public function critcss_rm_callback() {
120 // Ajax handler to delete a critical CSS from the filesystem
121 // Check referer.
122 check_ajax_referer( 'rm_critcss_nonce', 'critcss_rm_nonce' );
123
124 // Initialize error and status flags.
125 $error = true;
126 $status = false;
127
128 // Allow no file for MANUAL rules (as they may not exist just yet).
129 if ( current_user_can( 'manage_options' ) && empty( $_POST['critcssfile'] ) ) {
130 $error = false;
131 } elseif ( current_user_can( 'manage_options' ) && $this->critcss_check_filename( $_POST['critcssfile'] ) ) {
132 // Or check user permissios and filename
133 // Set file path and delete it.
134 $critcssfile = AO_CCSS_DIR . strip_tags( $_POST['critcssfile'] );
135 if ( file_exists( $critcssfile ) ) {
136 $status = unlink( $critcssfile );
137 $error = false;
138 }
139 }
140
141 // Prepare response.
142 if ( $error ) {
143 $response['code'] = '500';
144 $response['string'] = 'Error removing file ' . $critcssfile . '.';
145 } else {
146 $response['code'] = '200';
147 if ( $status ) {
148 $response['string'] = 'File ' . $critcssfile . ' removed.';
149 } else {
150 $response['string'] = 'No file to be removed.';
151 }
152 }
153
154 // Dispatch respose.
155 echo json_encode( $response );
156
157 // Close ajax request.
158 wp_die();
159 }
160
161 public function critcss_rm_all_callback() {
162 // Ajax handler to delete a critical CSS from the filesystem
163 // Check referer.
164 check_ajax_referer( 'rm_critcss_all_nonce', 'critcss_rm_all_nonce' );
165
166 // Initialize error and status flags.
167 $error = true;
168 $status = false;
169
170 // Remove all ccss files on filesystem.
171 if ( current_user_can( 'manage_options' ) ) {
172 if ( file_exists( AO_CCSS_DIR ) && is_dir( AO_CCSS_DIR ) ) {
173 array_map( 'unlink', glob( AO_CCSS_DIR . 'ccss_*.css', GLOB_BRACE ) );
174 $error = false;
175 $status = true;
176 }
177 }
178
179 // Prepare response.
180 if ( $error ) {
181 $response['code'] = '500';
182 $response['string'] = 'Error removing all critical CSS files.';
183 } else {
184 $response['code'] = '200';
185 if ( $status ) {
186 $response['string'] = 'Critical CSS Files removed.';
187 } else {
188 $response['string'] = 'No file removed.';
189 }
190 }
191
192 // Dispatch respose.
193 echo json_encode( $response );
194
195 // Close ajax request.
196 wp_die();
197 }
198
199 public function ao_ccss_export_callback() {
200 // Ajax handler export settings
201 // Check referer.
202 check_ajax_referer( 'ao_ccss_export_nonce', 'ao_ccss_export_nonce' );
203
204 if ( ! class_exists( 'ZipArchive' ) ) {
205 $response['code'] = '500';
206 $response['msg'] = 'PHP ZipArchive not present, cannot create zipfile';
207 echo json_encode( $response );
208 wp_die();
209 }
210
211 // Init array, get options and prepare the raw object.
212 $settings = array();
213
214 // CCSS settings.
215 $settings['ccss']['rules'] = get_option( 'autoptimize_ccss_rules' );
216 $settings['ccss']['additional'] = get_option( 'autoptimize_ccss_additional' );
217 $settings['ccss']['viewport'] = get_option( 'autoptimize_ccss_viewport' );
218 $settings['ccss']['finclude'] = get_option( 'autoptimize_ccss_finclude' );
219 $settings['ccss']['rtimelimit'] = get_option( 'autoptimize_ccss_rtimelimit' );
220 $settings['ccss']['noptimize'] = get_option( 'autoptimize_ccss_noptimize' );
221 $settings['ccss']['debug'] = get_option( 'autoptimize_ccss_debug' );
222 $settings['ccss']['key'] = get_option( 'autoptimize_ccss_key' );
223 $settings['ccss']['deferjquery'] = get_option( 'autoptimize_ccss_deferjquery' );
224 $settings['ccss']['domain'] = get_option( 'autoptimize_ccss_domain' );
225 $settings['ccss']['forcepath'] = get_option( 'autoptimize_ccss_forcepath' );
226 $settings['ccss']['loggedin'] = get_option( 'autoptimize_ccss_loggedin' );
227 $settings['ccss']['rlimit'] = get_option( 'autoptimize_ccss_rlimit' );
228 $settings['ccss']['unloadccss'] = get_option( 'autoptimize_ccss_unloadccss' );
229
230 // JS settings.
231 $settings['js']['root'] = get_option( 'autoptimize_js' );
232 $settings['js']['aggregate'] = get_option( 'autoptimize_js_aggregate' );
233 $settings['js']['defer_not_aggregate'] = get_option( 'autoptimize_js_defer_not_aggregate' );
234 $settings['js']['defer_inline'] = get_option( 'autoptimize_js_defer_inline' );
235 $settings['js']['exclude'] = get_option( 'autoptimize_js_exclude' );
236 $settings['js']['forcehead'] = get_option( 'autoptimize_js_forcehead' );
237 $settings['js']['justhead'] = get_option( 'autoptimize_js_justhead' );
238 $settings['js']['trycatch'] = get_option( 'autoptimize_js_trycatch' );
239 $settings['js']['include_inline'] = get_option( 'autoptimize_js_include_inline');
240
241 // CSS settings.
242 $settings['css']['root'] = get_option( 'autoptimize_css' );
243 $settings['css']['aggregate'] = get_option( 'autoptimize_css_aggregate' );
244 $settings['css']['datauris'] = get_option( 'autoptimize_css_datauris' );
245 $settings['css']['justhead'] = get_option( 'autoptimize_css_justhead' );
246 $settings['css']['defer'] = get_option( 'autoptimize_css_defer' );
247 $settings['css']['defer_inline'] = get_option( 'autoptimize_css_defer_inline' );
248 $settings['css']['inline'] = get_option( 'autoptimize_css_inline' );
249 $settings['css']['exclude'] = get_option( 'autoptimize_css_exclude' );
250 $settings['css']['include_inline'] = get_option( 'autoptimize_css_include_inline' );
251
252 // Others.
253 $settings['other']['autoptimize_imgopt_settings'] = get_option( 'autoptimize_imgopt_settings' );
254 $settings['other']['autoptimize_extra_settings'] = get_option( 'autoptimize_extra_settings' );
255 $settings['other']['autoptimize_cache_fallback'] = get_option( 'autoptimize_cache_fallback' );
256 $settings['other']['autoptimize_cache_nogzip'] = get_option( 'autoptimize_cache_nogzip' );
257 $settings['other']['autoptimize_cdn_url'] = get_option( 'autoptimize_cdn_url' );
258 $settings['other']['autoptimize_enable_meta_ao_settings'] = get_option( 'autoptimize_enable_meta_ao_settings' );
259 $settings['other']['autoptimize_enable_site_config'] = get_option( 'autoptimize_enable_site_config' );
260 $settings['other']['autoptimize_html'] = get_option( 'autoptimize_html' );
261 $settings['other']['autoptimize_html_keepcomments'] = get_option( 'autoptimize_html_keepcomments' );
262 $settings['other']['autoptimize_minify_excluded'] = get_option( 'autoptimize_minify_excluded' );
263 $settings['other']['autoptimize_optimize_checkout'] = get_option( 'autoptimize_optimize_checkout' );
264 $settings['other']['autoptimize_optimize_logged'] = get_option( 'autoptimize_optimize_logged' );
265
266 if ( defined( 'AO_PRO_VERSION' ) ) {
267 $settings['pro']['boosters'] = get_option( 'autoptimize_pro_boosters' );
268 $settings['pro']['pagecache'] = get_option( 'autoptimize_pro_pagecache' );
269 }
270
271 // Initialize error flag.
272 $error = true;
273
274 // Check user permissions.
275 if ( current_user_can( 'manage_options' ) ) {
276 // Prepare settings file path and content.
277 $exportfile = AO_CCSS_DIR . 'settings.json';
278 $contents = json_encode( $settings );
279 $status = file_put_contents( $exportfile, $contents, LOCK_EX );
280 $error = false;
281 }
282
283 // Prepare archive.
284 $zipfile = AO_CCSS_DIR . str_replace( array('.', '/'), '_', parse_url( AUTOPTIMIZE_WP_SITE_URL, PHP_URL_HOST ) ) . '_' . date( 'Ymd-H\hi' ) . '_ao_ccss_settings.zip';
285 $file = pathinfo( $zipfile, PATHINFO_BASENAME );
286 $zip = new ZipArchive();
287 $ret = $zip->open( $zipfile, ZipArchive::CREATE );
288 if ( true !== $ret ) {
289 $error = true;
290 } else {
291 $zip->addFile( AO_CCSS_DIR . 'settings.json', 'settings.json' );
292 if ( file_exists( AO_CCSS_DIR . 'queue.json' ) ) {
293 $zip->addFile( AO_CCSS_DIR . 'queue.json', 'queue.json' );
294 }
295 $options = array(
296 'add_path' => './',
297 'remove_all_path' => true,
298 );
299 $zip->addGlob( AO_CCSS_DIR . '*.css', 0, $options );
300 $zip->close();
301 }
302
303 // Prepare response.
304 if ( ! $status || $error ) {
305 $response['code'] = '500';
306 $response['msg'] = 'Error saving file ' . $file . ', code: ' . $ret;
307 } else {
308 $response['code'] = '200';
309 $response['msg'] = 'File ' . $file . ' saved.';
310 $response['file'] = $file;
311 }
312
313 // Dispatch respose.
314 echo json_encode( $response );
315
316 // Close ajax request.
317 wp_die();
318 }
319
320 public function ao_ccss_import_callback() {
321 // Ajax handler import settings
322 // Check referer.
323 check_ajax_referer( 'ao_ccss_import_nonce', 'ao_ccss_import_nonce' );
324
325 // Initialize error flag.
326 $error = false;
327
328 // Process an uploaded file with no errors.
329 if ( current_user_can( 'manage_options' ) && ! $_FILES['file']['error'] && $_FILES['file']['size'] < 500001 && strpos( $_FILES['file']['name'], '.zip' ) === strlen( $_FILES['file']['name'] ) - 4 ) {
330 // create tmp dir with hard guess name in AO_CCSS_DIR.
331 $_secret_dir = wp_hash( uniqid( md5( AUTOPTIMIZE_CACHE_URL ), true ) );
332 $_import_tmp_dir = trailingslashit( AO_CCSS_DIR . $_secret_dir );
333 mkdir( $_import_tmp_dir, 0774, true );
334
335 // Save file to that tmp directory but give it our own name to prevent directory traversal risks when using original name.
336 $zipfile = $_import_tmp_dir . uniqid( 'import_settings-', true ) . '.zip';
337 move_uploaded_file( $_FILES['file']['tmp_name'], $zipfile );
338
339 // Extract archive in the tmp directory.
340 $zip = new ZipArchive;
341 if ( $zip->open( $zipfile ) === true ) {
342 // loop through all files in the zipfile.
343 for ($i = 0; $i < $zip->numFiles; $i++) {
344 // but only extract known good files.
345 if ( preg_match('/^settings\.json$|^\.\/ccss_[a-z0-9]{32}\.css$/', $zip->getNameIndex( $i ) ) > 0 ) {
346 $zip->extractTo( AO_CCSS_DIR, $zip->getNameIndex( $i ) );
347 }
348 }
349 $zip->close();
350 } else {
351 $error = 'could not extract';
352 }
353
354 // and remove temp. dir with all contents (the import-zipfile).
355 $this->rrmdir( $_import_tmp_dir );
356
357 if ( ! $error ) {
358 // Archive extraction ok, continue importing settings from AO_CCSS_DIR.
359 // Settings file.
360 $importfile = AO_CCSS_DIR . 'settings.json';
361
362 if ( file_exists( $importfile ) ) {
363 // Get settings and turn them into an object.
364 $settings = json_decode( file_get_contents( $importfile ), true );
365
366 // Update options from settings, but only for known options.
367 // CCSS.
368 foreach ( array( 'rules', 'additional', 'viewport', 'finclude', 'rtimelimit', 'noptimize', 'debug', 'key', 'deferjquery', 'domain', 'forcepath', 'loggedin', 'rlimit', 'unloadccss' ) as $ccss_setting ) {
369 if ( false === array_key_exists( 'ccss', $settings ) || false === array_key_exists( $ccss_setting, $settings['ccss'] ) ) {
370 continue;
371 } else {
372 update_option( 'autoptimize_ccss_' . $ccss_setting, $settings['ccss'][$ccss_setting] );
373 }
374 }
375
376 // JS.
377 foreach ( array( 'root', 'aggregate', 'defer_not_aggregate', 'defer_inline', 'exclude', 'forcehead', 'trycatch', 'include_inline' ) as $js_setting ) {
378 if ( false === array_key_exists( 'js', $settings ) || false === array_key_exists( $js_setting, $settings['js'] ) ) {
379 continue;
380 } else if ( 'root' === $js_setting ) {
381 update_option( 'autoptimize_js', $settings['js']['root'] );
382 } else {
383 update_option( 'autoptimize_js_' . $js_setting, $settings['js'][$js_setting] );
384 }
385 }
386
387 // CSS.
388 foreach ( array( 'root', 'aggregate', 'datauris', 'justhead', 'defer', 'defer_inline', 'inline', 'exclude', 'include_inline' ) as $css_setting ) {
389 if ( false === array_key_exists( 'css', $settings ) || false === array_key_exists( $css_setting, $settings['css'] ) ) {
390 continue;
391 } else if ( 'root' === $css_setting ) {
392 update_option( 'autoptimize_css', $settings['css']['root'] );
393 } else {
394 update_option( 'autoptimize_css_' . $css_setting, $settings['css'][$css_setting] );
395 }
396 }
397
398 // Other.
399 foreach ( array( 'autoptimize_imgopt_settings', 'autoptimize_extra_settings', 'autoptimize_cache_fallback', 'autoptimize_cache_nogzip', 'autoptimize_cdn_url', 'autoptimize_enable_meta_ao_settings', 'autoptimize_enable_site_config', 'autoptimize_html', 'autoptimize_html_keepcomments', 'autoptimize_minify_excluded', 'autoptimize_optimize_checkout', 'autoptimize_optimize_logged' ) as $other_setting ) {
400 if ( false === array_key_exists( 'other', $settings ) || false === array_key_exists( $other_setting, $settings['other'] ) ) {
401 continue;
402 } else {
403 update_option( $other_setting, $settings['other'][$other_setting] );
404 }
405 }
406
407 // AO Pro.
408 if ( defined( 'AO_PRO_VERSION' ) && array_key_exists( 'pro', $settings ) ) {
409 update_option( 'autoptimize_pro_boosters', $settings['pro']['boosters'] );
410 update_option( 'autoptimize_pro_pagecache', $settings['pro']['pagecache'] );
411 }
412 } else {
413 // Settings file doesn't exist, update error flag.
414 $error = 'settings file does not exist';
415 }
416 }
417 } else {
418 $error = 'file could not be saved';
419 }
420
421 // Prepare response.
422 if ( $error ) {
423 $response['code'] = '500';
424 $response['msg'] = 'Error importing settings: ' . $error;
425 } else {
426 $response['code'] = '200';
427 $response['msg'] = 'Settings imported successfully';
428 }
429
430 // Dispatch respose.
431 echo json_encode( $response );
432
433 // Close ajax request.
434 wp_die();
435 }
436
437 public function ao_ccss_queuerunner_callback() {
438 check_ajax_referer( 'ao_ccss_queuerunner_nonce', 'ao_ccss_queuerunner_nonce' );
439
440 // Process an uploaded file with no errors.
441 if ( current_user_can( 'manage_options' ) ) {
442 if ( ! file_exists( AO_CCSS_LOCK ) ) {
443 $ccss_cron = new autoptimizeCriticalCSSCron();
444 $ccss_cron->ao_ccss_queue_control();
445 $response['code'] = '200';
446 $response['msg'] = 'Queue processing done';
447 } else {
448 $response['code'] = '302';
449 $response['msg'] = 'Lock file found';
450 }
451 } else {
452 $response['code'] = '500';
453 $response['msg'] = 'Not allowed';
454 }
455
456 // Dispatch respose.
457 echo json_encode( $response );
458
459 // Close ajax request.
460 wp_die();
461 }
462
463 public function ao_ccss_saverules_callback() {
464 check_ajax_referer( 'ao_ccss_saverules_nonce', 'ao_ccss_saverules_nonce' );
465
466 // save rules over AJAX, too many users forget to press "save changes".
467 if ( current_user_can( 'manage_options' ) ) {
468 if ( array_key_exists( 'critcssrules', $_POST ) ) {
469 $rules = stripslashes( $_POST['critcssrules'] ); // ugly, but seems correct as per https://developer.wordpress.org/reference/functions/stripslashes_deep/#comment-1045
470 if ( ! empty( $rules ) ) {
471 $_unsafe_rules_array = json_decode( wp_strip_all_tags( $rules ), true );
472 if ( ! empty( $_unsafe_rules_array ) && is_array( $_unsafe_rules_array ) ) {
473 $_safe_rules_array = array();
474 if ( array_key_exists( 'paths', $_unsafe_rules_array ) ) {
475 $_safe_rules_array['paths'] = $_unsafe_rules_array['paths'];
476 }
477 if ( array_key_exists( 'types', $_unsafe_rules_array ) ) {
478 $_safe_rules_array['types'] = $_unsafe_rules_array['types'];
479 }
480 $_safe_rules = json_encode( $_safe_rules_array, JSON_FORCE_OBJECT );
481 if ( ! empty( $_safe_rules ) ) {
482 update_option( 'autoptimize_ccss_rules', $_safe_rules );
483 $response['code'] = '200';
484 $response['msg'] = 'Rules saved';
485 } else {
486 $_error = 'Could not auto-save rules (safe rules empty)';
487 }
488 } else {
489 $_error = 'Could not auto-save rules (rules could not be json_decoded)';
490 }
491 } else {
492 $_error = 'Could not auto-save rules (rules empty)';
493 }
494 } else {
495 $_error = 'Could not auto-save rules (rules not in $_POST)';
496 }
497 } else {
498 $_error = 'Not allowed';
499 }
500
501 if ( ! isset( $response ) && $_error ) {
502 $response['code'] = '500';
503 $response['msg'] = $_error;
504 }
505
506 // Dispatch respose.
507 echo json_encode( $response );
508
509 // Close ajax request.
510 wp_die();
511 }
512
513 public function critcss_check_filename( $filename ) {
514 // Try to avoid directory traversal when reading/writing/deleting critical CSS files.
515 if ( strpos( $filename, 'ccss_' ) !== 0 ) {
516 return false;
517 } elseif ( substr( $filename, -4, 4 ) !== '.css' ) {
518 return false;
519 } elseif ( sanitize_file_name( $filename ) !== $filename ) {
520 // Use WordPress core's sanitize_file_name to see if anything fishy is going on.
521 return false;
522 } else {
523 return true;
524 }
525 }
526
527 public function rrmdir( $path ) {
528 // recursively remove a directory as found on
529 // https://andy-carter.com/blog/recursively-remove-a-directory-in-php.
530 $files = glob($path . '/*');
531 foreach ( $files as $file ) {
532 is_dir( $file ) ? $this->rrmdir( $file ) : unlink( $file );
533 }
534 rmdir( $path );
535
536 return;
537 }
538 }
539