cart) {
echo '
' . "\n";
}
if (empty($events)) {
return;
}
$json = self::encode_context_json($events);
if ($json === null) {
return;
}
wp_add_inline_script(
'agenized-context',
'window.__agenized_context_events=' . $json . ';',
'before'
);
}
/**
* Collect all context events for this request (native WP/WC only).
*
* @return list}>
*/
private static function collect_events(): array {
$events = [];
$url_path = isset( $_SERVER['REQUEST_URI'] ) ? sanitize_text_field( wp_unslash( $_SERVER['REQUEST_URI'] ) ) : '';
if ( strpos( $url_path, '?' ) !== false ) {
$url_path = strstr( $url_path, '?', true ) ?: $url_path;
}
$document_title = wp_get_document_title();
// 1. add_to_cart (from transient, set by woocommerce_add_to_cart or fragment)
$add_payload = self::get_last_add_to_cart_payload();
if ($add_payload !== null) {
$events[] = ['event' => 'add_to_cart', 'data' => $add_payload];
self::clear_last_add_to_cart();
}
// 2. product_view (single product page)
if (function_exists('is_product') && is_product()) {
$product = wc_get_product(get_the_ID());
if ($product && is_a($product, 'WC_Product')) {
$events[] = [
'event' => 'product_view',
'data' => self::normalize_product_data($product, null),
];
}
}
// 3. cart_view (cart page)
if (function_exists('is_cart') && is_cart() && function_exists('WC') && WC()->cart) {
$cart = WC()->cart;
$events[] = [
'event' => 'cart_view',
'data' => [
'cart_item_count' => $cart->get_cart_contents_count(),
'cart_total' => (float) $cart->get_total('edit'),
'currency' => get_woocommerce_currency(),
],
];
}
// 3b. checkout_viewed (checkout; not thank-you — that is order_completed)
if ( function_exists( 'is_checkout' ) && is_checkout() && function_exists( 'is_wc_endpoint_url' ) && ! is_wc_endpoint_url( 'order-received' ) ) {
if ( strlen( $document_title ) >= 4 ) {
$events[] = [
'event' => 'checkout_viewed',
'data' => [
'url_path' => $url_path ?: '/',
'title' => $document_title,
'url' => home_url( $url_path ?: '/' ),
],
];
}
}
// 4. search_performed (search results)
if (is_search()) {
global $wp_query;
$events[] = [
'event' => 'search_performed',
'data' => [
'query' => get_search_query(),
'results_count' => $wp_query->found_posts ?? 0,
],
];
}
// 5. checkout_error (after validation or stored from process)
if (function_exists('is_checkout') && is_checkout() && !is_wc_endpoint_url('order-received')) {
$error_payload = self::get_checkout_error_payload();
if ($error_payload !== null) {
$events[] = ['event' => 'checkout_error', 'data' => $error_payload];
}
}
// 6. order_completed (thank you / order-received page)
if (function_exists('is_checkout') && is_checkout() && function_exists('is_wc_endpoint_url') && is_wc_endpoint_url('order-received')) {
$order_id = absint(get_query_var('order-received'));
if ($order_id && function_exists('wc_get_order')) {
$order = wc_get_order($order_id);
if ($order && is_a($order, 'WC_Order')) {
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only query param used to verify order ownership on thank-you page.
$key = isset($_GET['key']) ? sanitize_text_field(wp_unslash($_GET['key'])) : '';
if ($key !== '' && $order->get_order_key() === $key) {
$events[] = [
'event' => 'order_completed',
'data' => [
'order_id' => (string) $order->get_id(),
'amount' => (float) $order->get_total(),
'currency' => $order->get_currency(),
],
];
}
}
}
}
// 7. page_view (generic pages — skip cart and checkout; those use cart_view / checkout_viewed)
$emit_page_view = strlen( $document_title ) >= 4;
if ( $emit_page_view && function_exists( 'is_cart' ) && is_cart() ) {
$emit_page_view = false;
}
if ( $emit_page_view && function_exists( 'is_checkout' ) && is_checkout() && function_exists( 'is_wc_endpoint_url' ) && ! is_wc_endpoint_url( 'order-received' ) ) {
$emit_page_view = false;
}
if ( $emit_page_view ) {
$events[] = [
'event' => 'page_view',
'data' => [
'url_path' => $url_path ?: '/',
'title' => $document_title,
],
];
}
return $events;
}
private static function get_session_key(): string {
try {
if (function_exists('WC') && WC()->session) {
if (method_exists(WC()->session, 'get_customer_unique_id')) {
$id = WC()->session->get_customer_unique_id();
return $id ? (string) $id : 'guest';
}
if (method_exists(WC()->session, 'get_customer_id')) {
$id = WC()->session->get_customer_id();
return $id ? (string) $id : 'guest';
}
}
} catch (\Throwable $e) {
// Never break the store for a context event session key.
}
return 'guest';
}
private static function transient_key_add(): string {
return self::TRANSIENT_LAST_ADD_PREFIX . self::get_session_key();
}
private static function get_last_add_to_cart_payload(): ?array {
$key = self::transient_key_add();
$raw = get_transient($key);
if (!is_array($raw)) {
return null;
}
return $raw;
}
private static function clear_last_add_to_cart(): void {
delete_transient(self::transient_key_add());
}
/**
* WooCommerce add to cart hook: store payload for next page load or fragment.
*/
public static function on_add_to_cart($cart_item_key, $product_id, $quantity, $variation_id, $variation, $cart_item_data): void {
try {
$product = wc_get_product($variation_id ? $variation_id : $product_id);
if (!$product || !is_a($product, 'WC_Product')) {
return;
}
$cart = function_exists('WC') && WC()->cart ? WC()->cart : null;
$data = self::normalize_product_data($product, $quantity);
if ($cart) {
$data['cart_item_count'] = $cart->get_cart_contents_count();
$data['cart_total'] = (float) $cart->get_total('edit');
$data['currency'] = get_woocommerce_currency();
}
set_transient(self::transient_key_add(), $data, self::TRANSIENT_LAST_ADD_TTL);
} catch (\Throwable $e) {
// Never break add-to-cart for a context event.
}
}
/**
* Add a fragment so AJAX add-to-cart can send context event.
* Fragment replaces #agenized-context-add with a script that pushes the event.
*/
public static function add_fragment_for_add_to_cart(array $fragments): array {
$payload = self::get_last_add_to_cart_payload();
if ($payload !== null) {
self::clear_last_add_to_cart();
$json = self::encode_context_json($payload);
if ($json !== null) {
$js = '(function(){var aw=window.AgenizedWidget;if(aw&&typeof aw.pushContext==="function")aw.pushContext({event:"add_to_cart",data:' . $json . '});})();';
if (function_exists('wp_get_inline_script_tag')) {
$fragments['#agenized-context-add'] = wp_get_inline_script_tag($js);
} else {
$fragments['#agenized-context-add'] = '';
}
}
}
return $fragments;
}
/**
* JSON-encode for embedding in inline <script> (hex-escapes + UTF-8 safety).
*
* @param mixed $data
*/
private static function encode_context_json($data): ?string {
$options = JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT;
if (defined('JSON_INVALID_UTF8_SUBSTITUTE')) {
$options |= JSON_INVALID_UTF8_SUBSTITUTE;
}
$json = wp_json_encode($data, $options);
return \is_string($json) ? $json : null;
}
private static function normalize_product_data(\WC_Product $product, ?int $quantity): array {
$data = [
'product_id' => (string) $product->get_id(),
'name' => $product->get_name(),
'price' => (float) $product->get_price(),
'currency' => get_woocommerce_currency(),
];
if ($quantity !== null) {
$data['quantity'] = $quantity;
}
if ($product->get_sku()) {
$data['product_id'] = $product->get_sku();
}
return $data;
}
private const TRANSIENT_CHECKOUT_ERROR = 'agenized_checkout_error';
public static function maybe_capture_checkout_error(): void {
try {
if (!function_exists('wc_get_notices')) {
return;
}
$errors = wc_get_notices('error');
if (empty($errors)) {
return;
}
$payload = [
'error_type' => 'checkout_validation',
'step' => 'checkout',
];
set_transient(self::TRANSIENT_CHECKOUT_ERROR . '_' . self::get_session_key(), $payload, 120);
} catch (\Throwable $e) {
// Never break checkout for a context event.
}
}
private static function get_checkout_error_payload(): ?array {
$key = self::TRANSIENT_CHECKOUT_ERROR . '_' . self::get_session_key();
$raw = get_transient($key);
if (!is_array($raw)) {
return null;
}
delete_transient($key);
return $raw;
}
}