PluginProbe
WPFunnels – Funnel Builder for WooCommerce with Checkout & One Click Upsell / 3.10.6
WPFunnels – Funnel Builder for WooCommerce with Checkout & One Click Upsell v3.10.6
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 / Tracking / class-wpfnl-telemetry.php

class-wpfnl-telemetry.php in WPFunnels – Funnel Builder for WooCommerce with Checkout & One Click Upsell 3.10.6, at includes/core/Tracking/class-wpfnl-telemetry.php

287 lines 9.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace WPFunnels\Tracking;
4
5 use LinnoSDK\Telemetry\Client;
6
7 class Telemetry {
8
9 /**
10 * PostHog API key.
11 *
12 * @var string
13 */
14 private const POSTHOG_API_KEY = 'phc_lpaTTy4DiOGImfWtE6NK8puO5dZ9y3lum35BxUledi4';
15
16 /**
17 * Approved feature identifiers for usage tracking.
18 *
19 * @var string[]
20 */
21 private const APPROVED_FEATURES = [
22 'template_library',
23 'order_bumps',
24 'upsells_downsells',
25 ];
26
27 /**
28 * Singleton instance.
29 *
30 * @var Telemetry|null
31 */
32 private static $instance;
33
34 /**
35 * Linno telemetry client.
36 *
37 * @var Client|null
38 */
39 private $client;
40
41 /**
42 * Initialize telemetry singleton.
43 *
44 * @return Telemetry|null
45 */
46 public static function init() {
47 if ( ! self::$instance ) {
48 self::$instance = new self();
49 }
50
51 return self::$instance;
52 }
53
54 /**
55 * Boot the PostHog-backed telemetry client and register triggers.
56 *
57 * Failure-safe: any SDK exception is caught so telemetry never interrupts
58 * plugin activation or other core flows.
59 */
60 private function __construct() {
61 if ( ! class_exists( '\\LinnoSDK\\Telemetry\\Client' ) ) {
62 return;
63 }
64
65 try {
66 $this->client = new Client( [
67 'pluginFile' => WPFNL_FILE,
68 'slug' => 'wpfunnels',
69 'pluginName' => 'WPFunnels',
70 'version' => WPFNL_VERSION,
71 'driver' => 'posthog',
72 'driver_config' => [
73 'api_key' => self::POSTHOG_API_KEY,
74 'host' => 'https://eu.i.posthog.com',
75 ],
76 'review_prompt' => [
77 'webhook' => 'https://getwpfunnels.com/wp-json/mrm/v1/form/44/webhook/receive?token=b553ed8f2d6f9ff1612b4fe79c19ff9ee8a30c170eb547f1aba9376eb7ac8039',
78 'min_feedback_length' => 50,
79 'snooze_schedule' => [ 7, 30, 90 ],
80 'nps_question' => 'How likely are you to recommend WPFunnels to your friends or colleagues?',
81 'low_score_threshold' => 7,
82 'review_url' => 'https://wordpress.org/support/plugin/wpfunnels/reviews/#new-post',
83 'support_url' => 'https://getwpfunnels.com/support/',
84 'privacy_url' => 'https://getwpfunnels.com/privacy-policy/',
85 'installed_option_key' => 'wpfunnels_installed_time',
86 'position' => 'bottom-right',
87 'allowed_screens' => [
88 WPFNL_MAIN_PAGE_SLUG,
89 WPFNL_FUNNEL_PAGE_SLUG,
90 WPFNL_TEMPLATE_PAGE_SLUG,
91 WPFNL_SETTINGS_SLUG,
92 WPFNL_GLOBAL_SETTINGS_SLUG,
93 WPFNL_EDIT_FUNNEL_SLUG,
94 WPFNL_INTEGRATIONS_MAIN_PAGE_SLUG,
95 ],
96 ],
97 ] );
98 } catch ( \Exception $e ) {
99 return;
100 }
101
102 $this->register_triggers();
103 }
104
105 /**
106 * Register all telemetry triggers: onboarding lifecycle, feature usage, and
107 * per-funnel Aha milestone.
108 *
109 * Activation and deactivation lifecycle events are handled automatically by
110 * the Linno SDK through register_activation_hook / register_deactivation_hook.
111 * Onboarding completion is single-fire; the SDK guards repeat emissions via
112 * has_sent_event( 'onboarding_completed' ).
113 *
114 * @return void
115 */
116 private function register_triggers(): void {
117 if ( ! $this->client ) {
118 return;
119 }
120
121 try {
122 $this->client->define_triggers( [
123 'onboarding' => [
124 'hook' => 'wpfunnels_setup_wizard_complete',
125 'callback' => [ $this, 'setup_payload' ],
126 ],
127 'feature_used' => [
128 'template_library' => [
129 'hook' => 'wpfunnels_template_imported',
130 'callback' => [ $this, 'feature_usage_payload' ],
131 ],
132 'order_bumps' => [
133 'hook' => 'wpfunnels_order_bump_added',
134 'callback' => [ $this, 'feature_usage_payload' ],
135 ],
136 'upsells_downsells' => [
137 'hook' => 'wpfunnels_upsell_configured',
138 'callback' => [ $this, 'feature_usage_payload' ],
139 ],
140 ],
141 ] );
142 } catch ( \Exception $e ) {
143 // Failure-safe: trigger registration errors must not surface to callers.
144 }
145
146 // Per-funnel Aha milestone wired directly; SDK KUI thresholding is
147 // site-scoped and cannot enforce the funnel-level single-fire guarantee.
148 add_action( 'wpfunnels/funnel_order_placed', [ $this, 'handle_aha_milestone' ], 10, 3 );
149 }
150
151 // -------------------------------------------------------------------------
152 // Payload callbacks
153 // -------------------------------------------------------------------------
154
155 /**
156 * Onboarding completion payload.
157 *
158 * @param int $funnel_id Funnel id passed by the setup wizard hook.
159 * @param string $action Setup action label.
160 *
161 * @return array<string,mixed>
162 */
163 public function setup_payload( int $funnel_id = 0, string $action = '' ): array {
164 return [
165 'action' => sanitize_text_field( $action ),
166 '__timestamp' => $this->current_timestamp(),
167 ];
168 }
169
170 /**
171 * Feature usage payload.
172 *
173 * Accepts the arguments supplied by whichever product-action hook fired.
174 * Only approved feature identifiers are passed (validated by the trigger map
175 * itself), and no customer PII is included.
176 *
177 * @param int|string $funnel_id Funnel id.
178 * @param int|string $step_id Step id (0 when not applicable).
179 * @param mixed ...$_extra Additional hook args silently ignored.
180 *
181 * @return array<string,mixed>
182 */
183 public function feature_usage_payload( $funnel_id = 0, $step_id = 0, ...$_extra ): array {
184 $payload = [
185 '__timestamp' => $this->current_timestamp(),
186 ];
187 return $payload;
188 }
189
190 // -------------------------------------------------------------------------
191 // Aha milestone (per-funnel first live sale)
192 // -------------------------------------------------------------------------
193
194 /**
195 * Handle the funnel order placed hook for the per-funnel Aha milestone.
196 *
197 * Fires the Aha event exactly once per funnel. Duplicate, retried, or
198 * replayed order events for the same funnel are silently skipped.
199 *
200 * @param int|string $order_id Order id.
201 * @param int|string $funnel_id Funnel id.
202 * @param int|string $checkout_id Checkout step id.
203 *
204 * @return void
205 */
206 public function handle_aha_milestone( $order_id, $funnel_id, $checkout_id ): void {
207 $funnel_id = absint( $funnel_id );
208
209 if ( ! $funnel_id ) {
210 return;
211 }
212
213 if ( $this->has_aha_completed( $funnel_id ) ) {
214 return;
215 }
216
217 try {
218 if ( $this->client ) {
219 $this->client->track_kui( 'first_live_sale', $this->aha_payload( $order_id, $funnel_id ) );
220 }
221 } catch ( \Exception $e ) {
222 // Failure-safe: telemetry errors must never block order processing.
223 }
224
225 $this->mark_aha_completed( $funnel_id, $order_id );
226 }
227
228 /**
229 * Check whether the first-live-sale Aha milestone has been recorded for a funnel.
230 *
231 * @param int $funnel_id Funnel post ID.
232 *
233 * @return bool
234 */
235 public function has_aha_completed( int $funnel_id ): bool {
236 return (bool) get_post_meta( $funnel_id, '_wpfnl_first_live_sale_completed', true );
237 }
238
239 /**
240 * Persist the Aha milestone state for a funnel.
241 *
242 * Stores three meta keys so the milestone is traceable:
243 * - _wpfnl_first_live_sale_completed (boolean flag)
244 * - _wpfnl_first_live_sale_at (UTC ISO-8601 timestamp)
245 * - _wpfnl_first_live_sale_order (order ID)
246 *
247 * @param int $funnel_id Funnel post ID.
248 * @param int|string $order_id Order that triggered the milestone.
249 *
250 * @return void
251 */
252 public function mark_aha_completed( int $funnel_id, $order_id ): void {
253 update_post_meta( $funnel_id, '_wpfnl_first_live_sale_completed', '1' );
254 update_post_meta( $funnel_id, '_wpfnl_first_live_sale_at', $this->current_timestamp() );
255 update_post_meta( $funnel_id, '_wpfnl_first_live_sale_order', absint( $order_id ) );
256 }
257
258 /**
259 * Build the Aha milestone payload.
260 *
261 * Excludes all customer PII. Only funnel context and the order ID are included.
262 *
263 * @param int|string $order_id Order id.
264 * @param int $funnel_id Funnel id.
265 *
266 * @return array<string,mixed>
267 */
268 public function aha_payload( $order_id, int $funnel_id ): array {
269 return [
270 '__timestamp' => $this->current_timestamp(),
271 ];
272 }
273
274 // -------------------------------------------------------------------------
275 // Helpers
276 // -------------------------------------------------------------------------
277
278 /**
279 * Return current UTC timestamp for analytics ingestion.
280 *
281 * @return string ISO-8601 UTC timestamp.
282 */
283 private function current_timestamp(): string {
284 return gmdate( 'c' );
285 }
286 }
287