Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions admin/admin-tab-integrate.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,25 @@
'phase' => GTM4WP_PHASE_STABLE,
'plugintocheck' => 'woocommerce/woocommerce.php',
),
GTM4WP_OPTION_INTEGRATE_WCBLOCKSADDTOCART => array(
'label' => esc_html__( 'Use WooCommerce Blocks add-to-cart', 'duracelltomi-google-tag-manager' ),
'description' => sprintf(
gtm4wp_safe_admin_html(
// translators: 1: anchor element linking to WooCommerce Blocks DOM events docs. 2: closing anchor element.
__(
'Enable this experimental feature to track WooCommerce Blocks cart interactions (Add to Cart + Options, mini-cart, and cart page).<br/>
This option uses the %1$sDOM events%2$s from WooCommerce Blocks to detect when products are added to or removed from the cart.<br/>
<strong>When enabled, the legacy add-to-cart tracking will be disabled.</strong><br/>
This feature requires "Track e-commerce" to be enabled.',
'duracelltomi-google-tag-manager'
)
),
'<a href="https://developer.woocommerce.com/docs/block-development/extensible-blocks/cart-and-checkout-blocks/dom-events/" target="_blank" rel="noopener">',
'</a>'
),
'phase' => GTM4WP_PHASE_EXPERIMENTAL,
'plugintocheck' => 'woocommerce/woocommerce.php',
),
GTM4WP_OPTION_INTEGRATE_WCPRODPERIMPRESSION => array(
'label' => esc_html__( 'Products per impression', 'duracelltomi-google-tag-manager' ),
'description' => gtm4wp_safe_admin_html(
Expand Down
98 changes: 96 additions & 2 deletions admin/admin.php
Original file line number Diff line number Diff line change
Expand Up @@ -652,8 +652,13 @@ function gtm4wp_sanitize_options( $options ) {
}
} elseif ( GTM4WP_OPTION_GTM_PLACEMENT === $optionname ) {
// GTM container ON/OFF + compat mode.
$container_on_off = (bool) $options['container-on'];
$container_compat = (int) $options['compat-mode'];
$container_on_off = isset( $options['container-on'] )
? (bool) $options['container-on']
: ( GTM4WP_PLACEMENT_OFF !== (int) $optionvalue );

$container_compat = isset( $options['compat-mode'] )
? (int) $options['compat-mode']
: (int) $optionvalue;

if ( ! $container_on_off ) {
$output[ $optionname ] = GTM4WP_PLACEMENT_OFF;
Expand Down Expand Up @@ -1298,6 +1303,19 @@ function gtm4wp_show_warning() {
);
echo '</strong></p></div>';
}

if ( $gtm4wp_options[ GTM4WP_OPTION_INTEGRATE_WCTRACKECOMMERCE ] && $gtm4wp_options[ GTM4WP_OPTION_INTEGRATE_WCBLOCKSADDTOCART ] ) {
$blocks_health = gtm4wp_get_blocks_integration_health_state();

if ( ! $blocks_health['store_api_route'] || ! $blocks_health['gtm4wp_product_api'] ) {
echo '<div class="gtm4wp-notice notice notice-warning" data-href="?wc-blocks-health"><p><strong>';
esc_html_e(
'WooCommerce Blocks add-to-cart tracking is enabled but its required REST endpoints are unavailable. Please ensure WooCommerce Blocks assets and the GTM4WP REST endpoints are accessible.',
'duracelltomi-google-tag-manager'
);
echo '</strong></p></div>';
}
}
}

/**
Expand Down Expand Up @@ -1381,6 +1399,82 @@ function gtm4wp_show_upgrade_notification( $current_plugin_metadata, $new_plugin
add_action( 'admin_menu', 'gtm4wp_add_admin_page' );
add_action( 'admin_enqueue_scripts', 'gtm4wp_add_admin_js' );
add_action( 'admin_notices', 'gtm4wp_show_warning' );

/**
* Returns health status for WooCommerce Blocks add-to-cart integration.
*
* @return array<string,bool>
*/
function gtm4wp_get_blocks_integration_health_state() {
$result = array(
'store_api_route' => false,
'gtm4wp_product_api' => false,
);

// First, try to exercise the endpoints directly so themes/hosts that lazy-load routes do not trigger false warnings.
$result['store_api_route'] = gtm4wp_rest_route_responds( '/wc/store/v1/cart', true );
$result['gtm4wp_product_api'] = gtm4wp_rest_route_responds( '/gtm4wp/v1/product/0', true );

if ( $result['store_api_route'] && $result['gtm4wp_product_api'] ) {
return $result;
}

if ( ! function_exists( 'rest_get_server' ) ) {
return $result;
}

$server = rest_get_server();

if ( ! $server ) {
return $result;
}

$routes = $server->get_routes();

foreach ( $routes as $route => $handler ) {
if ( ! $result['store_api_route'] && 0 === strpos( $route, '/wc/store/v1/cart' ) ) {
$result['store_api_route'] = true;
}

if ( ! $result['gtm4wp_product_api'] && 0 === strpos( $route, '/gtm4wp/v1/product' ) ) {
$result['gtm4wp_product_api'] = true;
}

if ( $result['store_api_route'] && $result['gtm4wp_product_api'] ) {
break;
}
}

return $result;
}

/**
* Checks if a REST route responds without returning rest_no_route.
*
* @param string $route Route path to check.
* @param bool $allow_client_error Treat 4xx responses as success (route exists but request invalid).
* @return bool
*/
function gtm4wp_rest_route_responds( $route, $allow_client_error = false ) {
if ( ! class_exists( 'WP_REST_Request' ) || ! function_exists( 'rest_do_request' ) ) {
return false;
}

$request = new WP_REST_Request( 'GET', $route );
$response = rest_do_request( $request );

if ( is_wp_error( $response ) ) {
return 'rest_no_route' !== $response->get_error_code();
}

$status = (int) $response->get_status();

if ( $allow_client_error ) {
return $status >= 200 && $status < 500;
}

return $status >= 200 && $status < 400;
}
add_action( 'admin_head', 'gtm4wp_admin_head' );
add_filter( 'plugin_action_links', 'gtm4wp_add_plugin_action_links', 10, 2 );
add_action( 'wp_ajax_gtm4wp_dismiss_notice', 'gtm4wp_dismiss_notice' );
Expand Down
2 changes: 2 additions & 0 deletions common/readoptions.php
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@
define( 'GTM4WP_OPTION_INTEGRATE_WCNOORDERTRACKEDFLAG', 'integrate-woocommerce-do-not-use-order-tracked-flag' );
define( 'GTM4WP_OPTION_INTEGRATE_WCCLEARECOMMERCEDL', 'integrate-woocommerce-clear-ecommerce-datalayer' );
define( 'GTM4WP_OPTION_INTEGRATE_WCDLMAXTIMEOUT', 'integrate-woocommerce-datalayer-max-timeout' );
define( 'GTM4WP_OPTION_INTEGRATE_WCBLOCKSADDTOCART', 'integrate-woocommerce-blocks-add-to-cart' );

define( 'GTM4WP_OPTION_INTEGRATE_WPECOMMERCE', 'integrate-wp-e-commerce' );

Expand Down Expand Up @@ -199,6 +200,7 @@
GTM4WP_OPTION_INTEGRATE_WCNOORDERTRACKEDFLAG => false,
GTM4WP_OPTION_INTEGRATE_WCCLEARECOMMERCEDL => false,
GTM4WP_OPTION_INTEGRATE_WCDLMAXTIMEOUT => 2000,
GTM4WP_OPTION_INTEGRATE_WCBLOCKSADDTOCART => false,

GTM4WP_OPTION_INTEGRATE_WPECOMMERCE => false,

Expand Down
2 changes: 1 addition & 1 deletion dist/js/gtm4wp-woocommerce.js

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions duracelltomi-google-tag-manager-for-wordpress.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
* Plugin Name: GTM4WP - A Google Tag Manager (GTM) plugin for WordPress
* Plugin URI: https://gtm4wp.com/
* Description: The first Google Tag Manager plugin for WordPress with business goals in mind
* Version: 1.22.2
* Version: 1.22.3
* Requires at least: 3.4.0
* Requires PHP: 7.4
* Author: Thomas Geiger
Expand All @@ -25,7 +25,7 @@
* WC tested up to: 9.8
*/

define( 'GTM4WP_VERSION', '1.22.2' );
define( 'GTM4WP_VERSION', '1.22.3' );
define( 'GTM4WP_PATH', plugin_dir_path( __FILE__ ) );

global $gtp4wp_plugin_url, $gtp4wp_plugin_basename, $gtp4wp_script_path;
Expand Down
148 changes: 144 additions & 4 deletions integration/woocommerce.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,12 @@ function gtm4wp_woocommerce_add_global_vars( $return ) {
$return['gtm4wp_product_per_impression'] = (int) ( $gtm4wp_options[ GTM4WP_OPTION_INTEGRATE_WCPRODPERIMPRESSION ] );
$return['gtm4wp_clear_ecommerce'] = (bool) ( $gtm4wp_options[ GTM4WP_OPTION_INTEGRATE_WCCLEARECOMMERCEDL ] );
$return['gtm4wp_datalayer_max_timeout'] = (int) ( $gtm4wp_options[ GTM4WP_OPTION_INTEGRATE_WCDLMAXTIMEOUT ] );
$return['gtm4wp_blocks_add_to_cart'] = (bool) ( $gtm4wp_options[ GTM4WP_OPTION_INTEGRATE_WCTRACKECOMMERCE ] && $gtm4wp_options[ GTM4WP_OPTION_INTEGRATE_WCBLOCKSADDTOCART ] );
$return['gtm4wp_rest_root'] = esc_url_raw( get_rest_url() );
$return['gtm4wp_blocks_ajax_url'] = esc_url_raw( admin_url( 'admin-ajax.php' ) );
$return['gtm4wp_blocks_product_nonce'] = wp_create_nonce( 'gtm4wp-product-data' );
$return['gtm4wp_blocks_dedupe_window'] = (int) apply_filters( 'gtm4wp_blocks_dedupe_window', 800 );
$return['gtm4wp_rest_nonce'] = wp_create_nonce( 'wp_rest' );

return $return;
}
Expand Down Expand Up @@ -92,7 +98,20 @@ function gtm4wp_woocommerce_process_product( $product, $additional_product_attri
);

if ( 'variation' === $product_type ) {
$_temp_productdata['item_group_id'] = $parent_product_id;
$parent_product = wc_get_product( $parent_product_id );
$parent_item_id = '';

if ( $parent_product instanceof WC_Product && $parent_product->get_sku() ) {
$parent_item_id = $parent_product->get_sku();
}

$_temp_productdata['item_group_sku'] = $parent_item_id;

if ( $gtm4wp_options[ GTM4WP_OPTION_INTEGRATE_WCUSESKU ] && '' !== $parent_item_id ) {
$_temp_productdata['item_group_id'] = $parent_item_id;
} else {
$_temp_productdata['item_group_id'] = $parent_product_id;
}
}

if ( 1 === count( $product_cat_parts ) ) {
Expand Down Expand Up @@ -962,7 +981,8 @@ function gtm4wp_woocommerce_single_add_to_cart_tracking() {
global $product, $gtm4wp_options;

// exit early if there is nothing to do.
if ( false === $gtm4wp_options[ GTM4WP_OPTION_INTEGRATE_WCTRACKECOMMERCE ] ) {
if ( false === $gtm4wp_options[ GTM4WP_OPTION_INTEGRATE_WCTRACKECOMMERCE ]
|| ! empty( $gtm4wp_options[ GTM4WP_OPTION_INTEGRATE_WCBLOCKSADDTOCART ] ) ) {
return;
}

Expand Down Expand Up @@ -1376,8 +1396,27 @@ function gtm4wp_woocommerce_enqueue_scripts() {

if ( $gtm4wp_options[ GTM4WP_OPTION_INTEGRATE_WCTRACKECOMMERCE ] ) {
$in_footer = (bool) apply_filters( 'gtm4wp_' . GTM4WP_OPTION_INTEGRATE_WCTRACKECOMMERCE, true );
wp_enqueue_script( 'gtm4wp-ecommerce-generic', $gtp4wp_script_path . 'gtm4wp-ecommerce-generic.js', array(), GTM4WP_VERSION, $in_footer );
wp_enqueue_script( 'gtm4wp-woocommerce', $gtp4wp_script_path . 'gtm4wp-woocommerce.js', array( 'jquery' ), GTM4WP_VERSION, $in_footer );

$default_version = GTM4WP_VERSION;
$generic_script_ver = $default_version;
$woocommerce_script_ver = $default_version;

if ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) {
$script_dir = trailingslashit( dirname( __DIR__ ) ) . 'js/';

$generic_script_path = $script_dir . 'gtm4wp-ecommerce-generic.js';
if ( file_exists( $generic_script_path ) ) {
$generic_script_ver = filemtime( $generic_script_path );
}

$woocommerce_script_path = $script_dir . 'gtm4wp-woocommerce.js';
if ( file_exists( $woocommerce_script_path ) ) {
$woocommerce_script_ver = filemtime( $woocommerce_script_path );
}
}

wp_enqueue_script( 'gtm4wp-ecommerce-generic', $gtp4wp_script_path . 'gtm4wp-ecommerce-generic.js', array(), $generic_script_ver, $in_footer );
wp_enqueue_script( 'gtm4wp-woocommerce', $gtp4wp_script_path . 'gtm4wp-woocommerce.js', array( 'jquery' ), $woocommerce_script_ver, $in_footer );
}
}

Expand Down Expand Up @@ -1507,6 +1546,107 @@ function gtm4wp_woocommerce_add_productdata_to_wc_block( $content, $data, $produ
add_action( 'wp_enqueue_scripts', 'gtm4wp_woocommerce_enqueue_scripts' );
add_filter( GTM4WP_WPFILTER_ADDGLOBALVARS_ARRAY, 'gtm4wp_woocommerce_add_global_vars' );

add_action( 'rest_api_init', 'gtm4wp_register_wc_product_rest_route' );
add_action( 'wp_ajax_gtm4wp_product_data', 'gtm4wp_ajax_get_product_data' );
add_action( 'wp_ajax_nopriv_gtm4wp_product_data', 'gtm4wp_ajax_get_product_data' );

/**
* Registers REST API endpoint to fetch processed product data.
*
* @return void
*/
function gtm4wp_register_wc_product_rest_route() {
register_rest_route(
'gtm4wp/v1',
'/product/(?P<id>\d+)',
array(
'methods' => WP_REST_Server::READABLE,
'callback' => 'gtm4wp_rest_get_product_data',
'permission_callback' => '__return_true',
)
);
}

/**
* REST API callback to retrieve processed product data.
*
* @param WP_REST_Request $request The REST request.
* @return WP_REST_Response|WP_Error
*/
function gtm4wp_rest_get_product_data( WP_REST_Request $request ) {
$product_id = absint( $request['id'] );

if ( $product_id <= 0 ) {
return new WP_Error( 'gtm4wp_invalid_product', __( 'Invalid product ID.', 'duracelltomi-google-tag-manager' ), array( 'status' => 400 ) );
}

$product = wc_get_product( $product_id );

if ( ! $product ) {
return new WP_Error( 'gtm4wp_product_not_found', __( 'Product not found.', 'duracelltomi-google-tag-manager' ), array( 'status' => 404 ) );
}

$product_data = gtm4wp_woocommerce_process_product( $product, array(), 'productdetail' );

if ( ! $product_data ) {
return new WP_Error( 'gtm4wp_product_unavailable', __( 'Unable to build product data.', 'duracelltomi-google-tag-manager' ), array( 'status' => 404 ) );
}

return rest_ensure_response(
array(
'product' => $product_data,
)
);
}

/**
* AJAX fallback handler for product data when REST is not accessible.
*
* @return void
*/
function gtm4wp_ajax_get_product_data() {
check_ajax_referer( 'gtm4wp-product-data', 'nonce' );

$product_id = isset( $_REQUEST['product_id'] ) ? absint( $_REQUEST['product_id'] ) : 0;

if ( $product_id <= 0 ) {
wp_send_json_error(
array(
'message' => __( 'Invalid product ID.', 'duracelltomi-google-tag-manager' ),
),
400
);
}

$product = wc_get_product( $product_id );

if ( ! $product ) {
wp_send_json_error(
array(
'message' => __( 'Product not found.', 'duracelltomi-google-tag-manager' ),
),
404
);
}

$product_data = gtm4wp_woocommerce_process_product( $product, array(), 'ajax_fallback' );

if ( ! $product_data ) {
wp_send_json_error(
array(
'message' => __( 'Unable to build product data.', 'duracelltomi-google-tag-manager' ),
),
500
);
}

wp_send_json_success(
array(
'product' => $product_data,
)
);
}

add_filter( 'woocommerce_blocks_product_grid_item_html', 'gtm4wp_woocommerce_add_productdata_to_wc_block', 10, 3 );

add_action( 'woocommerce_thankyou', 'gtm4wp_woocommerce_thankyou' );
Expand Down
Loading