$installed && ! empty( $state['active'] ), 'installed' => $installed, 'version' => $version, ); } /** Return installed/active/version state for the allow-listed Woodmart theme. */ function abtestkit_connected_environment_woodmart() : array { $theme = wp_get_theme( 'woodmart' ); $installed = $theme->exists(); $version = $installed ? trim( (string) $theme->get( 'Version' ) ) : ''; return array( 'active' => $installed && in_array( 'woodmart', array( (string) get_stylesheet(), (string) get_template() ), true ), 'installed' => $installed, 'version' => 1 === preg_match( '/^[A-Za-z0-9][A-Za-z0-9._+\-]{0,31}$/D', $version ) ? $version : null, ); } /** Build the exact evidence-driven environment-v2 integration allow-list. */ function abtestkit_connected_environment_integrations() : array { return array( 'elementor' => abtestkit_connected_environment_plugin( array( 'elementor/elementor.php' ) ), 'woodmart' => abtestkit_connected_environment_woodmart(), 'cache_enabler' => abtestkit_connected_environment_plugin( array( 'cache-enabler/cache-enabler.php' ) ), 'litespeed_cache' => abtestkit_connected_environment_plugin( array( 'litespeed-cache/litespeed-cache.php' ) ), 'wp_rocket' => abtestkit_connected_environment_plugin( array( 'wp-rocket/wp-rocket.php' ) ), 'w3_total_cache' => abtestkit_connected_environment_plugin( array( 'w3-total-cache/w3-total-cache.php' ) ), 'wp_super_cache' => abtestkit_connected_environment_plugin( array( 'wp-super-cache/wp-cache.php' ) ), 'autoptimize' => abtestkit_connected_environment_plugin( array( 'autoptimize/autoptimize.php' ) ), ); } /** Build the strict environment-v2 payload from non-content platform state. */ function abtestkit_connected_environment_snapshot( int $observed_at ) : array { $woocommerce_active = defined( 'WC_VERSION' ); return array( 'schema_version' => 2, 'observed_at' => $observed_at, 'plugin_version' => abtestkit_hosted_sync_plugin_version(), 'wordpress_version' => (string) get_bloginfo( 'version' ), 'php_version' => PHP_VERSION, 'woocommerce' => array( 'active' => $woocommerce_active, 'version' => $woocommerce_active ? (string) WC_VERSION : null, ), 'multisite' => is_multisite(), 'environment' => (string) wp_get_environment_type(), 'environment_source' => 'wp_get_environment_type', 'integrations' => abtestkit_connected_environment_integrations(), ); } /** Validate an exact locally persisted environment payload before replay. */ function abtestkit_connected_environment_valid_snapshot( $snapshot ) : bool { if ( ! is_array( $snapshot ) ) { return false; } $keys = array_keys( $snapshot ); sort( $keys ); $schema_version = $snapshot['schema_version'] ?? null; $expected_keys = 2 === $schema_version ? array( 'environment', 'environment_source', 'integrations', 'multisite', 'observed_at', 'php_version', 'plugin_version', 'schema_version', 'woocommerce', 'wordpress_version' ) : array( 'environment', 'environment_source', 'multisite', 'observed_at', 'php_version', 'plugin_version', 'schema_version', 'woocommerce', 'wordpress_version' ); if ( $expected_keys !== $keys ) { return false; } $version_pattern = '/^[A-Za-z0-9][A-Za-z0-9._+\-]{0,31}$/D'; if ( ! in_array( $schema_version, array( 1, 2 ), true ) || ! is_int( $snapshot['observed_at'] ) || $snapshot['observed_at'] <= 0 || ! is_bool( $snapshot['multisite'] ) || ! is_string( $snapshot['environment'] ) || ! in_array( $snapshot['environment'], array( 'local', 'development', 'staging', 'production' ), true ) || 'wp_get_environment_type' !== $snapshot['environment_source'] || ! is_string( $snapshot['plugin_version'] ) || 1 !== preg_match( $version_pattern, $snapshot['plugin_version'] ) || ! is_string( $snapshot['wordpress_version'] ) || 1 !== preg_match( $version_pattern, $snapshot['wordpress_version'] ) || ! is_string( $snapshot['php_version'] ) || 1 !== preg_match( $version_pattern, $snapshot['php_version'] ) ) { return false; } $woocommerce = $snapshot['woocommerce']; if ( ! is_array( $woocommerce ) || array( 'active', 'version' ) !== array_keys( $woocommerce ) || ! is_bool( $woocommerce['active'] ) ) { return false; } $woocommerce_valid = $woocommerce['active'] ? is_string( $woocommerce['version'] ) && 1 === preg_match( $version_pattern, $woocommerce['version'] ) : null === $woocommerce['version']; if ( ! $woocommerce_valid || 1 === $schema_version ) { return $woocommerce_valid; } $integrations = $snapshot['integrations']; $integration_keys = array( 'elementor', 'woodmart', 'cache_enabler', 'litespeed_cache', 'wp_rocket', 'w3_total_cache', 'wp_super_cache', 'autoptimize' ); if ( ! is_array( $integrations ) || $integration_keys !== array_keys( $integrations ) ) { return false; } foreach ( $integrations as $integration ) { if ( ! is_array( $integration ) || array( 'active', 'installed', 'version' ) !== array_keys( $integration ) || ! is_bool( $integration['active'] ) || ! is_bool( $integration['installed'] ) ) { return false; } if ( $integration['active'] && ! $integration['installed'] ) { return false; } if ( ! $integration['installed'] && null !== $integration['version'] ) { return false; } if ( null !== $integration['version'] && ( ! is_string( $integration['version'] ) || 1 !== preg_match( $version_pattern, $integration['version'] ) ) ) { return false; } } return true; } /** Hash only environment values, excluding the observation time. */ function abtestkit_connected_environment_fingerprint( array $snapshot ) : string { unset( $snapshot['observed_at'] ); return hash( 'sha256', (string) wp_json_encode( $snapshot, JSON_UNESCAPED_SLASHES ) ); } /** Send one exact pending snapshot, weekly when unchanged and sooner on change. */ function abtestkit_connected_environment_run() : void { if ( ! abtestkit_connected_platform_can_create_or_launch() ) { return; } $connection = abtestkit_hosted_get_connection(); if ( ! is_array( $connection ) || ! empty( $connection['pending_credential'] ) ) { return; } $state = get_option( ABTESTKIT_CONNECTED_ENVIRONMENT_OPTION, array() ); $state = is_array( $state ) ? $state : array(); $now = time(); $pending = $state['pending'] ?? null; if ( null !== $pending && ! abtestkit_connected_environment_valid_snapshot( $pending ) ) { unset( $state['pending'] ); $state['last_error_code'] = 'invalid_local_snapshot'; update_option( ABTESTKIT_CONNECTED_ENVIRONMENT_OPTION, $state, false ); return; } if ( is_array( $pending ) ) { if ( (int) ( $state['last_attempt_at'] ?? 0 ) > $now - HOUR_IN_SECONDS ) { return; } $snapshot = $pending; $fingerprint = abtestkit_connected_environment_fingerprint( $snapshot ); } else { $observed_at = max( $now, (int) ( $state['last_observed_at'] ?? 0 ) + 1 ); $snapshot = abtestkit_connected_environment_snapshot( $observed_at ); if ( ! abtestkit_connected_environment_valid_snapshot( $snapshot ) ) { return; } $fingerprint = abtestkit_connected_environment_fingerprint( $snapshot ); if ( hash_equals( (string) ( $state['last_fingerprint'] ?? '' ), $fingerprint ) && (int) ( $state['last_success_at'] ?? 0 ) > $now - ABTESTKIT_CONNECTED_ENVIRONMENT_REFRESH_SECONDS ) { return; } $state['pending'] = $snapshot; } $state['last_attempt_at'] = $now; $state['last_error_code'] = 'transport'; update_option( ABTESTKIT_CONNECTED_ENVIRONMENT_OPTION, $state, false ); $response = abtestkit_hosted_service_request( 'PUT', '/sites/' . (string) $connection['site_ref'] . '/environment', $snapshot, (string) $connection['credential'] ); if ( is_wp_error( $response ) ) { return; } $status = (int) $response['status']; $data = $response['data']; $response_keys = is_array( $data ) ? array_keys( $data ) : array(); sort( $response_keys ); if ( in_array( $status, array( 200, 201 ), true ) && is_array( $data ) && true === ( $data['accepted'] ?? null ) && is_bool( $data['duplicate'] ?? null ) && array( 'accepted', 'duplicate', 'observed_at' ) === $response_keys && (int) ( $data['observed_at'] ?? 0 ) === (int) $snapshot['observed_at'] ) { unset( $state['pending'] ); $state['last_success_at'] = time(); $state['last_observed_at'] = (int) $snapshot['observed_at']; $state['last_fingerprint'] = $fingerprint; $state['last_error_code'] = ''; } elseif ( 404 === $status ) { $state['last_error_code'] = 'service_route_unavailable'; } elseif ( in_array( $status, array( 401, 403 ), true ) ) { $state['last_error_code'] = 'service_denied'; } else { $state['last_error_code'] = 'service_unavailable'; } update_option( ABTESTKIT_CONNECTED_ENVIRONMENT_OPTION, $state, false ); } add_action( ABTESTKIT_CONNECTED_TRANSPORT_HOOK, 'abtestkit_connected_environment_run', 30 );