Aimtell Push Notifications and WooCommerce plugins to increase WooCommerce sales with intelligently targeted notifications. Version: 1.0.1 Copyright 2024 Aimtell, Inc. */ if (!defined('ABSPATH')) exit; // Create a class "AimtellWooCommerce" to integrtate with the WooCommerce platform if (!defined('ATWC_CREATED')) { #[\AllowDynamicProperties] class AimtellWooCommerce { public function __construct() { $this->init(); } /** * Initialize the plugin. Should only be called once. */ private function init() { // Prevent multiple instances if (defined('ATWC_CREATED')) return; // Define a constant to prevent multiple instances define('ATWC_CREATED', time()); if (!defined('ATWC_DO_LOG')) define('ATWC_DO_LOG', true); // Start session if (!session_id()) { session_start(); } // Add handlers $this->add_handlers(); // Add scripts, necessary for some client side Aimtell data colelction $this->add_scripts(); } /** * Get the cart ID from the session or generate a new one if necessary. * @param $force_update - force an update of the cart ID. This prevents returning a potentially-cached cart ID. * @return string - the cart ID. */ private function get_cart_id($force_update=false) { // force update the cart id if ($force_update) { $this->clear_cart_id(); } // if we already have a cart ID, return it if (!empty($_SESSION['aimtell_cart_id'])) { return $_SESSION['aimtell_cart_id']; } // generate and return new cart id return $this->generate_cart_id(); } /** * Generate a new cart ID. * @param $store_id - store the cart ID in the session. * @return string - the new cart ID. */ private function generate_cart_id($store_id=true) { // generate a new cartid $cart_id = md5($this->get_subscriber_uid().uniqid()); if ($store_id) $_SESSION['aimtell_cart_id'] = $cart_id; return $cart_id; } /** * Clear the cart ID from the session. Necessary to allow for multiple carts after empty or purchase events. */ private function clear_cart_id() { if (isset($_SESSION['aimtell_cart_id'])) { $this->atlog("Cart is empty! Clearing cart id ".$_SESSION['aimtell_cart_id']."...",4); } unset($_SESSION['aimtell_cart_id']); } /** * Add WooCommerce handlers via WordPress actions. */ private function add_handlers() { // any time the cart is changed add_action('woocommerce_cart_updated', array($this, 'woocommerce_cart_updated'), 11, 0); // when items are added to cart add_action('woocommerce_add_cart_item_data', array($this, 'woocommerce_add_cart_item_data'), 10, 4); } /** * Add scripts to the header or footer via WordPress actions. */ private function add_scripts() { add_action('wp_footer', [$this, 'footer_js'], 11, 0); } /** * Add the necessary Aimtell JS to the footer. */ public function footer_js() { ?> atlog("woocommerce_add_cart_item_data handler...",4); $this->handle_cart_add($product_id); } /** * Handle cart updated event. Occurs any time the cart is updated. */ public function woocommerce_cart_updated() { if (defined('ATWC_CART_UPDATED')) { return; } define('ATWC_CART_UPDATED',time()); $this->did_woocommerce_cart_updated = true; // Update cart, providing an opportunity to empty the cart id if we have nothing in the cart $this->handle_cart_update(); } /** * Get the end user Aimtell subscriber ID from the session or cookie. * @return string - the subscriber ID. */ private function get_subscriber_uid() { $subscriber_uid = ''; if (!$subscriber_uid && isset($_SESSION) && isset($_SESSION["_aimtellSubscriberID"])) $subscriber_uid = $_SESSION["_aimtellSubscriberID"]; if (!$subscriber_uid && isset($_COOKIE) && isset($_COOKIE["_aimtellSubscriberID"])) $subscriber_uid = $_COOKIE["_aimtellSubscriberID"]; return $subscriber_uid; } /** * Get the Aimtell owner ID from the session or cookie. * @return string - the owner ID. */ private function get_owner_uid() { $owner_uid = ''; if (!$owner_uid && isset($_SESSION) && isset($_SESSION["_aimtellOwnerID"])) $owner_uid = $_SESSION["_aimtellOwnerID"]; if (!$owner_uid && isset($_COOKIE) && isset($_COOKIE["_aimtellOwnerID"])) $owner_uid = $_COOKIE["_aimtellOwnerID"]; return $owner_uid; } /** * Get the Aimtell site ID from the session or cookie. * @return string - the site ID. */ private function get_idsite() { $idsite = ''; if (!$idsite && isset($_SESSION) && isset($_SESSION["_aimtellIdSite"])) $idsite = $_SESSION["_aimtellIdSite"]; if (!$idsite && isset($_COOKIE) && isset($_COOKIE["_aimtellIdSite"])) $idsite = $_COOKIE["_aimtellIdSite"]; return $idsite; } /** * Get the Aimtell auth token from the session or cookie. * @return string - the auth token. */ private function get_auth_token() { $auth_token = ''; if (!$auth_token && isset($_SESSION) && isset($_SESSION["aimtell_auth_token"])) $auth_token = $_SESSION["aimtell_auth_token"]; if (!$auth_token && isset($_COOKIE) && isset($_COOKIE["aimtell_auth_token"])) $auth_token = $_COOKIE["aimtell_auth_token"]; return $auth_token; } /** * Handle cart add event. Occurs any time an item is added to the cart. * @param $product_id - the product ID of the item added to the cart. * @return StdClass - the response object from the tracking API event { success:bool, error:string, response:string } */ private function handle_cart_add($product_id='') { if (!empty($product_id)) { $this->atlog("Product added to cart: $product_id",4); $this->last_item_added = wc_get_product($product_id); } $data = $this->generate_atcart_payload(); // Perform Aimtell cart tracking via external API call $ret = $this->cart_tracking($data); $this->atlog("Aimtell->cart_tracking response: ".json_encode($ret),4); return $ret; } /** * Perform cart tracking via external API call. * @param $data - the data to send to the API { cart:string, subscriber:string, owner_uid:string, idSite:string } * @return StdClass - the response object { success:bool, error:string, response:string } */ private function cart_tracking($data) { $ret = new StdClass(); // validation if (empty($data)) { $ret->success = false; $ret->error = "Missing/invalid cart_tracking: data"; return $ret; } else { if (empty($data->cart)) { $ret->success = false; $ret->error = "Missing/invalid cart_tracking: data.cart"; return $ret; } if (empty($data->subscriber)) { $ret->success = false; $ret->error = "Missing/invalid cart_tracking: data.subscriber"; return $ret; } if (empty($data->owner_uid)) { $ret->success = false; $ret->error = "Missing/invalid cart_tracking: data.owner_uid"; return $ret; } if (empty($data->idSite)) { $ret->success = false; $ret->error = "Missing/invalid cart_tracking: data.idSite"; return $ret; } } $this->atlog("Sending cart tracking data to Aimtell: ".json_encode($data),4); $curl = curl_init(); curl_setopt_array( $curl, array( CURLOPT_URL => 'https://api.aimtell.com/prod/shopify/cookie', CURLOPT_RETURNTRANSFER => true, CURLOPT_ENCODING => '', CURLOPT_MAXREDIRS => 10, CURLOPT_TIMEOUT => 0, CURLOPT_FOLLOWLOCATION => true, CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1, CURLOPT_CUSTOMREQUEST => 'POST', CURLOPT_POSTFIELDS =>json_encode($data), CURLOPT_HTTPHEADER => array( 'Content-Type: text/plain;charset=UTF-8' ), ) ); $response = curl_exec($curl); // capture the response curl_close($curl); $ret->response = $response; $ret->success = true; return $ret; } /** * Handle cart update event. Occurs any time the cart contents are changed. */ private function handle_cart_update() { // Get the last item added to the cart $item = $this->get_last_item_added(); $cart_items = $this->get_cart_items(); // If we have nothing in the cart, then we're done here if (empty($item) && empty($cart_items)) { // $this->atlog("nothing in the cart",4); $this->clear_cart_id(); return; } } /** * Handle webhooks. Webhooks can occur when a cart is updated, or when items are added. */ private function handle_webhooks() { $cart_id = $this->get_cart_id(); $this->handle_cart_add(); } /** * Get the cart items from the WooCommerce cart. * @param $force_update - force an update of the cart items. This prevents returning potentially-cached cart items. * @return array - Array of cart items. */ private function get_cart_items($force_update=false) { if (isset($this->cart_items) && !$force_update) { return $this->cart_items; } $items = array(); $cart = WC()->cart->get_cart(); foreach($cart as $cart_item_key => $item) { $has_items = true; // Process each cart item $product_id = $item['product_id']; $quantity = $item['quantity']; // $this->atlog("product_id: $product_id",4); // $this->atlog("quantity: $quantity",4); $items[] = $item; } return $items; } /** * Convenience method for retrieving the last item added to the cart. * @return WC_Product - the last item added to the cart. */ private function get_last_item_added() { if (isset($this->last_item_added)) return $this->last_item_added; return null; } /** * Convenience method for generating a payload for cart tracking API calls. * @return StdClass - the payload object { cart:string, subscriber:string, owner_uid:string, idSite:string, variables:StdClass } */ private function generate_atcart_payload() { $data = new StdClass(); $last_item = $this->get_last_item_added(); $data->cart = $this->get_cart_id(); $data->subscriber = $this->get_subscriber_uid(); $data->owner_uid = $this->get_owner_uid(); $data->idSite = $this->get_idsite(); $data->variables = $this->generate_atcart_variables(); $item_count = 1; if (isset($data->variables) && isset($data->variables->item_count)) { $data->item_count = $data->variables->item_count; } // response object should contain: {result:success,abandoned_cart:true} return $data; } /** * Convenience method for generating properly-formed cart data for atcart_payload. * @return StdClass - the variables object [ { price:string, url:string, icon:string, title:string, item_count:int }, ... ] */ private function generate_atcart_variables() { $price = ''; $url = ''; $icon = ''; $title = ''; $item_count = 0; $data = new StdClass(); $last_item = $this->get_last_item_added(); if (!empty($last_item)) { $last_item_id = $last_item->get_id(); // get the product $product = wc_get_product($last_item_id); if (!empty($product)) { $price = $product->get_price(); $url = get_permalink( $product->get_id() ); $icon = wp_get_attachment_image_url($product->get_image_id()); $title = $product->get_name(); $item_count = 1; } } $data->price = $price; $data->url = $url; $data->icon = $icon; $data->title = $title; $data->item_count = $item_count; return $data; } /** * Log a message through the main plugin logger (wp-content/uploads/aimtell/aimtell.log). * @param $msg - the message to log. * @param $level - indicates logging Error [1], Warning [2], Info [3], Debug [4]. */ private function atlog($msg, $level = 1) { if (function_exists('atlog')) atlog($msg, $level); } } new AimtellWooCommerce(); }