PluginProbe
Powered Cache – Caching and Optimization for WordPress – Easily Improve PageSpeed & Web Vitals Score / 2.1
Powered Cache – Caching and Optimization for WordPress – Easily Improve PageSpeed & Web Vitals Score v2.1
trunk 1.0 1.0.1 1.1 1.1.1 1.1.2 1.2 1.2.1 1.2.2 1.2.3 1.2.4 1.2.5 1.2.6 1.2.7 1.2.8 2.0 2.0.1 2.0.2 2.0.3 2.0.4 2.1 2.1.1 2.1.2 2.2 2.2.1 All 69 releases
powered-cache / includes / classes / Config.php

Config.php in Powered Cache – Caching and Optimization for WordPress – Easily Improve PageSpeed & Web Vitals Score 2.1, at includes/classes/Config.php

1,096 lines 35.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Configurator Class of the plugin
4 *
5 * @package PoweredCache
6 */
7
8 namespace PoweredCache;
9
10 use function PoweredCache\Utils\can_configure_htaccess;
11 use function PoweredCache\Utils\can_configure_object_cache;
12 use function PoweredCache\Utils\get_cache_dir;
13 use function PoweredCache\Utils\get_object_cache_dropins;
14 use function PoweredCache\Utils\mobile_browsers;
15 use function PoweredCache\Utils\mobile_prefixes;
16 use function PoweredCache\Utils\permalink_structure_has_trailingslash;
17 use function PoweredCache\Utils\remove_dir;
18
19 if ( ! defined( 'ABSPATH' ) ) {
20 exit; // Exit if accessed directly.
21 }
22
23 // phpcs:disable Generic.Strings.UnnecessaryStringConcat.Found
24 // phpcs:disable WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
25 // phpcs:disable WordPress.PHP.DevelopmentFunctions.error_log_var_export
26 // phpcs:disable WordPress.PHP.NoSilencedErrors.Discouraged
27 // phpcs:disable WordPress.WP.AlternativeFunctions.file_system_read_file_put_contents
28
29 /**
30 * Class Config
31 */
32 class Config {
33 /**
34 * placeholder
35 *
36 * @since 1.0
37 */
38 public function __construct() {
39 }
40
41 /**
42 * Return an instance of the current class
43 *
44 * @return Config
45 * @since 2.0 removed WP_Filesystem_Direct deps
46 * @since 1.1 initialize WP_Filesystem_Direct
47 * @since 1.0
48 */
49 public static function factory() {
50 static $instance = false;
51
52 if ( ! $instance ) {
53 $instance = new self();
54 }
55
56 return $instance;
57 }
58
59
60 /**
61 * Setup object-cache.php
62 *
63 * @param string $backend Persistent object cache backend. (memcached, redis etc..)
64 *
65 * @return bool
66 * @since 1.0
67 */
68 public function setup_object_cache( $backend = 'off' ) {
69 $file = untrailingslashit( WP_CONTENT_DIR ) . '/object-cache.php';
70
71 /**
72 * Since object cache has impact on the entire network
73 * It only allowed by the network admin
74 */
75 if ( is_multisite() && ! current_user_can( 'manage_network' ) ) {
76 return false;
77 }
78
79 if ( 'off' === $backend && file_exists( $file ) && false !== strpos( file_get_contents( $file ), 'POWERED_OBJECT_CACHE' ) ) {
80 /**
81 * Remove object-cache.php file only when the created file belongs to PoweredCache
82 */
83 unlink( $file );
84
85 return true;
86 }
87
88 if ( 'off' === $backend ) {
89 return true;
90 }
91
92 $file_string = $this->object_cache_file_content( $backend );
93
94 if ( ! file_put_contents( $file, $file_string, LOCK_EX ) ) {
95 return false;
96 }
97
98 return true;
99 }
100
101 /**
102 * object-cache.php contents
103 *
104 * @param string $backend Persistent object cache backend
105 *
106 * @return mixed|void
107 * @since 1.0
108 * @see Powered_Cache_Admin_Helper::object_cache_dropins
109 *
110 * @since 1.1 supports `POWERED_CACHE_OBJECT_CACHE_DROPIN`
111 */
112 public function object_cache_file_content( $backend ) {
113 $string = '<?php ' . "\n";
114 $string .= "defined( 'ABSPATH' ) || exit;" . PHP_EOL;
115 $string .= "define( 'POWERED_OBJECT_CACHE', true );" . PHP_EOL;
116 $string .= "if ( ! defined( 'WP_CACHE_KEY_SALT' ) ) {" . PHP_EOL;
117 $string .= "\t" . "define( 'WP_CACHE_KEY_SALT', DB_NAME );" . PHP_EOL;
118 $string .= '}' . PHP_EOL;
119
120 $object_caches = get_object_cache_dropins();
121
122 $string .= 'if ( defined( \'POWERED_CACHE_OBJECT_CACHE_DROPIN\') && @file_exists( POWERED_CACHE_OBJECT_CACHE_DROPIN ) ) {' . PHP_EOL;
123 $string .= "\t" . 'include( POWERED_CACHE_OBJECT_CACHE_DROPIN );' . PHP_EOL;
124 $string .= '} elseif ( @file_exists( \'' . $object_caches[ $backend ] . '\' ) ) {' . PHP_EOL;
125 $string .= "\t" . 'include( \'' . $object_caches[ $backend ] . '\' );' . PHP_EOL;
126 $string .= '} else {' . PHP_EOL;
127 $string .= "\t" . 'define( \'POWERED_OBJECT_CACHE_HAS_PROBLEM\', true );' . PHP_EOL;
128 $string .= '}';
129
130 /**
131 * Filters object-cache.php file contents.
132 *
133 * @hook powered_cache_object_cache_file_content
134 *
135 * @param {string} $string The content of the object-cache.php file
136 *
137 * @return {string} New value.
138 *
139 * @since 1.0
140 */
141 return apply_filters( 'powered_cache_object_cache_file_content', $string );
142 }
143
144 /**
145 * Generate advanced-cache.php and define WP_CACHE
146 *
147 * @param bool $status status of the page caching
148 *
149 * @return bool
150 * @since 1.0
151 */
152 public function setup_page_cache( $status ) {
153
154 /**
155 * Forcing multisite settings always true
156 */
157 if ( is_multisite() && ! POWERED_CACHE_IS_NETWORK ) {
158 $status = true;
159 }
160
161 $this->generate_advanced_cache_file();
162 $this->define_wp_cache( $status );
163
164 if ( can_configure_htaccess() ) {
165 $this->configure_htaccess( $status );
166 }
167
168 $this->protect_cache_dir();
169
170 return true;
171 }
172
173
174 /**
175 * Generates advanced-cache.php
176 *
177 * @return bool
178 * @since 1.1 is_multisite control added
179 * @since 1.0
180 */
181 public function generate_advanced_cache_file() {
182 $file = untrailingslashit( WP_CONTENT_DIR ) . '/advanced-cache.php';
183 $settings = \PoweredCache\Utils\get_settings();
184
185 $file_string = '';
186
187 /**
188 * multisite setups should always have `advanced-cache.php` file
189 */
190 if ( true === $settings['enable_page_cache'] || is_multisite() ) {
191 $file_string = $this->advanced_cache_file_content();
192 }
193
194 if ( ! file_put_contents( $file, $file_string ) ) {
195 return false;
196 }
197
198 return true;
199 }
200
201
202 /**
203 * Prepare advanced-cache.php contents
204 *
205 * @return mixed|void
206 * @since 1.1 supports `POWERED_CACHE_ADVANCED_CACHE_DROPIN`
207 * @since 1.0
208 */
209 public function advanced_cache_file_content() {
210 $string = '<?php ' . PHP_EOL;
211 $string .= "defined( 'ABSPATH' ) || exit;" . PHP_EOL;
212 $string .= "define( 'POWERED_CACHE_PAGE_CACHING', true );" . PHP_EOL . PHP_EOL;
213 // lookup order 1) network-wide , 2) subdomain specific (if any) 3) domain specific
214 $string .= "\$config_locations[] = WP_CONTENT_DIR . '/pc-config/config-network.php';" . PHP_EOL;
215 $string .= "if ( is_multisite() && defined( 'SUBDOMAIN_INSTALL' ) && ! SUBDOMAIN_INSTALL ) {" . PHP_EOL;
216 $string .= "\t" . "\$request_uri = explode( '/', ltrim( \$_SERVER['REQUEST_URI'], '/' ) );" . PHP_EOL;
217 $string .= "\t" . 'if ( ! empty( $request_uri[0] ) ) {' . PHP_EOL;
218 $string .= "\t" . "\t" . "\$config_locations[] = WP_CONTENT_DIR . '/pc-config/config-' . \$_SERVER['HTTP_HOST'] . '-' . \$request_uri[0] . '.php';" . PHP_EOL;
219 $string .= "\t" . '}' . PHP_EOL;
220 $string .= '}' . PHP_EOL;
221
222 $string .= "\$config_locations[] = WP_CONTENT_DIR . '/pc-config/config-' . \$_SERVER['HTTP_HOST'] . '.php';" . PHP_EOL . PHP_EOL;
223
224 $string .= 'foreach ( $config_locations as $config_file ) {' . PHP_EOL;
225 $string .= "\t" . 'if ( @file_exists( $config_file ) ) {' . PHP_EOL;
226 $string .= "\t" . "\t" . 'include( $config_file );' . PHP_EOL;
227 $string .= "\t" . "\t" . 'break;' . PHP_EOL;
228 $string .= "\t" . '}' . PHP_EOL;
229 $string .= '}' . PHP_EOL . PHP_EOL;
230
231 $string .= "if ( ! isset( \$GLOBALS['powered_cache_options'] ) ) {" . PHP_EOL;
232 $string .= "\t" . 'return;' . PHP_EOL;
233 $string .= '}' . PHP_EOL . PHP_EOL;
234
235 $string .= 'if ( defined( \'POWERED_CACHE_ADVANCED_CACHE_DROPIN\') && @file_exists( POWERED_CACHE_ADVANCED_CACHE_DROPIN ) ) {' . PHP_EOL;
236 $string .= "\t" . 'include( POWERED_CACHE_ADVANCED_CACHE_DROPIN );' . PHP_EOL;
237 $string .= '} elseif ( @file_exists( \'' . POWERED_CACHE_DROPIN_DIR . 'page-cache.php' . '\' ) ) {' . PHP_EOL;
238 $string .= "\t" . 'include( \'' . POWERED_CACHE_DROPIN_DIR . 'page-cache.php' . '\' );' . PHP_EOL;
239 $string .= '} else {' . PHP_EOL;
240 $string .= "\t" . 'define( \'POWERED_CACHE_PAGE_CACHING_HAS_PROBLEM\', true );' . PHP_EOL;
241 $string .= '}';
242
243 /**
244 * Filters advanced-cache.php file contents.
245 *
246 * @hook powered_cache_advanced_cache_file_content
247 *
248 * @param {string} $string The content of the advanced-cache.php file
249 *
250 * @return {string} New value.
251 *
252 * @since 1.0
253 */
254 return apply_filters( 'powered_cache_advanced_cache_file_content', $string );
255 }
256
257
258 /**
259 * Define WP_CACHE constant
260 *
261 * @param bool $status The status of the caching
262 *
263 * @return bool
264 * @since 1.0
265 */
266 public function define_wp_cache( $status ) {
267 $config_path = $this->find_wp_config_file();
268
269 if ( ! $config_path ) {
270 return false;
271 }
272
273 if ( defined( 'WP_CACHE' ) && WP_CACHE === $status ) {
274 return true;
275 }
276
277 $config_file_string = file_get_contents( $config_path );
278
279 // Config file is empty. Maybe couldn't read it?
280 if ( empty( $config_file_string ) ) {
281 return false;
282 }
283
284 $config_file = preg_split( "#(\r\n|\r|\n)#", $config_file_string );
285 $line_key = false;
286
287 foreach ( $config_file as $key => $line ) {
288 if ( ! preg_match( '/^\s*define\(\s*(\'|")([A-Z_]+)(\'|")(.*)/', $line, $match ) ) {
289 continue;
290 }
291
292 if ( 'WP_CACHE' === $match[2] ) {
293 $line_key = $key;
294 }
295 }
296
297 if ( false !== $line_key ) {
298 unset( $config_file[ $line_key ] );
299 }
300
301 $status_string = ( $status ) ? 'true' : 'false';
302
303 array_shift( $config_file );
304 array_unshift( $config_file, '<?php', "define( 'WP_CACHE', $status_string ); // Powered Cache" );
305
306 foreach ( $config_file as $key => $line ) {
307 if ( '' === $line ) {
308 unset( $config_file[ $key ] );
309 }
310 }
311
312 if ( ! file_put_contents( $config_path, implode( PHP_EOL, $config_file ) ) ) {
313 return false;
314 }
315
316 return true;
317 }
318
319 /**
320 * seeking wp-config file
321 *
322 * @return bool|string
323 * @since 1.0
324 */
325 public function find_wp_config_file() {
326 $file = '/wp-config.php';
327
328 for ( $i = 1; $i <= 3; $i ++ ) {
329 if ( $i > 1 ) {
330 $file = '/..' . $file;
331 }
332
333 if ( file_exists( untrailingslashit( ABSPATH ) . $file ) ) {
334 $config_path = untrailingslashit( ABSPATH ) . $file;
335 break;
336 }
337 }
338
339 if ( ! isset( $config_path ) ) {
340 return false;
341 }
342
343 return $config_path;
344 }
345
346 /**
347 * Create .htaccess file based on current setting preferences
348 *
349 * @param bool $enable Configure .htaccess automatically when it's true
350 *
351 * @return bool
352 * @since 1.0
353 */
354 public function configure_htaccess( $enable = true ) {
355 if ( ! function_exists( '\get_home_path' ) ) {
356 require_once ABSPATH . '/wp-admin/includes/file.php';
357 }
358
359 $htaccess_file = get_home_path() . '.htaccess';
360 $settings = \PoweredCache\Utils\get_settings();
361
362 /**
363 * Filters whether automatically update or not update .htaccess file
364 *
365 * @hook powered_cache_auto_htaccess_update
366 *
367 * @param {boolean} true to automatic update.
368 *
369 * @return {boolean} New value.
370 *
371 * @since 1.1.1
372 */
373 if ( true !== apply_filters( 'powered_cache_auto_htaccess_update', true ) ) {
374 return false;
375 }
376
377 /**
378 * Apache users can control automatic configuration
379 *
380 * @since 1.2
381 */
382 $automatic_configuration = $settings['auto_configure_htaccess'];
383
384 if ( is_multisite() && ! POWERED_CACHE_IS_NETWORK ) {
385 $automatic_configuration = false; // individual sites shouldn't use .htaccess on multisite
386 }
387
388 if ( ! $automatic_configuration ) {
389 return false;
390 }
391
392 if ( is_writable( $htaccess_file ) ) {
393 $contents = file_get_contents( $htaccess_file );
394
395 // clean up
396 $contents = preg_replace( '/# BEGIN POWERED CACHE(.*)# END POWERED CACHE\s*?/isU', '', $contents );
397
398 if ( false === $enable ) {
399 return file_put_contents( $htaccess_file, $contents );
400 }
401
402 $rules = $this->htaccess_rules();
403 $contents = $rules . $contents;
404
405 // Update the .htacces file
406 if ( ! file_put_contents( $htaccess_file, $contents ) ) {
407 return false;
408 }
409
410 return true;
411 }
412
413 return false;
414 }
415
416 /**
417 * Prepares .htaccess rules for the caching
418 *
419 * @return string $rules
420 * @since 1.1
421 */
422 public function htaccess_rules() {
423 $rules = '';
424 $settings = \PoweredCache\Utils\get_settings();
425
426 /**
427 * Filters base htaccess rules
428 *
429 * @hook powered_cache_pre_htaccess
430 *
431 * @param {string} empty htaccesss rules by default
432 *
433 * @return {boolean} New value.
434 *
435 * @since 1.1
436 */
437 $rules .= apply_filters( 'powered_cache_pre_htaccess', '' );
438
439 $rules .= '# BEGIN POWERED CACHE' . PHP_EOL;
440
441 /**
442 * Filters whether doing the configuration for browser cache or not
443 *
444 * @hook powered_cache_browser_cache
445 *
446 * @param {boolean} true for creating .htaccess rules for browser cache
447 *
448 * @return {boolean} New value.
449 *
450 * @since 1.1
451 */
452 if ( apply_filters( 'powered_cache_browser_cache', true ) ) {
453 $wp_mime_types = wp_get_mime_types();
454 $mime_types = array_flip( $wp_mime_types );
455 // mimes
456 $rules .= '<IfModule mod_mime.c>' . PHP_EOL;
457 foreach ( $mime_types as $mime_type => $ext ) {
458 $ext_str = '.' . str_replace( '|', ' .', $ext );
459 $rules .= ' AddType ' . $mime_type . ' ' . $ext_str . PHP_EOL;
460 }
461 $rules .= '</IfModule>' . PHP_EOL;
462
463 // set expire time
464
465 $rules .= '<IfModule mod_expires.c>' . PHP_EOL;
466 $rules .= ' ExpiresActive On' . PHP_EOL;
467 $rules .= ' ExpiresByType text/html "access plus 0 seconds"' . PHP_EOL;
468 $rules .= ' ExpiresByType text/richtext "access plus 0 seconds"' . PHP_EOL;
469 $rules .= ' ExpiresByType text/plain "access plus 0 seconds"' . PHP_EOL;
470 $rules .= ' ExpiresByType text/xsd "access plus 0 seconds"' . PHP_EOL;
471 $rules .= ' ExpiresByType text/xsl "access plus 0 seconds"' . PHP_EOL;
472 $rules .= ' ExpiresByType text/xml "access plus 0 seconds"' . PHP_EOL;
473 $rules .= ' ExpiresByType text/cache-manifest "access plus 0 seconds"' . PHP_EOL;
474
475 foreach ( $mime_types as $mime_type => $ext ) {
476
477 if ( in_array( $mime_type, array( 'text/html', 'text/richtext', 'text/plain', 'text/xsd', 'text/xsl', 'text/xml', 'text/cache-manifest' ), true ) ) {
478 continue;
479 }
480
481 $expiry_time = $this->get_browser_cache_lifespan( $mime_type );
482
483 $rules .= ' ExpiresByType ' . $mime_type . ' "' . $expiry_time . '"' . PHP_EOL;
484 }
485
486 $font_types = [
487 'application/x-font-ttf',
488 'application/x-font-woff ',
489 'application/x-font-woff2',
490 'font/opentype',
491 'application/vnd.ms-fontobject',
492 'application/font-sfnt',
493 'image/svg+xml',
494 ];
495
496 foreach ( $font_types as $mime_type ) {
497 $expiry_time = $this->get_browser_cache_lifespan( $mime_type );
498
499 $rules .= ' ExpiresByType ' . $mime_type . ' "' . $expiry_time . '"' . PHP_EOL;
500 }
501
502 $rules .= '</IfModule>' . PHP_EOL;
503 }
504
505 // Add cors rules
506 if ( $settings['enable_cdn'] ) {
507
508 /**
509 * Add CORS configuration
510 *
511 * @link https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_enabled_image
512 * @since 2.1
513 */
514 $rules .= '<IfModule mod_setenvif.c>' . PHP_EOL;
515 $rules .= ' <IfModule mod_headers.c>' . PHP_EOL;
516 $rules .= ' <FilesMatch "\.(avifs?|bmp|cur|gif|ico|jpe?g|jxl|a?png|svgz?|webp)$">' . PHP_EOL;
517 $rules .= ' SetEnvIf Origin ":" IS_CORS' . PHP_EOL;
518 $rules .= ' Header set Access-Control-Allow-Origin "*" env=IS_CORS' . PHP_EOL;
519 $rules .= ' </FilesMatch>' . PHP_EOL;
520 $rules .= ' </IfModule>' . PHP_EOL;
521 $rules .= '</IfModule>' . PHP_EOL . PHP_EOL;
522
523 // configure fonts
524 $rules .= '<FilesMatch "\.(ttf|ttc|otf|eot|woff|woff2|font.css)$">' . PHP_EOL;
525 $rules .= ' <IfModule mod_headers.c>' . PHP_EOL;
526 $rules .= ' Header set Access-Control-Allow-Origin "*"' . PHP_EOL;
527 $rules .= ' </IfModule>' . PHP_EOL;
528 $rules .= '</FilesMatch>' . PHP_EOL . PHP_EOL;
529 }
530
531 // gzip
532 $rules .= '<IfModule mod_deflate.c>' . PHP_EOL;
533 $rules .= ' <IfModule mod_headers.c>' . PHP_EOL;
534 $rules .= ' Header set X-Powered-By "Powered Cache"' . PHP_EOL;
535 $rules .= ' Header append Vary User-Agent env=!dont-vary' . PHP_EOL;
536 $rules .= ' </IfModule>' . PHP_EOL;
537 $rules .= ' AddOutputFilterByType DEFLATE text/css text/x-component application/x-javascript application/javascript text/javascript text/x-js text/html text/richtext image/svg+xml text/plain text/xsd text/xsl text/xml image/bmp application/java application/msword application/vnd.ms-fontobject application/x-msdownload image/x-icon application/json application/vnd.ms-access application/vnd.ms-project application/x-font-otf application/vnd.ms-opentype application/vnd.oasis.opendocument.database application/vnd.oasis.opendocument.chart application/vnd.oasis.opendocument.formula application/vnd.oasis.opendocument.graphics application/vnd.oasis.opendocument.presentation application/vnd.oasis.opendocument.spreadsheet application/vnd.oasis.opendocument.text audio/ogg application/pdf application/vnd.ms-powerpoint application/x-shockwave-flash image/tiff application/x-font-ttf application/vnd.ms-opentype audio/wav application/vnd.ms-write application/font-woff application/font-woff2 application/vnd.ms-excel'
538 . PHP_EOL; // phpcs:ignore
539 $rules .= ' <IfModule mod_mime.c>' . PHP_EOL;
540 $rules .= ' AddOutputFilter DEFLATE js css htm html xml' . PHP_EOL;
541 $rules .= ' </IfModule>' . PHP_EOL;
542
543 $rules .= '</IfModule>' . PHP_EOL;
544
545 // remove etag
546 $rules .= '<IfModule mod_headers.c>' . PHP_EOL;
547 $rules .= 'Header unset ETag' . PHP_EOL;
548 $rules .= '</IfModule>' . PHP_EOL . PHP_EOL;
549
550 /**
551 * Filters whether doing the configuration for htaccess rewrite cache or not
552 *
553 * @hook powered_cache_mod_rewrite
554 *
555 * @param {boolean} true for creating .htaccess rewrite rules.
556 *
557 * @return {boolean} New value.
558 *
559 * @since 2.0
560 */
561 if ( apply_filters( 'powered_cache_mod_rewrite', true ) ) { // rewrite
562
563 // add gzip type for .html.gz format
564 if ( $settings['gzip_compression'] ) {
565 $rules .= '<IfModule mod_mime.c>' . PHP_EOL;
566 $rules .= ' AddType text/html .html.gz' . PHP_EOL;
567 $rules .= ' AddEncoding gzip .gz' . PHP_EOL;
568 $rules .= '</IfModule>' . PHP_EOL;
569 $rules .= '<IfModule mod_setenvif.c>' . PHP_EOL;
570 $rules .= ' SetEnvIfNoCase Request_URI \.html.gz$ no-gzip' . PHP_EOL;
571 $rules .= '</IfModule>' . PHP_EOL;
572 }
573
574 $env_powered_cache_ua = '';
575 $env_powered_cache_ssl = '';
576 $env_powered_cache_enc = '';
577
578 $rewrite_base = wp_parse_url( home_url() );
579 $rewrite_base = isset( $rewrite_base['path'] ) ? trailingslashit( $rewrite_base['path'] ) : '/';
580
581 $rules .= '<IfModule mod_rewrite.c>' . PHP_EOL;
582 $rules .= ' RewriteEngine On' . PHP_EOL;
583 $rules .= ' RewriteBase ' . $rewrite_base . PHP_EOL;
584 $rules .= ' AddDefaultCharset UTF-8 ' . PHP_EOL;
585
586 if ( true === $settings['cache_mobile'] && true === $settings['cache_mobile_separate_file'] ) {
587 $mobile_browsers = addcslashes( implode( '|', preg_split( '/[\s*,\s*]*,+[\s*,\s*]*/', mobile_browsers() ) ), ' ' );
588 $mobile_prefixes = addcslashes( implode( '|', preg_split( '/[\s*,\s*]*,+[\s*,\s*]*/', mobile_prefixes() ) ), ' ' );
589 // mobile env set
590 $rules .= ' RewriteCond %{HTTP_USER_AGENT} (' . $mobile_browsers . ') [NC]' . PHP_EOL;
591 $rules .= ' RewriteRule .* - [E=PC_UA:-mobile]' . PHP_EOL;
592 $rules .= ' RewriteCond %{HTTP_USER_AGENT} ^(' . $mobile_prefixes . ') [NC]' . PHP_EOL;
593 $rules .= ' RewriteRule .* - [E=PC_UA:-mobile]' . PHP_EOL;
594 $env_powered_cache_ua = '%{ENV:PC_UA}';
595 }
596
597 $rules .= ' RewriteCond %{HTTPS} on [OR]' . PHP_EOL;
598 $rules .= ' RewriteCond %{SERVER_PORT} ^443$ [OR]' . PHP_EOL;
599 $rules .= ' RewriteCond %{HTTP:X-Forwarded-Proto} https' . PHP_EOL;
600 $rules .= ' RewriteRule .* - [E=PC_SSL:-https]' . PHP_EOL;
601 $env_powered_cache_ssl = '%{ENV:PC_SSL}';
602
603 if ( true === $settings['gzip_compression'] ) {
604 $rules .= ' RewriteCond %{HTTP:Accept-Encoding} gzip' . PHP_EOL;
605 $rules .= ' RewriteRule .* - [E=PC_ENC:.gz]' . PHP_EOL;
606 $env_powered_cache_enc = '%{ENV:PC_ENC}';
607 }
608
609 $rules .= ' RewriteCond %{REQUEST_METHOD} !=POST' . PHP_EOL;
610 $rules .= ' RewriteCond %{QUERY_STRING} =""' . PHP_EOL;
611
612 if ( permalink_structure_has_trailingslash() ) {
613 $rules .= ' RewriteCond %{REQUEST_URI} !^.*[^/]$' . PHP_EOL;
614 $rules .= ' RewriteCond %{REQUEST_URI} !^.*//.*$' . PHP_EOL;
615 }
616
617 // Get root base
618 $site_root = wp_parse_url( site_url() );
619 $site_root = isset( $site_root['path'] ) ? trailingslashit( $site_root['path'] ) : '';
620
621 // reject user agent
622 $rejected_user_agents = (array) AdvancedCache::get_rejected_user_agents();
623 if ( ! empty( $rejected_user_agents ) ) {
624 $rules .= ' RewriteCond %{HTTP_USER_AGENT} !^(' . implode( '|', $rejected_user_agents ) . ').* [NC]' . PHP_EOL;
625 }
626
627 // rejected cookies
628 $rejected_cookies = AdvancedCache::get_rejected_cookies();
629 if ( ! empty( $rejected_cookies ) ) {
630 $rules .= ' RewriteCond %{HTTP:Cookie} !(' . implode( '|', $rejected_cookies ) . ') [NC]' . PHP_EOL;
631 }
632
633 // dont cache fbexternal
634 $rules .= ' RewriteCond %{HTTP_USER_AGENT} !^(facebookexternalhit).* [NC]' . PHP_EOL;
635
636 $cache_location = get_cache_dir();
637 $cache_location = untrailingslashit( $cache_location ) . '/powered-cache/';
638 if ( strpos( ABSPATH, $cache_location ) === false ) {
639 $cache_path = str_replace( $_SERVER['DOCUMENT_ROOT'], '', $cache_location ); // clean doc root
640 } else {
641 $cache_path = $site_root . str_replace( ABSPATH, '', $cache_location );
642 }
643
644 /**
645 * Filters whether running on 1and1_hosting or not
646 *
647 * @hook powered_cache_maybe_1and1_hosting
648 *
649 * @param {boolean} $status true if /kunden/homepage directory exists
650 *
651 * @return {boolean} New value.
652 *
653 * @since 1.1
654 */
655 if ( apply_filters( 'powered_cache_maybe_1and1_hosting', ( 0 === strpos( $_SERVER['DOCUMENT_ROOT'], '/kunden/homepage/' ) ) ) ) {
656 $rules .= ' RewriteCond "' . str_replace( '/kunden/homepage/', '/', $cache_location ) . '%{HTTP_HOST}' . '%{REQUEST_URI}/index' . $env_powered_cache_ssl . $env_powered_cache_ua . '.html' . $env_powered_cache_enc . '" -f' . PHP_EOL;
657 } else {
658 $rules .= ' RewriteCond "%{DOCUMENT_ROOT}/' . ltrim( $cache_path, '/' ) . '%{HTTP_HOST}' . '%{REQUEST_URI}/index' . $env_powered_cache_ssl . $env_powered_cache_ua . '.html' . $env_powered_cache_enc . '" -f' . PHP_EOL;
659 }
660 $rules .= ' RewriteRule .* "' . $cache_path . '%{HTTP_HOST}' . '%{REQUEST_URI}/index' . $env_powered_cache_ssl . $env_powered_cache_ua . '.html' . $env_powered_cache_enc . '" [L]' . PHP_EOL;
661
662 if ( $settings['gzip_compression'] ) {
663 $rules .= ' # prevent mod_deflate double gzip' . PHP_EOL;
664 $rules .= ' RewriteRule \.html\.gz$ - [T=text/html,E=no-gzip:1]' . PHP_EOL;
665 }
666
667 $rules .= '</IfModule>' . PHP_EOL;
668 }
669
670 /**
671 * Filters post htaccess rules
672 *
673 * @hook powered_cache_after_htaccess
674 *
675 * @param {string} empty htaccesss rules by default
676 *
677 * @return {boolean} New value.
678 *
679 * @since 2.0
680 */
681 $rules .= apply_filters( 'powered_cache_after_htaccess', '' );
682
683 $rules .= '# END POWERED CACHE' . PHP_EOL;
684
685 return $rules;
686 }
687
688 /**
689 * Make sure directory listing disabled
690 *
691 * @return bool
692 * @since 1.2
693 */
694 public function protect_cache_dir() {
695
696 if ( ! file_exists( get_cache_dir() ) ) {
697 mkdir( get_cache_dir() );
698 }
699
700 $file = get_cache_dir() . '.htaccess';
701
702 $file_string = 'Options -Indexes';
703
704 if ( ! file_put_contents( $file, $file_string ) ) {
705 return false;
706 }
707
708 return true;
709 }
710
711 /**
712 * Prepare config name
713 *
714 * @param bool $is_network Whether network-wide config name or not
715 *
716 * @return string configuration file name
717 * @since 2.0
718 */
719 public function get_config_filename( $is_network ) {
720 if ( $is_network ) {
721 return 'config-network.php';
722 } else {
723 $url_parts = wp_parse_url( home_url() );
724 $config_name = 'config-' . $url_parts['host'];
725
726 if ( is_multisite() && ! is_subdomain_install() ) {
727 if ( ! is_main_site( get_current_blog_id() ) && ! empty( $url_parts['path'] ) ) {
728 $subdir_name = ltrim( $url_parts['path'], '/' );
729 $config_name .= '-' . $subdir_name;
730 }
731 }
732
733 $config_name .= '.php';
734
735 return $config_name;
736 }
737 }
738
739 /**
740 * Save settings to file
741 *
742 * @param array $configuration plugin settings
743 * @param bool $network_wide Whether network-wide configuration or not
744 *
745 * @return bool
746 * @since 1.0
747 */
748 public function save_to_file( $configuration, $network_wide = false ) {
749 $config_dir = WP_CONTENT_DIR . '/pc-config';
750 $config_file_name = $this->get_config_filename( $network_wide );
751
752 if ( ! file_exists( $config_dir ) ) {
753 mkdir( $config_dir );
754 }
755
756 $config_file = trailingslashit( $config_dir ) . $config_file_name;
757
758 if ( ! file_exists( $config_file ) ) {
759 touch( $config_file );
760 }
761
762 $configuration['cache_location'] = get_cache_dir();
763
764 $config_file_string = '<?php' . PHP_EOL . "defined( 'ABSPATH' ) || exit;" . PHP_EOL . PHP_EOL;
765
766 $config_file_string .= "\$GLOBALS['powered_cache_options'] = " . var_export( $configuration, true ) . ';' . PHP_EOL . PHP_EOL;
767
768 // mobile cache varibales
769 $config_file_string .= '$powered_cache_mobile_browsers = ' . var_export( mobile_browsers(), true ) . ';' . PHP_EOL;
770 $config_file_string .= '$powered_cache_mobile_prefixes = ' . var_export( mobile_prefixes(), true ) . ';' . PHP_EOL;
771 $config_file_string .= '$powered_cache_rejected_user_agents = ' . var_export( AdvancedCache::get_rejected_user_agents(), true ) . ';' . PHP_EOL;
772 $config_file_string .= '$powered_cache_rejected_cookies = ' . var_export( AdvancedCache::get_rejected_cookies(), true ) . ';' . PHP_EOL;
773 $config_file_string .= '$powered_cache_vary_cookies = ' . var_export( AdvancedCache::get_vary_cookies(), true ) . ';' . PHP_EOL;
774 $config_file_string .= '$powered_cache_rejected_uri = ' . var_export( AdvancedCache::get_rejected_uri(), true ) . ';' . PHP_EOL;
775 $config_file_string .= '$powered_cache_accepted_query_strings = ' . var_export( AdvancedCache::get_accepted_query_strings(), true ) . ';' . PHP_EOL;
776
777 if ( permalink_structure_has_trailingslash() ) {
778 $config_file_string .= '$powered_cache_slash_check = true;' . PHP_EOL;
779 } else {
780 $config_file_string .= '$powered_cache_slash_check = false;' . PHP_EOL;
781 }
782
783 /**
784 * Fires before writing configuration file.
785 *
786 * @hook powered_cache_create_config_file
787 *
788 * @param {string} $config_file The path of the configuration file.
789 * @param {string} $config_file_string The contents of the configurations.
790 * @param {bool} $network_wide Whether network-wide configuration or not.
791 *
792 * @since 2.0
793 */
794 do_action( 'powered_cache_create_config_file', $config_file, $config_file_string, $network_wide );
795
796 if ( ! file_put_contents( $config_file, $config_file_string ) ) {
797 return false;
798 }
799
800 return true;
801 }
802
803
804 /**
805 * Prepares nginx configuration
806 *
807 * @return string $contents nginx rules
808 * @since 1.2 trailingslash rule added
809 *
810 * @since 1.1
811 */
812 public function nginx_rules() {
813 $settings = \PoweredCache\Utils\get_settings();
814
815 $contents = '';
816 $contents .= '##### POWERED CACHE CONF #####' . PHP_EOL;
817 $contents .= 'set $cache_uri $request_uri;' . PHP_EOL;
818 $contents .= 'set $pc_ssl "";' . PHP_EOL;
819 $contents .= 'set $pc_enc "";' . PHP_EOL;
820 $contents .= 'set $pc_ua "";' . PHP_EOL . PHP_EOL;
821
822 // post
823 $contents .= '# POST requests and urls with a query string should always go to PHP' . PHP_EOL;
824 $contents .= 'if ($request_method = POST) {' . PHP_EOL;
825 $contents .= ' set $cache_uri \'null cache\';' . PHP_EOL;
826 $contents .= '}' . PHP_EOL . PHP_EOL;
827
828 $contents .= 'location = /favicon.ico { log_not_found off; access_log off; }' . PHP_EOL;
829 $contents .= 'location = /robots.txt { try_files $uri $uri/ /index.php?$args; log_not_found off; access_log off; }' . PHP_EOL . PHP_EOL;
830
831 // query string
832 $contents .= 'if ($query_string != "") {' . PHP_EOL;
833 $contents .= ' set $cache_uri \'null cache\';' . PHP_EOL;
834 $contents .= '}' . PHP_EOL . PHP_EOL;
835
836 // Add CORS rules
837 if ( $settings['enable_cdn'] ) {
838 $contents .= 'location ~* .(jpg|jpeg|png|gif|ico|css|js|svg|eot|woff|woff2|ttf|otf)$ {' . PHP_EOL;
839 $contents .= 'add_header Access-Control-Allow-Origin *;' . PHP_EOL;
840 $contents .= '}' . PHP_EOL . PHP_EOL;
841 }
842
843 /**
844 * Documented in htaccess config
845 */
846 if ( apply_filters( 'powered_cache_browser_cache', true ) ) {
847 $contents .= 'location ~* .(jpg|jpeg|png|gif|ico|css|js|svg|eot|woff|woff2|ttf|otf)$ {' . PHP_EOL;
848 $contents .= ' expires 6M;' . PHP_EOL;
849 $contents .= '}' . PHP_EOL . PHP_EOL;
850 }
851
852 // https
853 $contents .= '# HTTPS' . PHP_EOL;
854 $contents .= 'if ($https = "on") {' . PHP_EOL;
855 $contents .= ' set $pc_ssl "-https";' . PHP_EOL;
856 $contents .= '}' . PHP_EOL . PHP_EOL;
857
858 $contents .= '# Don\'t cache uris containing the following segments' . PHP_EOL;
859 $contents .= 'if ($request_uri ~* "(/wp-admin/|/xmlrpc.php|/wp-(app|cron|login|register|mail).php|wp-.*.php|/feed/|index.php|wp-comments-popup.php|wp-links-opml.php|wp-locations.php|sitemap(_index)?.xml|[a-z0-9_-]+-sitemap([0-9]+)?.xml)") {' . PHP_EOL;
860 $contents .= ' set $cache_uri \'null cache\';' . PHP_EOL;
861 $contents .= '}' . PHP_EOL . PHP_EOL;
862
863 $rejected_user_agents = (array) AdvancedCache::get_rejected_user_agents();
864
865 $contents .= '# Don\'t use the cache for rejected agents' . PHP_EOL;
866 $contents .= 'if ($http_user_agent ~* "(' . implode( '|', $rejected_user_agents ) . '")) {' . PHP_EOL;
867 $contents .= ' set $cache_uri \'null cache\';' . PHP_EOL;
868 $contents .= '}' . PHP_EOL . PHP_EOL;
869
870 $rejected_cookies = (array) AdvancedCache::get_rejected_cookies();
871
872 $contents .= '# Don\'t use the cache for logged in users or recent commenters' . PHP_EOL;
873 $contents .= 'if ($http_cookie ~* "wordpress_[a-f0-9]+|' . implode( '|', $rejected_cookies ) . '") {' . PHP_EOL;
874 $contents .= ' set $cache_uri \'null cache\';' . PHP_EOL;
875 $contents .= '}' . PHP_EOL . PHP_EOL;
876
877 $contents .= 'if ($http_x_wap_profile) {' . PHP_EOL;
878 $contents .= ' set $pc_ua \'-mobile\';' . PHP_EOL;
879 $contents .= '}' . PHP_EOL . PHP_EOL;
880
881 if ( true === $settings['cache_mobile'] && true === $settings['cache_mobile_separate_file'] ) {
882 $mobile_browsers = addcslashes( implode( '|', preg_split( '/[\s*,\s*]*,+[\s*,\s*]*/', mobile_browsers() ) ), ' ' );
883 $mobile_prefixes = addcslashes( implode( '|', preg_split( '/[\s*,\s*]*,+[\s*,\s*]*/', mobile_prefixes() ) ), ' ' );
884
885 $contents .= 'if ($http_user_agent ~* (' . $mobile_browsers . ')) {' . PHP_EOL;
886 $contents .= ' set $pc_ua \'-mobile\';' . PHP_EOL;
887 $contents .= '}' . PHP_EOL . PHP_EOL;
888
889 $contents .= 'if ($http_user_agent ~* (' . $mobile_prefixes . ')) {' . PHP_EOL;
890 $contents .= ' set $pc_ua \'-mobile\';' . PHP_EOL;
891 $contents .= '}' . PHP_EOL . PHP_EOL;
892 }
893
894 $cache_suffix = 'html';
895
896 if ( true === $settings['gzip_compression'] ) {
897 $cache_suffix .= '.gz';
898 }
899
900 $contents .= 'location / {' . PHP_EOL;
901 /**
902 * Documented in htaccess config
903 */
904 if ( apply_filters( 'powered_cache_mod_rewrite', true ) ) { // rewrite
905 $contents .= ' add_header X-Powered-Cache nginx;' . PHP_EOL;
906 $contents .= ' try_files /wp-content/cache/powered-cache/$http_host/$cache_uri/index${pc_ssl}${pc_ua}.' . $cache_suffix . ' $uri $uri/ /index.php?$args;' . PHP_EOL;
907
908 } else {
909 $contents .= ' try_files $uri $uri/ /index.php?$args;' . PHP_EOL;
910 }
911
912 $contents .= '}' . PHP_EOL . PHP_EOL;
913
914 if ( permalink_structure_has_trailingslash() ) {
915 $contents .= '# add trailingslash rule' . PHP_EOL;
916 $contents .= 'rewrite ^([^.]*[^/])$ $1/ permanent;' . PHP_EOL;
917 }
918
919 return $contents;
920 }
921
922 /**
923 * Downloads configuration files
924 *
925 * @param string $server type supports apache and nginx
926 *
927 * @since 1.1
928 */
929 public function download_rewrite_rules( $server ) {
930
931 $rules = '';
932 $filename = 'conf';
933
934 if ( 'apache' === $server ) {
935 $rules = $this->htaccess_rules();
936 $filename = '.htaccess_powered_cache';
937 }
938
939 if ( 'nginx' === $server ) {
940 $rules = $this->nginx_rules();
941 $filename = 'poweredcache.conf';
942 }
943
944 nocache_headers();
945 @header( 'Content-Type: text/plain' );
946 @header( 'Content-Disposition: attachment; filename="' . $filename . '"' );
947 @header( 'Content-Transfer-Encoding: binary' );
948 @header( 'Content-Length: ' . strlen( $rules ) );
949 @header( 'Connection: close' );
950 echo $rules; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
951 exit;
952 }
953
954 /**
955 * Make the caching configurations with given options
956 *
957 * @param array $settings Plugin settings
958 * @param bool $network_wide Whether network-wide configuration or not
959 *
960 * @since 2.0
961 */
962 public function save_configuration( $settings, $network_wide = false ) {
963
964 if ( can_configure_object_cache() ) {
965 $this->setup_object_cache( $settings['object_cache'] );
966 }
967
968 $this->setup_page_cache( $settings['enable_page_cache'] );
969 $private_settings = [ 'cloudflare_email', 'cloudflare_api_key', 'cloudflare_zone' ];
970
971 foreach ( $private_settings as $setting_key ) {
972 unset( $settings[ $setting_key ] );
973 }
974
975 $this->save_to_file( $settings, $network_wide );
976 }
977
978 /**
979 * Clean-up all the configurations and cache related footprints
980 */
981 public function clean_up() {
982 $object_cache_dropin = untrailingslashit( WP_CONTENT_DIR ) . '/object-cache.php';
983 if ( file_exists( $object_cache_dropin ) && false !== strpos( file_get_contents( $object_cache_dropin ), 'POWERED_OBJECT_CACHE' ) ) {
984 unlink( $object_cache_dropin );
985 }
986
987 $advanced_cache_dropin = untrailingslashit( WP_CONTENT_DIR ) . '/advanced-cache.php';
988 if ( file_exists( $advanced_cache_dropin ) ) {
989 unlink( $advanced_cache_dropin );
990 }
991
992 $this->define_wp_cache( false );
993 $this->configure_htaccess( false );
994 $this->protect_cache_dir();
995
996 if ( ! file_exists( get_cache_dir() ) ) {
997 remove_dir( get_cache_dir() );
998 }
999
1000 $config_dir = WP_CONTENT_DIR . '/pc-config';
1001
1002 if ( is_multisite() ) {
1003 $config_file_name = $this->get_config_filename( POWERED_CACHE_IS_NETWORK );
1004 $config_file = trailingslashit( $config_dir ) . $config_file_name;
1005 if ( file_exists( $config_file ) ) {
1006 unlink( $config_file );
1007 }
1008 } else { // remove entire configuration directory on single site setup
1009 remove_dir( $config_dir );
1010 }
1011
1012 /**
1013 * Fires after cleanup all configurations
1014 *
1015 * @hook powered_cache_after_clean_up
1016 *
1017 * @since 2.0
1018 */
1019 do_action( 'powered_cache_after_clean_up' );
1020
1021 }
1022
1023 /**
1024 * Apache allows both format like A2592000 => "access plus 1 month"
1025 * A => access, M => Modified
1026 *
1027 * @param string $mime_type Mimetype
1028 *
1029 * @return string cache lifespan for apache
1030 * @see http://httpd.apache.org/docs/current/mod/mod_expires.html
1031 * @since 2.0
1032 */
1033 public function get_browser_cache_lifespan( $mime_type ) {
1034 switch ( $mime_type ) {
1035 case 'text/css':
1036 case 'application/javascript':
1037 /**
1038 * Filters TTL for CSS/JS files.
1039 *
1040 * @hook powered_cache_browser_cache_assets_lifespan
1041 *
1042 * @param {string} $expiry_time .htaccess lifespan
1043 *
1044 * @return {string} New value.
1045 *
1046 * @since 1.1
1047 */
1048 $expiry_time = apply_filters( 'powered_cache_browser_cache_assets_lifespan', 'access plus 1 year' );
1049 break;
1050 case 'image/jpeg':
1051 case 'image/gif':
1052 case 'image/png':
1053 case 'image/bmp':
1054 case 'image/tiff':
1055 case 'image/webp':
1056 case 'image/heic':
1057 $expiry_time = 'access plus 6 months';
1058 break;
1059 case 'image/x-icon':
1060 $expiry_time = 'access plus 1 week'; // favicon
1061 break;
1062 default:
1063 /**
1064 * Filters default TTL for browser cache lifespan.
1065 *
1066 * @hook powered_cache_browser_cache_default_lifespan
1067 *
1068 * @param {string} $expiry_time .htaccess lifespan
1069 *
1070 * @return {string} New value.
1071 *
1072 * @since 1.1
1073 */
1074 $expiry_time = apply_filters( 'powered_cache_browser_cache_default_lifespan', 'access plus 1 month' );
1075 }
1076
1077 /**
1078 * Filters TTL for browser cache lifespan.
1079 *
1080 * @hook powered_cache_browser_cache_lifespan
1081 *
1082 * @param {string} $expiry_time .htaccess lifespan
1083 * @param {string} $mime_type mime type
1084 *
1085 * @return {string} New value.
1086 *
1087 * @since 2.0
1088 */
1089 $expiry_time = apply_filters( 'powered_cache_browser_cache_lifespan', $expiry_time, $mime_type );
1090
1091 return $expiry_time;
1092 }
1093
1094
1095 }
1096