*/ function abtestkit_hosted_sync_get_state() : array { $stored = get_option( ABTESTKIT_HOSTED_SYNC_OPTION, array() ); $stored = is_array( $stored ) ? $stored : array(); return array( 'schema_version' => 1, 'records' => isset( $stored['records'] ) && is_array( $stored['records'] ) ? $stored['records'] : array(), 'deletions' => isset( $stored['deletions'] ) && is_array( $stored['deletions'] ) ? array_values( $stored['deletions'] ) : array(), 'failure_count' => max( 0, (int) ( $stored['failure_count'] ?? 0 ) ), 'next_attempt_at' => max( 0, (int) ( $stored['next_attempt_at'] ?? 0 ) ), 'paused' => ! empty( $stored['paused'] ), 'last_error_code' => sanitize_key( (string) ( $stored['last_error_code'] ?? '' ) ), 'last_checked_at' => max( 0, (int) ( $stored['last_checked_at'] ?? 0 ) ), 'last_success_at' => max( 0, (int) ( $stored['last_success_at'] ?? 0 ) ), 'snapshot_sync_enabled' => isset( $stored['snapshot_sync_enabled'] ) && is_bool( $stored['snapshot_sync_enabled'] ) ? $stored['snapshot_sync_enabled'] : null, 'platform_sync_enabled' => ! empty( $stored['platform_sync_enabled'] ), ); } /** * Persist sync state without autoloading aggregate retry data. * * @param array $state State. * @return void */ function abtestkit_hosted_sync_save_state( array $state ) : void { update_option( ABTESTKIT_HOSTED_SYNC_OPTION, $state, false ); } /** * Add the dedicated bounded cron interval. * * @param array> $schedules Schedules. * @return array> */ function abtestkit_hosted_sync_cron_schedules( array $schedules ) : array { $schedules[ ABTESTKIT_HOSTED_SYNC_INTERVAL ] = array( 'interval' => 15 * MINUTE_IN_SECONDS, 'display' => __( 'Every 15 minutes (abtestkit hosted sync)', 'abtestkit' ), ); return $schedules; } add_filter( 'cron_schedules', 'abtestkit_hosted_sync_cron_schedules' ); /** * Schedule background syncing only for a valid, explicitly connected site. * The worker separately requires either the current platform agreement or an * enabled email preference before it transmits aggregate data. * * @return void */ function abtestkit_hosted_sync_schedule() : void { if ( null === abtestkit_hosted_get_connection() ) { return; } if ( ! wp_next_scheduled( ABTESTKIT_HOSTED_SYNC_HOOK ) ) { wp_schedule_event( time() + 5 * MINUTE_IN_SECONDS, ABTESTKIT_HOSTED_SYNC_INTERVAL, ABTESTKIT_HOSTED_SYNC_HOOK ); } } /** * Remove every hosted sync event. * * @return void */ function abtestkit_hosted_sync_unschedule() : void { wp_clear_scheduled_hook( ABTESTKIT_HOSTED_SYNC_HOOK ); } /** * Remove local retry/snapshot state without touching local tests. * * @return void */ function abtestkit_hosted_sync_remove_local_state() : void { abtestkit_hosted_sync_unschedule(); delete_option( ABTESTKIT_HOSTED_SYNC_OPTION ); delete_transient( 'abtestkit_hosted_sync_lock' ); } /** * Maintain only the local cron registration during ordinary requests. * * @return void */ function abtestkit_hosted_sync_maybe_schedule() : void { if ( null === abtestkit_hosted_get_connection() ) { abtestkit_hosted_sync_unschedule(); return; } abtestkit_hosted_sync_schedule(); } add_action( 'init', 'abtestkit_hosted_sync_maybe_schedule', 20 ); /** * Read the plugin version directly from the main file header. * * @return string */ function abtestkit_hosted_sync_plugin_version() : string { static $version = ''; if ( '' === $version && function_exists( 'get_file_data' ) ) { $data = get_file_data( dirname( __DIR__ ) . '/abtestkit.php', array( 'version' => 'Version' ), 'plugin' ); $version = isset( $data['version'] ) ? (string) $data['version'] : ''; } return 1 === preg_match( '/^[0-9]+\.[0-9]+\.[0-9]+(?:[-+][0-9A-Za-z.-]+)?$/D', $version ) ? $version : '0.0.0'; } /** * Project one local analytics summary into the strict hosted snapshot schema. * * @param array $test Test. * @param string $test_ref Opaque hosted test reference. * @param int $state_version Monotonic state version. * @param string $idempotency_key Stable retry key. * @param bool $include_display_name Whether email preferences permit the bounded test name. * @return array|WP_Error */ function abtestkit_hosted_sync_build_snapshot( array $test, string $test_ref, int $state_version, string $idempotency_key, bool $include_display_name ) { $now = time(); $stats = function_exists( 'abtestkit_pt_stats' ) ? abtestkit_pt_stats( $test ) : array(); $goal = function_exists( 'abtestkit_pt_normalize_goal_for_display' ) ? abtestkit_pt_normalize_goal_for_display( $test['goal'] ?? 'clicks' ) : sanitize_key( (string) ( $test['goal'] ?? 'clicks' ) ); $http_context = array(); if ( function_exists( 'abtestkit_pt_http_exclusions_bulk' ) ) { $http_contexts = abtestkit_pt_http_exclusions_bulk( array( $test ) ); $test_id = isset( $test['id'] ) ? (string) $test['id'] : ''; $http_context = isset( $http_contexts[ $test_id ] ) && is_array( $http_contexts[ $test_id ] ) ? $http_contexts[ $test_id ] : array(); } $health = function_exists( 'abtestkit_pt_health_summary' ) ? abtestkit_pt_health_summary( $test, $stats, $http_context ) : array(); $analytics_test = $test; $analytics_test['goal'] = $goal; $currency = function_exists( 'abtestkit_reporting_currency_settings' ) ? abtestkit_reporting_currency_settings() : array( 'code' => 'GBP', 'decimals' => 2 ); $summary = abtestkit_build_analytics_summary( $analytics_test, is_array( $stats ) ? $stats : array(), array(), is_array( $health ) ? $health : array(), array(), 'day', array( 'now' => $now, 'currency' => $currency ) ); $allowed_kinds = array( 'page', 'post', 'product', 'reusable_section', 'custom_code', 'custom_css', 'custom_html', 'custom_javascript' ); $kind = sanitize_key( (string) ( $summary['test']['kind'] ?? 'page' ) ); $kind = in_array( $kind, $allowed_kinds, true ) ? $kind : 'page'; $allowed_statuses = array( 'draft', 'running', 'paused', 'winner', 'complete' ); $status = sanitize_key( (string) ( $summary['test']['status'] ?? 'paused' ) ); $status = in_array( $status, $allowed_statuses, true ) ? $status : 'paused'; $allowed_goals = array( 'clicks', 'form', 'destination_url', 'scroll_depth', 'add_to_cart', 'purchase' ); $goal = in_array( $goal, $allowed_goals, true ) ? $goal : 'clicks'; $started_at = max( 0, min( $now, (int) ( $summary['test']['started_at'] ?? 0 ) ) ); $finished_at = max( 0, min( $now, (int) ( $summary['test']['finished_at'] ?? 0 ) ) ); if ( $finished_at > 0 && ( 0 === $started_at || $finished_at < $started_at ) ) { $finished_at = 0; } $variants = array(); foreach ( array( 'A', 'B' ) as $variant ) { $variant_summary = isset( $summary['variants'][ $variant ] ) && is_array( $summary['variants'][ $variant ] ) ? $summary['variants'][ $variant ] : array(); $impressions = max( 0, (int) ( $variant_summary['impressions'] ?? 0 ) ); $conversions = max( 0, (int) ( $variant_summary['conversions'] ?? 0 ) ); if ( $impressions > 1000000000000 || $conversions > $impressions ) { return new WP_Error( 'abtestkit_hosted_invalid_local_metrics' ); } $revenue = 'purchase' === $goal ? max( 0.0, (float) ( $variant_summary['revenue']['total'] ?? 0 ) ) : 0.0; if ( ! is_finite( $revenue ) || $revenue > 1000000000000000 ) { return new WP_Error( 'abtestkit_hosted_invalid_local_metrics' ); } $variants[ $variant ] = array( 'impressions' => $impressions, 'conversions' => $conversions, 'revenue' => $revenue, ); } $leader = strtoupper( (string) ( $summary['comparison']['observed_leader'] ?? '' ) ); $leader = in_array( $leader, array( 'A', 'B' ), true ) ? $leader : null; $winner = strtoupper( (string) ( $summary['comparison']['declared_winner'] ?? '' ) ); $winner = in_array( $winner, array( 'A', 'B' ), true ) ? $winner : null; $winner_basis = sanitize_key( (string) ( $summary['comparison']['winner_basis'] ?? '' ) ); $winner_basis = in_array( $winner_basis, array( 'statistical_decision', 'current_leader', 'manual_selection' ), true ) ? $winner_basis : null; if ( null === $winner ) { $winner_basis = null; } elseif ( null === $winner_basis ) { $winner_basis = 'winner' === $status ? 'statistical_decision' : 'manual_selection'; } $recommendation = sanitize_key( (string) ( $summary['recommendation']['code'] ?? 'collect_data' ) ); $allowed_recommendations = array( 'completed_result', 'statistical_winner', 'resolve_health_issue', 'start_when_ready', 'review_paused_test', 'collect_data', 'collect_both_variants', 'continue_collecting', 'review_tied_result', 'review_current_leader', 'continue_until_clearer_result', 'continue_until_decision' ); if ( ! in_array( $recommendation, $allowed_recommendations, true ) ) { $recommendation = 'collect_data'; } $warning_codes = array(); foreach ( array_slice( (array) ( $health['checks'] ?? array() ), 0, 25 ) as $check ) { if ( ! is_array( $check ) ) { continue; } $check_status = sanitize_key( (string) ( $check['original_status'] ?? $check['status'] ?? '' ) ); if ( ! in_array( $check_status, array( 'attention', 'broken' ), true ) ) { continue; } $warning_code = abtestkit_analytics_identifier( $check['id'] ?? 'health_warning', 'health_warning' ); if ( ! empty( $check['acknowledged'] ) && 'http_tracking' === $warning_code ) { $warning_code = 'http_tracking_acknowledged'; } if ( ! in_array( $warning_code, $warning_codes, true ) ) { $warning_codes[] = $warning_code; } } $health_status = sanitize_key( (string) ( $health['raw_status'] ?? $summary['health']['status'] ?? 'unknown' ) ); $health_status = in_array( $health_status, array( 'unknown', 'good', 'attention', 'broken' ), true ) ? $health_status : 'unknown'; $display_name = ''; if ( $include_display_name ) { $display_name = sanitize_text_field( (string) ( $test['title'] ?? '' ) ); if ( function_exists( 'mb_substr' ) ) { $display_name = mb_substr( $display_name, 0, 160, 'UTF-8' ); } else { $display_name_characters = preg_split( '//u', $display_name, -1, PREG_SPLIT_NO_EMPTY ); $display_name = is_array( $display_name_characters ) ? implode( '', array_slice( $display_name_characters, 0, 160 ) ) : ''; } } $currency_code = null; $currency_decimals = null; if ( 'purchase' === $goal ) { $currency_code = strtoupper( (string) ( $currency['code'] ?? '' ) ); $currency_decimals = (int) ( $currency['decimals'] ?? 2 ); if ( 1 !== preg_match( '/^[A-Z]{3}$/D', $currency_code ) || $currency_decimals < 0 || $currency_decimals > 8 ) { return new WP_Error( 'abtestkit_hosted_invalid_currency' ); } } return array( 'schema_version' => 1, 'state_version' => $state_version, 'idempotency_key' => $idempotency_key, 'generated_at' => $now, 'plugin_version' => abtestkit_hosted_sync_plugin_version(), 'analytics_schema_version' => 2, 'currency_code' => $currency_code, 'currency_decimals' => $currency_decimals, 'test' => array( 'ref' => $test_ref, 'display_name' => $display_name, 'kind' => $kind, 'status' => $status, 'goal' => $goal, 'started_at' => $started_at, 'finished_at' => $finished_at, ), 'sample' => array( 'state' => (string) ( $summary['sample']['state'] ?? 'no_data' ), 'impressions' => $variants['A']['impressions'] + $variants['B']['impressions'], 'conversions' => $variants['A']['conversions'] + $variants['B']['conversions'], ), 'variants' => $variants, 'result' => array( 'recommendation_code' => $recommendation, 'current_leader' => $leader, 'saved_winner' => $winner, 'winner_basis' => $winner_basis, ), 'health' => array( 'status' => $health_status, 'warning_codes' => $warning_codes, ), ); } /** * Hash only the meaningful snapshot state, excluding retry metadata. * * @param array $snapshot Snapshot. * @return string */ function abtestkit_hosted_sync_semantic_hash( array $snapshot ) : string { unset( $snapshot['state_version'], $snapshot['idempotency_key'], $snapshot['generated_at'] ); $encoded = wp_json_encode( $snapshot, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE ); return hash( 'sha256', is_string( $encoded ) ? $encoded : '' ); } /** * Save a bounded retry state after one failed remote operation. * * @param array $state State. * @param string $code Stable local error code. * @return void */ function abtestkit_hosted_sync_record_failure( array $state, string $code ) : void { $state['failure_count'] = min( 5, (int) $state['failure_count'] + 1 ); $state['last_error_code'] = sanitize_key( $code ); $state['paused'] = $state['failure_count'] >= 5; $delay = min( DAY_IN_SECONDS, 15 * MINUTE_IN_SECONDS * ( 2 ** max( 0, $state['failure_count'] - 1 ) ) ); $state['next_attempt_at'] = $state['paused'] ? 0 : time() + $delay; abtestkit_hosted_sync_save_state( $state ); } /** * Reconcile stable opaque references and remote deletions with local tests. * * @param array $state State. * @param array> $tests Tests. * @return array{0:array,1:array>}|WP_Error */ function abtestkit_hosted_sync_reconcile_records( array $state, array $tests ) { $by_key = array(); foreach ( $tests as $test ) { if ( ! is_array( $test ) || empty( $test['id'] ) ) { continue; } $local_key = hash( 'sha256', (string) $test['id'] ); $by_key[ $local_key ] = $test; if ( ! isset( $state['records'][ $local_key ] ) || ! is_array( $state['records'][ $local_key ] ) ) { if ( count( $state['records'] ) >= 5000 ) { return new WP_Error( 'abtestkit_hosted_too_many_tests' ); } $ref_record = abtestkit_connected_test_ref_record( (string) $test['id'] ); if ( is_wp_error( $ref_record ) ) { return $ref_record; } $state['records'][ $local_key ] = array( 'test_ref' => (string) $ref_record['test_ref'], 'state_version' => 0, 'confirmed_hash' => '', 'pending' => null, ); } } foreach ( array_keys( $state['records'] ) as $local_key ) { if ( isset( $by_key[ $local_key ] ) ) { continue; } $record = $state['records'][ $local_key ]; if ( is_array( $record ) && ! empty( $record['test_ref'] ) ) { $state['deletions'][] = (string) $record['test_ref']; } unset( $state['records'][ $local_key ] ); } $state['deletions'] = array_values( array_unique( array_slice( $state['deletions'], -5000 ) ) ); return array( $state, $by_key ); } /** * Run one bounded background sync cycle. * * @return void */ function abtestkit_hosted_run_sync() : void { $connection = abtestkit_hosted_get_connection(); if ( ! is_array( $connection ) ) { abtestkit_hosted_sync_unschedule(); return; } $state = abtestkit_hosted_sync_get_state(); if ( $state['paused'] || $state['next_attempt_at'] > time() || get_transient( 'abtestkit_hosted_sync_lock' ) ) { return; } set_transient( 'abtestkit_hosted_sync_lock', '1', 5 * MINUTE_IN_SECONDS ); try { if ( ! empty( $connection['pending_credential'] ) ) { abtestkit_hosted_rotate_connection_credential( $connection ); $connection = abtestkit_hosted_get_connection(); if ( ! is_array( $connection ) ) { abtestkit_hosted_sync_record_failure( $state, 'credential_recovery_failed' ); return; } } $site_ref = (string) $connection['site_ref']; $check_due = null === $state['snapshot_sync_enabled'] || $state['last_checked_at'] <= time() - ABTESTKIT_HOSTED_STATUS_REFRESH_INTERVAL; if ( $check_due ) { $status = abtestkit_hosted_service_request( 'GET', '/sites/' . $site_ref . '/status', array(), (string) $connection['credential'] ); if ( ! is_wp_error( $status ) && in_array( (int) $status['status'], array( 401, 404 ), true ) ) { abtestkit_hosted_remove_local_connection( $connection ); return; } if ( is_wp_error( $status ) || 200 !== $status['status'] || true !== ( $status['data']['connected'] ?? null ) || ! is_bool( $status['data']['snapshot_sync_enabled'] ?? null ) || ! hash_equals( $site_ref, (string) ( $status['data']['site_ref'] ?? '' ) ) ) { abtestkit_hosted_sync_record_failure( $state, 'status_check_failed' ); return; } $updated_connection = abtestkit_hosted_connection_with_status_agreement( $connection, $status['data'] ); if ( is_array( $updated_connection ) && $updated_connection !== $connection ) { if ( ! abtestkit_hosted_replace_local_connection( $connection, $updated_connection ) ) { return; } $connection = $updated_connection; } $state['last_checked_at'] = time(); $state['snapshot_sync_enabled'] = (bool) $status['data']['snapshot_sync_enabled']; } $state['platform_sync_enabled'] = abtestkit_connected_platform_can_create_or_launch(); $aggregate_sync_enabled = true === $state['snapshot_sync_enabled']; $tests = function_exists( 'abtestkit_pt_all' ) ? abtestkit_pt_all() : array(); $reconciled = abtestkit_hosted_sync_reconcile_records( $state, is_array( $tests ) ? array_values( $tests ) : array() ); if ( is_wp_error( $reconciled ) ) { abtestkit_hosted_sync_record_failure( $state, $reconciled->get_error_code() ); return; } list( $state, $tests_by_key ) = $reconciled; if ( ! $aggregate_sync_enabled ) { foreach ( $state['records'] as &$record ) { if ( ! is_array( $record ) || empty( $record['test_ref'] ) ) { continue; } if ( ! empty( $record['confirmed_hash'] ) || ! empty( $record['pending'] ) ) { $state['deletions'][] = (string) $record['test_ref']; } $record['confirmed_hash'] = ''; $record['pending'] = null; } unset( $record ); $state['deletions'] = array_values( array_unique( array_slice( $state['deletions'], -5000 ) ) ); } $operations = 0; while ( $operations < 5 && ! empty( $state['deletions'] ) ) { $test_ref = (string) $state['deletions'][0]; $response = abtestkit_hosted_service_request( 'DELETE', '/sites/' . $site_ref . '/tests/' . $test_ref, array(), (string) $connection['credential'] ); if ( ! is_wp_error( $response ) && in_array( (int) $response['status'], array( 401, 404 ), true ) ) { abtestkit_hosted_remove_local_connection( $connection ); return; } if ( is_wp_error( $response ) || 200 !== $response['status'] || array( 'deleted' => true ) !== $response['data'] ) { abtestkit_hosted_sync_record_failure( $state, 'snapshot_delete_failed' ); return; } array_shift( $state['deletions'] ); ++$operations; $state['last_success_at'] = time(); } if ( $aggregate_sync_enabled ) { foreach ( $tests_by_key as $local_key => $test ) { if ( $operations >= 5 || ! isset( $state['records'][ $local_key ] ) ) { break; } $record = $state['records'][ $local_key ]; $pending = isset( $record['pending'] ) && is_array( $record['pending'] ) ? $record['pending'] : null; if ( is_array( $pending ) && (int) ( $pending['created_at'] ?? 0 ) < time() - 23 * HOUR_IN_SECONDS ) { $pending = null; } if ( ! is_array( $pending ) ) { $idempotency = abtestkit_hosted_random_credential(); if ( is_wp_error( $idempotency ) ) { abtestkit_hosted_sync_record_failure( $state, 'random_failed' ); return; } $next_version = max( 0, (int) ( $record['state_version'] ?? 0 ) ) + 1; $snapshot = abtestkit_hosted_sync_build_snapshot( $test, (string) $record['test_ref'], $next_version, 'snapshot_' . $idempotency, $state['snapshot_sync_enabled'] ); if ( is_wp_error( $snapshot ) ) { abtestkit_hosted_sync_record_failure( $state, $snapshot->get_error_code() ); return; } $semantic_hash = abtestkit_hosted_sync_semantic_hash( $snapshot ); if ( hash_equals( (string) ( $record['confirmed_hash'] ?? '' ), $semantic_hash ) ) { continue; } $pending = array( 'snapshot' => $snapshot, 'semantic_hash' => $semantic_hash, 'created_at' => time(), ); $record['state_version'] = $next_version; $record['pending'] = $pending; $state['records'][ $local_key ] = $record; abtestkit_hosted_sync_save_state( $state ); } $snapshot = isset( $pending['snapshot'] ) && is_array( $pending['snapshot'] ) ? $pending['snapshot'] : array(); $response = abtestkit_hosted_service_request( 'PUT', '/sites/' . $site_ref . '/tests/' . (string) $record['test_ref'], $snapshot, (string) $connection['credential'] ); if ( ! is_wp_error( $response ) && in_array( (int) $response['status'], array( 401, 404 ), true ) ) { abtestkit_hosted_remove_local_connection( $connection ); return; } if ( is_wp_error( $response ) || ! in_array( $response['status'], array( 200, 201 ), true ) || true !== ( $response['data']['accepted'] ?? null ) || (int) ( $response['data']['state_version'] ?? 0 ) !== (int) ( $snapshot['state_version'] ?? -1 ) ) { abtestkit_hosted_sync_record_failure( $state, 'snapshot_write_failed' ); return; } $record['confirmed_hash'] = (string) $pending['semantic_hash']; $record['pending'] = null; $state['records'][ $local_key ] = $record; ++$operations; $state['last_success_at'] = time(); } } $state['failure_count'] = 0; $state['next_attempt_at'] = 0; $state['paused'] = false; $state['last_error_code'] = ''; abtestkit_hosted_sync_save_state( $state ); } finally { delete_transient( 'abtestkit_hosted_sync_lock' ); } } add_action( ABTESTKIT_HOSTED_SYNC_HOOK, 'abtestkit_hosted_run_sync' ); /** * Queue an immediate bounded retry from the administrator screen. * * @return void */ function abtestkit_hosted_sync_now() : void { if ( ! current_user_can( 'manage_options' ) ) { wp_die( esc_html__( 'You are not allowed to manage this connection.', 'abtestkit' ) ); } check_admin_referer( 'abtestkit_hosted_sync_now' ); if ( null === abtestkit_hosted_get_connection() ) { abtestkit_hosted_redirect( 'connection-failed' ); } $state = abtestkit_hosted_sync_get_state(); $state['failure_count'] = 0; $state['next_attempt_at'] = 0; $state['paused'] = false; $state['last_error_code'] = ''; $state['last_checked_at'] = 0; abtestkit_hosted_sync_save_state( $state ); if ( ! wp_next_scheduled( ABTESTKIT_HOSTED_SYNC_HOOK, array( 'manual' ) ) ) { wp_schedule_single_event( time() + 1, ABTESTKIT_HOSTED_SYNC_HOOK, array( 'manual' ) ); } abtestkit_hosted_redirect( 'sync-queued' ); } add_action( 'admin_post_abtestkit_hosted_sync_now', 'abtestkit_hosted_sync_now' ); /** * Render local-only connection health without exposing credentials or payloads. * * @return void */ function abtestkit_hosted_sync_render_status() : void { $state = abtestkit_hosted_sync_get_state(); echo '

' . esc_html__( 'Email update status', 'abtestkit' ) . '

'; if ( $state['paused'] ) { echo '

' . esc_html__( 'Email updates need attention.', 'abtestkit' ) . ' ' . esc_html__( 'Try “Check for updates”. Your local tests will keep working normally.', 'abtestkit' ) . '

'; } elseif ( false === $state['snapshot_sync_enabled'] ) { echo '

' . esc_html__( 'Email updates are paused. Choose at least one update in Manage email preferences to turn them on.', 'abtestkit' ) . '

'; } elseif ( true === $state['snapshot_sync_enabled'] ) { echo '

' . esc_html__( 'Email updates are active. Test changes are checked automatically.', 'abtestkit' ) . '

'; } else { echo '

' . esc_html__( 'Getting things ready. This usually takes a moment.', 'abtestkit' ) . '

'; } if ( $state['last_checked_at'] > 0 ) { /* translators: %s: Date and time of the last email update check. */ echo '

' . esc_html( sprintf( __( 'Last checked: %s', 'abtestkit' ), wp_date( get_option( 'date_format' ) . ' ' . get_option( 'time_format' ), $state['last_checked_at'] ) ) ) . '

'; } }