strtolower(trim((string) $h, ". \t\n\r\0\x0B")), $hosts))); } public static function linkHosts(): array { $hosts = (array) apply_filters('blyzer_events_allowed_link_hosts', self::LINK_HOSTS); return array_values(array_filter(array_map(fn($h) => strtolower(trim((string) $h, ". \t\n\r\0\x0B")), $hosts))); } public static function cleanLink($value): ?string { $url = self::cleanUrl($value); if ($url === null) return null; return in_array(strtolower((string) wp_parse_url($url, PHP_URL_HOST)), self::linkHosts(), true) ? $url : null; } public static function cleanStream($value): ?string { $url = self::cleanLink($value); if ($url !== null || !is_string($value) || strlen($value) > 2048 || !preg_match('#^https://#i', $value)) return $url; $url = esc_url_raw($value, ['https']); if ($url === '') return null; $hosts = array_map('strtolower', (array) apply_filters('blyzer_events_allowed_stream_hosts', self::STREAM_HOSTS)); return in_array(strtolower((string) wp_parse_url($url, PHP_URL_HOST)), $hosts, true) ? $url : null; } public static function cleanUrl($value): ?string { if (!is_string($value) || $value === '' || strlen($value) > 2048) return null; if (!preg_match('#^https://#i', $value)) return null; $url = esc_url_raw($value, ['https']); if ($url === '') return null; $host = strtolower((string) wp_parse_url($url, PHP_URL_HOST)); if ($host === '') return null; foreach (self::allowedHosts() as $allowed) { if ($host === $allowed || str_ends_with($host, '.' . $allowed)) return $url; } return null; } public static function clean(array $item): array { foreach ($item as $key => $value) { if (is_array($value)) { $item[$key] = self::clean($value); } elseif (is_string($key) && preg_match(self::STREAM_KEYS, $key)) { $item[$key] = self::cleanStream($value); } elseif (is_string($key) && preg_match(self::LINK_KEYS, $key)) { $item[$key] = self::cleanLink($value); } elseif (is_string($key) && preg_match(self::URL_KEYS, $key)) { $item[$key] = self::cleanUrl($value); } elseif (is_string($value)) { $item[$key] = wp_check_invalid_utf8($value); } elseif (!is_scalar($value) && $value !== null) { $item[$key] = null; } } return $item; } public static function rows(array $items): array { return array_map([self::class, 'clean'], array_values(array_filter($items, 'is_array'))); } public static function str($value): string { return is_scalar($value) ? trim((string) $value) : ''; } public static function initials(string $title, string $abbr = ''): string { $abbr = self::str($abbr); if ($abbr !== '' && mb_strlen($abbr) <= 3) return mb_strtoupper($abbr); $words = preg_split('/[^\p{L}\p{N}]+/u', $title, -1, PREG_SPLIT_NO_EMPTY) ?: []; if (!$words) return '?'; if (count($words) === 1) return mb_strtoupper(mb_substr($words[0], 0, 2)); return mb_strtoupper(mb_substr($words[0], 0, 1) . mb_substr($words[1], 0, 1)); } public static function timestamp($raw): ?int { if (is_int($raw) || (is_string($raw) && preg_match('/^\d{9,11}$/', $raw))) return (int) $raw; if (!is_string($raw) || $raw === '') return null; $ts = strtotime($raw); return $ts === false ? null : $ts; } public static function date(?int $ts, string $format = ''): string { if ($ts === null) return ''; return (string) wp_date($format !== '' ? $format : (string) get_option('date_format'), $ts); } public static function time(?int $ts): string { return $ts === null ? '' : (string) wp_date((string) get_option('time_format'), $ts); } public static function number($value): string { if ($value === null || $value === '' || !is_numeric($value)) return ''; $f = (float) $value; $decimals = 0; if (floor($f) != $f) $decimals = strlen(rtrim(substr(sprintf('%.3F', $f - floor($f)), 2), '0')); return number_format_i18n($f, min(3, $decimals)); } public static function side(array $row, string $prefix, string $labelKey, $score, bool $won, bool $lost, bool $skip = false): array { $title = self::str($row['team' . $prefix . 'Title'] ?? ''); $label = self::str($row[$labelKey] ?? ''); $hasTeam = $title !== ''; $name = $hasTeam ? $title : ($label !== '' ? $label : ($skip ? __('bye', 'blyzer-events') : __('TBD', 'blyzer-events'))); $scoreLabel = ($score === null || $score === '') ? '' : self::str($score); $cls = []; if (!$hasTeam) $cls[] = $skip ? 'is-bye' : 'is-tbd'; if ($won) $cls[] = 'is-winner'; if ($lost) $cls[] = 'is-loser'; return [ 'id' => $row['team' . $prefix . 'Id'] ?? null, 'name' => $name, 'hasTeam' => $hasTeam, 'url' => $hasTeam ? ($row['team' . $prefix . 'Url'] ?? null) : null, 'logo' => $hasTeam ? ($row['team' . $prefix . 'Logo'] ?? null) : null, 'flag' => $row['team' . $prefix . 'NationalityFlag'] ?? null, 'initials' => $hasTeam ? self::initials($title, self::str($row['team' . $prefix . 'Abbr'] ?? '')) : '', 'score' => $scoreLabel, 'scoreLabel' => $scoreLabel !== '' ? $scoreLabel : '–', 'isWinner' => $won, 'isLoser' => $lost, 'cls' => implode(' ', $cls), ]; } public static function event(array $event): array { $event = self::clean($event); if ($event === []) return []; $title = self::str($event['title'] ?? ''); $start = self::timestamp($event['startAt'] ?? null); $end = self::timestamp($event['endAt'] ?? null); $live = !empty($event['live']); $inTime = self::str($event['inTime'] ?? ''); if ($live) { $statusKey = 'live'; $statusLabel = __('Live', 'blyzer-events'); } elseif ($inTime === 'now' || ($event['status'] ?? '') === 'started') { $statusKey = 'now'; $statusLabel = __('In progress', 'blyzer-events'); } elseif ($inTime === 'past' || ($event['status'] ?? '') === 'ended') { $statusKey = 'past'; $statusLabel = __('Finished', 'blyzer-events'); } else { $statusKey = 'future'; $statusLabel = __('Upcoming', 'blyzer-events'); } $forms = [ 'tournament' => __('Tournament', 'blyzer-events'), 'league' => __('League', 'blyzer-events'), 'race' => __('Race', 'blyzer-events'), ]; $levels = [ 'pro' => __('Pro', 'blyzer-events'), 'semi-pro' => __('Semi-pro', 'blyzer-events'), 'hobby' => __('Hobby', 'blyzer-events'), ]; $dateLabel = ''; if ($start !== null) { $sameDay = $end === null || wp_date('Y-m-d', $start) === wp_date('Y-m-d', $end); $dateLabel = $sameDay ? trim(self::date($start) . (wp_date('H:i', $start) !== '00:00' ? ' ' . self::time($start) : '')) : self::date($start) . ' – ' . self::date($end); } $counts = is_array($event['counts'] ?? null) ? $event['counts'] : []; $config = is_array($event['configuration'] ?? null) ? $event['configuration'] : []; $game = is_array($event['game'] ?? null) ? $event['game'] : []; $tiles = []; if ($dateLabel !== '') $tiles[] = ['label' => __('Date', 'blyzer-events'), 'value' => $dateLabel]; if (self::str($event['place'] ?? '') !== '') $tiles[] = ['label' => __('Venue', 'blyzer-events'), 'value' => self::str($event['place'])]; if (!empty($counts['teams'])) $tiles[] = ['label' => __('Teams', 'blyzer-events'), 'value' => number_format_i18n((int) $counts['teams'])]; if (!empty($counts['players'])) $tiles[] = ['label' => __('Players', 'blyzer-events'), 'value' => number_format_i18n((int) $counts['players'])]; if (!empty($counts['matches'])) $tiles[] = ['label' => __('Matches', 'blyzer-events'), 'value' => number_format_i18n((int) $counts['matches'])]; if (!empty($counts['raceStages'])) $tiles[] = ['label' => __('Stages', 'blyzer-events'), 'value' => number_format_i18n((int) $counts['raceStages'])]; $format = self::formatLabel($event['form'] ?? '', $counts, $config); if ($format !== '') $tiles[] = ['label' => __('Format', 'blyzer-events'), 'value' => $format]; $organizer = self::str($event['ownerOrganisation'] ?? '') ?: self::str($event['ownerName'] ?? ''); $countdown = $statusKey === 'future' && $start !== null && $start > time(); return array_merge($event, [ 'initials' => self::initials($title, self::str($event['abbr'] ?? '')), 'isLive' => $live, 'statusKey' => $statusKey, 'statusLabel' => $statusLabel, 'formLabel' => $forms[self::str($event['form'] ?? '')] ?? '', 'levelLabel' => $levels[self::str($event['level'] ?? '')] ?? '', 'gameTitle' => self::str($game['title'] ?? ''), 'gameIcon' => $game['icon'] ?? null, 'dateLabel' => $dateLabel, 'startDate' => self::date($start), 'endDate' => self::date($end), 'datetime' => $start !== null ? gmdate('c', $start) : '', 'organizer' => $organizer, 'countdown' => $countdown, 'startTs' => $countdown ? $start : 0, /* translators: %s: start date and time of the event */ 'countdownLabel' => $countdown ? sprintf(__('Starts on %s', 'blyzer-events'), $dateLabel) : '', 'canApply' => ($event['status'] ?? '') === 'opened' && $inTime !== 'past' && !empty($event['url']), 'tiles' => $tiles, 'hasTiles' => $tiles !== [], ]); } private static function formatLabel($form, array $counts, array $config): string { $groups = (int) ($counts['groups'] ?? 0); $rounds = (int) ($counts['playoffRounds'] ?? 0); $parts = []; if ($form === 'race') { return ''; } if ($groups > 1) { /* translators: %s: number of groups */ $parts[] = sprintf(_n('%s group', '%s groups', $groups, 'blyzer-events'), number_format_i18n($groups)); } elseif ($groups === 1 && $form === 'league') { $parts[] = __('League table', 'blyzer-events'); } if ($rounds > 0) { $parts[] = ($config['playoffElimination'] ?? '') === 'double' ? __('double elimination playoff', 'blyzer-events') : __('playoff', 'blyzer-events'); } $label = implode(' + ', $parts); return $label === '' ? '' : mb_strtoupper(mb_substr($label, 0, 1)) . mb_substr($label, 1); } public static function teams(array $items): array { $out = []; foreach (self::rows($items) as $team) { $title = self::str($team['title'] ?? ''); $rank = isset($team['rank']) && is_numeric($team['rank']) ? (int) $team['rank'] : null; $other = self::rankOther($team['rankOther'] ?? null); $players = isset($team['countPlayers']) && is_numeric($team['countPlayers']) ? (int) $team['countPlayers'] : null; $team += ['url' => null, 'logo' => null, 'nationalityFlag' => null, 'nationalityTitle' => null]; $out[] = array_merge($team, [ 'initials' => self::initials($title, self::str($team['abbr'] ?? '')), 'rankLabel' => $rank !== null ? $rank . '.' : $other, 'isChampion' => $rank === 1, /* translators: %s: number of players */ 'playersLabel' => $players ? sprintf(_n('%s player', '%s players', $players, 'blyzer-events'), number_format_i18n($players)) : '', ]); } return $out; } public static function rankOther($value): string { $map = [ 'dns' => _x('DNS', 'did not start', 'blyzer-events'), 'dnf' => _x('DNF', 'did not finish', 'blyzer-events'), 'dsq' => _x('DSQ', 'disqualified', 'blyzer-events'), ]; return is_string($value) ? ($map[strtolower($value)] ?? '') : ''; } public static function groups(array $items, array $opts): array { $groups = []; $any = ['form' => false, 'extra' => false]; foreach (self::rows($items) as $group) { if ($opts['group'] !== '' && strcasecmp(self::str($group['label'] ?? ''), $opts['group']) !== 0) continue; if ($opts['extend'] !== '' && (bool) ($group['extend'] ?? false) !== ($opts['extend'] === '1')) continue; $places = []; foreach (array_values(array_filter((array) ($group['places'] ?? []), 'is_array')) as $i => $place) { $num = fn($k) => isset($place[$k]) && is_numeric($place[$k]) ? (int) $place[$k] : 0; $played = isset($place['played']) && is_numeric($place['played']) ? (int) $place['played'] : $num('wins') + $num('winsExtraTime') + $num('ties') + $num('losses') + $num('lossesExtraTime'); $diff = isset($place['scoreDifference']) && is_numeric($place['scoreDifference']) ? (int) $place['scoreDifference'] : $num('scoreAchieved') - $num('scoreReceived'); if ($num('winsExtraTime') || $num('lossesExtraTime')) $any['extra'] = true; $form = []; foreach (array_slice(array_values((array) ($place['form'] ?? [])), -5) as $r) { $r = strtoupper(self::str($r)); if (!in_array($r, ['W', 'D', 'L'], true)) continue; $form[] = self::formItem($r); $any['form'] = true; } while (count($form) < 5) $form[] = ['key' => 'none', 'label' => '–', 'title' => '']; $title = self::str($place['teamTitle'] ?? ''); $place += ['teamUrl' => null, 'teamLogo' => null, 'teamNationalityFlag' => null]; $places[] = array_merge($place, [ 'position' => isset($place['finalRank']) && is_numeric($place['finalRank']) && (int) $place['finalRank'] > 0 ? (int) $place['finalRank'] : $i + 1, 'initials' => self::initials($title, self::str($place['teamAbbr'] ?? '')), 'played' => $played, 'scoreLabel' => $num('scoreAchieved') . ':' . $num('scoreReceived'), 'diffLabel' => $diff > 0 ? '+' . $diff : ($diff < 0 ? '−' . abs($diff) : '0'), 'formItems' => $form, ]); } $legend = []; foreach (array_filter((array) ($group['legend'] ?? []), 'is_array') as $row) { $teams = array_filter(array_map([self::class, 'str'], (array) ($row['teams'] ?? []))); if (!$teams) continue; $legend[] = [ 'teamsLabel' => implode(', ', $teams), 'points' => self::str($row['points'] ?? ''), 'rule' => self::str($row['rule'] ?? ''), ]; } $title = self::str($group['title'] ?? ''); $groups[] = array_merge($group, [ 'badge' => self::str($group['label'] ?? '') !== '' ? mb_substr(self::str($group['label']), 0, 3) : self::initials($title), 'places' => $places, 'legend' => $legend, 'hasPlaces' => $places !== [], ]); } $flag = fn($attr, $auto) => $attr === 'yes' ? true : ($attr === 'no' ? false : $auto); return [ 'items' => $groups, 'multi' => count($groups) > 1, 'wrapClass' => count($groups) > 1 ? 'bze-groups is-multi' : 'bze-groups', 'showForm' => $flag($opts['form'], $any['form']), 'showExtra' => $flag($opts['extra'], $any['extra']), 'showDiff' => $flag($opts['diff'], true), ]; } private static function formItem(string $r): array { $map = [ 'W' => ['key' => 'w', 'label' => _x('W', 'form: win', 'blyzer-events'), 'title' => __('Win', 'blyzer-events')], 'D' => ['key' => 'd', 'label' => _x('D', 'form: draw', 'blyzer-events'), 'title' => __('Draw', 'blyzer-events')], 'L' => ['key' => 'l', 'label' => _x('L', 'form: loss', 'blyzer-events'), 'title' => __('Loss', 'blyzer-events')], ]; return $map[$r]; } public static function playoff(array $items, array $opts, string $uid): array { $rounds = []; foreach (self::rows($items) as $round) { if ($opts['extend'] !== '' && (bool) ($round['extend'] ?? false) !== ($opts['extend'] === '1')) continue; $rounds[] = self::round($round, $uid); } usort($rounds, fn($a, $b) => ((int) ($a['sort'] ?? 0)) <=> ((int) ($b['sort'] ?? 0))); $order = ['main' => 0, 'winners' => 1, 'losers' => 2, 'grand-final' => 3]; $buckets = []; foreach ($rounds as $round) { $side = in_array($round['side'] ?? null, ['winners', 'losers', 'grand-final'], true) ? $round['side'] : 'main'; $key = (!empty($round['extend']) ? 'extend-' : '') . $side; $buckets[$key][] = $round; } uksort($buckets, function ($a, $b) use ($order) { $ea = str_starts_with($a, 'extend-'); $eb = str_starts_with($b, 'extend-'); if ($ea !== $eb) return $ea ? 1 : -1; return ($order[preg_replace('/^extend-/', '', $a)] ?? 9) <=> ($order[preg_replace('/^extend-/', '', $b)] ?? 9); }); $titles = [ 'main' => '', 'winners' => __('Winners bracket', 'blyzer-events'), 'losers' => __('Losers bracket', 'blyzer-events'), 'grand-final' => __('Grand final', 'blyzer-events'), ]; $brackets = []; foreach ($buckets as $key => $list) { $extend = str_starts_with($key, 'extend-'); $side = preg_replace('/^extend-/', '', $key); $title = $titles[$side] ?? ''; if ($extend) $title = $title !== '' ? __('Additional playoff', 'blyzer-events') . ' – ' . $title : __('Additional playoff', 'blyzer-events'); $brackets[] = [ 'key' => $key, 'title' => $title, 'rounds' => self::layout($list, $side !== 'losers'), ]; } $showTitles = count($brackets) > 1 || ($brackets && !in_array($brackets[0]['key'], ['main', 'winners'], true)); foreach ($brackets as &$b) { if (!$showTitles) $b['title'] = ''; $b['hasPills'] = count($b['rounds']) > 1; $b['cls'] = 'bze-bracket bze-bracket--' . $b['key'] . ' has-' . count($b['rounds']) . '-rounds'; } unset($b); return ['items' => $rounds, 'brackets' => $brackets]; } private static function round(array $round, string $uid): array { $duels = []; foreach (array_values(array_filter((array) ($round['duels'] ?? []), 'is_array')) as $duel) { $winner = $duel['winner'] ?? null; $skip = !empty($duel['skip']); $one = self::side($duel, 'One', 'labelOne', $duel['finalCriterionOne'] ?? null, $winner === 'one', $winner === 'two', $skip); $two = self::side($duel, 'Two', 'labelTwo', $duel['finalCriterionTwo'] ?? null, $winner === 'two', $winner === 'one', $skip); $played = $one['score'] !== '' || $two['score'] !== ''; if ($skip) { $one['scoreLabel'] = ''; $two['scoreLabel'] = ''; } $duel += ['note' => null]; $duels[] = array_merge($duel, [ 'one' => $one, 'two' => $two, 'isPlayed' => $played, 'isSkip' => $skip, 'cls' => 'bze-duel' . ($played ? ' is-played' : '') . ($skip ? ' is-skip' : '') . ($winner ? ' has-winner' : ''), ]); } $title = self::str($round['title'] ?? ''); $label = self::str($round['label'] ?? ''); return array_merge($round, [ 'duels' => $duels, 'badge' => $label !== '' ? mb_substr($label, 0, 4) : self::initials($title), 'anchor' => $uid . '-r' . (int) ($round['id'] ?? 0), ]); } private static function layout(array $rounds, bool $allowExtras = true): array { $main = []; $extras = []; $n = count($rounds); $lastMulti = -1; foreach ($rounds as $i => $r) { if (count($r['duels']) > 1) $lastMulti = $i; } $finalIndex = $n - 1; foreach ($rounds as $i => $r) { if ($allowExtras && $i > $lastMulti && $i !== $finalIndex && count($r['duels']) <= 1 && $lastMulti >= 0) { $extras[] = ['title' => $r['title'] ?? '', 'duels' => $r['duels']]; } else { $main[] = $r; } } foreach ($main as &$r) { $possible = array_values(array_filter($r['duels'], fn($d) => !empty($d['possible']))); if ($possible && count($possible) < count($r['duels'])) { $r['duels'] = array_values(array_filter($r['duels'], fn($d) => empty($d['possible']))); $r['ownExtras'] = [['title' => __('If needed', 'blyzer-events'), 'duels' => $possible]]; } } unset($r); $count = count($main); foreach ($main as $i => &$r) { $c = count($r['duels']); $next = $i + 1 < $count ? count($main[$i + 1]['duels']) : 0; $size = ($next > 0 && $c > 1 && $next === (int) ceil($c / 2)) ? 2 : 1; $pairs = []; foreach (array_chunk($r['duels'], $size) as $chunk) { $pairs[] = [ 'slots' => $chunk, 'pairCls' => 'bze-pair' . (count($chunk) === 2 ? ' is-pair' : ' is-single') . ($next > 0 ? ' has-next' : '') . ($i > 0 ? ' has-prev' : ''), ]; } if (!$pairs) $pairs[] = ['slots' => [], 'pairCls' => 'bze-pair is-empty']; $r['pairs'] = $pairs; $r['extras'] = array_merge($r['ownExtras'] ?? [], $i === $count - 1 ? $extras : []); unset($r['ownExtras']); $r['roundCls'] = 'bze-round' . ($i === $count - 1 ? ' is-last' : '') . ($i === 0 ? ' is-first' : ''); } unset($r); return $main; } public static function matches(array $items, array $opts, string $uid): array { $matches = []; foreach (self::rows($items) as $m) { $matches[] = self::match($m); } $groupIds = array_unique(array_filter(array_map(fn($m) => ($m['phase'] ?? 'groups') === 'groups' ? self::str($m['eventGroupId'] ?? ($m['groupLabel'] ?? '')) : '', $matches))); if (count($groupIds) <= 1) { foreach ($matches as &$m) { if (($m['phase'] ?? 'groups') === 'groups') $m['phaseLabel'] = ''; } unset($m); } if ($opts['group'] !== '') { $matches = array_filter($matches, fn($m) => strcasecmp(self::str($m['groupLabel'] ?? ''), $opts['group']) === 0); } if ($opts['phase'] !== '') { $matches = array_filter($matches, fn($m) => ($m['phase'] ?? '') === $opts['phase']); } if ($opts['team'] > 0) { $matches = array_filter($matches, fn($m) => (int) ($m['teamHomeId'] ?? 0) === $opts['team'] || (int) ($m['teamAwayId'] ?? 0) === $opts['team']); } if ($opts['status'] === 'played') { $matches = array_filter($matches, fn($m) => $m['isPlayed']); } elseif ($opts['status'] === 'upcoming') { $matches = array_filter($matches, fn($m) => !$m['isPlayed'] && !$m['isLive']); } elseif ($opts['status'] === 'live') { $matches = array_filter($matches, fn($m) => $m['isLive']); } $matches = array_values($matches); $upcoming = $opts['upcoming'] ?? 'all'; if ($upcoming !== 'all' && $opts['limit'] <= 0 && $opts['status'] === '') { return self::compact($matches, (int) $upcoming, (int) ($opts['recent'] ?? 3)); } if ($opts['order'] === 'desc') $matches = array_reverse($matches); if ($opts['limit'] > 0) $matches = array_slice($matches, 0, $opts['limit']); return [ 'items' => $matches, 'sections' => self::sections($matches, $opts['group_by']), ]; } private static function compact(array $matches, int $upcoming, int $recent): array { $live = array_values(array_filter($matches, fn($m) => $m['isLive'])); $next = array_slice(array_values(array_filter($matches, fn($m) => !$m['isLive'] && !$m['isPlayed'])), 0, max(0, $upcoming)); $done = array_slice(array_reverse(array_values(array_filter($matches, fn($m) => $m['isPlayed']))), 0, max(0, $recent)); $sections = []; foreach ([ 'live' => [$live, __('Live now', 'blyzer-events')], 'upcoming' => [$next, __('Upcoming matches', 'blyzer-events')], 'recent' => [$done, __('Latest results', 'blyzer-events')], ] as $key => [$items, $title]) { if ($items) $sections[] = ['key' => $key, 'title' => $title, 'badge' => '', 'items' => $items, 'hasHead' => true]; } return [ 'items' => array_merge($live, $next, $done), 'sections' => $sections, 'isCompact' => true, 'hiddenCount' => max(0, count($matches) - count($live) - count($next) - count($done)), ]; } private static function match(array $m): array { $ts = self::timestamp($m['playedAt'] ?? null); $home = $m['scoreHome'] ?? null; $away = $m['scoreAway'] ?? null; $hasScore = is_numeric($home) && is_numeric($away); $inTime = self::str($m['inTime'] ?? ''); $live = !empty($m['live']) || ($inTime === 'now' && !isset($m['live'])); $played = !$live && $hasScore && ($inTime === 'past' || $inTime === '' || !isset($m['inTime'])); $homeWin = $played && (float) $home > (float) $away; $awayWin = $played && (float) $away > (float) $home; if ($live) { $state = 'live'; $statusLabel = __('Live', 'blyzer-events'); } elseif ($played) { $state = 'played'; $statusLabel = __('Final', 'blyzer-events'); } else { $state = 'upcoming'; $statusLabel = $ts !== null ? self::time($ts) : __('TBD', 'blyzer-events'); } $area = self::str($m['area'] ?? ''); if ($area !== '' && is_numeric($area)) { /* translators: %s: number of the court / pitch */ $area = sprintf(__('Court %s', 'blyzer-events'), $area); } $phaseLabel = self::str($m['groupLabel'] ?? '') ?: self::str($m['playoffRoundLabel'] ?? ''); $phaseTitle = self::str($m['groupTitle'] ?? '') ?: self::str($m['playoffRoundTitle'] ?? ''); $m += ['note' => null, 'teamHomeTitle' => null, 'teamAwayTitle' => null, 'url' => null, 'stream' => null]; return array_merge($m, [ 'playedAtDate' => self::date($ts), 'playedAtTime' => self::time($ts), 'datetime' => $ts !== null ? gmdate('c', $ts) : '', 'timestamp' => $ts, 'state' => $state, 'isLive' => $live, 'isPlayed' => $played, 'isUpcoming' => $state === 'upcoming', 'hasScore' => $hasScore, 'statusLabel' => $statusLabel, 'metaLine' => implode(' · ', array_filter([self::date($ts), $area])), 'areaLabel' => $area, 'phaseLabel' => mb_substr($phaseLabel, 0, 5), 'phaseTitle' => $phaseTitle, 'extraTimeLabel' => !empty($m['extraTime']) ? _x('OT', 'overtime', 'blyzer-events') : '', 'isWinnerHome' => $homeWin, 'isWinnerAway' => $awayWin, 'home' => self::side($m, 'Home', 'labelHome', $hasScore ? $home : null, $homeWin, $awayWin), 'away' => self::side($m, 'Away', 'labelAway', $hasScore ? $away : null, $awayWin, $homeWin), 'cls' => 'bze-match is-' . $state, ]); } private static function sections(array $matches, string $groupBy): array { if ($groupBy === 'none' || !$matches) { return $matches ? [['key' => 'all', 'title' => '', 'badge' => '', 'items' => $matches, 'hasHead' => false]] : []; } if ($groupBy === 'auto' || $groupBy === '') { $groupIds = array_unique(array_filter(array_map(fn($m) => ($m['phase'] ?? '') === 'groups' || !isset($m['phase']) ? self::str($m['eventGroupId'] ?? ($m['groupLabel'] ?? '')) : '', $matches), fn($v) => $v !== '')); $groupBy = count($groupIds) <= 1 ? 'round' : 'group'; } $sections = []; foreach ($matches as $m) { $phase = self::str($m['phase'] ?? ''); if (!in_array($phase, ['groups', 'playoff', 'friendly'], true)) $phase = self::str($m['playoffRoundLabel'] ?? '') !== '' ? 'playoff' : 'groups'; [$key, $title, $badge, $rank] = self::sectionKey($m, $phase, $groupBy); if (!isset($sections[$key])) { $sections[$key] = ['key' => $key, 'title' => $title, 'badge' => $badge, 'items' => [], 'hasHead' => true, 'rank' => $rank, 'pos' => count($sections)]; } $sections[$key]['items'][] = $m; } if ($groupBy !== 'day') { uasort($sections, fn($a, $b) => [$a['rank'][0], $a['rank'][1], $a['pos']] <=> [$b['rank'][0], $b['rank'][1], $b['pos']]); } return array_values(array_map(function ($s) { unset($s['rank'], $s['pos']); return $s; }, $sections)); } private static function sectionKey(array $m, string $phase, string $groupBy): array { if ($groupBy === 'day') { $ts = $m['timestamp']; if ($ts === null) return ['day-none', __('Date to be announced', 'blyzer-events'), '', [0, 0]]; return ['day-' . wp_date('Y-m-d', $ts), (string) wp_date('l, ' . get_option('date_format'), $ts), (string) wp_date('j', $ts), [0, 0]]; } if ($phase === 'playoff') { $label = self::str($m['playoffRoundLabel'] ?? ''); $title = self::str($m['playoffRoundTitle'] ?? '') ?: ($label !== '' ? $label : __('Playoff', 'blyzer-events')); return ['po-' . ($label !== '' ? $label : $title), $title, mb_substr($label, 0, 4), [2, 0]]; } if ($phase === 'friendly') { return ['friendly', __('Friendly matches', 'blyzer-events'), '', [3, 0]]; } if ($groupBy === 'round') { $round = self::str($m['round'] ?? ''); if ($round === '') return ['round-none', __('Matches', 'blyzer-events'), '', [1, PHP_INT_MAX]]; /* translators: %s: round number */ $title = is_numeric($round) ? sprintf(__('Round %s', 'blyzer-events'), $round) : $round; return ['round-' . $round, $title, mb_substr($round, 0, 3), [1, is_numeric($round) ? (int) $round : PHP_INT_MAX - 1]]; } $label = self::str($m['groupLabel'] ?? ''); $title = self::str($m['groupTitle'] ?? '') ?: ($label !== '' ? $label : __('Matches', 'blyzer-events')); return ['group-' . ($label !== '' ? $label : $title), $title, mb_substr($label, 0, 3), [1, 0]]; } public static function statKey(string $label): string { return sanitize_key(remove_accents($label)); } public static function players(array $items, array $meta, array $opts): array { $rows = self::rows($items); $statistics = []; foreach ((array) ($meta['statistics'] ?? []) as $s) { if (!is_array($s)) continue; $label = self::str($s['label'] ?? ''); if ($label === '' || $label === 'sum') continue; $statistics[] = ['label' => $label, 'title' => self::str($s['title'] ?? '') ?: $label, 'key' => self::statKey($label), 'sum' => !empty($s['sum'])]; } if (!$statistics) { $seen = []; foreach ($rows as $p) { $stats = is_array($p['statistics'] ?? null) ? $p['statistics'] : (is_array($p['totalStatistics'] ?? null) ? $p['totalStatistics'] : []); foreach (array_keys($stats) as $label) { $label = (string) $label; if ($label === 'sum' || isset($seen[$label])) continue; $seen[$label] = true; $statistics[] = ['label' => $label, 'title' => $label, 'key' => self::statKey($label), 'sum' => true]; } } } $hasSum = false; $players = []; foreach ($rows as $p) { $stats = is_array($p['statistics'] ?? null) ? $p['statistics'] : (is_array($p['totalStatistics'] ?? null) ? $p['totalStatistics'] : []); foreach ($stats as $k => $v) { $lower = mb_strtolower((string) $k); if (!array_key_exists($lower, $stats)) $stats[$lower] = $v; } $values = []; $byKey = []; foreach ($statistics as $s) { $v = $stats[$s['label']] ?? null; $values[] = ['value' => is_numeric($v) ? self::number($v) : '0']; $byKey[$s['key']] = is_numeric($v) ? (float) $v : 0.0; } if (isset($stats['sum']) && is_numeric($stats['sum'])) $hasSum = true; $name = self::str($p['name'] ?? ''); $position = self::str($p['position'] ?? ''); $p += ['url' => null, 'photo' => null, 'teamUrl' => null, 'teamLogo' => null, 'dress' => null, 'nationalityFlag' => null]; $players[] = array_merge($p, [ 'statistics' => $stats, 'totalStatistics' => $stats, 'stats' => $values, 'sumValue' => isset($stats['sum']) && is_numeric($stats['sum']) ? self::number($stats['sum']) : '', 'initials' => self::initials($name), 'teamInitials' => self::initials(self::str($p['teamTitle'] ?? '')), 'positionLabel' => $position === 'captain' ? _x('C', 'captain', 'blyzer-events') : ($position === 'assistant' ? _x('A', 'assistant captain', 'blyzer-events') : ''), 'positionTitle' => $position === 'captain' ? __('Captain', 'blyzer-events') : ($position === 'assistant' ? __('Assistant captain', 'blyzer-events') : ''), '_sort' => $byKey + ['sum' => isset($stats['sum']) && is_numeric($stats['sum']) ? (float) $stats['sum'] : 0.0], ]); } if ($opts['team'] > 0) { $players = array_values(array_filter($players, fn($p) => (int) ($p['teamId'] ?? 0) === $opts['team'])); } $sort = $opts['sort']; if ($sort === 'name') { usort($players, fn($x, $y) => strcasecmp(remove_accents((string) ($x['name'] ?? '')), remove_accents((string) ($y['name'] ?? '')))); } elseif ($sort !== '' && ($sort === 'sum' || in_array($sort, array_column($statistics, 'key'), true))) { usort($players, fn($x, $y) => [($y['_sort'][$sort] ?? 0), $x['name'] ?? ''] <=> [($x['_sort'][$sort] ?? 0), $y['name'] ?? '']); } if ($opts['limit'] > 0) $players = array_slice($players, 0, $opts['limit']); foreach ($players as $i => &$p) { $p['rank'] = $i + 1; unset($p['_sort']); } unset($p); return [ 'items' => $players, 'statistics' => array_map(fn($s) => ['label' => $s['label'], 'title' => $s['title'], 'key' => $s['key']], $statistics), 'showSum' => $hasSum, ]; } public static function raceStages(array $items): array { $stages = []; foreach (self::rows($items) as $i => $stage) { $ts = self::timestamp($stage['playedAt'] ?? null); $results = []; $hasPoints = false; foreach (array_values(array_filter((array) ($stage['results'] ?? []), 'is_array')) as $r) { $rank = isset($r['finalRank']) && is_numeric($r['finalRank']) ? (int) $r['finalRank'] : null; $other = self::rankOther($r['rankOther'] ?? null); if (isset($r['points']) && is_numeric($r['points'])) $hasPoints = true; $title = self::str($r['teamTitle'] ?? ''); $r += ['teamUrl' => null, 'teamLogo' => null]; $results[] = array_merge($r, [ 'rankLabel' => $rank !== null ? $rank . '.' : ($other !== '' ? $other : '–'), 'valueLabel' => self::number($r['value'] ?? null) ?: ($other !== '' ? $other : '–'), 'pointsLabel' => self::number($r['points'] ?? null), 'initials' => self::initials($title), 'cls' => 'bze-row' . ($rank !== null && $rank <= 3 ? ' is-podium is-rank-' . $rank : '') . ($rank === null ? ' is-out' : ''), ]); } $closed = !empty($stage['closed']); $stage += ['logo' => null, 'description' => null]; $stages[] = array_merge($stage, [ 'badge' => (string) ($i + 1), 'dateLabel' => trim(self::date($ts) . ' ' . self::time($ts)), 'datetime' => $ts !== null ? gmdate('c', $ts) : '', 'statusLabel' => $closed ? __('Final', 'blyzer-events') : ($results ? __('Provisional', 'blyzer-events') : __('Upcoming', 'blyzer-events')), 'statusKey' => $closed ? 'past' : ($results ? 'now' : 'future'), 'results' => $results, 'hasResults' => $results !== [], 'showPoints' => $hasPoints, ]); } return ['items' => $stages]; } public static function raceStandings(array $items, array $teams): array { $byId = []; foreach (self::rows($teams) as $t) { if (isset($t['id'])) $byId[(int) $t['id']] = $t; } $rows = []; foreach (self::rows($items) as $r) { $team = $byId[(int) ($r['teamId'] ?? 0)] ?? []; $rank = isset($r['rank']) && is_numeric($r['rank']) ? (int) $r['rank'] : null; $other = self::rankOther($r['rankOther'] ?? ($team['rankOther'] ?? null)); $title = self::str($r['teamTitle'] ?? ''); $r += ['teamUrl' => null, 'teamLogo' => null]; $rows[] = array_merge($r, [ 'rankLabel' => $rank !== null && $other === '' ? $rank . '.' : ($other !== '' ? $other : '–'), 'totalLabel' => $other !== '' && !is_numeric($r['total'] ?? null) ? $other : (self::number($r['total'] ?? null) ?: '–'), 'stagesLabel' => isset($r['stages']) && is_numeric($r['stages']) ? (string) (int) $r['stages'] : '', 'initials' => self::initials($title, self::str($team['abbr'] ?? '')), 'isOut' => $other !== '', 'cls' => 'bze-row' . ($rank !== null && $rank <= 3 && $other === '' ? ' is-podium is-rank-' . $rank : '') . ($other !== '' ? ' is-out' : ''), '_o' => [$other !== '' ? 1 : 0, $rank ?? PHP_INT_MAX], ]); } usort($rows, fn($a, $b) => $a['_o'] <=> $b['_o']); foreach ($rows as &$r) unset($r['_o']); unset($r); return ['items' => $rows, 'showStages' => (bool) array_filter(array_column($rows, 'stagesLabel'))]; } }