| @@ -27,8 +27,15 @@ | ||
| 27 | 27 | */ |
| 28 | 28 | protected $mixpanel; |
| 29 | 29 | |
| 30 | 30 | /** |
| 31 | + * Whether the Mixpanel user has already been identified during this request. | |
| 32 | + * | |
| 33 | + * @var bool | |
| 34 | + */ | |
| 35 | + private $identified = false; | |
| 36 | + | |
| 37 | + /** | |
| 31 | 38 | * Constructor. |
| 32 | 39 | * |
| 33 | 40 | * @param Optin $optin The Mixpanel opt-in service. |
| 34 | 41 | * @param TrackingPlugin $mixpanel The Mixpanel tracking plugin service. |
| @@ -56,18 +63,81 @@ | ||
| 56 | 63 | * |
| 57 | 64 | * @return array<string, mixed> |
| 58 | 65 | */ |
| 59 | 66 | protected function get_default_event_properties(): array { |
| 60 | - $user = get_imagify_user(); | |
| 61 | - $license_owner = ''; | |
| 67 | + $email = $this->get_license_owner_email(); | |
| 68 | + $license_owner = '' !== $email ? hash( 'sha256', $email ) : ''; | |
| 62 | 69 | |
| 63 | - if ( ! is_wp_error( $user ) && ! empty( $user->email ) ) { | |
| 64 | - $license_owner = hash( 'sha256', $user->email ); | |
| 65 | - } | |
| 70 | + $this->identify_user( $email ); | |
| 66 | 71 | |
| 67 | 72 | return [ |
| 68 | 73 | 'context' => 'wp_plugin', |
| 69 | 74 | 'license_owner' => $license_owner, |
| 70 | - 'user_id' => (int) get_current_user_id(), | |
| 71 | 75 | ]; |
| 76 | + } | |
| 77 | + | |
| 78 | + /** | |
| 79 | + * Registers a stable `distinct_id` super property on the Mixpanel instance. | |
| 80 | + * | |
| 81 | + * Mixpanel needs `distinct_id` on every event to compute user-level metrics | |
| 82 | + * (MAU/DAU, retention, cohorts, funnels). `TrackingPlugin::identify()` hashes | |
| 83 | + * the identifier with sha224 before it is registered, so no raw email or | |
| 84 | + * domain ever leaves the site. | |
| 85 | + * | |
| 86 | + * The identifier is the Imagify license owner email — the same strategy as | |
| 87 | + * WP Rocket, so one license maps to one Mixpanel user across all its sites. | |
| 88 | + * When no license email is available (unlicensed or unreachable API), the | |
| 89 | + * site host is used instead so events are still attributed to a stable, | |
| 90 | + * anonymized identity rather than none at all. | |
| 91 | + * | |
| 92 | + * Called from `get_default_event_properties()` — the single choke point every | |
| 93 | + * tracked event goes through — so the license user is fetched only once and | |
| 94 | + * never on plugin bootstrap. Runs at most once per instance per request; the | |
| 95 | + * underlying Mixpanel instance is shared, so the property applies to every | |
| 96 | + * subsequent event. | |
| 97 | + * | |
| 98 | + * @param string $email The license owner email, or an empty string when unknown. | |
| 99 | + * | |
| 100 | + * @return void | |
| 101 | + */ | |
| 102 | + protected function identify_user( string $email ): void { | |
| 103 | + if ( $this->identified ) { | |
| 104 | + return; | |
| 105 | + } | |
| 106 | + | |
| 107 | + $identifier = '' !== $email ? $email : $this->get_site_identifier(); | |
| 108 | + | |
| 109 | + if ( '' === $identifier ) { | |
| 110 | + return; | |
| 111 | + } | |
| 112 | + | |
| 113 | + $this->mixpanel->identify( $identifier ); | |
| 114 | + | |
| 115 | + $this->identified = true; | |
| 116 | + } | |
| 117 | + | |
| 118 | + /** | |
| 119 | + * Returns the Imagify license owner email. | |
| 120 | + * | |
| 121 | + * @return string The email, or an empty string when the account is unknown. | |
| 122 | + */ | |
| 123 | + private function get_license_owner_email(): string { | |
| 124 | + $user = get_imagify_user(); | |
| 125 | + | |
| 126 | + if ( is_wp_error( $user ) || empty( $user->email ) ) { | |
| 127 | + return ''; | |
| 128 | + } | |
| 129 | + | |
| 130 | + return (string) $user->email; | |
| 131 | + } | |
| 132 | + | |
| 133 | + /** | |
| 134 | + * Returns the site host, used as a fallback identifier when no license email exists. | |
| 135 | + * | |
| 136 | + * @return string The host, or an empty string when it cannot be resolved. | |
| 137 | + */ | |
| 138 | + private function get_site_identifier(): string { | |
| 139 | + $host = wp_parse_url( get_home_url(), PHP_URL_HOST ); | |
| 140 | + | |
| 141 | + return is_string( $host ) ? $host : ''; | |
| 72 | 142 | } |
| 73 | 143 | } |