ecution_time = 0; } // Leave 1 second "buffer" for other operations if $max_execution_time has reasonable value. if ( $max_execution_time > 10 ) { $max_execution_time -= 1; } } /** * Filters the amount of storage space used by one directory and all its children, in megabytes. * * Return the actual used space to short-circuit the recursive PHP file size calculation * and use something else, like a CDN API or native operating system tools for better performance. * * @since 5.6.0 * * @param int|false $space_used The amount of used space, in bytes. Default false. * @param string $directory Full path of a directory. * @param string|string[]|null $exclude Full path of a subdirectory to exclude from the total, * or array of paths. * @param int $max_execution_time Maximum time to run before giving up. In seconds. * @param array $directory_cache Array of cached directory paths. */ $size = apply_filters( 'pre_recurse_dirsize', false, $directory, $exclude, $max_execution_time, $directory_cache ); if ( false === $size ) { $size = 0; $handle = opendir( $directory ); if ( $handle ) { while ( ( $file = readdir( $handle ) ) !== false ) { $path = $directory . '/' . $file; if ( '.' !== $file && '..' !== $file ) { if ( is_file( $path ) ) { $size += filesize( $path ); } elseif ( is_dir( $path ) ) { $handlesize = recurse_dirsize( $path, $exclude, $max_execution_time, $directory_cache ); if ( $handlesize > 0 ) { $size += $handlesize; } } if ( $max_execution_time > 0 && ( microtime( true ) - WP_START_TIMESTAMP ) > $max_execution_time ) { // Time exceeded. Give up instead of risking a fatal timeout. $size = null; break; } } } closedir( $handle ); } } if ( ! is_array( $directory_cache ) ) { $directory_cache = array(); } $directory_cache[ $directory ] = $size; // Only write the transient on the top level call and not on recursive calls. if ( $save_cache ) { $expiration = ( wp_using_ext_object_cache() ) ? 0 : 10 * YEAR_IN_SECONDS; set_transient( 'dirsize_cache', $directory_cache, $expiration ); } return $size; } /** * Cleans directory size cache used by recurse_dirsize(). * * Removes the current directory and all parent directories from the `dirsize_cache` transient. * * @since 5.6.0 * @since 5.9.0 Added input validation with a notice for invalid input. * * @param string $path Full path of a directory or file. */ function clean_dirsize_cache( $path ) { if ( ! is_string( $path ) || empty( $path ) ) { wp_trigger_error( '', sprintf( /* translators: 1: Function name, 2: A variable type, like "boolean" or "integer". */ __( '%1$s only accepts a non-empty path string, received %2$s.' ), 'clean_dirsize_cache()', '' . gettype( $path ) . '' ) ); return; } $directory_cache = get_transient( 'dirsize_cache' ); if ( empty( $directory_cache ) ) { return; } $expiration = ( wp_using_ext_object_cache() ) ? 0 : 10 * YEAR_IN_SECONDS; if ( ! str_contains( $path, '/' ) && ! str_contains( $path, '\\' ) ) { unset( $directory_cache[ $path ] ); set_transient( 'dirsize_cache', $directory_cache, $expiration ); return; } $last_path = null; $path = untrailingslashit( $path ); unset( $directory_cache[ $path ] ); while ( $last_path !== $path && DIRECTORY_SEPARATOR !== $path && '.' !== $path && '..' !== $path ) { $last_path = $path; $path = dirname( $path ); unset( $directory_cache[ $path ] ); } set_transient( 'dirsize_cache', $directory_cache, $expiration ); } /** * Checks compatibility with the current WordPress version. * * @since 5.2.0 * * @global string $wp_version The WordPress version string. * * @param string $required Minimum required WordPress version. * @return bool True if required version is compatible or empty, false if not. */ function is_wp_version_compatible( $required ) { global $wp_version; // Strip off any -alpha, -RC, -beta, -src suffixes. list( $version ) = explode( '-', $wp_version ); if ( is_string( $required ) ) { $trimmed = trim( $required ); if ( substr_count( $trimmed, '.' ) > 1 && str_ends_with( $trimmed, '.0' ) ) { $required = substr( $trimmed, 0, -2 ); } } return empty( $required ) || version_compare( $version, $required, '>=' ); } /** * Checks compatibility with the current PHP version. * * @since 5.2.0 * * @param string $required Minimum required PHP version. * @return bool True if required version is compatible or empty, false if not. */ function is_php_version_compatible( $required ) { return empty( $required ) || version_compare( PHP_VERSION, $required, '>=' ); } /** * Checks if two numbers are nearly the same. * * This is similar to using `round()` but the precision is more fine-grained. * * @since 5.3.0 * * @param int|float $expected The expected value. * @param int|float $actual The actual number. * @param int|float $precision Optional. The allowed variation. Default 1. * @return bool Whether the numbers match within the specified precision. */ function wp_fuzzy_number_match( $expected, $actual, $precision = 1 ) { return abs( (float) $expected - (float) $actual ) <= $precision; } /** * Creates and returns the markup for an admin notice. * * @since 6.4.0 * * @param string $message The message. * @param array $args { * Optional. An array of arguments for the admin notice. Default empty array. * * @type string $type Optional. The type of admin notice. * For example, 'error', 'success', 'warning', 'info'. * Default empty string. * @type bool $dismissible Optional. Whether the admin notice is dismissible. Default false. * @type string $id Optional. The value of the admin notice's ID attribute. Default empty string. * @type string[] $additional_classes Optional. A string array of class names. Default empty array. * @type string[] $attributes Optional. Additional attributes for the notice div. Default empty array. * @type bool $paragraph_wrap Optional. Whether to wrap the message in paragraph tags. Default true. * } * @return string The markup for an admin notice. */ function wp_get_admin_notice( $message, $args = array() ) { $defaults = array( 'type' => '', 'dismissible' => false, 'id' => '', 'additional_classes' => array(), 'attributes' => array(), 'paragraph_wrap' => true, ); $args = wp_parse_args( $args, $defaults ); /** * Filters the arguments for an admin notice. * * @since 6.4.0 * * @param array $args The arguments for the admin notice. * @param string $message The message for the admin notice. */ $args = apply_filters( 'wp_admin_notice_args', $args, $message ); $id = ''; $classes = 'notice'; $attributes = ''; if ( is_string( $args['id'] ) ) { $trimmed_id = trim( $args['id'] ); if ( '' !== $trimmed_id ) { $id = 'id="' . $trimmed_id . '" '; } } if ( is_string( $args['type'] ) ) { $type = trim( $args['type'] ); if ( str_contains( $type, ' ' ) ) { _doing_it_wrong( __FUNCTION__, sprintf( /* translators: %s: The "type" key. */ __( 'The %s key must be a string without spaces.' ), 'type' ), '6.4.0' ); } if ( '' !== $type ) { $classes .= ' notice-' . $type; } } if ( true === $args['dismissible'] ) { $classes .= ' is-dismissible'; } if ( is_array( $args['additional_classes'] ) && ! empty( $args['additional_classes'] ) ) { $classes .= ' ' . implode( ' ', $args['additional_classes'] ); } if ( is_array( $args['attributes'] ) && ! empty( $args['attributes'] ) ) { $attributes = ''; foreach ( $args['attributes'] as $attr => $val ) { if ( is_bool( $val ) ) { $attributes .= $val ? ' ' . $attr : ''; } elseif ( is_int( $attr ) ) { $attributes .= ' ' . esc_attr( trim( $val ) ); } elseif ( $val ) { $attributes .= ' ' . $attr . '="' . esc_attr( trim( $val ) ) . '"'; } } } if ( false !== $args['paragraph_wrap'] ) { $message = "

$message

"; } $markup = sprintf( '
%4$s
', $id, $classes, $attributes, $message ); /** * Filters the markup for an admin notice. * * @since 6.4.0 * * @param string $markup The HTML markup for the admin notice. * @param string $message The message for the admin notice. * @param array $args The arguments for the admin notice. */ return apply_filters( 'wp_admin_notice_markup', $markup, $message, $args ); } /** * Outputs an admin notice. * * @since 6.4.0 * * @param string $message The message to output. * @param array $args { * Optional. An array of arguments for the admin notice. Default empty array. * * @type string $type Optional. The type of admin notice. * For example, 'error', 'success', 'warning', 'info'. * Default empty string. * @type bool $dismissible Optional. Whether the admin notice is dismissible. Default false. * @type string $id Optional. The value of the admin notice's ID attribute. Default empty string. * @type string[] $additional_classes Optional. A string array of class names. Default empty array. * @type string[] $attributes Optional. Additional attributes for the notice div. Default empty array. * @type bool $paragraph_wrap Optional. Whether to wrap the message in paragraph tags. Default true. * } */ function wp_admin_notice( $message, $args = array() ) { /** * Fires before an admin notice is output. * * @since 6.4.0 * * @param string $message The message for the admin notice. * @param array $args The arguments for the admin notice. */ do_action( 'wp_admin_notice', $message, $args ); echo wp_kses_post( wp_get_admin_notice( $message, $args ) ); } re_format Format where. * * @return int|false */ public 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. * * @param array $extras Extra arguments. * * @return bool */ public function flush_cache( $extras = array() ) { return $this->_flush_cache_for_sql_group( 'remaining', $extras ); } /** * Flush cache for SQL groups. * * @access private * * @param string $group Group. * @param array $extras Extra arguments. * * @return bool */ private function _flush_cache_for_sql_group( $group, $extras = array() ) { $this->wpdb_mixin->timer_start(); if ( $this->debug ) { $filename = Util_Debug::log( 'dbcache', 'flushing based on sqlquery group ' . $group . ' with extras ' . wp_json_encode( $extras ) ); } if ( $this->_config->get_boolean( 'dbcache.debug_purge' ) ) { Util_Debug::log_purge( 'dbcache', '_flush_cache_for_sql_group', array( $group, $extras ) ); } $cache = $this->_get_cache(); $flush_groups = $this->_get_flush_groups( $group, $extras ); $v = true; $this->cache_flushes++; foreach ( $flush_groups as $f_group => $nothing ) { if ( $this->debug ) { $filename = Util_Debug::log( 'dbcache', 'flush group ' . $f_group ); } $v &= $cache->flush( $f_group ); } $this->time_total += $this->wpdb_mixin->timer_stop(); return $v; } /** * Returns cache object. * * @return W3_Cache_Base */ public function _get_cache() { static $cache = array(); if ( ! isset( $cache[0] ) ) { $engine = $this->_config->get_string( 'dbcache.engine' ); switch ( $engine ) { case 'memcached': $engine_config = 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' ), 'binary_protocol' => $this->_config->get_boolean( 'dbcache.memcached.binary_protocol' ), ); break; case 'redis': $engine_config = array( 'servers' => $this->_config->get_array( 'dbcache.redis.servers' ), 'verify_tls_certificates' => $this->_config->get_boolean( 'dbcache.redis.verify_tls_certificates' ), 'persistent' => $this->_config->get_boolean( 'dbcache.redis.persistent' ), 'timeout' => $this->_config->get_integer( 'dbcache.redis.timeout' ), 'retry_interval' => $this->_config->get_integer( 'dbcache.redis.retry_interval' ), 'read_timeout' => $this->_config->get_integer( 'dbcache.redis.read_timeout' ), 'dbid' => $this->_config->get_integer( 'dbcache.redis.dbid' ), 'password' => $this->_config->get_string( 'dbcache.redis.password' ), ); break; case 'file': $engine_config = 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: $engine_config = array(); } $engine_config['module'] = 'dbcache'; $engine_config['host'] = Util_Environment::host(); $engine_config['instance_id'] = Util_Environment::instance_id(); $cache[0] = Cache::instance( $engine, $engine_config ); } return $cache[0]; } /** * Check if can cache sql. * * @param string $sql SQL query. * @param string $cache_reject_reason Cache reject reason. * * @return boolean */ public 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' ) ) { $http_referer = isset( $_SERVER['HTTP_REFERER'] ) ? filter_var( stripslashes( $_SERVER['HTTP_REFERER'] ), FILTER_SANITIZE_URL ) : ''; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput // wp_admin is always defined for ajax requests, check by referrer. if ( strpos( $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 bool */ public 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 SQL query. * * @return bool */ public 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 ( ! empty( $expr ) && preg_match( '~' . $expr . '~i', $sql ) ) { return false; } } return true; } /** * Check request URI * * @return boolean */ public function _check_request_uri() { $auto_reject_uri = array( 'wp-login', 'wp-register', ); $request_uri = isset( $_SERVER['REQUEST_URI'] ) ? filter_var( stripslashes( $_SERVER['REQUEST_URI'] ), FILTER_SANITIZE_URL ) : ''; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput foreach ( $auto_reject_uri as $uri ) { if ( strstr( $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 ( ! empty( $expr ) && preg_match( '~' . $expr . '~i', $request_uri ) ) { return false; } } return true; } /** * Checks for WordPress cookies. * * @return bool */ public function _check_cookies() { foreach ( array_keys( $_COOKIE ) as $cookie_name ) { if ( 'wordpress_test_cookie' === $cookie_name ) { 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 bool */ public function _check_logged_in() { foreach ( array_keys( $_COOKIE ) as $cookie_name ) { if ( strpos( $cookie_name, 'wordpress_logged_in' ) === 0 ) { return false; } } return true; } /** * Get group. * * @access private * * @param string $sql SQL query. * * @return string */ 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; } /** * Contains only tables. * * @accress private * * @param array $tables Tables. * * @param array $allowed Allowed. */ private function contains_only_tables( $tables, $allowed ) { if ( empty( $tables ) ) { return false; } foreach ( $tables as $t ) { if ( ! isset( $allowed[ $t ] ) ) { return false; } } return true; } /** * Get flush groups * * @access private * * @param string $group Group. * * @param array $extras Extra arguments. */ 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; } /** * Get reject reason. * * @return string */ 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 ); } /** * Get reject reason message. * * @param string $key 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; } } /** * Footer comment. * * @param array $strings Strings. * * @return array */ public function w3tc_footer_comment( $strings ) { $reject_reason = $this->get_reject_reason(); $append = empty( $reject_reason ) ? '' : sprintf( ' (%1$s)', $reject_reason ); if ( $this->query_hits ) { $strings[] = sprintf( // translators: 1: Query hits, 2: Total queries, 3: Total time, 4: Engine name, 5: Reject reason. __( 'Database Caching %1$d/%2$d queries in %3$.3f seconds using %4$s%5$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( // translators: 1: Engine name, 2: Reject reason. __( 'Database Caching using %1$s%2$s', 'w3-total-cache' ), Cache::engine_name( $this->_config->get_string( 'dbcache.engine' ) ), $append ); } if ( $this->debug ) { $strings[] = ''; $strings[] = __( 'Db cache debug info:', 'w3-total-cache' ); $strings[] = sprintf( '%1$s%2$d', str_pad( __( 'Total queries: ', 'w3-total-cache' ), 20 ), $this->query_total ); $strings[] = sprintf( '%1$s%2$d', str_pad( __( 'Cached queries: ', 'w3-total-cache' ), 20 ), $this->query_hits ); $strings[] = sprintf( '%1$s%2$.4f', str_pad( __( 'Total query time: ', 'w3-total-cache' ), 20 ), $this->time_total ); } if ( $this->log_filehandle ) { fclose( $this->log_filehandle ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_read_fclose $this->log_filehandle = false; } return $strings; } /** * Usage statistics of request. * * @param object $storage Storage object. * * @return void */ 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 ); $storage->counter_add( 'dbcache_flushes', $this->cache_flushes ); $time_ms = (int) ( $this->time_total * 1000 ); $storage->counter_add( 'dbcache_time_ms', $time_ms ); } /** * Log query. * * @access private * * @param string $line Line to add. * * @return void */ private function log_query( $line ) { if ( ! $this->log_filehandle ) { $filename = Util_Debug::log_filename( 'dbcache-queries' ); $this->log_filehandle = fopen( $filename, 'a' ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_read_fopen } fputcsv( $this->log_filehandle, $line, "\t" ); } }