PluginProbe
Powered Cache – Caching and Optimization for WordPress – Easily Improve PageSpeed & Web Vitals Score / 2.0.3
Powered Cache – Caching and Optimization for WordPress – Easily Improve PageSpeed & Web Vitals Score v2.0.3
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.0.3, at includes/classes/Config.php

1,063 lines 33.8 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/is', '', $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 // gzip
506 $rules .= '<IfModule mod_deflate.c>' . PHP_EOL;
507 $rules .= ' <IfModule mod_headers.c>' . PHP_EOL;
508 $rules .= ' Header set X-Powered-By "Powered Cache"' . PHP_EOL;
509 $rules .= ' Header append Vary User-Agent env=!dont-vary' . PHP_EOL;
510 $rules .= ' </IfModule>' . PHP_EOL;
511 $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'
512 . PHP_EOL; // phpcs:ignore
513 $rules .= ' <IfModule mod_mime.c>' . PHP_EOL;
514 $rules .= ' AddOutputFilter DEFLATE js css htm html xml' . PHP_EOL;
515 $rules .= ' </IfModule>' . PHP_EOL;
516
517 $rules .= '</IfModule>' . PHP_EOL;
518
519 // remove etag
520 $rules .= '<IfModule mod_headers.c>' . PHP_EOL;
521 $rules .= 'Header unset ETag' . PHP_EOL;
522 $rules .= '</IfModule>' . PHP_EOL . PHP_EOL;
523
524 /**
525 * Filters whether doing the configuration for htaccess rewrite cache or not
526 *
527 * @hook powered_cache_mod_rewrite
528 *
529 * @param {boolean} true for creating .htaccess rewrite rules.
530 *
531 * @return {boolean} New value.
532 *
533 * @since 2.0
534 */
535 if ( apply_filters( 'powered_cache_mod_rewrite', true ) ) { // rewrite
536
537 // add gzip type for .html.gz format
538 if ( $settings['gzip_compression'] ) {
539 $rules .= '<IfModule mod_mime.c>' . PHP_EOL;
540 $rules .= ' AddType text/html .html.gz' . PHP_EOL;
541 $rules .= ' AddEncoding gzip .gz' . PHP_EOL;
542 $rules .= '</IfModule>' . PHP_EOL;
543 $rules .= '<IfModule mod_setenvif.c>' . PHP_EOL;
544 $rules .= ' SetEnvIfNoCase Request_URI \.html.gz$ no-gzip' . PHP_EOL;
545 $rules .= '</IfModule>' . PHP_EOL;
546 }
547
548 $env_powered_cache_ua = '';
549 $env_powered_cache_ssl = '';
550 $env_powered_cache_enc = '';
551
552 $rewrite_base = wp_parse_url( home_url() );
553 $rewrite_base = isset( $rewrite_base['path'] ) ? trailingslashit( $rewrite_base['path'] ) : '/';
554
555 $rules .= '<IfModule mod_rewrite.c>' . PHP_EOL;
556 $rules .= ' RewriteEngine On' . PHP_EOL;
557 $rules .= ' RewriteBase ' . $rewrite_base . PHP_EOL;
558 $rules .= ' AddDefaultCharset UTF-8 ' . PHP_EOL;
559
560 if ( true === $settings['cache_mobile'] && true === $settings['cache_mobile_separate_file'] ) {
561 $mobile_browsers = addcslashes( implode( '|', preg_split( '/[\s*,\s*]*,+[\s*,\s*]*/', mobile_browsers() ) ), ' ' );
562 $mobile_prefixes = addcslashes( implode( '|', preg_split( '/[\s*,\s*]*,+[\s*,\s*]*/', mobile_prefixes() ) ), ' ' );
563 // mobile env set
564 $rules .= ' RewriteCond %{HTTP_USER_AGENT} (' . $mobile_browsers . ') [NC]' . PHP_EOL;
565 $rules .= ' RewriteRule .* - [E=PC_UA:-mobile]' . PHP_EOL;
566 $rules .= ' RewriteCond %{HTTP_USER_AGENT} ^(' . $mobile_prefixes . ') [NC]' . PHP_EOL;
567 $rules .= ' RewriteRule .* - [E=PC_UA:-mobile]' . PHP_EOL;
568 $env_powered_cache_ua = '%{ENV:PC_UA}';
569 }
570
571 $rules .= ' RewriteCond %{HTTPS} on [OR]' . PHP_EOL;
572 $rules .= ' RewriteCond %{SERVER_PORT} ^443$ [OR]' . PHP_EOL;
573 $rules .= ' RewriteCond %{HTTP:X-Forwarded-Proto} https' . PHP_EOL;
574 $rules .= ' RewriteRule .* - [E=PC_SSL:-https]' . PHP_EOL;
575 $env_powered_cache_ssl = '%{ENV:PC_SSL}';
576
577 if ( true === $settings['gzip_compression'] ) {
578 $rules .= ' RewriteCond %{HTTP:Accept-Encoding} gzip' . PHP_EOL;
579 $rules .= ' RewriteRule .* - [E=PC_ENC:.gz]' . PHP_EOL;
580 $env_powered_cache_enc = '%{ENV:PC_ENC}';
581 }
582
583 $rules .= ' RewriteCond %{REQUEST_METHOD} !=POST' . PHP_EOL;
584 $rules .= ' RewriteCond %{QUERY_STRING} =""' . PHP_EOL;
585
586 if ( permalink_structure_has_trailingslash() ) {
587 $rules .= ' RewriteCond %{REQUEST_URI} !^.*[^/]$' . PHP_EOL;
588 $rules .= ' RewriteCond %{REQUEST_URI} !^.*//.*$' . PHP_EOL;
589 }
590
591 // Get root base
592 $site_root = wp_parse_url( site_url() );
593 $site_root = isset( $site_root['path'] ) ? trailingslashit( $site_root['path'] ) : '';
594
595 // reject user agent
596 $rejected_user_agents = (array) AdvancedCache::get_rejected_user_agents();
597 if ( ! empty( $rejected_user_agents ) ) {
598 $rules .= ' RewriteCond %{HTTP_USER_AGENT} !^(' . implode( '|', $rejected_user_agents ) . ').* [NC]' . PHP_EOL;
599 }
600
601 // rejected cookies
602 $rejected_cookies = AdvancedCache::get_rejected_cookies();
603 if ( ! empty( $rejected_cookies ) ) {
604 $rules .= ' RewriteCond %{HTTP:Cookie} !(' . implode( '|', $rejected_cookies ) . ') [NC]' . PHP_EOL;
605 }
606
607 // dont cache fbexternal
608 $rules .= ' RewriteCond %{HTTP_USER_AGENT} !^(facebookexternalhit).* [NC]' . PHP_EOL;
609
610 $cache_location = get_cache_dir();
611 $cache_location = untrailingslashit( $cache_location ) . '/powered-cache/';
612 if ( strpos( ABSPATH, $cache_location ) === false ) {
613 $cache_path = str_replace( $_SERVER['DOCUMENT_ROOT'], '', $cache_location ); // clean doc root
614 } else {
615 $cache_path = $site_root . str_replace( ABSPATH, '', $cache_location );
616 }
617
618 /**
619 * Filters whether running on 1and1_hosting or not
620 *
621 * @hook powered_cache_maybe_1and1_hosting
622 *
623 * @param {boolean} $status true if /kunden/homepage directory exists
624 *
625 * @return {boolean} New value.
626 *
627 * @since 1.1
628 */
629 if ( apply_filters( 'powered_cache_maybe_1and1_hosting', ( 0 === strpos( $_SERVER['DOCUMENT_ROOT'], '/kunden/homepage/' ) ) ) ) {
630 $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;
631 } else {
632 $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;
633 }
634 $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;
635
636 if ( $settings['gzip_compression'] ) {
637 $rules .= ' # prevent mod_deflate double gzip' . PHP_EOL;
638 $rules .= ' RewriteRule \.html\.gz$ - [T=text/html,E=no-gzip:1]' . PHP_EOL;
639 }
640
641 $rules .= '</IfModule>' . PHP_EOL;
642 }
643
644 /**
645 * Filters post htaccess rules
646 *
647 * @hook powered_cache_after_htaccess
648 *
649 * @param {string} empty htaccesss rules by default
650 *
651 * @return {boolean} New value.
652 *
653 * @since 2.0
654 */
655 $rules .= apply_filters( 'powered_cache_after_htaccess', '' );
656
657 $rules .= '# END POWERED CACHE';
658
659 return $rules;
660 }
661
662 /**
663 * Make sure directory listing disabled
664 *
665 * @return bool
666 * @since 1.2
667 */
668 public function protect_cache_dir() {
669
670 if ( ! file_exists( get_cache_dir() ) ) {
671 mkdir( get_cache_dir() );
672 }
673
674 $file = get_cache_dir() . '.htaccess';
675
676 $file_string = 'Options -Indexes';
677
678 if ( ! file_put_contents( $file, $file_string ) ) {
679 return false;
680 }
681
682 return true;
683 }
684
685 /**
686 * Prepare config name
687 *
688 * @param bool $is_network Whether network-wide config name or not
689 *
690 * @return string configuration file name
691 * @since 2.0
692 */
693 public function get_config_filename( $is_network ) {
694 if ( $is_network ) {
695 return 'config-network.php';
696 } else {
697 $url_parts = wp_parse_url( home_url() );
698 $config_name = 'config-' . $url_parts['host'];
699
700 if ( is_multisite() && ! is_subdomain_install() ) {
701 if ( ! is_main_site( get_current_blog_id() ) && ! empty( $url_parts['path'] ) ) {
702 $subdir_name = ltrim( $url_parts['path'], '/' );
703 $config_name .= '-' . $subdir_name;
704 }
705 }
706
707 $config_name .= '.php';
708
709 return $config_name;
710 }
711 }
712
713 /**
714 * Save settings to file
715 *
716 * @param array $configuration plugin settings
717 * @param bool $network_wide Whether network-wide configuration or not
718 *
719 * @return bool
720 * @since 1.0
721 */
722 public function save_to_file( $configuration, $network_wide = false ) {
723 $config_dir = WP_CONTENT_DIR . '/pc-config';
724 $config_file_name = $this->get_config_filename( $network_wide );
725
726 if ( ! file_exists( $config_dir ) ) {
727 mkdir( $config_dir );
728 }
729
730 $config_file = trailingslashit( $config_dir ) . $config_file_name;
731
732 if ( ! file_exists( $config_file ) ) {
733 touch( $config_file );
734 }
735
736 $configuration['cache_location'] = get_cache_dir();
737
738 $config_file_string = '<?php' . PHP_EOL . "defined( 'ABSPATH' ) || exit;" . PHP_EOL . PHP_EOL;
739
740 $config_file_string .= "\$GLOBALS['powered_cache_options'] = " . var_export( $configuration, true ) . ';' . PHP_EOL . PHP_EOL;
741
742 // mobile cache varibales
743 $config_file_string .= '$powered_cache_mobile_browsers = ' . var_export( mobile_browsers(), true ) . ';' . PHP_EOL;
744 $config_file_string .= '$powered_cache_mobile_prefixes = ' . var_export( mobile_prefixes(), true ) . ';' . PHP_EOL;
745 $config_file_string .= '$powered_cache_rejected_user_agents = ' . var_export( AdvancedCache::get_rejected_user_agents(), true ) . ';' . PHP_EOL;
746 $config_file_string .= '$powered_cache_rejected_cookies = ' . var_export( AdvancedCache::get_rejected_cookies(), true ) . ';' . PHP_EOL;
747 $config_file_string .= '$powered_cache_vary_cookies = ' . var_export( AdvancedCache::get_vary_cookies(), true ) . ';' . PHP_EOL;
748 $config_file_string .= '$powered_cache_rejected_uri = ' . var_export( AdvancedCache::get_rejected_uri(), true ) . ';' . PHP_EOL;
749 $config_file_string .= '$powered_cache_accepted_query_strings = ' . var_export( AdvancedCache::get_accepted_query_strings(), true ) . ';' . PHP_EOL;
750
751 if ( permalink_structure_has_trailingslash() ) {
752 $config_file_string .= '$powered_cache_slash_check = true;' . PHP_EOL;
753 } else {
754 $config_file_string .= '$powered_cache_slash_check = false;' . PHP_EOL;
755 }
756
757 /**
758 * Fires before writing configuration file.
759 *
760 * @hook powered_cache_create_config_file
761 *
762 * @param {string} $config_file The path of the configuration file.
763 * @param {string} $config_file_string The contents of the configurations.
764 * @param {bool} $network_wide Whether network-wide configuration or not.
765 *
766 * @since 2.0
767 */
768 do_action( 'powered_cache_create_config_file', $config_file, $config_file_string, $network_wide );
769
770 if ( ! file_put_contents( $config_file, $config_file_string ) ) {
771 return false;
772 }
773
774 return true;
775 }
776
777
778 /**
779 * Prepares nginx configuration
780 *
781 * @return string $contents nginx rules
782 * @since 1.2 trailingslash rule added
783 *
784 * @since 1.1
785 */
786 public function nginx_rules() {
787 $settings = \PoweredCache\Utils\get_settings();
788
789 $contents = '';
790 $contents .= '##### POWERED CACHE CONF #####' . PHP_EOL;
791 $contents .= 'set $cache_uri $request_uri;' . PHP_EOL;
792 $contents .= 'set $pc_ssl "";' . PHP_EOL;
793 $contents .= 'set $pc_enc "";' . PHP_EOL;
794 $contents .= 'set $pc_ua "";' . PHP_EOL . PHP_EOL;
795
796 // post
797 $contents .= '# POST requests and urls with a query string should always go to PHP' . PHP_EOL;
798 $contents .= 'if ($request_method = POST) {' . PHP_EOL;
799 $contents .= ' set $cache_uri \'null cache\';' . PHP_EOL;
800 $contents .= '}' . PHP_EOL . PHP_EOL;
801
802 $contents .= 'location = /favicon.ico { log_not_found off; access_log off; }' . PHP_EOL;
803 $contents .= 'location = /robots.txt { try_files $uri $uri/ /index.php?$args; log_not_found off; access_log off; }' . PHP_EOL . PHP_EOL;
804
805 // query string
806 $contents .= 'if ($query_string != "") {' . PHP_EOL;
807 $contents .= ' set $cache_uri \'null cache\';' . PHP_EOL;
808 $contents .= '}' . PHP_EOL . PHP_EOL;
809
810 /**
811 * Documented in htaccess config
812 */
813 if ( apply_filters( 'powered_cache_browser_cache', true ) ) {
814 $contents .= 'location ~* .(jpg|jpeg|png|gif|ico|css|js|svg|eot|woff|woff2|ttf|otf)$ {' . PHP_EOL;
815 $contents .= ' expires 6M;' . PHP_EOL;
816 $contents .= '}' . PHP_EOL . PHP_EOL;
817 }
818
819 // https
820 $contents .= '# HTTPS' . PHP_EOL;
821 $contents .= 'if ($https = "on") {' . PHP_EOL;
822 $contents .= ' set $pc_ssl "-https";' . PHP_EOL;
823 $contents .= '}' . PHP_EOL . PHP_EOL;
824
825 $contents .= '# Don\'t cache uris containing the following segments' . PHP_EOL;
826 $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;
827 $contents .= ' set $cache_uri \'null cache\';' . PHP_EOL;
828 $contents .= '}' . PHP_EOL . PHP_EOL;
829
830 $rejected_user_agents = (array) AdvancedCache::get_rejected_user_agents();
831
832 $contents .= '# Don\'t use the cache for rejected agents' . PHP_EOL;
833 $contents .= 'if ($http_user_agent ~* "(' . implode( '|', $rejected_user_agents ) . '")) {' . PHP_EOL;
834 $contents .= ' set $cache_uri \'null cache\';' . PHP_EOL;
835 $contents .= '}' . PHP_EOL . PHP_EOL;
836
837 $rejected_cookies = (array) AdvancedCache::get_rejected_cookies();
838
839 $contents .= '# Don\'t use the cache for logged in users or recent commenters' . PHP_EOL;
840 $contents .= 'if ($http_cookie ~* "wordpress_[a-f0-9]+|' . implode( '|', $rejected_cookies ) . '") {' . PHP_EOL;
841 $contents .= ' set $cache_uri \'null cache\';' . PHP_EOL;
842 $contents .= '}' . PHP_EOL . PHP_EOL;
843
844 $contents .= 'if ($http_x_wap_profile) {' . PHP_EOL;
845 $contents .= ' set $pc_ua \'-mobile\';' . PHP_EOL;
846 $contents .= '}' . PHP_EOL . PHP_EOL;
847
848 if ( true === $settings['cache_mobile'] && true === $settings['cache_mobile_separate_file'] ) {
849 $mobile_browsers = addcslashes( implode( '|', preg_split( '/[\s*,\s*]*,+[\s*,\s*]*/', mobile_browsers() ) ), ' ' );
850 $mobile_prefixes = addcslashes( implode( '|', preg_split( '/[\s*,\s*]*,+[\s*,\s*]*/', mobile_prefixes() ) ), ' ' );
851
852 $contents .= 'if ($http_user_agent ~* (' . $mobile_browsers . ')) {' . PHP_EOL;
853 $contents .= ' set $pc_ua \'-mobile\';' . PHP_EOL;
854 $contents .= '}' . PHP_EOL . PHP_EOL;
855
856 $contents .= 'if ($http_user_agent ~* (' . $mobile_prefixes . ')) {' . PHP_EOL;
857 $contents .= ' set $pc_ua \'-mobile\';' . PHP_EOL;
858 $contents .= '}' . PHP_EOL . PHP_EOL;
859 }
860
861 $cache_suffix = 'html';
862
863 if ( true === $settings['gzip_compression'] ) {
864 $cache_suffix .= '.gz';
865 }
866
867 $contents .= 'location / {' . PHP_EOL;
868 /**
869 * Documented in htaccess config
870 */
871 if ( apply_filters( 'powered_cache_mod_rewrite', true ) ) { // rewrite
872 $contents .= ' add_header X-Powered-Cache nginx;' . PHP_EOL;
873 $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;
874
875 } else {
876 $contents .= ' try_files $uri $uri/ /index.php?$args;' . PHP_EOL;
877 }
878
879 $contents .= '}' . PHP_EOL . PHP_EOL;
880
881 if ( permalink_structure_has_trailingslash() ) {
882 $contents .= '# add trailingslash rule' . PHP_EOL;
883 $contents .= 'rewrite ^([^.]*[^/])$ $1/ permanent;' . PHP_EOL;
884 }
885
886 return $contents;
887 }
888
889 /**
890 * Downloads configuration files
891 *
892 * @param string $server type supports apache and nginx
893 *
894 * @since 1.1
895 */
896 public function download_rewrite_rules( $server ) {
897
898 $rules = '';
899 $filename = 'conf';
900
901 if ( 'apache' === $server ) {
902 $rules = $this->htaccess_rules();
903 $filename = '.htaccess_powered_cache';
904 }
905
906 if ( 'nginx' === $server ) {
907 $rules = $this->nginx_rules();
908 $filename = 'poweredcache.conf';
909 }
910
911 nocache_headers();
912 @header( 'Content-Type: text/plain' );
913 @header( 'Content-Disposition: attachment; filename="' . $filename . '"' );
914 @header( 'Content-Transfer-Encoding: binary' );
915 @header( 'Content-Length: ' . strlen( $rules ) );
916 @header( 'Connection: close' );
917 echo $rules; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
918 exit;
919 }
920
921 /**
922 * Make the caching configurations with given options
923 *
924 * @param array $settings Plugin settings
925 * @param bool $network_wide Whether network-wide configuration or not
926 *
927 * @since 2.0
928 */
929 public function save_configuration( $settings, $network_wide = false ) {
930
931 if ( can_configure_object_cache() ) {
932 $this->setup_object_cache( $settings['object_cache'] );
933 }
934
935 $this->setup_page_cache( $settings['enable_page_cache'] );
936 $private_settings = [ 'cloudflare_email', 'cloudflare_api_key', 'cloudflare_zone' ];
937
938 foreach ( $private_settings as $setting_key ) {
939 unset( $settings[ $setting_key ] );
940 }
941
942 $this->save_to_file( $settings, $network_wide );
943 }
944
945 /**
946 * Clean-up all the configurations and cache related footprints
947 */
948 public function clean_up() {
949 $object_cache_dropin = untrailingslashit( WP_CONTENT_DIR ) . '/object-cache.php';
950 if ( file_exists( $object_cache_dropin ) && false !== strpos( file_get_contents( $object_cache_dropin ), 'POWERED_OBJECT_CACHE' ) ) {
951 unlink( $object_cache_dropin );
952 }
953
954 $advanced_cache_dropin = untrailingslashit( WP_CONTENT_DIR ) . '/advanced-cache.php';
955 if ( file_exists( $advanced_cache_dropin ) ) {
956 unlink( $advanced_cache_dropin );
957 }
958
959 $this->define_wp_cache( false );
960 $this->configure_htaccess( false );
961 $this->protect_cache_dir();
962
963 if ( ! file_exists( get_cache_dir() ) ) {
964 remove_dir( get_cache_dir() );
965 }
966
967 $config_dir = WP_CONTENT_DIR . '/pc-config';
968
969 if ( is_multisite() ) {
970 $config_file_name = $this->get_config_filename( POWERED_CACHE_IS_NETWORK );
971 $config_file = trailingslashit( $config_dir ) . $config_file_name;
972 if ( file_exists( $config_file ) ) {
973 unlink( $config_file );
974 }
975 } else { // remove entire configuration directory on single site setup
976 remove_dir( $config_dir );
977 }
978
979 /**
980 * Fires after cleanup all configurations
981 *
982 * @hook powered_cache_after_clean_up
983 *
984 * @since 2.0
985 */
986 do_action( 'powered_cache_after_clean_up' );
987
988 }
989
990 /**
991 * Apache allows both format like A2592000 => "access plus 1 month"
992 * A => access, M => Modified
993 *
994 * @param string $mime_type Mimetype
995 *
996 * @return string cache lifespan for apache
997 * @see http://httpd.apache.org/docs/current/mod/mod_expires.html
998 * @since 2.0
999 */
1000 public function get_browser_cache_lifespan( $mime_type ) {
1001 switch ( $mime_type ) {
1002 case 'text/css':
1003 case 'application/javascript':
1004 /**
1005 * Filters TTL for CSS/JS files.
1006 *
1007 * @hook powered_cache_browser_cache_assets_lifespan
1008 *
1009 * @param {string} $expiry_time .htaccess lifespan
1010 *
1011 * @return {string} New value.
1012 *
1013 * @since 1.1
1014 */
1015 $expiry_time = apply_filters( 'powered_cache_browser_cache_assets_lifespan', 'access plus 1 year' );
1016 break;
1017 case 'image/jpeg':
1018 case 'image/gif':
1019 case 'image/png':
1020 case 'image/bmp':
1021 case 'image/tiff':
1022 case 'image/webp':
1023 case 'image/heic':
1024 $expiry_time = 'access plus 6 months';
1025 break;
1026 case 'image/x-icon':
1027 $expiry_time = 'access plus 1 week'; // favicon
1028 break;
1029 default:
1030 /**
1031 * Filters default TTL for browser cache lifespan.
1032 *
1033 * @hook powered_cache_browser_cache_default_lifespan
1034 *
1035 * @param {string} $expiry_time .htaccess lifespan
1036 *
1037 * @return {string} New value.
1038 *
1039 * @since 1.1
1040 */
1041 $expiry_time = apply_filters( 'powered_cache_browser_cache_default_lifespan', 'access plus 1 month' );
1042 }
1043
1044 /**
1045 * Filters TTL for browser cache lifespan.
1046 *
1047 * @hook powered_cache_browser_cache_lifespan
1048 *
1049 * @param {string} $expiry_time .htaccess lifespan
1050 * @param {string} $mime_type mime type
1051 *
1052 * @return {string} New value.
1053 *
1054 * @since 2.0
1055 */
1056 $expiry_time = apply_filters( 'powered_cache_browser_cache_lifespan', $expiry_time, $mime_type );
1057
1058 return $expiry_time;
1059 }
1060
1061
1062 }
1063