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

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