PluginProbe
WPFunnels – Funnel Builder for WooCommerce with Checkout & One Click Upsell / 3.13.1
WPFunnels – Funnel Builder for WooCommerce with Checkout & One Click Upsell v3.13.1
3.13.1 3.13.0 3.12.13 3.12.12 3.12.11 3.12.10 3.12.9 3.12.8 3.12.7 3.12.6 3.12.5 3.12.4 3.12.3 3.12.1 3.12.2 3.12.0 3.11.1 3.11.0 3.10.9 3.10.8 3.10.7 3.10.6 2.8.16 2.8.17 2.8.18 All 259 releases
wpfunnels / includes / core / Reporting / CheckoutTracker.php

CheckoutTracker.php in WPFunnels – Funnel Builder for WooCommerce with Checkout & One Click Upsell 3.13.1, at includes/core/Reporting/CheckoutTracker.php

157 lines 4.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace WPFunnels\Report;
4
5 class CheckoutTracker {
6
7 const TABLE_SUFFIX = 'wpfnl_checkout_visits';
8
9 /**
10 * Create the checkout visits table using dbDelta.
11 */
12 public static function create_table() {
13 global $wpdb;
14 $wpdb->hide_errors();
15 $charset_collate = $wpdb->get_charset_collate();
16 $table = $wpdb->prefix . self::TABLE_SUFFIX;
17
18 $sql = "CREATE TABLE IF NOT EXISTS {$table} (
19 id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
20 type varchar(10) NOT NULL DEFAULT 'store',
21 funnel_id bigint(20) unsigned NOT NULL DEFAULT 0,
22 session_hash varchar(32) NOT NULL,
23 visit_date date NOT NULL,
24 date_created datetime NOT NULL,
25 PRIMARY KEY (id),
26 UNIQUE KEY unique_visit (session_hash, type, funnel_id, visit_date)
27 ) {$charset_collate};";
28
29 require_once ABSPATH . 'wp-admin/includes/upgrade.php';
30 dbDelta( $sql );
31 }
32
33 /**
34 * Record a checkout page visit with per-browser-session deduplication.
35 *
36 * Uniqueness key: WooCommerce session ID (unique per browser — incognito gets
37 * a separate session from the regular browser). Falls back to md5(IP+UA) when
38 * the WC session is unavailable (e.g. called outside a WC context).
39 *
40 * @param string $type 'funnel' or 'store'
41 * @param int $funnel_id Funnel post ID (0 for store visits)
42 */
43 public static function track_visit( $type, $funnel_id = 0 ) {
44 $user_agent = isset( $_SERVER['HTTP_USER_AGENT'] )
45 ? sanitize_text_field( wp_unslash( $_SERVER['HTTP_USER_AGENT'] ) )
46 : '';
47
48 if ( self::is_bot( $user_agent ) ) {
49 return;
50 }
51
52 // WC session ID is unique per browser session — incognito vs normal = different IDs.
53 // Fall back to IP+UA when WC session isn't available.
54 if ( function_exists( 'WC' ) && WC()->session && WC()->session->get_customer_id() ) {
55 $session_hash = md5( WC()->session->get_customer_id() );
56 } else {
57 $session_hash = md5( self::get_client_ip() . $user_agent );
58 }
59
60 $visit_date = current_time( 'Y-m-d' );
61 $date_created = current_time( 'mysql' );
62
63 global $wpdb;
64 $table = $wpdb->prefix . self::TABLE_SUFFIX;
65
66 $wpdb->query( $wpdb->prepare(
67 "INSERT IGNORE INTO {$table} (type, funnel_id, session_hash, visit_date, date_created)
68 VALUES (%s, %d, %s, %s, %s)",
69 $type,
70 (int) $funnel_id,
71 $session_hash,
72 $visit_date,
73 $date_created
74 ) );
75 }
76
77 /**
78 * Count unique funnel checkout visitors for a date range.
79 *
80 * @param string $start_date
81 * @param string $end_date
82 * @return int
83 */
84 public static function get_funnel_checkout_visits( $start_date, $end_date ) {
85 global $wpdb;
86 $table = $wpdb->prefix . self::TABLE_SUFFIX;
87
88 return (int) $wpdb->get_var( $wpdb->prepare(
89 "SELECT COUNT(id) FROM {$table}
90 WHERE type = 'funnel'
91 AND visit_date >= %s AND visit_date <= %s",
92 date( 'Y-m-d', strtotime( $start_date ) ),
93 date( 'Y-m-d', strtotime( $end_date ) )
94 ) );
95 }
96
97 /**
98 * Count unique native WC checkout visitors for a date range.
99 *
100 * @param string $start_date
101 * @param string $end_date
102 * @return int
103 */
104 public static function get_store_checkout_visits( $start_date, $end_date ) {
105 global $wpdb;
106 $table = $wpdb->prefix . self::TABLE_SUFFIX;
107
108 return (int) $wpdb->get_var( $wpdb->prepare(
109 "SELECT COUNT(id) FROM {$table}
110 WHERE type = 'store'
111 AND visit_date >= %s AND visit_date <= %s",
112 date( 'Y-m-d', strtotime( $start_date ) ),
113 date( 'Y-m-d', strtotime( $end_date ) )
114 ) );
115 }
116
117 /**
118 * Returns true if the user agent looks like a known bot/crawler.
119 *
120 * @param string $user_agent
121 * @return bool
122 */
123 private static function is_bot( $user_agent ) {
124 if ( empty( $user_agent ) ) {
125 return true;
126 }
127 $bot_tokens = array( 'bot', 'crawl', 'slurp', 'spider', 'mediapartners', 'facebookexternalhit', 'wget', 'curl', 'python-requests', 'go-http-client' );
128 $ua_lower = strtolower( $user_agent );
129 foreach ( $bot_tokens as $token ) {
130 if ( strpos( $ua_lower, $token ) !== false ) {
131 return true;
132 }
133 }
134 return false;
135 }
136
137 /**
138 * Resolve client IP, honouring common proxy headers (Cloudflare first).
139 *
140 * @return string
141 */
142 private static function get_client_ip() {
143 $headers = array( 'HTTP_CF_CONNECTING_IP', 'HTTP_X_FORWARDED_FOR', 'HTTP_X_REAL_IP', 'REMOTE_ADDR' );
144 foreach ( $headers as $header ) {
145 if ( ! empty( $_SERVER[ $header ] ) ) {
146 $ip = sanitize_text_field( wp_unslash( $_SERVER[ $header ] ) );
147 // X-Forwarded-For may be a comma-separated chain; take the first
148 if ( strpos( $ip, ',' ) !== false ) {
149 $ip = trim( explode( ',', $ip )[0] );
150 }
151 return $ip;
152 }
153 }
154 return '';
155 }
156 }
157