* * Don't send content if it isn't modified */ $exit = true; } if ( $this->_config->get_boolean( 'browsercache.enabled' ) ) { if ( $this->_config->get_boolean( 'browsercache.html.last_modified' ) ) { $headers['Last-Modified'] = Util_Content::http_date( $time ); } if ( $this->_config->get_boolean( 'browsercache.html.expires' ) ) { $headers['Expires'] = Util_Content::http_date( $expires ); } if ( $this->_config->get_boolean( 'browsercache.html.cache.control' ) ) { switch ( $this->_config->get_string( 'browsercache.html.cache.policy' ) ) { case 'cache': $headers['Pragma'] = 'public'; $headers['Cache-Control'] = 'public'; break; case 'cache_public_maxage': $headers['Pragma'] = 'public'; $headers['Cache-Control'] = sprintf( 'max-age=%d, public', $max_age ); break; case 'cache_validation': $headers['Pragma'] = 'public'; $headers['Cache-Control'] = 'public, must-revalidate, proxy-revalidate'; break; case 'cache_noproxy': $headers['Pragma'] = 'public'; $headers['Cache-Control'] = 'private, must-revalidate'; break; case 'cache_maxage': $headers['Pragma'] = 'public'; $headers['Cache-Control'] = sprintf( 'max-age=%d, public, must-revalidate, proxy-revalidate', $max_age ); break; case 'no_cache': $headers['Pragma'] = 'no-cache'; $headers['Cache-Control'] = 'max-age=0, private, no-store, no-cache, must-revalidate'; break; } } if ( $this->_config->get_boolean( 'browsercache.html.etag' ) ) { $headers['ETag'] = '"' . $etag . '"'; } } $headers = array_merge( $headers, $this->_get_common_headers( $compression ) ); /** * Send headers to client */ $result = $this->_headers( $headers ); if ( $exit ) exit(); return $result; } /** * Returns headers to send regardless is page caching is active */ function _get_common_headers( $compression ) { $headers = array(); if ( $this->_config->get_boolean( 'browsercache.enabled' ) ) { if ( $this->_config->get_boolean( 'browsercache.html.w3tc' ) ) { $headers['X-Powered-By'] = Util_Environment::w3tc_header(); } } $vary = ''; //compressed && UAG if ( $compression && $this->_page_key_extension['useragent'] ) { $vary = 'Accept-Encoding,User-Agent,Cookie'; $headers['Content-Encoding'] = $compression; //compressed } elseif ( $compression ) { $vary = 'Accept-Encoding'; $headers['Content-Encoding'] = $compression; //uncompressed && UAG } elseif ( $this->_page_key_extension['useragent'] ) { $vary = 'User-Agent,Cookie'; } //Add Cookie to vary if user logged in and not previously set if ( !$this->_check_logged_in() && strpos( $vary, 'Cookie' ) === false ) { if ( $vary ) $vary .= ',Cookie'; else $vary = 'Cookie'; } /** * Add vary header */ if ( $vary ) $headers['Vary'] = $vary; /** * Disable caching for preview mode */ if ( Util_Environment::is_preview_mode() ) { $headers['Pragma'] = 'private'; $headers['Cache-Control'] = 'private'; } return $headers; } /** * Check if content was modified by time * * @param integer $time * @return boolean */ function _check_modified_since( $time ) { if ( !empty( $_SERVER['HTTP_IF_MODIFIED_SINCE'] ) ) { $if_modified_since = htmlspecialchars( stripslashes( $_SERVER['HTTP_IF_MODIFIED_SINCE'] ) ); // phpcs:ignore // IE has tacked on extra data to this header, strip it if ( ( $semicolon = strrpos( $if_modified_since, ';' ) ) !== false ) { $if_modified_since = substr( $if_modified_since, 0, $semicolon ); } return $time == strtotime( $if_modified_since ); } return false; } /** * Check if content was modified by etag * * @param string $etag * @return boolean */ function _check_match( $etag ) { if ( !empty( $_SERVER['HTTP_IF_NONE_MATCH'] ) ) { $if_none_match = htmlspecialchars( stripslashes( $_SERVER['HTTP_IF_NONE_MATCH'] ) ); // phpcs:ignore $client_etags = explode( ',', $if_none_match ); foreach ( $client_etags as $client_etag ) { $client_etag = trim( $client_etag ); if ( $etag == $client_etag ) { return true; } } } return false; } /** * Bad Behavior support * * @return void */ function _bad_behavior() { $bb_file = $this->_config->get_string( 'pgcache.bad_behavior_path' ); if ( $bb_file != '' ) require_once $bb_file; } /** * Parses dynamic tags */ function _parse_dynamic( $buffer ) { if ( !defined( 'W3TC_DYNAMIC_SECURITY' ) ) return $buffer; $buffer = preg_replace_callback( '~(.*)~Uis', array( $this, '_parse_dynamic_mfunc' ), $buffer ); $buffer = preg_replace_callback( '~(.*)~Uis', array( $this, '_parse_dynamic_mclude' ), $buffer ); return $buffer; } /** * Parse dynamic mfunc callback * * @param array $matches * @return string */ function _parse_dynamic_mfunc( $matches ) { $code1 = trim( $matches[1] ); $code2 = trim( $matches[2] ); $code = ( $code1 ? $code1 : $code2 ); if ( $code ) { $code = trim( $code, ';' ) . ';'; try { ob_start(); $result = eval( $code ); $output = ob_get_contents(); ob_end_clean(); } catch ( \Exception $ex ) { $result = false; } if ( $result === false ) { $output = sprintf( 'Unable to execute code: %s', htmlspecialchars( $code ) ); } } else { $output = htmlspecialchars( 'Invalid mfunc tag syntax. The correct format is: or PHP code.' ); } return $output; } /** * Parse dynamic mclude callback * * @param array $matches * @return string */ function _parse_dynamic_mclude( $matches ) { $file1 = trim( $matches[1] ); $file2 = trim( $matches[2] ); $file = ( $file1 ? $file1 : $file2 ); if ( $file ) { $file = ABSPATH . $file; if ( file_exists( $file ) && is_readable( $file ) ) { ob_start(); include $file; $output = ob_get_contents(); ob_end_clean(); } else { $output = sprintf( 'Unable to open file: %s', htmlspecialchars( $file ) ); } } else { $output = htmlspecialchars( 'Incorrect mclude tag syntax. The correct format is: or path/to/file.php.' ); } return $output; } /** * Checks if buffer has dynamic tags * * @param string $buffer * @return boolean */ function _has_dynamic( $buffer ) { if ( !defined( 'W3TC_DYNAMIC_SECURITY' ) ) return false; return preg_match( '~(.*)~Uis', $buffer ); } /** * Check whether requested page has content type that can be cached * * @return bool */ private function _is_cacheable_content_type() { $content_type = ''; $headers = headers_list(); foreach ( $headers as $header ) { $header = strtolower( $header ); $m = null; if ( preg_match( '~\s*content-type\s*:([^;]+)~', $header, $m ) ) { $content_type = trim( $m[1] ); } } $cache_headers = apply_filters( 'w3tc_is_cacheable_content_type', array( '' /* redirects, they have only Location header set */, 'application/json', 'text/html', 'text/xml', 'text/xsl', 'application/xhtml+xml', 'application/rss+xml', 'application/atom+xml', 'application/rdf+xml', 'application/xml' ) ); return in_array( $content_type, $cache_headers ); } /** * Fills $_request_url_fragments['path'], $_request_url_fragments['querystring'] * with cache-related normalized values */ private function _preprocess_request_uri() { $p = explode( '?', $this->_request_uri, 2 ); $this->_request_url_fragments['path'] = $p[0]; $this->_request_url_fragments['querystring'] = ( empty( $p[1] ) ? '' : '?' . $p[1] ); $this->_request_url_fragments = $this->_normalize_url_fragments( $this->_request_url_fragments ); } private function _normalize_url_fragments( $fragments ) { $fragments = w3tc_apply_filters( 'pagecache_normalize_url_fragments', $fragments ); $fragments['querystring'] = $this->_normalize_querystring( $fragments['querystring'] ); return $fragments; } private function _normalize_querystring( $querystring ) { $ignore_qs = $this->_config->get_array( 'pgcache.accept.qs' ); $ignore_qs = w3tc_apply_filters( 'pagecache_extract_accept_qs', $ignore_qs ); Util_Rule::array_trim( $ignore_qs ); if ( empty( $ignore_qs ) || empty( $querystring ) ) { return $querystring; } $querystring_naked = substr( $querystring, 1 ); foreach ( $ignore_qs as $qs ) { $m = null; if ( strpos( $qs, '=' ) === false ) { $regexp = Util_Environment::preg_quote( str_replace( '+', ' ', $qs ) ); if ( @preg_match( "~^(.*?&|)$regexp(=[^&]*)?(&.*|)$~i", $querystring_naked, $m ) ) { $querystring_naked = $m[1] . $m[3]; } } else { $regexp = Util_Environment::preg_quote( str_replace( '+', ' ', $qs ) ); if ( @preg_match( "~^(.*?&|)$regexp(&.*|)$~i", $querystring_naked, $m ) ) { $querystring_naked = $m[1] . $m[2]; } } } $querystring_naked = preg_replace( '~[&]+~', '&', $querystring_naked ); $querystring_naked = trim( $querystring_naked, '&' ); return empty( $querystring_naked ) ? '' : '?' . $querystring_naked; } /** * */ public function delayed_cache_print() { if ( $this->_late_caching && $this->_caching ) { $this->_cached_data = $this->_extract_cached_page( true ); if ( $this->_cached_data ) { global $w3_late_caching_succeeded; $w3_late_caching_succeeded = true; $this->process_status = 'hit'; $this->process_cached_page_and_exit( $this->_cached_data ); // if is passes here - exit is not possible now and // will happen on init return; } } if ( $this->_late_init && $this->_caching ) { $this->process_status = 'hit'; $this->process_cached_page_and_exit( $this->_cached_data ); // if is passes here - exit is not possible now and // will happen on init return; } } private function _maybe_save_cached_result( $buffer, $response_headers, $has_dynamic ) { $mobile_group = $this->_page_key_extension['useragent']; $referrer_group = $this->_page_key_extension['referrer']; $encryption = $this->_page_key_extension['encryption']; $compression_header = $this->_page_key_extension['compression']; $compressions_to_store = $this->_get_compressions(); /** * Don't compress here for debug mode or dynamic tags * because we need to modify buffer before send it to client */ if ( $this->_debug || $has_dynamic ) { $compressions_to_store = array( false ); } // right now dont return compressed buffer if we are dynamic, // that will happen on shutdown after processing dynamic stuff $compression_of_returned_content = ( $has_dynamic ? false : $compression_header ); $headers = $this->_get_cached_headers( $response_headers['plain'] ); if ( !empty( $headers['Status-Code'] ) ) { $is_404 = ( $headers['Status-Code'] == 404 ); } elseif ( function_exists( 'is_404' ) ) { $is_404 = is_404(); } else { $is_404 = false; } if ( $this->_enhanced_mode ) { // redirect issued, if we have some old cache entries // they will be turned into fresh files and catch further requests if ( isset( $response_headers['kv']['Location'] ) ) { $cache = $this->_get_cache( $this->_page_key_extension['group'] ); foreach ( $compressions_to_store as $_compression ) { $_page_key = $this->_get_page_key( array_merge( $this->_page_key_extension, array( 'compression' => $_compression ) ) ); $cache->hard_delete( $_page_key ); } return $buffer; } } $content_type = ''; if ( $this->_enhanced_mode && !$this->_late_init ) { register_shutdown_function( array( $this, '_check_rules_present' ) ); if ( isset( $response_headers['kv']['Content-Type'] ) ) { $content_type = $response_headers['kv']['Content-Type']; } } $time = time(); $cache = $this->_get_cache( $this->_page_key_extension['group'] ); /** * Store different versions of cache */ $buffers = array(); $something_was_set = false; foreach ( $compressions_to_store as $_compression ) { $this->_set_extract_page_key( array_merge( $this->_page_key_extension, array( 'compression' => $_compression, 'content_type' => $content_type ) ), true ); if ( empty( $this->_page_key ) ) continue; // Compress content $buffers[$_compression] = $this->_compress( $buffer, $_compression ); // Store cache data $_data = array( '404' => $is_404, 'headers' => $headers, 'time' => $time, 'content' => $buffers[$_compression] ); if ( !empty( $_compression ) ) { $_data['c'] = $_compression; } if ( $has_dynamic ) $_data['has_dynamic'] = true; $_data = apply_filters( 'w3tc_pagecache_set', $_data, $this->_page_key, $this->_page_group ); if ( !empty( $_data ) ) { $cache->set( $this->_page_key, $_data, $this->_lifetime, $this->_page_group ); $something_was_set = true; } } if ( $something_was_set ) { $this->process_status = 'miss_fill'; } else { $this->process_status = 'miss_third_party'; } // Change buffer if using compression if ( defined( 'W3TC_PAGECACHE_OUTPUT_COMPRESSION_OFF' ) ) { $compression_header = false; } elseif ( $compression_of_returned_content && isset( $buffers[$compression_of_returned_content] ) ) { $buffer = $buffers[$compression_of_returned_content]; } // Calculate content etag $etag = md5( $buffer ); // Send headers $this->_send_headers( $is_404, $time, $etag, $compression_header, $headers ); return $buffer; } public function w3tc_usage_statistics_of_request( $storage ) { global $w3tc_start_microtime; $time_ms = 0; if ( !empty( $w3tc_start_microtime ) ) { $time_ms = (int)( ( microtime( true ) - $w3tc_start_microtime ) * 1000 ); $storage->counter_add( 'pagecache_requests_time_10ms', (int)( $time_ms / 10 ) ); } if ( !empty( $this->process_status ) ) { // see registered keys in PgCache_Plugin.w3tc_usage_statistics_metrics $storage->counter_add( 'php_requests_pagecache_' . $this->process_status, 1 ); if ( $this->_debug ) { self::log( 'finished in ' . $time_ms . ' size ' . $this->output_size . ' with process status ' . $this->process_status . ' reason ' . $this->cache_reject_reason); } } } /** * Log. */ static protected function log( $msg ) { $data = sprintf( "[%s] [%s] [%s] %s\n", date( 'r' ), isset( $_SERVER['REQUEST_URI'] ) ? filter_var( stripslashes( $_SERVER['REQUEST_URI'] ), FILTER_SANITIZE_URL ) : '', ! empty( $_SERVER['HTTP_REFERER'] ) ? htmlspecialchars( $_SERVER['HTTP_REFERER'] ) : '-', $msg ); $data = strtr( $data, '<>', '..' ); $filename = Util_Debug::log_filename( 'pagecache' ); return @file_put_contents( $filename, $data, FILE_APPEND ); } }