['played', 'upcoming', 'live'], 'theme' => self::THEMES, 'group_by' => ['auto', 'round', 'group', 'day', 'none'], 'phase' => ['groups', 'playoff', 'friendly'], 'order' => ['asc', 'desc'], 'extend' => ['0', '1'], 'form' => ['yes', 'no', 'auto'], 'extra' => ['yes', 'no', 'auto'], 'diff' => ['yes', 'no', 'auto'], 'header' => ['yes', 'no'], 'live' => ['auto', 'no'], 'apply' => ['auto', 'no'], ]; private const DEFAULTS = ['limit' => '-1', 'upcoming' => '3', 'recent' => '3', 'team' => '0', 'group_by' => 'auto', 'order' => 'asc', 'form' => 'auto', 'extra' => 'auto', 'diff' => 'auto', 'header' => 'yes', 'apply' => 'auto']; public const QUERY_KEYS = ['limit', 'upcoming', 'recent', 'group', 'status', 'sort', 'theme', 'group_by', 'phase', 'team', 'order', 'extend', 'form', 'extra', 'diff', 'sections', 'header', 'apply']; private static int $instance = 0; private static bool $preview = false; private static bool $configAdded = false; public static function register(): void { foreach (Plugin::TYPES as $type) { add_shortcode('blyzer_' . $type, fn($atts) => self::output($type, is_array($atts) ? $atts : [])); } add_shortcode('blyzer_tournament', fn($atts) => self::output('tournament', is_array($atts) ? $atts : [])); add_action('init', function () { wp_register_style('blyzer-events', BLYZER_EVENTS_URL . 'assets/css/blyzer-events.css', [], Plugin::assetVersion('assets/css/blyzer-events.css')); wp_register_script('blyzer-events', BLYZER_EVENTS_URL . 'assets/js/blyzer-events.js', [], Plugin::assetVersion('assets/js/blyzer-events.js'), ['in_footer' => true, 'strategy' => 'defer']); }); } public static function types(): array { return array_merge(['tournament'], Plugin::TYPES); } public static function output(string $type, array $atts, int $instance = 0): string { if ($instance > 0) self::$instance = $instance - 1; try { if ($type === 'tournament') return self::tournament($atts); if (in_array($type, Plugin::TYPES, true)) return self::render($atts, $type); } catch (\Throwable $e) { if (defined('WP_DEBUG_LOG') && WP_DEBUG_LOG) { error_log('Blyzer Events: ' . $type . ' ' . $e->getMessage()); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log } } return ''; } public static function preview(int $eventId): string { self::$preview = true; $html = self::output('tournament', ['id' => $eventId, 'live' => 'no']); self::$preview = false; return $html; } public static function liveEnabled(): bool { return (int) get_option('blyzer_live', 1) === 1; } public static function placeholder(): string { return (string) get_option('blyzer_draft_placeholder', __('This event is not published yet.', 'blyzer-events')); } public static function defaultTheme(): string { $theme = (string) get_option('blyzer_theme', 'auto'); return in_array($theme, self::THEMES, true) ? $theme : 'auto'; } private static function enum($value, array $allowed, string $default = ''): string { $value = strtolower(trim((string) $value)); return in_array($value, $allowed, true) ? $value : $default; } private static function atts($atts, string $tag): array { $atts = shortcode_atts([ 'id' => 0, 'limit' => '', 'upcoming' => '', 'recent' => '', 'group' => '', 'status' => '', 'sort' => '', 'theme' => '', 'group_by' => '', 'phase' => '', 'team' => 0, 'order' => '', 'extend' => '', 'form' => '', 'extra' => '', 'diff' => '', 'sections' => '', 'header' => '', 'live' => '', 'apply' => '', ], is_array($atts) ? $atts : [], $tag); $group = sanitize_text_field((string) $atts['group']); $sections = array_values(array_intersect(array_map('trim', explode(',', strtolower((string) $atts['sections']))), self::PANELS)); return [ 'id' => absint($atts['id']), 'limit' => trim((string) $atts['limit']) === '' ? -1 : min(1000, absint($atts['limit'])), 'upcoming' => strtolower(trim((string) $atts['upcoming'])) === 'all' ? 'all' : (trim((string) $atts['upcoming']) === '' ? '3' : (string) min(100, absint($atts['upcoming']))), 'recent' => trim((string) $atts['recent']) === '' ? 3 : min(100, absint($atts['recent'])), 'group' => mb_substr($group, 0, 50), 'status' => self::enum($atts['status'], ['played', 'upcoming', 'live']), 'sort' => mb_substr(Data::statKey((string) $atts['sort']), 0, 20), 'theme' => self::enum($atts['theme'], self::THEMES, self::defaultTheme()), 'group_by' => self::enum($atts['group_by'], ['auto', 'round', 'group', 'day', 'none'], 'auto'), 'phase' => self::enum($atts['phase'], ['groups', 'playoff', 'friendly']), 'team' => absint($atts['team']), 'order' => self::enum($atts['order'], ['asc', 'desc'], 'asc'), 'extend' => self::enum($atts['extend'], ['0', '1']), 'form' => self::enum($atts['form'], ['yes', 'no', 'auto'], 'auto'), 'extra' => self::enum($atts['extra'], ['yes', 'no', 'auto'], 'auto'), 'diff' => self::enum($atts['diff'], ['yes', 'no', 'auto'], 'auto'), 'sections' => $sections, 'header' => self::enum($atts['header'], ['yes', 'no'], 'yes'), 'live' => self::enum($atts['live'], ['auto', 'no'], 'auto'), 'apply' => self::enum($atts['apply'], ['auto', 'no'], 'auto'), ]; } public static function published(int $eventId): bool { if ($eventId <= 0 || get_post_type($eventId) !== 'blyzer_event') return false; if (self::$preview && current_user_can('edit_post', $eventId)) return true; return get_post_status($eventId) === 'publish'; } private static function notice(): string { return '
' . '' . esc_html__('Results from', 'blyzer-events') . '' . '
'; } private static function externalLinks(string $html): string { return (string) preg_replace('/]*\btarget=)([^>]*\bhref="https:)/', ' $atts['id'], 'type' => $type, 'n' => self::$instance]; foreach (self::QUERY_KEYS as $key) { $value = is_array($atts[$key]) ? implode(',', $atts[$key]) : (string) $atts[$key]; if ($value === '' || $value === (self::DEFAULTS[$key] ?? null)) continue; $query[$key] = $value; } return http_build_query($query, '', '&', PHP_QUERY_RFC3986); } private static function html(int $eventId, string $type, array $atts, string $uid): string { try { return self::panel($eventId, $type, $atts, $uid); } catch (\Throwable $e) { if (defined('WP_DEBUG_LOG') && WP_DEBUG_LOG) { error_log('Blyzer Events: ' . $type . ' ' . $e->getMessage()); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log } return ''; } } private static function panel(int $eventId, string $type, array $atts, string $uid): string { $template = TemplateResolver::resolve($eventId, $type); if (trim($template) === '') return ''; $context = self::context($eventId, $type, $atts, $uid); $context['items'] = apply_filters('blyzer_events_items', $context['items'], $type, $eventId, $atts); $context['count'] = is_array($context['items']) ? count($context['items']) : 0; $context['hasItems'] = $context['count'] > 0; $context['uid'] = $uid; $context = apply_filters('blyzer_events_context', $context, $type, $eventId, $atts); $html = TemplateEngine::render($template, $context); return $template === DefaultTemplates::get($type) ? self::externalLinks(wp_kses($html, self::allowedHtml(), ['http', 'https'])) : wp_kses_post($html); } private static function context(int $eventId, string $type, array $atts, string $uid): array { switch ($type) { case 'event': $event = Data::event(Api::fetch($eventId, 'event')); if ($event && $atts['apply'] === 'no') $event['canApply'] = false; return ['event' => $event, 'items' => $event ? [$event] : []]; case 'teams': $items = Data::teams(Api::fetch($eventId, 'teams')); if ($atts['limit'] > 0) $items = array_slice($items, 0, $atts['limit']); return ['items' => $items]; case 'groups': $ctx = Data::groups(Api::fetch($eventId, 'groups'), $atts); if ($atts['limit'] > 0) $ctx['items'] = array_slice($ctx['items'], 0, $atts['limit']); return $ctx; case 'playoff_rounds': return Data::playoff(Api::fetch($eventId, 'playoff_rounds'), $atts, $uid); case 'matches': return Data::matches(Api::fetch($eventId, 'matches'), $atts, $uid) + ['moreUrl' => self::moreUrl($eventId)]; case 'players': $payload = Api::load($eventId, 'players'); if ($atts['limit'] === -1) $atts['limit'] = 10; return Data::players($payload['data'], $payload['meta'], $atts) + ['moreUrl' => self::moreUrl($eventId)]; case 'race_stages': return Data::raceStages(Api::fetch($eventId, 'race_stages')); case 'race_standings': $ctx = Data::raceStandings(Api::fetch($eventId, 'race_standings'), Api::fetch($eventId, 'teams')); if ($atts['limit'] > 0) $ctx['items'] = array_slice($ctx['items'], 0, $atts['limit']); return $ctx; } return ['items' => []]; } public static function tournament($atts): string { $atts = self::atts($atts, 'blyzer_tournament'); $eventId = $atts['id']; if (!self::published($eventId)) return self::notice(); $uid = self::uid($eventId); $event = Data::event(Api::fetch($eventId, 'event')); $counts = is_array($event['counts'] ?? null) ? $event['counts'] : null; $config = is_array($event['configuration'] ?? null) ? $event['configuration'] : []; $isRace = ($event['form'] ?? '') === 'race'; $has = function (string $countKey, string $type) use ($counts, $eventId): bool { if ($counts !== null && array_key_exists($countKey, $counts)) return (int) $counts[$countKey] > 0; return Api::fetch($eventId, $type) !== []; }; $panels = []; if (!$isRace && $has('groups', 'groups')) { $panels['groups'] = [Data::str($config['groupsTitle'] ?? '') ?: __('Groups', 'blyzer-events'), 'groups', []]; } if (!$isRace && $has('playoffRounds', 'playoff_rounds')) { $rounds = array_filter(Api::fetch($eventId, 'playoff_rounds'), 'is_array'); $extend = array_filter($rounds, fn($r) => !empty($r['extend'])); if (count($extend) < count($rounds)) { $panels['playoff'] = [Data::str($config['playoffTitle'] ?? '') ?: __('Playoff', 'blyzer-events'), 'playoff_rounds', ['extend' => '0']]; } if ($extend) { $panels['extra'] = [__('Additional playoff', 'blyzer-events'), 'playoff_rounds', ['extend' => '1']]; } } if ($isRace || $has('raceStages', 'race_stages')) { $panels['standings'] = [__('Standings', 'blyzer-events'), 'race_standings', []]; $panels['stages'] = [__('Stages', 'blyzer-events'), 'race_stages', []]; } if ($has('matches', 'matches')) { $panels['matches'] = [__('Matches', 'blyzer-events'), 'matches', []]; } if ($has('teams', 'teams')) { $panels['teams'] = [__('Teams', 'blyzer-events'), 'teams', []]; } if ($has('players', 'players')) { $panels['players'] = [__('Players', 'blyzer-events'), 'players', []]; } if ($atts['sections']) { $panels = array_intersect_key($panels, array_flip($atts['sections'])); } $out = ''; if ($atts['header'] === 'yes') { $out .= self::html($eventId, 'event', $atts, $uid); } if ($panels) { $nav = ''; $body = ''; $first = true; foreach ($panels as $key => [$label, $type, $extra]) { $id = $uid . '-' . $key; $nav .= '' . esc_html($label) . ''; $body .= '