first commit

This commit is contained in:
User A0264400
2026-04-01 23:20:16 +03:00
commit a766acdc90
23071 changed files with 4933189 additions and 0 deletions

View File

@@ -0,0 +1,53 @@
<?php
namespace Automattic\WooCommerce\Caches;
use Automattic\WooCommerce\Caching\ObjectCache;
use Automattic\WooCommerce\Internal\DataStores\Orders\CustomOrdersTableController;
/**
* A class to cache order objects.
*/
class OrderCache extends ObjectCache {
/**
* Get the cache key and prefix to use for Orders.
*
* @return string
*/
public function get_object_type(): string {
if ( 'yes' === get_option( CustomOrdersTableController::HPOS_DATASTORE_CACHING_ENABLED_OPTION ) ) {
/**
* The use of datastore caching moves persistent data caching to the datastore. Order object caching then only
* acts as request level caching as the `order_objects` cache group is set as non-persistent.
*/
return 'order_objects';
} else {
return 'orders';
}
}
/**
* Get the id of an object to be cached.
*
* @param array|object $object The object to be cached.
* @return int|string|null The id of the object, or null if it can't be determined.
*/
protected function get_object_id( $object ) {
return $object->get_id();
}
/**
* Validate an object before caching it.
*
* @param array|object $object The object to validate.
* @return string[]|null An array of error messages, or null if the object is valid.
*/
protected function validate( $object ): ?array {
if ( ! $object instanceof \WC_Abstract_Order ) {
return array( 'The supplied order is not an instance of WC_Abstract_Order, ' . gettype( $object ) );
}
return null;
}
}

View File

@@ -0,0 +1,84 @@
<?php
namespace Automattic\WooCommerce\Caches;
use Automattic\WooCommerce\Internal\Features\FeaturesController;
use Automattic\WooCommerce\Utilities\OrderUtil;
/**
* A class to control the usage of the orders cache.
*/
class OrderCacheController {
/**
* The orders cache to use.
*
* @var OrderCache
*/
private $order_cache;
/**
* The orders cache to use.
*
* @var FeaturesController
*/
private $features_controller;
/**
* The backup value of the cache usage enable status, stored while the cache is temporarily disabled.
*
* @var null|bool
*/
private $orders_cache_usage_backup = null;
/**
* Class initialization, invoked by the DI container.
*
* @internal
* @param OrderCache $order_cache The order cache engine to use.
*/
final public function init( OrderCache $order_cache ) {
$this->order_cache = $order_cache;
}
/**
* Whether order cache usage is enabled. Currently, linked to custom orders' table usage.
*
* @return bool True if the order cache is enabled.
*/
public function orders_cache_usage_is_enabled(): bool {
return OrderUtil::custom_orders_table_usage_is_enabled();
}
/**
* Temporarily disable the order cache if it's enabled.
*
* This is a purely in-memory operation: a variable is created with the value
* of the current enable status for the feature, and this variable
* is checked by orders_cache_usage_is_enabled. In the next request the
* feature will be again enabled or not depending on how the feature is set.
*/
public function temporarily_disable_orders_cache_usage(): void {
if ( $this->orders_cache_usage_is_temporarly_disabled() ) {
return;
}
$this->orders_cache_usage_backup = $this->orders_cache_usage_is_enabled();
}
/**
* Check if the order cache has been temporarily disabled.
*
* @return bool True if the order cache is currently temporarily disabled.
*/
public function orders_cache_usage_is_temporarly_disabled(): bool {
return null !== $this->orders_cache_usage_backup;
}
/**
* Restore the order cache usage that had been temporarily disabled.
*/
public function maybe_restore_orders_cache_usage(): void {
$this->orders_cache_usage_backup = null;
}
}

View File

@@ -0,0 +1,249 @@
<?php
declare( strict_types=1 );
namespace Automattic\WooCommerce\Caches;
use Automattic\WooCommerce\Caching\ObjectCache;
use Automattic\WooCommerce\Enums\OrderStatus;
use Automattic\WooCommerce\Internal\DataStores\Orders\OrdersTableDataStore;
use Automattic\WooCommerce\Utilities\OrderUtil;
/**
* A class to cache counts for various order statuses.
*/
class OrderCountCache {
/**
* Cache prefix.
*
* @var string
*/
private $cache_prefix = 'order-count';
/**
* Default value for the duration of the objects in the cache, in seconds
* (may not be used depending on the cache engine used WordPress cache implementation).
*
* @var int
*/
protected $expiration = DAY_IN_SECONDS;
/**
* Retrieves the list of known statuses by order type. A cached array of statuses is saved per order type for
* improved backward compatibility with some of the extensions that don't register all statuses they use with
* WooCommerce.
*
* @param string $order_type The type of order.
*
* @return string[]
*/
private function get_saved_statuses_for_type( string $order_type ) {
$statuses = wp_cache_get( $this->get_saved_statuses_cache_key( $order_type ) );
if ( ! is_array( $statuses ) ) {
$statuses = array();
}
return $statuses;
}
/**
* Adds the given statuses to the cached statuses array for the order type if they are not already stored.
*
* @param string $order_type The order type to save with.
* @param string[] $order_statuses One or more normalised statuses to add.
*
* @return void
*/
private function ensure_statuses_for_type( string $order_type, array $order_statuses ) {
if ( empty( $order_statuses ) ) {
return;
}
$existing = $this->get_saved_statuses_for_type( $order_type );
$new_statuses = array_diff( $order_statuses, $existing );
if ( empty( $new_statuses ) ) {
return;
}
$merged = array_unique( array_merge( $existing, $new_statuses ) );
wp_cache_set( $this->get_saved_statuses_cache_key( $order_type ), $merged, '', $this->expiration );
}
/**
* Get the default statuses.
*
* @return string[]
*
* @deprecated 10.1.0 This method will be removed in the future.
*/
public function get_default_statuses() {
return array_merge(
array_keys( wc_get_order_statuses() ),
array( OrderStatus::TRASH )
);
}
/**
* Get the cache key for a given order type and status.
*
* @param string $order_type The type of order.
* @param string $order_status The status of the order.
* @return string The cache key.
*/
private function get_cache_key( $order_type, $order_status ) {
return $this->cache_prefix . '_' . $order_type . '_' . $order_status;
}
/**
* Get the cache key saved statuses of the given order type.
*
* @param string $order_type The type of order.
*
* @return string The cache key.
*/
private function get_saved_statuses_cache_key( string $order_type ) {
return $this->cache_prefix . '_' . $order_type . '_statuses';
}
/**
* Check if the cache has a value for a given order type and status.
*
* @param string $order_type The type of order.
* @param string $order_status The status of the order.
* @return bool True if the cache has a value, false otherwise.
*/
public function is_cached( $order_type, $order_status ) {
$cache_key = $this->get_cache_key( $order_type, $order_status );
return wp_cache_get( $cache_key ) !== false;
}
/**
* Set the cache value for a given order type and status.
*
* @param string $order_type The type of order.
* @param string $order_status The status slug of the order.
* @param int $value The value to set.
* @return bool True if the value was set, false otherwise.
*/
public function set( $order_type, $order_status, int $value ): bool {
$this->ensure_statuses_for_type( (string) $order_type, array( (string) $order_status ) );
$cache_key = $this->get_cache_key( $order_type, $order_status );
return wp_cache_set( $cache_key, $value, '', $this->expiration );
}
/**
* Set the cache count value for multiple statuses at once.
*
* @param string $order_type The order type being set.
* @param array $counts Normalized counts keyed by status slug
* (e.g. [ 'wc-processing' => 10, 'wc-pending' => 5 ]).
*
* @return array|bool[] Success map from wp_cache_set_multiple().
*/
public function set_multiple( string $order_type, array $counts ) {
if ( empty( $counts ) ) {
return array();
}
$this->ensure_statuses_for_type( $order_type, array_keys( $counts ) );
$mapped_counts = array();
foreach ( $counts as $status => $count ) {
$mapped_counts[ $this->get_cache_key( $order_type, $status ) ] = (int) $count;
}
return wp_cache_set_multiple( $mapped_counts, '', $this->expiration );
}
/**
* Get the cache value for a given order type and set of statuses.
*
* @param string $order_type The type of order.
* @param string[] $order_statuses The statuses of the order.
* @return int[] The cache value.
*/
public function get( $order_type, $order_statuses = array() ) {
$order_type = (string) $order_type;
if ( empty( $order_statuses ) ) {
$order_statuses = $this->get_saved_statuses_for_type( $order_type );
if ( empty( $order_statuses ) ) {
return null;
}
}
$cache_keys = array_map( function( $order_statuses ) use ( $order_type ) {
return $this->get_cache_key( $order_type, $order_statuses );
}, $order_statuses );
$cache_values = wp_cache_get_multiple( $cache_keys );
$status_values = array();
foreach ( $cache_values as $key => $value ) {
// Return null for the entire cache if any of the requested statuses are not found because they fell out of cache.
if ( $value === false ) {
return null;
}
$order_status = str_replace( $this->get_cache_key( $order_type, '' ), '', $key );
$status_values[ $order_status ] = $value;
}
return $status_values;
}
/**
* Increment the cache value for a given order status.
*
* @param string $order_type The type of order.
* @param string $order_status The status of the order.
* @param int $offset The amount to increment by.
* @return int The new value of the cache.
*/
public function increment( $order_type, $order_status, $offset = 1 ) {
$cache_key = $this->get_cache_key( $order_type, $order_status );
return wp_cache_incr( $cache_key, $offset );
}
/**
* Decrement the cache value for a given order status.
*
* @param string $order_type The type of order.
* @param string $order_status The status of the order.
* @param int $offset The amount to decrement by.
* @return int The new value of the cache.
*/
public function decrement( $order_type, $order_status, $offset = 1 ) {
$cache_key = $this->get_cache_key( $order_type, $order_status );
return wp_cache_decr( $cache_key, $offset );
}
/**
* Flush the cache for a given order type and statuses.
*
* @param string $order_type The type of order.
* @param string[] $order_statuses The statuses of the order.
* @return void
*/
public function flush( $order_type = 'shop_order', $order_statuses = array() ) {
$order_type = (string) $order_type;
$flush_saved_statuses = false;
if ( empty( $order_statuses ) ) {
$order_statuses = $this->get_saved_statuses_for_type( $order_type );
$flush_saved_statuses = true;
}
$cache_keys = array_map( function( $order_statuses ) use ( $order_type ) {
return $this->get_cache_key( $order_type, $order_statuses );
}, $order_statuses );
if ( $flush_saved_statuses ) {
// If all statuses are being flushed, go ahead and flush the status list so any permanently removed statuses are cleared out.
$cache_keys[] = $this->get_saved_statuses_cache_key( $order_type );
}
wp_cache_delete_multiple( $cache_keys );
}
}

View File

@@ -0,0 +1,202 @@
<?php
declare( strict_types=1 );
namespace Automattic\WooCommerce\Caches;
use WC_Order;
use Automattic\WooCommerce\Enums\OrderStatus;
use Automattic\WooCommerce\Utilities\OrderUtil;
/**
* A service class to help with updates to the aggregate orders cache.
*
* @internal
*/
class OrderCountCacheService {
const BACKGROUND_EVENT_HOOK = 'woocommerce_refresh_order_count_cache';
/**
* OrderCountCache instance.
*
* @var OrderCountCache
*/
private $order_count_cache;
/**
* Array of order ids with their last transitioned status as key value pairs.
*
* @var array
*/
private $order_statuses = array();
/**
* Array of order ids with their initial status as key value pairs.
*
* @var array
*/
private $initial_order_statuses = array();
/**
* Class initialization, invoked by the DI container.
*
* @internal
*/
final public function init() {
$this->order_count_cache = new OrderCountCache();
add_action( 'woocommerce_new_order', array( $this, 'update_on_new_order' ), 10, 2 );
add_action( 'woocommerce_order_status_changed', array( $this, 'update_on_order_status_changed' ), 10, 4 );
add_action( 'woocommerce_before_trash_order', array( $this, 'update_on_order_trashed' ), 10, 2 );
add_action( 'woocommerce_before_delete_order', array( $this, 'update_on_order_deleted' ), 10, 2 );
add_action( self::BACKGROUND_EVENT_HOOK, array( $this, 'refresh_cache' ) );
add_action( 'action_scheduler_ensure_recurring_actions', array( $this, 'schedule_background_actions' ) );
if ( defined( 'WC_PLUGIN_BASENAME' ) ) {
add_action( 'deactivate_' . WC_PLUGIN_BASENAME, array( $this, 'unschedule_background_actions' ) );
}
}
/**
* Refresh the cache for a given order type.
*
* @param string $order_type The order type.
* @return void
*/
public function refresh_cache( $order_type ) {
$this->order_count_cache->flush( $order_type );
OrderUtil::get_count_for_type( $order_type );
}
/**
* Register background caching for each order type.
*
* @return void
*/
public function schedule_background_actions() {
$order_types = wc_get_order_types( 'order-count' );
$frequency = HOUR_IN_SECONDS * 12;
foreach ( $order_types as $order_type ) {
as_schedule_recurring_action( time() + $frequency, $frequency, self::BACKGROUND_EVENT_HOOK, array( $order_type ), 'count', true );
}
}
/**
* Unschedules background actions.
*
* @since 10.0.0
* @internal
*/
public function unschedule_background_actions() {
WC()->queue()->cancel_all( self::BACKGROUND_EVENT_HOOK );
}
/**
* Update the cache when a new order is made.
*
* @param int $order_id Order id.
* @param WC_Order $order The order.
*/
public function update_on_new_order( $order_id, $order ) {
if ( ! $this->order_count_cache->is_cached( $order->get_type(), $this->get_prefixed_status( $order->get_status() ) ) ) {
return;
}
// If the order status was updated, we need to increment the order count cache for the
// initial status that was errantly decremented on order status change.
if ( isset( $this->initial_order_statuses[ $order_id ] ) ) {
$this->order_count_cache->increment( $order->get_type(), $this->get_prefixed_status( $this->initial_order_statuses[ $order_id ] ) );
}
// If the order status count has already been incremented, we can skip incrementing it again.
if ( isset( $this->order_statuses[ $order->get_id() ] ) && $this->order_statuses[ $order->get_id() ] === $order->get_status() ) {
return;
}
$this->order_statuses[ $order_id ] = $order->get_status();
$this->order_count_cache->increment( $order->get_type(), $this->get_prefixed_status( $order->get_status() ) );
}
/**
* Update the cache when an order is trashed.
*
* @param int $order_id Order id.
* @param WC_Order $order The order.
*/
public function update_on_order_trashed( $order_id, $order ) {
if (
! $this->order_count_cache->is_cached( $order->get_type(), $this->get_prefixed_status( $order->get_status() ) ) ||
! $this->order_count_cache->is_cached( $order->get_type(), OrderStatus::TRASH ) ) {
return;
}
$this->order_count_cache->decrement( $order->get_type(), $this->get_prefixed_status( $order->get_status() ) );
$this->order_count_cache->increment( $order->get_type(), OrderStatus::TRASH );
}
/**
* Update the cache when an order is deleted.
*
* @param int $order_id Order id.
* @param WC_Order $order The order.
*/
public function update_on_order_deleted( $order_id, $order ) {
if ( ! $this->order_count_cache->is_cached( $order->get_type(), $this->get_prefixed_status( $order->get_status() ) ) ) {
return;
}
$this->order_count_cache->decrement( $order->get_type(), $this->get_prefixed_status( $order->get_status() ) );
}
/**
* Update the cache whenver an order status changes.
*
* @param int $order_id Order id.
* @param string $previous_status the old WooCommerce order status.
* @param string $next_status the new WooCommerce order status.
* @param WC_Order $order The order.
*/
public function update_on_order_status_changed( $order_id, $previous_status, $next_status, $order ) {
if (
! $this->order_count_cache->is_cached( $order->get_type(), $this->get_prefixed_status( $next_status ) ) ||
! $this->order_count_cache->is_cached( $order->get_type(), $this->get_prefixed_status( $previous_status ) )
) {
return;
}
// If the order status count has already been incremented, we can skip incrementing it again.
if ( isset( $this->order_statuses[ $order_id ] ) && $this->order_statuses[ $order_id ] === $next_status ) {
return;
}
$this->order_statuses[ $order_id ] = $next_status;
$was_decremented = $this->order_count_cache->decrement( $order->get_type(), $this->get_prefixed_status( $previous_status ) );
$this->order_count_cache->increment( $order->get_type(), $this->get_prefixed_status( $next_status ) );
// Set the initial order status in case this is a new order and the previous status should not be decremented.
if ( ! isset( $this->initial_order_statuses[ $order_id ] ) && $was_decremented ) {
$this->initial_order_statuses[ $order_id ] = $previous_status;
}
}
/**
* Get the prefixed status.
*
* @param string $status The status.
* @return string
*/
private function get_prefixed_status( $status ) {
$status = 'wc-' . $status;
$special_statuses = array(
'wc-' . OrderStatus::AUTO_DRAFT => OrderStatus::AUTO_DRAFT,
'wc-' . OrderStatus::TRASH => OrderStatus::TRASH,
);
if ( isset( $special_statuses[ $status ] ) ) {
return $special_statuses[ $status ];
}
return $status;
}
}