wp-plugin-swiss-football-ma.../includes/class-swi-foot-rest.php
2026-03-27 13:59:28 +01:00

457 lines
20 KiB
PHP

<?php
class Swi_Foot_REST {
public function __construct() {
add_action('rest_api_init', array($this, 'register_routes'));
}
/**
* Normalize team data for consistent API responses
* @param array $team Raw team data from API
* @return array Normalized team object
*/
private function normalize_team($team) {
return array(
'teamId' => isset($team['teamId']) ? (string)$team['teamId'] : (isset($team['id']) ? (string)$team['id'] : ''),
'teamName' => isset($team['teamName']) ? $team['teamName'] : (isset($team['name']) ? $team['name'] : ''),
'teamLeagueName' => isset($team['teamLeagueName']) ? $team['teamLeagueName'] : (isset($team['league']) ? $team['league'] : null),
'icon' => isset($team['icon']) ? $team['icon'] : null,
);
}
/**
* Normalize match data for consistent API responses
* @param array $match Raw match data from API
* @return array Normalized match object
*/
private function normalize_match($match) {
// Extract team names from teams array if not present as direct fields
$teamNameA = '';
$teamNameB = '';
if (!empty($match['teams']) && is_array($match['teams'])) {
foreach ($match['teams'] as $team) {
if (!empty($team['isHomeTeam']) && !empty($team['teamName'])) {
$teamNameA = $team['teamName'];
} elseif (empty($team['isHomeTeam']) && !empty($team['teamName'])) {
$teamNameB = $team['teamName'];
}
}
}
// Use direct fields if available, fallback to extracted values, then fallback field variations
return array(
'matchId' => isset($match['matchId']) ? (string)$match['matchId'] : (isset($match['id']) ? (string)$match['id'] : ''),
'matchDate' => isset($match['matchDate']) ? $match['matchDate'] : (isset($match['date']) ? $match['date'] : null),
'teamAId' => isset($match['teamAId']) ? (string)$match['teamAId'] : (isset($match['homeTeamId']) ? (string)$match['homeTeamId'] : ''),
'teamBId' => isset($match['teamBId']) ? (string)$match['teamBId'] : (isset($match['awayTeamId']) ? (string)$match['awayTeamId'] : ''),
'teamNameA' => isset($match['teamNameA']) ? $match['teamNameA'] : ($teamNameA ?: (isset($match['homeTeamName']) ? $match['homeTeamName'] : '')),
'teamNameB' => isset($match['teamNameB']) ? $match['teamNameB'] : ($teamNameB ?: (isset($match['awayTeamName']) ? $match['awayTeamName'] : '')),
'scoreTeamA' => isset($match['scoreTeamA']) ? $match['scoreTeamA'] : (isset($match['homeScore']) ? $match['homeScore'] : null),
'scoreTeamB' => isset($match['scoreTeamB']) ? $match['scoreTeamB'] : (isset($match['awayScore']) ? $match['awayScore'] : null),
'matchStateName' => isset($match['matchStateName']) ? $match['matchStateName'] : (isset($match['status']) ? $match['status'] : null),
'stadiumFieldName' => isset($match['stadiumFieldName']) ? $match['stadiumFieldName'] : (isset($match['stadiumPlaygroundName']) ? $match['stadiumPlaygroundName'] : null),
'leagueName' => isset($match['leagueName']) ? $match['leagueName'] : null,
'divisionName' => isset($match['divisionName']) ? $match['divisionName'] : null,
'roundNbr' => isset($match['roundNbr']) ? $match['roundNbr'] : null,
'matchTypeName' => isset($match['matchTypeName']) ? $match['matchTypeName'] : null,
'hasMatchStarted' => isset($match['hasMatchStarted']) ? $match['hasMatchStarted'] : false,
'isMatchPause' => isset($match['isMatchPause']) ? $match['isMatchPause'] : false,
'hasMatchEnded' => isset($match['hasMatchEnded']) ? $match['hasMatchEnded'] : false,
'intermediateResults' => isset($match['intermediateResults']) ? $match['intermediateResults'] : null,
'teams' => isset($match['teams']) ? $match['teams'] : array(),
);
}
public function register_routes() {
register_rest_route('swi-foot/v1', '/teams', array(
'methods' => 'GET',
'callback' => array($this, 'get_teams'),
'permission_callback' => '__return_true'
));
register_rest_route('swi-foot/v1', '/matches', array(
'methods' => 'GET',
'callback' => array($this, 'get_matches_for_team'),
'permission_callback' => '__return_true',
'args' => array(
'team_id' => array(
'validate_callback' => function($param, $request, $key) {
return is_numeric($param);
}
)
)
));
register_rest_route('swi-foot/v1', '/block-status/(?P<match_id>\d+)/(?P<endpoint>[a-z_]+)', array(
'methods' => 'GET',
'callback' => array($this, 'check_block_status'),
'permission_callback' => '__return_true'
));
// Single match details: /wp-json/swi-foot/v1/match/<match_id>
register_rest_route('swi-foot/v1', '/match/(?P<match_id>[^/]+)', array(
'methods' => 'GET',
'callback' => array($this, 'get_match_details'),
'permission_callback' => '__return_true',
'args' => array(
'match_id' => array(
'validate_callback' => function($param, $request, $key) {
return is_string($param) && strlen($param) > 0;
}
)
)
));
// Match events with status: /wp-json/swi-foot/v1/events/<match_id>
// Requires authentication to prevent abuse from non-logged-in users
register_rest_route('swi-foot/v1', '/events/(?P<match_id>[^/]+)', array(
'methods' => 'GET',
'callback' => array($this, 'get_match_events'),
'permission_callback' => function() {
return is_user_logged_in() || apply_filters('swi_foot_rest_events_public', false);
},
'args' => array(
'match_id' => array(
'validate_callback' => function($param, $request, $key) {
return is_string($param) && strlen($param) > 0;
}
)
)
));
register_rest_route('swi-foot/v1', '/common-ids', array(
'methods' => 'GET',
'callback' => array($this, 'get_common_ids'),
'permission_callback' => '__return_true'
));
// Admin actions
register_rest_route('swi-foot/v1', '/admin/refresh-teams', array(
'methods' => 'POST',
'callback' => array($this, 'admin_refresh_teams'),
'permission_callback' => function() { return current_user_can('manage_options'); }
));
register_rest_route('swi-foot/v1', '/admin/clear-cache', array(
'methods' => 'POST',
'callback' => array($this, 'admin_clear_cache'),
'permission_callback' => function() { return current_user_can('manage_options'); }
));
register_rest_route('swi-foot/v1', '/admin/test-connection', array(
'methods' => 'POST',
'callback' => array($this, 'admin_test_connection'),
'permission_callback' => function() { return current_user_can('manage_options'); }
));
register_rest_route('swi-foot/v1', '/admin/finished/(?P<match_id>[^/]+)', array(
'methods' => 'DELETE',
'callback' => array($this, 'admin_delete_finished_match'),
'permission_callback' => function() { return current_user_can('manage_options'); },
'args' => array(
'match_id' => array(
'validate_callback' => function($param, $request, $key) {
return is_string($param) && strlen($param) > 0;
}
)
)
));
register_rest_route('swi-foot/v1', '/admin/finished', array(
'methods' => 'DELETE',
'callback' => array($this, 'admin_delete_all_finished_matches'),
'permission_callback' => function() { return current_user_can('manage_options'); }
));
}
public function get_teams($request) {
$api = new Swi_Foot_API();
$teams = $api->get_teams();
if (is_wp_error($teams)) {
return new WP_REST_Response(array('error' => $teams->get_error_message()), 400);
}
// Normalize team data for consistent responses
$normalized = array();
if (is_array($teams)) {
foreach ($teams as $team) {
$normalized[] = $this->normalize_team($team);
}
}
return rest_ensure_response($normalized);
}
public function get_matches_for_team($request) {
$team_id = $request->get_param('team_id');
if (empty($team_id)) {
return new WP_REST_Response(array('error' => 'team_id required'), 400);
}
$api = new Swi_Foot_API();
$schedule = $api->get_schedule($team_id);
if (is_wp_error($schedule)) {
return new WP_REST_Response(array('error' => $schedule->get_error_message()), 400);
}
// Normalize matches for consistent responses
$normalized = array();
if (is_array($schedule)) {
foreach ($schedule as $match) {
$normalized[] = $this->normalize_match($match);
}
}
return rest_ensure_response($normalized);
}
public function get_common_ids($request) {
$api = new Swi_Foot_API();
$commons = $api->get_commons_ids();
if (is_wp_error($commons)) {
return new WP_REST_Response(array('error' => $commons->get_error_message()), 400);
}
return rest_ensure_response($commons);
}
/**
* Return full match details for a given match_id
* GET /wp-json/swi-foot/v1/match/<match_id>
*/
public function get_match_details($request) {
$match_id = $request->get_param('match_id');
if (empty($match_id)) {
return new WP_REST_Response(array('error' => 'match_id required'), 400);
}
$api = new Swi_Foot_API();
$match = $api->get_match_details($match_id);
if (is_wp_error($match)) {
return new WP_REST_Response(array('error' => $match->get_error_message()), 400);
}
// Normalize match data for consistent API responses
$normalized = $this->normalize_match($match);
return rest_ensure_response($normalized);
}
/**
* Get match events with current match status (for frontend refresh polling)
*
* Endpoint: GET /wp-json/swi-foot/v1/events/<match_id>
*
* Returns the latest match events (sorted newest first) along with critical match
* status information (hasMatchStarted, hasMatchEnded). Frontend polls this endpoint
* at configurable intervals to update live event displays.
*
* Permission: Requires logged-in user (can be overridden with swi_foot_rest_events_public filter)
*
* @param WP_REST_Request $request The REST request object containing match_id parameter
* @return WP_REST_Response Array with keys:
* - matchId (string): The match ID
* - events (array): Array of event objects with matchMinute, eventTypeName, playerName, teamName, timestamp
* - hasMatchStarted (bool): Whether the match has begun
* - hasMatchEnded (bool): Whether the match has concluded
* - matchStateName (string|null): Current match status label
*/
public function get_match_events($request) {
$match_id = $request->get_param('match_id');
if (empty($match_id)) {
return new WP_REST_Response(array('error' => 'match_id required'), 400);
}
$api = new Swi_Foot_API();
// Get match details for status
$match = $api->get_match_details($match_id);
if (is_wp_error($match)) {
return new WP_REST_Response(array('error' => $match->get_error_message()), 400);
}
// Try saved finished match data first
$events = array();
$saved = $api->get_finished_match_data($match_id);
if ($saved) {
$events = $saved['events'] ?? array();
} else {
// Fetch live events
$events = $api->get_match_events($match_id);
if (is_wp_error($events)) {
$events = array();
}
}
// Get event ordering preference from query parameter
$event_order = $request->get_param('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
// Strip milliseconds if present, keep only date and time part
$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_has_ended = !empty($match['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'];
});
}
}
return rest_ensure_response(array(
'matchId' => $match_id,
'events' => (array)$events,
'hasMatchStarted' => $match['hasMatchStarted'] ?? false,
'hasMatchEnded' => $match['hasMatchEnded'] ?? false,
'matchStateName' => $match['matchStateName'] ?? null,
));
}
public function admin_refresh_teams($request) {
$api = new Swi_Foot_API();
// Collect debug information to assist troubleshooting 401 responses
$debug = array();
if (method_exists($api, 'debug_get_token_info')) {
$debug = $api->debug_get_token_info();
error_log('Swiss Football Debug: refresh-teams token_present=' . ($debug['token_present'] ? '1' : '0') . ' base=' . $debug['base_url'] . ' url=' . $debug['team_list_url']);
}
delete_transient('swi_foot_teams');
$teams = $api->get_teams();
if (is_wp_error($teams)) {
// Try to include debug information in response for admin callers
$err = $teams->get_error_message();
error_log('Swiss Football API: refresh-teams failed - ' . $err);
return new WP_REST_Response(array('error' => $err, 'debug' => $debug), 400);
}
return rest_ensure_response(array('success' => true, 'data' => $teams, 'debug' => $debug));
}
public function admin_clear_cache($request) {
$keys = get_transient('swi_foot_match_keys');
if (is_array($keys)) {
foreach ($keys as $mid) {
delete_transient('swi_foot_match_' . $mid);
}
}
delete_transient('swi_foot_match_keys');
delete_transient('swi_foot_teams');
return rest_ensure_response(array('success' => true));
}
public function admin_test_connection($request) {
$api = new Swi_Foot_API();
if (method_exists($api, 'test_connection')) {
$result = $api->test_connection();
} else {
$result = array(
'success' => false,
'error' => 'test_connection method not found',
'details' => 'Plugin may be incorrectly installed'
);
}
// Return response with full error details
if (is_array($result) && isset($result['success'])) {
if ($result['success']) {
return rest_ensure_response(array(
'success' => true,
'message' => isset($result['details']) ? $result['details'] : 'Connection successful!'
));
} else {
return new WP_REST_Response(array(
'success' => false,
'error' => isset($result['error']) ? $result['error'] : 'Connection failed',
'details' => isset($result['details']) ? $result['details'] : 'Unknown error'
), 400);
}
}
// Fallback for unexpected response format
return new WP_REST_Response(array(
'success' => false,
'error' => 'Unexpected response from API test',
'details' => 'Unable to determine connection status'
), 400);
}
public function check_block_status($request) {
$match_id = $request->get_param('match_id');
$endpoint = $request->get_param('endpoint');
if (empty($match_id) || empty($endpoint)) {
return rest_ensure_response(array('status' => 'error', 'message' => 'Missing parameters'));
}
$api = new Swi_Foot_API();
// Map endpoint names to API methods
$method_map = array(
'roster' => 'get_match_players',
'bench' => 'get_match_bench',
'referees' => 'get_match_referees',
'events' => 'get_match_events'
);
if (!isset($method_map[$endpoint])) {
return rest_ensure_response(array('status' => 'error', 'message' => __('Unknown endpoint', 'swi_foot_matchdata')));
}
$method = $method_map[$endpoint];
$result = $api->$method($match_id);
// Determine status based on result
if (is_wp_error($result)) {
$code = $result->get_error_code();
if ($code === 'data_not_available') {
return rest_ensure_response(array('status' => 'warning', 'message' => __('Data not yet available', 'swi_foot_matchdata')));
} else {
return rest_ensure_response(array('status' => 'error', 'message' => __('No data available', 'swi_foot_matchdata')));
}
} elseif (empty($result)) {
return rest_ensure_response(array('status' => 'error', 'message' => __('No data available', 'swi_foot_matchdata')));
} else {
return rest_ensure_response(array('status' => 'success', 'message' => __('Data available', 'swi_foot_matchdata')));
}
}
public function admin_delete_finished_match($request) {
$match_id = $request->get_param('match_id');
if (empty($match_id)) return new WP_REST_Response(array('error' => 'match_id required'), 400);
$data = get_option('swi_foot_finished_matches', array());
if (isset($data[$match_id])) {
unset($data[$match_id]);
update_option('swi_foot_finished_matches', $data);
return rest_ensure_response(array('success' => true));
}
return new WP_REST_Response(array('error' => 'not found'), 404);
}
public function admin_delete_all_finished_matches($request) {
delete_option('swi_foot_finished_matches');
return rest_ensure_response(array('success' => true));
}
}
new Swi_Foot_REST();