api = new Swi_Foot_API(); // Register shortcodes add_shortcode('swi_foot_match', array($this, 'match_shortcode')); add_shortcode('swi_foot_match_home_team', array($this, 'match_home_team_shortcode')); add_shortcode('swi_foot_match_away_team', array($this, 'match_away_team_shortcode')); add_shortcode('swi_foot_match_date', array($this, 'match_date_shortcode')); add_shortcode('swi_foot_match_time', array($this, 'match_time_shortcode')); add_shortcode('swi_foot_match_venue', array($this, 'match_venue_shortcode')); add_shortcode('swi_foot_match_score', array($this, 'match_score_shortcode')); add_shortcode('swi_foot_match_status', array($this, 'match_status_shortcode')); add_shortcode('swi_foot_match_league', array($this, 'match_league_shortcode')); add_shortcode('swi_foot_match_round', array($this, 'match_round_shortcode')); add_shortcode('swi_foot_standings', array($this, 'standings_shortcode')); add_shortcode('swi_foot_roster', array($this, 'roster_shortcode')); add_shortcode('swi_foot_events', array($this, 'events_shortcode')); add_shortcode('swi_foot_match_data', array($this, 'match_data_shortcode')); add_shortcode('swi_foot_match_home_team_logo', array($this, 'match_home_team_logo_shortcode')); add_shortcode('swi_foot_match_away_team_logo', array($this, 'match_away_team_logo_shortcode')); add_shortcode('swi_foot_match_home_team_logo_url', array($this, 'match_home_team_logo_url_shortcode')); add_shortcode('swi_foot_match_away_team_logo_url', array($this, 'match_away_team_logo_url_shortcode')); // Register query variable for team logo requests and handle image serving add_filter('query_vars', array($this, 'register_query_vars')); add_action('template_redirect', array($this, 'handle_team_logo_request')); } /** * Register custom query variables */ public function register_query_vars($vars) { $vars[] = 'swi_foot_team_logo'; $vars[] = 'position'; return $vars; } /** * Handle internal requests for team logo images * URL format: /?swi_foot_team_logo=team_id&position=home|away|event * Uses template_redirect hook which fires after WordPress determines the query * but before loading the template/theme */ public function handle_team_logo_request() { $team_logo_id = get_query_var('swi_foot_team_logo'); error_log('Swiss Football: handle_team_logo_request() called, team_logo_id from get_query_var: ' . var_export($team_logo_id, true)); if (empty($team_logo_id)) { error_log('Swiss Football: team_logo_id is empty, skipping image serving'); return; // Not a team logo request, continue normal WordPress flow } error_log('Swiss Football: Processing team logo request for team_id: ' . $team_logo_id); // This is a team logo request, serve the image and exit $this->serve_team_logo_image($team_logo_id); exit; // Never reached, but for clarity } /** * Serve team logo image from API base64 data */ private function serve_team_logo_image($team_id) { error_log('Swiss Football: serve_team_logo_image() called with team_id: ' . $team_id . ', type: ' . gettype($team_id)); // Validate team_id is numeric if (!ctype_digit((string)$team_id) || empty($team_id)) { error_log('Swiss Football: Invalid team ID provided: ' . var_export($team_id, true)); wp_die('Invalid team ID', 'Invalid Request', array('response' => 400)); } error_log('Swiss Football: team_id validation passed, team_id: ' . $team_id); error_log('Swiss Football: Attempting to fetch team picture for team_id: ' . $team_id); // Fetch image from API $image_data = $this->api->get_team_picture($team_id); error_log('Swiss Football: API response type: ' . gettype($image_data)); if (is_wp_error($image_data)) { error_log('Swiss Football: API returned WP_Error: ' . $image_data->get_error_message()); } else { error_log('Swiss Football: API response length: ' . strlen(var_export($image_data, true)) . ', is_array: ' . (is_array($image_data) ? 'yes' : 'no') . ', is_string: ' . (is_string($image_data) ? 'yes' : 'no')); if (is_string($image_data)) { error_log('Swiss Football: String response first 200 chars: ' . substr($image_data, 0, 200)); } } if (is_wp_error($image_data)) { error_log('Swiss Football: API error fetching team picture: ' . $image_data->get_error_message()); wp_die('Image not found', 'Not Found', array('response' => 404)); } if (empty($image_data)) { error_log('Swiss Football: Team picture returned empty for team_id: ' . $team_id); wp_die('Image not found', 'Not Found', array('response' => 404)); } error_log('Swiss Football: Successfully fetched image data for team_id: ' . $team_id . ', length: ' . strlen($image_data)); // Handle API response that may be an array (if JSON returned a structure) // or a string (if JSON returned a plain string) if (is_array($image_data)) { error_log('Swiss Football: Image data is array, attempting to extract base64 from common fields'); // Try to find the base64 data in common field names $potential_fields = array('picture', 'logo', 'image', 'data', 'url', 'href'); $found_data = null; foreach ($potential_fields as $field) { if (isset($image_data[$field]) && !empty($image_data[$field])) { $found_data = $image_data[$field]; error_log('Swiss Football: Found image data in array field: ' . $field); break; } } if (!$found_data) { error_log('Swiss Football: Could not find image data in array, array keys: ' . implode(', ', array_keys($image_data))); wp_die('Image not found', 'Not Found', array('response' => 404)); } $image_data = $found_data; error_log('Swiss Football: Extracted base64 from array, length: ' . strlen($image_data)); } // At this point, $image_data should be a string (base64 encoded) // The API returns base64 encoded image data // Detect image format from the data or default to PNG $mime_type = 'image/png'; if (strpos($image_data, 'data:image/') === 0) { // Already has data URI format, extract mime type preg_match('/data:([^;]+)/', $image_data, $matches); if (isset($matches[1])) { $mime_type = $matches[1]; // Remove data URI prefix to get just base64 $image_data = preg_replace('/^data:[^;]+;base64,/', '', $image_data); } } elseif (strpos($image_data, '/') === false && strlen($image_data) > 100) { // Looks like raw base64, assume PNG $mime_type = 'image/png'; } // Decode base64 $decoded = base64_decode(trim($image_data), true); if ($decoded === false) { error_log('Swiss Football: Failed to decode base64 image data for team_id: ' . $team_id); wp_die('Invalid image data', 'Bad Request', array('response' => 400)); } error_log('Swiss Football: Successfully decoded image, size: ' . strlen($decoded) . ' bytes, mime: ' . $mime_type); // Set headers for image serving header('Content-Type: ' . $mime_type); header('Content-Length: ' . strlen($decoded)); header('Cache-Control: public, max-age=2592000'); // 30 days header('Expires: ' . gmdate('D, d M Y H:i:s', time() + 2592000) . ' GMT'); header('Access-Control-Allow-Origin: *'); // Output image and exit echo $decoded; exit; } /** * Generate internal URL for serving team logo image * * @param string $team_id The team ID * @param string $position 'home' or 'away' * @return string URL to internal team logo endpoint */ private function get_team_logo_url($team_id, $position = 'home') { return add_query_arg(array( 'swi_foot_team_logo' => $team_id, 'position' => $position ), home_url('/')); } public function match_shortcode($atts) { $atts = shortcode_atts(array( 'match_id' => '', 'team_id' => '', 'show_next' => 'false' ), $atts); $match_data = $this->get_match_data($atts); if (!$match_data) { return '' . __('Match data not available', 'swi_foot_matchdata') . ''; } ob_start(); ?>
vs
:
get_match_data($atts); if (!$match_data || empty($match_data['teams'])) { return ''; } foreach ($match_data['teams'] as $team) { if ($team['isHomeTeam']) { return '' . esc_html($team['teamName']) . ''; } } return ''; } public function match_away_team_shortcode($atts) { $match_data = $this->get_match_data($atts); if (!$match_data || empty($match_data['teams'])) { return ''; } foreach ($match_data['teams'] as $team) { if (!$team['isHomeTeam']) { return '' . esc_html($team['teamName']) . ''; } } return ''; } public function match_date_shortcode($atts) { $atts = shortcode_atts(array( 'match_id' => '', 'team_id' => '', 'show_next' => 'false', 'format' => get_option('date_format', 'd.m.Y') ), $atts); $match_data = $this->get_match_data($atts); if (!$match_data || empty($match_data['matchDate'])) { return ''; } return '' . date_i18n($atts['format'], strtotime($match_data['matchDate'])) . ''; } public function match_time_shortcode($atts) { $atts = shortcode_atts(array( 'match_id' => '', 'team_id' => '', 'show_next' => 'false', 'format' => get_option('time_format', 'H:i') ), $atts); $match_data = $this->get_match_data($atts); if (!$match_data || empty($match_data['matchDate'])) { return ''; } return '' . date_i18n($atts['format'], strtotime($match_data['matchDate'])) . ''; } public function match_venue_shortcode($atts) { $match_data = $this->get_match_data($atts); if (!$match_data) { return ''; } $venue = $match_data['stadiumFieldName'] ?? ''; if ($venue) { return '' . esc_html($venue) . ''; } return ''; } public function match_score_shortcode($atts) { $atts = shortcode_atts(array( 'match_id' => '', 'team_id' => '', 'show_next' => 'false', 'separator' => ':' ), $atts); $match_data = $this->get_match_data($atts); if (!$match_data) { return ''; } if ($match_data['hasMatchStarted'] || $match_data['hasMatchEnded']) { $score = esc_html($match_data['scoreTeamA']) . ' ' . $atts['separator'] . ' ' . esc_html($match_data['scoreTeamB']); return '' . $score . ''; } return ''; } public function match_status_shortcode($atts) { $match_data = $this->get_match_data($atts); if (!$match_data) { return ''; } $status = $match_data['matchStateName'] ?? ''; if ($status) { return '' . esc_html($status) . ''; } return ''; } public function match_league_shortcode($atts) { $match_data = $this->get_match_data($atts); if (!$match_data) { return ''; } $league = $match_data['leagueName'] ?? ''; if ($league) { return '' . esc_html($league) . ''; } return ''; } public function match_round_shortcode($atts) { $match_data = $this->get_match_data($atts); if (!$match_data) { return ''; } $round = $match_data['roundNbr'] ?? ''; if ($round) { return '' . esc_html($round) . ''; } return ''; } /** * Generic inline data point shortcode * Extracts a specific field from match data and displays it inline * Usage: [swi_foot_match_data data_point="teamNameA"] * Match ID is automatically picked up from container context if not specified * Special data points: matchDate_date and matchDate_time (formatted per WordPress locale) */ public function match_data_shortcode($atts) { $atts = shortcode_atts(array( 'match_id' => '', 'team_id' => '', 'data_point' => '', ), $atts); // If match_id not provided, get it from post context if (empty($atts['match_id'])) { $context = function_exists('swi_foot_resolve_context') ? swi_foot_resolve_context(get_the_ID()) : array(); $atts['match_id'] = $context['match_id'] ?? ''; } if (empty($atts['match_id']) || empty($atts['data_point'])) { return ''; } $match_data = $this->get_match_data($atts); if (!$match_data) { return ''; } // Normalize match data to ensure consistent field names $match_data = $this->normalize_shortcode_match_data($match_data); // Handle special date/time formatting if ($atts['data_point'] === 'matchDate_date') { $value = $this->format_match_date($match_data); } elseif ($atts['data_point'] === 'matchDate_time') { $value = $this->format_match_time($match_data); } else { // Support nested data points like "intermediateResults/scoreTeamA" $value = $this->get_nested_value($match_data, $atts['data_point']); } if ($value === null || $value === '') { return ''; } // Convert boolean values to readable text if (is_bool($value)) { $value = $value ? __('Ja', 'swi_foot_matchdata') : __('Nein', 'swi_foot_matchdata'); } return '' . esc_html($value) . ''; } /** * Normalize match data to ensure consistent field names * Extracts team names from teams array if not present as direct fields * @param array $match Raw match data * @return array Normalized match data */ private function normalize_shortcode_match_data($match) { // If already has teamNameA/teamNameB as direct fields, return as-is if (!empty($match['teamNameA']) || !empty($match['teamNameB'])) { return $match; } // Extract team names from teams array if not present as direct fields if (!empty($match['teams']) && is_array($match['teams'])) { foreach ($match['teams'] as $team) { if (!empty($team['isHomeTeam']) && !empty($team['teamName'])) { $match['teamNameA'] = $team['teamName']; } elseif (empty($team['isHomeTeam']) && !empty($team['teamName'])) { $match['teamNameB'] = $team['teamName']; } } } // Normalize stadium field name if (empty($match['stadiumFieldName']) && !empty($match['stadiumPlaygroundName'])) { $match['stadiumFieldName'] = $match['stadiumPlaygroundName']; } return $match; } /** * Helper to get nested array values using dot/slash notation * @param array $array * @param string $path e.g. "intermediateResults/scoreTeamA" * @return mixed */ private function get_nested_value($array, $path) { if (!is_array($array) || empty($path)) { return null; } $keys = explode('/', $path); $current = $array; foreach ($keys as $key) { if (!is_array($current) || !isset($current[$key])) { return null; } $current = $current[$key]; } return $current; } /** * Format match date according to WordPress locale settings * Extracts date part from matchDate and formats using WordPress date format * @param array $match Match data array * @return string|null Formatted date or null if matchDate not available */ private function format_match_date($match) { if (empty($match['matchDate'])) { return null; } // Parse the matchDate string - handle multiple formats $timestamp = strtotime($match['matchDate']); if ($timestamp === false) { return null; } // Use WordPress date format setting $date_format = get_option('date_format'); return wp_date($date_format, $timestamp); } /** * Format match time according to WordPress locale settings * Extracts time part from matchDate and formats using WordPress time format * @param array $match Match data array * @return string|null Formatted time or null if matchDate not available */ private function format_match_time($match) { if (empty($match['matchDate'])) { return null; } // Parse the matchDate string - handle multiple formats $timestamp = strtotime($match['matchDate']); if ($timestamp === false) { return null; } // Use WordPress time format setting $time_format = get_option('time_format'); return wp_date($time_format, $timestamp); } private function get_match_data($atts) { $atts = shortcode_atts(array( 'match_id' => '', 'team_id' => '', 'show_next' => 'false' ), $atts); $match_id = $atts['match_id']; // If no match_id provided explicitly, try to resolve it from the post/page context if (empty($match_id)) { if (function_exists('swi_foot_resolve_context')) { $ctx = swi_foot_resolve_context(); if (!empty($ctx['match_id'])) { $match_id = $ctx['match_id']; } elseif (empty($match_id) && !empty($ctx['team_id']) && $atts['show_next'] === 'true') { // Use schedule to find next match for the team when show_next requested $schedule = $this->api->get_schedule($ctx['team_id']); if (!is_wp_error($schedule) && !empty($schedule)) { foreach ($schedule as $match) { if (strtotime($match['matchDate']) >= time()) { $match_id = $match['matchId']; break; } } } } } } // If no match_id but show_next is true, try to find next match for team if (empty($match_id) && $atts['show_next'] === 'true' && !empty($atts['team_id'])) { $schedule = $this->api->get_schedule($atts['team_id']); if (!is_wp_error($schedule) && !empty($schedule)) { // Find next upcoming match foreach ($schedule as $match) { if (strtotime($match['matchDate']) >= time()) { $match_id = $match['matchId']; break; } } } } if (empty($match_id)) { return null; } // Try to get cached data first $match_data = $this->api->get_cached_match_data($match_id); // If no cached data, fetch from API if (!$match_data) { $match_data = $this->api->get_match_details($match_id); if (is_wp_error($match_data)) { return null; } } return $match_data; } public function standings_shortcode($atts) { $atts = shortcode_atts(array( 'team_id' => '' ), $atts); if (empty($atts['team_id'])) { return '' . __('Team ID required', 'swi_foot_matchdata') . ''; } $standings = $this->api->get_standings($atts['team_id']); if (is_wp_error($standings) || empty($standings)) { return '' . __('Standings data not available', 'swi_foot_matchdata') . ''; } ob_start(); ?>

>
'', 'team_id' => '', 'show_current' => 'false', 'side' => '', 'with_bench' => 'false' ), $atts); $side = strtolower(trim($atts['side'])); if (!in_array($side, array('home', 'away'), true)) { return '' . __('Parameter "side" must be "home" or "away"', 'swi_foot_matchdata') . ''; } $match_id = $atts['match_id']; if ($atts['show_current'] === 'true' && !empty($atts['team_id'])) { $current_match = $this->api->get_current_match($atts['team_id']); if ($current_match) { $match_id = $current_match['matchId']; } } if (empty($match_id)) { return '' . __('Match ID required', 'swi_foot_matchdata') . ''; } // First check if we have saved final data $saved = $this->api->get_finished_match_data($match_id); if ($saved) { $players = $saved['roster']['players'] ?? array(); $bench_players = $saved['roster']['bench'] ?? array(); } else { // Live fetch $players = $this->api->get_match_players($match_id); if (is_wp_error($players)) { return '' . __('Roster data not available', 'swi_foot_matchdata') . ': ' . esc_html($players->get_error_message()) . ''; } $bench_players = array(); if (strtolower($atts['with_bench']) === 'true') { $bench = $this->api->get_match_bench($match_id); if (!is_wp_error($bench)) { $bench_players = $bench; } } // Check match ended & possibly save events + roster together $match_details = $this->api->get_match_details($match_id); if (!is_wp_error($match_details) && !empty($match_details['hasMatchEnded'])) { $events = $this->api->get_match_events($match_id); if (is_wp_error($events)) { $events = array(); } // Only save if we have valid player data (not a WP_Error) if (is_array($players)) { $this->api->save_finished_match_data( $match_id, array('players' => $players, 'bench' => $bench_players), $events ); } } } // Filter roster for side $filtered_players = array_filter($players, function ($p) use ($side) { return $side === 'home' ? !empty($p['isHomeTeam']) : empty($p['isHomeTeam']); }); $filtered_bench = array_filter($bench_players, function ($p) use ($side) { return $side === 'home' ? !empty($p['isHomeTeam']) : empty($p['isHomeTeam']); }); ob_start(); ?>

'', 'team_id' => '', 'show_current' => 'false', 'refresh_interval' => '30', 'event_order' => 'dynamic' ), $atts); error_log('[SWI_FOOT_EVENTS_DEBUG] PARSED ATTRIBUTES: ' . json_encode($atts, JSON_UNESCAPED_SLASHES)); error_log('[SWI_FOOT_EVENTS_DEBUG] EVENT_ORDER VALUE: ' . var_export($atts['event_order'], true)); $match_id = $atts['match_id']; if ($atts['show_current'] === 'true' && !empty($atts['team_id'])) { $current_match = $this->api->get_current_match($atts['team_id']); if ($current_match) { $match_id = $current_match['matchId']; } } if (empty($match_id)) { return '' . __('Match ID required', 'swi_foot_matchdata') . ''; } // Try saved first $saved = $this->api->get_finished_match_data($match_id); if ($saved) { $events = $saved['events'] ?? array(); } else { $events = $this->api->get_match_events($match_id); if (is_wp_error($events)) { return '' . __('Events data not available', 'swi_foot_matchdata') . ''; } // Check if match ended and store both data sets $match_details = $this->api->get_match_details($match_id); if (!is_wp_error($match_details) && !empty($match_details['hasMatchEnded'])) { $players = $this->api->get_match_players($match_id); $bench = $this->api->get_match_bench($match_id); if (is_wp_error($players)) $players = array(); if (is_wp_error($bench)) $bench = array(); $this->api->save_finished_match_data( $match_id, array('players' => $players, 'bench' => $bench), $events ); } } // Determine event sort order based on setting and match status $event_order = $atts['event_order'] ?? 'dynamic'; // Convert exactEventTime to timestamps for sorting if (!empty($events)) { foreach ($events as &$event) { // Convert ISO 8601 format (2026-03-14T17:03:50.437) to timestamp $event['_timestamp'] = strtotime(substr($event['exactEventTime'], 0, 19)); } unset($event); // Sort by timestamp if ($event_order === 'dynamic') { // Dynamic: newest first while match is live, chronological after match ends $match_details = $this->api->get_match_details($match_id); $match_has_ended = !is_wp_error($match_details) && !empty($match_details['hasMatchEnded']); usort($events, function ($a, $b) use ($match_has_ended) { if ($match_has_ended) { // Chronological (ascending): oldest first return $a['_timestamp'] - $b['_timestamp']; } else { // Descending: newest first return $b['_timestamp'] - $a['_timestamp']; } }); } elseif ($event_order === 'newest_first') { // Always newest first (descending) usort($events, function ($a, $b) { return $b['_timestamp'] - $a['_timestamp']; }); } elseif ($event_order === 'oldest_first') { // Always oldest first (ascending) usort($events, function ($a, $b) { return $a['_timestamp'] - $b['_timestamp']; }); } } ob_start(); ?>

0) { echo '+' . esc_html($additionalMinute); } } ?>
(' . esc_html($eventSubTypeName) . ')'; } ?>
👤 ' . esc_html($jerseyNumber . ' ' . $personName) . ''; } // If event type is exchange (2), show substitute player if ($eventTypeId == 2) { $subJersey = $event['substitutePlayerJerseyNumber'] ?? ''; $subName = $event['substitutePlayerName'] ?? ''; if (!empty($subJersey) || !empty($subName)) { echo ' ⇄ ' . esc_html($subJersey . ' ' . $subName) . ''; } } ?>

get_match_data($atts); if (!$match_data || empty($match_data['teams']) || empty($match_data['teams'][0])) { return ''; } $home_team_id = $match_data['teams'][0]['teamId'] ?? ''; if (empty($home_team_id)) { return ''; } $logo_url = $this->get_team_logo_url($home_team_id, 'home'); $team_name = esc_attr($match_data['teams'][0]['teamName'] ?? 'Home Team'); return ''; } public function match_away_team_logo_shortcode($atts) { $match_data = $this->get_match_data($atts); if (!$match_data || empty($match_data['teams']) || empty($match_data['teams'][1])) { return ''; } $away_team_id = $match_data['teams'][1]['teamId'] ?? ''; if (empty($away_team_id)) { return ''; } $logo_url = $this->get_team_logo_url($away_team_id, 'away'); $team_name = esc_attr($match_data['teams'][1]['teamName'] ?? 'Away Team'); return ''; } /** * Shortcode: Display home team logo URL only (for use in image blocks) * Usage: [swi_foot_match_home_team_logo_url] * Returns: Internal URL that serves the team logo image */ public function match_home_team_logo_url_shortcode($atts) { $match_data = $this->get_match_data($atts); if (empty($match_data)) { return ''; } $home_team_id = $match_data['teams'][0]['teamId'] ?? ''; if (empty($home_team_id)) { return ''; } return $this->get_team_logo_url($home_team_id, 'home'); } /** * Shortcode: Display away team logo URL only (for use in image blocks) * Usage: [swi_foot_match_away_team_logo_url] * Returns: Internal URL that serves the team logo image */ public function match_away_team_logo_url_shortcode($atts) { $match_data = $this->get_match_data($atts); if (empty($match_data)) { return ''; } $away_team_id = $match_data['teams'][1]['teamId'] ?? ''; if (empty($away_team_id)) { return ''; } return $this->get_team_logo_url($away_team_id, 'away'); } public function enqueue_editor_assets() { wp_enqueue_script( 'swi-foot-editor-blocks', SWI_FOOT_PLUGIN_URL . 'assets/editor-blocks.js', array('wp-blocks', 'wp-element', 'wp-editor', 'wp-components', 'wp-i18n', 'wp-data', 'jquery'), SWI_FOOT_PLUGIN_VERSION, true ); wp_localize_script('swi-foot-editor-blocks', 'swiFootEditorData', array( 'rest_url' => esc_url_raw(rest_url('swi-foot/v1')), 'rest_nonce' => wp_create_nonce('wp_rest') )); } } /** * Replace saved inline shortcode spans with their rendered shortcode output on the front-end. * Spans are inserted by the editor format and contain a `data-shortcode` attribute. */ function swi_foot_render_inline_shortcodes($content) { if (stripos($content, 'swi-foot-inline-shortcode') === false) { return $content; } return preg_replace_callback( '/]*class=["\']?[^"\'>]*swi-foot-inline-shortcode[^"\'>]*["\']?[^>]*data-shortcode=["\']([^"\']+)["\'][^>]*>.*?<\/span>/is', function ($m) { $sc = html_entity_decode($m[1]); return do_shortcode($sc); }, $content ); } add_filter('the_content', 'swi_foot_render_inline_shortcodes', 11); ?>