PluginProbe
WPFunnels – Funnel Builder for WooCommerce with Checkout & One Click Upsell / 3.13.3
WPFunnels – Funnel Builder for WooCommerce with Checkout & One Click Upsell v3.13.3
3.13.3 3.13.2 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 All 261 releases
wpfunnels / includes / core / Reporting / CheckoutTracker.php

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

169 lines 4.8 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 $inserted = $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 if ( $inserted ) {
77 /**
78 * Fires when a new unique checkout visit is recorded (a repeat visit
79 * the same day is ignored by the table's unique key).
80 *
81 * @since 3.14.0
82 *
83 * @param string $type 'funnel' or 'store'.
84 */
85 do_action( 'wpfunnels/checkout_visit_recorded', $type );
86 }
87 }
88
89 /**
90 * Count unique funnel checkout visitors for a date range.
91 *
92 * @param string $start_date
93 * @param string $end_date
94 * @return int
95 */
96 public static function get_funnel_checkout_visits( $start_date, $end_date ) {
97 global $wpdb;
98 $table = $wpdb->prefix . self::TABLE_SUFFIX;
99
100 return (int) $wpdb->get_var( $wpdb->prepare(
101 "SELECT COUNT(id) FROM {$table}
102 WHERE type = 'funnel'
103 AND visit_date >= %s AND visit_date <= %s",
104 date( 'Y-m-d', strtotime( $start_date ) ),
105 date( 'Y-m-d', strtotime( $end_date ) )
106 ) );
107 }
108
109 /**
110 * Count unique native WC checkout visitors for a date range.
111 *
112 * @param string $start_date
113 * @param string $end_date
114 * @return int
115 */
116 public static function get_store_checkout_visits( $start_date, $end_date ) {
117 global $wpdb;
118 $table = $wpdb->prefix . self::TABLE_SUFFIX;
119
120 return (int) $wpdb->get_var( $wpdb->prepare(
121 "SELECT COUNT(id) FROM {$table}
122 WHERE type = 'store'
123 AND visit_date >= %s AND visit_date <= %s",
124 date( 'Y-m-d', strtotime( $start_date ) ),
125 date( 'Y-m-d', strtotime( $end_date ) )
126 ) );
127 }
128
129 /**
130 * Returns true if the user agent looks like a known bot/crawler.
131 *
132 * @param string $user_agent
133 * @return bool
134 */
135 private static function is_bot( $user_agent ) {
136 if ( empty( $user_agent ) ) {
137 return true;
138 }
139 $bot_tokens = array( 'bot', 'crawl', 'slurp', 'spider', 'mediapartners', 'facebookexternalhit', 'wget', 'curl', 'python-requests', 'go-http-client' );
140 $ua_lower = strtolower( $user_agent );
141 foreach ( $bot_tokens as $token ) {
142 if ( strpos( $ua_lower, $token ) !== false ) {
143 return true;
144 }
145 }
146 return false;
147 }
148
149 /**
150 * Resolve client IP, honouring common proxy headers (Cloudflare first).
151 *
152 * @return string
153 */
154 private static function get_client_ip() {
155 $headers = array( 'HTTP_CF_CONNECTING_IP', 'HTTP_X_FORWARDED_FOR', 'HTTP_X_REAL_IP', 'REMOTE_ADDR' );
156 foreach ( $headers as $header ) {
157 if ( ! empty( $_SERVER[ $header ] ) ) {
158 $ip = sanitize_text_field( wp_unslash( $_SERVER[ $header ] ) );
159 // X-Forwarded-For may be a comma-separated chain; take the first
160 if ( strpos( $ip, ',' ) !== false ) {
161 $ip = trim( explode( ',', $ip )[0] );
162 }
163 return $ip;
164 }
165 }
166 return '';
167 }
168 }
169