From 5182ff6827af5983acecf8baaae9f259b11ccbca Mon Sep 17 00:00:00 2001 From: Udi Li-Hod Date: Sat, 29 Aug 2026 01:33:41 +0300 Subject: [PATCH] Analytics: reliable activation event, stable install identity, consistent categories Co-Authored-By: Claude Opus 4.8 --- php/class-admin.php | 6 ++++-- php/class-analytics.php | 37 ++++++++++++++++++++++++++++++++++--- php/class-assets.php | 3 ++- php/class-deactivation.php | 2 +- 4 files changed, 41 insertions(+), 7 deletions(-) diff --git a/php/class-admin.php b/php/class-admin.php index 70b2ebb3..a9c2c61a 100644 --- a/php/class-admin.php +++ b/php/class-admin.php @@ -538,9 +538,10 @@ protected function maybe_track_global_transformation( $analytics, $submission, $ $analytics->track( 'transformation_applied', - 'media', + 'feature_usage', null, array( + 'feature' => 'transformation', 'scope' => 'global', 'transformation_count' => count( $matched ), ) @@ -565,9 +566,10 @@ protected function track_gallery_configured( $analytics, $data ) { $analytics->track( 'gallery_configured', - 'features', + 'feature_usage', null, array( + 'feature' => 'gallery', 'layout' => isset( $config['displayProps']['mode'] ) ? $config['displayProps']['mode'] : '', 'media_count' => isset( $config['mediaAssets'] ) && is_array( $config['mediaAssets'] ) ? count( $config['mediaAssets'] ) : 0, ) diff --git a/php/class-analytics.php b/php/class-analytics.php index 61df3b29..d9347d3f 100644 --- a/php/class-analytics.php +++ b/php/class-analytics.php @@ -121,7 +121,7 @@ public static function stash_activation() { 'new_version' => $current, 'days_since_last_active' => $days_since_last_active, ), - HOUR_IN_SECONDS + DAY_IN_SECONDS ); } catch ( \Throwable $e ) { // Fail silent: activation must never break. @@ -147,6 +147,11 @@ public static function record_deactivation() { * @return void */ public function maybe_send_pending_activation() { + // Leave the stash intact if emission is disabled, so the event is not + // consumed-and-dropped and can still be sent on a later admin load. + if ( ! $this->is_enabled() ) { + return; + } $pending = get_transient( self::PENDING_ACTIVATION ); if ( empty( $pending ) || ! is_array( $pending ) ) { return; @@ -314,6 +319,7 @@ protected function base_params() { 'wp_version' => get_bloginfo( 'version' ), 'php_version' => PHP_VERSION, 'site_id' => hash( 'sha256', home_url() ), + 'install_id' => $this->get_install_id(), 'session_id' => $this->get_session_id(), 'user_role' => $this->get_user_role(), 'is_multisite' => is_multisite(), @@ -335,6 +341,25 @@ protected function base_params() { return $params; } + /** + * Stable, session-independent installation identifier. + * + * Generated once and persisted, so events emitted off-interactive + * (cron/queue/front-end), where `session_id` is empty, still carry a + * durable join key. Unlike `site_id` it survives a domain change. + * + * @return string + */ + protected function get_install_id() { + $id = get_option( '_cloudinary_install_id' ); + if ( empty( $id ) ) { + $id = wp_generate_uuid4(); + add_option( '_cloudinary_install_id', $id, '', false ); + } + + return (string) $id; + } + /** * Per-admin-session identifier, derived from the hashed WP login token. * @@ -369,7 +394,13 @@ protected function get_user_role() { return (string) reset( $user->roles ); } - return ''; + // No acting user (cron/queue/front-end): record the context instead of + // an empty string so off-interactive events remain distinguishable. + if ( wp_doing_cron() ) { + return 'cron'; + } + + return is_admin() ? 'system' : 'front'; } /** @@ -475,7 +506,7 @@ public function maybe_send_smoke_event() { } set_transient( $throttle_key, true, 5 * MINUTE_IN_SECONDS ); - $this->track( 'poc_smoke_test', 'poc' ); + $this->track( 'poc_smoke_test', 'system' ); } /** diff --git a/php/class-assets.php b/php/class-assets.php index dbd72061..8bb4dac8 100644 --- a/php/class-assets.php +++ b/php/class-assets.php @@ -193,9 +193,10 @@ public function track_cache_uploaded( $attachment_id, $result ) { $analytics->track( 'cache_uploaded', - 'cache', + 'feature_usage', null, array( + 'feature' => 'asset_sync', 'item_count' => 1, 'status' => is_wp_error( $result ) ? 'error' : 'success', ) diff --git a/php/class-deactivation.php b/php/class-deactivation.php index 04cf82d6..3fac7424 100644 --- a/php/class-deactivation.php +++ b/php/class-deactivation.php @@ -446,7 +446,7 @@ public function rest_callback( WP_REST_Request $request ) { if ( $analytics && ! $is_contact_flow ) { $analytics->track( 'deactivation_submitted', - 'deactivation', + 'activation_funnel', null, array( 'reason_id' => sanitize_text_field( $reason ),