dispatcher = $dispatcher ?: new EventDispatcher(); } /** * Registers WordPress action hooks. */ public function register(): void { add_action('wp_enqueue_scripts', [$this, 'enqueueScripts']); add_action('wp_footer', [$this, 'outputQueuedEvents'], 99); add_action('comment_post', [$this, 'onCommentPost'], 10, 3); } /** * Retrieves the event dispatcher instance. * * @return EventDispatcher */ public function getDispatcher(): EventDispatcher { return $this->dispatcher; } /** * Checks if tracking should be suppressed for the current user. * * @return bool */ public static function shouldExcludeCurrentUser(): bool { if (!Options::get('exclude_logged_in_users', false)) { return false; } if (!function_exists('is_user_logged_in') || !is_user_logged_in()) { return false; } $excludedRoles = Options::get('excluded_user_roles', ['administrator', 'shop_manager']); if (empty($excludedRoles)) { return true; } if (function_exists('wp_get_current_user')) { $user = wp_get_current_user(); if ($user && !empty($user->roles)) { foreach ($user->roles as $role) { if (in_array($role, (array) $excludedRoles, true)) { return true; } } } } return false; } /** * Enqueues the client bridge script and localizes options. */ public function enqueueScripts(): void { if (is_admin() || self::shouldExcludeCurrentUser()) { return; } $url = defined('ZBRIDGE_URL') ? ZBRIDGE_URL : (defined('WP_ZBRIDGE_URL') ? WP_ZBRIDGE_URL : (defined('WP_ZARAZ_URL') ? WP_ZARAZ_URL : '')); $ver = defined('ZBRIDGE_VERSION') ? ZBRIDGE_VERSION : (defined('WP_ZBRIDGE_VERSION') ? WP_ZBRIDGE_VERSION : (defined('WP_ZARAZ_VERSION') ? WP_ZARAZ_VERSION : '1.2.7')); $path = defined('ZBRIDGE_PATH') ? ZBRIDGE_PATH : (defined('WP_ZBRIDGE_PATH') ? WP_ZBRIDGE_PATH : (defined('WP_ZARAZ_PATH') ? WP_ZARAZ_PATH : dirname(__DIR__, 2) . '/')); wp_enqueue_script( 'wp-zaraz-bridge', $url . 'assets/js/zaraz-bridge.js', [], $ver, ['strategy' => 'defer', 'in_footer' => false] ); $clientConfig = $this->getClientConfig(); wp_localize_script('wp-zaraz-bridge', 'wpZarazConfig', $clientConfig); if (class_exists(\WPZaraz\Licensing\FeatureGate::class) && \WPZaraz\Licensing\FeatureGate::hasAccess('pro_tracking')) { $proScriptPath = $path . 'assets/js/tracker-pro.js'; if (file_exists($proScriptPath)) { wp_enqueue_script( 'wp-zaraz-tracker-pro', $url . 'assets/js/tracker-pro.js', ['wp-zaraz-bridge'], $ver, ['strategy' => 'defer', 'in_footer' => true] ); } } } /** * Prepares client-side configuration array. * * @return array */ public function getClientConfig(): array { $extensionsRaw = Options::get('download_extensions', 'pdf,zip,csv,docx,xlsx,mp3,mp4'); $extensions = array_filter(array_map('trim', explode(',', strtolower($extensionsRaw)))); $config = [ 'trackScroll' => (bool) Options::get('track_scroll', true), 'scrollThresholds' => Options::get('scroll_thresholds', [25, 50, 75, 90]), 'trackOutbound' => (bool) Options::get('track_outbound', true), 'trackExternalLinks' => (bool) Options::get('track_external_links', true), 'trackDownloads' => (bool) Options::get('track_downloads', true), 'downloadExtensions' => array_values($extensions), 'trackTimeOnPage' => (bool) Options::get('track_time_on_page', true), 'timeThresholds' => (array) Options::get('time_thresholds', [10, 30, 60, 120, 300]), 'trackEmailClicks' => (bool) Options::get('track_email_clicks', true), 'trackPhoneClicks' => (bool) Options::get('track_phone_clicks', true), 'trackSearch' => (bool) Options::get('track_search', true), 'trackUnsubmittedSearch' => (bool) Options::get('track_unsubmitted_search', true), 'trackSearchSelection' => (bool) Options::get('track_search_selection', true), 'trackVariantSelection' => (bool) Options::get('wc_track_variant_selection', true), 'trackPaymentInfo' => false, 'useParentIdForVariations' => (bool) Options::get('wc_use_parent_id_for_variations', false), 'searchTerm' => function_exists('is_search') && is_search() ? get_search_query() : '', 'is404' => function_exists('is_404') && is_404(), ]; /** * Filter frontend tracker client-side configuration. * * @param array $config */ return (array) apply_filters('zbridge_frontend_tracker_config', $config); } /** * Outputs any queued events in the footer. */ public function outputQueuedEvents(): void { if (self::shouldExcludeCurrentUser()) { return; } // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Pre-formatted inline script generated by EventDispatcher echo $this->dispatcher->renderInlineJs(); } /** * Handles WordPress comment submission event capture. * * @param int $commentId Comment ID. * @param mixed $commentApproved Approval status. * @param array $commentdata Comment data array. */ public function onCommentPost(int $commentId, $commentApproved, array $commentdata = []): void { if (!Options::get('track_comments', true)) { return; } $payload = [ 'post_id' => isset($commentdata['comment_post_ID']) ? (int) $commentdata['comment_post_ID'] : 0, 'comment_id' => $commentId, ]; $this->setFlashEvent('comment_submit', $payload); } /** * Sets a transient 1-time flash cookie consumed by zaraz-bridge.js on next page load. * * @param string $eventName Event name. * @param array $payload Event payload. */ protected function setFlashEvent(string $eventName, array $payload): void { if (headers_sent()) { return; } $data = wp_json_encode([ 'type' => 'track', 'name' => $eventName, 'payload' => $payload, ]); $cookiePath = defined('COOKIEPATH') && COOKIEPATH ? COOKIEPATH : '/'; $cookieDomain = defined('COOKIE_DOMAIN') && COOKIE_DOMAIN ? COOKIE_DOMAIN : ''; $secure = is_ssl(); setcookie('wp_zbridge_flash_event', rawurlencode((string) $data), time() + 300, $cookiePath, $cookieDomain, $secure, false); } }