1
|
|
|
<?php |
2
|
|
|
/** |
3
|
|
|
* Critical CSS settings AJAX logic. |
4
|
|
|
*/ |
5
|
|
|
|
6
|
|
|
if ( ! defined( 'ABSPATH' ) ) { |
7
|
|
|
exit; |
8
|
|
|
} |
9
|
|
|
|
10
|
|
|
class autoptimizeCriticalCSSSettingsAjax { |
11
|
|
View Code Duplication |
public function __construct() |
|
|
|
|
12
|
|
|
{ |
13
|
|
|
// fetch all options at once and populate them individually explicitely as globals. |
14
|
|
|
$all_options = autoptimizeCriticalCSSBase::fetch_options(); |
|
|
|
|
15
|
|
|
foreach ( $all_options as $_option => $_value ) { |
|
|
|
|
16
|
|
|
global ${$_option}; |
17
|
|
|
${$_option} = $_value; |
18
|
|
|
} |
19
|
|
|
$this->run(); |
20
|
|
|
} |
21
|
|
|
|
22
|
|
|
public function run() { |
23
|
|
|
// add filters. |
24
|
|
|
add_action( 'wp_ajax_fetch_critcss', array( $this, 'critcss_fetch_callback' ) ); |
25
|
|
|
add_action( 'wp_ajax_save_critcss', array( $this, 'critcss_save_callback' ) ); |
26
|
|
|
add_action( 'wp_ajax_rm_critcss', array( $this, 'critcss_rm_callback' ) ); |
27
|
|
|
add_action( 'wp_ajax_rm_critcss_all', array( $this, 'critcss_rm_all_callback' ) ); |
28
|
|
|
add_action( 'wp_ajax_ao_ccss_export', array( $this, 'ao_ccss_export_callback' ) ); |
29
|
|
|
add_action( 'wp_ajax_ao_ccss_import', array( $this, 'ao_ccss_import_callback' ) ); |
30
|
|
|
} |
31
|
|
|
|
32
|
|
|
public function critcss_fetch_callback() { |
33
|
|
|
// Ajax handler to obtain a critical CSS file from the filesystem. |
34
|
|
|
// Check referer. |
35
|
|
|
check_ajax_referer( 'fetch_critcss_nonce', 'critcss_fetch_nonce' ); |
36
|
|
|
|
37
|
|
|
// Initialize error flag. |
38
|
|
|
$error = true; |
39
|
|
|
|
40
|
|
|
// Allow no content for MANUAL rules (as they may not exist just yet). |
41
|
|
View Code Duplication |
if ( current_user_can( 'manage_options' ) && empty( $_POST['critcssfile'] ) ) { |
|
|
|
|
42
|
|
|
$content = ''; |
43
|
|
|
$error = false; |
44
|
|
|
} elseif ( current_user_can( 'manage_options' ) && $this->critcss_check_filename( $_POST['critcssfile'] ) ) { |
45
|
|
|
// Or check user permissios and filename. |
46
|
|
|
// Set file path and obtain its content. |
47
|
|
|
$critcssfile = AO_CCSS_DIR . strip_tags( $_POST['critcssfile'] ); |
48
|
|
|
if ( file_exists( $critcssfile ) ) { |
49
|
|
|
$content = file_get_contents( $critcssfile ); |
50
|
|
|
$error = false; |
51
|
|
|
} |
52
|
|
|
} |
53
|
|
|
|
54
|
|
|
// Prepare response. |
55
|
|
|
if ( $error ) { |
56
|
|
|
$response['code'] = '500'; |
|
|
|
|
57
|
|
|
$response['string'] = 'Error reading file ' . $critcssfile . '.'; |
|
|
|
|
58
|
|
|
} else { |
59
|
|
|
$response['code'] = '200'; |
|
|
|
|
60
|
|
|
$response['string'] = $content; |
|
|
|
|
61
|
|
|
} |
62
|
|
|
|
63
|
|
|
// Dispatch respose. |
64
|
|
|
echo json_encode( $response ); |
65
|
|
|
|
66
|
|
|
// Close ajax request. |
67
|
|
|
wp_die(); |
68
|
|
|
} |
69
|
|
|
|
70
|
|
|
public function critcss_save_callback() { |
71
|
|
|
$error = false; |
72
|
|
|
$status = false; |
73
|
|
|
$response = array(); |
74
|
|
|
|
75
|
|
|
// Ajax handler to write a critical CSS to the filesystem |
76
|
|
|
// Check referer. |
77
|
|
|
check_ajax_referer( 'save_critcss_nonce', 'critcss_save_nonce' ); |
78
|
|
|
|
79
|
|
|
// Allow empty contents for MANUAL rules (as they are fetched later). |
80
|
|
|
if ( current_user_can( 'manage_options' ) && empty( $_POST['critcssfile'] ) ) { |
81
|
|
|
$critcssfile = false; |
82
|
|
|
$status = true; |
83
|
|
|
} elseif ( current_user_can( 'manage_options' ) && $this->critcss_check_filename( $_POST['critcssfile'] ) ) { |
84
|
|
|
// Or check user permissios and filename |
85
|
|
|
// Set critical CSS content. |
86
|
|
|
$critcsscontents = stripslashes( $_POST['critcsscontents'] ); |
87
|
|
|
|
88
|
|
|
// If there is content and it's valid, write the file. |
89
|
|
|
if ( $critcsscontents && autoptimizeCriticalCSSCore::ao_ccss_check_contents( $critcsscontents ) ) { |
90
|
|
|
// Set file path and status. |
91
|
|
|
$critcssfile = AO_CCSS_DIR . strip_tags( $_POST['critcssfile'] ); |
92
|
|
|
$status = file_put_contents( $critcssfile, $critcsscontents, LOCK_EX ); |
93
|
|
|
// Or set as error. |
94
|
|
|
} else { |
95
|
|
|
$error = true; |
96
|
|
|
} |
97
|
|
|
// Or just set an error. |
98
|
|
|
} else { |
99
|
|
|
$error = true; |
100
|
|
|
} |
101
|
|
|
|
102
|
|
|
// Prepare response. |
103
|
|
View Code Duplication |
if ( ! $status || $error ) { |
|
|
|
|
104
|
|
|
$response['code'] = '500'; |
105
|
|
|
$response['string'] = 'Error saving file ' . $critcssfile . '.'; |
|
|
|
|
106
|
|
|
} else { |
107
|
|
|
$response['code'] = '200'; |
108
|
|
|
if ( $critcssfile ) { |
|
|
|
|
109
|
|
|
$response['string'] = 'File ' . $critcssfile . ' saved.'; |
110
|
|
|
} else { |
111
|
|
|
$response['string'] = 'Empty content do not need to be saved.'; |
112
|
|
|
} |
113
|
|
|
} |
114
|
|
|
|
115
|
|
|
// Dispatch respose. |
116
|
|
|
echo json_encode( $response ); |
117
|
|
|
|
118
|
|
|
// Close ajax request. |
119
|
|
|
wp_die(); |
120
|
|
|
} |
121
|
|
|
|
122
|
|
|
|
123
|
|
|
public function critcss_rm_callback() { |
124
|
|
|
// Ajax handler to delete a critical CSS from the filesystem |
125
|
|
|
// Check referer. |
126
|
|
|
check_ajax_referer( 'rm_critcss_nonce', 'critcss_rm_nonce' ); |
127
|
|
|
|
128
|
|
|
// Initialize error and status flags. |
129
|
|
|
$error = true; |
130
|
|
|
$status = false; |
131
|
|
|
|
132
|
|
|
// Allow no file for MANUAL rules (as they may not exist just yet). |
133
|
|
View Code Duplication |
if ( current_user_can( 'manage_options' ) && empty( $_POST['critcssfile'] ) ) { |
|
|
|
|
134
|
|
|
$error = false; |
135
|
|
|
} elseif ( current_user_can( 'manage_options' ) && $this->critcss_check_filename( $_POST['critcssfile'] ) ) { |
136
|
|
|
// Or check user permissios and filename |
137
|
|
|
// Set file path and delete it. |
138
|
|
|
$critcssfile = AO_CCSS_DIR . strip_tags( $_POST['critcssfile'] ); |
139
|
|
|
if ( file_exists( $critcssfile ) ) { |
140
|
|
|
$status = unlink( $critcssfile ); |
141
|
|
|
$error = false; |
142
|
|
|
} |
143
|
|
|
} |
144
|
|
|
|
145
|
|
|
// Prepare response. |
146
|
|
View Code Duplication |
if ( $error ) { |
|
|
|
|
147
|
|
|
$response['code'] = '500'; |
|
|
|
|
148
|
|
|
$response['string'] = 'Error removing file ' . $critcssfile . '.'; |
|
|
|
|
149
|
|
|
} else { |
150
|
|
|
$response['code'] = '200'; |
|
|
|
|
151
|
|
|
if ( $status ) { |
152
|
|
|
$response['string'] = 'File ' . $critcssfile . ' removed.'; |
153
|
|
|
} else { |
154
|
|
|
$response['string'] = 'No file to be removed.'; |
155
|
|
|
} |
156
|
|
|
} |
157
|
|
|
|
158
|
|
|
// Dispatch respose. |
159
|
|
|
echo json_encode( $response ); |
160
|
|
|
|
161
|
|
|
// Close ajax request. |
162
|
|
|
wp_die(); |
163
|
|
|
} |
164
|
|
|
|
165
|
|
|
public function critcss_rm_all_callback() { |
166
|
|
|
// Ajax handler to delete a critical CSS from the filesystem |
167
|
|
|
// Check referer. |
168
|
|
|
check_ajax_referer( 'rm_critcss_all_nonce', 'critcss_rm_all_nonce' ); |
169
|
|
|
|
170
|
|
|
// Initialize error and status flags. |
171
|
|
|
$error = true; |
172
|
|
|
$status = false; |
173
|
|
|
|
174
|
|
|
// Remove all ccss files on filesystem. |
175
|
|
|
if ( current_user_can( 'manage_options' ) ) { |
176
|
|
|
if ( file_exists( AO_CCSS_DIR ) && is_dir( AO_CCSS_DIR ) ) { |
177
|
|
|
array_map( 'unlink', glob( AO_CCSS_DIR . 'ccss_*.css', GLOB_BRACE ) ); |
178
|
|
|
$error = false; |
179
|
|
|
$status = true; |
180
|
|
|
} |
181
|
|
|
} |
182
|
|
|
|
183
|
|
|
// Prepare response. |
184
|
|
|
if ( $error ) { |
185
|
|
|
$response['code'] = '500'; |
|
|
|
|
186
|
|
|
$response['string'] = 'Error removing all critical CSS files.'; |
187
|
|
|
} else { |
188
|
|
|
$response['code'] = '200'; |
|
|
|
|
189
|
|
|
if ( $status ) { |
190
|
|
|
$response['string'] = 'Critical CSS Files removed.'; |
191
|
|
|
} else { |
192
|
|
|
$response['string'] = 'No file removed.'; |
193
|
|
|
} |
194
|
|
|
} |
195
|
|
|
|
196
|
|
|
// Dispatch respose. |
197
|
|
|
echo json_encode( $response ); |
198
|
|
|
|
199
|
|
|
// Close ajax request. |
200
|
|
|
wp_die(); |
201
|
|
|
} |
202
|
|
|
|
203
|
|
|
public function ao_ccss_export_callback() { |
204
|
|
|
// Ajax handler export settings |
205
|
|
|
// Check referer. |
206
|
|
|
check_ajax_referer( 'ao_ccss_export_nonce', 'ao_ccss_export_nonce' ); |
207
|
|
|
|
208
|
|
|
if ( ! class_exists( 'ZipArchive' ) ) { |
209
|
|
|
$response['code'] = '500'; |
|
|
|
|
210
|
|
|
$response['msg'] = 'PHP ZipArchive not present, cannot create zipfile'; |
211
|
|
|
echo json_encode( $response ); |
212
|
|
|
wp_die(); |
213
|
|
|
} |
214
|
|
|
|
215
|
|
|
// Init array, get options and prepare the raw object. |
216
|
|
|
$settings = array(); |
217
|
|
|
$settings['rules'] = get_option( 'autoptimize_ccss_rules' ); |
218
|
|
|
$settings['additional'] = get_option( 'autoptimize_ccss_additional' ); |
219
|
|
|
$settings['viewport'] = get_option( 'autoptimize_ccss_viewport' ); |
220
|
|
|
$settings['finclude'] = get_option( 'autoptimize_ccss_finclude' ); |
221
|
|
|
$settings['rlimit'] = get_option( 'autoptimize_ccss_rlimit' ); |
222
|
|
|
$settings['noptimize'] = get_option( 'autoptimize_ccss_noptimize' ); |
223
|
|
|
$settings['debug'] = get_option( 'autoptimize_ccss_debug' ); |
224
|
|
|
$settings['key'] = get_option( 'autoptimize_ccss_key' ); |
225
|
|
|
|
226
|
|
|
// Initialize error flag. |
227
|
|
|
$error = true; |
228
|
|
|
|
229
|
|
|
// Check user permissions. |
230
|
|
|
if ( current_user_can( 'manage_options' ) ) { |
231
|
|
|
// Prepare settings file path and content. |
232
|
|
|
$exportfile = AO_CCSS_DIR . 'settings.json'; |
233
|
|
|
$contents = json_encode( $settings ); |
234
|
|
|
$status = file_put_contents( $exportfile, $contents, LOCK_EX ); |
235
|
|
|
$error = false; |
236
|
|
|
} |
237
|
|
|
|
238
|
|
|
// Prepare archive. |
239
|
|
|
$zipfile = AO_CCSS_DIR . date( 'Ymd-H\hi' ) . '_ao_ccss_settings.zip'; |
240
|
|
|
$file = pathinfo( $zipfile, PATHINFO_BASENAME ); |
241
|
|
|
$zip = new ZipArchive(); |
242
|
|
|
$ret = $zip->open( $zipfile, ZipArchive::CREATE ); |
243
|
|
|
if ( true !== $ret ) { |
244
|
|
|
$error = true; |
245
|
|
|
} else { |
246
|
|
|
$zip->addFile( AO_CCSS_DIR . 'settings.json', 'settings.json' ); |
247
|
|
|
if ( file_exists( AO_CCSS_DIR . 'queue.json' ) ) { |
248
|
|
|
$zip->addFile( AO_CCSS_DIR . 'queue.json', 'queue.json' ); |
249
|
|
|
} |
250
|
|
|
$options = array( |
251
|
|
|
'add_path' => './', |
252
|
|
|
'remove_all_path' => true, |
253
|
|
|
); |
254
|
|
|
$zip->addGlob( AO_CCSS_DIR . '*.css', 0, $options ); |
255
|
|
|
$zip->close(); |
256
|
|
|
} |
257
|
|
|
|
258
|
|
|
// Prepare response. |
259
|
|
View Code Duplication |
if ( ! $status || $error ) { |
|
|
|
|
260
|
|
|
$response['code'] = '500'; |
|
|
|
|
261
|
|
|
$response['msg'] = 'Error saving file ' . $file . ', code: ' . $ret; |
262
|
|
|
} else { |
263
|
|
|
$response['code'] = '200'; |
264
|
|
|
$response['msg'] = 'File ' . $file . ' saved.'; |
265
|
|
|
$response['file'] = $file; |
266
|
|
|
} |
267
|
|
|
|
268
|
|
|
// Dispatch respose. |
269
|
|
|
echo json_encode( $response ); |
270
|
|
|
|
271
|
|
|
// Close ajax request. |
272
|
|
|
wp_die(); |
273
|
|
|
} |
274
|
|
|
|
275
|
|
|
public function ao_ccss_import_callback() { |
276
|
|
|
// Ajax handler import settings |
277
|
|
|
// Check referer. |
278
|
|
|
check_ajax_referer( 'ao_ccss_import_nonce', 'ao_ccss_import_nonce' ); |
279
|
|
|
|
280
|
|
|
// Initialize error flag. |
281
|
|
|
$error = false; |
282
|
|
|
|
283
|
|
|
// Process an uploaded file with no errors. |
284
|
|
|
if ( current_user_can( 'manage_options' ) && ! $_FILES['file']['error'] && $_FILES['file']['size'] < 500001 && strpos( $_FILES['file']['name'], '.zip' ) === strlen( $_FILES['file']['name'] ) - 4 ) { |
285
|
|
|
// create tmp dir with hard guess name in AO_CCSS_DIR. |
286
|
|
|
$_secret_dir = wp_hash( uniqid( md5( AUTOPTIMIZE_CACHE_URL ), true ) ); |
287
|
|
|
$_import_tmp_dir = trailingslashit( AO_CCSS_DIR . $_secret_dir ); |
288
|
|
|
mkdir( $_import_tmp_dir ); |
289
|
|
|
|
290
|
|
|
// Save file to that tmp directory but give it our own name to prevent directory traversal risks when using original name. |
291
|
|
|
$zipfile = $_import_tmp_dir . uniqid( 'import_settings-', true ) . '.zip'; |
292
|
|
|
move_uploaded_file( $_FILES['file']['tmp_name'], $zipfile ); |
293
|
|
|
|
294
|
|
|
// Extract archive in the tmp directory. |
295
|
|
|
$zip = new ZipArchive; |
296
|
|
|
if ( $zip->open( $zipfile ) === true ) { |
297
|
|
|
// loop through all files in the zipfile. |
298
|
|
|
for ($i = 0; $i < $zip->numFiles; $i++) { |
299
|
|
|
// but only extract known good files. |
300
|
|
|
if ( preg_match('/^settings\.json$|^ccss_[a-z0-9]{32}\.css$/', $zip->getNameIndex( $i ) ) > 0 ) { |
301
|
|
|
$zip->extractTo( AO_CCSS_DIR, $zip->getNameIndex( $i ) ); |
302
|
|
|
} |
303
|
|
|
} |
304
|
|
|
$zip->close(); |
305
|
|
|
} else { |
306
|
|
|
$error = 'could not extract'; |
307
|
|
|
} |
308
|
|
|
|
309
|
|
|
// and remove temp. dir with all contents (the import-zipfile). |
310
|
|
|
$this->rrmdir( $_import_tmp_dir ); |
311
|
|
|
|
312
|
|
|
if ( ! $error ) { |
|
|
|
|
313
|
|
|
// Archive extraction ok, continue importing settings from AO_CCSS_DIR. |
314
|
|
|
// Settings file. |
315
|
|
|
$importfile = AO_CCSS_DIR . 'settings.json'; |
316
|
|
|
|
317
|
|
|
if ( file_exists( $importfile ) ) { |
318
|
|
|
// Get settings and turn them into an object. |
319
|
|
|
$settings = json_decode( file_get_contents( $importfile ), true ); |
320
|
|
|
|
321
|
|
|
// Update options. |
322
|
|
|
update_option( 'autoptimize_ccss_rules', $settings['rules'] ); |
323
|
|
|
update_option( 'autoptimize_ccss_additional', $settings['additional'] ); |
324
|
|
|
update_option( 'autoptimize_ccss_viewport', $settings['viewport'] ); |
325
|
|
|
update_option( 'autoptimize_ccss_finclude', $settings['finclude'] ); |
326
|
|
|
update_option( 'autoptimize_ccss_rlimit', $settings['rlimit'] ); |
327
|
|
|
update_option( 'autoptimize_ccss_noptimize', $settings['noptimize'] ); |
328
|
|
|
update_option( 'autoptimize_ccss_debug', $settings['debug'] ); |
329
|
|
|
update_option( 'autoptimize_ccss_key', $settings['key'] ); |
330
|
|
|
} else { |
331
|
|
|
// Settings file doesn't exist, update error flag. |
332
|
|
|
$error = 'settings file does not exist'; |
333
|
|
|
} |
334
|
|
|
} |
335
|
|
|
} else { |
336
|
|
|
$error = 'file could not be saved'; |
337
|
|
|
} |
338
|
|
|
|
339
|
|
|
// Prepare response. |
340
|
|
|
if ( $error ) { |
|
|
|
|
341
|
|
|
$response['code'] = '500'; |
|
|
|
|
342
|
|
|
$response['msg'] = 'Error importing settings: ' . $error; |
343
|
|
|
} else { |
344
|
|
|
$response['code'] = '200'; |
|
|
|
|
345
|
|
|
$response['msg'] = 'Settings imported successfully'; |
346
|
|
|
} |
347
|
|
|
|
348
|
|
|
// Dispatch respose. |
349
|
|
|
echo json_encode( $response ); |
350
|
|
|
|
351
|
|
|
// Close ajax request. |
352
|
|
|
wp_die(); |
353
|
|
|
} |
354
|
|
|
|
355
|
|
|
public function critcss_check_filename( $filename ) { |
356
|
|
|
// Try to avoid directory traversal when reading/writing/deleting critical CSS files. |
357
|
|
|
if ( strpos( $filename, 'ccss_' ) !== 0 ) { |
358
|
|
|
return false; |
359
|
|
|
} elseif ( substr( $filename, -4, 4 ) !== '.css' ) { |
360
|
|
|
return false; |
361
|
|
|
} elseif ( sanitize_file_name( $filename ) !== $filename ) { |
362
|
|
|
// Use WordPress core's sanitize_file_name to see if anything fishy is going on. |
363
|
|
|
return false; |
364
|
|
|
} else { |
365
|
|
|
return true; |
366
|
|
|
} |
367
|
|
|
} |
368
|
|
|
|
369
|
|
|
public function rrmdir( $path ) { |
370
|
|
|
// recursively remove a directory as found on |
371
|
|
|
// https://andy-carter.com/blog/recursively-remove-a-directory-in-php. |
372
|
|
|
$files = glob($path . '/*'); |
373
|
|
|
foreach ( $files as $file ) { |
374
|
|
|
is_dir( $file ) ? $this->rrmdir( $file ) : unlink( $file ); |
375
|
|
|
} |
376
|
|
|
rmdir( $path ); |
377
|
|
|
|
378
|
|
|
return; |
379
|
|
|
} |
380
|
|
|
} |
381
|
|
|
|
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.