object_cache_file_content( $backend ); if ( ! file_put_contents( $file, $file_string, LOCK_EX ) ) { return false; } return true; } /** * object-cache.php contents * * @param string $backend Persistent object cache backend * * @return mixed|void * @since 1.0 * @see Powered_Cache_Admin_Helper::object_cache_dropins * * @since 1.1 supports `POWERED_CACHE_OBJECT_CACHE_DROPIN` */ public function object_cache_file_content( $backend ) { $string = 'generate_advanced_cache_file(); $this->define_wp_cache( $status ); if ( can_configure_htaccess() ) { $this->configure_htaccess( $status ); } $this->protect_cache_dir(); return true; } /** * Generates advanced-cache.php * * @return bool * @since 1.1 is_multisite control added * @since 1.0 */ public function generate_advanced_cache_file() { $file = untrailingslashit( WP_CONTENT_DIR ) . '/advanced-cache.php'; $settings = \PoweredCache\Utils\get_settings(); $file_string = ''; /** * multisite setups should always have `advanced-cache.php` file */ if ( true === $settings['enable_page_cache'] || is_multisite() ) { $file_string = $this->advanced_cache_file_content(); } if ( ! file_put_contents( $file, $file_string ) ) { return false; } return true; } /** * Prepare advanced-cache.php contents * * @return mixed|void * @since 1.1 supports `POWERED_CACHE_ADVANCED_CACHE_DROPIN` * @since 1.0 */ public function advanced_cache_file_content() { $string = 'find_wp_config_file(); if ( ! $config_path ) { return false; } if ( defined( 'WP_CACHE' ) && WP_CACHE === $status ) { return true; } $config_file_string = file_get_contents( $config_path ); // Config file is empty. Maybe couldn't read it? if ( empty( $config_file_string ) ) { return false; } $config_file = preg_split( "#(\r\n|\r|\n)#", $config_file_string ); $line_key = false; foreach ( $config_file as $key => $line ) { if ( ! preg_match( '/^\s*define\(\s*(\'|")([A-Z_]+)(\'|")(.*)/', $line, $match ) ) { continue; } if ( 'WP_CACHE' === $match[2] ) { $line_key = $key; } } if ( false !== $line_key ) { unset( $config_file[ $line_key ] ); } $status_string = ( $status ) ? 'true' : 'false'; array_shift( $config_file ); array_unshift( $config_file, ' $line ) { if ( '' === $line ) { unset( $config_file[ $key ] ); } } if ( ! file_put_contents( $config_path, implode( PHP_EOL, $config_file ) ) ) { return false; } return true; } /** * seeking wp-config file * * @return bool|string * @since 1.0 */ public function find_wp_config_file() { $file = '/wp-config.php'; for ( $i = 1; $i <= 3; $i ++ ) { if ( $i > 1 ) { $file = '/..' . $file; } if ( file_exists( untrailingslashit( ABSPATH ) . $file ) ) { $config_path = untrailingslashit( ABSPATH ) . $file; break; } } if ( ! isset( $config_path ) ) { return false; } return $config_path; } /** * Create .htaccess file based on current setting preferences * * @param bool $enable Configure .htaccess automatically when it's true * * @return bool * @since 1.0 */ public function configure_htaccess( $enable = true ) { if ( ! function_exists( '\get_home_path' ) ) { require_once ABSPATH . '/wp-admin/includes/file.php'; } $htaccess_file = get_home_path() . '.htaccess'; $settings = \PoweredCache\Utils\get_settings(); /** * Filters whether automatically update or not update .htaccess file * * @hook powered_cache_auto_htaccess_update * * @param {boolean} true to automatic update. * * @return {boolean} New value. * * @since 1.1.1 */ if ( true !== apply_filters( 'powered_cache_auto_htaccess_update', true ) ) { return false; } /** * Apache users can control automatic configuration * * @since 1.2 */ $automatic_configuration = $settings['auto_configure_htaccess']; if ( is_multisite() && ! POWERED_CACHE_IS_NETWORK ) { $automatic_configuration = false; // individual sites shouldn't use .htaccess on multisite } if ( ! $automatic_configuration ) { return false; } if ( is_writable( $htaccess_file ) ) { $contents = file_get_contents( $htaccess_file ); // clean up $contents = preg_replace( '/# BEGIN POWERED CACHE(.*)# END POWERED CACHE/is', '', $contents ); if ( false === $enable ) { return file_put_contents( $htaccess_file, $contents ); } $rules = $this->htaccess_rules(); $contents = $rules . $contents; // Update the .htacces file if ( ! file_put_contents( $htaccess_file, $contents ) ) { return false; } return true; } return false; } /** * Prepares .htaccess rules for the caching * * @return string $rules * @since 1.1 */ public function htaccess_rules() { $rules = ''; $settings = \PoweredCache\Utils\get_settings(); /** * Filters base htaccess rules * * @hook powered_cache_pre_htaccess * * @param {string} empty htaccesss rules by default * * @return {boolean} New value. * * @since 1.1 */ $rules .= apply_filters( 'powered_cache_pre_htaccess', '' ); $rules .= '# BEGIN POWERED CACHE' . PHP_EOL; /** * Filters whether doing the configuration for browser cache or not * * @hook powered_cache_browser_cache * * @param {boolean} true for creating .htaccess rules for browser cache * * @return {boolean} New value. * * @since 1.1 */ if ( apply_filters( 'powered_cache_browser_cache', true ) ) { $wp_mime_types = wp_get_mime_types(); $mime_types = array_flip( $wp_mime_types ); // mimes $rules .= '' . PHP_EOL; foreach ( $mime_types as $mime_type => $ext ) { $ext_str = '.' . str_replace( '|', ' .', $ext ); $rules .= ' AddType ' . $mime_type . ' ' . $ext_str . PHP_EOL; } $rules .= '' . PHP_EOL; // set expire time $rules .= '' . PHP_EOL; $rules .= ' ExpiresActive On' . PHP_EOL; $rules .= ' ExpiresByType text/html "access plus 0 seconds"' . PHP_EOL; $rules .= ' ExpiresByType text/richtext "access plus 0 seconds"' . PHP_EOL; $rules .= ' ExpiresByType text/plain "access plus 0 seconds"' . PHP_EOL; $rules .= ' ExpiresByType text/xsd "access plus 0 seconds"' . PHP_EOL; $rules .= ' ExpiresByType text/xsl "access plus 0 seconds"' . PHP_EOL; $rules .= ' ExpiresByType text/xml "access plus 0 seconds"' . PHP_EOL; $rules .= ' ExpiresByType text/cache-manifest "access plus 0 seconds"' . PHP_EOL; foreach ( $mime_types as $mime_type => $ext ) { if ( in_array( $mime_type, array( 'text/html', 'text/richtext', 'text/plain', 'text/xsd', 'text/xsl', 'text/xml', 'text/cache-manifest' ), true ) ) { continue; } $expiry_time = $this->get_browser_cache_lifespan( $mime_type ); $rules .= ' ExpiresByType ' . $mime_type . ' "' . $expiry_time . '"' . PHP_EOL; } $font_types = [ 'application/x-font-ttf', 'application/x-font-woff ', 'application/x-font-woff2', 'font/opentype', 'application/vnd.ms-fontobject', 'application/font-sfnt', 'image/svg+xml', ]; foreach ( $font_types as $mime_type ) { $expiry_time = $this->get_browser_cache_lifespan( $mime_type ); $rules .= ' ExpiresByType ' . $mime_type . ' "' . $expiry_time . '"' . PHP_EOL; } $rules .= '' . PHP_EOL; } // gzip $rules .= '' . PHP_EOL; $rules .= ' ' . PHP_EOL; $rules .= ' Header set X-Powered-By "Powered Cache"' . PHP_EOL; $rules .= ' Header append Vary User-Agent env=!dont-vary' . PHP_EOL; $rules .= ' ' . PHP_EOL; $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' . PHP_EOL; // phpcs:ignore $rules .= ' ' . PHP_EOL; $rules .= ' AddOutputFilter DEFLATE js css htm html xml' . PHP_EOL; $rules .= ' ' . PHP_EOL; $rules .= '' . PHP_EOL; // remove etag $rules .= '' . PHP_EOL; $rules .= 'Header unset ETag' . PHP_EOL; $rules .= '' . PHP_EOL . PHP_EOL; /** * Filters whether doing the configuration for htaccess rewrite cache or not * * @hook powered_cache_mod_rewrite * * @param {boolean} true for creating .htaccess rewrite rules. * * @return {boolean} New value. * * @since 2.0 */ if ( apply_filters( 'powered_cache_mod_rewrite', true ) ) { // rewrite // add gzip type for .html.gz format if ( $settings['gzip_compression'] ) { $rules .= '' . PHP_EOL; $rules .= ' AddType text/html .html.gz' . PHP_EOL; $rules .= ' AddEncoding gzip .gz' . PHP_EOL; $rules .= '' . PHP_EOL; $rules .= '' . PHP_EOL; $rules .= ' SetEnvIfNoCase Request_URI \.html.gz$ no-gzip' . PHP_EOL; $rules .= '' . PHP_EOL; } $env_powered_cache_ua = ''; $env_powered_cache_ssl = ''; $env_powered_cache_enc = ''; $rewrite_base = wp_parse_url( home_url() ); $rewrite_base = isset( $rewrite_base['path'] ) ? trailingslashit( $rewrite_base['path'] ) : '/'; $rules .= '' . PHP_EOL; $rules .= ' RewriteEngine On' . PHP_EOL; $rules .= ' RewriteBase ' . $rewrite_base . PHP_EOL; $rules .= ' AddDefaultCharset UTF-8 ' . PHP_EOL; if ( true === $settings['cache_mobile'] && true === $settings['cache_mobile_separate_file'] ) { $mobile_browsers = addcslashes( implode( '|', preg_split( '/[\s*,\s*]*,+[\s*,\s*]*/', mobile_browsers() ) ), ' ' ); $mobile_prefixes = addcslashes( implode( '|', preg_split( '/[\s*,\s*]*,+[\s*,\s*]*/', mobile_prefixes() ) ), ' ' ); // mobile env set $rules .= ' RewriteCond %{HTTP_USER_AGENT} (' . $mobile_browsers . ') [NC]' . PHP_EOL; $rules .= ' RewriteRule .* - [E=PC_UA:-mobile]' . PHP_EOL; $rules .= ' RewriteCond %{HTTP_USER_AGENT} ^(' . $mobile_prefixes . ') [NC]' . PHP_EOL; $rules .= ' RewriteRule .* - [E=PC_UA:-mobile]' . PHP_EOL; $env_powered_cache_ua = '%{ENV:PC_UA}'; } $rules .= ' RewriteCond %{HTTPS} on [OR]' . PHP_EOL; $rules .= ' RewriteCond %{SERVER_PORT} ^443$ [OR]' . PHP_EOL; $rules .= ' RewriteCond %{HTTP:X-Forwarded-Proto} https' . PHP_EOL; $rules .= ' RewriteRule .* - [E=PC_SSL:-https]' . PHP_EOL; $env_powered_cache_ssl = '%{ENV:PC_SSL}'; if ( true === $settings['gzip_compression'] ) { $rules .= ' RewriteCond %{HTTP:Accept-Encoding} gzip' . PHP_EOL; $rules .= ' RewriteRule .* - [E=PC_ENC:.gz]' . PHP_EOL; $env_powered_cache_enc = '%{ENV:PC_ENC}'; } $rules .= ' RewriteCond %{REQUEST_METHOD} !=POST' . PHP_EOL; $rules .= ' RewriteCond %{QUERY_STRING} =""' . PHP_EOL; if ( permalink_structure_has_trailingslash() ) { $rules .= ' RewriteCond %{REQUEST_URI} !^.*[^/]$' . PHP_EOL; $rules .= ' RewriteCond %{REQUEST_URI} !^.*//.*$' . PHP_EOL; } // Get root base $site_root = wp_parse_url( site_url() ); $site_root = isset( $site_root['path'] ) ? trailingslashit( $site_root['path'] ) : ''; // reject user agent $rejected_user_agents = (array) AdvancedCache::get_rejected_user_agents(); if ( ! empty( $rejected_user_agents ) ) { $rules .= ' RewriteCond %{HTTP_USER_AGENT} !^(' . implode( '|', $rejected_user_agents ) . ').* [NC]' . PHP_EOL; } // rejected cookies $rejected_cookies = AdvancedCache::get_rejected_cookies(); if ( ! empty( $rejected_cookies ) ) { $rules .= ' RewriteCond %{HTTP:Cookie} !(' . implode( '|', $rejected_cookies ) . ') [NC]' . PHP_EOL; } // dont cache fbexternal $rules .= ' RewriteCond %{HTTP_USER_AGENT} !^(facebookexternalhit).* [NC]' . PHP_EOL; $cache_location = get_cache_dir(); $cache_location = untrailingslashit( $cache_location ) . '/powered-cache/'; if ( strpos( ABSPATH, $cache_location ) === false ) { $cache_path = str_replace( $_SERVER['DOCUMENT_ROOT'], '', $cache_location ); // clean doc root } else { $cache_path = $site_root . str_replace( ABSPATH, '', $cache_location ); } /** * Filters whether running on 1and1_hosting or not * * @hook powered_cache_maybe_1and1_hosting * * @param {boolean} $status true if /kunden/homepage directory exists * * @return {boolean} New value. * * @since 1.1 */ if ( apply_filters( 'powered_cache_maybe_1and1_hosting', ( 0 === strpos( $_SERVER['DOCUMENT_ROOT'], '/kunden/homepage/' ) ) ) ) { $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; } else { $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; } $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; if ( $settings['gzip_compression'] ) { $rules .= ' # prevent mod_deflate double gzip' . PHP_EOL; $rules .= ' RewriteRule \.html\.gz$ - [T=text/html,E=no-gzip:1]' . PHP_EOL; } $rules .= '' . PHP_EOL; } /** * Filters post htaccess rules * * @hook powered_cache_after_htaccess * * @param {string} empty htaccesss rules by default * * @return {boolean} New value. * * @since 2.0 */ $rules .= apply_filters( 'powered_cache_after_htaccess', '' ); $rules .= '# END POWERED CACHE'; return $rules; } /** * Make sure directory listing disabled * * @return bool * @since 1.2 */ public function protect_cache_dir() { if ( ! file_exists( get_cache_dir() ) ) { mkdir( get_cache_dir() ); } $file = get_cache_dir() . '.htaccess'; $file_string = 'Options -Indexes'; if ( ! file_put_contents( $file, $file_string ) ) { return false; } return true; } /** * Prepare config name * * @param bool $is_network Whether network-wide config name or not * * @return string configuration file name * @since 2.0 */ public function get_config_filename( $is_network ) { if ( $is_network ) { return 'config-network.php'; } else { $url_parts = wp_parse_url( home_url() ); $config_name = 'config-' . $url_parts['host']; if ( is_multisite() && ! is_subdomain_install() ) { if ( ! is_main_site( get_current_blog_id() ) && ! empty( $url_parts['path'] ) ) { $subdir_name = ltrim( $url_parts['path'], '/' ); $config_name .= '-' . $subdir_name; } } $config_name .= '.php'; return $config_name; } } /** * Save settings to file * * @param array $configuration plugin settings * @param bool $network_wide Whether network-wide configuration or not * * @return bool * @since 1.0 */ public function save_to_file( $configuration, $network_wide = false ) { $config_dir = WP_CONTENT_DIR . '/pc-config'; $config_file_name = $this->get_config_filename( $network_wide ); if ( ! file_exists( $config_dir ) ) { mkdir( $config_dir ); } $config_file = trailingslashit( $config_dir ) . $config_file_name; if ( ! file_exists( $config_file ) ) { touch( $config_file ); } $configuration['cache_location'] = get_cache_dir(); $config_file_string = 'htaccess_rules(); $filename = '.htaccess_powered_cache'; } if ( 'nginx' === $server ) { $rules = $this->nginx_rules(); $filename = 'poweredcache.conf'; } nocache_headers(); @header( 'Content-Type: text/plain' ); @header( 'Content-Disposition: attachment; filename="' . $filename . '"' ); @header( 'Content-Transfer-Encoding: binary' ); @header( 'Content-Length: ' . strlen( $rules ) ); @header( 'Connection: close' ); echo $rules; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped exit; } /** * Make the caching configurations with given options * * @param array $settings Plugin settings * @param bool $network_wide Whether network-wide configuration or not * * @since 2.0 */ public function save_configuration( $settings, $network_wide = false ) { if ( can_configure_object_cache() ) { $this->setup_object_cache( $settings['object_cache'] ); } $this->setup_page_cache( $settings['enable_page_cache'] ); $private_settings = [ 'cloudflare_email', 'cloudflare_api_key', 'cloudflare_zone' ]; foreach ( $private_settings as $setting_key ) { unset( $settings[ $setting_key ] ); } $this->save_to_file( $settings, $network_wide ); } /** * Clean-up all the configurations and cache related footprints */ public function clean_up() { $object_cache_dropin = untrailingslashit( WP_CONTENT_DIR ) . '/object-cache.php'; if ( file_exists( $object_cache_dropin ) && false !== strpos( file_get_contents( $object_cache_dropin ), 'POWERED_OBJECT_CACHE' ) ) { unlink( $object_cache_dropin ); } $advanced_cache_dropin = untrailingslashit( WP_CONTENT_DIR ) . '/advanced-cache.php'; if ( file_exists( $advanced_cache_dropin ) ) { unlink( $advanced_cache_dropin ); } $this->define_wp_cache( false ); $this->configure_htaccess( false ); $this->protect_cache_dir(); if ( ! file_exists( get_cache_dir() ) ) { remove_dir( get_cache_dir() ); } $config_dir = WP_CONTENT_DIR . '/pc-config'; if ( is_multisite() ) { $config_file_name = $this->get_config_filename( POWERED_CACHE_IS_NETWORK ); $config_file = trailingslashit( $config_dir ) . $config_file_name; if ( file_exists( $config_file ) ) { unlink( $config_file ); } } else { // remove entire configuration directory on single site setup remove_dir( $config_dir ); } /** * Fires after cleanup all configurations * * @hook powered_cache_after_clean_up * * @since 2.0 */ do_action( 'powered_cache_after_clean_up' ); } /** * Apache allows both format like A2592000 => "access plus 1 month" * A => access, M => Modified * * @param string $mime_type Mimetype * * @return string cache lifespan for apache * @see http://httpd.apache.org/docs/current/mod/mod_expires.html * @since 2.0 */ public function get_browser_cache_lifespan( $mime_type ) { switch ( $mime_type ) { case 'text/css': case 'application/javascript': /** * Filters TTL for CSS/JS files. * * @hook powered_cache_browser_cache_assets_lifespan * * @param {string} $expiry_time .htaccess lifespan * * @return {string} New value. * * @since 1.1 */ $expiry_time = apply_filters( 'powered_cache_browser_cache_assets_lifespan', 'access plus 1 year' ); break; case 'image/jpeg': case 'image/gif': case 'image/png': case 'image/bmp': case 'image/tiff': case 'image/webp': case 'image/heic': $expiry_time = 'access plus 6 months'; break; case 'image/x-icon': $expiry_time = 'access plus 1 week'; // favicon break; default: /** * Filters default TTL for browser cache lifespan. * * @hook powered_cache_browser_cache_default_lifespan * * @param {string} $expiry_time .htaccess lifespan * * @return {string} New value. * * @since 1.1 */ $expiry_time = apply_filters( 'powered_cache_browser_cache_default_lifespan', 'access plus 1 month' ); } /** * Filters TTL for browser cache lifespan. * * @hook powered_cache_browser_cache_lifespan * * @param {string} $expiry_time .htaccess lifespan * @param {string} $mime_type mime type * * @return {string} New value. * * @since 2.0 */ $expiry_time = apply_filters( 'powered_cache_browser_cache_lifespan', $expiry_time, $mime_type ); return $expiry_time; } }