diff --git a/src/structure/components/com_sportsmanager/admin.php b/src/structure/components/com_sportsmanager/admin.php index 20d68b5..1d358c4 100644 --- a/src/structure/components/com_sportsmanager/admin.php +++ b/src/structure/components/com_sportsmanager/admin.php @@ -953,6 +953,14 @@ function adminEinstellungen(): void redirectSportsManagerURL('&task=admin_uebersicht'); } +function adminSportshub(): void +{ + ini_set('memory_limit', '1024M'); + $db = getDatabase(true); + $exporter = new SMExporter($db); + $exporter->run(); +} + function adminDatenbank(): void { $db = getDatabase(true); @@ -14252,7 +14260,7 @@ function adminImportTurnierdisziplinMeldungenSpieleForm(): void HTML_sportsmanager_admin::adminImportTurnierdisziplinMeldungenSpieleForm($row, $veranstalter); } -#[NoReturn] function adminLoeschenTurnierdisziplinMeldungenSpiele($id): void +function adminLoeschenTurnierdisziplinMeldungenSpiele($id): void { $db = getDatabase(); global $_FILES; diff --git a/src/structure/components/com_sportsmanager/sportsmanager.php b/src/structure/components/com_sportsmanager/sportsmanager.php index a62ea3d..c74f7ce 100644 --- a/src/structure/components/com_sportsmanager/sportsmanager.php +++ b/src/structure/components/com_sportsmanager/sportsmanager.php @@ -43,6 +43,7 @@ require_once JPATH_SITE . '/components/com_sportsmanager/views/sportsmanager/vie require_once JPATH_SITE . '/components/com_sportsmanager/views/sportsmanager/view_ticker.php'; require_once JPATH_SITE . '/components/com_sportsmanager/util/image.php'; require_once JPATH_SITE . '/components/com_sportsmanager/util/email.php'; +require_once JPATH_SITE . '/components/com_sportsmanager/util/export.php'; require_once JPATH_SITE . '/components/com_sportsmanager/database/update.php'; // will also include init.php and util.php initDatabase(); @@ -83,6 +84,9 @@ if ($task == "spielerbild") { case 'admin_einstellungen_save': adminSaveEinstellungen(); break; + case 'admin_sportshub': + adminSportshub(); + break; case 'admin_datenbank': adminDatenbank(); break; diff --git a/src/structure/components/com_sportsmanager/util/export.php b/src/structure/components/com_sportsmanager/util/export.php new file mode 100644 index 0000000..6535d89 --- /dev/null +++ b/src/structure/components/com_sportsmanager/util/export.php @@ -0,0 +1,804 @@ + Vorrunde + * - rundenstufe = 1,2,3 => Hauptrunde with groups: + * 1 => Profifeld, 2 => Amateurfeld, 3 => Hobbyfeld + * - For team-based exports: + * - stage/group are synthesized as defaults. + * + * Place this in a Joomla context (CLI script, controller, or a small admin entry point). + */ + +defined('_JEXEC') or die; + +use Joomla\Database\DatabaseDriver; + +final class SMExporter +{ + + private DatabaseDriver $db; + + private DateTimeZone $sourceTz; + private DateTimeZone $outputTz; + + // Optional filter; set to null for all seasons + private $exportSeasonId = null; + + // Optional file output; set to null to echo JSON only + private string $outputFile = JPATH_ROOT . '/tsm.json'; + + private array $playerCache = []; + private array $meldungsPlayersCache = []; + private array $teamCache = []; + + private array $disciplineShortNames = ['Offenes Doppel' => 'OD', + 'Offenes Einzel' => 'OE', + 'Damen Doppel' => 'DD', + 'Damen Einzel' => 'DE', + 'Herren Doppel' => 'HD', + 'Herren Einzel' => 'HE', + 'Junioren Doppel' => 'JD', + 'Junioren Einzel' => 'JE', + 'Senioren Doppel' => 'SD', + 'Senioren Einzel' => 'SE',]; + + public function __construct(DatabaseDriver $db) + { + $this->db = $db; + $this->sourceTz = new DateTimeZone('Europe/Berlin'); + $this->outputTz = new DateTimeZone('UTC'); + } + + /** + * Format a DB date/datetime into UTC ISO-8601. + */ + function isoUtc(?string $value, DateTimeZone $sourceTz, DateTimeZone $outputTz): ?string + { + if (!$value || $value === '0000-00-00' || $value === '0000-00-00 00:00:00') { + return null; + } + + try { + $dt = new DateTimeImmutable($value, $sourceTz); + return $dt->setTimezone($outputTz)->format('Y-m-d\TH:i:s\Z'); + } catch (Throwable $e) { + return null; + } + } + + /** + * Build shortName from kuerzel or disziplin text. + */ + function disciplineShortName(?string $kuerzel, ?string $disziplin): string + { + $kuerzel = trim((string)$kuerzel); + if ($kuerzel !== '') { + return $kuerzel; + } + + $disziplin = (string)$disziplin; + foreach ($this->disciplineShortNames as $needle => $short) { + if (mb_stripos($disziplin, $needle) !== false) { + return $short; + } + } + + // Last-resort fallback: initials of the words + $words = preg_split('/\s+/', trim($disziplin)); + $initials = ''; + foreach ($words as $w) { + if ($w !== '') { + $initials .= mb_substr($w, 0, 1); + } + } + + return $initials !== '' ? mb_strtoupper($initials) : 'X'; + } + + /** + * Build a person object from spieler_id or from turniermeldung_spieler_name. + */ + function getPersonForSpieler( + Joomla\Database\DatabaseInterface $db, + ?int $spielerId, + ?int $turniermeldungSpielerId, + array &$playerCache + ): array + { + if ($spielerId && isset($playerCache[$spielerId])) { + return $playerCache[$spielerId]; + } + + if ($spielerId) { + $query = $db->getQuery(true) + ->select(['spieler_id', 'vorname', 'nachname', 'spielernr']) + ->from($db->quoteName('#__sportsmanager_spieler')) + ->where($db->quoteName('spieler_id') . ' = ' . (int)$spielerId); + + $db->setQuery($query); + $row = $db->loadObject(); + + $person = [ + 'name' => $row ? trim(($row->vorname ?? '') . ' ' . ($row->nachname ?? '')) : ('Spieler ' . $spielerId), + 'playerNr' => $row && !empty($row->spielernr) ? (string)$row->spielernr : null, + ]; + + $playerCache[$spielerId] = $person; + return $person; + } + + if ($turniermeldungSpielerId) { + $query = $db->getQuery(true) + ->select(['vorname', 'nachname', 'vereinsname']) + ->from($db->quoteName('#__sportsmanager_turniermeldung_spieler_name')) + ->where($db->quoteName('turniermeldung_spieler_id') . ' = ' . (int)$turniermeldungSpielerId) + ->order('turniermeldung_spieler_name_id ASC'); + + $db->setQuery($query); + $row = $db->loadObject(); + + if ($row) { + return [ + 'name' => trim(($row->vorname ?? '') . ' ' . ($row->nachname ?? '')), + 'playerNr' => null, + ]; + } + } + + return [ + 'name' => 'Unknown', + 'playerNr' => null, + ]; + } + + /** + * Get all players assigned to a turniermeldung. + */ + function getMeldungPlayers( + Joomla\Database\DatabaseInterface $db, + int $turniermeldungId, + array &$meldungsPlayersCache, + array &$playerCache + ): array + { + if (isset($meldungsPlayersCache[$turniermeldungId])) { + return $meldungsPlayersCache[$turniermeldungId]; + } + + $query = $db->getQuery(true) + ->select(['tms.turniermeldung_spieler_id', 'tms.spieler_id']) + ->from($db->quoteName('#__sportsmanager_turniermeldung_spieler', 'tms')) + ->where('tms.turniermeldung_id = ' . $turniermeldungId) + ->order('tms.turniermeldung_spieler_id ASC'); + + $db->setQuery($query); + $rows = $db->loadObjectList(); + + $players = []; + foreach ($rows as $row) { + $players[] = $this->getPersonForSpieler($db, $row->spieler_id ? (int)$row->spieler_id : null, (int)$row->turniermeldung_spieler_id, $playerCache); + } + + $meldungsPlayersCache[$turniermeldungId] = $players; + return $players; + } + + /** + * Build display name for a side. + */ + function buildSideName(array $players): string + { + if (count($players) === 0) { + return 'Unknown'; + } + + if (count($players) === 1) { + return $players[0]['name']; + } + + $names = []; + foreach ($players as $p) { + $names[] = $p['name']; + } + + return implode(' / ', $names); + } + + /** + * Parse set scores from a result string. + * Supports patterns like "11:7;11:9", "11-7 11-9", etc. + */ + function parseSets(array $detail): array + { + $sets = []; + $n = 1; + foreach ($detail as $set) { + $set = trim((string)$set); + if ($set === '') { + continue; + } + + preg_match_all('/(\d{1,2})\s*[:\-]\s*(\d{1,2})/', $set, $m, PREG_SET_ORDER); + foreach ($m as $match) { + $sets[] = [ + 'number' => $n++, + 'scoreHome' => (int)$match[1], + 'scoreAway' => (int)$match[2], + ]; + } + } + return $sets; + } + + /** + * Determine match state. + */ + function determineMatchState($result, array $detail): string + { + if ($result !== null || ( !empty($detail) && !array_filter($detail, fn($v) => $v === null || trim((string)$v) === ''))) { + return 'PLAYED'; + } + return 'SCHEDULED'; + } + + /** + * Determine match state. + */ + function determineWinner(array $detail, string $sourceType): string + { + if ($sourceType === 'TEAM') { + $sets = $this->parseSets($detail); + $homeSetsWon = 0; + $awaySetsWon = 0; + foreach ($sets as $set) { + if ($set['scoreHome'] > $set['scoreAway']) { + $homeSetsWon++; + } elseif ($set['scoreHome'] < $set['scoreAway']) { + $awaySetsWon++; + } + } + if ($homeSetsWon > $awaySetsWon) { + return 'HOME'; + } elseif ($homeSetsWon < $awaySetsWon) { + return 'AWAY'; + } + return 'DRAW'; + } else { + if (count($detail) >0) { + if ($detail[0] == 1) { + return 'HOME'; + } elseif ($detail[0] == 2) { + return 'AWAY'; + } else { + return 'DRAW'; + } + } + return 'UNKNOWN'; + } + } + + /** + * Determine group state based on a cutoff date. + */ + function determineFinishedState(?string $cutoffDate): string + { + if (!$cutoffDate || $cutoffDate === '0000-00-00') { + return 'PLANNED'; + } + + $today = new DateTimeImmutable('today'); + $cutoff = new DateTimeImmutable($cutoffDate); + + return ($cutoff < $today) ? 'FINISHED' : 'IN_PROGRESS'; + } + + /** + * Build one match object. + */ + function buildMatch( + array $homePlayers, + array $awayPlayers, + ?string $startDate, + ?string $endDate, + ?int $result, + array $detail, + string $sourceType + ): array + { + $type = (count($homePlayers) > 1 || count($awayPlayers) > 1) ? 'DOUBLE' : 'SINGLE'; + $match = [ + 'startDate' => $startDate, + 'endDate' => $endDate, + 'type' => $type, + 'state' => $this->determineMatchState($result, $detail), + 'players' => [], + 'sets' => $sourceType === 'TEAM' ? $this->parseSets($detail) : [], + 'winner' => $this->determineWinner($detail, $sourceType), + ]; + + foreach ($homePlayers as $p) { + $match['players'][] = [ + 'name' => $p['name'], + 'playerNr' => $p['playerNr'], + 'side' => 'HOME', + ]; + } + foreach ($awayPlayers as $p) { + $match['players'][] = [ + 'name' => $p['name'], + 'playerNr' => $p['playerNr'], + 'side' => 'AWAY', + ]; + } + + // If detail parsing produced no sets, keep the structure valid by leaving it empty. + if ($sourceType === 'TEAM' && empty($match['sets']) && $result !== null) { + $match['sets'] = []; + } + + return $match; + } + + /** + * group matches. Based on String (e.g. E1E1, D1D1, D1D1, E2E2, D2D2 etc.) + */ + function groupMatches(array $pattern, array $games): array { + $grouped = []; + $order = []; // to preserve first appearance order + + foreach ($pattern as $i => $slot) { + if (!array_key_exists($i, $games)) { + continue; + } + if (!isset($grouped[$slot])) { + $grouped[$slot] = [ + 'type' => $slot, + 'sets' => [] + ]; + $order[] = $slot; // remember order of first occurrence + } + + $grouped[$slot]['sets'][] = $games[$i]; + } + + // rebuild ordered result + $result = []; + foreach ($order as $slot) { + $result[] = $grouped[$slot]; + } + + return $result; + } + + function run(): void + { + set_time_limit(0); + $db = $this->db; + $export = [ + 'meta' => [ + // Keep the organization value explicit, or replace it from settings if available. + 'organisation' => 'TFVSH', + ], + 'seasons' => [], + ]; + + // Seasons + $seasonSql = $db->getQuery(true) + ->select(['saison_id', 'saisonbezeichnung']) + ->from($db->quoteName('#__sportsmanager_saison')) + ->order('saisonbezeichnung ASC, saison_id ASC'); + + if ($this->exportSeasonId !== null) { + $seasonSql->where('saison_id = ' . (int)$this->exportSeasonId); + } + + $db->setQuery($seasonSql); + $seasons = $db->loadObjectList(); + + foreach ($seasons as $season) { + $seasonNode = [ + 'name' => (string)$season->saisonbezeichnung, + 'events' => [], + ]; + + /** + * Event type 1: turniere + */ + $turnierSql = $db->getQuery(true) + ->select([ + 't.turnier_id', + 't.turnierbezeichnung', + 't.erster_tag', + 't.letzter_tag', + ]) + ->from($db->quoteName('#__sportsmanager_turnier', 't')) + ->where('t.saison_id = ' . (int)$season->saison_id) + ->order('t.erster_tag ASC, t.turnier_id ASC'); + + $db->setQuery($turnierSql); + $turniere = $db->loadObjectList(); + + foreach ($turniere as $turnier) { + $eventNode = [ + 'name' => (string)$turnier->turnierbezeichnung, + 'disciplines' => [], + ]; + + $discSql = $db->getQuery(true) + ->select([ + 'd.turnierdisziplin_id', + 'd.disziplin', + 'd.kuerzel', + 'd.beginn', + ]) + ->from($db->quoteName('#__sportsmanager_turnierdisziplin', 'd')) + ->where('d.turnier_id = ' . (int)$turnier->turnier_id) + ->order('d.reihenfolge ASC, d.turnierdisziplin_id ASC'); + + $db->setQuery($discSql); + $disciplines = $db->loadObjectList(); + + foreach ($disciplines as $disc) { + $disciplineNode = [ + 'name' => (string)$disc->disziplin, + 'shortName' => $this->disciplineShortName($disc->kuerzel, $disc->disziplin), + 'stages' => [], + ]; + + $spielSql = $db->getQuery(true) + ->select([ + 'ts.turnierspiel_id', + 'ts.turnierdisziplin_id', + 'ts.spiel_nummer', + 'ts.runde', + 'ts.rundenstufe', + 'ts.heim_meldung_id', + 'ts.gast_meldung_id', + 'ts.ergebnis', + 'ts.ergebnis_detailliert', + 'hm.meldungsgruppe_id AS heim_meldungsgruppe_id', + 'gm.meldungsgruppe_id AS gast_meldungsgruppe_id', + ]) + ->from($db->quoteName('#__sportsmanager_turnierspiel', 'ts')) + ->leftJoin($db->quoteName('#__sportsmanager_turniermeldung', 'hm') . ' ON hm.turniermeldung_id = ts.heim_meldung_id') + ->leftJoin($db->quoteName('#__sportsmanager_turniermeldung', 'gm') . ' ON gm.turniermeldung_id = ts.gast_meldung_id') + ->where('ts.turnierdisziplin_id = ' . (int)$disc->turnierdisziplin_id) + ->order('COALESCE(ts.rundenstufe, 99) ASC, COALESCE(ts.runde, 99) ASC, COALESCE(ts.spiel_nummer, 99) ASC, ts.turnierspiel_id ASC'); + + $db->setQuery($spielSql); + $spiele = $db->loadObjectList(); + + $stages = []; + foreach ($spiele as $spiel) { + $rundenstufe = (int)($spiel->rundenstufe ?? 0); + + if ($rundenstufe === 10) { + $stageName = 'Vorrunde'; + $groupName = 'Vorrunde'; + } elseif (in_array($rundenstufe, [1, 2, 3], true)) { + $stageName = 'Hauptrunde'; + $groupName = match ($rundenstufe) { + 1 => 'Profifeld', + 2 => 'Amateurfeld', + 3 => 'Hobbyfeld', + }; + } else { + $stageName = 'Stage A'; + $groupName = 'Group A'; + } + + $groupKey = $stageName . '|' . $groupName; + + if (!isset($stages[$stageName])) { + $stages[$stageName] = []; + } + if (!isset($stages[$stageName][$groupKey])) { + $stages[$stageName][$groupKey] = [ + 'name' => $groupName, + 'rounds' => [], + '_dates' => [], + ]; + } + + $homePlayers = $this->getMeldungPlayers($db, (int)$spiel->heim_meldung_id, $this->meldungsPlayersCache, $this->playerCache); + $awayPlayers = $this->getMeldungPlayers($db, (int)$spiel->gast_meldung_id, $this->meldungsPlayersCache, $this->playerCache); + + $homeName = $this->buildSideName($homePlayers); + $awayName = $this->buildSideName($awayPlayers); + + $startDate = $this->isoUtc((string)$disc->beginn, $this->sourceTz, $this->outputTz) ?: $this->isoUtc($turnier->erster_tag . ' 00:00:00', $this->sourceTz, $this->outputTz); + $endDate = $this->isoUtc((string)$disc->beginn, $this->sourceTz, $this->outputTz) ?: $this->isoUtc($turnier->letzter_tag . ' 23:59:59', $this->sourceTz, $this->outputTz); + + $match = $this->buildMatch( + $homePlayers, + $awayPlayers, + $startDate, + $endDate, + $spiel->ergebnis !== null ? (int)$spiel->ergebnis : null, + [$spiel->ergebnis ?? null], + 'TURNIER' + ); + + $roundIndex = $spiel->runde ?? 1; + $roundKey = (string)$roundIndex; + + if (!isset($stages[$stageName][$groupKey]['rounds'][$roundKey])) { + $stages[$stageName][$groupKey]['rounds'][$roundKey] = [ + 'index' => $roundIndex, + 'name' => 'Round ' . $roundIndex, + 'matchdays' => [], + ]; + } + + $matchdayName = 'Day ' . (int)($spiel->spiel_nummer ?: $spiel->turnierspiel_id); + $stages[$stageName][$groupKey]['rounds'][$roundKey]['matchdays'][] = [ + 'name' => $matchdayName, + 'startDate' => $startDate, + 'endDate' => $endDate, + 'teamHome' => ['name' => $homeName], + 'teamAway' => ['name' => $awayName], + 'matches' => [$match], + ]; + + $stages[$stageName][$groupKey]['_dates'][] = $turnier->letzter_tag ?: $turnier->erster_tag; + } + + foreach ($stages as $stageName => $groupBucket) { + $stageNode = [ + 'name' => $stageName, + 'groups' => [], + ]; + + foreach ($groupBucket as $groupKey => $groupData) { + $cutoffDate = null; + if (!empty($groupData['_dates'])) { + $cutoffDate = max($groupData['_dates']); + } + + $groupNode = [ + 'name' => $groupData['name'], + 'tournamentMode' => 'unknown', + 'state' => $this->determineFinishedState($cutoffDate), + 'rounds' => [], + ]; + + ksort($groupData['rounds'], SORT_NATURAL); + foreach ($groupData['rounds'] as $round) { + $groupNode['rounds'][] = $round; + } + + $stageNode['groups'][] = $groupNode; + } + + $disciplineNode['stages'][] = $stageNode; + } + + $eventNode['disciplines'][] = $disciplineNode; + } + + $seasonNode['events'][] = $eventNode; + } + + /** + * Event type 2: team-based events from veranstaltung via team/begegnung/teamspiel + */ + $veranstaltungSql = $db->getQuery(true) + ->select([ + 'v.veranstaltung_id', + 'v.bezeichnung', + 'v.erster_tag', + 'v.letzter_tag', + 'tsm.modus AS modus' + ]) + ->from($db->quoteName('#__sportsmanager_veranstaltung', 'v')) + ->innerJoin($db->quoteName('#__sportsmanager_team', 'tm') . ' ON tm.veranstaltung_id = v.veranstaltung_id') + ->leftJoin($db->quoteName('#__sportsmanager_teamspiel_modus', 'tsm') . ' ON tsm.teamspiel_modus_id = v.modus_id') + ->where('v.saison_id = ' . (int)$season->saison_id) + ->group('v.veranstaltung_id, v.bezeichnung, v.erster_tag, v.letzter_tag') + ->order('v.erster_tag, v.veranstaltung_id'); + + $db->setQuery($veranstaltungSql); + $veranstaltungen = $db->loadObjectList(); + + foreach ($veranstaltungen as $veranstaltung) { + + $modus = array_map('trim', explode(',', $veranstaltung->modus)); + + $eventNode = [ + 'name' => (string)$veranstaltung->bezeichnung, + 'disciplines' => [ + [ + 'name' => 'Teamspiel', + 'shortName' => 'TS', + 'stages' => [ + [ + 'name' => 'Stage A', + 'groups' => [], + ], + ], + ], + ], + ]; + + $groupNode = [ + 'name' => 'Group A', + 'tournamentMode' => 'unknown', + 'state' => 'PLANNED', + 'rounds' => [], + ]; + + $begegnungSql = $db->getQuery(true) + ->select([ + 'b.begegnung_id', + 'b.heim_team_id', + 'b.gast_team_id', + 'b.zeitpunkt', + 'b.spieltag', + 'b.spieltag_titel', + 'b.spiel_nr', + ]) + ->from($db->quoteName('#__sportsmanager_begegnung', 'b')) + ->innerJoin($db->quoteName('#__sportsmanager_team', 'th') . ' ON th.team_id = b.heim_team_id') + ->innerJoin($db->quoteName('#__sportsmanager_team', 'tg') . ' ON tg.team_id = b.gast_team_id') + ->where('th.veranstaltung_id = ' . (int)$veranstaltung->veranstaltung_id) + ->order('COALESCE(b.spieltag, 9999) ASC, b.zeitpunkt ASC, b.begegnung_id ASC'); + + $db->setQuery($begegnungSql); + $begegnungen = $db->loadObjectList(); + + $groupDates = []; + $roundsByKey = []; + + foreach ($begegnungen as $begegnung) { + $homeTeamId = (int)$begegnung->heim_team_id; + $awayTeamId = (int)$begegnung->gast_team_id; + + if (!isset($this->teamCache[$homeTeamId])) { + $db->setQuery( + $db->getQuery(true) + ->select(['team_id', 'teamname']) + ->from($db->quoteName('#__sportsmanager_team')) + ->where('team_id = ' . $homeTeamId) + ); + $this->teamCache[$homeTeamId] = $db->loadObject(); + } + if (!isset($this->teamCache[$awayTeamId])) { + $db->setQuery( + $db->getQuery(true) + ->select(['team_id', 'teamname']) + ->from($db->quoteName('#__sportsmanager_team')) + ->where('team_id = ' . $awayTeamId) + ); + $this->teamCache[$awayTeamId] = $db->loadObject(); + } + + $homeTeam = $this->teamCache[$homeTeamId]; + $awayTeam = $this->teamCache[$awayTeamId]; + + $roundIndex = $begegnung->spieltag ?? 1; + $roundKey = (string)$roundIndex; + if (!isset($roundsByKey[$roundKey])) { + $roundsByKey[$roundKey] = [ + 'index' => $roundIndex, + 'name' => 'Round ' . $roundIndex, + 'matchdays' => [], + ]; + } + + $teamspielSql = $db->getQuery(true) + ->select([ + 'ts.teamspiel_id', + 'ts.teamspiel_nummer', + 'ts.heim_spieler_1_id', + 'ts.heim_spieler_2_id', + 'ts.gast_spieler_1_id', + 'ts.gast_spieler_2_id', + 'ts.teamspiel_heim_punkte', + 'ts.teamspiel_gast_punkte', + 'ts.teamspiel_heim_spielpunkte', + 'ts.teamspiel_gast_spielpunkte', + 'ts.ergebnis_detailliert', + ]) + ->from($db->quoteName('#__sportsmanager_teamspiel', 'ts')) + ->where('ts.begegnung_id = ' . (int)$begegnung->begegnung_id) + ->order('ts.teamspiel_nummer ASC, ts.teamspiel_id ASC'); + + $db->setQuery($teamspielSql); + $teamspiele = $db->loadObjectList(); + + $matchdayMatches = []; + $teamspiele = $this->groupMatches($modus, $teamspiele); + foreach ($teamspiele as $group) { + if (empty($group['sets'])) { + continue; + } + + $ts = $group['sets'][0]; + // use modus here + $homePlayers = []; + $awayPlayers = []; + + $homePlayers[] = $this->getPersonForSpieler($db, (int)$ts->heim_spieler_1_id, null, $this->playerCache); + if (!empty($ts->heim_spieler_2_id)) { + $homePlayers[] = $this->getPersonForSpieler($db, (int)$ts->heim_spieler_2_id, null, $this->playerCache); + } + + $awayPlayers[] = $this->getPersonForSpieler($db, (int)$ts->gast_spieler_1_id, null, $this->playerCache); + if (!empty($ts->gast_spieler_2_id)) { + $awayPlayers[] = $this->getPersonForSpieler($db, (int)$ts->gast_spieler_2_id, null, $this->playerCache); + } + + $startDate = $this->isoUtc($begegnung->zeitpunkt, $this->sourceTz, $this->outputTz); + $endDate = $this->isoUtc($begegnung->zeitpunkt, $this->sourceTz, $this->outputTz); + + $detail = []; + foreach ($group['sets'] as $set) { + $detail[] = $set->ergebnis_detailliert ?? null; + } + + $matchdayMatches[] = $this->buildMatch( + $homePlayers, + $awayPlayers, + $startDate, + $endDate, + ($ts->teamspiel_heim_punkte !== null || $ts->teamspiel_gast_punkte !== null) ? 1 : null, + $detail, + 'TEAM' + ); + } + + $matchdayName = trim((string)$begegnung->spieltag_titel) !== '' + ? (string)$begegnung->spieltag_titel + : ('Spieltag ' . $roundIndex); + + $roundsByKey[$roundKey]['matchdays'][] = [ + 'name' => $matchdayName, + 'startDate' => $this->isoUtc($begegnung->zeitpunkt, $this->sourceTz, $this->outputTz), + 'endDate' => $this->isoUtc($begegnung->zeitpunkt, $this->sourceTz, $this->outputTz), + 'teamHome' => ['name' => $homeTeam->teamname ?? ('Team ' . $homeTeamId)], + 'teamAway' => ['name' => $awayTeam->teamname ?? ('Team ' . $awayTeamId)], + 'matches' => $matchdayMatches, + ]; + + $groupDates[] = substr((string)$begegnung->zeitpunkt, 0, 10); + } + + if (!empty($groupDates)) { + $groupNode['state'] = $this->determineFinishedState(max($groupDates)); + } + + ksort($roundsByKey, SORT_NATURAL); + foreach ($roundsByKey as $round) { + $groupNode['rounds'][] = $round; + } + + $eventNode['disciplines'][0]['stages'][0]['groups'][] = $groupNode; + $seasonNode['events'][] = $eventNode; + } + + $export['seasons'][] = $seasonNode; + } + + $json = json_encode($export, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT); + + if ($json === false) { + throw new RuntimeException('JSON encoding failed: ' . json_last_error_msg()); + } + + if ($this->outputFile) { + file_put_contents($this->outputFile, $json . PHP_EOL); + } + + if (PHP_SAPI !== 'cli') { + header('Content-Type: application/json; charset=utf-8'); + } + + echo 'Successfully created export file: ' . $this->outputFile; + } +} diff --git a/src/structure/components/com_sportsmanager/views/sportsmanager/view_admin.php b/src/structure/components/com_sportsmanager/views/sportsmanager/view_admin.php index cfa38cf..16eb818 100644 --- a/src/structure/components/com_sportsmanager/views/sportsmanager/view_admin.php +++ b/src/structure/components/com_sportsmanager/views/sportsmanager/view_admin.php @@ -145,6 +145,12 @@ class HTML_sportsmanager_admin + + + +