is_woocommerce_active()) {
add_action('admin_notices', array($this, 'woocommerce_missing_notice'));
return;
}
// Initialize plugin
$this->init();
}
/**
* Get instance
*
* @return Acoustic_Connect_Woo
*/
public static function get_instance()
{
if (null === self::$instance) {
self::$instance = new self();
}
return self::$instance;
}
/**
* Check if WooCommerce is active
*
* @return bool
*/
private function is_woocommerce_active()
{
// Check if WooCommerce class exists
if (class_exists('WooCommerce')) {
return true;
}
// Fallback: check active plugins
if (!function_exists('is_plugin_active')) {
include_once ABSPATH . 'wp-admin/includes/plugin.php';
}
if (function_exists('is_plugin_active')) {
return is_plugin_active('woocommerce/woocommerce.php');
}
return false;
}
/**
* WooCommerce missing notice
*/
public function woocommerce_missing_notice()
{
$message = sprintf(
/* translators: %s: WooCommerce plugin link */
esc_html__('Acoustic Connect for WooCommerce requires WooCommerce to be installed and active. You can download %s here.', 'acoustic-connect-woo'),
'WooCommerce'
);
echo '
' . wp_kses_post($message) . '
';
}
/**
* Initialize plugin
*/
private function init()
{
// Load plugin files
$this->load_files();
// Register hooks
$this->register_hooks();
// Initialize components
$this->init_components();
// Note: load_plugin_textdomain() is not needed when hosted on WordPress.org
// WordPress automatically loads translations for plugins in the directory
}
/**
* Load required files
*/
private function load_files()
{
require_once ACOUSTIC_CONNECT_WOO_PLUGIN_DIR . 'includes/class-admin-settings.php';
require_once ACOUSTIC_CONNECT_WOO_PLUGIN_DIR . 'includes/class-event-handler.php';
}
/**
* Register hooks
*/
private function register_hooks()
{
// Enqueue scripts
add_action('wp_enqueue_scripts', array($this, 'enqueue_scripts'));
// Add settings link
add_filter('plugin_action_links_' . ACOUSTIC_CONNECT_WOO_PLUGIN_BASENAME, array($this, 'add_settings_link'));
// AJAX handler for connection test
add_action('wp_ajax_acoustic_connect_test_connection', array($this, 'ajax_test_connection'));
// Note: Logout handling is done in JavaScript by detecting when customerData.customerId
// changes from a value to empty. This approach is more reliable than PHP hooks because
// WordPress redirects before wp_footer renders during logout.
}
/**
* Initialize components
*/
private function init_components()
{
// Initialize admin settings
Acoustic_Connect_Admin_Settings::get_instance();
// Initialize event handler
Acoustic_Connect_Event_Handler::get_instance();
}
/**
* Plugin activation
*/
public function activate()
{
// Set default options
$defaults = array(
'acoustic_connect_enabled' => 'no',
'acoustic_connect_app_key' => '',
'acoustic_connect_collector_url' => '',
'acoustic_connect_subscription_tier' => 'pro',
'acoustic_connect_sdk_version' => 'latest',
'acoustic_connect_sdk_minified' => 'yes',
'acoustic_connect_debug_mode' => 'no',
'acoustic_connect_track_page_view' => 'yes',
'acoustic_connect_track_product_view' => 'yes',
'acoustic_connect_track_search' => 'yes',
'acoustic_connect_track_add_to_cart' => 'yes',
'acoustic_connect_track_remove_from_cart' => 'yes',
'acoustic_connect_track_checkout' => 'yes',
);
foreach ($defaults as $key => $value) {
if (false === get_option($key)) {
add_option($key, $value);
}
}
// Set activation timestamp
add_option('acoustic_connect_activated_time', time());
// Flush rewrite rules
flush_rewrite_rules();
}
/**
* Plugin deactivation
*/
public function deactivate()
{
// Flush rewrite rules
flush_rewrite_rules();
}
/**
* Enqueue scripts and styles
*/
public function enqueue_scripts()
{
// Only load if enabled and configured
$enabled = get_option('acoustic_connect_enabled', 'no');
$app_key = get_option('acoustic_connect_app_key', '');
$collector_url = get_option('acoustic_connect_collector_url', '');
if ('yes' !== $enabled || empty($app_key) || empty($collector_url)) {
return;
}
// Get SDK configuration (CDN is the only source)
$sdk_version = get_option('acoustic_connect_sdk_version', 'latest');
$sdk_minified = get_option('acoustic_connect_sdk_minified', 'yes');
// Use CDN for SDK
$file_name = ('yes' === $sdk_minified) ? 'acoconnect.min.js' : 'acoconnect.js';
$version_path = ('latest' === $sdk_version) ? 'latest' : $sdk_version;
$sdk_url = "https://cdn.goacoustic.com/connect/{$version_path}/{$file_name}";
$sdk_ver = null; // Don't set version for external URLs
// Enqueue Acoustic Connect Generic SDK
wp_enqueue_script(
'acoustic-connect-sdk',
$sdk_url,
array(),
$sdk_ver,
true
);
// Get subscription tier
$subscription_tier = get_option('acoustic_connect_subscription_tier', 'pro');
$debug_mode = get_option('acoustic_connect_debug_mode', 'no');
// Create dynamic version string that changes when settings change
// This ensures browser cache is busted when configuration is updated
$wrapper_version = ACOUSTIC_CONNECT_WOO_VERSION . '-' . md5($app_key . $collector_url . $subscription_tier . $debug_mode . $sdk_version . $sdk_minified);
// Enqueue WooCommerce wrapper
// TODO: consider also having a wrapper in CDN
wp_enqueue_script(
'acoustic-connect-wrapper',
ACOUSTIC_CONNECT_WOO_PLUGIN_URL . 'assets/js/acoustic-connect-wrapper.js',
array('jquery', 'acoustic-connect-sdk'),
$wrapper_version,
true
);
// Pass configuration to JavaScript
$config = array(
'appKey' => $app_key,
'postUrl' => $collector_url,
'subscriptionTier' => $subscription_tier,
'debugMode' => ('yes' === $debug_mode),
'currency' => get_woocommerce_currency(),
'customerData' => $this->get_customer_data(),
);
// use wp_add_inline_script(string $handle, string $data, string $position = 'after' ): bool
// instead of wp_localize_script( string $handle, string $object_name, array $l10n ): bool
wp_localize_script('acoustic-connect-wrapper', 'acousticConnectConfig', $config);
// Pass product data on product pages
if (is_product()) {
global $product;
if ($product) {
$product_data = array(
'id' => $product->get_id(),
'name' => $product->get_name(),
'description' => $product->get_short_description(),
'price' => $product->get_price(),
'regular_price' => $product->get_regular_price(),
'sale_price' => $product->get_sale_price(),
'currency' => get_woocommerce_currency(),
'sku' => $product->get_sku(),
'category' => $this->get_product_category($product),
'url' => get_permalink($product->get_id()),
'image' => wp_get_attachment_url($product->get_image_id()),
'in_stock' => $product->is_in_stock(),
'discount' => $product->get_sale_price() ? ($product->get_regular_price() - $product->get_sale_price()) : 0,
);
wp_localize_script('acoustic-connect-wrapper', 'acousticProductData', $product_data);
}
}
// Pass order data on thank-you page
if (is_order_received_page()) {
$order_id = get_query_var('order-received');
if ($order_id) {
$order = wc_get_order($order_id);
if ($order) {
$order_data = $this->get_order_data($order);
wp_localize_script('acoustic-connect-wrapper', 'acousticOrderData', $order_data);
}
}
}
}
/**
* Get product category
*
* @param WC_Product $product Product object.
* @return string
*/
private function get_product_category($product)
{
$categories = get_the_terms($product->get_id(), 'product_cat');
if (!empty($categories) && !is_wp_error($categories)) {
return $categories[0]->name;
}
return '';
}
/**
* Get order data for tracking
*
* @param WC_Order $order Order object.
* @return array
*/
private function get_order_data($order)
{
$items = array();
foreach ($order->get_items() as $item) {
$product = $item->get_product();
$items[] = array(
'product_id' => $product ? $product->get_id() : 0,
'product_name' => $item->get_name(),
'quantity' => $item->get_quantity(),
'price' => $item->get_total() / $item->get_quantity(),
'discount' => $item->get_subtotal() - $item->get_total(),
'category' => $product ? $this->get_product_category($product) : '',
);
}
return array(
'order_id' => $order->get_id(),
'total' => $order->get_total(),
'subtotal' => $order->get_subtotal(),
'tax' => $order->get_total_tax(),
'shipping' => $order->get_shipping_total(),
'discount' => $order->get_discount_total(),
'currency' => $order->get_currency(),
'items' => $items,
);
}
/**
* Get customer data for identifiableAttributes
* Returns customer ID (WordPress user ID or WC session ID for guests), email, and phone
*
* @return array
*/
private function get_customer_data()
{
$customer_data = array(
'customerId' => '',
'email' => '',
'phone' => '',
);
// Try logged-in user ID first
if (is_user_logged_in()) {
$user_id = get_current_user_id();
$user = wp_get_current_user();
$customer_data['customerId'] = strval($user_id);
// Safely get and trim email
$email = isset($user->user_email) ? trim($user->user_email) : '';
$customer_data['email'] = !empty($email) ? $email : '';
// Try to get phone from WooCommerce billing data
$billing_phone = get_user_meta($user_id, 'billing_phone', true);
$phone = is_string($billing_phone) ? trim($billing_phone) : '';
$customer_data['phone'] = !empty($phone) ? $phone : '';
} elseif (function_exists('WC') && WC()->customer) {
// For customers: use WooCommerce customer ID
$session_customer_id = WC()->customer->get_id();
if ($session_customer_id) {
$customer_data['customerId'] = strval($session_customer_id);
}
}
return $customer_data;
}
/**
* AJAX handler for connection test
*/
public function ajax_test_connection()
{
// Verify nonce
check_ajax_referer('acoustic_connect_admin', 'nonce');
// Check user permissions
if (!current_user_can('manage_woocommerce')) {
wp_send_json_error(array('message' => 'Unauthorized'), 403);
}
// Get parameters
$collector_url = isset($_POST['collector_url']) ? esc_url_raw(wp_unslash($_POST['collector_url'])) : '';
$app_key = isset($_POST['app_key']) ? sanitize_text_field(wp_unslash($_POST['app_key'])) : '';
if (empty($collector_url)) {
wp_send_json_error(array('message' => 'Collector URL is required'));
}
if (empty($app_key)) {
wp_send_json_error(array('message' => 'App Key is required'));
}
// Create a test signal payload (Type 21 message)
$test_payload = array(
'messageVersion' => '13.0.0',
'serialNumber' => 1,
'sessions' => array(
array(
'id' => '00000000000000000000000000000001',
'startTime' => round(microtime(true) * 1000),
'timezoneOffset' => 0,
'messages' => array(
array(
'type' => 21,
'offset' => 0,
'count' => 1,
'fromWeb' => true,
'signal' => array(
'signalType' => 'test',
'name' => 'Connection test from WordPress plugin',
'category' => 'System',
'effect' => 'positive'
)
)
),
'clientEnvironment' => array(
'webEnvironment' => array(
'libVersion' => '1.0.0-test',
'domain' => wp_parse_url(home_url(), PHP_URL_HOST)
)
)
)
)
);
// Send POST request to Collector URL
$response = wp_remote_post($collector_url, array(
'timeout' => 10,
'headers' => array(
'Content-Type' => 'application/json',
'X-Tealeaf-SaaS-AppKey' => $app_key,
),
'body' => wp_json_encode($test_payload),
'sslverify' => true
));
// Check for errors
if (is_wp_error($response)) {
wp_send_json_error(array(
'message' => 'Connection failed: ' . $response->get_error_message()
));
}
$status_code = wp_remote_retrieve_response_code($response);
$body = wp_remote_retrieve_body($response);
// Success responses (200, 201, 204)
if ($status_code >= 200 && $status_code < 300) {
wp_send_json_success(array(
'message' => 'Connection successful! Status: ' . $status_code,
'status_code' => $status_code
));
}
// Handle specific error codes
if ($status_code === 401 || $status_code === 403) {
wp_send_json_error(array(
'message' => 'Authentication failed. Check your App Key. Status: ' . $status_code
));
}
if ($status_code === 400) {
wp_send_json_error(array(
'message' => 'Bad request. The Collector URL may be incorrect. Status: ' . $status_code
));
}
// Generic error
wp_send_json_error(array(
'message' => 'Connection failed with status: ' . $status_code,
'status_code' => $status_code
));
}
/**
* Add settings link to plugins page
*
* @param array $links Plugin action links.
* @return array
*/
public function add_settings_link($links)
{
$settings_link = sprintf(
'%s',
esc_url(admin_url('admin.php?page=wc-settings&tab=acoustic_connect')),
esc_html__('Settings', 'acoustic-connect-woo')
);
array_unshift($links, $settings_link);
return $links;
}
/**
* Declare HPOS compatibility
*/
public function declare_hpos_compatibility()
{
if (class_exists(\Automattic\WooCommerce\Utilities\FeaturesUtil::class)) {
\Automattic\WooCommerce\Utilities\FeaturesUtil::declare_compatibility('custom_order_tables', ACOUSTIC_CONNECT_WOO_PLUGIN_FILE, true);
}
}
}
/**
* Initialize plugin
*
* @return Acoustic_Connect_Woo
*/
function acoustic_connect_woo_init()
{
return Acoustic_Connect_Woo::get_instance();
}
// Initialize plugin after all plugins are loaded
add_action('plugins_loaded', 'acoustic_connect_woo_init', 10);