Merge branch 'update/versioned-transients-alt'

This commit is contained in:
Mike Jolley 2019-02-06 13:18:50 +00:00
commit 00b48febac
9 changed files with 206 additions and 158 deletions

View File

@ -37,7 +37,9 @@ class WC_REST_System_Status_Tools_V2_Controller extends WC_REST_Controller {
*/
public function register_routes() {
register_rest_route(
$this->namespace, '/' . $this->rest_base, array(
$this->namespace,
'/' . $this->rest_base,
array(
array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'get_items' ),
@ -49,7 +51,9 @@ class WC_REST_System_Status_Tools_V2_Controller extends WC_REST_Controller {
);
register_rest_route(
$this->namespace, '/' . $this->rest_base . '/(?P<id>[\w-]+)', array(
$this->namespace,
'/' . $this->rest_base . '/(?P<id>[\w-]+)',
array(
'args' => array(
'id' => array(
'description' => __( 'Unique identifier for the resource.', 'woocommerce' ),
@ -199,7 +203,7 @@ class WC_REST_System_Status_Tools_V2_Controller extends WC_REST_Controller {
__( 'Note:', 'woocommerce' ),
__( 'This tool will update your WooCommerce database to the latest version. Please ensure you make sufficient backups before proceeding.', 'woocommerce' )
),
)
),
);
// Jetpack does the image resizing heavy lifting so you don't have to.
@ -226,7 +230,8 @@ class WC_REST_System_Status_Tools_V2_Controller extends WC_REST_Controller {
'name' => $tool['name'],
'action' => $tool['button'],
'description' => $tool['desc'],
), $request
),
$request
)
);
}
@ -254,7 +259,8 @@ class WC_REST_System_Status_Tools_V2_Controller extends WC_REST_Controller {
'name' => $tool['name'],
'action' => $tool['button'],
'description' => $tool['desc'],
), $request
),
$request
)
);
}
@ -418,6 +424,7 @@ class WC_REST_System_Status_Tools_V2_Controller extends WC_REST_Controller {
case 'clear_transients':
wc_delete_product_transients();
wc_delete_shop_order_transients();
delete_transient( 'wc_count_comments' );
$attribute_taxonomies = wc_get_attribute_taxonomies();
@ -494,14 +501,16 @@ class WC_REST_System_Status_Tools_V2_Controller extends WC_REST_Controller {
case 'recount_terms':
$product_cats = get_terms(
'product_cat', array(
'product_cat',
array(
'hide_empty' => false,
'fields' => 'id=>parent',
)
);
_wc_term_recount( $product_cats, get_taxonomy( 'product_cat' ), true, false );
$product_tags = get_terms(
'product_tag', array(
'product_tag',
array(
'hide_empty' => false,
'fields' => 'id=>parent',
)

View File

@ -12,18 +12,64 @@ defined( 'ABSPATH' ) || exit;
*/
class WC_Cache_Helper {
/**
* Transients to delete on shutdown.
*
* @var array Array of transient keys.
*/
private static $delete_transients = array();
/**
* Hook in methods.
*/
public static function init() {
add_action( 'shutdown', array( __CLASS__, 'delete_transients_on_shutdown' ), 10 );
add_action( 'template_redirect', array( __CLASS__, 'geolocation_ajax_redirect' ) );
add_action( 'admin_notices', array( __CLASS__, 'notices' ) );
add_action( 'delete_version_transients', array( __CLASS__, 'delete_version_transients' ) );
add_action( 'delete_version_transients', array( __CLASS__, 'delete_version_transients' ), 10 );
add_action( 'wp', array( __CLASS__, 'prevent_caching' ) );
add_action( 'clean_term_cache', array( __CLASS__, 'clean_term_cache' ), 10, 2 );
add_action( 'edit_terms', array( __CLASS__, 'clean_term_cache' ), 10, 2 );
}
/**
* Add a transient to delete on shutdown.
*
* @since 3.6.0
* @param string|array $keys Transient key or keys.
*/
public static function queue_delete_transient( $keys ) {
self::$delete_transients = array_unique( array_merge( is_array( $keys ) ? $keys : array( $keys ), self::$delete_transients ) );
}
/**
* Transients that don't need to be cleaned right away can be deleted on shutdown to avoid repetition.
*
* @since 3.6.0
*/
public static function delete_transients_on_shutdown() {
if ( self::$delete_transients ) {
foreach ( self::$delete_transients as $key ) {
delete_transient( $key );
}
self::$delete_transients = array();
}
}
/**
* Used to clear layered nav counts based on passed attribute names.
*
* @since 3.6.0
* @param array $attribute_keys Attribute keys.
*/
public static function invalidate_attribute_count( $attribute_keys ) {
if ( $attribute_keys ) {
foreach ( $attribute_keys as $attribute_key ) {
self::queue_delete_transient( 'wc_layered_nav_counts_' . $attribute_key );
}
}
}
/**
* Get prefix for use with wp_cache_set. Allows all cache in a group to be invalidated at once.
*
@ -134,62 +180,16 @@ class WC_Cache_Helper {
public static function get_transient_version( $group, $refresh = false ) {
$transient_name = $group . '-transient-version';
$transient_value = get_transient( $transient_name );
$transient_value = strval( $transient_value ? $transient_value : '' );
if ( '' === $transient_value || true === $refresh ) {
$old_transient_value = $transient_value;
$transient_value = (string) time();
if ( $old_transient_value === $transient_value ) {
// Time did not change but transient needs flushing now.
self::delete_version_transients( $transient_value );
} else {
self::queue_delete_version_transients( $transient_value );
}
if ( false === $transient_value || true === $refresh ) {
$transient_value = (string) time();
set_transient( $transient_name, $transient_value );
}
return $transient_value;
}
/**
* Queues a cleanup event for version transients.
*
* @param string $version Version of the transient to remove.
*/
protected static function queue_delete_version_transients( $version = '' ) {
if ( ! wp_using_ext_object_cache() && ! empty( $version ) ) {
wp_schedule_single_event( time() + 30, 'delete_version_transients', array( $version ) );
}
}
/**
* When the transient version increases, this is used to remove all past transients to avoid filling the DB.
*
* Note; this only works on transients appended with the transient version, and when object caching is not being used.
*
* @since 2.3.10
* @param string $version Version of the transient to remove.
*/
public static function delete_version_transients( $version = '' ) {
if ( ! wp_using_ext_object_cache() && ! empty( $version ) ) {
global $wpdb;
$limit = apply_filters( 'woocommerce_delete_version_transients_limit', 1000 );
if ( ! $limit ) {
return;
}
$affected = $wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->options} WHERE option_name LIKE %s LIMIT %d;", '\_transient\_%' . $version, $limit ) ); // WPCS: cache ok, db call ok.
// If affected rows is equal to limit, there are more rows to delete. Delete in 30 secs.
if ( $affected === $limit ) {
self::queue_delete_version_transients( $version );
}
}
}
/**
* Set constants to prevent caching by some plugins.
*
@ -254,6 +254,34 @@ class WC_Cache_Helper {
}
}
}
/**
* When the transient version increases, this is used to remove all past transients to avoid filling the DB.
*
* Note; this only works on transients appended with the transient version, and when object caching is not being used.
*
* @deprecated 3.6.0 Adjusted transient usage to include versions within the transient values, making this cleanup obsolete.
* @since 2.3.10
* @param string $version Version of the transient to remove.
*/
public static function delete_version_transients( $version = '' ) {
if ( ! wp_using_ext_object_cache() && ! empty( $version ) ) {
global $wpdb;
$limit = apply_filters( 'woocommerce_delete_version_transients_limit', 1000 );
if ( ! $limit ) {
return;
}
$affected = $wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->options} WHERE option_name LIKE %s LIMIT %d;", '\_transient\_%' . $version, $limit ) ); // WPCS: cache ok, db call ok.
// If affected rows is equal to limit, there are more rows to delete. Delete in 30 secs.
if ( $affected === $limit ) {
wp_schedule_single_event( time() + 30, 'delete_version_transients', array( $version ) );
}
}
}
}
WC_Cache_Helper::init();

View File

@ -226,7 +226,8 @@ class WC_Shipping {
public function get_shipping_classes() {
if ( empty( $this->shipping_classes ) ) {
$classes = get_terms(
'product_shipping_class', array(
'product_shipping_class',
array(
'hide_empty' => '0',
'orderby' => 'name',
)
@ -317,9 +318,12 @@ class WC_Shipping {
unset( $package_to_hash['contents'][ $item_id ]['data'] );
}
// Get rates stored in the WC session data for this package.
$wc_session_key = 'shipping_for_package_' . $package_key;
$stored_rates = WC()->session->get( $wc_session_key );
// Calculate the hash for this package so we can tell if it's changed since last calculation.
$package_hash = 'wc_ship_' . md5( wp_json_encode( $package_to_hash ) . WC_Cache_Helper::get_transient_version( 'shipping' ) );
$session_key = 'shipping_for_package_' . $package_key;
$stored_rates = WC()->session->get( $session_key );
if ( ! is_array( $stored_rates ) || $package_hash !== $stored_rates['package_hash'] || 'yes' === get_option( 'woocommerce_shipping_debug_mode', 'no' ) ) {
foreach ( $this->load_shipping_methods( $package ) as $shipping_method ) {
@ -333,7 +337,8 @@ class WC_Shipping {
// Store in session to avoid recalculation.
WC()->session->set(
$session_key, array(
$wc_session_key,
array(
'package_hash' => $package_hash,
'rates' => $package['rates'],
)

View File

@ -128,12 +128,11 @@ class WC_Product_Data_Store_CPT extends WC_Data_Store_WP implements WC_Object_Da
$this->update_attributes( $product, true );
$this->update_version_and_type( $product );
$this->handle_updated_props( $product );
$this->clear_caches( $product );
$product->save_meta_data();
$product->apply_changes();
$this->clear_caches( $product );
do_action( 'woocommerce_new_product', $id );
}
}
@ -245,11 +244,10 @@ class WC_Product_Data_Store_CPT extends WC_Data_Store_WP implements WC_Object_Da
$this->update_attributes( $product );
$this->update_version_and_type( $product );
$this->handle_updated_props( $product );
$this->clear_caches( $product );
$product->apply_changes();
$this->clear_caches( $product );
do_action( 'woocommerce_update_product', $product->get_id() );
}
@ -714,7 +712,6 @@ class WC_Product_Data_Store_CPT extends WC_Data_Store_WP implements WC_Object_Da
}
if ( ! is_wp_error( wp_set_post_terms( $product->get_id(), $terms, 'product_visibility', false ) ) ) {
delete_transient( 'wc_featured_products' );
do_action( 'woocommerce_product_set_visibility', $product->get_id(), $product->get_catalog_visibility() );
}
}
@ -738,8 +735,6 @@ class WC_Product_Data_Store_CPT extends WC_Data_Store_WP implements WC_Object_Da
foreach ( $attributes as $attribute_key => $attribute ) {
$value = '';
delete_transient( 'wc_layered_nav_counts_' . $attribute_key );
if ( is_null( $attribute ) ) {
if ( taxonomy_exists( $attribute_key ) ) {
// Handle attributes that have been unset.
@ -830,6 +825,11 @@ class WC_Product_Data_Store_CPT extends WC_Data_Store_WP implements WC_Object_Da
*/
protected function clear_caches( &$product ) {
wc_delete_product_transients( $product->get_id() );
if ( $product->get_parent_id( 'edit' ) ) {
wc_delete_product_transients( $product->get_parent_id( 'edit' ) );
WC_Cache_Helper::incr_cache_prefix( 'product_' . $product->get_parent_id( 'edit' ) );
}
WC_Cache_Helper::invalidate_attribute_count( array_keys( $product->get_attributes() ) );
WC_Cache_Helper::incr_cache_prefix( 'product_' . $product->get_id() );
}

View File

@ -169,7 +169,7 @@ class WC_Product_Variable_Data_Store_CPT extends WC_Product_Data_Store_CPT imple
$variation_attributes = array();
$attributes = $product->get_attributes();
$child_ids = $product->get_children();
$cache_key = WC_Cache_Helper::get_cache_prefix( 'products' ) . 'product_variation_attributes_' . $product->get_id();
$cache_key = WC_Cache_Helper::get_cache_prefix( 'product_' . $product->get_id() ) . 'product_variation_attributes_' . $product->get_id();
$cache_group = 'products';
$cached_data = wp_cache_get( $cache_key, $cache_group );
@ -253,9 +253,16 @@ class WC_Product_Variable_Data_Store_CPT extends WC_Product_Data_Store_CPT imple
*
* @since 2.5.0 a single transient is used per product for all prices, rather than many transients per product.
*/
$transient_name = 'wc_var_prices_' . $product->get_id();
$transient_name = 'wc_var_prices_' . $product->get_id();
$transient_version = WC_Cache_Helper::get_transient_version( 'product' );
$price_hash = $this->get_price_hash( $product, $for_display );
$price_hash = $this->get_price_hash( $product, $for_display );
// Check if prices array is stale.
if ( ! isset( $this->prices_array['version'] ) || $this->prices_array['version'] !== $transient_version ) {
$this->prices_array = array(
'version' => $transient_version,
);
}
/**
* $this->prices_array is an array of values which may have been modified from what is stored in transients - this may not match $transient_cached_prices_array.
@ -265,8 +272,10 @@ class WC_Product_Variable_Data_Store_CPT extends WC_Product_Data_Store_CPT imple
$transient_cached_prices_array = array_filter( (array) json_decode( strval( get_transient( $transient_name ) ), true ) );
// If the product version has changed since the transient was last saved, reset the transient cache.
if ( empty( $transient_cached_prices_array['version'] ) || WC_Cache_Helper::get_transient_version( 'product' ) !== $transient_cached_prices_array['version'] ) {
$transient_cached_prices_array = array( 'version' => WC_Cache_Helper::get_transient_version( 'product' ) );
if ( ! isset( $transient_cached_prices_array['version'] ) || $transient_version !== $transient_cached_prices_array['version'] ) {
$transient_cached_prices_array = array(
'version' => $transient_version,
);
}
// If the prices are not stored for this hash, generate them and add to the transient.
@ -401,14 +410,7 @@ class WC_Product_Variable_Data_Store_CPT extends WC_Product_Data_Store_CPT imple
}
}
$price_hash[] = WC_Cache_Helper::get_transient_version( 'product' );
$price_hash = md5(
wp_json_encode(
apply_filters( 'woocommerce_get_variation_prices_hash', $price_hash, $product, $for_display )
)
);
return $price_hash;
return md5( wp_json_encode( apply_filters( 'woocommerce_get_variation_prices_hash', $price_hash, $product, $for_display ) ) );
}
/**

View File

@ -130,7 +130,9 @@ class WC_Shortcode_Products {
'page' => 1, // Page for pagination.
'paginate' => false, // Should results be paginated.
'cache' => true, // Should shortcode output be cached.
), $attributes, $this->type
),
$attributes,
$this->type
);
if ( ! absint( $attributes['columns'] ) ) {
@ -534,7 +536,7 @@ class WC_Shortcode_Products {
* @return string
*/
protected function get_transient_name() {
$transient_name = 'wc_product_loop' . substr( md5( wp_json_encode( $this->query_args ) . $this->type ), 28 );
$transient_name = 'wc_product_loop_' . md5( wp_json_encode( $this->query_args ) . $this->type );
if ( 'rand' === $this->query_args['orderby'] ) {
// When using rand, we'll cache a number of random queries and pull those to avoid querying rand on each page load.
@ -542,8 +544,6 @@ class WC_Shortcode_Products {
$transient_name .= $rand_index;
}
$transient_name .= WC_Cache_Helper::get_transient_version( 'product_query' );
return $transient_name;
}
@ -554,11 +554,14 @@ class WC_Shortcode_Products {
* @return object Object with the following props; ids, per_page, found_posts, max_num_pages, current_page
*/
protected function get_query_results() {
$transient_name = $this->get_transient_name();
$cache = wc_string_to_bool( $this->attributes['cache'] ) === true;
$results = $cache ? get_transient( $transient_name ) : false;
$transient_name = $this->get_transient_name();
$transient_version = WC_Cache_Helper::get_transient_version( 'product_query' );
$cache = wc_string_to_bool( $this->attributes['cache'] ) === true;
$transient_value = $cache ? get_transient( $transient_name ) : false;
if ( false === $results ) {
if ( isset( $transient_value['value'], $transient_value['version'] ) && $transient_value['version'] === $transient_version ) {
$results = $transient_value['value'];
} else {
if ( 'top_rated_products' === $this->type ) {
add_filter( 'posts_clauses', array( __CLASS__, 'order_by_rating_post_clauses' ) );
$query = new WP_Query( $this->query_args );
@ -578,11 +581,17 @@ class WC_Shortcode_Products {
);
if ( $cache ) {
set_transient( $transient_name, $results, DAY_IN_SECONDS * 30 );
$transient_value = array(
'version' => $transient_version,
'value' => $results,
);
set_transient( $transient_name, $transient_value, DAY_IN_SECONDS * 30 );
}
}
// Remove ordering query arguments which may have been added by get_catalog_ordering_args.
WC()->query->remove_ordering_args();
return $results;
}

View File

@ -1459,27 +1459,35 @@ function wc_postcode_location_matcher( $postcode, $objects, $object_id_key, $obj
function wc_get_shipping_method_count( $include_legacy = false ) {
global $wpdb;
$transient_name = 'wc_shipping_method_count_' . ( $include_legacy ? 1 : 0 ) . '_' . WC_Cache_Helper::get_transient_version( 'shipping' );
$method_count = get_transient( $transient_name );
$transient_name = $include_legacy ? 'wc_shipping_method_count_legacy' : 'wc_shipping_method_count';
$transient_version = WC_Cache_Helper::get_transient_version( 'shipping' );
$transient_value = get_transient( $transient_name );
if ( false === $method_count ) {
$method_count = absint( $wpdb->get_var( "SELECT COUNT(*) FROM {$wpdb->prefix}woocommerce_shipping_zone_methods" ) );
if ( $include_legacy ) {
// Count activated methods that don't support shipping zones.
$methods = WC()->shipping()->get_shipping_methods();
foreach ( $methods as $method ) {
if ( isset( $method->enabled ) && 'yes' === $method->enabled && ! $method->supports( 'shipping-zones' ) ) {
$method_count++;
}
}
}
set_transient( $transient_name, $method_count, DAY_IN_SECONDS * 30 );
if ( isset( $transient_value['value'], $transient_value['version'] ) && $transient_value['version'] === $transient_version ) {
return absint( $transient_value['value'] );
}
return absint( $method_count );
$method_count = absint( $wpdb->get_var( "SELECT COUNT(*) FROM {$wpdb->prefix}woocommerce_shipping_zone_methods" ) );
if ( $include_legacy ) {
// Count activated methods that don't support shipping zones.
$methods = WC()->shipping()->get_shipping_methods();
foreach ( $methods as $method ) {
if ( isset( $method->enabled ) && 'yes' === $method->enabled && ! $method->supports( 'shipping-zones' ) ) {
$method_count++;
}
}
}
$transient_value = array(
'version' => $transient_version,
'value' => $method_count,
);
set_transient( $transient_name, $transient_value, DAY_IN_SECONDS * 30 );
return $method_count;
}
/**

View File

@ -92,64 +92,38 @@ function wc_product_dimensions_enabled() {
}
/**
* Clear all transients cache for product data.
* Clear transient cache for product data.
*
* @param int $post_id (default: 0).
* @param int $post_id (default: 0) The product ID.
*/
function wc_delete_product_transients( $post_id = 0 ) {
// Core transients.
// Transient data to clear with a fixed name which may be stale after product updates.
$transients_to_clear = array(
'wc_products_onsale',
'wc_featured_products',
'wc_outofstock_count',
'wc_low_stock_count',
'wc_count_comments',
);
// Transient names that include an ID.
$post_transient_names = array(
'wc_product_children_',
'wc_var_prices_',
'wc_related_',
'wc_child_has_weight_',
'wc_child_has_dimensions_',
);
if ( $post_id > 0 ) {
foreach ( $post_transient_names as $transient ) {
$transients_to_clear[] = $transient . $post_id;
}
// Does this product have a parent?
$product = wc_get_product( $post_id );
if ( $product ) {
if ( $product->get_parent_id() > 0 ) {
wc_delete_product_transients( $product->get_parent_id() );
}
if ( 'variable' === $product->get_type() ) {
wp_cache_delete(
WC_Cache_Helper::get_cache_prefix( 'products' ) . 'product_variation_attributes_' . $product->get_id(),
'products'
);
}
$attributes = $product->get_attributes();
if ( $attributes ) {
foreach ( $attributes as $attribute_key => $attribute ) {
$transients_to_clear[] = 'wc_layered_nav_counts_' . $attribute_key;
}
}
}
}
// Delete transients.
foreach ( $transients_to_clear as $transient ) {
delete_transient( $transient );
}
if ( $post_id > 0 ) {
// Transient names that include an ID - since they are dynamic they cannot be cleaned in bulk without the ID.
$post_transient_names = array(
'wc_product_children_',
'wc_var_prices_',
'wc_related_',
'wc_child_has_weight_',
'wc_child_has_dimensions_',
);
foreach ( $post_transient_names as $transient ) {
delete_transient( $transient . $post_id );
}
}
// Increments the transient version to invalidate cache.
WC_Cache_Helper::get_transient_version( 'product', true );

View File

@ -95,7 +95,8 @@ if ( ! function_exists( 'wc_create_new_customer' ) ) {
}
$new_customer_data = apply_filters(
'woocommerce_new_customer_data', array(
'woocommerce_new_customer_data',
array(
'user_login' => $username,
'user_pass' => $password,
'user_email' => $email,
@ -215,10 +216,13 @@ function wc_customer_bought_product( $customer_email, $user_id, $product_id ) {
return $result;
}
$transient_name = 'wc_cbp_' . md5( $customer_email . $user_id . WC_Cache_Helper::get_transient_version( 'orders' ) );
$result = get_transient( $transient_name );
$transient_name = 'wc_customer_bought_product_' . md5( $customer_email . $user_id );
$transient_version = WC_Cache_Helper::get_transient_version( 'orders' );
$transient_value = get_transient( $transient_name );
if ( false === $result ) {
if ( isset( $transient_value['value'], $transient_value['version'] ) && $transient_value['version'] === $transient_version ) {
$result = $transient_value['value'];
} else {
$customer_data = array( $user_id );
if ( $user_id ) {
@ -255,7 +259,12 @@ function wc_customer_bought_product( $customer_email, $user_id, $product_id ) {
); // WPCS: unprepared SQL ok.
$result = array_map( 'absint', $result );
set_transient( $transient_name, $result, DAY_IN_SECONDS * 30 );
$transient_value = array(
'version' => $transient_version,
'value' => $result,
);
set_transient( $transient_name, $transient_value, DAY_IN_SECONDS * 30 );
}
return in_array( absint( $product_id ), $result, true );
}
@ -579,7 +588,11 @@ function wc_reset_order_customer_id_on_deleted_user( $user_id ) {
global $wpdb;
$wpdb->update(
$wpdb->postmeta, array( 'meta_value' => 0 ), array(
$wpdb->postmeta,
array(
'meta_value' => 0,
),
array(
'meta_key' => '_customer_user',
'meta_value' => $user_id,
)