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

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