<?php /** * FIFA Kauss 2026 — theme functions. * * @package FifaKauss2026 */ if ( ! defined( 'ABSPATH' ) ) { exit; } define( 'FK26_VERSION', '1.0.0' ); function fk26_setup() { add_theme_support( 'title-tag' ); add_theme_support( 'html5', array( 'style', 'script' ) ); } add_action( 'after_setup_theme', 'fk26_setup' ); function fk26_enqueue_style() { // Theme stylesheet (mostly empty — actual styles inline in index.php). wp_enqueue_style( 'fk26-style', get_stylesheet_uri(), array(), FK26_VERSION ); } add_action( 'wp_enqueue_scripts', 'fk26_enqueue_style' ); // Remove emoji clutter from <head>. remove_action( 'wp_head', 'print_emoji_detection_script', 7 ); remove_action( 'wp_print_styles', 'print_emoji_styles' ); /** * Affiliate / partner redirects. * * Cloaked links — e.g. /go/spinbetter — are kept on this domain so we can * track clicks, swap destinations without redeploying, and avoid leaking the * raw affiliate URL into the page source. * * Add new destinations by extending the $map array below. */ function fk26_partner_redirects() { // Match the request path (ignoring query string, case-insensitive). $path = strtolower( trim( wp_parse_url( $_SERVER['REQUEST_URI'] ?? '', PHP_URL_PATH ) ?: '', '/' ) ); $map = array( 'go/spinbetter' => 'https://velzom.com/2Wvd?p=%2Fregistration%2F', 'go/klondaika' => 'https://record.ncepartners.com/_kcK9Bxm-iBdKliXjmQ3S3WNd7ZgqdRLk/59', 'go/fugu' => 'https://fuguway610.com/c592f0daa', ); if ( ! isset( $map[ $path ] ) ) { return; } $dest = $map[ $path ]; // Preserve any incoming query string so attribution / UTM parameters pass through. if ( ! empty( $_SERVER['QUERY_STRING'] ) ) { $dest .= ( strpos( $dest, '?' ) === false ? '?' : '&' ) . $_SERVER['QUERY_STRING']; } // 301 — permanent, search-engine-safe. Use 302 if you need to swap destinations often. wp_redirect( $dest, 301 ); exit; } add_action( 'template_redirect', 'fk26_partner_redirects' ); add_action( 'init', 'fk26_partner_redirects', 1 ); // also fire early in case template_redirect is too late /** * Kick & Win — AWeber email capture + cross-site "one prize per email". * Configure your AWeber credentials in wp-config.php (see the include header). */ require_once get_theme_file_path( 'kickwin-aweber.php' ); /** * Kick & Win — reward email via wp_mail() + SMTP (configure FKLV_SMTP_* in wp-config.php). */ require_once get_theme_file_path( 'kickwin-mailer.php' ); /** * LVBET Banner — admin-managed campaign pop-up (Settings → LVBET Banner). * Prints window.__LVBET_CFG for lvbet-popup.js. */ require_once get_theme_file_path( 'lvbet-banner.php' ); // Force the Kick & Win widget to Latvian on fifa-kauss.lv regardless of the // WordPress Site Language setting (which controls <html lang>). add_action( 'wp_head', function () { echo "<script>window.__KW_LANG='lv';</script>\n"; }, 1 ); // ================================================================ // FKLV Live Scores — appended by Hermes // ================================================================ // ================================================================ // FKLV Live Scores // ================================================================ if ( ! defined( 'ABSPATH' ) ) exit; define( 'FKLV_API_KEY', 'e740f809bamsh409cee5b9af2ea8p1875d0jsndedbf6a06db2' ); define( 'FKLV_API_HOST', 'flashscore4.p.rapidapi.com' ); define( 'FKLV_SCORES_OPTION', 'fklv_scores_cache' ); define( 'FKLV_FIXTURE_OPTION','fklv_fixture_ids' ); define( 'FKLV_CRON_HOOK', 'fklv_poll_scores' ); define( 'FKLV_TMPL_ID', 'lvUBR5F8' ); define( 'FKLV_STAGE_ID', 'SbLsX4y7' ); define( 'FKLV_SEASON_ID', '185' ); define( 'FKLV_WINDOW_START', 1781136000 ); define( 'FKLV_WINDOW_END', 1784649600 ); class FKLV_Live_Scores { public static function init() { add_action( 'rest_api_init', array( __CLASS__, 'register_routes' ) ); add_filter( 'cron_schedules', array( __CLASS__, 'add_cron_schedule' ) ); add_action( FKLV_CRON_HOOK, array( __CLASS__, 'poll_scores' ) ); add_action( 'init', array( __CLASS__, 'maybe_schedule_cron' ) ); } public static function add_cron_schedule( $schedules ) { $schedules['fklv_5min'] = array( 'interval' => 300, 'display' => 'Every 5 minutes (FKLV)', ); return $schedules; } public static function maybe_schedule_cron() { if ( ! wp_next_scheduled( FKLV_CRON_HOOK ) ) { wp_schedule_event( time(), 'fklv_5min', FKLV_CRON_HOOK ); } } public static function admin_or_token() { if ( current_user_can( 'manage_options' ) ) return true; $token = isset( $_REQUEST['token'] ) ? sanitize_text_field( $_REQUEST['token'] ) : ''; return $token === 'fklv_init_2026'; } public static function get_fixtures() { $cached = get_option( FKLV_FIXTURE_OPTION, null ); if ( is_array( $cached ) && count( $cached ) > 0 ) { return $cached; } return self::fetch_and_save_fixtures(); } private static function fetch_and_save_fixtures() { // Fetch both fixtures (upcoming) AND results (started/finished) — matches move between them $endpoints = array( 'fixtures', 'results' ); $all = array(); $seen = array(); foreach ( $endpoints as $ep ) { $url = 'https://' . FKLV_API_HOST . '/api/flashscore/v2/tournaments/' . $ep . '?tournament_template_id=' . FKLV_TMPL_ID . '&tournament_stage_id=' . FKLV_STAGE_ID . '&season_id=' . FKLV_SEASON_ID; $resp = wp_remote_get( $url, array( 'timeout' => 15, 'headers' => array( 'x-rapidapi-key' => FKLV_API_KEY, 'x-rapidapi-host' => FKLV_API_HOST, ), ) ); if ( is_wp_error( $resp ) || 200 !== wp_remote_retrieve_response_code( $resp ) ) continue; $raw = json_decode( wp_remote_retrieve_body( $resp ), true ); if ( ! is_array( $raw ) ) continue; foreach ( $raw as $m ) { if ( empty( $m['match_id'] ) || isset( $seen[ $m['match_id'] ] ) ) continue; $seen[ $m['match_id'] ] = true; $all[] = array( 'match_id' => $m['match_id'], 'home' => isset( $m['home_team']['name'] ) ? $m['home_team']['name'] : '', 'away' => isset( $m['away_team']['name'] ) ? $m['away_team']['name'] : '', 'timestamp' => (int) ( isset( $m['timestamp'] ) ? $m['timestamp'] : 0 ), ); } } if ( $all ) { update_option( FKLV_FIXTURE_OPTION, $all, false ); } return $all; } private static function fetch_and_save_fixtures_UNUSED() { $url = 'https://' . FKLV_API_HOST . '/api/flashscore/v2/tournaments/fixtures' . '?tournament_template_id=' . FKLV_TMPL_ID . '&tournament_stage_id=' . FKLV_STAGE_ID . '&season_id=' . FKLV_SEASON_ID; $resp = wp_remote_get( $url, array( 'timeout' => 15, 'headers' => array( 'x-rapidapi-key' => FKLV_API_KEY, 'x-rapidapi-host' => FKLV_API_HOST, ), ) ); if ( is_wp_error( $resp ) || 200 !== wp_remote_retrieve_response_code( $resp ) ) { return array(); } $raw = json_decode( wp_remote_retrieve_body( $resp ), true ); if ( ! is_array( $raw ) ) return array(); $fixtures = array(); foreach ( $raw as $m ) { if ( empty( $m['match_id'] ) ) continue; $fixtures[] = array( 'match_id' => $m['match_id'], 'home' => isset( $m['home_team']['name'] ) ? $m['home_team']['name'] : '', 'away' => isset( $m['away_team']['name'] ) ? $m['away_team']['name'] : '', 'timestamp' => (int) ( isset( $m['timestamp'] ) ? $m['timestamp'] : 0 ), ); } if ( $fixtures ) { update_option( FKLV_FIXTURE_OPTION, $fixtures, false ); } return $fixtures; } private static function fetch_live_wc_match_ids() { $url = 'https://' . FKLV_API_HOST . '/api/flashscore/v2/matches/live?sport_id=1'; $resp = wp_remote_get( $url, array( 'timeout' => 10, 'headers' => array( 'x-rapidapi-key' => FKLV_API_KEY, 'x-rapidapi-host' => FKLV_API_HOST, ), ) ); if ( is_wp_error( $resp ) || 200 !== wp_remote_retrieve_response_code( $resp ) ) return array(); $tournaments = json_decode( wp_remote_retrieve_body( $resp ), true ); if ( ! is_array( $tournaments ) ) return array(); $out = array(); foreach ( $tournaments as $t ) { if ( false === strpos( $t['tournament_url'], '/world/' ) ) continue; foreach ( $t['matches'] as $m ) { $out[] = array( 'match_id' => $m['match_id'], 'home' => isset( $m['home_team']['name'] ) ? $m['home_team']['name'] : '', 'away' => isset( $m['away_team']['name'] ) ? $m['away_team']['name'] : '', 'timestamp' => isset( $m['timestamp'] ) ? (int) $m['timestamp'] : 0, ); } } return $out; } public static function poll_scores() { $now = time(); if ( $now < FKLV_WINDOW_START || $now > FKLV_WINDOW_END ) { return; } $fixtures = self::get_fixtures(); if ( empty( $fixtures ) ) return; // Also pick up currently-live World Cup matches (they drop from fixtures once started) $live_ids = self::fetch_live_wc_match_ids(); $fixture_ids = array(); foreach ( $fixtures as $f ) $fixture_ids[ $f['match_id'] ] = true; foreach ( $live_ids as $live ) { if ( ! isset( $fixture_ids[ $live['match_id'] ] ) ) { $fixtures[] = $live; $fixture_ids[ $live['match_id'] ] = true; } } $scores = get_option( FKLV_SCORES_OPTION, array() ); if ( ! is_array( $scores ) ) $scores = array(); foreach ( $fixtures as $fix ) { $mid = $fix['match_id']; $ts = (int) $fix['timestamp']; if ( $now < $ts ) continue; if ( ! empty( $scores[ $mid ]['finished'] ) ) continue; $detail = self::fetch_match_details( $mid ); if ( null === $detail ) continue; $status = isset( $detail['match_status'] ) ? $detail['match_status'] : array(); $sc = isset( $detail['scores'] ) ? $detail['scores'] : array(); $h = isset( $sc['home_total'] ) ? $sc['home_total'] : ( isset( $sc['home'] ) ? $sc['home'] : null ); $a = isset( $sc['away_total'] ) ? $sc['away_total'] : ( isset( $sc['away'] ) ? $sc['away'] : null ); $is_finished = ! empty( $status['is_finished'] ); $is_in_progress = ! empty( $status['is_in_progress'] ); $is_started = ! empty( $status['is_started'] ); if ( $is_finished ) { $status_str = 'finished'; } elseif ( $is_in_progress || $is_started ) { $status_str = 'live'; } else { continue; } $scores[ $mid ] = array( 'home' => $fix['home'], 'away' => $fix['away'], 'h' => null !== $h ? (int) $h : null, 'a' => null !== $a ? (int) $a : null, 'status' => $status_str, 'finished' => $is_finished, 'ts' => $ts, ); } // Second pass: re-check any cached 'live' match IDs that were NOT in the // fixture/live lists above — catches matches that just finished and // disappeared from /matches/live before we could mark them done. foreach ( $scores as $mid => $cached ) { if ( empty( $cached['status'] ) || $cached['status'] !== 'live' ) continue; if ( ! empty( $cached['finished'] ) ) continue; // Already processed in the main loop above — skip if ( isset( $fixture_ids[ $mid ] ) ) continue; $detail = self::fetch_match_details( $mid ); if ( null === $detail ) continue; $dstatus = isset( $detail['match_status'] ) ? $detail['match_status'] : array(); $dsc = isset( $detail['scores'] ) ? $detail['scores'] : array(); $is_fin = ! empty( $dstatus['is_finished'] ); $is_prog = ! empty( $dstatus['is_in_progress'] ) || ! empty( $dstatus['is_started'] ); $h2 = isset( $dsc['home_total'] ) ? $dsc['home_total'] : ( isset( $dsc['home'] ) ? $dsc['home'] : null ); $a2 = isset( $dsc['away_total'] ) ? $dsc['away_total'] : ( isset( $dsc['away'] ) ? $dsc['away'] : null ); if ( $is_fin ) { $scores[ $mid ]['h'] = null !== $h2 ? (int) $h2 : $cached['h']; $scores[ $mid ]['a'] = null !== $a2 ? (int) $a2 : $cached['a']; $scores[ $mid ]['status'] = 'finished'; $scores[ $mid ]['finished'] = true; } elseif ( $is_prog && null !== $h2 && null !== $a2 ) { $scores[ $mid ]['h'] = (int) $h2; $scores[ $mid ]['a'] = (int) $a2; } } update_option( FKLV_SCORES_OPTION, $scores, false ); } private static function fetch_match_details( $match_id ) { $url = 'https://' . FKLV_API_HOST . '/api/flashscore/v2/matches/details?match_id=' . urlencode( $match_id ); $resp = wp_remote_get( $url, array( 'timeout' => 10, 'headers' => array( 'x-rapidapi-key' => FKLV_API_KEY, 'x-rapidapi-host' => FKLV_API_HOST, ), ) ); if ( is_wp_error( $resp ) || 200 !== wp_remote_retrieve_response_code( $resp ) ) { return null; } $data = json_decode( wp_remote_retrieve_body( $resp ), true ); return is_array( $data ) ? $data : null; } public static function register_routes() { register_rest_route( 'fklv/v1', '/scores', array( 'methods' => 'GET', 'callback' => array( __CLASS__, 'rest_scores' ), 'permission_callback' => '__return_true', ) ); register_rest_route( 'fklv/v1', '/refresh-fixtures', array( 'methods' => 'GET, POST', 'callback' => array( __CLASS__, 'rest_refresh_fixtures' ), 'permission_callback' => array( __CLASS__, 'admin_or_token' ), ) ); register_rest_route( 'fklv/v1', '/poll-now', array( 'methods' => 'GET, POST', 'callback' => array( __CLASS__, 'rest_poll_now' ), 'permission_callback' => array( __CLASS__, 'admin_or_token' ), ) ); register_rest_route( 'fklv/v1', '/debug', array( 'methods' => 'GET', 'callback' => array( __CLASS__, 'rest_debug' ), 'permission_callback' => array( __CLASS__, 'admin_or_token' ), ) ); register_rest_route( 'fklv/v1', '/clear-scores', array( 'methods' => 'GET', 'callback' => array( __CLASS__, 'rest_clear_scores' ), 'permission_callback' => array( __CLASS__, 'admin_or_token' ), ) ); register_rest_route( 'fklv/v1', '/sync-bracket', array( 'methods' => 'GET', 'callback' => array( __CLASS__, 'rest_sync_bracket' ), 'permission_callback' => array( __CLASS__, 'admin_or_token' ), ) ); } public static function rest_clear_scores( WP_REST_Request $req ) { delete_option( FKLV_SCORES_OPTION ); return new WP_REST_Response( array( 'ok' => true ), 200 ); } // English → Latvian team name map (Flashscore → data.js) private static $lv_map = array( 'Mexico' => 'Meksika', 'South Africa' => 'Dienvidāfrika', 'South Korea' => 'Dienvidkoreja', 'Czech Republic' => 'Čehija', 'Canada' => 'Kanāda', 'Bosnia and Herzegovina' => 'Bosnija', 'Bosnia & Herzegovina' => 'Bosnija', 'Qatar' => 'Katara', 'Switzerland' => 'Šveice', 'Brazil' => 'Brazīlija', 'Morocco' => 'Maroka', 'Haiti' => 'Haiti', 'Scotland' => 'Skotija', 'USA' => 'ASV', 'United States' => 'ASV', 'Paraguay' => 'Paragvaja', 'Australia' => 'Austrālija', 'Turkey' => 'Turcija', 'Germany' => 'Vācija', 'Curacao' => 'Kirasao', 'Curaçao' => 'Kirasao', 'Ivory Coast' => 'Kotdivuāra', "Cote d'Ivoire" => 'Kotdivuāra', 'Ecuador' => 'Ekvadora', 'Netherlands' => 'Nīderlande', 'Japan' => 'Japāna', 'Sweden' => 'Zviedrija', 'Tunisia' => 'Tunisija', 'Spain' => 'Spānija', 'Cape Verde' => 'Kaboverde', 'Saudi Arabia' => 'Saūda Arābija', 'Uruguay' => 'Urugvaja', 'Belgium' => 'Beļģija', 'Egypt' => 'Ēģipte', 'Iran' => 'Irāna', 'New Zealand' => 'Jaunzēlande', 'France' => 'Francija', 'Senegal' => 'Senegāla', 'Iraq' => 'Irāka', 'Norway' => 'Norvēģija', 'Argentina' => 'Argentīna', 'Algeria' => 'Alžīrija', 'Austria' => 'Austrija', 'Jordan' => 'Jordānija', 'Portugal' => 'Portugāle', 'DR Congo' => 'Kongo DR', 'D.R. Congo' => 'Kongo DR', 'Uzbekistan' => 'Uzbekistāna', 'Colombia' => 'Kolumbija', 'England' => 'Anglija', 'Croatia' => 'Horvātija', 'Ghana' => 'Gana', 'Panama' => 'Panama', ); private static function to_lv( $name ) { return isset( self::$lv_map[ $name ] ) ? self::$lv_map[ $name ] : $name; } public static function rest_scores( WP_REST_Request $req ) { $scores = get_option( FKLV_SCORES_OPTION, array() ); if ( ! is_array( $scores ) ) $scores = array(); $out = array(); foreach ( $scores as $mid => $s ) { if ( null === $s['h'] || null === $s['a'] ) continue; $out[] = array( 'match_id' => $mid, 'home' => self::to_lv( $s['home'] ), 'away' => self::to_lv( $s['away'] ), 'home_score' => $s['h'], 'away_score' => $s['a'], 'status' => $s['status'], 'timestamp' => $s['ts'], ); } return rest_ensure_response( array( 'matches' => $out, 'updated_at' => time(), 'status' => 'ok', 'count' => count( $out ), ) ); } public static function rest_refresh_fixtures( WP_REST_Request $req ) { delete_option( FKLV_FIXTURE_OPTION ); $fixtures = self::fetch_and_save_fixtures(); return rest_ensure_response( array( 'ok' => true, 'count' => count( $fixtures ) ) ); } public static function rest_poll_now( WP_REST_Request $req ) { self::poll_scores(); $scores = get_option( FKLV_SCORES_OPTION, array() ); return rest_ensure_response( array( 'ok' => true, 'count' => count( $scores ) ) ); } public static function rest_debug( WP_REST_Request $req ) { $now = time(); $fixtures = get_option( FKLV_FIXTURE_OPTION, array() ); $result = array( 'now' => $now, 'window_start' => FKLV_WINDOW_START, 'in_window' => ( $now >= FKLV_WINDOW_START && $now <= FKLV_WINDOW_END ), 'fixtures_count' => count( $fixtures ), 'first_fixture' => isset( $fixtures[0] ) ? $fixtures[0] : null, ); $url = 'https://' . FKLV_API_HOST . '/api/flashscore/v2/matches/details?match_id=h4EoUB7T'; $resp = wp_remote_get( $url, array( 'timeout' => 10, 'headers' => array( 'x-rapidapi-key' => FKLV_API_KEY, 'x-rapidapi-host' => FKLV_API_HOST, ), ) ); if ( is_wp_error( $resp ) ) { $result['api_error'] = $resp->get_error_message(); } else { $result['api_code'] = wp_remote_retrieve_response_code( $resp ); $result['api_body'] = substr( wp_remote_retrieve_body( $resp ), 0, 300 ); } return rest_ensure_response( $result ); } // ── Bracket sync endpoint ───────────────────────────────────────────── // Called by Hermes cron every 2h. Reads finished KO match scores, // determines winners, and updates KO_MATCHES_RAW in index.php. // Also adds scores to r32+ entries that already have scores in cache. // Returns {ok, changes, slots_updated, bracket}. // Bracket: which r32 source slots feed each downstream slot. // home = winner of first source, away = winner of second source. // 'K-103' uses losers of K-101/K-102 instead of winners (3rd place). private static $bracket = array( 'K-89' => array( 'h_src' => 'K-73', 'a_src' => 'K-76' ), 'K-90' => array( 'h_src' => 'K-75', 'a_src' => 'K-78' ), 'K-91' => array( 'h_src' => 'K-74', 'a_src' => 'K-77' ), 'K-92' => array( 'h_src' => 'K-79', 'a_src' => 'K-80' ), 'K-93' => array( 'h_src' => 'K-81', 'a_src' => 'K-82' ), 'K-94' => array( 'h_src' => 'K-83', 'a_src' => 'K-84' ), 'K-95' => array( 'h_src' => 'K-87', 'a_src' => 'K-86' ), 'K-96' => array( 'h_src' => 'K-85', 'a_src' => 'K-88' ), 'K-97' => array( 'h_src' => 'K-89', 'a_src' => 'K-90' ), 'K-98' => array( 'h_src' => 'K-91', 'a_src' => 'K-92' ), 'K-99' => array( 'h_src' => 'K-93', 'a_src' => 'K-94' ), 'K-100' => array( 'h_src' => 'K-95', 'a_src' => 'K-96' ), 'K-101' => array( 'h_src' => 'K-97', 'a_src' => 'K-99' ), 'K-102' => array( 'h_src' => 'K-98', 'a_src' => 'K-100' ), 'K-103' => array( 'h_src' => 'K-101_LOSER', 'a_src' => 'K-102_LOSER' ), 'K-104' => array( 'h_src' => 'K-101', 'a_src' => 'K-102' ), ); // Placeholder names used in index.php — if a slot has one of these, // it needs to be filled in (or already has a real team name). private static function is_placeholder( $name ) { if ( empty( $name ) ) return true; $ph_prefixes = array( '1/8 Nr.', '1/4 Nr.', 'Pusfināls', 'Par 3. vietu', 'Fināls' ); foreach ( $ph_prefixes as $p ) { if ( strpos( $name, $p ) !== false ) return true; } return false; } // Determine winner of a finished KO match from /scores cache. // Returns Latvian team name, or null if not finished / data missing. // Pass $want_loser=true to get the loser instead (for 3rd place match). private static function get_ko_winner( $k_slot, $scores_by_lv, $want_loser = false ) { // scores_by_lv is keyed by "home_lv::away_lv" -> {h, a, home, away} // We match by slot name which maps to team names in KO_MATCHES_RAW. // Easier: use the slot's known home/away teams to look up score. // We'll pass the KO slot data separately. return null; // placeholder — real impl in rest_sync_bracket } public static function rest_sync_bracket( WP_REST_Request $req ) { $changes = array(); $bracket = array(); $dry_run = ( $req->get_param( 'dry' ) === '1' ); // 1. Read the scores cache (Latvian names — output of rest_scores) $scores_raw = get_option( FKLV_SCORES_OPTION, array() ); if ( ! is_array( $scores_raw ) ) $scores_raw = array(); // Build lookup: "home_lv::away_lv" → score data // Only KO-stage matches (timestamp >= 1782586800 = 2026-06-27 19:00 UTC / K-73 start). // This prevents group-stage scores from the same team pairing from being mistaken // for a KO result when the KO match hasn't been played yet. $ko_cutoff = 1782586800; // 2026-06-27 19:00 UTC — K-73 kickoff (first r32 match) $lv_score = array(); foreach ( $scores_raw as $mid => $s ) { if ( empty( $s['home'] ) || empty( $s['away'] ) ) continue; $ts = isset( $s['ts'] ) ? (int) $s['ts'] : 0; if ( $ts < $ko_cutoff ) continue; // skip group-stage matches $h_lv = self::to_lv( $s['home'] ); $a_lv = self::to_lv( $s['away'] ); $entry = array( 'home' => $h_lv, 'away' => $a_lv, 'h' => isset( $s['h'] ) ? $s['h'] : null, 'a' => isset( $s['a'] ) ? $s['a'] : null, 'status' => isset( $s['status'] ) ? $s['status'] : '', 'finished' => ! empty( $s['finished'] ), 'ts' => $ts, ); // Use BOTH orderings for lookup (API may store as home/away differently than index.php) // But prefer: index home::index away ordering. Both are stored for safety. $lv_score[ $h_lv . '::' . $a_lv ] = $entry; $lv_score[ $a_lv . '::' . $h_lv ] = $entry; // reverse lookup } // 2. Read index.php and parse current KO_MATCHES_RAW $theme_dir = get_theme_root() . '/fifa-kauss-2026'; $idx_path = $theme_dir . '/index.php'; if ( ! file_exists( $idx_path ) ) { return rest_ensure_response( array( 'ok' => false, 'error' => 'index.php not found' ) ); } $idx_content = file_get_contents( $idx_path ); if ( false === $idx_content ) { return rest_ensure_response( array( 'ok' => false, 'error' => 'Cannot read index.php' ) ); } // Extract KO_MATCHES_RAW block $ko_start = strpos( $idx_content, 'KO_MATCHES_RAW = [' ); if ( false === $ko_start ) { return rest_ensure_response( array( 'ok' => false, 'error' => 'KO_MATCHES_RAW not found in index.php' ) ); } // Find matching closing bracket $depth = 0; $ko_arr_start = $ko_start + strlen( 'KO_MATCHES_RAW = ' ); $ko_arr_end = $ko_arr_start; for ( $i = $ko_arr_start; $i < min( $ko_arr_start + 100000, strlen( $idx_content ) ); $i++ ) { $c = $idx_content[ $i ]; if ( $c === '[' ) $depth++; elseif ( $c === ']' ) { $depth--; if ( $depth === 0 ) { $ko_arr_end = $i + 1; break; } } } $ko_raw = substr( $idx_content, $ko_arr_start, $ko_arr_end - $ko_arr_start ); // Parse each match entry: extract id, home, away, score if present $ko_entries = array(); // Parse each entry: pattern handles optional score:{} nested braces preg_match_all( '/\{[^{]*id:"(K-\d+)"[^}]*(?:\{[^}]*\})?[^}]*\}/s', $ko_raw, $entry_matches, PREG_OFFSET_CAPTURE ); foreach ( $entry_matches[0] as $idx_pair ) { $entry_text = $idx_pair[0]; preg_match( '/id:"(K-\d+)"/', $entry_text, $id_m ); if ( ! $id_m ) continue; $id = $id_m[1]; $home = ''; $away = ''; if ( preg_match( '/home:"([^"]*)"/', $entry_text, $hm ) ) $home = $hm[1]; if ( preg_match( '/away:"([^"]*)"/', $entry_text, $am ) ) $away = $am[1]; $ko_entries[ $id ] = array( 'home' => $home, 'away' => $away, 'raw' => $entry_text, 'has_score' => (bool) preg_match( '/score:\{/', $entry_text ), ); } // 3. Determine winners from finished matches // Winner map: K-slot → winning Latvian team name $winners = array(); $losers = array(); foreach ( $ko_entries as $id => $entry ) { if ( empty( $entry['home'] ) || empty( $entry['away'] ) ) continue; $key = $entry['home'] . '::' . $entry['away']; $score_d = isset( $lv_score[ $key ] ) ? $lv_score[ $key ] : null; if ( ! $score_d || ! $score_d['finished'] ) continue; // Determine score orientation: API cache may store teams in different order than index.php $same_order = ( $score_d['home'] === $entry['home'] ); $h = $same_order ? (int) $score_d['h'] : (int) $score_d['a']; $a = $same_order ? (int) $score_d['a'] : (int) $score_d['h']; if ( $h > $a ) { $winners[ $id ] = $entry['home']; $losers[ $id ] = $entry['away']; } elseif ( $a > $h ) { $winners[ $id ] = $entry['away']; $losers[ $id ] = $entry['home']; } // Draw: in knockout, goes to extra time/penalties — winner still determined // but score cache only shows FT score. We'll leave a draw unresolved // (a poll-now will eventually have the final result via is_finished). } // 4. Build updated index.php content $new_idx = $idx_content; foreach ( self::$bracket as $slot => $srcs ) { if ( ! isset( $ko_entries[ $slot ] ) ) continue; $current = $ko_entries[ $slot ]; // Resolve new home team $new_home = null; $h_src = $srcs['h_src']; if ( strpos( $h_src, '_LOSER' ) !== false ) { $src_id = str_replace( '_LOSER', '', $h_src ); $new_home = isset( $losers[ $src_id ] ) ? $losers[ $src_id ] : null; } else { $new_home = isset( $winners[ $h_src ] ) ? $winners[ $h_src ] : null; } // Resolve new away team $new_away = null; $a_src = $srcs['a_src']; if ( strpos( $a_src, '_LOSER' ) !== false ) { $src_id = str_replace( '_LOSER', '', $a_src ); $new_away = isset( $losers[ $src_id ] ) ? $losers[ $src_id ] : null; } else { $new_away = isset( $winners[ $a_src ] ) ? $winners[ $a_src ] : null; } // Record bracket status $bracket[ $slot ] = array( 'home_src' => $h_src, 'away_src' => $a_src, 'home_winner' => $new_home, 'away_winner' => $new_away, 'current_home' => $current['home'], 'current_away' => $current['away'], ); // Check what needs updating $needs_home = $new_home && self::is_placeholder( $current['home'] ) && $new_home !== $current['home']; $needs_away = $new_away && self::is_placeholder( $current['away'] ) && $new_away !== $current['away']; // Also: if current has a real team but it's wrong (edge case after data correction) // Skip correction if the slot already has a score — trust manually-set data. if ( ! $needs_home && $new_home && ! self::is_placeholder( $current['home'] ) && $current['home'] !== $new_home && empty( $current['has_score'] ) ) { $needs_home = true; $changes[] = 'CORRECTION: ' . $slot . ' home was "' . $current['home'] . '", correcting to "' . $new_home . '"'; } if ( ! $needs_away && $new_away && ! self::is_placeholder( $current['away'] ) && $current['away'] !== $new_away && empty( $current['has_score'] ) ) { $needs_away = true; $changes[] = 'CORRECTION: ' . $slot . ' away was "' . $current['away'] . '", correcting to "' . $new_away . '"'; } if ( ! $needs_home && ! $needs_away ) continue; // Build replacement raw entry $old_entry = $current['raw']; $new_entry = $old_entry; if ( $needs_home ) { // Replace home:"PLACEHOLDER" with home:"new_home" $new_entry = preg_replace( '/home:"[^"]*"/', 'home:"' . addslashes( $new_home ) . '"', $new_entry, 1 ); $changes[] = $slot . ': home ' . $current['home'] . ' → ' . $new_home; } if ( $needs_away ) { $new_entry = preg_replace( '/away:"[^"]*"/', 'away:"' . addslashes( $new_away ) . '"', $new_entry, 1 ); $changes[] = $slot . ': away ' . $current['away'] . ' → ' . $new_away; } if ( $new_entry !== $old_entry ) { $new_idx = str_replace( $old_entry, $new_entry, $new_idx ); } } // 5. Also update scores in r32 entries that are missing score:{} field // (K-77 through K-88 were added without scores; add them once finished) foreach ( $ko_entries as $id => $entry ) { // Only for r32 matches without a score yet if ( preg_match( '/score:\{/', $entry['raw'] ) ) continue; // already has score if ( empty( $entry['home'] ) || empty( $entry['away'] ) ) continue; $key = $entry['home'] . '::' . $entry['away']; $sd = isset( $lv_score[ $key ] ) ? $lv_score[ $key ] : null; if ( ! $sd || ! $sd['finished'] ) continue; // Determine which score value belongs to which side. // sd['home'] is the API-cache home (may differ from index.php home ordering). // If sd['home'] matches entry['home'], scores are in same order; otherwise swap. $same_order = ( $sd['home'] === $entry['home'] ); $h_score = $same_order ? (int) $sd['h'] : (int) $sd['a']; $a_score = $same_order ? (int) $sd['a'] : (int) $sd['h']; $old_r = $entry['raw']; $new_r = preg_replace( '/(away:"[^"]*")/', '$1, score:{h:' . $h_score . ',a:' . $a_score . '}', $old_r, 1 ); if ( $new_r !== $old_r ) { $new_idx = str_replace( $old_r, $new_r, $new_idx ); $changes[] = $id . ': score added ' . $entry['home'] . ' ' . $h_score . '-' . $a_score . ' ' . $entry['away']; } } // 6. Write back if changed $slots_updated = count( $changes ); if ( $slots_updated > 0 && ! $dry_run ) { file_put_contents( $idx_path, $new_idx ); // Also trigger fixture refresh to pick up new KO matches delete_option( FKLV_FIXTURE_OPTION ); self::fetch_and_save_fixtures(); } update_option( 'fklv_bracket_last_sync', time(), false ); return rest_ensure_response( array( 'ok' => true, 'dry_run' => $dry_run, 'slots_updated' => $slots_updated, 'changes' => $changes, 'bracket' => $bracket, ) ); } } FKLV_Live_Scores::init(); // AGENTS.md bootstrap — runs once then removes itself add_action('init', function() { $target = get_template_directory() . '/AGENTS.md'; if (!file_exists($target)) { file_put_contents($target, base64_decode('IyBXb3JkUHJlc3MgVGhlbWUgQWdlbnQgUnVsZXMgKFNlcnBhKQoKUnVsZXNldCBmb3IgQUkgYWdlbnRzIChDbGF1ZGUgQ29kZSwgRHJvaWQsIEN1cnNvciwgZXRjLikgd29ya2luZyBvbiBTZXJwYSBXb3JkUHJlc3MgdGhlbWUgcHJvamVjdHMuCgotLS0KCiMjIFJ1bGUgIzEg4oCUIFNhZmV0eSBmaXJzdAoKLSBObyBkZXN0cnVjdGl2ZSBvcHMgd2l0aG91dCBleHBsaWNpdCBhcHByb3ZhbDogREIgZHJvcHMvdHJ1bmNhdGUvcmVzZXQgKGB3cCBkYiByZXNldGAsIGBEUk9QYCwgYFRSVU5DQVRFYCksIGJ1bGsgZmlsZSBkZWxldGVzLCBgZ2l0IGNsZWFuYCwgZm9yY2UgcHVzaGVzLgotIE5ldmVyIGNvbW1pdCBzZWNyZXRzLiBOZXZlciBleHBvc2Uga2V5cyBpbiBjb2RlLCBsb2dzLCBvciBhbnl0aGluZyBzZW50IHRvIHRoZSBicm93c2VyLgotIFZlcmlmeSB0aGUgdGFyZ2V0IGJlZm9yZSBhY3Rpbmcgb24gcHJvZC9saXZlIGRhdGEuIFdoZW4gdW5zdXJlLCBzdG9wIGFuZCBhc2suCgotLS0KCiMjIFJ1bGUgIzIg4oCUIEFQSSAvIGVuZHBvaW50cyDigJQgdGhlIHRoZW1lIChjbGllbnQpIG93bnMgYWxsIGNvbm5lY3Rpb25zCgotIFRoZSB0aGVtZSBoYW5kbGVzIEFMTCBleHRlcm5hbCBBUEkgY29ubmVjdGlvbnMgYW5kIGZldGNoZXMgaXRzZWxmLgotIE5vIG91dGVyL2V4dGVybmFsIGFnZW50IG1ha2VzIHRob3NlIGNhbGxzIG9uIHRoZSB0aGVtZSdzIGJlaGFsZi4KLSBEbyBleHRlcm5hbCByZXF1ZXN0cyBzZXJ2ZXItc2lkZSB2aWEgdGhlIFdQIEhUVFAgQVBJOiBgd3BfcmVtb3RlX2dldCgpYCAvIGB3cF9yZW1vdGVfcG9zdCgpYC4gTmV2ZXIgbGVhayBjcmVkZW50aWFscyB0byB0aGUgYnJvd3Nlci4KLSBBbnkgQUpBWCAvIFJFU1QgZW5kcG9pbnQgdGhlIHRoZW1lIGV4cG9zZXMgbXVzdCBiZSBub25jZS1wcm90ZWN0ZWQgYW5kIGNhcGFiaWxpdHktY2hlY2tlZC4KCi0tLQoKIyMgUnVsZSAjMyDigJQgQ29uZmlnICYgc2VjcmV0cwoKQWxsIGtleXMsIGVuZHBvaW50cywgYW5kIGNyZWRlbnRpYWxzIGNvbWUgZnJvbSBgd3AtY29uZmlnLnBocGAgY29uc3RhbnRzIC8gZW52IHZhcnMuIE5vIGhhcmRjb2RlZCBVUkxzLCB0b2tlbnMsIG9yIEFQSSBrZXlzIGFueXdoZXJlIGluIHRoZW1lIGNvZGUuCgpgYGBwaHAKLy8gd3AtY29uZmlnLnBocApkZWZpbmUoJ0FQSV9LRVknLCBnZXRlbnYoJ0FQSV9LRVknKSk7CmRlZmluZSgnQVBJX1VSTCcsIGdldGVudignQVBJX1VSTCcpKTsKCi8vIHRoZW1lCiRrZXkgPSBkZWZpbmVkKCdBUElfS0VZJykgPyBBUElfS0VZIDogJyc7CmBgYAoKLS0tCgojIyBSdWxlICM0IOKAlCBXb3JkUHJlc3Mgc2VjdXJpdHkgYmFzaWNzIChkbykKCi0gRXNjYXBlIG9uIG91dHB1dCwgZXNjYXBlIGxhdGU6IGBlc2NfaHRtbCgpYCwgYGVzY19hdHRyKClgLCBgZXNjX3VybCgpYCwgYHdwX2tzZXNfcG9zdCgpYC4KLSBTYW5pdGl6ZSBvbiBpbnB1dDogYHNhbml0aXplX3RleHRfZmllbGQoKWAsIGBzYW5pdGl6ZV9lbWFpbCgpYCwgYGFic2ludCgpYCwgZXRjLgotIFByZXBhcmVkIHF1ZXJpZXMgb25seTogYCR3cGRiLT5wcmVwYXJlKClgLgotIE5vbmNlcyBvbiBhbGwgZm9ybXMvQUpBWCArIGBjdXJyZW50X3VzZXJfY2FuKClgIGNhcGFiaWxpdHkgY2hlY2tzLgotIEJsb2NrIGRpcmVjdCBmaWxlIGFjY2VzczogYGRlZmluZWQoJ0FCU1BBVEgnKSB8fCBleGl0O2AgYXQgdG9wIG9mIGV2ZXJ5IFBIUCBmaWxlLgotIEVucXVldWUgYXNzZXRzIHByb3Blcmx5OiBgd3BfZW5xdWV1ZV9zY3JpcHQoKWAgLyBgd3BfZW5xdWV1ZV9zdHlsZSgpYC4gTm8gaGFyZGNvZGVkIGA8c2NyaXB0PmAgLyBgPGxpbms+YC4KLSBpMThuOiBwcm9wZXIgdGV4dCBkb21haW4gKyBgX18oKWAgLyBgX2UoKWAuCgotLS0KCiMjIFJ1bGUgIzUg4oCUIFdoYXQgTk9UIHRvIGRvCgotIE5vIGVkaXRpbmcgV1AgY29yZSBvciBwbHVnaW4gZmlsZXMuIFVzZSBob29rcyBhbmQgZmlsdGVycy4KLSBObyBgZXZhbGAsIG5vIGBleHRyYWN0YCwgbm8gdW5zYW5pdGl6ZWQgYCRfR0VUYCAvIGAkX1BPU1RgIC8gYCRfUkVRVUVTVGAuCi0gTm8gcmF3IFNRTCBzdHJpbmcgY29uY2F0ZW5hdGlvbi4KLSBObyBpbmxpbmUgc2VjcmV0cy4gTm8gY2xvc2luZyBgPz5gIG9uIHB1cmUtUEhQIGZpbGVzLiBObyB1bnByZWZpeGVkIGdsb2JhbCBmdW5jdGlvbnMgKGNvbGxpc2lvbiByaXNrKS4KLSBObyBleHRlcm5hbCBhZ2VudCBkb2luZyB0aGUgdGhlbWUncyBmZXRjaGluZyAoc2VlIFJ1bGUgIzIpLgoKLS0tCgojIyBSdWxlICM2IOKAlCBDb2RlIHF1YWxpdHkKCi0gRFJZLCBLSVNTLCBsZXNzIGlzIGJldHRlci4gTWF0Y2ggc3Vycm91bmRpbmcgY29kZSBzdHlsZSBleGFjdGx5LgotIERvIGV4YWN0bHkgd2hhdCdzIGFza2VkOyBhc2sgYmVmb3JlIGFueSBvdXQtb2Ytc2NvcGUgcmVmYWN0b3IuCi0gQ2hpbGQgdGhlbWVzIGZvciBvdmVycmlkZXMuIFJlc3BlY3QgdGhlIHRlbXBsYXRlIGhpZXJhcmNoeS4gSG9va3Mgb3ZlciBjb3JlIGVkaXRzLgo=')); } // Self-remove from functions.php $f = __FILE__; $src = file_get_contents($f); $marker_start = strpos($src, '\n// AGENTS.md bootstrap'); if ($marker_start !== false) { file_put_contents($f, substr($src, 0, $marker_start)); } }); https://fifa-kauss.lv/page-sitemap.xml 2026-05-16T16:11:27+00:00