plugin updates

This commit is contained in:
Tony Volpe
2024-09-05 11:04:01 -04:00
parent ed6b060261
commit 50cd64dd3d
925 changed files with 16918 additions and 13003 deletions

View File

@@ -209,6 +209,7 @@ class WC_Site_Tracking {
include_once WC_ABSPATH . 'includes/tracks/events/class-wc-order-tracking.php';
include_once WC_ABSPATH . 'includes/tracks/events/class-wc-coupon-tracking.php';
include_once WC_ABSPATH . 'includes/tracks/events/class-wc-theme-tracking.php';
include_once WC_ABSPATH . 'includes/tracks/events/class-wc-product-collection-block-tracking.php';
$tracking_classes = array(
'WC_Extensions_Tracking',
@@ -221,6 +222,7 @@ class WC_Site_Tracking {
'WC_Order_Tracking',
'WC_Coupon_Tracking',
'WC_Theme_Tracking',
'WC_Product_Collection_Block_Tracking',
);
foreach ( $tracking_classes as $tracking_class ) {
@@ -233,5 +235,26 @@ class WC_Site_Tracking {
}
}
/**
* Sets a cookie for tracking purposes, but only if tracking is enabled/allowed.
*
* @internal
* @since 9.2.0
*
* @param string $cookie_key The key of the cookie.
* @param string $cookie_value The value of the cookie.
* @param int $expire Expiry of the cookie.
* @param bool $secure Whether the cookie should be served only over https.
* @param bool $http_only Whether the cookie is only accessible over HTTP.
*
* @return bool If setting the cookie was attempted (will be false if tracking is not allowed).
*/
public static function set_tracking_cookie( string $cookie_key, string $cookie_value, int $expire = 0, bool $secure = false, bool $http_only = false ): bool {
if ( self::is_tracking_enabled() ) {
wc_setcookie( $cookie_key, $cookie_value, $expire, $secure, $http_only );
return true;
}
return false;
}
}

View File

@@ -73,7 +73,7 @@ class WC_Tracks_Client {
// Don't set cookie on API requests.
if ( ! Constants::is_true( 'REST_REQUEST' ) && ! Constants::is_true( 'XMLRPC_REQUEST' ) ) {
wc_setcookie( 'tk_ai', $anon_id );
WC_Site_Tracking::set_tracking_cookie( 'tk_ai', $anon_id );
}
}

View File

@@ -34,7 +34,7 @@ class WC_Extensions_Tracking {
'section' => empty( $_REQUEST['section'] ) ? '_featured' : wc_clean( wp_unslash( $_REQUEST['section'] ) ),
);
$event = 'extensions_view';
$event = 'extensions_view';
if ( 'helper' === $properties['section'] ) {
$event = 'subscriptions_view';
}
@@ -52,7 +52,7 @@ class WC_Extensions_Tracking {
* Send a Tracks event when the Extensions page gets a bad response or no response
* from the WCCOM extensions API.
*
* @param string $error
* @param string $error Error message.
*/
public function track_extensions_page_connection_error( string $error = '' ) {
// phpcs:disable WordPress.Security.NonceVerification.Recommended
@@ -89,7 +89,17 @@ class WC_Extensions_Tracking {
* Send a Tracks even when a Helper connection process completed successfully.
*/
public function track_helper_connection_complete() {
WC_Tracks::record_event( 'extensions_subscriptions_connected' );
$properties = array();
if ( ! empty( $_GET['utm_source'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
$properties['utm_source'] = wc_clean( wp_unslash( $_GET['utm_source'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
}
if ( ! empty( $_GET['utm_campaign'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
$properties['utm_campaign'] = wc_clean( wp_unslash( $_GET['utm_campaign'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
}
WC_Tracks::record_event( 'extensions_subscriptions_connected', $properties );
}
/**

View File

@@ -0,0 +1,320 @@
<?php
declare( strict_types = 1);
defined( 'ABSPATH' ) || exit;
use Automattic\WooCommerce\Blocks\Templates\SingleProductTemplate;
use Automattic\WooCommerce\Blocks\Templates\CartTemplate;
use Automattic\WooCommerce\Blocks\Templates\MiniCartTemplate;
use Automattic\WooCommerce\Blocks\Templates\CheckoutTemplate;
use Automattic\WooCommerce\Blocks\Templates\ProductCatalogTemplate;
use Automattic\WooCommerce\Blocks\Templates\ProductAttributeTemplate;
use Automattic\WooCommerce\Blocks\Templates\OrderConfirmationTemplate;
/**
* This class adds actions to track usage of the Product Collection Block.
*/
class WC_Product_Collection_Block_Tracking {
/**
* Init Tracking.
*/
public function init() {
add_action( 'save_post', array( $this, 'track_collection_instances' ), 10, 2 );
}
/**
* Track feature usage of the Product Collection block within the site editor.
*
* @param int $post_id The post ID.
* @param \WP_Post $post The post object.
*
* @return void
*/
public function track_collection_instances( $post_id, $post ) {
if ( ! defined( 'REST_REQUEST' ) || ! REST_REQUEST || ! wc_current_theme_is_fse_theme() ) {
return;
}
if ( ! $post instanceof \WP_Post ) {
return;
}
// Don't track autosaves and drafts.
$post_status = $post->post_status;
if ( 'publish' !== $post_status ) {
return;
}
// Important: Only track instances within specific types.
$post_type = $post->post_type;
if ( ! in_array( $post_type, array( 'post', 'page', 'wp_template', 'wp_template_part', 'wp_block' ), true ) ) {
return;
}
if ( ! has_block( 'woocommerce/product-collection', $post ) && ! has_block( 'core/template-part', $post ) && ! has_block( 'core/block', $post ) ) {
return;
}
$blocks = parse_blocks( $post->post_content );
if ( empty( $blocks ) ) {
return;
}
$instances = $this->parse_blocks_track_data( $blocks );
if ( empty( $instances ) ) {
return;
}
// Count orders.
// Hint: Product count included in Track event. See WC_Tracks::get_blog_details().
$order_count = 0;
foreach ( wc_get_order_statuses() as $status_slug => $status_name ) {
$order_count += wc_orders_count( $status_slug );
}
$additional_data = array(
'editor_context' => $this->parse_editor_location_context( $post ),
'order_count' => $order_count,
);
foreach ( $instances as $instance ) {
$event_properties = array_merge(
$additional_data,
$instance
);
\WC_Tracks::record_event(
'product_collection_instance',
$event_properties
);
}
}
/**
* Track usage of the Product Collection block within the given blocks.
*
* @param array $blocks The parsed blocks to check.
* @param bool $is_in_single_product Whether we are in a single product container (used for keeping state in the recurring process).
* @param bool $is_in_template_part Whether we are in a template part (used for keeping state in the recurring process).
* @param bool $is_in_synced_pattern Whether we are in a synced block (used for keeping state in the recurring process).
*
* @return array Parsed instances of the Product Collection block.
*/
private function parse_blocks_track_data( $blocks, $is_in_single_product = false, $is_in_template_part = false, $is_in_synced_pattern = false ) {
$instances = array();
if ( ! is_array( $blocks ) || empty( $blocks ) ) {
return $instances;
}
foreach ( $blocks as $block ) {
if ( empty( $block['blockName'] ) ) {
continue;
}
if ( 'woocommerce/product-collection' === $block['blockName'] ) {
$instances[] = array(
'collection' => $block['attrs']['collection'] ?? 'product-catalog',
'in_single_product' => $is_in_single_product ? 'yes' : 'no',
'in_template_part' => $is_in_template_part ? 'yes' : 'no',
'in_synced_pattern' => $is_in_synced_pattern ? 'yes' : 'no',
'filters' => wp_json_encode( $this->get_query_filters_usage_data( $block ) ),
);
}
// Track instances within single product container.
$local_is_in_single_product = $is_in_single_product;
if ( 'woocommerce/single-product' === $block['blockName'] ) {
$local_is_in_single_product = true;
}
// Track instances within template part.
// Hint: Supports up to two levels of depth.
if ( ! $is_in_synced_pattern && ! $is_in_template_part && 'core/template-part' === $block['blockName'] ) {
$template_part_theme = $block['attrs']['theme'] ?? '';
$template_part_slug = $block['attrs']['slug'] ?? '';
$template_part = get_block_template( $template_part_theme . '//' . $template_part_slug, 'wp_template_part' );
if ( $template_part instanceof WP_Block_Template && ! empty( $template_part->content ) ) {
// Recursive.
$instances = array_merge( $instances, $this->parse_blocks_track_data( parse_blocks( $template_part->content ), $local_is_in_single_product, true, $is_in_synced_pattern ) );
}
}
// Track instances within synced block.
// Hint: Supports up to two levels of depth.
if ( ! $is_in_synced_pattern && ! $is_in_template_part && 'core/block' === $block['blockName'] ) {
$block_id = $block['attrs']['ref'] ?? 0;
$synced_pattern = get_post( $block_id );
if ( $synced_pattern instanceof WP_Post && ! empty( $synced_pattern->post_content ) ) {
// Recursive.
$instances = array_merge( $instances, $this->parse_blocks_track_data( parse_blocks( $synced_pattern->post_content ), $local_is_in_single_product, $is_in_template_part, true ) );
}
}
// Recursive.
if ( ! empty( $block['innerBlocks'] ) ) {
$instances = array_merge( $instances, $this->parse_blocks_track_data( $block['innerBlocks'], $local_is_in_single_product, $is_in_template_part, $is_in_synced_pattern ) );
}
}
return $instances;
}
/**
* Parse editor's location context from WP Post.
*
* Possible contexts:
* - post
* - page
* - single-product
* - product-archive
* - cart
* - checkout
* - product-catalog
* - order-confirmation
*
* @param WP_Post $post The Post instance.
*
* @return string Returns the context.
*/
private function parse_editor_location_context( $post ) {
$context = 'other';
if ( ! $post instanceof \WP_Post ) {
return $context;
}
$post_type = $post->post_type;
if ( ! in_array( $post_type, array( 'post', 'page', 'wp_template', 'wp_template_part', 'wp_block' ), true ) ) {
return $context;
}
if ( 'wp_template' === $post_type ) {
$name = $post->post_name;
if ( false !== strpos( $name, SingleProductTemplate::SLUG ) ) {
$context = 'single-product';
} elseif ( ProductAttributeTemplate::SLUG === $name ) {
$context = 'product-archive';
} elseif ( false !== strpos( $name, 'taxonomy-' ) ) { // Including the '-' in the check to avoid false positives.
$taxonomy = str_replace( 'taxonomy-', '', $name );
$product_taxonomies = get_object_taxonomies( 'product', 'names' );
if ( in_array( $taxonomy, $product_taxonomies, true ) ) {
$context = 'product-archive';
}
} elseif ( in_array( $name, array( CartTemplate::SLUG, MiniCartTemplate::SLUG ), true ) ) {
$context = 'cart';
} elseif ( CheckoutTemplate::SLUG === $name ) {
$context = 'checkout';
} elseif ( ProductCatalogTemplate::SLUG === $name ) {
$context = 'product-catalog';
} elseif ( OrderConfirmationTemplate::SLUG === $name ) {
$context = 'order-confirmation';
}
}
if ( in_array( $post_type, array( 'wp_block', 'wp_template_part' ), true ) ) {
$context = 'isolated';
}
if ( 'page' === $post_type ) {
$context = 'page';
}
if ( 'post' === $post_type ) {
$context = 'post';
}
return $context;
}
/**
* Parse the collection query filters from the query attributes.
*
* @param array $block The parsed block.
* @return array The filters data for tracking.
*/
private function get_query_filters_usage_data( $block ) {
if ( ! isset( $block['attrs'] ) ) {
return array();
}
$query_attrs = $block['attrs']['query'] ?? array();
$filters = array(
'inherit' => 'no',
'order-by' => 'no',
'on-sale' => 'no',
'stock-status' => 'no',
'handpicked' => 'no',
'keyword' => 'no',
'attributes' => 'no',
'category' => 'no',
'tag' => 'no',
'featured' => 'no',
'created' => 'no',
'price' => 'no',
);
if ( ! empty( $query_attrs['inherit'] ) && true === $query_attrs['inherit'] ) {
$filters['inherit'] = 'yes';
}
if ( ( ! empty( $query_attrs['order'] ) && 'asc' !== $query_attrs['order'] ) || ( ! empty( $query_attrs['orderBy'] ) && 'title' !== $query_attrs['orderBy'] ) ) {
$filters['order-by'] = 'yes';
}
if ( ! empty( $query_attrs['woocommerceOnSale'] ) ) {
$filters['on-sale'] = 'yes';
}
if ( ! empty( $query_attrs['woocommerceStockStatus'] ) ) {
$stock_statuses = wc_get_product_stock_status_options();
$default_values = 'yes' === get_option( 'woocommerce_hide_out_of_stock_items' ) ? array_diff_key( $stock_statuses, array( 'outofstock' => '' ) ) : $stock_statuses;
$default_diff = array_diff( array_keys( $default_values ), $query_attrs['woocommerceStockStatus'] );
if ( ! empty( $default_diff ) ) {
$filters['stock-status'] = 'yes';
}
}
if ( ! empty( $query_attrs['woocommerceAttributes'] ) ) {
$filters['attributes'] = 'yes';
}
if ( ! empty( $query_attrs['timeFrame'] ) ) {
$filters['created'] = 'yes';
}
if ( ! empty( $query_attrs['taxQuery'] ) ) {
if ( ! empty( $query_attrs['taxQuery']['product_cat'] ) ) {
$filters['category'] = 'yes';
}
if ( ! empty( $query_attrs['taxQuery']['product_tag'] ) ) {
$filters['tag'] = 'yes';
}
}
if ( ! empty( $query_attrs['woocommerceHandPickedProducts'] ) ) {
$filters['handpicked'] = 'yes';
}
if ( ! empty( $query_attrs['search'] ) ) {
$filters['keyword'] = 'yes';
}
if ( ! empty( $query_attrs['featured'] ) ) {
$filters['featured'] = 'yes';
}
if ( ! empty( $query_attrs['priceRange'] ) ) {
$filters['price'] = 'yes';
}
return $filters;
}
}

View File

@@ -346,6 +346,7 @@ class WC_Products_Tracking {
'tags' => count( $product->get_tag_ids() ),
'upsells' => ! empty( $product->get_upsell_ids() ) ? 'yes' : 'no',
'weight' => $product->get_weight() ? 'yes' : 'no',
'global_unique_id' => $product->get_global_unique_id() ? 'yes' : 'no',
);
WC_Tracks::record_event( 'product_add_publish', $properties );