PluginProbe
WP-Optimize – Cache, Compress images, Minify & Clean database to boost page speed & performance / 3.3.1
WP-Optimize – Cache, Compress images, Minify & Clean database to boost page speed & performance v3.3.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.3.1, at webp/class-wp-optimize-webp.php

447 lines 12.5 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 * Constructor
20 */
21 private function __construct() {
22 $this->_should_use_webp = (bool) WP_Optimize()->get_options()->get_option('webp_conversion');
23
24 if ($this->_should_use_webp && $this->get_webp_conversion_test_result()) {
25 if (!is_admin()) {
26 $this->maybe_decide_webp_serve_method();
27 }
28 }
29
30 add_action('wpo_reset_webp_conversion_test_result', array($this, 'reset_webp_serving_method'));
31 }
32
33 /**
34 * Returns singleton instance
35 *
36 * @return WP_Optimize_WebP
37 */
38 public static function get_instance() {
39 static $instance = null;
40 if (null === $instance) {
41 $instance = new WP_Optimize_WebP();
42 }
43 return $instance;
44 }
45
46 /**
47 * Test Run and find converter status
48 */
49 private function set_converter_status() {
50 $converter_status = WPO_WebP_Test_Run::get_converter_status();
51 if ($this->is_webp_conversion_successful()) {
52 WP_Optimize()->get_options()->update_option('webp_conversion_test', true);
53 WP_Optimize()->get_options()->update_option('webp_converters', $converter_status['working_converters']);
54 }
55 }
56
57 /**
58 * If webp images should be used, then decide whether it is possible to server webp
59 * using rewrite rules or using altered html method
60 */
61 private function maybe_decide_webp_serve_method() {
62 $this->save_htaccess_rules();
63 if (!$this->is_webp_redirection_possible()) {
64 $this->empty_htaccess_file();
65 $this->maybe_use_alter_html();
66 }
67 }
68
69 /**
70 * If alter html method is possible, then use it
71 */
72 private function maybe_use_alter_html() {
73 if ($this->is_alter_html_possible()) {
74 $this->use_alter_html();
75 }
76 }
77
78 /**
79 * Even if server support .htaccess rewrite, sometimes it is not possible
80 * to serve webp images. So, find it webp redirection is possible or not
81 *
82 * @return bool
83 */
84 public function is_webp_redirection_possible() {
85 $redirection_possible = WP_Optimize()->get_options()->get_option('redirection_possible');
86 if (!empty($redirection_possible)) return 'true' === $redirection_possible;
87 return $this->run_webp_serving_self_test();
88 }
89
90 /**
91 * Decide whether the browser requesting the URL can accept webp images or not
92 *
93 * @return bool
94 */
95 private function is_browser_accepting_webp() {
96 return (isset($_SERVER['HTTP_ACCEPT']) && false !== strpos($_SERVER['HTTP_ACCEPT'], 'image/webp'));
97 }
98
99 /**
100 * Detect whether using alter HTML method is possible or not
101 *
102 * @return bool
103 */
104 private function is_alter_html_possible() {
105 if ($this->is_browser_accepting_webp()) {
106 return true;
107 }
108 return false;
109 }
110
111 /**
112 * Setup alter html method
113 */
114 private function use_alter_html() {
115 WPO_WebP_Alter_HTML::get_instance();
116 }
117
118 /**
119 * Initialize .htaccess
120 */
121 private function setup_htaccess_file() {
122 if (null !== $this->_htaccess) return;
123 $wp_uploads = wp_get_upload_dir();
124 $htaccess_file = $wp_uploads['basedir'] . '/.htaccess';
125 if (!file_exists($htaccess_file)) {
126 file_put_contents($htaccess_file, '');
127 }
128 $this->_htaccess = new WP_Optimize_Htaccess($htaccess_file);
129 }
130
131 /**
132 * Save .htaccess rules
133 *
134 * @return void
135 */
136 public function save_htaccess_rules() {
137 $this->setup_htaccess_file();
138 $this->add_webp_mime_type();
139 $htaccess_comment_section = 'WP-Optimize WebP Rules';
140 if ($this->_htaccess->is_commented_section_exists($htaccess_comment_section)) return;
141 $this->_htaccess->update_commented_section($this->prepare_webp_htaccess_rules(), $htaccess_comment_section);
142 $this->_htaccess->write_file();
143 }
144
145 /**
146 * Empty .htaccess file
147 */
148 public function empty_htaccess_file() {
149 $this->setup_htaccess_file();
150 $htaccess_comment_sections = array(
151 'WP-Optimize WebP Rules',
152 'Register webp mime type',
153 );
154 foreach ($htaccess_comment_sections as $htaccess_comment_section) {
155 $this->_htaccess->remove_commented_section($htaccess_comment_section);
156 $this->_htaccess->write_file();
157 }
158 }
159
160 /**
161 * Prepare array of htaccess rules to use webp images.
162 *
163 * @return array
164 */
165 private function prepare_webp_htaccess_rules() {
166 return array(
167 array(
168 '<IfModule mod_rewrite.c>',
169 'RewriteEngine On',
170 '',
171 '# Redirect to existing converted image in same dir (if browser supports webp)',
172 'RewriteCond %{HTTP_ACCEPT} image/webp',
173 'RewriteCond %{REQUEST_FILENAME} (?i)(.*)(\.jpe?g|\.png)$',
174 'RewriteCond %1%2\.webp -f',
175 'RewriteRule (?i)(.*)(\.jpe?g|\.png)$ %1%2\.webp [T=image/webp,E=EXISTING:1,E=ADDVARY:1,L]',
176 '',
177 '# Make sure that browsers which does not support webp also gets the Vary:Accept header',
178 '# when requesting images that would be redirected to webp on browsers that does.',
179 array(
180 '<IfModule mod_headers.c>',
181 array(
182 '<FilesMatch "(?i)\.(jpe?g|png)$">',
183 'Header append "Vary" "Accept"',
184 '</FilesMatch>',
185 ),
186 '</IfModule>',
187 ),
188 '',
189 '</IfModule>',
190 '',
191 ),
192 array(
193 '# Rules for handling requests for webp images',
194 '# ---------------------------------------------',
195 '',
196 '# Set Vary:Accept header if we came here by way of our redirect, which set the ADDVARY environment variable',
197 '# The purpose is to make proxies and CDNs aware that the response varies with the Accept header',
198 '<IfModule mod_headers.c>',
199 array(
200 '<IfModule mod_setenvif.c>',
201 '# Apache appends "REDIRECT_" in front of the environment variables defined in mod_rewrite, but LiteSpeed does not',
202 '# So, the next lines are for Apache, in order to set environment variables without "REDIRECT_"',
203 'SetEnvIf REDIRECT_EXISTING 1 EXISTING=1',
204 'SetEnvIf REDIRECT_ADDVARY 1 ADDVARY=1',
205 '',
206 'Header append "Vary" "Accept" env=ADDVARY',
207 '',
208 '# Set X-WPO-WebP header for diagnose purposes',
209 'Header set "X-WPO-WebP" "Redirected directly to existing webp" env=EXISTING',
210 '</IfModule>',
211 ),
212 '</IfModule>',
213 ),
214 );
215 }
216
217 /**
218 * Add webp mime type to htaccess rules.
219 */
220 private function add_webp_mime_type() {
221 $htaccess_comment_section = 'Register webp mime type';
222 if ($this->_htaccess->is_exists() && !$this->_htaccess->is_commented_section_exists($htaccess_comment_section)) {
223 $webp_mime_type = array(
224 array(
225 '<IfModule mod_mime.c>',
226 'AddType image/webp .webp',
227 '</IfModule>',
228 ),
229 );
230 $this->_htaccess->update_commented_section($webp_mime_type, $htaccess_comment_section);
231 $this->_htaccess->write_file();
232 }
233 }
234
235 /**
236 * Checks whether webp conversion test is successful or not
237 *
238 * @return bool
239 */
240 public function is_webp_conversion_successful() {
241 $upload_dir = wp_upload_dir();
242 $destination = $upload_dir['basedir']. '/wpo/images/wpo_logo_small.png.webp';
243 return file_exists($destination);
244 }
245
246 /**
247 * Checks whether sample webp conversion test should be run or not
248 *
249 * @return bool Returns true if sample test should be run, false otherwise
250 */
251 public function should_run_webp_conversion_test() {
252 $webp_conversion_test = $this->get_webp_conversion_test_result();
253 return (true !== $webp_conversion_test);
254 }
255
256 /**
257 * Returns webp conversion test result
258 *
259 * @return boolean Returns the value of the webp_conversion_test saved in the options table
260 */
261 private function get_webp_conversion_test_result() {
262 return (bool) WP_Optimize()->get_options()->get_option('webp_conversion_test');
263 }
264
265 /**
266 * Checks whether the webp redirection is possible or not and sets flag
267 *
268 * @return bool Returns true if webp is served successfully, false otherwise
269 */
270 private function run_webp_serving_self_test() {
271 $self_test = WPO_WebP_Self_Test::get_instance();
272
273 if ($self_test->is_webp_served()) {
274 WP_Optimize()->get_options()->update_option('redirection_possible', 'true');
275 return true;
276 }
277 WP_Optimize()->get_options()->update_option('redirection_possible', 'false');
278 $this->empty_htaccess_file();
279 return false;
280 }
281
282 /**
283 * Resets webp serving method by running self test, if needed purges cache and empties `uploads/.htaccess` file
284 */
285 public function reset_webp_serving_method() {
286 if (self::is_shell_functions_available() && $this->_should_use_webp) {
287 $this->reset_webp_options();
288 $this->run_self_test();
289 list($old_redirection_possible, $new_redirection_possible) = $this->get_old_and_new_redirection_possibility();
290 $this->maybe_purge_cache($old_redirection_possible, $new_redirection_possible);
291 $this->maybe_empty_htaccess_file($new_redirection_possible);
292 } else {
293 $this->disable_webp_conversion();
294 }
295 }
296
297 /**
298 * Resets WebP related options
299 */
300 private function reset_webp_options() {
301 $options = WP_Optimize()->get_options();
302 $options->update_option('old_redirection_possible', $options->get_option('redirection_possible'));
303 $options->update_option('webp_conversion_test', false);
304 $options->update_option('webp_converters', false);
305 $options->update_option('redirection_possible', false);
306 $this->remove_webp_test_image_file();
307 }
308
309 /**
310 * Running self test to find available converters and possibility of serving webp using redirection method
311 */
312 private function run_self_test() {
313 $this->set_converter_status();
314 if ($this->get_webp_conversion_test_result()) {
315 $this->save_htaccess_rules();
316 $this->run_webp_serving_self_test();
317 } else {
318 $this->disable_webp_conversion();
319 }
320 }
321
322 /**
323 * Gets old and new redirection possibility values
324 *
325 * @return array
326 */
327 private function get_old_and_new_redirection_possibility() {
328 $options = WP_Optimize()->get_options();
329 return array(
330 $options->get_option('old_redirection_possible'),
331 $options->get_option('redirection_possible'),
332 );
333 }
334
335 /**
336 * Cache is cleared when there is a change in the potential for serving WebP using redirection.
337 *
338 * @param string $old_redirection_possible
339 * @param string $new_redirection_possible
340 */
341 private function maybe_purge_cache($old_redirection_possible, $new_redirection_possible) {
342 if ($old_redirection_possible !== $new_redirection_possible) {
343 WP_Optimize()->get_page_cache()->purge();
344 }
345 }
346
347 /**
348 * Remove redirection rules from `uploads/.htaccess` file if redirection is not possible
349 *
350 * @param string $new_redirection_possible
351 */
352 private function maybe_empty_htaccess_file($new_redirection_possible) {
353 if ('false' === $new_redirection_possible) {
354 $this->empty_htaccess_file();
355 }
356 }
357
358 /**
359 * Initialize cron scheduler
360 */
361 public function init_webp_cron_scheduler() {
362 if (!wp_next_scheduled('wpo_reset_webp_conversion_test_result')) {
363 wp_schedule_event(time(), 'wpo_daily', 'wpo_reset_webp_conversion_test_result');
364 }
365 }
366
367 /**
368 * Remove all cron schedules
369 */
370 public function remove_webp_cron_schedules() {
371 wp_clear_scheduled_hook('wpo_reset_webp_conversion_test_result');
372 }
373
374 /**
375 * Return the true if webp conversion is enabled and vice-versa
376 *
377 * @return bool
378 */
379 public function is_webp_conversion_enabled() {
380 return $this->_should_use_webp;
381 }
382
383 /**
384 * Set the webp_conversion option value to false and remove webp cron schedules
385 */
386 public function disable_webp_conversion() {
387 $this->empty_htaccess_file();
388 WP_Optimize()->get_options()->update_option("webp_conversion", false);
389 $this->remove_webp_cron_schedules();
390 }
391
392 /**
393 * Remove webp converted test image file
394 */
395 private function remove_webp_test_image_file() {
396 $upload_dir = wp_upload_dir();
397 $destination = $upload_dir['basedir']. '/wpo/images/wpo_logo_small.png.webp';
398 if (@file_exists($destination)) { // phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged -- suppress PHP warning in case of failure
399 @unlink($destination); // phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged -- suppress PHP warning in case of failure
400 }
401 }
402
403 /**
404 * Run during plugin deactivation
405 */
406 public function plugin_deactivate() {
407 $this->empty_htaccess_file();
408 $this->remove_webp_test_image_file();
409 }
410
411 /**
412 * Determines whether the php shell functions are available or not
413 *
414 * @return bool
415 */
416 public static function is_shell_functions_available() {
417 $shell_functions = self::get_shell_functions();
418 foreach ($shell_functions as $shell_function) {
419 if (!function_exists($shell_function)) return false;
420 }
421 return true;
422 }
423
424 /**
425 * List of php shell function names
426 *
427 * @return string[]
428 */
429 private static function get_shell_functions() {
430 return array(
431 'escapeshellarg',
432 'escapeshellcmd',
433 'exec',
434 'passthru',
435 'proc_close',
436 'proc_get_status',
437 'proc_nice',
438 'proc_open',
439 'proc_terminate',
440 'shell_exec',
441 'system',
442 );
443 }
444 }
445
446 endif;
447