PluginProbe
WP-Optimize – Cache, Compress images, Minify & Clean database to boost page speed & performance / 3.7.1
WP-Optimize – Cache, Compress images, Minify & Clean database to boost page speed & performance v3.7.1
4.7.0 4.6.1 4.6.0 4.5.5 4.5.4 4.5.3 4.5.2 3.2.20 3.2.21 3.2.22 3.2.3 3.2.5 3.2.6 3.2.7 3.2.9 3.3.0 3.3.1 3.3.2 3.4.0 3.4.1 3.4.2 3.5.0 3.6.0 3.7.0 3.7.1 All 111 releases
wp-optimize / webp / class-wp-optimize-webp.php

class-wp-optimize-webp.php in WP-Optimize – Cache, Compress images, Minify & Clean database to boost page speed & performance 3.7.1, at webp/class-wp-optimize-webp.php

518 lines 15.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 if (!defined('WPO_VERSION')) die('No direct access allowed');
4
5 if (!class_exists('WP_Optimize_WebP')) :
6
7 class WP_Optimize_WebP {
8
9 private $_htaccess = null;
10
11 /**
12 * Set to true when webp is enabled and vice-versa
13 *
14 * @var boolean
15 */
16 private $_should_use_webp = false;
17
18 /**
19 * The logger for this instance
20 *
21 * @var mixed
22 */
23 private $logger;
24
25 /**
26 * Constructor
27 */
28 private function __construct() {
29 $this->_should_use_webp = (bool) WP_Optimize()->get_options()->get_option('webp_conversion');
30
31 if ($this->_should_use_webp && $this->get_webp_conversion_test_result()) {
32 if (!is_admin()) {
33
34 // Allow filters added in theme files to run
35 add_action('after_setup_theme', array($this, 'maybe_decide_webp_serve_method'));
36 }
37 }
38
39 $this->logger = new Updraft_File_Logger($this->get_logfile_path());
40
41 add_action('wpo_reset_webp_conversion_test_result', array($this, 'reset_webp_serving_method'));
42 add_action('wpo_prune_webp_logs', array($this, 'prune_webp_logs'));
43 }
44
45 /**
46 * Returns singleton instance
47 *
48 * @return WP_Optimize_WebP
49 */
50 public static function get_instance() {
51 static $instance = null;
52 if (null === $instance) {
53 $instance = new WP_Optimize_WebP();
54 }
55 return $instance;
56 }
57
58 /**
59 * Returns the path to the logfile
60 *
61 * @return string - file path
62 */
63 private function get_logfile_path() {
64 return WP_Optimize_Utils::get_log_file_path('webp');
65 }
66
67 /**
68 * Logging of interesting messages related to Webp
69 *
70 * @param string $message
71 */
72 public function log($message) {
73 $this->logger->info($message);
74 }
75
76 /**
77 * Prunes the log file
78 */
79 public function prune_webp_logs() {
80 $this->log("Pruning the WebP log file");
81 $this->logger->prune_logs();
82 }
83
84 /**
85 * Test Run and find converter status
86 */
87 private function set_converter_status() {
88 $converter_status = WPO_WebP_Test_Run::get_converter_status();
89 if ($this->is_webp_conversion_successful()) {
90 WP_Optimize()->get_options()->update_option('webp_conversion_test', true);
91 WP_Optimize()->get_options()->update_option('webp_converters', $converter_status['working_converters']);
92 }
93 }
94
95 /**
96 * If .htaccess redirection is not possible, attempts to use the alter_html method
97 */
98 public function maybe_decide_webp_serve_method() {
99 if (!$this->is_webp_redirection_possible()) {
100 $this->maybe_use_alter_html();
101 }
102 }
103
104 /**
105 * If alter html method is possible, then use it
106 */
107 private function maybe_use_alter_html() {
108 if ($this->is_alter_html_possible()) {
109 $this->empty_htaccess_file();
110 $this->use_alter_html();
111 }
112 }
113
114 /**
115 * Even if server support .htaccess rewrite, sometimes it is not possible
116 * to serve webp images. So, find it webp redirection is possible or not
117 * Also applies `wpo_force_webp_serve_using_altered_html` filter for users to be able to
118 * force Altered HTML method
119 *
120 * @return bool
121 */
122 public function is_webp_redirection_possible() {
123 if (apply_filters('wpo_force_webp_serve_using_altered_html', false)) {
124 return false;
125 }
126 $redirection_possible = WP_Optimize()->get_options()->get_option('redirection_possible');
127 if (!empty($redirection_possible)) return 'true' === $redirection_possible;
128 return $this->run_webp_serving_self_test();
129 }
130
131 /**
132 * Decide whether the browser requesting the URL can accept webp images or not
133 *
134 * @return bool
135 */
136 private function is_browser_accepting_webp() {
137 return (isset($_SERVER['HTTP_ACCEPT']) && false !== strpos($_SERVER['HTTP_ACCEPT'], 'image/webp'));
138 }
139
140 /**
141 * Detect whether using alter HTML method is possible or not
142 *
143 * @return bool
144 */
145 private function is_alter_html_possible() {
146 if ($this->is_browser_accepting_webp()) {
147 return true;
148 }
149 return false;
150 }
151
152 /**
153 * Setup alter html method
154 */
155 private function use_alter_html() {
156 WPO_WebP_Alter_HTML::get_instance();
157 }
158
159 /**
160 * Initialize .htaccess
161 */
162 private function setup_htaccess_file() {
163 if (null !== $this->_htaccess) return;
164 $wp_uploads = wp_get_upload_dir();
165 $htaccess_file = $wp_uploads['basedir'] . '/.htaccess';
166 if (!file_exists($htaccess_file)) {
167 file_put_contents($htaccess_file, '');
168 }
169 $this->_htaccess = new WP_Optimize_Htaccess($htaccess_file);
170 }
171
172 /**
173 * Save .htaccess rules
174 *
175 * @return void
176 */
177 public function save_htaccess_rules() {
178 $this->setup_htaccess_file();
179 $this->add_webp_mime_type();
180 $htaccess_comment_section = 'WP-Optimize WebP Rules';
181 if ($this->_htaccess->is_commented_section_exists($htaccess_comment_section)) return;
182 $this->_htaccess->update_commented_section($this->prepare_webp_htaccess_rules(), $htaccess_comment_section);
183 $this->_htaccess->write_file();
184 WP_Optimize()->get_options()->update_option('htaccess_has_webp_rules', true);
185 }
186
187 /**
188 * Empty .htaccess file
189 */
190 public function empty_htaccess_file() {
191 // Setting default to true, so on initial run (when option is not yet present in the DB) we don't break the function here
192 if (!WP_Optimize()->get_options()->get_option('htaccess_has_webp_rules', true)) return;
193 $this->setup_htaccess_file();
194 $htaccess_comment_sections = array(
195 'WP-Optimize WebP Rules',
196 'Register webp mime type',
197 );
198 foreach ($htaccess_comment_sections as $htaccess_comment_section) {
199 if (!$this->_htaccess->is_commented_section_exists($htaccess_comment_section)) continue;
200 $this->_htaccess->remove_commented_section($htaccess_comment_section);
201 $this->_htaccess->write_file();
202 }
203 WP_Optimize()->get_options()->update_option('htaccess_has_webp_rules', false);
204 }
205
206 /**
207 * Prepare array of htaccess rules to use webp images.
208 *
209 * @return array
210 */
211 private function prepare_webp_htaccess_rules() {
212 return array(
213 array(
214 '<IfModule mod_rewrite.c>',
215 'RewriteEngine On',
216 '',
217 '# Redirect to existing converted image in same dir (if browser supports webp)',
218 'RewriteCond %{HTTP_ACCEPT} image/webp',
219 'RewriteCond %{REQUEST_FILENAME} (?i)(.*)(\.jpe?g|\.png)$',
220 'RewriteCond %1%2\.webp -f',
221 'RewriteRule (?i)(.*)(\.jpe?g|\.png)$ %1%2\.webp [T=image/webp,E=EXISTING:1,E=ADDVARY:1,L]',
222 '',
223 '# Make sure that browsers which does not support webp also gets the Vary:Accept header',
224 '# when requesting images that would be redirected to webp on browsers that does.',
225 array(
226 '<IfModule mod_headers.c>',
227 array(
228 '<FilesMatch "(?i)\.(jpe?g|png)$">',
229 'Header append "Vary" "Accept"',
230 '</FilesMatch>',
231 ),
232 '</IfModule>',
233 ),
234 '',
235 '</IfModule>',
236 '',
237 ),
238 array(
239 '# Rules for handling requests for webp images',
240 '# ---------------------------------------------',
241 '',
242 '# Set Vary:Accept header if we came here by way of our redirect, which set the ADDVARY environment variable',
243 '# The purpose is to make proxies and CDNs aware that the response varies with the Accept header',
244 '<IfModule mod_headers.c>',
245 array(
246 '<IfModule mod_setenvif.c>',
247 '# Apache appends "REDIRECT_" in front of the environment variables defined in mod_rewrite, but LiteSpeed does not',
248 '# So, the next lines are for Apache, in order to set environment variables without "REDIRECT_"',
249 'SetEnvIf REDIRECT_EXISTING 1 EXISTING=1',
250 'SetEnvIf REDIRECT_ADDVARY 1 ADDVARY=1',
251 '',
252 'Header append "Vary" "Accept" env=ADDVARY',
253 '',
254 '# Set X-WPO-WebP header for diagnose purposes',
255 'Header set "X-WPO-WebP" "Redirected directly to existing webp" env=EXISTING',
256 '</IfModule>',
257 ),
258 '</IfModule>',
259 ),
260 );
261 }
262
263 /**
264 * Add webp mime type to htaccess rules.
265 */
266 private function add_webp_mime_type() {
267 $htaccess_comment_section = 'Register webp mime type';
268 if ($this->_htaccess->is_exists() && !$this->_htaccess->is_commented_section_exists($htaccess_comment_section)) {
269 $webp_mime_type = array(
270 array(
271 '<IfModule mod_mime.c>',
272 'AddType image/webp .webp',
273 '</IfModule>',
274 ),
275 );
276 $this->_htaccess->update_commented_section($webp_mime_type, $htaccess_comment_section);
277 $this->_htaccess->write_file();
278 }
279 }
280
281 /**
282 * Checks whether webp conversion test is successful or not
283 *
284 * @return bool
285 */
286 public function is_webp_conversion_successful() {
287 $upload_dir = wp_upload_dir();
288 $destination = $upload_dir['basedir']. '/wpo/images/wpo_logo_small.png.webp';
289 return file_exists($destination);
290 }
291
292 /**
293 * Checks whether sample webp conversion test should be run or not
294 *
295 * @return bool Returns true if sample test should be run, false otherwise
296 */
297 public function should_run_webp_conversion_test() {
298 $webp_conversion_test = $this->get_webp_conversion_test_result();
299 return (true !== $webp_conversion_test);
300 }
301
302 /**
303 * Returns webp conversion test result
304 *
305 * @return boolean Returns the value of the webp_conversion_test saved in the options table
306 */
307 private function get_webp_conversion_test_result() {
308 return (bool) WP_Optimize()->get_options()->get_option('webp_conversion_test');
309 }
310
311 /**
312 * Checks whether the webp redirection is possible or not and sets flag
313 *
314 * @return bool Returns true if webp is served successfully, false otherwise
315 */
316 private function run_webp_serving_self_test() {
317 $self_test = WPO_WebP_Self_Test::get_instance();
318
319 if ($self_test->is_webp_served()) {
320 WP_Optimize()->get_options()->update_option('redirection_possible', 'true');
321 return true;
322 }
323 WP_Optimize()->get_options()->update_option('redirection_possible', 'false');
324 $this->empty_htaccess_file();
325 return false;
326 }
327
328 /**
329 * Resets webp serving method by running self test, if needed purges cache and empties `uploads/.htaccess` file
330 */
331 public function reset_webp_serving_method() {
332 if ($this->shell_functions_available() && $this->_should_use_webp) {
333 $this->reset_webp_options();
334 $this->run_self_test();
335 list($old_redirection_possible, $new_redirection_possible) = $this->get_old_and_new_redirection_possibility();
336 $this->maybe_purge_cache($old_redirection_possible, $new_redirection_possible);
337 $this->maybe_empty_htaccess_file($new_redirection_possible);
338 } else {
339 $this->disable_webp_conversion();
340 $this->log("Reset WebP Serving method failed, disabling WebP conversion");
341 }
342 }
343
344 /**
345 * Resets WebP related options
346 */
347 private function reset_webp_options() {
348 $options = WP_Optimize()->get_options();
349 $options->update_option('old_redirection_possible', $options->get_option('redirection_possible'));
350 $options->update_option('webp_conversion_test', false);
351 $options->update_option('webp_converters', false);
352 $options->update_option('redirection_possible', false);
353 $this->remove_webp_test_image_file();
354 }
355
356 /**
357 * Running self test to find available converters and possibility of serving webp using redirection method
358 */
359 private function run_self_test() {
360 $this->set_converter_status();
361 if ($this->get_webp_conversion_test_result()) {
362 $this->save_htaccess_rules();
363 $this->run_webp_serving_self_test();
364 } else {
365 $this->disable_webp_conversion();
366 $this->log("No working WebP converter was found on the server when running self-test, disabling WebP conversion");
367 }
368 }
369
370 /**
371 * Gets old and new redirection possibility values
372 *
373 * @return array
374 */
375 private function get_old_and_new_redirection_possibility() {
376 $options = WP_Optimize()->get_options();
377 return array(
378 $options->get_option('old_redirection_possible'),
379 $options->get_option('redirection_possible'),
380 );
381 }
382
383 /**
384 * Cache is cleared when there is a change in the potential for serving WebP using redirection.
385 *
386 * @param string $old_redirection_possible
387 * @param string $new_redirection_possible
388 */
389 private function maybe_purge_cache($old_redirection_possible, $new_redirection_possible) {
390 if ($old_redirection_possible !== $new_redirection_possible) {
391 WP_Optimize()->get_page_cache()->purge();
392 $log_old_value = empty($old_redirection_possible) ? "null" : $old_redirection_possible;
393 $log_new_value = empty($new_redirection_possible) ? "null" : $new_redirection_possible;
394 $this->log("Purging cache because redirection_possible value changed from: " .
395 $log_old_value .
396 " to " .
397 $log_new_value
398 );
399 }
400 }
401
402 /**
403 * Remove redirection rules from `uploads/.htaccess` file if redirection is not possible
404 *
405 * @param string $new_redirection_possible
406 */
407 private function maybe_empty_htaccess_file($new_redirection_possible) {
408 if ('false' === $new_redirection_possible) {
409 $this->empty_htaccess_file();
410 }
411 }
412
413 /**
414 * Initialize cron scheduler
415 */
416 public function init_webp_cron_scheduler() {
417 if (!wp_next_scheduled('wpo_reset_webp_conversion_test_result')) {
418 wp_schedule_event(time(), 'wpo_daily', 'wpo_reset_webp_conversion_test_result');
419 }
420 if (!wp_next_scheduled('wpo_prune_webp_logs')) {
421 wp_schedule_event(time(), 'weekly', 'wpo_prune_webp_logs');
422 }
423 }
424
425 /**
426 * Remove all cron schedules
427 */
428 public function remove_webp_cron_schedules() {
429 wp_clear_scheduled_hook('wpo_reset_webp_conversion_test_result');
430 wp_clear_scheduled_hook('wpo_prune_webp_logs');
431 }
432
433 /**
434 * Return the true if webp conversion is enabled and vice-versa
435 *
436 * @return bool
437 */
438 public function is_webp_conversion_enabled() {
439 return $this->_should_use_webp;
440 }
441
442 /**
443 * Set the webp_conversion option value to false and remove webp cron schedules
444 */
445 public function disable_webp_conversion() {
446 $this->empty_htaccess_file();
447 WP_Optimize()->get_options()->update_option("webp_conversion", false);
448 $this->remove_webp_cron_schedules();
449 }
450
451 /**
452 * Remove webp converted test image file
453 */
454 private function remove_webp_test_image_file() {
455 $upload_dir = wp_upload_dir();
456 $destination = $upload_dir['basedir']. '/wpo/images/wpo_logo_small.png.webp';
457 if (@file_exists($destination)) { // phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged -- suppress PHP warning in case of failure
458 @unlink($destination); // phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged -- suppress PHP warning in case of failure
459 }
460 }
461
462 /**
463 * Run during plugin deactivation
464 */
465 public function plugin_deactivate() {
466 $this->empty_htaccess_file();
467 $this->remove_webp_test_image_file();
468 }
469
470 /**
471 * Determines whether one of the PHP shell functions required for WebP convertion is available or not.
472 *
473 * @return bool
474 */
475 public function shell_functions_available() {
476 return function_exists('escapeshellarg') && ($this->any_function_exists(array('exec', 'passthru')) || $this->all_functions_exist(array('proc_open', 'proc_close')) || $this->all_functions_exist(array('popen', 'pclose')));
477 }
478
479 /**
480 * Determines whether one of the PHP shell functions required for WebP convertion is available or not.
481 *
482 * @deprecated 3.6.0
483 * @return bool
484 */
485 public static function is_shell_functions_available() {
486 _deprecated_function(__METHOD__, '3.6.0', 'WP_Optimize_WebP::is_shell_functions_available');
487 return WP_Optimize_WebP::get_instance()->shell_functions_available();
488 }
489
490 /**
491 * Check if all of the functions from the list is available.
492 *
493 * @param array $functions
494 * @return bool
495 */
496 private function all_functions_exist($functions) {
497 foreach ($functions as $function) {
498 if (!function_exists($function)) return false;
499 }
500 return true;
501 }
502
503 /**
504 * Check if one of the functions from the list is available.
505 *
506 * @param array $functions
507 * @return bool
508 */
509 private function any_function_exists($functions) {
510 foreach ($functions as $function) {
511 if (function_exists($function)) return true;
512 }
513 return false;
514 }
515 }
516
517 endif;
518