View file File name : DbCache_WpdbInjection_QueryCaching.php Content :<?php namespace W3TC; /** * class DbCache_WpdbInjection_QueryCaching */ class DbCache_WpdbInjection_QueryCaching extends DbCache_WpdbInjection { /** * Array of queries * * @var array */ var $query_stats = array(); /** * Queries total * * @var integer */ var $query_total = 0; /** * Query cache hits * * @var integer */ var $query_hits = 0; /** * Query cache misses * * @var integer */ var $query_misses = 0; /** * Time total * * @var integer */ var $time_total = 0; /** * Config */ var $_config = null; /** * Lifetime * * @var integer */ var $_lifetime = null; /** * Request-global cache reject reason * null until filled * * @var string */ private $cache_reject_reason = null; /** * Request-global check reject scope * false until set * * @var bool */ private $cache_reject_request_wide = false; private $debug = false; private $reject_logged = false; private $reject_constants; private $use_filters; /** * Result of check if caching is possible at the level of current http request * null until filled */ private $can_cache_once_per_request_result = null; /* * @param string $dbuser * @param string $dbpassword * @param string $dbname * @param string $dbhost */ function __construct() { $c = Dispatcher::config(); $this->_config = $c; $this->_lifetime = $c->get_integer( 'dbcache.lifetime' ); $this->debug = $c->get_boolean( 'dbcache.debug' ); $this->reject_logged = $c->get_boolean( 'dbcache.reject.logged' ); $this->reject_constants = $c->get_array( 'dbcache.reject.constants' ); $this->use_filters = $this->_config->get_boolean( 'dbcache.use_filters' ); } /** * Executes query * * @param string $query * @return integer */ function query( $query ) { if ( !$this->wpdb_mixin->ready ) { return $this->next_injection->query( $query ); } $reason = ''; $cached = false; $data = false; $time_total = 0; $group = ''; $flush_after_query = false; $this->query_total++; $caching = $this->_can_cache( $query, $reason ); if ( preg_match( '~^\s*start transaction\b~is', $query ) ) { $this->cache_reject_reason = 'transaction'; $reason = $this->cache_reject_reason; $caching = false; } if ( preg_match( '~^\s*insert\b|^\s*delete\b|^\s*update\b|^\s*replace\b|^\s*commit\b|^\s*truncate\b|^\s*drop\b|^\s*create\b~is', $query ) ) { $this->cache_reject_reason = 'modification query'; $reason = $this->cache_reject_reason; $caching = false; $flush_after_query = true; } if ( $this->use_filters && function_exists( 'apply_filters' ) ) { $reason = apply_filters( 'w3tc_dbcache_can_cache_sql', ( $caching ? '' : $reason ), $query ); $caching = empty( $reason ); } if ( $caching ) { $this->wpdb_mixin->timer_start(); //$cache_key = $this->_get_cache_key($query); $cache = $this->_get_cache(); $group = $this->_get_group( $query ); $data = $cache->get( md5( $query ), $group ); $time_total = $this->wpdb_mixin->timer_stop(); } if ( is_array( $data ) ) { $cached = true; $this->query_hits++; $this->wpdb_mixin->last_error = $data['last_error']; $this->wpdb_mixin->last_query = $data['last_query']; $this->wpdb_mixin->last_result = $data['last_result']; $this->wpdb_mixin->col_info = $data['col_info']; $this->wpdb_mixin->num_rows = $data['num_rows']; $return_val = $data['return_val']; } else { $this->query_misses++; $this->wpdb_mixin->timer_start(); $return_val = $this->next_injection->query( $query ); $time_total = $this->wpdb_mixin->timer_stop(); if ( $flush_after_query ) { $group = $this->_get_group( $query ); $this->_flush_cache_for_sql_group( $group, array( 'modification_query' => $query ) ); } if ( $caching ) { $data = array( 'last_error' => $this->wpdb_mixin->last_error, 'last_query' => $this->wpdb_mixin->last_query, 'last_result' => $this->wpdb_mixin->last_result, 'col_info' => $this->wpdb_mixin->col_info, 'num_rows' => $this->wpdb_mixin->num_rows, 'return_val' => $return_val ); $cache = $this->_get_cache(); $group = $this->_get_group( $query ); $filter_data = array( 'query' => $query, 'group' => $group, 'content' => $data, 'expiration' => $this->_lifetime ); if ( $this->use_filters && function_exists( 'apply_filters' ) ) { $filter_data = apply_filters( 'w3tc_dbcache_cache_set', $filter_data ); } $cache->set( md5( $filter_data['query'] ), $filter_data['content'], $filter_data['expiration'], $filter_data['group'] ); } } if ( $this->debug ) { $this->query_stats[] = array( 'query' => $query, 'group' => $group, 'caching' => $caching, 'reason' => $reason, 'cached' => $cached, 'data_size' => ( $data ? strlen( serialize( $data ) ) : 0 ), 'time_total' => $time_total ); } $this->time_total += $time_total; return $return_val; } function _escape( $data ) { return $this->next_injection->_escape( $data ); } /** * Initializes object, calls underlying processor */ function initialize() { return $this->next_injection->initialize(); } /** * Insert a row into a table. * * @param string $table * @param array $data * @param array|string $format * @return int|false */ function insert( $table, $data, $format = null ) { return $this->next_injection->insert( $table, $data, $format ); } /** * Replace a row into a table. * * @param string $table * @param array $data * @param array|string $format * @return int|false */ function replace( $table, $data, $format = null ) { $group = $this->_get_group( $table ); $this->_flush_cache_for_sql_group( $group, array( 'wpdb_replace' => $table ) ); return $this->next_injection->replace( $table, $data, $format ); } /** * Update a row in the table * * @param string $table * @param array $data * @param array $where * @param array|string $format * @param array|string $format_where * @return int|false */ function update( $table, $data, $where, $format = null, $where_format = null ) { $group = $this->_get_group( $table ); $this->_flush_cache_for_sql_group( $group, array( 'wpdb_update' => $table ) ); return $this->next_injection->update( $table, $data, $where, $format, $where_format ); } /** * Deletes from table */ function delete( $table, $where, $where_format = null ) { $group = $this->_get_group( $table ); $this->_flush_cache_for_sql_group( $group, array( 'wpdb_delete' => $table ) ); return $this->next_injection->delete( $table, $where, $where_format ); } /** * Flushes cache * * @return boolean */ function flush_cache( $extras = array() ) { return $this->_flush_cache_for_sql_group( 'remaining', $extras ); } private function _flush_cache_for_sql_group( $group, $extras = array() ) { if ( $this->debug ) { $filename = Util_Debug::log( 'dbcache', 'flushing based on sqlquery group ' . $group . ' with extras ' . json_encode( $extras ) ); } $cache = $this->_get_cache(); $flush_groups = $this->_get_flush_groups( $group, $extras ); $v = true; foreach ( $flush_groups as $f_group => $nothing ) { if ( $this->debug ) { $filename = Util_Debug::log( 'dbcache', 'flush group ' . $f_group ); } $v &= $cache->flush( $f_group ); } return $v; } /** * Returns cache object * * @return W3_Cache_Base */ function _get_cache() { static $cache = array(); if ( !isset( $cache[0] ) ) { $engine = $this->_config->get_string( 'dbcache.engine' ); switch ( $engine ) { case 'memcached': $engineConfig = array( 'servers' => $this->_config->get_array( 'dbcache.memcached.servers' ), 'persistent' => $this->_config->get_boolean( 'dbcache.memcached.persistent' ), 'aws_autodiscovery' => $this->_config->get_boolean( 'dbcache.memcached.aws_autodiscovery' ), 'username' => $this->_config->get_string( 'dbcache.memcached.username' ), 'password' => $this->_config->get_string( 'dbcache.memcached.password' ) ); break; case 'redis': $engineConfig = array( 'servers' => $this->_config->get_array( 'dbcache.redis.servers' ), 'persistent' => $this->_config->get_boolean( 'dbcache.redis.persistent' ), 'dbid' => $this->_config->get_integer( 'dbcache.redis.dbid' ), 'password' => $this->_config->get_string( 'dbcache.redis.password' ) ); break; case 'file': $engineConfig = array( 'use_wp_hash' => true, 'section' => 'db', 'locking' => $this->_config->get_boolean( 'dbcache.file.locking' ), 'flush_timelimit' => $this->_config->get_integer( 'timelimit.cache_flush' ) ); break; default: $engineConfig = array(); } $engineConfig['module'] = 'dbcache'; $engineConfig['host'] = Util_Environment::host(); $engineConfig['instance_id'] = Util_Environment::instance_id(); $cache[0] = Cache::instance( $engine, $engineConfig ); } return $cache[0]; } /** * Check if can cache sql * * @param string $sql * @param string $cache_reject_reason * @return boolean */ function _can_cache( $sql, &$cache_reject_reason ) { /** * Skip if request-wide reject reason specified. * Note - as a result requedt-wide checks are done only once per request */ if ( !is_null( $this->cache_reject_reason ) ) { $cache_reject_reason = $this->cache_reject_reason; $this->cache_reject_request_wide = true; return false; } /** * Do once-per-request check if needed */ if ( is_null( $this->can_cache_once_per_request_result ) ) { $this->can_cache_once_per_request_result = $this->_can_cache_once_per_request(); if ( !$this->can_cache_once_per_request_result ) { $this->cache_reject_request_wide = true; return false; } } /** * Check for constants */ foreach ( $this->reject_constants as $name ) { if ( defined( $name ) && constant( $name ) ) { $this->cache_reject_reason = $name . ' constant defined'; $cache_reject_reason = $this->cache_reject_reason; return false; } } /** * Check for AJAX requests */ $ajax_skip = false; if ( defined( 'DOING_AJAX' ) ) { // wp_admin is always defined for ajax requests, check by referrer if ( isset( $_SERVER['HTTP_REFERER'] ) && strpos( $_SERVER['HTTP_REFERER'], '/wp-admin/' ) === false ) $ajax_skip = true; } /** * Skip if admin */ if ( defined( 'WP_ADMIN' ) && !$ajax_skip ) { $this->cache_reject_reason = 'WP_ADMIN'; $cache_reject_reason = $this->cache_reject_reason; return false; } /** * Skip if SQL is rejected */ if ( !$this->_check_sql( $sql ) ) { $cache_reject_reason = 'query not cacheable'; return false; } /** * Skip if user is logged in */ if ( $this->reject_logged && !$this->_check_logged_in() ) { $this->cache_reject_reason = 'user.logged_in'; $cache_reject_reason = $this->cache_reject_reason; return false; } return true; } /** * Check if can cache sql, checks which have constant results during whole request * * @return boolean */ function _can_cache_once_per_request() { /** * Skip if disabled */ if ( !$this->_config->get_boolean( 'dbcache.enabled' ) ) { $this->cache_reject_reason = 'dbcache.disabled'; return false; } /** * Skip if request URI is rejected */ if ( !$this->_check_request_uri() ) { $this->cache_reject_reason = 'request'; return false; } /** * Skip if cookie is rejected */ if ( !$this->_check_cookies() ) { $this->cache_reject_reason = 'cookie'; return false; } return true; } /** * Check SQL * * @param string $sql * @return boolean */ function _check_sql( $sql ) { $auto_reject_strings = $this->_config->get_array( 'dbcache.reject.words' ); if ( preg_match( '~' . implode( '|', $auto_reject_strings ) . '~is', $sql ) ) { return false; } $reject_sql = $this->_config->get_array( 'dbcache.reject.sql' ); foreach ( $reject_sql as $expr ) { $expr = trim( $expr ); $expr = str_replace( '{prefix}', $this->wpdb_mixin->prefix, $expr ); if ( $expr != '' && preg_match( '~' . $expr . '~i', $sql ) ) { return false; } } return true; } /** * Check request URI * * @return boolean */ function _check_request_uri() { $auto_reject_uri = array( 'wp-login', 'wp-register' ); foreach ( $auto_reject_uri as $uri ) { if ( strstr( $_SERVER['REQUEST_URI'], $uri ) !== false ) { return false; } } $reject_uri = $this->_config->get_array( 'dbcache.reject.uri' ); $reject_uri = array_map( array( '\W3TC\Util_Environment', 'parse_path' ), $reject_uri ); foreach ( $reject_uri as $expr ) { $expr = trim( $expr ); if ( $expr != '' && preg_match( '~' . $expr . '~i', $_SERVER['REQUEST_URI'] ) ) { return false; } } return true; } /** * Checks for WordPress cookies * * @return boolean */ function _check_cookies() { foreach ( array_keys( $_COOKIE ) as $cookie_name ) { if ( $cookie_name == 'wordpress_test_cookie' ) { continue; } if ( preg_match( '/^wp-postpass|^comment_author/', $cookie_name ) ) { return false; } } foreach ( $this->_config->get_array( 'dbcache.reject.cookie' ) as $reject_cookie ) { foreach ( array_keys( $_COOKIE ) as $cookie_name ) { if ( strstr( $cookie_name, $reject_cookie ) !== false ) { return false; } } } return true; } /** * Check if user is logged in * * @return boolean */ function _check_logged_in() { foreach ( array_keys( $_COOKIE ) as $cookie_name ) { if ( strpos( $cookie_name, 'wordpress_logged_in' ) === 0 ) return false; } return true; } private function _get_group( $sql ) { $sql = strtolower( $sql ); // collect list of tables used in query if ( preg_match_all( '~(^|[\s,`])' . $this->wpdb_mixin->prefix . '([0-9a-zA-Z_]+)~i', $sql, $m ) ) { $tables = array_unique( $m[2] ); } else { $tables = array(); } if ( $this->contains_only_tables( $tables, array( 'options' => '*' ) ) ) { $group = 'options'; } elseif ( $this->contains_only_tables( $tables, array( 'comments' => '*', 'commentsmeta' => '*' ) ) ) { $group = 'comments'; } elseif ( count( $tables ) <= 1 ) { $group = 'singletables'; // request with single table affected } else { $group = 'remaining'; } if ( $this->use_filters && function_exists( 'apply_filters' ) ) { $group = apply_filters( 'w3tc_dbcache_get_sql_group', $group, $sql, $tables ); } return $group; } private function contains_only_tables( $tables, $allowed ) { if ( empty( $tables ) ) { return false; } foreach ( $tables as $t ) { if ( !isset( $allowed[$t] ) ) { return false; } } return true; } private function _get_flush_groups( $group, $extras = array() ) { $groups_to_flush = array(); switch ( $group ) { case 'remaining': case 'singletables': $groups_to_flush = array( 'remaining' => '*', 'options' => '*', 'comments' => '*', 'singletables' => '*' ); break; // options are updated on each second request, // ignore by default probability that SELECTs with joins with options // are critical and don't flush "remaining". // That can be changed by w3tc_dbcache_get_flush_groups filter case 'options': $groups_to_flush = array( $group => '*' ); break; default: $groups_to_flush = array( $group => '*', 'remaining' => '*' ); } if ( $this->use_filters && function_exists( 'apply_filters' ) ) { $groups_to_flush = apply_filters( 'w3tc_dbcache_get_flush_groups', $groups_to_flush, $group, $extras ); } return $groups_to_flush; } public function get_reject_reason() { if ( is_null( $this->cache_reject_reason ) ) return ''; $request_wide_string = $this->cache_reject_request_wide ? ( function_exists( '__' ) ? __( 'Request-wide', 'w3-total-cache' ).' ' : 'Request ' ) : ''; return $request_wide_string . $this->_get_reject_reason_message( $this->cache_reject_reason ); } /** * * * @param unknown $key * @return string|void */ private function _get_reject_reason_message( $key ) { if ( !function_exists( '__' ) ) return $key; switch ( $key ) { case 'dbcache.disabled': return __( 'Database caching is disabled', 'w3-total-cache' ); case 'DONOTCACHEDB': return __( 'DONOTCACHEDB constant is defined', 'w3-total-cache' ); case 'DOING_AJAX': return __( 'Doing AJAX', 'w3-total-cache' ); case 'request': return __( 'Request URI is rejected', 'w3-total-cache' ); case 'cookie': return __( 'Cookie is rejected', 'w3-total-cache' ); case 'DOING_CRONG': return __( 'Doing cron', 'w3-total-cache' ); case 'APP_REQUEST': return __( 'Application request', 'w3-total-cache' ); case 'XMLRPC_REQUEST': return __( 'XMLRPC request', 'w3-total-cache' ); case 'WP_ADMIN': return __( 'wp-admin', 'w3-total-cache' ); case 'SHORTINIT': return __( 'Short init', 'w3-total-cache' ); case 'query': return __( 'Query is rejected', 'w3-total-cache' ); case 'user.logged_in': return __( 'User is logged in', 'w3-total-cache' ); default: return $key; } } public function w3tc_footer_comment( $strings ) { $reason = $this->get_reject_reason(); $append = ( $reason ? sprintf( ' (%s)', $reason ) : '' ); if ( $this->query_hits ) { $strings[] = sprintf( __( 'Database Caching %d/%d queries in %.3f seconds using %s%s', 'w3-total-cache' ), $this->query_hits, $this->query_total, $this->time_total, Cache::engine_name( $this->_config->get_string( 'dbcache.engine' ) ), $append ); } else { $strings[] = sprintf( __( 'Database Caching using %s%s', 'w3-total-cache' ), Cache::engine_name( $this->_config->get_string( 'dbcache.engine' ) ), $append ); } if ( $this->debug ) { $strings[] = ''; $strings[] = "Db cache debug info:"; $strings[] = sprintf( "%s%d", str_pad( 'Total queries: ', 20 ), $this->query_total ); $strings[] = sprintf( "%s%d", str_pad( 'Cached queries: ', 20 ), $this->query_hits ); $strings[] = sprintf( "%s%.4f", str_pad( 'Total query time: ', 20 ), $this->time_total ); if ( count( $this->query_stats ) ) { $strings[] = "SQL info:"; $strings[] = sprintf( "%s | %s | %s | % s | %s | %s | %s", str_pad( '#', 5, ' ', STR_PAD_LEFT ), str_pad( 'Time (s)', 8, ' ', STR_PAD_LEFT ), str_pad( 'Caching (Reject reason)', 30, ' ', STR_PAD_BOTH ), str_pad( 'Status', 10, ' ', STR_PAD_BOTH ), str_pad( 'Data size (b)', 13, ' ', STR_PAD_LEFT ), str_pad( 'Group', 10, ' ', STR_PAD_BOTH ), 'Query' ); foreach ( $this->query_stats as $index => $query ) { $strings[] = sprintf( "%s | %s | %s | %s | %s | %s | %s", str_pad( $index + 1, 5, ' ', STR_PAD_LEFT ), str_pad( round( $query['time_total'], 4 ), 8, ' ', STR_PAD_LEFT ), str_pad( ( $query['caching'] ? 'enabled' : sprintf( 'disabled (%s)', $query['reason'] ) ), 30, ' ', STR_PAD_BOTH ), str_pad( ( $query['cached'] ? 'cached' : 'not cached' ), 10, ' ', STR_PAD_BOTH ), str_pad( $query['data_size'], 13, ' ', STR_PAD_LEFT ), str_pad( $query['group'], 10, ' ', STR_PAD_LEFT ), trim( $query['query'] ) ); } } $strings[] = ''; } return $strings; } public function w3tc_usage_statistics_of_request( $storage ) { $storage->counter_add( 'dbcache_calls_total', $this->query_total ); $storage->counter_add( 'dbcache_calls_hits', $this->query_hits ); } }