160 ) { return new WP_Error( 'abtestkit_connected_invalid_test' ); } $key = ABTESTKIT_CONNECTED_TEST_REF_PREFIX . hash( 'sha256', $test_id ); $record = get_option( $key, null ); if ( null === $record ) { $ref = ''; if ( function_exists( 'abtestkit_hosted_sync_get_state' ) ) { $sync = abtestkit_hosted_sync_get_state(); $prior = $sync['records'][ hash( 'sha256', $test_id ) ]['test_ref'] ?? ''; if ( is_string( $prior ) && 1 === preg_match( '/^test_[A-Za-z0-9_-]{43}$/D', $prior ) ) { $ref = $prior; } } if ( '' === $ref ) { $random = abtestkit_hosted_random_credential(); if ( is_wp_error( $random ) ) { return $random; } $ref = 'test_' . $random; } add_option( $key, array( 'test_ref' => $ref, 'observed' => false ), '', 'no' ); $record = get_option( $key, null ); } if ( ! is_array( $record ) || ! is_string( $record['test_ref'] ?? null ) || 1 !== preg_match( '/^test_[A-Za-z0-9_-]{43}$/D', $record['test_ref'] ) || ! is_bool( $record['observed'] ?? null ) ) { return new WP_Error( 'abtestkit_connected_test_ref_unavailable' ); } return $record; } function abtestkit_connected_mark_test_observed( string $test_id, array $record ) : bool { $record['observed'] = true; $key = ABTESTKIT_CONNECTED_TEST_REF_PREFIX . hash( 'sha256', $test_id ); update_option( $key, $record, false ); $saved = get_option( $key, null ); if ( ! is_array( $saved ) || empty( $saved['observed'] ) || ( $saved['test_ref'] ?? '' ) !== $record['test_ref'] ) { abtestkit_connected_collection_gap( 'reference_write_failed' ); return false; } return true; } function abtestkit_connected_collection_gap( string $code ) : void { update_option( ABTESTKIT_CONNECTED_COLLECTION_STATE_OPTION, array( 'last_gap_at' => time(), 'last_error_code' => sanitize_key( $code ), ), false ); } /** An exact, privacy-minimised projection of a stored experiment. */ function abtestkit_connected_test_payload( array $test, string $evidence ) { $kind = sanitize_key( (string) ( $test['kind'] ?? '' ) ); $goal = function_exists( 'abtestkit_pt_normalize_goal_for_display' ) ? abtestkit_pt_normalize_goal_for_display( $test['goal'] ?? '' ) : sanitize_key( (string) ( $test['goal'] ?? '' ) ); $status = sanitize_key( (string) ( $test['status'] ?? '' ) ); if ( ! in_array( $kind, array( 'page', 'post', 'product', 'reusable_section', 'custom_css', 'custom_html', 'custom_javascript' ), true ) || ! in_array( $goal, array( 'clicks', 'form', 'destination_url', 'scroll_depth', 'add_to_cart', 'purchase' ), true ) || ! in_array( $status, array( 'draft', 'running', 'paused', 'winner', 'complete' ), true ) ) { return new WP_Error( 'abtestkit_connected_unsupported_test' ); } return array( 'kind' => $kind, 'goal' => $goal, 'status' => $status, 'evidence' => $evidence ); } /** Queue one event without changing the merchant's committed local outcome. */ function abtestkit_connected_queue_test_event( string $name, string $test_id, array $test, string $ref, string $evidence = 'observed' ) : bool { $payload = abtestkit_connected_test_payload( $test, $evidence ); if ( is_wp_error( $payload ) ) { abtestkit_connected_collection_gap( $payload->get_error_code() ); return false; } if ( 'winner_declared' === $name ) { $winner = strtoupper( (string) ( $test['winner'] ?? '' ) ); if ( ! in_array( $winner, array( 'A', 'B' ), true ) || 'statistical_decision' !== (string) ( $test['winner_basis'] ?? '' ) ) { return false; } $payload['winner'] = $winner; $payload['winner_basis'] = 'statistical_decision'; } if ( 1 !== (int) get_option( ABTestKit_Connected_Outbox::SCHEMA_OPTION, 0 ) && current_user_can( 'manage_options' ) ) { ABTestKit_Connected_Outbox::install(); } $installation = ABTestKit_Connected_Installation::get_or_create(); $event_id = ABTestKit_Connected_Event::new_id(); if ( is_wp_error( $installation ) || is_wp_error( $event_id ) ) { abtestkit_connected_collection_gap( is_wp_error( $installation ) ? $installation->get_error_code() : 'random_failed' ); return false; } $event = array( 'schema_version' => 1, 'event_id' => $event_id, 'installation_id' => (string) $installation['installation_id'], 'event_name' => $name, 'occurred_at' => time(), 'plugin_version' => abtestkit_hosted_sync_plugin_version(), 'test_ref' => $ref, 'payload' => $payload, ); $queued = ABTestKit_Connected_Outbox::enqueue( $event, time() ); if ( is_wp_error( $queued ) ) { abtestkit_connected_collection_gap( $queued->get_error_code() ); return false; } return true; } /** * Observe only registry state confirmed by a successful option write. Older * tests receive a baseline of their prior state, never a fabricated creation. */ function abtestkit_connected_collect_registry_change( array $before, array $after ) : void { if ( ! abtestkit_connected_platform_can_create_or_launch() ) { return; } $old = array(); $new = array(); foreach ( $before as $test ) { if ( is_array( $test ) && ! empty( $test['id'] ) ) { $old[ (string) $test['id'] ] = $test; } } foreach ( $after as $test ) { if ( is_array( $test ) && ! empty( $test['id'] ) ) { $new[ (string) $test['id'] ] = $test; } } foreach ( $old + $new as $test_id => $unused ) { $previous = $old[ $test_id ] ?? null; $current = $new[ $test_id ] ?? null; if ( is_array( $previous ) && is_array( $current ) && $previous === $current ) { continue; } if ( ! is_array( $previous ) && is_array( $current ) ) { abtestkit_connected_milestone_seed_new_test( (string) $test_id ); } $record = abtestkit_connected_test_ref_record( (string) $test_id ); if ( is_wp_error( $record ) ) { abtestkit_connected_collection_gap( $record->get_error_code() ); continue; } $ref = (string) $record['test_ref']; if ( ! $record['observed'] ) { $baseline_test = is_array( $previous ) ? $previous : null; $initial_name = is_array( $baseline_test ) ? 'experiment_baseline' : 'experiment_created'; $initial_test = is_array( $baseline_test ) ? $baseline_test : $current; if ( ! is_array( $initial_test ) || ! abtestkit_connected_queue_test_event( $initial_name, (string) $test_id, $initial_test, $ref, is_array( $baseline_test ) ? 'baseline' : 'observed' ) ) { continue; } if ( ! abtestkit_connected_mark_test_observed( (string) $test_id, $record ) ) { continue; } if ( ! is_array( $previous ) && is_array( $current ) && 'running' === (string) ( $current['status'] ?? '' ) ) { abtestkit_connected_queue_test_event( 'experiment_launched', (string) $test_id, $current, $ref ); } } if ( ! is_array( $current ) ) { abtestkit_connected_queue_test_event( 'experiment_deleted', (string) $test_id, $previous, $ref ); continue; } if ( ! is_array( $previous ) ) { continue; } $from = (string) ( $previous['status'] ?? '' ); $to = (string) ( $current['status'] ?? '' ); if ( $from === $to ) { continue; } $event_name = ''; if ( 'running' === $to ) { $event_name = 'paused' === $from ? 'experiment_resumed' : 'experiment_launched'; } elseif ( 'paused' === $to ) { $event_name = 'experiment_paused'; } elseif ( 'complete' === $to ) { $event_name = 'experiment_completed'; } elseif ( 'winner' === $to ) { $event_name = 'winner_declared'; } if ( '' !== $event_name ) { abtestkit_connected_queue_test_event( $event_name, (string) $test_id, $current, $ref ); } } } /** Bound upgrade reconciliation to 20 baselines per cron tick. */ function abtestkit_connected_reconcile_baselines() : void { if ( ! abtestkit_connected_platform_can_create_or_launch() ) { return; } $tests = get_option( 'abtestkit_page_tests', array() ); if ( ! is_array( $tests ) ) { return; } $queued = 0; foreach ( $tests as $test ) { if ( $queued >= 20 ) { break; } if ( ! is_array( $test ) || empty( $test['id'] ) ) { continue; } $test_id = (string) $test['id']; $record = abtestkit_connected_test_ref_record( $test_id ); if ( is_wp_error( $record ) ) { abtestkit_connected_collection_gap( $record->get_error_code() ); continue; } if ( $record['observed'] ) { continue; } if ( ! abtestkit_connected_queue_test_event( 'experiment_baseline', $test_id, $test, (string) $record['test_ref'], 'baseline' ) ) { break; } if ( ! abtestkit_connected_mark_test_observed( $test_id, $record ) ) { break; } ++$queued; } }