initial state
This commit is contained in:
@@ -0,0 +1,466 @@
|
||||
<?php
|
||||
|
||||
class Swi_Foot_Admin
|
||||
{
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
add_action('admin_menu', array($this, 'add_admin_menu'));
|
||||
add_action('admin_init', array($this, 'settings_init'));
|
||||
add_action('admin_enqueue_scripts', array($this, 'admin_scripts'));
|
||||
add_action('add_meta_boxes', array($this, 'add_match_meta_boxes'));
|
||||
// Finished-match deletion moved to REST endpoints (see includes/class-swi-foot-rest.php)
|
||||
}
|
||||
|
||||
public function add_admin_menu()
|
||||
{
|
||||
add_options_page(
|
||||
__('Swiss Football Matchdata Settings', 'swi_foot_matchdata'),
|
||||
__('Swiss Football', 'swi_foot_matchdata'),
|
||||
'manage_options',
|
||||
'swiss-football-matchdata',
|
||||
array($this, 'options_page')
|
||||
);
|
||||
}
|
||||
|
||||
public function settings_init()
|
||||
{
|
||||
register_setting('swi_foot_settings', 'swi_foot_api_base_url');
|
||||
register_setting('swi_foot_settings', 'swi_foot_api_username');
|
||||
register_setting('swi_foot_settings', 'swi_foot_api_password');
|
||||
register_setting('swi_foot_settings', 'swi_foot_verein_id');
|
||||
register_setting('swi_foot_settings', 'swi_foot_season_id');
|
||||
register_setting('swi_foot_settings', 'swi_foot_match_cache_duration');
|
||||
|
||||
add_settings_section(
|
||||
'swi_foot_api_section',
|
||||
__('API Configuration', 'swi_foot_matchdata'),
|
||||
array($this, 'api_section_callback'),
|
||||
'swi_foot_settings'
|
||||
);
|
||||
|
||||
add_settings_field('swi_foot_api_base_url', __('API Base URL', 'swi_foot_matchdata'), array($this, 'base_url_render'), 'swi_foot_settings', 'swi_foot_api_section');
|
||||
add_settings_field('swi_foot_api_username', __('API Username (Application Key)', 'swi_foot_matchdata'), array($this, 'username_render'), 'swi_foot_settings', 'swi_foot_api_section');
|
||||
add_settings_field('swi_foot_api_password', __('API Password (Application Pass)', 'swi_foot_matchdata'), array($this, 'password_render'), 'swi_foot_settings', 'swi_foot_api_section');
|
||||
add_settings_field('swi_foot_verein_id', __('Verein ID (Club ID)', 'swi_foot_matchdata'), array($this, 'verein_id_render'), 'swi_foot_settings', 'swi_foot_api_section');
|
||||
add_settings_field('swi_foot_season_id', __('Season ID', 'swi_foot_matchdata'), array($this, 'season_id_render'), 'swi_foot_settings', 'swi_foot_api_section');
|
||||
|
||||
add_settings_section(
|
||||
'swi_foot_cache_section',
|
||||
__('Cache Settings', 'swi_foot_matchdata'),
|
||||
array($this, 'cache_section_callback'),
|
||||
'swi_foot_settings'
|
||||
);
|
||||
|
||||
add_settings_field('swi_foot_match_cache_duration', __('Match Data Cache Duration (seconds)', 'swi_foot_matchdata'), array($this, 'cache_duration_render'), 'swi_foot_settings', 'swi_foot_cache_section');
|
||||
}
|
||||
|
||||
public function api_section_callback()
|
||||
{
|
||||
echo '<p>' . __('Configure your Swiss Football API credentials here.', 'swi_foot_matchdata') . '</p>';
|
||||
}
|
||||
|
||||
public function cache_section_callback()
|
||||
{
|
||||
echo '<p>' . __('Configure caching settings for match data.', 'swi_foot_matchdata') . '</p>';
|
||||
}
|
||||
|
||||
public function base_url_render()
|
||||
{
|
||||
$base_url = get_option('swi_foot_api_base_url', 'https://stg-club-api-services.football.ch');
|
||||
echo '<input type="url" name="swi_foot_api_base_url" value="' . esc_attr($base_url) . '" class="regular-text" />';
|
||||
echo '<p class="description">' . __('The base URL for the Swiss Football API', 'swi_foot_matchdata') . '</p>';
|
||||
}
|
||||
|
||||
public function username_render()
|
||||
{
|
||||
$username = get_option('swi_foot_api_username');
|
||||
echo '<input type="text" name="swi_foot_api_username" value="' . esc_attr($username) . '" class="regular-text" />';
|
||||
echo '<p class="description">' . __('Your API application key', 'swi_foot_matchdata') . '</p>';
|
||||
}
|
||||
|
||||
public function password_render()
|
||||
{
|
||||
$password = get_option('swi_foot_api_password');
|
||||
echo '<input type="password" name="swi_foot_api_password" value="' . esc_attr($password) . '" class="regular-text" />';
|
||||
echo '<p class="description">' . __('Your API application password', 'swi_foot_matchdata') . '</p>';
|
||||
}
|
||||
|
||||
public function verein_id_render()
|
||||
{
|
||||
$verein_id = get_option('swi_foot_verein_id');
|
||||
echo '<input type="number" name="swi_foot_verein_id" value="' . esc_attr($verein_id) . '" class="regular-text" />';
|
||||
echo '<p class="description">' . __('Enter your club\'s Verein ID (Club ID)', 'swi_foot_matchdata') . '</p>';
|
||||
}
|
||||
|
||||
public function season_id_render()
|
||||
{
|
||||
$season_id = get_option('swi_foot_season_id', date('Y'));
|
||||
echo '<input type="number" name="swi_foot_season_id" value="' . esc_attr($season_id) . '" class="regular-text" min="2020" max="2030" />';
|
||||
echo '<p class="description">' . __('Current season ID (usually the year)', 'swi_foot_matchdata') . '</p>';
|
||||
}
|
||||
|
||||
public function cache_duration_render()
|
||||
{
|
||||
$duration = get_option('swi_foot_match_cache_duration', 30);
|
||||
echo '<input type="number" name="swi_foot_match_cache_duration" value="' . esc_attr($duration) . '" min="10" max="300" />';
|
||||
echo '<p class="description">' . __('How long to cache match data in seconds (10-300)', 'swi_foot_matchdata') . '</p>';
|
||||
}
|
||||
|
||||
public function admin_scripts($hook)
|
||||
{
|
||||
if ($hook === 'settings_page_swiss-football-matchdata') {
|
||||
wp_enqueue_script('swi-foot-admin', SWI_FOOT_PLUGIN_URL . 'assets/admin.js', array('jquery'), SWI_FOOT_PLUGIN_VERSION, true);
|
||||
wp_localize_script('swi-foot-admin', 'swi_foot_ajax', array(
|
||||
'ajax_url' => admin_url('admin-ajax.php'),
|
||||
'nonce' => wp_create_nonce('swi_foot_nonce'),
|
||||
'rest_url' => esc_url_raw(rest_url('swi-foot/v1')),
|
||||
'rest_nonce' => wp_create_nonce('wp_rest')
|
||||
));
|
||||
wp_enqueue_style('swi-foot-admin', SWI_FOOT_PLUGIN_URL . 'assets/admin.css', array(), SWI_FOOT_PLUGIN_VERSION);
|
||||
}
|
||||
|
||||
// Add shortcode help to post/page editors
|
||||
global $pagenow;
|
||||
if (in_array($pagenow, array('post.php', 'post-new.php', 'edit.php'))) {
|
||||
// Enqueue the registered editor bundle so WordPress picks the built asset when available.
|
||||
// The script handle `swi-foot-editor-blocks` is registered in `register_blocks()`.
|
||||
wp_enqueue_script('swi-foot-editor-blocks');
|
||||
|
||||
// Add admin footer debug output to help diagnose missing script tags.
|
||||
add_action('admin_footer', array($this, 'print_editor_script_debug'));
|
||||
// Post context editor panel: allow editor to set per-post season/team/match
|
||||
wp_enqueue_script('swi-foot-post-context', SWI_FOOT_PLUGIN_URL . 'assets/post-context.js', array('wp-plugins','wp-edit-post','wp-element','wp-data','wp-components'), SWI_FOOT_PLUGIN_VERSION, true);
|
||||
wp_localize_script('swi-foot-post-context', 'swi_foot_post_context', array(
|
||||
'rest_url' => esc_url_raw(rest_url('swi-foot/v1')),
|
||||
'rest_nonce' => wp_create_nonce('wp_rest'),
|
||||
'default_season' => get_option('swi_foot_season_id', date('Y'))
|
||||
));
|
||||
wp_enqueue_style('swi-foot-admin', SWI_FOOT_PLUGIN_URL . 'assets/admin.css', array(), SWI_FOOT_PLUGIN_VERSION);
|
||||
}
|
||||
}
|
||||
|
||||
public function print_editor_script_debug()
|
||||
{
|
||||
// Only show on post editor pages
|
||||
$pagenow = isset($GLOBALS['pagenow']) ? $GLOBALS['pagenow'] : '';
|
||||
if (!in_array($pagenow, array('post.php', 'post-new.php', 'edit.php'))) return;
|
||||
|
||||
// Check registration/enqueue status and attempt to find the resolved src
|
||||
$registered = false;
|
||||
$enqueued = false;
|
||||
$src = '';
|
||||
global $wp_scripts;
|
||||
if (isset($wp_scripts) && is_object($wp_scripts)) {
|
||||
$registered = wp_script_is('swi-foot-editor-blocks', 'registered');
|
||||
$enqueued = wp_script_is('swi-foot-editor-blocks', 'enqueued');
|
||||
$handle = $wp_scripts->query('swi-foot-editor-blocks', 'registered');
|
||||
if ($handle && isset($wp_scripts->registered['swi-foot-editor-blocks']->src)) {
|
||||
$src = $wp_scripts->registered['swi-foot-editor-blocks']->src;
|
||||
}
|
||||
}
|
||||
|
||||
$msg = array(
|
||||
'registered' => $registered ? 'yes' : 'no',
|
||||
'enqueued' => $enqueued ? 'yes' : 'no',
|
||||
'src' => $src
|
||||
);
|
||||
echo "<script>console.debug('swi-foot: editor script debug', ". json_encode($msg) .");</script>";
|
||||
}
|
||||
|
||||
public function add_match_meta_boxes()
|
||||
{
|
||||
add_meta_box(
|
||||
'swi-foot-shortcodes',
|
||||
__('Swiss Football Shortcodes', 'swi_foot_matchdata'),
|
||||
array($this, 'shortcodes_meta_box'),
|
||||
array('post', 'page'),
|
||||
'side',
|
||||
'default'
|
||||
);
|
||||
}
|
||||
|
||||
public function shortcodes_meta_box($post)
|
||||
{
|
||||
?>
|
||||
<div class="swi-foot-shortcodes-help">
|
||||
<h4><?php _e('Available Shortcodes:', 'swi_foot_matchdata'); ?></h4>
|
||||
|
||||
<div class="shortcode-group">
|
||||
<strong><?php _e('Full Match Display:', 'swi_foot_matchdata'); ?></strong>
|
||||
<code data-shortcode="[swi_foot_match match_id="12345"]">[swi_foot_match match_id="12345"]</code>
|
||||
<code data-shortcode="[swi_foot_match team_id="67" show_next="true"]">[swi_foot_match team_id="67" show_next="true"]</code>
|
||||
</div>
|
||||
|
||||
<div class="shortcode-group">
|
||||
<strong><?php _e('Individual Match Elements:', 'swi_foot_matchdata'); ?></strong>
|
||||
<code data-shortcode="[swi_foot_match_home_team match_id="12345"]">[swi_foot_match_home_team match_id="12345"]</code>
|
||||
<code data-shortcode="[swi_foot_match_away_team match_id="12345"]">[swi_foot_match_away_team match_id="12345"]</code>
|
||||
<code data-shortcode="[swi_foot_match_date match_id="12345" format="d.m.Y"]">[swi_foot_match_date match_id="12345" format="d.m.Y"]</code>
|
||||
<code data-shortcode="[swi_foot_match_time match_id="12345" format="H:i"]">[swi_foot_match_time match_id="12345" format="H:i"]</code>
|
||||
<code data-shortcode="[swi_foot_match_venue match_id="12345"]">[swi_foot_match_venue match_id="12345"]</code>
|
||||
<code data-shortcode="[swi_foot_match_score match_id="12345" separator=":"]">[swi_foot_match_score match_id="12345" separator=":"]</code>
|
||||
<code data-shortcode="[swi_foot_match_status match_id="12345"]">[swi_foot_match_status match_id="12345"]</code>
|
||||
<code data-shortcode="[swi_foot_match_league match_id="12345"]">[swi_foot_match_league match_id="12345"]</code>
|
||||
<code data-shortcode="[swi_foot_match_round match_id="12345"]">[swi_foot_match_round match_id="12345"]</code>
|
||||
</div>
|
||||
|
||||
<div class="shortcode-group">
|
||||
<strong><?php _e('Parameters:', 'swi_foot_matchdata'); ?></strong>
|
||||
<ul>
|
||||
<li><code>match_id</code> - Specific match ID</li>
|
||||
<li><code>team_id</code> - Team ID (for next match)</li>
|
||||
<li><code>show_next</code> - Show next match (true/false)</li>
|
||||
<li><code>format</code> - Date/time format</li>
|
||||
<li><code>separator</code> - Score separator</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<p><em><?php _e('Click any shortcode above to copy it to clipboard', 'swi_foot_matchdata'); ?></em></p>
|
||||
</div>
|
||||
<script>
|
||||
jQuery(document).ready(function($) {
|
||||
$('.swi-foot-shortcodes-help code[data-shortcode]').css('cursor', 'pointer').on('click', function() {
|
||||
var shortcode = $(this).data('shortcode');
|
||||
if (navigator.clipboard) {
|
||||
navigator.clipboard.writeText(shortcode).then(function() {
|
||||
alert('<?php _e('Shortcode copied to clipboard!', 'swi_foot_matchdata'); ?>');
|
||||
});
|
||||
} else {
|
||||
// Use native clipboard API
|
||||
var textArea = document.createElement('textarea');
|
||||
textArea.value = shortcode;
|
||||
document.body.appendChild(textArea);
|
||||
textArea.select();
|
||||
document.execCommand('copy');
|
||||
document.body.removeChild(textArea);
|
||||
alert('<?php _e('Shortcode copied to clipboard!', 'swi_foot_matchdata'); ?>');
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
<?php
|
||||
}
|
||||
|
||||
public function options_page()
|
||||
{
|
||||
?>
|
||||
<div class="wrap">
|
||||
<h1><?php _e('Swiss Football Matchdata Settings', 'swi_foot_matchdata'); ?></h1>
|
||||
|
||||
<form action="options.php" method="post">
|
||||
<?php
|
||||
settings_fields('swi_foot_settings');
|
||||
do_settings_sections('swi_foot_settings');
|
||||
submit_button();
|
||||
?>
|
||||
</form>
|
||||
|
||||
<div class="swi-foot-admin-section">
|
||||
<h2><?php _e('Connection Test', 'swi_foot_matchdata'); ?></h2>
|
||||
<p>
|
||||
<button type="button" id="test-connection" class="button">
|
||||
<?php _e('Test API Connection', 'swi_foot_matchdata'); ?>
|
||||
</button>
|
||||
<span id="connection-status"></span>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="swi-foot-admin-section">
|
||||
<h2><?php _e('Team Management', 'swi_foot_matchdata'); ?></h2>
|
||||
<p>
|
||||
<button type="button" id="refresh-teams" class="button">
|
||||
<?php _e('Refresh Teams List', 'swi_foot_matchdata'); ?>
|
||||
</button>
|
||||
<span id="refresh-status"></span>
|
||||
</p>
|
||||
|
||||
<div id="teams-list">
|
||||
<?php $this->display_teams(); ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="swi-foot-admin-section">
|
||||
<h2><?php _e('Cache Management', 'swi_foot_matchdata'); ?></h2>
|
||||
<p>
|
||||
<button type="button" id="clear-cache" class="button">
|
||||
<?php _e('Clear Match Data Cache', 'swi_foot_matchdata'); ?>
|
||||
</button>
|
||||
<span id="cache-status"></span>
|
||||
</p>
|
||||
<?php $this->display_cache_info(); ?>
|
||||
</div>
|
||||
|
||||
<div class="swi-foot-admin-section">
|
||||
<h2><?php _e('Finished Matches Data', 'swi_foot_matchdata'); ?></h2>
|
||||
<?php $this->display_finished_matches(); ?>
|
||||
</div>
|
||||
|
||||
<div class="swi-foot-admin-section">
|
||||
<h2><?php _e('Quick Shortcode Reference', 'swi_foot_matchdata'); ?></h2>
|
||||
<div class="shortcode-quick-ref">
|
||||
<div class="shortcode-examples">
|
||||
<h4><?php _e('Common Examples:', 'swi_foot_matchdata'); ?></h4>
|
||||
<p><strong><?php _e('Display full match info:', 'swi_foot_matchdata'); ?></strong><br>
|
||||
<code>[swi_foot_match match_id="12345"]</code>
|
||||
</p>
|
||||
|
||||
<p><strong><?php _e('Show next match for a team:', 'swi_foot_matchdata'); ?></strong><br>
|
||||
<code>[swi_foot_match team_id="67" show_next="true"]</code>
|
||||
</p>
|
||||
|
||||
<p><strong><?php _e('Individual elements in text:', 'swi_foot_matchdata'); ?></strong><br>
|
||||
<?php _e('The match between', 'swi_foot_matchdata'); ?> <code>[swi_foot_match_home_team match_id="12345"]</code> <?php _e('and', 'swi_foot_matchdata'); ?> <code>[swi_foot_match_away_team match_id="12345"]</code> <?php _e('is on', 'swi_foot_matchdata'); ?> <code>[swi_foot_match_date match_id="12345"]</code></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
|
||||
private function display_teams()
|
||||
{
|
||||
$api = new Swi_Foot_API();
|
||||
$teams = $api->get_teams();
|
||||
|
||||
if (is_wp_error($teams)) {
|
||||
echo '<p class="notice notice-error">' .
|
||||
sprintf(__('Error loading teams: %s', 'swi_foot_matchdata'), $teams->get_error_message()) .
|
||||
'</p>';
|
||||
return;
|
||||
}
|
||||
|
||||
if (empty($teams)) {
|
||||
echo '<p class="notice notice-warning">' .
|
||||
__('No teams found. Please check your API configuration.', 'swi_foot_matchdata') .
|
||||
'</p>';
|
||||
return;
|
||||
}
|
||||
|
||||
echo '<h3>' . __('Available Teams:', 'swi_foot_matchdata') . '</h3>';
|
||||
echo '<div class="swi-foot-teams-grid">';
|
||||
foreach ($teams as $team) {
|
||||
echo '<div class="team-card">';
|
||||
echo '<strong>' . esc_html($team['teamName'] ?? 'Unknown Team') . '</strong><br>';
|
||||
echo '<small>ID: ' . esc_html($team['teamId'] ?? 'N/A') . '</small><br>';
|
||||
if (!empty($team['teamLeagueName'])) {
|
||||
echo '<small>' . esc_html($team['teamLeagueName']) . '</small>';
|
||||
}
|
||||
echo '</div>';
|
||||
}
|
||||
echo '</div>';
|
||||
}
|
||||
|
||||
private function display_cache_info()
|
||||
{
|
||||
$keys = get_transient('swi_foot_match_keys');
|
||||
$cache_count = is_array($keys) ? count($keys) : 0;
|
||||
$cache_duration = get_option('swi_foot_match_cache_duration', 30);
|
||||
|
||||
echo '<p>' . sprintf(__('Currently caching %d match records with %d second cache duration.', 'swi_foot_matchdata'), $cache_count, $cache_duration) . '</p>';
|
||||
|
||||
if ($cache_count > 0) {
|
||||
$timestamps = array();
|
||||
foreach ($keys as $k) {
|
||||
$payload = get_transient('swi_foot_match_' . $k);
|
||||
if (is_array($payload) && isset($payload['cached_at'])) {
|
||||
$timestamps[] = (int) $payload['cached_at'];
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($timestamps)) {
|
||||
$oldest_timestamp = min($timestamps);
|
||||
$newest_timestamp = max($timestamps);
|
||||
|
||||
echo '<p><strong>' . __('Cache Range:', 'swi_foot_matchdata') . '</strong><br>';
|
||||
echo __('Oldest:', 'swi_foot_matchdata') . ' ' . date_i18n(get_option('date_format') . ' ' . get_option('time_format'), $oldest_timestamp) . '<br>';
|
||||
echo __('Newest:', 'swi_foot_matchdata') . ' ' . date_i18n(get_option('date_format') . ' ' . get_option('time_format'), $newest_timestamp) . '</p>';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function display_finished_matches()
|
||||
{
|
||||
$finished = get_option('swi_foot_finished_matches', array());
|
||||
if (empty($finished)) {
|
||||
echo '<p>' . __('No finished match data stored.', 'swi_foot_matchdata') . '</p>';
|
||||
return;
|
||||
}
|
||||
|
||||
echo '<table class="widefat fixed striped">';
|
||||
echo '<thead>
|
||||
<tr>
|
||||
<th>' . __('Match ID', 'swi_foot_matchdata') . '</th>
|
||||
<th>' . __('Saved At', 'swi_foot_matchdata') . '</th>
|
||||
<th>' . __('Players', 'swi_foot_matchdata') . '</th>
|
||||
<th>' . __('Bench', 'swi_foot_matchdata') . '</th>
|
||||
<th>' . __('Events', 'swi_foot_matchdata') . '</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead><tbody>';
|
||||
|
||||
foreach ($finished as $mid => $item) {
|
||||
$players = count($item['roster']['players'] ?? array());
|
||||
$bench = count($item['roster']['bench'] ?? array());
|
||||
$events = count($item['events'] ?? array());
|
||||
$saved = !empty($item['saved_at']) ? date_i18n(get_option('date_format') . ' ' . get_option('time_format'), $item['saved_at']) : '-';
|
||||
|
||||
echo '<tr>
|
||||
<td>' . esc_html($mid) . '</td>
|
||||
<td>' . esc_html($saved) . '</td>
|
||||
<td>' . esc_html($players) . '</td>
|
||||
<td>' . esc_html($bench) . '</td>
|
||||
<td>' . esc_html($events) . '</td>
|
||||
<td>
|
||||
<button class="button swi-foot-clear-finished" data-id="' . esc_attr($mid) . '">' . __('Delete', 'swi_foot_matchdata') . '</button>
|
||||
</td>
|
||||
</tr>';
|
||||
}
|
||||
|
||||
echo '</tbody></table>';
|
||||
echo '<p><button id="swi-foot-clear-all-finished" class="button button-secondary">' . __('Clear All Finished Matches', 'swi_foot_matchdata') . '</button></p>';
|
||||
|
||||
// Include inline JS for actions
|
||||
?>
|
||||
<script>
|
||||
jQuery(function($) {
|
||||
$('.swi-foot-clear-finished').on('click', function() {
|
||||
if (!confirm('<?php _e('Delete this finished match data?', 'swi_foot_matchdata'); ?>')) return;
|
||||
var btn = $(this);
|
||||
var matchId = btn.data('id');
|
||||
fetch(swi_foot_ajax.rest_url.replace(/\/$/, '') + '/admin/finished/' + encodeURIComponent(matchId), {
|
||||
method: 'DELETE',
|
||||
credentials: 'same-origin',
|
||||
headers: { 'X-WP-Nonce': swi_foot_ajax.rest_nonce }
|
||||
}).then(function(resp) { return resp.json(); }).then(function(resp) {
|
||||
if (resp && resp.success) {
|
||||
btn.closest('tr').fadeOut();
|
||||
} else {
|
||||
alert(resp.error || 'Error');
|
||||
}
|
||||
}).catch(function() { alert('Network error'); });
|
||||
});
|
||||
|
||||
$('#swi-foot-clear-all-finished').on('click', function() {
|
||||
if (!confirm('<?php _e('Clear all finished match data?', 'swi_foot_matchdata'); ?>')) return;
|
||||
fetch(swi_foot_ajax.rest_url.replace(/\/$/, '') + '/admin/finished', {
|
||||
method: 'DELETE',
|
||||
credentials: 'same-origin',
|
||||
headers: { 'X-WP-Nonce': swi_foot_ajax.rest_nonce }
|
||||
}).then(function(resp) { return resp.json(); }).then(function(resp) {
|
||||
if (resp && resp.success) {
|
||||
location.reload();
|
||||
} else {
|
||||
alert(resp.error || 'Error');
|
||||
}
|
||||
}).catch(function() { alert('Network error'); });
|
||||
});
|
||||
});
|
||||
</script>
|
||||
<?php
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,559 @@
|
||||
<?php
|
||||
|
||||
class Swi_Foot_API
|
||||
{
|
||||
|
||||
private $base_url;
|
||||
private $username;
|
||||
private $password;
|
||||
private $verein_id;
|
||||
private $season_id;
|
||||
private $cache_duration;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->base_url = get_option('swi_foot_api_base_url', 'https://stg-club-api-services.football.ch');
|
||||
$this->username = get_option('swi_foot_api_username');
|
||||
$this->password = get_option('swi_foot_api_password');
|
||||
$this->verein_id = get_option('swi_foot_verein_id');
|
||||
$this->season_id = get_option('swi_foot_season_id', date('Y'));
|
||||
$this->cache_duration = get_option('swi_foot_match_cache_duration', 30);
|
||||
|
||||
// AJAX actions were migrated to REST endpoints (see includes/class-swi-foot-rest.php)
|
||||
|
||||
// Hook to update match cache on page load
|
||||
add_action('wp', array($this, 'maybe_update_match_cache'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a full URL for the given endpoint, avoiding duplicate /api segments
|
||||
*/
|
||||
private function build_url($endpoint)
|
||||
{
|
||||
$base = rtrim($this->base_url, '/');
|
||||
|
||||
// If base already contains '/api' and endpoint starts with '/api', strip the endpoint prefix
|
||||
if (strpos($base, '/api') !== false && strpos($endpoint, '/api') === 0) {
|
||||
$endpoint = preg_replace('#^/api#', '', $endpoint);
|
||||
}
|
||||
|
||||
return $base . '/' . ltrim($endpoint, '/');
|
||||
}
|
||||
|
||||
public function maybe_update_match_cache()
|
||||
{
|
||||
// Only run on single posts/pages
|
||||
if (!is_single() && !is_page()) {
|
||||
return;
|
||||
}
|
||||
|
||||
global $post;
|
||||
if (!$post) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if content has match shortcodes
|
||||
if (
|
||||
!has_shortcode($post->post_content, 'swi_foot_match') &&
|
||||
!has_shortcode($post->post_content, 'swi_foot_match_home_team') &&
|
||||
!has_shortcode($post->post_content, 'swi_foot_match_away_team') &&
|
||||
!has_shortcode($post->post_content, 'swi_foot_match_date') &&
|
||||
!has_shortcode($post->post_content, 'swi_foot_match_time') &&
|
||||
!has_shortcode($post->post_content, 'swi_foot_match_venue') &&
|
||||
!has_shortcode($post->post_content, 'swi_foot_match_score') &&
|
||||
!has_shortcode($post->post_content, 'swi_foot_match_status') &&
|
||||
!has_shortcode($post->post_content, 'swi_foot_match_league') &&
|
||||
!has_shortcode($post->post_content, 'swi_foot_match_round')
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Extract match IDs from shortcodes
|
||||
$match_ids = array();
|
||||
$patterns = array(
|
||||
'/\[swi_foot_match[^\]]*match_id=["\']([^"\']+)["\'][^\]]*\]/',
|
||||
'/\[swi_foot_match_home_team[^\]]*match_id=["\']([^"\']+)["\'][^\]]*\]/',
|
||||
'/\[swi_foot_match_away_team[^\]]*match_id=["\']([^"\']+)["\'][^\]]*\]/',
|
||||
'/\[swi_foot_match_date[^\]]*match_id=["\']([^"\']+)["\'][^\]]*\]/',
|
||||
'/\[swi_foot_match_time[^\]]*match_id=["\']([^"\']+)["\'][^\]]*\]/',
|
||||
'/\[swi_foot_match_venue[^\]]*match_id=["\']([^"\']+)["\'][^\]]*\]/',
|
||||
'/\[swi_foot_match_score[^\]]*match_id=["\']([^"\']+)["\'][^\]]*\]/',
|
||||
'/\[swi_foot_match_status[^\]]*match_id=["\']([^"\']+)["\'][^\]]*\]/',
|
||||
'/\[swi_foot_match_league[^\]]*match_id=["\']([^"\']+)["\'][^\]]*\]/',
|
||||
'/\[swi_foot_match_round[^\]]*match_id=["\']([^"\']+)["\'][^\]]*\]/'
|
||||
);
|
||||
|
||||
foreach ($patterns as $pattern) {
|
||||
preg_match_all($pattern, $post->post_content, $matches);
|
||||
if (!empty($matches[1])) {
|
||||
$match_ids = array_merge($match_ids, $matches[1]);
|
||||
}
|
||||
}
|
||||
|
||||
// Update cache for found match IDs
|
||||
$match_ids = array_unique($match_ids);
|
||||
foreach ($match_ids as $match_id) {
|
||||
$this->get_match_details($match_id, false); // Don't force refresh unless needed
|
||||
}
|
||||
}
|
||||
|
||||
private function get_access_token()
|
||||
{
|
||||
$token = get_transient('swi_foot_access_token');
|
||||
|
||||
// Transient handles expiration; return if present
|
||||
if ($token) {
|
||||
return $token;
|
||||
}
|
||||
|
||||
// Fetch a new token
|
||||
return $this->refresh_access_token();
|
||||
}
|
||||
|
||||
private function refresh_access_token()
|
||||
{
|
||||
if (empty($this->username) || empty($this->password)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$response = wp_remote_post($this->build_url('/api/token'), array(
|
||||
'body' => json_encode(array(
|
||||
'applicationKey' => $this->username,
|
||||
'applicationPass' => $this->password
|
||||
)),
|
||||
'headers' => array(
|
||||
'Content-Type' => 'application/json'
|
||||
),
|
||||
'timeout' => 30
|
||||
));
|
||||
|
||||
if (is_wp_error($response)) {
|
||||
error_log('Swiss Football API: Token request failed - ' . $response->get_error_message());
|
||||
return false;
|
||||
}
|
||||
|
||||
$response_code = wp_remote_retrieve_response_code($response);
|
||||
if ($response_code !== 200) {
|
||||
$body_debug = wp_remote_retrieve_body($response);
|
||||
error_log('Swiss Football API: Token request returned ' . $response_code . ' - body: ' . substr($body_debug, 0, 1000));
|
||||
return false;
|
||||
}
|
||||
|
||||
$body = wp_remote_retrieve_body($response);
|
||||
|
||||
// Try JSON decode first, fallback to trimmed string
|
||||
$maybe_json = json_decode($body, true);
|
||||
if (is_string($maybe_json) && $maybe_json !== '') {
|
||||
$token = $maybe_json;
|
||||
} elseif (is_string($body) && $body !== '') {
|
||||
$token = trim($body, '"');
|
||||
} else {
|
||||
$token = false;
|
||||
}
|
||||
|
||||
if ($token) {
|
||||
// Store token in transient (30 minutes). WP constant MINUTE_IN_SECONDS available.
|
||||
set_transient('swi_foot_access_token', $token, 30 * MINUTE_IN_SECONDS);
|
||||
return $token;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private function api_request($endpoint, $params = array(), $retry_on_401 = true)
|
||||
{
|
||||
$token = $this->get_access_token();
|
||||
|
||||
if (!$token) {
|
||||
return new WP_Error('auth_failed', 'Failed to authenticate with Swiss Football API');
|
||||
}
|
||||
|
||||
$url = $this->build_url($endpoint);
|
||||
if (!empty($params)) {
|
||||
$url .= '?' . http_build_query($params);
|
||||
}
|
||||
|
||||
$response = wp_remote_get($url, array(
|
||||
'headers' => array(
|
||||
'X-User-Token' => $token,
|
||||
'Content-Type' => 'application/json'
|
||||
),
|
||||
'timeout' => 30
|
||||
));
|
||||
|
||||
if (is_wp_error($response)) {
|
||||
return $response;
|
||||
}
|
||||
|
||||
$response_code = wp_remote_retrieve_response_code($response);
|
||||
|
||||
// Handle 401 Unauthorized - token may have expired
|
||||
if ($response_code === 401 && $retry_on_401) {
|
||||
error_log('Swiss Football API: Received 401 Unauthorized, attempting to refresh token');
|
||||
// Clear the expired token
|
||||
delete_transient('swi_foot_access_token');
|
||||
// Refresh the token
|
||||
$new_token = $this->refresh_access_token();
|
||||
if ($new_token) {
|
||||
// Retry the request with the new token (prevent infinite recursion with retry_on_401 = false)
|
||||
return $this->api_request($endpoint, $params, false);
|
||||
}
|
||||
return new WP_Error('auth_failed', 'Authentication failed: Token refresh unsuccessful');
|
||||
}
|
||||
|
||||
if ($response_code !== 200) {
|
||||
$body_debug = wp_remote_retrieve_body($response);
|
||||
error_log('Swiss Football API: Request to ' . $url . ' returned ' . $response_code . ' - body: ' . substr($body_debug, 0, 1000));
|
||||
return new WP_Error('api_error', 'API request failed with code ' . $response_code);
|
||||
}
|
||||
|
||||
$body = wp_remote_retrieve_body($response);
|
||||
return json_decode($body, true);
|
||||
}
|
||||
|
||||
public function get_teams()
|
||||
{
|
||||
// Prefer transient-based caching for teams (24 hours)
|
||||
$teams = get_transient('swi_foot_teams');
|
||||
if ($teams !== false) {
|
||||
return $teams;
|
||||
}
|
||||
|
||||
if (empty($this->verein_id)) {
|
||||
return new WP_Error('no_verein_id', 'Verein ID not configured');
|
||||
}
|
||||
|
||||
$teams = $this->api_request('/api/team/list', array(
|
||||
'ClubId' => $this->verein_id,
|
||||
'SeasonId' => $this->season_id
|
||||
));
|
||||
|
||||
if (!is_wp_error($teams) && is_array($teams)) {
|
||||
set_transient('swi_foot_teams', $teams, 24 * HOUR_IN_SECONDS);
|
||||
}
|
||||
|
||||
return $teams;
|
||||
}
|
||||
|
||||
public function get_standings($team_id)
|
||||
{
|
||||
if (empty($this->verein_id)) {
|
||||
return new WP_Error('no_verein_id', 'Verein ID not configured');
|
||||
}
|
||||
|
||||
return $this->api_request('/api/club/ranking', array(
|
||||
'ClubId' => $this->verein_id,
|
||||
'SeasonId' => $this->season_id,
|
||||
'TeamId' => $team_id
|
||||
));
|
||||
}
|
||||
|
||||
public function get_schedule($team_id)
|
||||
{
|
||||
if (empty($this->verein_id)) {
|
||||
return new WP_Error('no_verein_id', 'Verein ID not configured');
|
||||
}
|
||||
|
||||
return $this->api_request('/api/club/schedule', array(
|
||||
'ClubId' => $this->verein_id,
|
||||
'SeasonId' => $this->season_id,
|
||||
'TeamId' => $team_id
|
||||
));
|
||||
}
|
||||
|
||||
public function get_match_details($match_id, $force_refresh = false)
|
||||
{
|
||||
// If we've permanently saved finished match data, return it (do not call API)
|
||||
$finished = $this->get_finished_match_data($match_id);
|
||||
if (!$force_refresh && $finished && isset($finished['match'])) {
|
||||
return $finished['match'];
|
||||
}
|
||||
|
||||
$transient_key = 'swi_foot_match_' . $match_id;
|
||||
|
||||
if (!$force_refresh) {
|
||||
$cached = get_transient($transient_key);
|
||||
if ($cached !== false) {
|
||||
return isset($cached['data']) ? $cached['data'] : $cached;
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch fresh data from API
|
||||
$match_data = $this->api_request('/api/match/' . $match_id);
|
||||
|
||||
if (!is_wp_error($match_data)) {
|
||||
// Cache the data as transient for configured duration
|
||||
$cache_payload = array(
|
||||
'data' => $match_data,
|
||||
'cached_at' => time()
|
||||
);
|
||||
set_transient($transient_key, $cache_payload, (int) $this->cache_duration);
|
||||
|
||||
// Maintain index of cached match ids for management / clearing
|
||||
$keys = get_transient('swi_foot_match_keys');
|
||||
if (!is_array($keys)) {
|
||||
$keys = array();
|
||||
}
|
||||
if (!in_array($match_id, $keys, true)) {
|
||||
$keys[] = $match_id;
|
||||
// No expiration for keys index so we can clear caches reliably
|
||||
set_transient('swi_foot_match_keys', $keys, 0);
|
||||
}
|
||||
|
||||
// If match finished, persist it permanently (store match details inside finished data)
|
||||
if (!empty($match_data['hasMatchEnded'])) {
|
||||
// Ensure finished entry exists and include match details
|
||||
$saved = $this->get_finished_match_data($match_id) ?: array();
|
||||
$saved['match'] = $match_data;
|
||||
// Keep any existing roster/events if present
|
||||
if (!isset($saved['roster'])) $saved['roster'] = array();
|
||||
if (!isset($saved['events'])) $saved['events'] = array();
|
||||
$all = get_option('swi_foot_finished_matches', array());
|
||||
$all[$match_id] = array_merge($all[$match_id] ?? array(), $saved, array('saved_at' => time()));
|
||||
update_option('swi_foot_finished_matches', $all);
|
||||
// Also delete transient cache to force reads from permanent store
|
||||
delete_transient($transient_key);
|
||||
}
|
||||
}
|
||||
|
||||
return $match_data;
|
||||
}
|
||||
|
||||
public function get_cached_match_data($match_id)
|
||||
{
|
||||
// Prefer finished permanent store
|
||||
$finished = $this->get_finished_match_data($match_id);
|
||||
if ($finished && isset($finished['match'])) {
|
||||
return $finished['match'];
|
||||
}
|
||||
|
||||
$transient_key = 'swi_foot_match_' . $match_id;
|
||||
$cached = get_transient($transient_key);
|
||||
if ($cached === false) return null;
|
||||
return isset($cached['data']) ? $cached['data'] : $cached;
|
||||
}
|
||||
|
||||
public function get_match_players($match_id)
|
||||
{
|
||||
return $this->api_request('/api/match/' . $match_id . '/players');
|
||||
}
|
||||
|
||||
public function get_match_bench($match_id)
|
||||
{
|
||||
return $this->api_request('/api/match/' . $match_id . '/bench');
|
||||
}
|
||||
|
||||
public function get_match_events($match_id)
|
||||
{
|
||||
return $this->api_request('/api/match/' . $match_id . '/events');
|
||||
}
|
||||
|
||||
public function get_team_picture($team_id)
|
||||
{
|
||||
// Special handling for team picture endpoint which returns 200 with data or 204 No Content
|
||||
$token = $this->get_access_token();
|
||||
|
||||
if (!$token) {
|
||||
error_log('Swiss Football API: get_team_picture - Failed to get access token');
|
||||
return null;
|
||||
}
|
||||
|
||||
$url = $this->build_url('/api/team/picture/' . $team_id);
|
||||
error_log('Swiss Football API: get_team_picture - Requesting URL: ' . $url);
|
||||
|
||||
$response = wp_remote_get($url, array(
|
||||
'headers' => array(
|
||||
'X-User-Token' => $token,
|
||||
'Content-Type' => 'application/json'
|
||||
),
|
||||
'timeout' => 30
|
||||
));
|
||||
|
||||
if (is_wp_error($response)) {
|
||||
error_log('Swiss Football API: get_team_picture - wp_remote_get error: ' . $response->get_error_message());
|
||||
return null;
|
||||
}
|
||||
|
||||
$response_code = wp_remote_retrieve_response_code($response);
|
||||
error_log('Swiss Football API: get_team_picture - Response code: ' . $response_code);
|
||||
|
||||
// Handle 401 Unauthorized - token may have expired
|
||||
if ($response_code === 401) {
|
||||
error_log('Swiss Football API: get_team_picture - Received 401, refreshing token and retrying');
|
||||
delete_transient('swi_foot_access_token');
|
||||
$new_token = $this->refresh_access_token();
|
||||
if ($new_token) {
|
||||
// Recursively retry with new token
|
||||
return $this->get_team_picture($team_id);
|
||||
}
|
||||
error_log('Swiss Football API: get_team_picture - Token refresh failed');
|
||||
return null;
|
||||
}
|
||||
|
||||
// Handle 204 No Content - team has no picture
|
||||
if ($response_code === 204) {
|
||||
error_log('Swiss Football API: get_team_picture - Team ' . $team_id . ' has no picture (204)');
|
||||
return null;
|
||||
}
|
||||
|
||||
// Handle other error responses
|
||||
if ($response_code !== 200) {
|
||||
$body = wp_remote_retrieve_body($response);
|
||||
error_log('Swiss Football API: get_team_picture - Request failed with code ' . $response_code . ' - body: ' . substr($body, 0, 500));
|
||||
return null;
|
||||
}
|
||||
|
||||
// Success - return the base64 image data
|
||||
$body = wp_remote_retrieve_body($response);
|
||||
error_log('Swiss Football API: get_team_picture - Successfully retrieved image, size: ' . strlen($body) . ' bytes');
|
||||
|
||||
return $body;
|
||||
}
|
||||
|
||||
public function get_commons_ids()
|
||||
{
|
||||
return $this->api_request('/api/commons/ids');
|
||||
}
|
||||
|
||||
/**
|
||||
* Debug helper — return token and resolved endpoint info for admin debugging.
|
||||
* Note: only for admin use; not called on public endpoints.
|
||||
*/
|
||||
public function debug_get_token_info()
|
||||
{
|
||||
$token = $this->get_access_token();
|
||||
$team_list_url = $this->build_url('/api/team/list') . '?' . http_build_query(array(
|
||||
'ClubId' => $this->verein_id,
|
||||
'SeasonId' => $this->season_id
|
||||
));
|
||||
|
||||
return array(
|
||||
'token_present' => ($token !== false && !empty($token)),
|
||||
'token_preview' => is_string($token) ? substr($token, 0, 32) : null,
|
||||
'base_url' => $this->base_url,
|
||||
'team_list_url' => $team_list_url
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Public helper to test whether we can obtain a valid access token
|
||||
* Returns true on success, false otherwise.
|
||||
*/
|
||||
public function test_connection()
|
||||
{
|
||||
// Actually test the connection by making an API request
|
||||
$result = $this->api_request('/api/teams', array('vereinId' => $this->verein_id));
|
||||
|
||||
// Check if the request was successful (not an error)
|
||||
if (is_wp_error($result)) {
|
||||
error_log('Swiss Football API: Connection test failed - ' . $result->get_error_message());
|
||||
return false;
|
||||
}
|
||||
|
||||
// Verify we got a response (array means success)
|
||||
return is_array($result);
|
||||
}
|
||||
|
||||
public function get_current_match($team_id)
|
||||
{
|
||||
$schedule = $this->get_schedule($team_id);
|
||||
|
||||
if (is_wp_error($schedule) || empty($schedule)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$current_time = time();
|
||||
$current_match = null;
|
||||
|
||||
// Look for upcoming match within 5 days or recent match within 2 days
|
||||
foreach ($schedule as $match) {
|
||||
$match_time = strtotime($match['matchDate']);
|
||||
|
||||
// Upcoming within 5 days
|
||||
if ($match_time > $current_time && ($match_time - $current_time) <= (5 * 24 * 60 * 60)) {
|
||||
$current_match = $match;
|
||||
break;
|
||||
}
|
||||
|
||||
// Recent within 2 days
|
||||
if ($match_time <= $current_time && ($current_time - $match_time) <= (2 * 24 * 60 * 60)) {
|
||||
$current_match = $match;
|
||||
}
|
||||
}
|
||||
|
||||
return $current_match;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Save finished match data permanently (roster + events in one structure)
|
||||
*/
|
||||
public function save_finished_match_data($match_id, $roster_data, $events_data)
|
||||
{
|
||||
if (empty($match_id)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$saved_matches = get_option('swi_foot_finished_matches', array());
|
||||
$existing = isset($saved_matches[$match_id]) ? $saved_matches[$match_id] : array();
|
||||
|
||||
$existing['roster'] = $roster_data;
|
||||
$existing['events'] = $events_data;
|
||||
$existing['saved_at'] = time();
|
||||
|
||||
// If match details are cached as transient, try to move them into permanent record
|
||||
$transient_key = 'swi_foot_match_' . $match_id;
|
||||
$match_details = get_transient($transient_key);
|
||||
if ($match_details !== false) {
|
||||
$existing['match'] = $match_details;
|
||||
delete_transient($transient_key);
|
||||
}
|
||||
|
||||
$saved_matches[$match_id] = $existing;
|
||||
update_option('swi_foot_finished_matches', $saved_matches);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve saved finished match data if available
|
||||
*/
|
||||
public function get_finished_match_data($match_id)
|
||||
{
|
||||
$saved_matches = get_option('swi_foot_finished_matches', array());
|
||||
return $saved_matches[$match_id] ?? null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve effective context for a post: season, team_id, match_id.
|
||||
* Priority: post meta 'swi_foot_context' -> plugin option defaults
|
||||
*/
|
||||
function swi_foot_resolve_context($post_id = null)
|
||||
{
|
||||
if (empty($post_id)) {
|
||||
$post_id = get_the_ID();
|
||||
}
|
||||
|
||||
$stored = get_post_meta($post_id, 'swi_foot_context', true);
|
||||
$season = null;
|
||||
$team_id = null;
|
||||
$match_id = null;
|
||||
|
||||
if (is_array($stored)) {
|
||||
$season = !empty($stored['season']) ? $stored['season'] : null;
|
||||
$team_id = !empty($stored['team_id']) ? $stored['team_id'] : null;
|
||||
$match_id = !empty($stored['match_id']) ? $stored['match_id'] : null;
|
||||
}
|
||||
|
||||
if (empty($season)) {
|
||||
$season = get_option('swi_foot_season_id', date('Y'));
|
||||
}
|
||||
|
||||
return array(
|
||||
'season' => $season,
|
||||
'team_id' => $team_id,
|
||||
'match_id' => $match_id
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,479 @@
|
||||
<?php
|
||||
|
||||
class Swi_Foot_Blocks
|
||||
{
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
// Register blocks immediately when this object is constructed.
|
||||
// The plugin constructs this class during the plugin init sequence,
|
||||
// so calling register_blocks() now ensures scripts/styles are registered
|
||||
// before `enqueue_block_editor_assets` runs later in the request.
|
||||
$this->register_blocks();
|
||||
add_action('wp_enqueue_scripts', array($this, 'enqueue_scripts'));
|
||||
// Editor assets enqueued below; data endpoints are provided via REST (see includes/class-swi-foot-rest.php)
|
||||
add_action('enqueue_block_editor_assets', array($this, 'enqueue_editor_assets'));
|
||||
}
|
||||
|
||||
public function register_blocks()
|
||||
{
|
||||
// Register blocks from metadata (block.json) and provide server-side render callbacks
|
||||
$base = dirname(__DIR__) . '/blocks';
|
||||
|
||||
// Register editor script and styles from webpack build
|
||||
$editor_js_url = SWI_FOOT_PLUGIN_URL . 'assets/build/editor-blocks.js';
|
||||
|
||||
wp_register_script(
|
||||
'swi-foot-editor-blocks',
|
||||
$editor_js_url,
|
||||
array('wp-blocks', 'wp-element', 'wp-block-editor', 'wp-components', 'wp-i18n', 'wp-data', 'wp-rich-text'),
|
||||
SWI_FOOT_PLUGIN_VERSION
|
||||
);
|
||||
|
||||
$editor_css = SWI_FOOT_PLUGIN_URL . 'assets/blocks.css';
|
||||
wp_register_style('swi-foot-editor-styles', $editor_css, array(), SWI_FOOT_PLUGIN_VERSION);
|
||||
|
||||
if ( file_exists( $base . '/standings/block.json' ) ) {
|
||||
register_block_type_from_metadata( $base . '/standings', array(
|
||||
'render_callback' => array( $this, 'render_standings_block' ),
|
||||
'editor_script' => 'swi-foot-editor-blocks',
|
||||
'editor_style' => 'swi-foot-editor-styles'
|
||||
) );
|
||||
}
|
||||
|
||||
if ( file_exists( $base . '/context/block.json' ) ) {
|
||||
register_block_type_from_metadata( $base . '/context', array(
|
||||
'render_callback' => function($attributes, $content, $block) {
|
||||
// Provide a transparent wrapper but push provider context to a global stack
|
||||
// so inner shortcodes and blocks can read it during rendering.
|
||||
$provided = is_array($attributes['swi_foot_context'] ?? null) ? $attributes['swi_foot_context'] : array();
|
||||
$post_defaults = function_exists('swi_foot_resolve_context') ? swi_foot_resolve_context(get_the_ID()) : array();
|
||||
$ctx = array_merge($post_defaults, $provided);
|
||||
|
||||
global $swi_foot_context_stack;
|
||||
if (!isset($swi_foot_context_stack) || !is_array($swi_foot_context_stack)) {
|
||||
$swi_foot_context_stack = array();
|
||||
}
|
||||
$swi_foot_context_stack[] = $ctx;
|
||||
|
||||
// Render inner blocks (so shortcodes executed within this scope can read the global)
|
||||
$rendered = do_blocks($content);
|
||||
|
||||
// Pop stack
|
||||
array_pop($swi_foot_context_stack);
|
||||
|
||||
return $rendered;
|
||||
},
|
||||
'editor_script' => 'swi-foot-editor-blocks',
|
||||
'editor_style' => 'swi-foot-editor-styles'
|
||||
) );
|
||||
}
|
||||
|
||||
if ( file_exists( $base . '/schedule/block.json' ) ) {
|
||||
register_block_type_from_metadata( $base . '/schedule', array(
|
||||
'render_callback' => array( $this, 'render_schedule_block' ),
|
||||
'editor_script' => 'swi-foot-editor-blocks',
|
||||
'editor_style' => 'swi-foot-editor-styles'
|
||||
) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Deprecated shortcode-inserter block
|
||||
* Kept for backwards compatibility only
|
||||
*/
|
||||
if ( file_exists( $base . '/shortcode-inserter/block.json' ) ) {
|
||||
register_block_type_from_metadata( $base . '/shortcode-inserter', array(
|
||||
'render_callback' => array( $this, 'render_shortcode_inserter_block' ),
|
||||
'editor_script' => 'swi-foot-editor-blocks',
|
||||
'editor_style' => 'swi-foot-editor-styles',
|
||||
'deprecated' => true,
|
||||
) );
|
||||
}
|
||||
|
||||
// Register additional blocks (editor-only save shortcodes)
|
||||
if ( file_exists( $base . '/team-data/block.json' ) ) {
|
||||
register_block_type_from_metadata( $base . '/team-data', array(
|
||||
'editor_script' => 'swi-foot-editor-blocks',
|
||||
'editor_style' => 'swi-foot-editor-styles'
|
||||
) );
|
||||
}
|
||||
|
||||
if ( file_exists( $base . '/match-roster/block.json' ) ) {
|
||||
register_block_type_from_metadata( $base . '/match-roster', array(
|
||||
'render_callback' => array( $this, 'render_match_roster_block' ),
|
||||
'editor_script' => 'swi-foot-editor-blocks',
|
||||
'editor_style' => 'swi-foot-editor-styles'
|
||||
) );
|
||||
}
|
||||
|
||||
if ( file_exists( $base . '/match-events/block.json' ) ) {
|
||||
register_block_type_from_metadata( $base . '/match-events', array(
|
||||
'render_callback' => array( $this, 'render_match_events_block' ),
|
||||
'editor_script' => 'swi-foot-editor-blocks',
|
||||
'editor_style' => 'swi-foot-editor-styles'
|
||||
) );
|
||||
}
|
||||
}
|
||||
|
||||
public function enqueue_scripts()
|
||||
{
|
||||
wp_enqueue_script('swi-foot-blocks', SWI_FOOT_PLUGIN_URL . 'assets/blocks.js', array('jquery'), SWI_FOOT_PLUGIN_VERSION, true);
|
||||
wp_enqueue_style('swi-foot-blocks', SWI_FOOT_PLUGIN_URL . 'assets/blocks.css', array(), SWI_FOOT_PLUGIN_VERSION);
|
||||
|
||||
// Localize REST API data for frontend event refresh polling
|
||||
wp_localize_script('swi-foot-blocks', 'swiFootRest', array(
|
||||
'rest_url' => rest_url('swi-foot/v1/'),
|
||||
'rest_nonce' => wp_create_nonce('wp_rest')
|
||||
));
|
||||
}
|
||||
|
||||
/*
|
||||
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'),
|
||||
SWI_FOOT_PLUGIN_VERSION
|
||||
);
|
||||
|
||||
wp_localize_script('swi-foot-editor-blocks', 'swiFootData', array(
|
||||
'teams' => $this->get_teams_for_editor(),
|
||||
'shortcodes' => array(
|
||||
'match' => __('Full Match Display', 'swi_foot_matchdata'),
|
||||
'match_home_team' => __('Home Team', 'swi_foot_matchdata'),
|
||||
'match_away_team' => __('Away Team', 'swi_foot_matchdata'),
|
||||
'match_date' => __('Match Date', 'swi_foot_matchdata'),
|
||||
'match_time' => __('Match Time', 'swi_foot_matchdata'),
|
||||
'match_venue' => __('Venue', 'swi_foot_matchdata'),
|
||||
'match_score' => __('Score', 'swi_foot_matchdata'),
|
||||
'match_status' => __('Status', 'swi_foot_matchdata'),
|
||||
'match_league' => __('League', 'swi_foot_matchdata'),
|
||||
'match_round' => __('Round', 'swi_foot_matchdata')
|
||||
)
|
||||
));
|
||||
}
|
||||
*/
|
||||
|
||||
public function enqueue_editor_assets()
|
||||
{
|
||||
// Enqueue the registered script handle (register_blocks() registers it on init)
|
||||
if ( ! wp_script_is( 'swi-foot-editor-blocks', 'enqueued' ) ) {
|
||||
wp_enqueue_script( 'swi-foot-editor-blocks' );
|
||||
}
|
||||
|
||||
// Enqueue editor styles globally to ensure block wrapper styling applies
|
||||
wp_enqueue_style( 'swi-foot-editor-styles' );
|
||||
|
||||
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'),
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
|
||||
private function get_teams_for_editor()
|
||||
{
|
||||
$api = new Swi_Foot_API();
|
||||
$teams = $api->get_teams();
|
||||
|
||||
if (is_wp_error($teams) || empty($teams)) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$team_options = array();
|
||||
foreach ($teams as $team) {
|
||||
$team_name = $team['teamName'] ?? '';
|
||||
$league = $team['teamLeagueName'] ?? '';
|
||||
|
||||
// If league name is available, append in parentheses
|
||||
if (!empty($league)) {
|
||||
$team_name .= ' (' . $league . ')';
|
||||
}
|
||||
|
||||
$team_options[] = array(
|
||||
'value' => $team['teamId'] ?? '',
|
||||
'label' => $team_name
|
||||
);
|
||||
}
|
||||
|
||||
return $team_options;
|
||||
}
|
||||
|
||||
public function render_standings_block($attributes, $content = '', $block = null)
|
||||
{
|
||||
// Resolve context: block context (from provider) overrides post meta defaults
|
||||
$provided = is_object($block) && !empty($block->context['swi-foot/context']) ? $block->context['swi-foot/context'] : null;
|
||||
$ctx = array();
|
||||
if (is_array($provided)) $ctx = $provided;
|
||||
// Merge with post-level defaults (post meta -> plugin options)
|
||||
$post_defaults = function_exists('swi_foot_resolve_context') ? swi_foot_resolve_context(get_the_ID()) : array();
|
||||
$ctx = array_merge($post_defaults, $ctx);
|
||||
|
||||
// Team must come from context only
|
||||
$team_id = $ctx['team_id'] ?? null;
|
||||
|
||||
if (empty($team_id)) {
|
||||
return '<div class="swi-foot-error">' . __('Standings: No team provided in container context.', 'swi_foot_matchdata') . '</div>';
|
||||
}
|
||||
|
||||
$api = new Swi_Foot_API();
|
||||
$standings = $api->get_standings($team_id);
|
||||
|
||||
if (is_wp_error($standings)) {
|
||||
return '<div class="swi-foot-error">' .
|
||||
sprintf(__('Error loading standings: %s', 'swi_foot_matchdata'), $standings->get_error_message()) .
|
||||
'</div>';
|
||||
}
|
||||
|
||||
if (empty($standings)) {
|
||||
return '<div class="swi-foot-notice">' . __('No standings data available.', 'swi_foot_matchdata') . '</div>';
|
||||
}
|
||||
|
||||
ob_start();
|
||||
?>
|
||||
<div class="swi-foot-standings">
|
||||
<h3><?php _e('Current Standings', 'swi_foot_matchdata'); ?></h3>
|
||||
<table class="swi-foot-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th><?php _e('Pos', 'swi_foot_matchdata'); ?></th>
|
||||
<th><?php _e('Team', 'swi_foot_matchdata'); ?></th>
|
||||
<th><?php _e('P', 'swi_foot_matchdata'); ?></th>
|
||||
<th><?php _e('W', 'swi_foot_matchdata'); ?></th>
|
||||
<th><?php _e('D', 'swi_foot_matchdata'); ?></th>
|
||||
<th><?php _e('L', 'swi_foot_matchdata'); ?></th>
|
||||
<th><?php _e('GF', 'swi_foot_matchdata'); ?></th>
|
||||
<th><?php _e('GA', 'swi_foot_matchdata'); ?></th>
|
||||
<th><?php _e('Pts', 'swi_foot_matchdata'); ?></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($standings as $team): ?>
|
||||
<tr<?php echo ($team['teamId'] == $team_id) ? ' class="highlight"' : ''; ?>>
|
||||
<td><?php echo esc_html($team['position']); ?></td>
|
||||
<td><?php echo esc_html($team['teamName']); ?></td>
|
||||
<td><?php echo esc_html($team['matches']); ?></td>
|
||||
<td><?php echo esc_html($team['wins']); ?></td>
|
||||
<td><?php echo esc_html($team['draws']); ?></td>
|
||||
<td><?php echo esc_html($team['losses']); ?></td>
|
||||
<td><?php echo esc_html($team['goalsFor']); ?></td>
|
||||
<td><?php echo esc_html($team['goalsAgainst']); ?></td>
|
||||
<td><strong><?php echo esc_html($team['points']); ?></strong></td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<?php
|
||||
return ob_get_clean();
|
||||
}
|
||||
|
||||
public function render_schedule_block($attributes, $content = '', $block = null)
|
||||
{
|
||||
$provided = is_object($block) && !empty($block->context['swi-foot/context']) ? $block->context['swi-foot/context'] : null;
|
||||
$ctx = array();
|
||||
if (is_array($provided)) $ctx = $provided;
|
||||
$post_defaults = function_exists('swi_foot_resolve_context') ? swi_foot_resolve_context(get_the_ID()) : array();
|
||||
$ctx = array_merge($post_defaults, $ctx);
|
||||
|
||||
// Team must come from context only
|
||||
$team_id = $ctx['team_id'] ?? null;
|
||||
$limit = isset($attributes['limit']) ? $attributes['limit'] : 5;
|
||||
|
||||
if (empty($team_id)) {
|
||||
return '<div class="swi-foot-error">' . __('Schedule: No team provided in container context.', 'swi_foot_matchdata') . '</div>';
|
||||
}
|
||||
|
||||
$api = new Swi_Foot_API();
|
||||
$events = $api->get_schedule($team_id);
|
||||
|
||||
if (is_wp_error($events)) {
|
||||
return '<div class="swi-foot-error">' .
|
||||
sprintf(__('Error loading schedule: %s', 'swi_foot_matchdata'), $events->get_error_message()) .
|
||||
'</div>';
|
||||
}
|
||||
|
||||
// Filter upcoming events and limit results
|
||||
$upcoming_events = array();
|
||||
if (is_array($events)) {
|
||||
foreach ($events as $event) {
|
||||
if (strtotime($event['matchDate']) >= time()) {
|
||||
$upcoming_events[] = $event;
|
||||
if (count($upcoming_events) >= $limit) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ob_start();
|
||||
?>
|
||||
<div class="swi-foot-schedule">
|
||||
<h3><?php _e('Upcoming Matches', 'swi_foot_matchdata'); ?></h3>
|
||||
<?php if (empty($upcoming_events)): ?>
|
||||
<p><?php _e('No upcoming matches scheduled.', 'swi_foot_matchdata'); ?></p>
|
||||
<?php else: ?>
|
||||
<div class="swi-foot-events">
|
||||
<?php foreach ($upcoming_events as $event): ?>
|
||||
<div class="swi-foot-event">
|
||||
<div class="event-date">
|
||||
<?php echo date_i18n(get_option('date_format') . ' ' . get_option('time_format'), strtotime($event['matchDate'])); ?>
|
||||
</div>
|
||||
<div class="event-teams">
|
||||
<?php echo esc_html($event['teamNameA'] ?? ''); ?> vs <?php echo esc_html($event['teamNameB'] ?? ''); ?>
|
||||
</div>
|
||||
<?php if (!empty($event['stadiumPlaygroundName'])): ?>
|
||||
<div class="event-location"><?php echo esc_html($event['stadiumPlaygroundName']); ?></div>
|
||||
<?php endif; ?>
|
||||
<?php if (!empty($event['leagueName'])): ?>
|
||||
<div class="event-league"><?php echo esc_html($event['leagueName']); ?></div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php
|
||||
return ob_get_clean();
|
||||
}
|
||||
|
||||
public function render_shortcode_inserter_block($attributes, $content = '', $block = null)
|
||||
{
|
||||
$provided = is_object($block) && !empty($block->context['swi-foot/context']) ? $block->context['swi-foot/context'] : null;
|
||||
$ctx = array();
|
||||
if (is_array($provided)) $ctx = $provided;
|
||||
$post_defaults = function_exists('swi_foot_resolve_context') ? swi_foot_resolve_context(get_the_ID()) : array();
|
||||
$ctx = array_merge($post_defaults, $ctx);
|
||||
|
||||
$shortcode_type = $attributes['shortcodeType'] ?? 'match';
|
||||
$match_id = $attributes['matchId'] ?? ($ctx['match_id'] ?? '');
|
||||
$team_id = $attributes['teamId'] ?? ($ctx['team_id'] ?? '');
|
||||
$show_next = !empty($attributes['showNext']);
|
||||
$format = $attributes['format'] ?? '';
|
||||
$separator = $attributes['separator'] ?? ':';
|
||||
|
||||
// Build shortcode based on attributes
|
||||
$shortcode_parts = array();
|
||||
|
||||
if (!empty($match_id)) {
|
||||
$shortcode_parts[] = 'match_id="' . esc_attr($match_id) . '"';
|
||||
}
|
||||
|
||||
if (!empty($team_id)) {
|
||||
$shortcode_parts[] = 'team_id="' . esc_attr($team_id) . '"';
|
||||
}
|
||||
|
||||
if ($show_next) {
|
||||
$shortcode_parts[] = 'show_next="true"';
|
||||
}
|
||||
|
||||
if (!empty($format) && in_array($shortcode_type, array('match_date', 'match_time'))) {
|
||||
$shortcode_parts[] = 'format="' . esc_attr($format) . '"';
|
||||
}
|
||||
|
||||
if (!empty($separator) && $separator !== ':' && $shortcode_type === 'match_score') {
|
||||
$shortcode_parts[] = 'separator="' . esc_attr($separator) . '"';
|
||||
}
|
||||
|
||||
$shortcode = '[swi_foot_' . $shortcode_type . (!empty($shortcode_parts) ? ' ' . implode(' ', $shortcode_parts) : '') . ']';
|
||||
|
||||
// Execute the shortcode and return inline wrapper so editors can insert it inside text
|
||||
$rendered = do_shortcode($shortcode);
|
||||
return '<span class="swi-foot-inline shortcode-inserter">' . $rendered . '</span>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Render match roster block
|
||||
* Gets match from container context, uses side attribute to determine which team to show
|
||||
*/
|
||||
public function render_match_roster_block($attributes, $content = '', $block = null)
|
||||
{
|
||||
// Get context from parent block container
|
||||
$provided = is_object($block) && !empty($block->context['swi-foot/context']) ? $block->context['swi-foot/context'] : null;
|
||||
$ctx = array();
|
||||
if (is_array($provided)) $ctx = $provided;
|
||||
$post_defaults = function_exists('swi_foot_resolve_context') ? swi_foot_resolve_context(get_the_ID()) : array();
|
||||
$ctx = array_merge($post_defaults, $ctx);
|
||||
|
||||
// Match must come from context
|
||||
$match_id = $ctx['match_id'] ?? '';
|
||||
$side = $attributes['side'] ?? 'home';
|
||||
$with_bench = $attributes['withBench'] ? 'true' : 'false';
|
||||
|
||||
if (empty($match_id)) {
|
||||
return '<div class="swi-foot-error">' . __('Match Roster: No match provided in container context.', 'swi_foot_matchdata') . '</div>';
|
||||
}
|
||||
|
||||
// Build shortcode using match from context and side attribute
|
||||
$shortcode = '[swi_foot_roster match_id="' . esc_attr($match_id) . '" side="' . esc_attr($side) . '"';
|
||||
if ($with_bench === 'true') {
|
||||
$shortcode .= ' with_bench="true"';
|
||||
}
|
||||
$shortcode .= ']';
|
||||
|
||||
// Process and return the shortcode
|
||||
return do_shortcode($shortcode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render match events block
|
||||
* Gets match from container context, displays live events for that match
|
||||
*/
|
||||
public function render_match_events_block($attributes, $content = '', $block = null)
|
||||
{
|
||||
// Get context from parent block container
|
||||
$provided = is_object($block) && !empty($block->context['swi-foot/context']) ? $block->context['swi-foot/context'] : null;
|
||||
$ctx = array();
|
||||
if (is_array($provided)) $ctx = $provided;
|
||||
$post_defaults = function_exists('swi_foot_resolve_context') ? swi_foot_resolve_context(get_the_ID()) : array();
|
||||
$ctx = array_merge($post_defaults, $ctx);
|
||||
|
||||
// Match must come from context
|
||||
$match_id = $ctx['match_id'] ?? '';
|
||||
$refresh_interval = $attributes['refreshInterval'] ?? 30;
|
||||
$event_order = $attributes['eventOrder'] ?? 'dynamic';
|
||||
|
||||
if (empty($match_id)) {
|
||||
return '<div class="swi-foot-error">' . __('Match Events: No match provided in container context.', 'swi_foot_matchdata') . '</div>';
|
||||
}
|
||||
|
||||
// Build shortcode using match from context
|
||||
$shortcode = '[swi_foot_events match_id="' . esc_attr($match_id) . '" refresh_interval="' . esc_attr($refresh_interval) . '" event_order="' . esc_attr($event_order) . '"]';
|
||||
|
||||
// Process and return the shortcode
|
||||
return do_shortcode($shortcode);
|
||||
}
|
||||
|
||||
private function render_team_selector($block_type)
|
||||
{
|
||||
$api = new Swi_Foot_API();
|
||||
$teams = $api->get_teams();
|
||||
|
||||
if (is_wp_error($teams) || empty($teams)) {
|
||||
return '<div class="swi-foot-error">' .
|
||||
__('Please configure your API settings and ensure teams are available.', 'swi_foot_matchdata') .
|
||||
'</div>';
|
||||
}
|
||||
|
||||
ob_start();
|
||||
?>
|
||||
<div class="swi-foot-team-selector">
|
||||
<p><?php printf(__('Please select a team to display %s:', 'swi_foot_matchdata'), $block_type); ?></p>
|
||||
<select onchange="swiFootSelectTeam(this.value, '<?php echo esc_attr($block_type); ?>')">
|
||||
<option value=""><?php _e('Select a team...', 'swi_foot_matchdata'); ?></option>
|
||||
<?php foreach ($teams as $team): ?>
|
||||
<option value="<?php echo esc_attr($team['teamId'] ?? ''); ?>">
|
||||
<?php echo esc_html($team['teamName'] ?? 'Unknown Team'); ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<?php
|
||||
return ob_get_clean();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,399 @@
|
||||
<?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);
|
||||
}
|
||||
)
|
||||
)
|
||||
));
|
||||
|
||||
// Also allow team_id as a path parameter: /wp-json/swi-foot/v1/matches/123
|
||||
register_rest_route('swi-foot/v1', '/matches/(?P<team_id>\d+)', 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);
|
||||
}
|
||||
)
|
||||
)
|
||||
));
|
||||
|
||||
// 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', '/commons-ids', array(
|
||||
'methods' => 'GET',
|
||||
'callback' => array($this, 'get_commons_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_commons_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')) {
|
||||
$ok = $api->test_connection();
|
||||
} else {
|
||||
$ok = false;
|
||||
}
|
||||
if ($ok) return rest_ensure_response(array('success' => true));
|
||||
return new WP_REST_Response(array('error' => 'Connection failed'), 400);
|
||||
}
|
||||
|
||||
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();
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user