Compare commits

...

161 Commits

Author SHA1 Message Date
Tim 6b68d8a5ed Fix DB migration for sync_log: bump version to 121 and mirror into script.php
Addresses review feedback (jmeyer26):
- update.php: move the sync_log table + dtfb_sync_url setting out of the
  already-released <120 migration block into a new <121 block, so instances
  already at version 120 actually receive them.
- script.php: create the sync_log table, seed dtfb_sync_url, and set the
  fresh-install datenbank_version to 121 (parity with update.php).
2026-06-04 11:53:10 +02:00
Tim 5843fda2d6 QA: harden DTFB player sync receiver
Applies 6 fixes to sync.php found during QA of the player-sync feature:
1. Normalise non-UTF-8 (latin1/Win-1252) payloads -> fixes silent 0-row imports
2. Fail loudly (success=false) when N rows parse but nothing is added/updated
3. Remove dead \ block (undefined-variable notice)
4. Gate mass-deactivation: skip the sweep when a payload carries < 50% of an
   org's currently-active members (configurable via sync_deactivation_min_ratio,
   default 0.5); adds/updates still proceed, skipped sweeps return warnings
5. Use a single DB clock (NOW()) for staging session id/cleanup
6. Enforce Passnummer format ^[0-9]{2}-[0-9]{4,6}\$ (parity with manual import)

Adds tests/dtfb-player-sync/FINDINGS.md documenting the findings and fixes.
End-to-end validation is to be done on the staging environments.
2026-06-04 11:41:50 +02:00
Tim 511c17468c QA Edge Case Fixes: enforce spielernr and preserve DTFB fields
- Fix 1: Discard players missing spielernr instead of auto-generating them

- Fix 2: Preserve lizenznr, geburtsjahr, and pseudonym fields during player updates

- Fix 3: Resolve PHP 8 array offset warning by using !empty() for geschlecht parsing

- Fix 4: Leave clubless players handling as-is (skip them) per user request
2026-06-04 00:33:05 +02:00
Tim 6f33599fd9 Remove lizenz import handling - field should never be overwritten at DTFB 2026-06-04 00:09:31 +02:00
Tim aac4c1458f Merge sportsmanager2-dev and fix critical sync bugs
Merge resolution:
- Combined migration 120 (sync_log table + dev branch schema changes)

Bug fixes from intensive code review:
- C1: Fix session_id type mismatch - use datetime format matching
  the staging table schema instead of varchar string (was breaking
  the entire sync receive import)
- C2: Fix staging table cleanup query - use datetime comparison
  matching the original admin import pattern
- W1: Add set_time_limit(300) to prevent timeout during large imports
- W2: Add REDIRECT_HTTP_AUTHORIZATION header support for Apache
  mod_rewrite compatibility
- W4: Add lizenz column parsing and update during sync import
- M1: Tighten export WHERE clause to require both aktueller_verein_id
  and spielernr (consistent with original export behavior)
- M2: Wrap syncGetLastStatus() in try/catch for graceful handling
  when sync_log table doesn't exist yet
2026-06-04 00:02:56 +02:00
Tim f39ade0e9d Implement Player Sync to DTFB (#286) 2026-06-03 18:36:39 +02:00
MarvinF 2a307b0987 Merge branch 'sportsmanager2-stage' into sportsmanager2-dev 2026-05-13 00:03:28 +02:00
MarvinF e8e6f7046d Merge pull request #283 from Deutscher-Tischfussballbund/sportsmanager2-issue282
add club mailing functionality to admin area
2026-05-13 00:02:35 +02:00
Marvin Flock 20ab5a44a9 fix: add table headers 2026-05-13 00:00:34 +02:00
Jürgen Meyer a5357e4a51 mailto Funktion bei Mannschaften in admin-Bereich Veranstaltung 2026-04-28 11:46:22 +02:00
Jürgen Meyer 68e16a3adb mailto Funktion bei Vereine in admin-Bereich 2026-04-28 09:45:29 +02:00
MarvinF cfc821f8ff Merge branch 'sportsmanager2-prod' into sportsmanager2-stage 2026-04-14 19:09:46 +02:00
MarvinF 582829331c Merge pull request #280 from Deutscher-Tischfussballbund/sportsmanager2-dev
dev to stage
2026-04-14 19:08:39 +02:00
MarvinF d8ccd08843 Merge branch 'sportsmanager2-stage' into sportsmanager2-dev 2026-04-14 19:08:15 +02:00
MarvinF 57e92da771 Merge pull request #279 from Deutscher-Tischfussballbund/sportsmanager2-issue274-neu
Enhancing Playerstatistics (Performance Index, Club Membership and more)
2026-04-14 19:06:45 +02:00
Marvin Flock 7f85888a26 fix: add table fix 2026-04-14 19:02:38 +02:00
Marvin Flock 13ad52f221 fix: add small fixes 2026-04-14 18:50:05 +02:00
Jürgen Meyer a44564a40e Anzeige naegative Satzpunkte in Ligatabelle 2026-04-09 11:11:37 +02:00
Jürgen Meyer ee4e817ad4 Aktualisierung Spielerstatistiken 2026-04-06 08:10:55 +02:00
jmeyer26 8a7ff6c234 Merge branch 'sportsmanager2-dev' into sportsmanager2-issue274-neu 2026-04-06 07:41:01 +02:00
Jürgen Meyer 8fb4ed1cdd Filter Mannschaften in Spielerstatistik 2026-04-06 07:33:33 +02:00
Jürgen Meyer a2243d9ccf Teams und Filter Spiele in Wertung hinzugefügt. 2026-04-06 05:53:13 +02:00
MarvinF 308fbae4e5 Merge pull request #277 from Deutscher-Tischfussballbund/sportsmanager2-issue275
Display Teamlogo in Hall of Fame
2026-04-05 19:10:55 +02:00
MarvinF d6d2a04ceb Merge branch 'sportsmanager2-dev' into sportsmanager2-issue275 2026-04-05 19:10:32 +02:00
MarvinF d7d6751b70 Merge pull request #278 from Deutscher-Tischfussballbund/sportsmanager2-issue276
Optimization of direct comparison
2026-04-05 19:07:34 +02:00
Jürgen Meyer b112b4dc31 Effizienzindex und Punkteschnitt zu Spielerstatistoken hinzugefügt. 2026-04-04 12:42:07 +02:00
jmeyer26 ac416c1822 Fehler Vergleich Satzpunkte korrigiert 2026-04-03 07:37:32 +02:00
Jürgen Meyer c92774b27d Spielerstatistik Race Permormance Index hinzugefügt 2026-04-02 18:48:43 +02:00
Jürgen Meyer b33a7e6a25 Unnötige Variablen gelöscht 2026-04-02 11:59:39 +02:00
Jürgen Meyer f86815dae5 Auswahl Direkter Vergleich nach Punkte, Satzpunkte, Tore 2026-04-02 11:56:51 +02:00
Jürgen Meyer 43a03bbb09 Hall of Fame - Zuordnung Mannschaftswappen über Teamname 2026-04-01 15:51:23 +02:00
MarvinF f430c7d35d Merge pull request #273 from Deutscher-Tischfussballbund/sportsmanager2-stage
stage to prod
2026-03-24 17:17:57 +01:00
MarvinF 73c352f6cf Merge branch 'sportsmanager2-prod' into sportsmanager2-stage 2026-03-24 17:16:00 +01:00
MarvinF c8c19ef8e0 Merge pull request #272 from Deutscher-Tischfussballbund/sportsmanager2-dev
dev to stage
2026-03-24 17:13:22 +01:00
MarvinF cb0db3833c Merge branch 'sportsmanager2-stage' into sportsmanager2-dev 2026-03-24 17:12:24 +01:00
MarvinF 29e7ea7ffa Merge pull request #271 from Deutscher-Tischfussballbund/fix/common-update
fix: refactor the update php to allow different databases
2026-03-24 17:10:46 +01:00
MarvinF 426a582844 Merge pull request #270 from Deutscher-Tischfussballbund/sportsmanager2-issue269
Fixes for Direct Compare, Player Statistics, Home field right
2026-03-24 02:12:16 +01:00
Marvin Flock c1763536c9 fix: refactor the update php to allow different databases 2026-03-24 01:38:18 +01:00
Jürgen Meyer 507861fd56 Neuberechung Quote in Spielerstatistik 2026-03-23 13:33:45 +01:00
Jürgen Meyer 168ad7dab4 Terminänderung funktionierte nicht mehr bei Tausch Heimrecht. 2026-03-23 12:08:11 +01:00
Jürgen Meyer c8483b077e Weiter Optimierung 2026-03-17 13:08:02 +01:00
Jürgen Meyer c23ed24962 Kleine Optimierung 2026-03-17 13:05:59 +01:00
Jürgen Meyer f70048f90f Fehlerkorrektur Direkter Vergleich 2026-03-17 12:47:43 +01:00
MarvinF 572ccee6fd Merge pull request #267 from Deutscher-Tischfussballbund/sportsmanager2-stage
stage to prod
2026-03-10 00:13:41 +01:00
MarvinF fa03965b17 Merge branch 'sportsmanager2-prod' into sportsmanager2-stage 2026-03-10 00:13:14 +01:00
MarvinF 0376b46470 Merge pull request #266 from Deutscher-Tischfussballbund/sportsmanager2-dev
dev to stage
2026-03-10 00:11:00 +01:00
MarvinF c8f419e00a Merge branch 'sportsmanager2-stage' into sportsmanager2-dev 2026-03-10 00:10:35 +01:00
MarvinF 116e4929b2 Merge pull request #265 from Deutscher-Tischfussballbund/sportsmanager2-issue261-issue262
Optimizations for Hall of fame, gameplans, associations, game postponement and more
2026-03-10 00:09:13 +01:00
MarvinF aed4d19c47 Merge branch 'sportsmanager2-dev' into sportsmanager2-issue261-issue262 2026-03-10 00:08:54 +01:00
Jürgen Meyer 2faa38e2b8 Hilfsausgabe gelöscht 2026-03-09 07:44:59 +01:00
Jürgen Meyer dc0fcea945 Beanstandung Copilot abgearbeitet 5 2026-03-09 07:43:20 +01:00
Jürgen Meyer e139ac4fc2 Beanstandung Copilot abgearbeitet 4 2026-03-08 23:59:38 +01:00
jmeyer26 5919994a3e Update src/structure/components/com_sportsmanager/admin.php
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-03-08 23:02:02 +01:00
jmeyer26 eccecedf95 Update src/structure/language/de-DE/de-DE.com_sportsmanager.ini
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-03-08 22:58:31 +01:00
Jürgen Meyer 9ed58f2916 Beanstandung Copilot abgearbeitet 2 2026-03-08 22:43:05 +01:00
Jürgen Meyer 1a9188c07b Beanstandung Copilot abgearbeitet 1 2026-03-08 21:46:09 +01:00
Jürgen Meyer 9435515088 Beim Löschen von Spieler bzw. Vereine werden die entsprechenden ids in Hall of Fame und Verbandsorgane gelöscht 2026-03-08 20:43:10 +01:00
Jürgen Meyer 4ada3a3b77 Bemerkungen angepasst 2026-03-08 12:23:39 +01:00
Jürgen Meyer 8391bf9df1 Automatische Spielberichtskorrektur bei doppelter Spielnummer 2026-03-08 12:19:10 +01:00
Jürgen Meyer bac4c0ada4 Funktion Spielbericht löschen hinzugefügt. 2026-03-08 09:23:27 +01:00
MarvinF ba50b0d6a4 Merge pull request #264 from Deutscher-Tischfussballbund/sportsmanager2-stage
stage to prod
2026-03-07 01:12:47 +01:00
MarvinF e4c4694dcb Merge branch 'sportsmanager2-prod' into sportsmanager2-stage 2026-03-07 01:12:18 +01:00
MarvinF e5de0030f2 Merge pull request #263 from Deutscher-Tischfussballbund/sportsmanager2-dev
dev to stage
2026-03-07 01:10:38 +01:00
MarvinF 8d364e0bfd Merge branch 'sportsmanager2-stage' into sportsmanager2-dev 2026-03-07 01:10:17 +01:00
Jürgen Meyer eed02e396d Evtl. vorhandene Ordnungsstrafen werden beim Löschen eines Spieles mitgelöscht. 2026-03-06 20:16:54 +01:00
Jürgen Meyer 40be14ad75 Heimrechttausch: Sicherheitsabfrage und speichern in Historie 2026-03-06 15:10:01 +01:00
Jürgen Meyer 54965f9ef2 Scrollbalken bei Ordnungsstrafen, Verbandsorgane und Hall of Fame 2026-03-06 10:43:46 +01:00
jmeyer26 8780229435 Merge pull request #260 from Deutscher-Tischfussballbund/sportsmanager2-250
Hall of Fame hinzugefügt
2026-03-02 19:51:09 +01:00
Jürgen Meyer a856a979aa Hall of Fame hinzugefügt 2026-03-02 19:49:22 +01:00
Jürgen Meyer 578fb76ead Klammer in update.php fehlte nach resolve conflicts 2026-02-25 11:00:02 +01:00
jmeyer26 b199b73d6f Merge pull request #258 from Deutscher-Tischfussballbund/sportsmanager2-issue070
Sportsmanager2 issue070
2026-02-25 09:42:22 +01:00
jmeyer26 0d61e9a823 Merge branch 'sportsmanager2-dev' into sportsmanager2-issue070 2026-02-25 09:40:39 +01:00
jmeyer26 299b0bef4e Merge pull request #257 from Deutscher-Tischfussballbund/sportsmanager2-explicit-penalties
Explicit penalties for league teams
2026-02-25 09:30:49 +01:00
Jürgen Meyer 57c158a770 Kleine Korrekturen 2026-02-24 18:49:29 +01:00
Wieland Hagen 84d07aecb1 Explicit penalties for league teams (3): add moderator_user_id 2026-02-23 16:18:35 +01:00
Wieland Hagen 5bd0735708 Explicit penalties for league teams (2): resolved issues in PR 2026-02-23 15:43:29 +01:00
Jürgen Meyer 69e2032916 Letzte Änderung 2026-02-13 11:00:11 +01:00
jmeyer26 e5aede7cac Update src/structure/components/com_sportsmanager/admin.php
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-02-13 10:35:07 +01:00
jmeyer26 7f8f842796 Update src/structure/components/com_sportsmanager/admin.php
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-02-13 08:53:21 +01:00
jmeyer26 112511aa12 Update src/structure/components/com_sportsmanager/sportsmanager.php
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-02-13 08:49:51 +01:00
jmeyer26 eacb0dc165 Update src/structure/components/com_sportsmanager/views/sportsmanager/view.html.php
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-02-13 08:47:22 +01:00
Jürgen Meyer 67f5f38d1a Vorschläge des Copiloten umgesetzt 2026-02-13 08:10:35 +01:00
jmeyer26 f65ec6be88 Update src/structure/components/com_sportsmanager/database/update.php
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-02-13 06:54:41 +01:00
jmeyer26 427bd545af Update src/structure/script.php
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-02-13 06:54:20 +01:00
jmeyer26 986cd6d99e Update src/structure/components/com_sportsmanager/admin.php
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-02-13 06:42:58 +01:00
Jürgen Meyer 66baeb0f77 update.php und script.php erweitert 2026-02-12 17:04:26 +01:00
Jürgen Meyer 46474c6c40 Verteilerliste erweitert 2026-02-12 15:58:23 +01:00
Jürgen Meyer a583131162 Exportfunktion und ein paar Korrekturen 2026-02-12 15:06:38 +01:00
Jürgen Meyer cb8b6ebe06 Einige Optimierungen 2026-02-12 11:33:29 +01:00
Jürgen Meyer d41376494f Bilder hinzugefügt und ein paar Optimierungen 2026-02-11 17:03:02 +01:00
Jürgen Meyer d462137424 Frontend für Verbandsorgane erstellt. 2026-02-11 13:58:12 +01:00
Jürgen Meyer 3cb3fa2b28 Formulare für Mitglieder Verbandsorgane erstellt. 2026-02-10 16:23:57 +01:00
Jürgen Meyer 10807c7ea8 Organisation hinzugefügt und kleine Korrekturen. 2026-02-10 10:00:46 +01:00
Jürgen Meyer e469e70b97 Formulare für Tabelle Verbandsorgane erstellt 2026-02-09 17:20:45 +01:00
Wieland Hagen 522369bb14 Explicit penalties for league teams 2026-02-09 11:09:02 +01:00
MarvinF 91caf76a71 Merge pull request #256 from Deutscher-Tischfussballbund/sportsmanager2-stage
stage to prod
2026-02-03 19:27:41 +01:00
MarvinF 12037f9bac Merge branch 'sportsmanager2-prod' into sportsmanager2-stage 2026-02-03 19:27:10 +01:00
MarvinF b1af98df54 Merge pull request #255 from Deutscher-Tischfussballbund/sportsmanager2-dev
dev to stage
2026-02-03 19:25:43 +01:00
MarvinF fe885160d9 Merge branch 'sportsmanager2-stage' into sportsmanager2-dev 2026-02-03 19:25:22 +01:00
MarvinF 308bbd4d76 Update build_release.yml
remove teams notification
2026-02-03 19:23:19 +01:00
MarvinF 89bbba9c1c Merge pull request #254 from Deutscher-Tischfussballbund/sportsmanager2-stage
stage to prod merge
2026-02-03 19:17:18 +01:00
MarvinF aa07d51cd3 Merge branch 'sportsmanager2-prod' into sportsmanager2-stage 2026-02-03 19:09:48 +01:00
MarvinF 5ca5015eb4 Merge pull request #253 from Deutscher-Tischfussballbund/sportsmanager2-dev
dev to stage merge
2026-02-03 19:09:10 +01:00
MarvinF a0ef2bda54 Merge branch 'sportsmanager2-stage' into sportsmanager2-dev 2026-02-03 19:08:43 +01:00
MarvinF 3300a66c19 Merge pull request #249 from Deutscher-Tischfussballbund/sportsmanager2-issue244
Sportsmanager2 issue244
2026-02-03 19:02:44 +01:00
MarvinF cd60c9f42a Merge branch 'sportsmanager2-dev' into sportsmanager2-issue244 2026-02-03 19:02:23 +01:00
MarvinF 692fd0a676 Merge pull request #248 from Deutscher-Tischfussballbund/sportsmanager2-issue247
remove delimiter on csv-export
2026-02-02 18:56:41 +01:00
MarvinF b8e21aa8ee Merge branch 'sportsmanager2-dev' into sportsmanager2-issue247 2026-02-02 18:56:04 +01:00
MarvinF 1d66905488 Merge pull request #251 from Deutscher-Tischfussballbund/Sportsmanager2-issue250
fix error "spiel_nr on null" in team tournament encounter
2026-02-02 17:00:30 +01:00
Jürgen Meyer 991d0501df Tipp des Copiloten benutzt :) 2026-01-31 19:04:08 +01:00
Jürgen Meyer c5ffbed4da Fehler bei Begegnung hinzufügen gefixt. 2026-01-31 18:49:25 +01:00
Jürgen Meyer 33c94ae907 Rechtschreibkorrketur 2026-01-28 18:04:26 +01:00
jmeyer26 8890ca35d2 Update src/structure/components/com_sportsmanager/views/sportsmanager/view.html.php
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-01-28 17:59:23 +01:00
jmeyer26 50d332cd45 Update src/structure/language/en-GB/en-GB.com_sportsmanager.ini
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-01-28 17:58:09 +01:00
jmeyer26 90cf2cc4ca Update src/structure/language/de-DE/de-DE.com_sportsmanager.ini
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-01-28 17:01:07 +01:00
jmeyer26 ea0515e827 Update src/structure/language/en-GB/en-GB.com_sportsmanager.ini
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-01-28 17:00:52 +01:00
Jürgen Meyer a868d56450 Spielerstatus im Verein in Spielerdetails 2026-01-28 16:51:52 +01:00
Jürgen Meyer eaaa75d584 Checkbox Lizenznummer erhalten entfernt. 2026-01-28 12:10:42 +01:00
Jürgen Meyer 1479fc7e20 Importfunktion angepasst 2026-01-27 18:27:28 +01:00
Jürgen Meyer 70563bc8e4 Formulare für Spielerimport angepasst. 2026-01-27 14:57:43 +01:00
Jürgen Meyer 218436c065 CSV Export Funktion mittels KI komplett neu erstellt. 2026-01-27 13:02:51 +01:00
Jürgen Meyer c21d20e532 Bei csv-Export wird Semikolon im Text durch Komma ersetzt (bei Export von Ligabetrieb, Ordnungsstrafen und Spielverlegungen) 2026-01-27 07:00:54 +01:00
MarvinF 8b22eb886d Merge pull request #246 from Deutscher-Tischfussballbund/sportsmanager2-stage
stage to prod
2026-01-26 17:59:27 +01:00
MarvinF ae05c42209 Merge branch 'sportsmanager2-prod' into sportsmanager2-stage 2026-01-26 17:59:03 +01:00
MarvinF 3bc1324b4b Merge pull request #245 from Deutscher-Tischfussballbund/sportsmanager2-dev
dev to stage
2026-01-26 17:57:36 +01:00
MarvinF 23ceb2e272 Merge branch 'sportsmanager2-stage' into sportsmanager2-dev 2026-01-26 17:56:42 +01:00
MarvinF 5b26295ebf Merge pull request #243 from Deutscher-Tischfussballbund/sportsmanager2-issue242
Sportsmanager2 issue242
2026-01-26 17:51:55 +01:00
Jürgen Meyer 9d3cd6b959 Telefonnummer Spielort in Team Details 2026-01-26 11:28:21 +01:00
Jürgen Meyer 6b7c5e7a3b Export Teamdaten implementiert. 2026-01-22 14:45:37 +01:00
Jürgen Meyer ab6f776b96 Tabellen Spielorte, Teams um die Felder telefon, EMail, Ruhetage und Trainingstage erweitert. 2026-01-22 11:31:27 +01:00
jmeyer26 8fed3c217f Merge pull request #241 from Deutscher-Tischfussballbund/sportsmanager2-issue240
Sportsmanager2 issue240
2026-01-22 09:18:25 +01:00
jmeyer26 633ec1f1cb Merge pull request #238 from Deutscher-Tischfussballbund/sportsmanager2-issue234
Sportsmanager2 issue234
2026-01-22 09:17:18 +01:00
Jürgen Meyer 41e229340d Formulare optimiert 2026-01-14 18:22:02 +01:00
Jürgen Meyer 379427d4c4 json Import implementiert. csv-Import um Lizenznummer erweitert. 2026-01-13 19:42:08 +01:00
Jürgen Meyer a2808b7db0 Erst mal ein bisschen aufgeräumt 2026-01-13 11:17:32 +01:00
Jürgen Meyer c3cec6c93c SQL-Anweisungen übersichtlicher gestaltet. 2026-01-06 16:53:19 +01:00
Jürgen Meyer 9913279519 Benutzerrechte für OS und SV angepasst 2026-01-06 13:10:40 +01:00
Jürgen Meyer 0c78ed8b63 Fehler bei Insert Antragsteller korrigiert 2026-01-04 13:29:29 +01:00
Jürgen Meyer 4a448cb61d Letzter Feinschliff 2026-01-04 12:14:21 +01:00
Jürgen Meyer 825ff04a89 script.php angepasst und noch ein paar kleine Korrekturen 2026-01-03 17:45:35 +01:00
Jürgen Meyer 8b7ca05fc1 Mailen von Spielverlegung und Export von Spielverlegungen 2026-01-02 23:12:45 +01:00
Jürgen Meyer 76f0ef5f55 Anlegen und Löschen von Spielverlegungen 2026-01-01 18:31:09 +01:00
Jürgen Meyer f76f1185f5 Abfragen optimiert 2025-12-30 18:53:44 +01:00
Jürgen Meyer 7253426b51 Menüstruktur für admin Spielverlegungen erstellt 2025-12-29 17:45:21 +01:00
Jürgen Meyer 4686f5d8db Liste Spielverlegungen in Frontend 2025-12-29 12:44:50 +01:00
Jürgen Meyer db80f584cd Verschiebereln erweitert. Kategoriefilter optimiert. 2025-12-25 13:20:54 +01:00
MarvinF 275693ecb3 Merge pull request #232 from Deutscher-Tischfussballbund/sportsmanager-issue069
implemented disciplinary penalties for clubs
2025-12-23 13:25:33 +01:00
MarvinF fb3c088835 Merge branch 'sportsmanager2-dev' into sportsmanager-issue069 2025-12-23 13:23:00 +01:00
jmeyer26 ddca0066bf Update src/structure/components/com_sportsmanager/admin.php
Co-authored-by: MarvinF <B3r@users.noreply.github.com>
2025-12-23 13:00:55 +01:00
MarvinF bc56e30682 Merge pull request #237 from Deutscher-Tischfussballbund/sportsmanager-issue236
Wrong winner when doing draw for cup
2025-12-23 12:39:19 +01:00
Jürgen Meyer 2fe9acb01c Änderungen gecheckt, Absender in joomlamail hinzugefügt. 2025-12-22 10:16:09 +01:00
Jürgen Meyer de3705ca65 Änderungen nach Kommentaren 2025-12-22 09:19:19 +01:00
Marvin Flock 947a150b06 fix: apply minor fixes to deprecated config and redundant variable assignment 2025-12-22 00:09:50 +01:00
Jürgen Meyer c8199abee1 Bei Pokalauslosung werden jetzt die richtigen Sieger erkannt 2025-12-21 18:35:44 +01:00
Jürgen Meyer 6c1a6aa0de Änderungen nach Kommentar vorgenommen 2025-12-21 05:21:36 +01:00
Marvin Flock 626b9c503a Merge branch 'sportsmanager2-dev' into sportsmanager-issue069
# Conflicts:
#	src/structure/components/com_sportsmanager/database/update.php
2025-12-20 17:21:54 +01:00
Jürgen Meyer 964ab1ca8b E-Mail über joomla. Status Überarbeitung. 2025-12-20 10:03:05 +01:00
Jürgen Meyer 783cffaf2b Kommemtar entfernt 2025-12-12 15:48:20 +01:00
Jürgen Meyer 12cb9a0e67 Fehler bei Export korrigiert. 2025-12-12 15:29:20 +01:00
Jürgen Meyer 49c8037dbf Export Ordnungsstrafen. Unnötige Tags entfernt. 2025-12-12 12:11:24 +01:00
Jürgen Meyer e7dbfa6402 Ordnungsstrafen implementiert 2025-12-11 17:52:36 +01:00
17 changed files with 11416 additions and 3197 deletions
-21
View File
@@ -98,24 +98,3 @@ jobs:
else
echo "No draft releases found"
fi
- name: Send notification to Teams
run: |
curl -H 'Content-Type: application/json' \
-d "{
\"@type\": \"MessageCard\",
\"@context\": \"http://schema.org/extensions\",
\"summary\": \"New Release\",
\"themeColor\": \"0076D7\",
\"title\": \"🚀 New Release: ${{ github.ref_name }}\",
\"sections\": [{
\"activityTitle\": \"Repository: ${{ github.repository }}\",
\"text\": \"${{ steps.release_notes_github.outputs.body }}\"
}],
\"potentialAction\": [{
\"@type\": \"OpenUri\",
\"name\": \"View Release\",
\"targets\": [{ \"os\": \"default\", \"uri\": \"https://github.com/${{ github.repository }}/releases/tag/${{ github.ref_name }}\" }]
}]
}" \
${{ secrets.TEAMS_WEBHOOK_URL }}
+22
View File
@@ -0,0 +1,22 @@
name: Nightly DTFB Player Sync
on:
schedule:
- cron: '0 2 * * *' # Every night at 2:00 AM UTC
workflow_dispatch: # Allow manual trigger from GitHub
jobs:
sync:
runs-on: ubuntu-latest
steps:
- name: Trigger DTFB Sync
run: |
response=$(curl -s -o /dev/null -w "%{http_code}" \
-X POST \
-H "Authorization: Bearer ${{ secrets.DTFB_SYNC_KEY }}" \
-H "Content-Type: application/json" \
"${{ secrets.DTFB_SYNC_TRIGGER_URL }}")
if [ "$response" != "200" ]; then
echo "Sync failed with HTTP $response"
exit 1
fi
echo "Sync triggered successfully"
@@ -21,12 +21,15 @@ COM_SPORTSMANAGER_LAYOUT_GENERAL_CONTENT_OPTION_TEAMS_JOINT="Mannschaften in gem
COM_SPORTSMANAGER_LAYOUT_GENERAL_CONTENT_OPTION_CLUBS="Vereine"
COM_SPORTSMANAGER_LAYOUT_GENERAL_CONTENT_OPTION_VENUES="Spielorte"
COM_SPORTSMANAGER_LAYOUT_GENERAL_CONTENT_OPTION_APPOINTMENTS="Termine"
COM_SPORTSMANAGER_LAYOUT_GENERAL_CONTENT_OPTION_MATCH_RESCHEDULING="Spielverlegungen"
COM_SPORTSMANAGER_LAYOUT_GENERAL_CONTENT_OPTION_ASSOCIATION_BODIES="Verbandsorgane"
COM_SPORTSMANAGER_LAYOUT_GENERAL_CONTENT_OPTION_HALL_OF_FAME="Hall Of Fame"
COM_SPORTSMANAGER_LAYOUT_GENERAL_CONTENT_OPTION_TITLE="Titel"
COM_SPORTSMANAGER_LAYOUT_GENERAL_CONTENT_OPTION_TITLE_DESC="Titel, der im Fenster oben angezeigt wird"
COM_SPORTSMANAGER_LAYOUT_GENERAL_CONTENT_OPTION_DESCRIPTION="Beschreibung"
COM_SPORTSMANAGER_LAYOUT_GENERAL_CONTENT_OPTION_DESCRIPTION_DESC="Beschreibung, die unterhalb des Titels angezeigt wird (WICHTIG: Werden HTML-Tags verwendet, müssen auch Umlaute in HTML-Code angeben werden)"
COM_SPORTSMANAGER_LAYOUT_GENERAL_CONTENT_OPTION_CATEGORIES="Kategorien"
COM_SPORTSMANAGER_LAYOUT_GENERAL_CONTENT_OPTION_CATEGORIES_DESC="Eine optionale Auswahl an durch Kommata getrennte Kategorienummern"
COM_SPORTSMANAGER_LAYOUT_GENERAL_CONTENT_OPTION_CATEGORIES_DESC="Eine optionale Auswahl von Kategorienummern durch Kommata oder Spiegelstrich getrennt"
COM_SPORTSMANAGER_LAYOUT_ELO_RANKING_TITLE="Layout: Elo-Rangliste"
COM_SPORTSMANAGER_LAYOUT_ELO_RANKING_DESC="Auflistung der Spieler sortiert nach Elo-Wertung"
COM_SPORTSMANAGER_LAYOUT_ELO_RANKING_OPTION_ELO_RANKING="Elo-Rangliste"
@@ -21,12 +21,15 @@ COM_SPORTSMANAGER_LAYOUT_GENERAL_CONTENT_OPTION_TEAMS_JOINT="Teams in joint list
COM_SPORTSMANAGER_LAYOUT_GENERAL_CONTENT_OPTION_CLUBS="Clubs"
COM_SPORTSMANAGER_LAYOUT_GENERAL_CONTENT_OPTION_VENUES="Venues"
COM_SPORTSMANAGER_LAYOUT_GENERAL_CONTENT_OPTION_APPOINTMENTS="Appointments"
COM_SPORTSMANAGER_LAYOUT_GENERAL_CONTENT_OPTION_MATCH_RESCHEDULING="Match reschedulings"
COM_SPORTSMANAGER_LAYOUT_GENERAL_CONTENT_OPTION_ASSOCIATION_BODIES="Association bodies"
COM_SPORTSMANAGER_LAYOUT_GENERAL_CONTENT_OPTION_HALL_OF_FAME="Hall of fame"
COM_SPORTSMANAGER_LAYOUT_GENERAL_CONTENT_OPTION_TITLE="Title"
COM_SPORTSMANAGER_LAYOUT_GENERAL_CONTENT_OPTION_TITLE_DESC="Title which will be shows on top"
COM_SPORTSMANAGER_LAYOUT_GENERAL_CONTENT_OPTION_DESCRIPTION="Description"
COM_SPORTSMANAGER_LAYOUT_GENERAL_CONTENT_OPTION_DESCRIPTION_DESC="Description that will be shows below the titel (IMPORTANT: if html tags are used, special characters must be maskeraded)"
COM_SPORTSMANAGER_LAYOUT_GENERAL_CONTENT_OPTION_CATEGORIES="Categories"
COM_SPORTSMANAGER_LAYOUT_GENERAL_CONTENT_OPTION_CATEGORIES_DESC="An optional selection of category numbers seperated by commas"
COM_SPORTSMANAGER_LAYOUT_GENERAL_CONTENT_OPTION_CATEGORIES_DESC="An optional selection of category numbers seperated by commas or bullet point"
COM_SPORTSMANAGER_LAYOUT_ELO_RANKING_TITLE="Layout: elo ranking"
COM_SPORTSMANAGER_LAYOUT_ELO_RANKING_DESC="Listing of players sorted by elo rating"
COM_SPORTSMANAGER_LAYOUT_ELO_RANKING_OPTION_ELO_RANKING="Elo ranking"
File diff suppressed because it is too large Load Diff
@@ -5435,6 +5435,346 @@ function updateDatabase(): void
}
}
if ($datenbank_version < 114) {
$query = "CREATE TABLE IF NOT EXISTS `#__sportsmanager_regelwerke` ("
. "\n `regelwerke_id` int(11) NOT NULL AUTO_INCREMENT,"
. "\n `regelwerk` varchar(32) NOT NULL,"
. "\n PRIMARY KEY (`regelwerke_id`),"
. "\n UNIQUE KEY `regelwerk` (`regelwerk`)"
. "\n ) ENGINE=MyISAM DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;";
$db->setQuery( $query );
if (!$db->execute()) { die($db->stderr(true)); }
$query = "CREATE TABLE IF NOT EXISTS `#__sportsmanager_verstoesse` ("
. "\n `verstoesse_id` int(11) NOT NULL AUTO_INCREMENT,"
. "\n `regelwerke_id` int(11) NOT NULL DEFAULT 0,"
. "\n `paragraph_spo` varchar(32) NOT NULL DEFAULT '',"
. "\n `paragraph_go` varchar(32) NOT NULL DEFAULT '',"
. "\n `verstoss` varchar(64) NOT NULL DEFAULT '',"
. "\n `haupttext` text NOT NULL,"
. "\n `zusatztext` text NOT NULL,"
. "\n `gebuehr` smallint(3) NOT NULL DEFAULT 0,"
. "\n `zusatzgebuehr` smallint(2) NOT NULL DEFAULT 0,"
. "\n `zur_auswahl` tinyint(1) NOT NULL DEFAULT 1,"
. "\n PRIMARY KEY (`verstoesse_id`)"
. "\n ) ENGINE=MyISAM DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;";
$db->setQuery( $query );
if (!$db->execute()) { die($db->stderr(true)); }
$query = "CREATE TABLE IF NOT EXISTS `#__sportsmanager_ordnungsstrafen` ("
. "\n `ordnungsstrafen_id` int(11) NOT NULL AUTO_INCREMENT,"
. "\n `verstoesse_id` int(11) NOT NULL DEFAULT 0,"
. "\n `begegnung_id` int(11) NOT NULL DEFAULT 0,"
. "\n `team_id` int(11) NOT NULL DEFAULT 0,"
. "\n `aussteller_id` int(11) NOT NULL DEFAULT 0,"
. "\n `ausstelldatum` datetime NOT NULL DEFAULT current_timestamp(),"
. "\n `versender_id` int(11) DEFAULT NULL,"
. "\n `versendedatum` datetime DEFAULT NULL,"
. "\n `rechnungssteller_id` int(11) DEFAULT NULL,"
. "\n `rechnungsdatum` datetime DEFAULT NULL,"
. "\n `multiplikator` tinyint(1) NOT NULL DEFAULT 1,"
. "\n `weitere_angaben` text NOT NULL,"
. "\n PRIMARY KEY (`ordnungsstrafen_id`)"
. "\n ) ENGINE=MyISAM DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;";
$db->setQuery( $query );
if (!$db->execute()) { die($db->stderr(true)); }
$query = "CREATE TABLE IF NOT EXISTS `#__sportsmanager_email_vorlagen` ("
. "\n `email_vorlagen_id` int(11) NOT NULL AUTO_INCREMENT,"
. "\n `vorlage` varchar(64) NOT NULL,"
. "\n `betreff` varchar(256) NOT NULL DEFAULT '',"
. "\n `von` varchar(64) NOT NULL DEFAULT '',"
. "\n `an` varchar(256) NOT NULL DEFAULT '',"
. "\n `cc` varchar(256) NOT NULL DEFAULT '',"
. "\n `bcc` varchar(256) NOT NULL DEFAULT '',"
. "\n `email_text` text NOT NULL,"
. "\n PRIMARY KEY (`email_vorlagen_id`),"
. "\n UNIQUE KEY `vorlage` (`vorlage`)"
. "\n ) ENGINE=MyISAM DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;";
$db->setQuery( $query );
if (!$db->execute()) { die($db->stderr(true)); }
$query = "INSERT IGNORE #__sportsmanager_einstellungen SET name = 'ordnungsstrafen_verwenden', wert = '0';";
$db->setQuery( $query );
if (!$db->execute()) { die($db->stderr(true)); }
$query = "INSERT IGNORE INTO `#__sportsmanager_email_vorlagen` (`email_vorlagen_id`, `vorlage`) VALUES (NULL, 'Ordnungsstrafe');";
$db->setQuery($query);
if (!$db->execute()) { die($db->stderr(true)); }
$columns = $db->getTableColumns('#__sportsmanager_veranstaltung');
if (!array_key_exists('regelwerke_id', $columns)) {
$query = "ALTER TABLE `#__sportsmanager_veranstaltung` ADD COLUMN `regelwerke_id` INT(11) NOT NULL DEFAULT '0' AFTER `bezeichnung`;";
$db->setQuery($query);
if (!$db->execute()) { die($db->stderr(true)); }
}
$query = "UPDATE #__sportsmanager_einstellungen"
. "\n SET wert = '114'"
. "\n WHERE name = 'datenbank_version'";
$db->setQuery($query);
if (!$db->execute()) {
die($db->stderr(true));
}
}
if ($datenbank_version < 115) {
// Erweiterung Tabelle #__sportsmanager_verschieberegel
$columns = $db->getTableColumns('#__sportsmanager_verschieberegel');
if (!array_key_exists('begruendung_erforderlich', $columns)
|| !array_key_exists('vereine_berechtigt', $columns)
|| !array_key_exists('verband_berechtigt', $columns)) {
$query = "ALTER TABLE `#__sportsmanager_verschieberegel`"
. "\n ADD COLUMN `begruendung_erforderlich` INT(1) NOT NULL DEFAULT 0 AFTER `ablehnen`,"
. "\n ADD COLUMN `vereine_berechtigt` INT(1) NOT NULL DEFAULT 1 AFTER `begruendung_erforderlich`,"
. "\n ADD COLUMN `verband_berechtigt` INT(1) NOT NULL DEFAULT 0 AFTER `vereine_berechtigt`;";
$db->setQuery($query);
if (!$db->execute()) {
die($db->stderr(true));
}
}
$query = "INSERT IGNORE INTO `#__sportsmanager_email_vorlagen` (`email_vorlagen_id`, `vorlage`) VALUES (NULL, 'Spielverlegung');";
$db->setQuery($query);
if (!$db->execute()) { die($db->stderr(true)); }
$query = "UPDATE #__sportsmanager_einstellungen"
. "\n SET wert = '115'"
. "\n WHERE name = 'datenbank_version'";
$db->setQuery($query);
if (!$db->execute()) {
die($db->stderr(true));
}
}
if ($datenbank_version < 116) {
$columns = $db->getTableColumns('#__sportsmanager_spielort');
if (!array_key_exists('telefon', $columns)
|| !array_key_exists('email', $columns)
|| !array_key_exists('ruhetage', $columns)) {
$query = "ALTER TABLE `#__sportsmanager_spielort`"
. "\n ADD `telefon` VARCHAR(64) NULL DEFAULT NULL AFTER `url`,"
. "\n ADD `email` VARCHAR(64) NULL DEFAULT NULL AFTER `telefon`,"
. "\n ADD `ruhetage` VARCHAR(64) NULL DEFAULT NULL AFTER `email`;";
$db->setQuery($query);
if (!$db->execute()) {
die($db->stderr(true));
}
}
$columns = $db->getTableColumns('#__sportsmanager_team');
if (!array_key_exists('trainingstage', $columns)){
$query = "ALTER TABLE `#__sportsmanager_team`"
. "\n ADD `trainingstage` VARCHAR(64) NULL DEFAULT NULL AFTER `heimspielort_id`;";
$db->setQuery($query);
if (!$db->execute()) {
die($db->stderr(true));
}
}
$query = "UPDATE #__sportsmanager_einstellungen"
. "\n SET wert = '116'"
. "\n WHERE name = 'datenbank_version'";
$db->setQuery($query);
if (!$db->execute()) {
die($db->stderr(true));
}
}
if ($datenbank_version < 117) {
$query = "CREATE TABLE IF NOT EXISTS `#__sportsmanager_team_strafen` ("
. "\n `team_strafen_id` int(11) NOT NULL AUTO_INCREMENT,"
. "\n `zeitpunkt` datetime NOT NULL,"
. "\n `moderator_user_id` int(11) DEFAULT NULL,"
. "\n `team_id` int(11) NOT NULL DEFAULT '0',"
. "\n `strafe` float(6,2) NOT NULL DEFAULT '0',"
. "\n `beschreibung` text NOT NULL,"
. "\n PRIMARY KEY (`team_strafen_id`)"
. "\n) ENGINE=MyISAM DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;";
$db->setQuery( $query );
if (!$db->execute()) { die($db->stderr(true)); }
$columns = $db->getTableColumns('#__sportsmanager_veranstaltung');
if (!array_key_exists('explizite_strafen', $columns)) {
$query = "ALTER TABLE #__sportsmanager_veranstaltung"
. "\n ADD explizite_strafen tinyint(4) DEFAULT '0' AFTER elo_wertung";
$db->setQuery($query);
if (!$db->execute()) {
die($db->stderr(true));
}
}
$query = "UPDATE #__sportsmanager_einstellungen SET wert = '117' WHERE name = 'datenbank_version'";
$db->setQuery($query);
if (!$db->execute()) { die($db->stderr(true)); }
}
if ($datenbank_version < 118) {
$query = "CREATE TABLE IF NOT EXISTS `#__sportsmanager_verbandsorgane` ("
. "\n `verbandsorgane_id` int(11) NOT NULL AUTO_INCREMENT,"
. "\n `veranstalter_id` int(11) DEFAULT NULL,"
. "\n `verbandsorgan` varchar(32) DEFAULT NULL,"
. "\n `kategorie` int(4) DEFAULT NULL,"
. "\n `reihenfolge` int(4) DEFAULT NULL,"
. "\n `email` varchar(64) DEFAULT NULL,"
. "\n `beschreibung` text NOT NULL,"
. "\n PRIMARY KEY (`verbandsorgane_id`),"
. "\n KEY (`veranstalter_id`)"
. "\n ) ENGINE=MyISAM DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;";
$db->setQuery($query);
if (!$db->execute()) {
die($db->stderr(true));
}
$query = "CREATE TABLE IF NOT EXISTS `#__sportsmanager_mitglied_von_verbandsorgan` ("
. "\n `mitglied_von_verbandsorgan_id` int(11) NOT NULL AUTO_INCREMENT,"
. "\n `verbandsorgane_id` int(11) DEFAULT NULL,"
. "\n `funktion` varchar(32) DEFAULT NULL,"
. "\n `zusatzinfo` varchar(64) DEFAULT NULL,"
. "\n `spieler_id` int(11) DEFAULT NULL,"
. "\n `nachname` varchar(32) DEFAULT NULL,"
. "\n `vorname` varchar(32) DEFAULT NULL,"
. "\n `email` varchar(64) DEFAULT NULL,"
. "\n `telefon` varchar(32) DEFAULT NULL,"
. "\n `mobil` varchar(32) DEFAULT NULL,"
. "\n `reihenfolge` int(11) DEFAULT NULL,"
. "\n PRIMARY KEY (`mitglied_von_verbandsorgan_id`),"
. "\n KEY (`verbandsorgane_id`),"
. "\n KEY (`spieler_id`)"
. "\n ) ENGINE=MyISAM DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;";
$db->setQuery($query);
if (!$db->execute()) {
die($db->stderr(true));
}
$query = "UPDATE #__sportsmanager_einstellungen"
. "\n SET wert = '118'"
. "\n WHERE name = 'datenbank_version'";
$db->setQuery($query);
if (!$db->execute()) {
die($db->stderr(true));
}
}
if ($datenbank_version < 119) {
$query = "CREATE TABLE IF NOT EXISTS `#__sportsmanager_halloffame` ("
. "\n `halloffame_id` int(11) NOT NULL AUTO_INCREMENT,"
. "\n `veranstalter_id` int(11) DEFAULT NULL,"
. "\n `halloffame` varchar(64) DEFAULT NULL,"
. "\n `kategorie` int(4) DEFAULT NULL,"
. "\n `spielform` int(11) DEFAULT NULL,"
. "\n `reihenfolge` int(4) DEFAULT NULL,"
. "\n PRIMARY KEY (`halloffame_id`),"
. "\n KEY `veranstalter_id` (`veranstalter_id`)"
. "\n ) ENGINE=MyISAM DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;";
$db->setQuery($query);
if (!$db->execute()) {
die($db->stderr(true));
}
$query = "CREATE TABLE IF NOT EXISTS `#__sportsmanager_mitglied_von_halloffame` ("
. "\n `mitglied_halloffame_id` int(11) NOT NULL AUTO_INCREMENT,"
. "\n `halloffame_id` int(11) NOT NULL,"
. "\n `jahr` int(4) DEFAULT NULL,"
. "\n `platz` int(11) DEFAULT NULL,"
. "\n `verein_id` int(11) DEFAULT NULL,"
. "\n `teamname` varchar(64) DEFAULT NULL,"
. "\n `spieler1_id` int(11) DEFAULT NULL,"
. "\n `spieler1` varchar(64) DEFAULT NULL,"
. "\n `spieler2_id` int(11) DEFAULT NULL,"
. "\n `spieler2` varchar(64) DEFAULT NULL,"
. "\n PRIMARY KEY (`mitglied_halloffame_id`),"
. "\n UNIQUE KEY `halloffame_jahr_platz` (`halloffame_id`,`jahr`,`platz`),"
. "\n KEY `halloffame_id` (`halloffame_id`),"
. "\n KEY `verein_id` (`verein_id`),"
. "\n KEY `spieler1_id` (`spieler1_id`),"
. "\n KEY `spieler2_id` (`spieler2_id`)"
. "\n ) ENGINE=MyISAM DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;";
$db->setQuery($query);
if (!$db->execute()) {
die($db->stderr(true));
}
$query = "UPDATE #__sportsmanager_einstellungen"
. "\n SET wert = '119'"
. "\n WHERE name = 'datenbank_version'";
$db->setQuery($query);
if (!$db->execute()) {
die($db->stderr(true));
}
}
if ($datenbank_version < 120) {
$columns = $db->getTableColumns('#__sportsmanager_teamspiel_modus');
if (!array_key_exists('spiele_in_spielerstatistik', $columns)){
$query = "ALTER TABLE `#__sportsmanager_teamspiel_modus`"
. "\n ADD `spiele_in_spielerstatistik` INT(4) NOT NULL DEFAULT '0' AFTER `heimtausch`;";
$db->setQuery($query);
if (!$db->execute()) {
die($db->stderr(true));
}
}
$columns = $db->getTableColumns('#__sportsmanager_bestenliste_punkte');
if (!array_key_exists('team_id', $columns)){
$query = "ALTER TABLE `#__sportsmanager_bestenliste_punkte`"
. "\n ADD `team_id` INT(11) NULL DEFAULT NULL AFTER `spieler_2_id`;";
$db->setQuery($query);
if (!$db->execute()) {
die($db->stderr(true));
}
}
$spielerstatistik_aktualisieren = true;
$query = "UPDATE #__sportsmanager_einstellungen"
. "\n SET wert = '120'"
. "\n WHERE name = 'datenbank_version'";
$db->setQuery($query);
if (!$db->execute()) {
die($db->stderr(true));
}
}
if ($datenbank_version < 121) {
$query = "CREATE TABLE IF NOT EXISTS `#__sportsmanager_sync_log` ("
. "\n `sync_id` INT(11) NOT NULL AUTO_INCREMENT,"
. "\n `sync_timestamp` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,"
. "\n `sync_direction` ENUM('push', 'receive') NOT NULL,"
. "\n `sync_trigger` ENUM('manual', 'cron', 'api') NOT NULL,"
. "\n `sync_status` ENUM('success', 'error') NOT NULL,"
. "\n `spieler_count` INT(11) DEFAULT 0,"
. "\n `spieler_updated` INT(11) DEFAULT 0,"
. "\n `spieler_added` INT(11) DEFAULT 0,"
. "\n `message` TEXT,"
. "\n `details` TEXT,"
. "\n PRIMARY KEY (`sync_id`),"
. "\n INDEX `idx_timestamp` (`sync_timestamp`)"
. "\n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;";
$db->setQuery($query);
if (!$db->execute()) {
die($db->stderr(true));
}
$query = "INSERT IGNORE #__sportsmanager_einstellungen SET name = 'dtfb_sync_url', wert = '';";
$db->setQuery($query);
if (!$db->execute()) {
die($db->stderr(true));
}
$query = "UPDATE #__sportsmanager_einstellungen"
. "\n SET wert = '121'"
. "\n WHERE name = 'datenbank_version'";
$db->setQuery($query);
if (!$db->execute()) {
die($db->stderr(true));
}
}
if ($termin_aktionen_email_setzen) {
$query = "SELECT aktion_user_id, termin_aktion_id"
. "\n FROM #__sportsmanager_termin_aktion";
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,899 @@
<?php
/**
* Sports Manager Sync Extension
*/
use Joomla\CMS\Application\SiteApplication;
use Joomla\CMS\Factory;
use Joomla\CMS\Language\Text;
defined("_JEXEC") or die();
/**
* Gets the Bearer token from the Authorization header.
*
* @return string|null
*/
function syncGetBearerToken(): ?string
{
$headers = null;
if (isset($_SERVER['Authorization'])) {
$headers = trim($_SERVER["Authorization"]);
} elseif (isset($_SERVER['HTTP_AUTHORIZATION'])) {
$headers = trim($_SERVER["HTTP_AUTHORIZATION"]);
} elseif (isset($_SERVER['REDIRECT_HTTP_AUTHORIZATION'])) {
$headers = trim($_SERVER["REDIRECT_HTTP_AUTHORIZATION"]);
} elseif (function_exists('apache_request_headers')) {
$requestHeaders = apache_request_headers();
$requestHeaders = array_combine(array_map('ucwords', array_keys($requestHeaders)), array_values($requestHeaders));
if (isset($requestHeaders['Authorization'])) {
$headers = trim($requestHeaders['Authorization']);
}
}
if (!empty($headers)) {
if (preg_match('/Bearer\s(\S+)/i', $headers, $matches)) {
return $matches[1];
}
}
return null;
}
/**
* Logs a sync event in the database.
*
* @param string $direction ('push' or 'receive')
* @param string $trigger ('manual', 'cron', or 'api')
* @param string $status ('success' or 'error')
* @param int $spieler_count
* @param int $spieler_updated
* @param int $spieler_added
* @param string $message
* @param string $details
*/
function syncLogEntry(string $direction, string $trigger, string $status, int $spieler_count, int $spieler_updated, int $spieler_added, string $message, string $details = ''): void
{
try {
$db = getDatabase();
$query = "INSERT INTO #__sportsmanager_sync_log"
. "\n SET sync_timestamp = NOW(),"
. "\n sync_direction = '" . $db->escape($direction) . "',"
. "\n sync_trigger = '" . $db->escape($trigger) . "',"
. "\n sync_status = '" . $db->escape($status) . "',"
. "\n spieler_count = " . intval($spieler_count) . ","
. "\n spieler_updated = " . intval($spieler_updated) . ","
. "\n spieler_added = " . intval($spieler_added) . ","
. "\n message = '" . $db->escape($message) . "',"
. "\n details = '" . $db->escape($details) . "'";
$db->setQuery($query);
$db->execute();
} catch (Exception $e) {
error_log("Failed to write sync log: " . $e->getMessage());
}
}
/**
* Returns HTML displaying the status of the last sync operation.
*
* @return string
*/
function syncGetLastStatus(): string
{
try {
$db = getDatabase();
$query = "SELECT * FROM #__sportsmanager_sync_log ORDER BY sync_id DESC LIMIT 1";
$rows = loadObjectList($db, $query);
if (count($rows) === 0) {
return "Noch nie synchronisiert";
}
$row = $rows[0];
$statusClass = $row->sync_status === 'success' ? 'uk-text-success' : 'uk-text-danger';
$statusText = $row->sync_status === 'success' ? 'Erfolgreich' : 'Fehlgeschlagen';
$directionText = $row->sync_direction === 'push' ? 'Export (Push)' : 'Import (Receive)';
$triggerText = $row->sync_trigger === 'manual' ? 'Manuell' : ($row->sync_trigger === 'cron' ? 'Cron' : 'API');
$stats = "";
if ($row->sync_status === 'success') {
$stats = sprintf(
" (Spieler gesamt: %d, Aktualisiert: %d, Hinzugefügt: %d)",
$row->spieler_count,
$row->spieler_updated,
$row->spieler_added
);
} else {
$stats = " (Fehler: " . htmlspecialchars($row->message) . ")";
}
return sprintf(
"<span class='%s'><strong>%s</strong></span> am %s via %s / %s%s",
$statusClass,
$statusText,
date('d.m.Y H:i:s', strtotime($row->sync_timestamp)),
$directionText,
$triggerText,
$stats
);
} catch (Exception $e) {
return "Noch nie synchronisiert";
}
}
/**
* Exports player data to a tab-separated CSV string.
* Excludes personal contact details and images.
*
* @return string
*/
function syncExportSpielerCSV(): string
{
$db = getDatabase();
$jahr = date("Y");
$query = "SELECT nachname, vorname, spielernr, lizenznr, lizenz, geschlecht";
$query .= ",\n IF(ISNULL(geburtsjahr), IF(geschlecht = 'M', 'H', 'D'), IF(" . ($jahr - 18) . " <= geburtsjahr, 'J', IF(" . ($jahr - 50) . " > geburtsjahr, 'S', IF(geschlecht = 'M', 'H', 'D')))) AS kategorie";
$query .= ",\n vereinsname as verein, vereinssitz, veranstalterbezeichnung as organisation, IF(mitgliedsstatus = 1, 'Aktiv', IF(mitgliedsstatus = 0, 'Ausgetreten', IF(mitgliedsstatus = 2, 'Eingeschränkt', 'Passiv'))) AS mitgliedsstatus";
$query .= ",\n geburtsjahr";
$query .= "\n FROM #__sportsmanager_spieler";
$query .= "\n LEFT JOIN #__sportsmanager_mitglied_von_verein ON #__sportsmanager_spieler.spieler_id = #__sportsmanager_mitglied_von_verein.spieler_id AND #__sportsmanager_mitglied_von_verein.verein_id = #__sportsmanager_spieler.aktueller_verein_id"
. "\n LEFT JOIN #__sportsmanager_verein ON #__sportsmanager_verein.verein_id = #__sportsmanager_spieler.aktueller_verein_id"
. "\n LEFT JOIN #__sportsmanager_veranstalter ON #__sportsmanager_veranstalter.veranstalter_id = #__sportsmanager_verein.veranstalter_id";
$query .= "\n WHERE NOT ISNULL(aktueller_verein_id) AND NOT ISNULL(spielernr) AND spielernr != ''";
$query .= "\n ORDER BY nachname, vorname";
$rows = loadObjectList($db, $query);
if (count($rows) === 0) {
return "";
}
$trennzeichen = "\t";
$header = "";
foreach ($rows[0] as $field => $value) {
$header .= $field . $trennzeichen;
}
$header = rtrim($header, $trennzeichen);
$data = "";
foreach ($rows as $row) {
$line = '';
foreach ($row as $value) {
if ((!isset($value)) or ($value === "")) {
$value = $trennzeichen;
} else {
$value = str_replace('"', '""', $value);
$value = str_replace("\t", ' ', $value);
$value = str_replace("\r", '', $value);
$value = str_replace("\n", ' ', $value);
$value = '="' . $value . '"' . $trennzeichen;
}
$line .= $value;
}
$data .= rtrim($line, $trennzeichen) . "\n";
}
$data = str_replace("\r", "", $data);
return "sep=" . $trennzeichen . "\n" . $header . "\n" . $data;
}
/**
* Pushes the exported CSV data to DTFB (dtfb_sync_url) via cURL.
*
* @param string $csvData
* @return array
*/
function syncPushToDtfb(string $csvData): array
{
$push_key = einstellungswert("api_push_key");
$sync_url = einstellungswert("dtfb_sync_url");
if (empty($sync_url)) {
return [
'success' => false,
'message' => 'Sync-URL nicht konfiguriert.'
];
}
if (empty($push_key)) {
return [
'success' => false,
'message' => 'API Push Key nicht konfiguriert.'
];
}
$ch = curl_init($sync_url);
if (!$ch) {
return [
'success' => false,
'message' => 'Initialisierung von cURL fehlgeschlagen.'
];
}
curl_setopt_array($ch, array(
CURLOPT_POST => TRUE,
CURLOPT_RETURNTRANSFER => TRUE,
CURLOPT_HTTPHEADER => array(
'Authorization: Bearer ' . $push_key,
'Content-Type: text/csv; charset=utf-8',
),
CURLOPT_TIMEOUT => 60,
CURLOPT_POSTFIELDS => $csvData,
));
$resp = curl_exec($ch);
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if (curl_errno($ch)) {
$error_msg = curl_error($ch);
curl_close($ch);
return [
'success' => false,
'message' => 'cURL-Fehler: ' . $error_msg
];
}
curl_close($ch);
if ($http_code !== 200) {
return [
'success' => false,
'message' => 'HTTP-Status ' . $http_code . ': ' . $resp
];
}
$result = json_decode($resp, true);
if (json_last_error() === JSON_ERROR_NONE) {
if (isset($result['success']) && $result['success']) {
return [
'success' => true,
'message' => $result['message'] ?? 'Erfolgreich synchronisiert.',
'spieler_count' => $result['spieler_count'] ?? 0,
'spieler_updated' => $result['spieler_updated'] ?? 0,
'spieler_added' => $result['spieler_added'] ?? 0
];
} else {
return [
'success' => false,
'message' => $result['error'] ?? $result['message'] ?? 'Import auf Empfängerseite fehlgeschlagen.'
];
}
}
if (str_contains(strtolower($resp), 'success')) {
return [
'success' => true,
'message' => 'Erfolgreich synchronisiert (Klartext-Antwort).'
];
}
return [
'success' => false,
'message' => 'Unerwartete Antwort vom Server: ' . substr($resp, 0, 200)
];
}
/**
* Processes incoming CSV data and imports it into the local database.
* Aborts and returns an error if any organization name in the CSV cannot
* be matched with an existing local organization.
*
* @param string $csvData
* @return array
*/
function syncReceiveSpielerImport(string $csvData): array
{
if (!ini_get('safe_mode'))
set_time_limit(300);
$db = getDatabase();
// Normalise the payload to UTF-8. The automatic/semi-automatic sync path is
// UTF-8 end to end, but a legacy/manual export (e.g. from TFVHH) can be
// latin1/Windows-1252 encoded. Without this, non-ASCII bytes (e.g. "ß" in an
// organisation name) get truncated when staged into the utf8mb4 import table,
// causing the organisation match to fail and every row to be skipped silently.
if (!mb_check_encoding($csvData, 'UTF-8')) {
$csvData = mb_convert_encoding($csvData, 'UTF-8', 'Windows-1252');
}
$lines = explode("\n", str_replace("\r", "", $csvData));
if (count($lines) < 2) {
return [
'success' => false,
'message' => 'Keine Daten in der CSV-Datei gefunden.'
];
}
$lineIdx = 0;
$titelzeile = trim($lines[$lineIdx]);
if (str_starts_with($titelzeile, "sep=")) {
$trennzeichen = substr($titelzeile, 4);
if ($trennzeichen === "") {
$trennzeichen = "\t";
}
$lineIdx++;
if (isset($lines[$lineIdx])) {
$titelzeile = trim($lines[$lineIdx]);
} else {
return [
'success' => false,
'message' => 'CSV-Datei enthält nach sep= keine Titelzeile.'
];
}
} else {
$trennzeichen = "\t";
}
$titel = explode($trennzeichen, strtolower($titelzeile));
$spalte = array();
foreach ($titel as $index => $bezeichnung) {
$bezeichnung = trim($bezeichnung);
$len = strlen($bezeichnung);
if ($len >= 2 && $bezeichnung[0] === '"' && $bezeichnung[$len - 1] === '"') {
$bezeichnung = trim(str_replace('""', '"', substr($bezeichnung, 1, $len - 2)));
}
if ($bezeichnung === "name" || $bezeichnung === "nachname") {
$spalte["nachname"] = $index;
} else if ($bezeichnung === "vorname") {
$spalte["vorname"] = $index;
} else if ($bezeichnung === "name, vorname" || $bezeichnung === "name,vorname") {
$spalte["name,vorname"] = $index;
} else if ($bezeichnung === "pseudonym") {
$spalte["pseudonym"] = $index;
} else if ($bezeichnung === "geschlecht" || $bezeichnung === "anrede") {
$spalte["geschlecht"] = $index;
} else if ($bezeichnung === "spielernr" || $bezeichnung === "spielernr." || $bezeichnung === "spielerpass") {
$spalte["spielernr"] = $index;
} else if ($bezeichnung === "spielernr alt" || $bezeichnung === "spielernr. alt" || $bezeichnung === "spielernr_alt") {
$spalte["spielernr_alt"] = $index;
} else if ($bezeichnung === "lizenznr" || $bezeichnung === "lizenznr.") {
$spalte["lizenznr"] = $index;
} else if ($bezeichnung === "organisation") {
$spalte["organisation"] = $index;
} else if ($bezeichnung === "vereinssitz") {
$spalte["vereinssitz"] = $index;
} else if ($bezeichnung === "vereinsname" || $bezeichnung === "verein") {
$spalte["vereinsname"] = $index;
} else if ($bezeichnung === "geburtsdatum") {
$spalte["geburtsdatum"] = $index;
} else if ($bezeichnung === "geburtsjahr") {
$spalte["geburtsjahr"] = $index;
} else if ($bezeichnung === "email" || $bezeichnung === "e-mail") {
$spalte["email"] = $index;
} else if (str_starts_with($bezeichnung, "stra")) {
$spalte["strasse"] = $index;
} else if ($bezeichnung === "plz/ort") {
$spalte["plz/ort"] = $index;
} else if ($bezeichnung === "plz") {
$spalte["plz"] = $index;
} else if ($bezeichnung === "ort") {
$spalte["ort"] = $index;
} else if ($bezeichnung === "landeskennung") {
$spalte["landeskennung"] = $index;
} else if ($bezeichnung === "telefon") {
$spalte["telefon"] = $index;
} else if ($bezeichnung === "mobil") {
$spalte["mobil"] = $index;
} else if ($bezeichnung === "austritt" || $bezeichnung === "ausgetreten") {
$spalte["ausgetreten"] = $index;
} else if ($bezeichnung === "mitgliedsstatus") {
$spalte["mitgliedsstatus"] = $index;
}
}
if (((!isset($spalte["nachname"]) || !isset($spalte["vorname"])) && !isset($spalte["name,vorname"])) || !isset($spalte["spielernr"])) {
return [
'success' => false,
'message' => 'Die übergebene Datei ist keine gültige Spielerdatei (erforderliche Spalten fehlen).'
];
}
$lineIdx++;
// Source the staging session id from the database clock, not PHP's. The
// stale-row cleanup below compares session_id against the database NOW(); if
// PHP and the database run in different timezones, a PHP-generated timestamp
// can fall outside the window and the just-inserted rows get deleted mid-import.
$session_id = loadResult($db, "SELECT DATE_FORMAT(NOW(), '%Y-%m-%d %H:%i:%s')");
if (empty($session_id)) {
$session_id = date('Y-m-d H:i:s');
}
$organisations = [];
$rows_to_insert = [];
for ($i = $lineIdx; $i < count($lines); $i++) {
$buffer = trim($lines[$i]);
if ($buffer === "") {
continue;
}
$daten = explode($trennzeichen, $buffer);
foreach ($daten as $index => $wert) {
$wert = trim($wert);
$len = strlen($wert);
if ($len < 2 || $wert[$len - 1] !== '"' || !($wert[0] === '"' || ($wert[0] === '=' && $wert[1] === '"'))) {
$daten[$index] = $wert;
} else if ($wert[0] === '"') {
$daten[$index] = trim(str_replace('""', '"', substr($wert, 1, $len - 2)));
} else {
$daten[$index] = trim(str_replace('""', '"', substr($wert, 2, $len - 3)));
}
}
if (isset($spalte["vorname"]) && isset($spalte["nachname"]) && isset($daten[$spalte["vorname"]]) && isset($daten[$spalte["nachname"]])) {
$nachname = $daten[$spalte["nachname"]];
$vorname = $daten[$spalte["vorname"]];
} else if (isset($spalte["name,vorname"]) && isset($daten[$spalte["name,vorname"]])) {
$pos = strpos($daten[$spalte["name,vorname"]], ",");
if ($pos === false) {
continue;
}
$nachname = trim(substr($daten[$spalte["name,vorname"]], 0, $pos));
$vorname = trim(substr($daten[$spalte["name,vorname"]], $pos + 1));
} else {
continue;
}
if ($vorname === "" || $nachname === "") {
continue;
}
$mitgliedsstatus = 1;
if (isset($spalte["mitgliedsstatus"]) && !empty($daten[$spalte["mitgliedsstatus"]])) {
$s = strtolower($daten[$spalte["mitgliedsstatus"]]);
if ($s === "ausgetreten") {
$mitgliedsstatus = 0;
} else if ($s === "passiv") {
$mitgliedsstatus = 3;
} else if (str_starts_with($s, "eingeschr")) {
$mitgliedsstatus = 2;
}
} else if (isset($spalte["ausgetreten"]) && !empty($daten[$spalte["ausgetreten"]])) {
if (strtolower($daten[$spalte["ausgetreten"]]) === "ja") {
$mitgliedsstatus = 0;
}
}
if ($mitgliedsstatus == 0) {
continue;
}
$geschlecht = isset($spalte["geschlecht"]) && !empty($daten[$spalte["geschlecht"]]) ? (($daten[$spalte["geschlecht"]][0] === "M" || $daten[$spalte["geschlecht"]][0] === "m" || $daten[$spalte["geschlecht"]][0] === "H" || $daten[$spalte["geschlecht"]][0] === "h") ? "M" : "W") : "M";
$spielernr = isset($daten[$spalte["spielernr"]]) ? trim($daten[$spalte["spielernr"]]) : "";
// Validate the Passnummer with the same format the manual import enforces
// (NN-NNNN[NN]). Invalid values are dropped rather than aborting the whole
// automated feed, keeping the player but treating them as having no pass.
if (!empty($spielernr) && !preg_match('/^[0-9]{2}-[0-9]{4,6}$/', $spielernr)) {
$spielernr = "";
}
$spielernr_alt = isset($spalte["spielernr_alt"]) && isset($daten[$spalte["spielernr_alt"]]) ? trim($daten[$spalte["spielernr_alt"]]) : "";
if (!empty($spielernr_alt) && !preg_match('/^[0-9]{2}-[0-9]{4,6}$/', $spielernr_alt)) {
$spielernr_alt = "";
}
$lizenznr = isset($spalte["lizenznr"]) && isset($daten[$spalte["lizenznr"]]) ? $daten[$spalte["lizenznr"]] : "";
if (!empty($lizenznr) && !ctype_digit(substr($lizenznr, strlen($lizenznr) - 1, 1))) {
$lizenznr = "";
}
$pseudonym = isset($spalte["pseudonym"]) && isset($daten[$spalte["pseudonym"]]) ? $daten[$spalte["pseudonym"]] : "";
$organisation = isset($spalte["organisation"]) && isset($daten[$spalte["organisation"]]) ? $daten[$spalte["organisation"]] : "";
$vereinssitz = isset($spalte["vereinssitz"]) && isset($daten[$spalte["vereinssitz"]]) ? $daten[$spalte["vereinssitz"]] : "";
$vereinsname = isset($spalte["vereinsname"]) && isset($daten[$spalte["vereinsname"]]) ? $daten[$spalte["vereinsname"]] : "";
$geburtsjahr = isset($spalte["geburtsjahr"]) && isset($daten[$spalte["geburtsjahr"]]) ? $daten[$spalte["geburtsjahr"]] : null;
if (empty($geburtsjahr) || !ctype_digit($geburtsjahr) || $geburtsjahr < 1800) {
$geburtsjahr = null;
}
if (!empty($organisation)) {
$organisations[trim($organisation)] = true;
}
$rows_to_insert[] = [
'vorname' => $vorname,
'nachname' => $nachname,
'spielernr' => $spielernr,
'spielernr_alt' => $spielernr_alt,
'lizenznr' => $lizenznr,
'pseudonym' => $pseudonym,
'organisation' => $organisation,
'vereinssitz' => $vereinssitz,
'vereinsname' => $vereinsname,
'geburtsjahr' => $geburtsjahr,
'mitgliedsstatus' => $mitgliedsstatus,
'geschlecht' => $geschlecht
];
}
if (empty($rows_to_insert)) {
return [
'success' => false,
'message' => 'Keine gültigen Spielerzeilen zum Importieren gefunden.'
];
}
// Auto-match Veranstalter by name. If it does not match, abort.
$org_map = [];
foreach (array_keys($organisations) as $orgName) {
$query = "SELECT veranstalter_id FROM #__sportsmanager_veranstalter WHERE veranstalterbezeichnung = '" . $db->escape($orgName) . "'";
$res = loadObjectList($db, $query);
if (count($res) === 0) {
return [
'success' => false,
'message' => 'Veranstalter "' . $orgName . '" existiert nicht auf diesem Empfänger-System. Import abgebrochen.'
];
}
$org_map[$orgName] = intval($res[0]->veranstalter_id);
}
// Insert into staging table
foreach ($rows_to_insert as $row) {
$query = "INSERT INTO #__sportsmanager_spieler_import"
. "\n SET session_id = '" . $db->escape($session_id) . "',"
. "\n vorname = '" . $db->escape($row['vorname']) . "',"
. "\n nachname = '" . $db->escape($row['nachname']) . "',"
. "\n spielernr = '" . $db->escape($row['spielernr']) . "',"
. "\n spielernr_alt = '" . $db->escape($row['spielernr_alt']) . "',"
. "\n lizenznr = '" . $db->escape($row['lizenznr']) . "',"
. "\n pseudonym = '" . $db->escape($row['pseudonym']) . "',"
. "\n geschlecht = '" . $db->escape($row['geschlecht']) . "',"
. "\n geburtsjahr = " . ($row['geburtsjahr'] === null ? "NULL" : "'" . $db->escape($row['geburtsjahr']) . "'") . ","
. "\n vereinsname = '" . $db->escape($row['vereinsname']) . "',"
. "\n vereinssitz = '" . $db->escape($row['vereinssitz']) . "',"
. "\n veranstalterbezeichnung = '" . $db->escape($row['organisation']) . "',"
. "\n mitgliedsstatus = '" . $row['mitgliedsstatus'] . "'";
$db->setQuery($query);
if (!$db->execute()) {
return [
'success' => false,
'message' => 'Fehler beim Schreiben in die Staging-Tabelle: ' . $db->stderr()
];
}
}
// Clean up older staging data (older than 5 minutes)
$query = "SELECT DISTINCT session_id"
. "\n FROM #__sportsmanager_spieler_import"
. "\n WHERE session_id < SUBTIME(NOW(), '00:05:00')";
$old_sessions = loadObjectList($db, $query);
foreach ($old_sessions as $old_session) {
$query = "DELETE FROM #__sportsmanager_spieler_import WHERE session_id = '" . $db->escape($old_session->session_id) . "'";
$db->setQuery($query);
$db->execute();
}
// Fetch staging players with matching ID
$query = "SELECT #__sportsmanager_spieler_import.*, #__sportsmanager_spieler.spieler_id"
. "\n FROM #__sportsmanager_spieler_import"
. "\n LEFT JOIN #__sportsmanager_spieler ON #__sportsmanager_spieler_import.spielernr != '' AND #__sportsmanager_spieler_import.spielernr = #__sportsmanager_spieler.spielernr"
. "\n WHERE session_id = '" . $db->escape($session_id) . "'";
$spieler_import = loadObjectList($db, $query);
// Count how many active players the incoming payload provides per organisation.
// The mass-deactivation below is only safe when the payload is a *full* roster;
// a partial CSV would otherwise silently deactivate every member not listed.
$incoming_per_org = [];
foreach ($rows_to_insert as $row) {
$o = trim($row['organisation']);
if ($o !== "") {
$incoming_per_org[$o] = ($incoming_per_org[$o] ?? 0) + 1;
}
}
// Minimum fraction of the currently-active roster the payload must contain
// before the sweep is allowed to run. Configurable; defaults to 0.5.
$deactivation_min_ratio = (float) (einstellungswert("sync_deactivation_min_ratio") ?? 0.5);
if ($deactivation_min_ratio <= 0 || $deactivation_min_ratio > 1) {
$deactivation_min_ratio = 0.5;
}
$warnings = [];
$deactivated_total = 0;
// Deactivate all memberships for involved organisations temporarily. The
// players present in the payload are reactivated further below; anyone not
// listed stays deactivated (i.e. is treated as having left the organisation).
foreach ($org_map as $orgName => $veranstalterId) {
$aktiv_vorher = (int) loadResult(
$db,
"SELECT COUNT(*) FROM #__sportsmanager_mitglied_von_verein"
. " INNER JOIN #__sportsmanager_verein USING (verein_id)"
. " WHERE veranstalter_id = " . $veranstalterId
. " AND NOT #__sportsmanager_mitglied_von_verein.ausgetreten"
);
$eingehend = $incoming_per_org[$orgName] ?? 0;
// Guard: skip the sweep when the payload looks like a partial roster
// (far fewer players than are currently active). This prevents a partial
// export from wiping an entire organisation's memberships.
if ($aktiv_vorher > 0 && $eingehend < $aktiv_vorher * $deactivation_min_ratio) {
$warnings[] = sprintf(
'Massen-Deaktivierung für "%s" übersprungen: nur %d von %d aktiven Mitgliedern in den Daten (mögliche Teil-Liste).',
$orgName,
$eingehend,
$aktiv_vorher
);
continue;
}
$query = "UPDATE #__sportsmanager_mitglied_von_verein INNER JOIN #__sportsmanager_verein USING (verein_id)"
. "\n SET mitgliedsstatus = 0,"
. "\n #__sportsmanager_mitglied_von_verein.ausgetreten = TRUE"
. "\n WHERE veranstalter_id = " . $veranstalterId;
$db->setQuery($query);
if (!$db->execute()) {
return [
'success' => false,
'message' => 'Fehler beim Deaktivieren der alten Vereinsmitgliedschaften.'
];
}
$deactivated_total += $aktiv_vorher;
}
$spieler_updated = 0;
$spieler_added = 0;
$spielerIdsHinzugefuegt = array();
foreach ($spieler_import as $t) {
$orgName = $t->veranstalterbezeichnung;
$veranstalterId = $org_map[$orgName] ?? -1;
if ($veranstalterId === -1 && !empty($orgName)) {
continue;
}
$spieler_id = $t->spieler_id;
$nachname = $t->nachname;
$vorname = $t->vorname;
$geschlecht = $t->geschlecht;
$lizenznr = $t->lizenznr;
$pseudonym = $t->pseudonym;
$vereinsname = $t->vereinsname;
$vereinssitz = $t->vereinssitz;
$geburtsjahr = $t->geburtsjahr;
$spielernr = $t->spielernr;
$mitgliedsstatus = $t->mitgliedsstatus;
if ($spieler_id === null && !empty($spielernr) && isset($spielerIdsHinzugefuegt[$spielernr])) {
$spieler_id = $spielerIdsHinzugefuegt[$spielernr];
}
if ($spieler_id === null && empty($spielernr)) {
continue;
}
if ($spieler_id === null && empty($vereinsname)) {
continue;
}
if ($spieler_id !== null) {
$query = "UPDATE #__sportsmanager_spieler"
. "\n SET vorname = '" . $db->escape($vorname) . "',"
. "\n nachname = '" . $db->escape($nachname) . "',"
. "\n geschlecht = '" . $db->escape($geschlecht) . "'"
. "\n WHERE spieler_id = " . intval($spieler_id);
$db->setQuery($query);
if (!$db->execute()) {
return [
'success' => false,
'message' => 'Fehler beim Aktualisieren des Spielers ID ' . $spieler_id
];
}
$spieler_updated++;
} else {
$query = "INSERT INTO #__sportsmanager_spieler"
. "\n SET vorname = '" . $db->escape($vorname) . "',"
. "\n nachname = '" . $db->escape($nachname) . "',"
. "\n spielernr = '" . $db->escape($spielernr) . "',"
. "\n lizenznr = '" . $db->escape($lizenznr) . "',"
. "\n geschlecht = '" . $db->escape($geschlecht) . "',"
. "\n geburtsjahr = " . ($geburtsjahr === null ? "NULL" : "'" . $db->escape($geburtsjahr) . "'");
if (!empty($pseudonym)) {
$query .= ",\n pseudonym = '" . $db->escape($pseudonym) . "'";
}
$db->setQuery($query);
if (!$db->execute()) {
return [
'success' => false,
'message' => 'Fehler beim Anlegen des neuen Spielers ' . $vorname . ' ' . $nachname
];
}
$spieler_id = $db->insertid();
$spielerIdsHinzugefuegt[$spielernr] = $spieler_id;
$spieler_added++;
}
if (!empty($vereinsname) && $veranstalterId !== -1) {
$query = "SELECT spieler_id FROM #__sportsmanager_mitglied_von_verein"
. "\n WHERE spieler_id = $spieler_id AND verein_id = "
. " (SELECT verein_id FROM #__sportsmanager_verein WHERE vereinsname = '" . $db->escape($vereinsname) . "' AND veranstalter_id = $veranstalterId LIMIT 1)";
$memb_check = loadObjectList($db, $query);
if (count($memb_check) > 0) {
$query = "UPDATE #__sportsmanager_mitglied_von_verein, #__sportsmanager_verein"
. "\n SET mitgliedsstatus = '$mitgliedsstatus', #__sportsmanager_mitglied_von_verein.ausgetreten = FALSE"
. "\n WHERE spieler_id = $spieler_id AND vereinsname = '" . $db->escape($vereinsname) . "' AND #__sportsmanager_verein.verein_id = #__sportsmanager_mitglied_von_verein.verein_id"
. " AND veranstalter_id = $veranstalterId";
$db->setQuery($query);
$db->execute();
} else {
$query = "SELECT verein_id FROM #__sportsmanager_verein"
. "\n WHERE vereinsname = '" . $db->escape($vereinsname) . "' AND veranstalter_id = $veranstalterId";
$club_rows = loadObjectList($db, $query);
if (count($club_rows) > 0) {
$verein_id = intval($club_rows[0]->verein_id);
} else {
$query = "INSERT INTO #__sportsmanager_verein"
. "\n SET vereinsname = '" . $db->escape($vereinsname) . "',"
. "\n veranstalter_id = $veranstalterId";
if (!empty($vereinssitz)) {
$query .= ",\n vereinssitz = '" . $db->escape($vereinssitz) . "'";
}
$db->setQuery($query);
$db->execute();
$verein_id = $db->insertid();
}
$query = "INSERT INTO #__sportsmanager_mitglied_von_verein"
. "\n SET spieler_id = $spieler_id, verein_id = $verein_id, mitgliedsstatus = '$mitgliedsstatus', ausgetreten = FALSE";
$db->setQuery($query);
$db->execute();
}
}
}
foreach ($org_map as $orgName => $veranstalterId) {
$query = "UPDATE #__sportsmanager_verein"
. "\n SET ausgetreten = TRUE"
. "\n WHERE NOT EXISTS(SELECT * FROM #__sportsmanager_mitglied_von_verein WHERE #__sportsmanager_verein.verein_id = #__sportsmanager_mitglied_von_verein.verein_id AND NOT #__sportsmanager_mitglied_von_verein.ausgetreten) AND NOT ausgetreten AND veranstalter_id = " . $veranstalterId;
$db->setQuery($query);
$db->execute();
$query = "UPDATE #__sportsmanager_verein"
. "\n SET ausgetreten = FALSE"
. "\n WHERE EXISTS(SELECT * FROM #__sportsmanager_mitglied_von_verein WHERE #__sportsmanager_verein.verein_id = #__sportsmanager_mitglied_von_verein.verein_id AND NOT #__sportsmanager_mitglied_von_verein.ausgetreten) AND ausgetreten AND veranstalter_id = " . $veranstalterId;
$db->setQuery($query);
$db->execute();
$query = "SELECT DISTINCT verein_id, #__sportsmanager_spieler_import.vereinsname, #__sportsmanager_spieler_import.vereinssitz"
. "\n FROM #__sportsmanager_spieler_import"
. "\n INNER JOIN #__sportsmanager_verein ON #__sportsmanager_verein.vereinsname = #__sportsmanager_spieler_import.vereinsname"
. "\n WHERE session_id = '" . $db->escape($session_id) . "' AND #__sportsmanager_spieler_import.veranstalterbezeichnung = '" . $db->escape($orgName) . "' AND #__sportsmanager_spieler_import.vereinsname != '' AND #__sportsmanager_spieler_import.vereinssitz != '' AND (ISNULL(#__sportsmanager_verein.vereinssitz) OR #__sportsmanager_verein.vereinssitz != #__sportsmanager_spieler_import.vereinssitz) AND NOT #__sportsmanager_verein.ausgetreten AND veranstalter_id = " . $veranstalterId;
$rows_headquarters = loadObjectList($db, $query);
foreach ($rows_headquarters as $row) {
$query = "UPDATE #__sportsmanager_verein"
. "\n SET vereinssitz = '" . $db->escape($row->vereinssitz) . "'"
. "\n WHERE verein_id = $row->verein_id";
$db->setQuery($query);
$db->execute();
}
}
$query = "DELETE FROM #__sportsmanager_spieler_import WHERE session_id = '" . $db->escape($session_id) . "'";
$db->setQuery($query);
$db->execute();
// Fail loudly on a zero-effect import: if valid rows were parsed but nothing
// was added or updated, the data almost certainly failed to map (e.g. an
// encoding mismatch corrupting organisation names). Reporting success here
// would silently hide data loss.
if (count($rows_to_insert) > 0 && $spieler_added === 0 && $spieler_updated === 0) {
return [
'success' => false,
'message' => 'Import ergab keine Änderungen trotz ' . count($rows_to_insert)
. ' gültiger Zeilen mögliche Encoding- oder Zuordnungsfehler.',
'spieler_count' => count($rows_to_insert),
'spieler_updated' => 0,
'spieler_added' => 0,
'warnings' => $warnings
];
}
aktuellerVereinAktualisieren();
ranglisteAktualisieren();
einstufungAktualisieren();
return [
'success' => true,
'spieler_count' => count($rows_to_insert),
'spieler_updated' => $spieler_updated,
'spieler_added' => $spieler_added,
'deactivated' => $deactivated_total,
'warnings' => $warnings
];
}
/**
* Endpoint task: triggered by GitHub Actions or other schedule.
* Authenticates with local api_push_key, exports player data, pushes to DTFB, and returns JSON.
*/
function apiSyncSpielerTrigger(): void
{
$token = syncGetBearerToken();
$expected_key = einstellungswert("api_push_key");
if (empty($expected_key) || $token !== $expected_key) {
header('HTTP/1.1 401 Unauthorized');
header('Content-Type: application/json; charset=utf-8');
echo json_encode(['success' => false, 'error' => 'Ungültiges Authentifizierungs-Token.']);
exit;
}
$csvData = syncExportSpielerCSV();
if (empty($csvData)) {
header('Content-Type: application/json; charset=utf-8');
echo json_encode(['success' => false, 'error' => 'Keine Spieler zum Synchronisieren gefunden.']);
exit;
}
$res = syncPushToDtfb($csvData);
// Log sync status
syncLogEntry(
'push',
'api',
$res['success'] ? 'success' : 'error',
$res['spieler_count'] ?? 0,
$res['spieler_updated'] ?? 0,
$res['spieler_added'] ?? 0,
$res['message'] ?? '',
''
);
header('Content-Type: application/json; charset=utf-8');
if ($res['success']) {
echo json_encode($res);
} else {
header('HTTP/1.1 500 Internal Server Error');
echo json_encode($res);
}
exit;
}
/**
* Endpoint task: receives CSV data from another Sportsmanager instance, imports it.
* Authenticates with local api_push_key.
*/
function apiSyncSpielerReceive(): void
{
$token = syncGetBearerToken();
$expected_key = einstellungswert("api_push_key");
if (empty($expected_key) || $token !== $expected_key) {
header('HTTP/1.1 401 Unauthorized');
header('Content-Type: application/json; charset=utf-8');
echo json_encode(['success' => false, 'error' => 'Ungültiges Authentifizierungs-Token.']);
exit;
}
$csvData = file_get_contents('php://input');
if (empty($csvData)) {
header('HTTP/1.1 400 Bad Request');
header('Content-Type: application/json; charset=utf-8');
echo json_encode(['success' => false, 'error' => 'Keine Formulardaten empfangen.']);
exit;
}
$res = syncReceiveSpielerImport($csvData);
// Log sync status
syncLogEntry(
'receive',
'api',
$res['success'] ? 'success' : 'error',
$res['spieler_count'] ?? 0,
$res['spieler_updated'] ?? 0,
$res['spieler_added'] ?? 0,
$res['message'] ?? '',
''
);
header('Content-Type: application/json; charset=utf-8');
if ($res['success']) {
echo json_encode($res);
} else {
header('HTTP/1.1 500 Internal Server Error');
echo json_encode($res);
}
exit;
}
@@ -185,17 +185,45 @@ function individualwettbewerbFilter($prefix): string
function kategorieFilter($prefix, $suffix = ""): string
{
global $params;
$kategorien = explode(",", $params->get('kategorien'));
$filter = "";
foreach ($kategorien as $s) {
$kategorie = intval(trim($s));
if ($kategorie == 0)
continue;
if (!empty($filter))
$filter .= ", ";
$filter .= $kategorie;
$result = [];
foreach ($kategorien as $item) {
$item = trim($item);
if ($item === '') continue;
// Prüfen, ob es ein Bereich ist
if (strpos($item, '-') !== false) {
$rangeParts = explode('-', $item);
// genau 2 Teile für einen gültigen Bereich
if (count($rangeParts) !== 2) continue;
$start = intval(trim($rangeParts[0]));
$end = intval(trim($rangeParts[1]));
if ($start <= 0 || $end <= 0 || $start > $end) continue;
for ($i = $start; $i <= $end; $i++) {
$result[$i] = true; // Duplikate vermeiden
}
return empty($filter) ? "" : (" " . $prefix . " (" . $filter . ") " . $suffix);
} else {
$num = intval($item);
if ($num > 0) {
$result[$num] = true;
}
}
}
if (empty($result)) {
return "";
}
$filter = array_keys($result);
sort($filter, SORT_NUMERIC);
return " $prefix (" . implode(", ", $filter) . ") $suffix";
}
function turnierFilter($prefix): string
@@ -24,6 +24,9 @@
<option value="vereine"><![CDATA[COM_SPORTSMANAGER_LAYOUT_GENERAL_CONTENT_OPTION_CLUBS]]></option>
<option value="spielorte"><![CDATA[COM_SPORTSMANAGER_LAYOUT_GENERAL_CONTENT_OPTION_VENUES]]></option>
<option value="termine"><![CDATA[COM_SPORTSMANAGER_LAYOUT_GENERAL_CONTENT_OPTION_APPOINTMENTS]]></option>
<option value="spielverlegungen"><![CDATA[COM_SPORTSMANAGER_LAYOUT_GENERAL_CONTENT_OPTION_MATCH_RESCHEDULING]]></option>
<option value="verbandsorgane"><![CDATA[COM_SPORTSMANAGER_LAYOUT_GENERAL_CONTENT_OPTION_ASSOCIATION_BODIES]]></option>
<option value="hall_of_fame"><![CDATA[COM_SPORTSMANAGER_LAYOUT_GENERAL_CONTENT_OPTION_HALL_OF_FAME]]></option>
</field>
<field name="titel"
type="text"
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -411,6 +411,51 @@ class HTML_sportsmanager_ticker
moreResults(matches, groups, day, page, 0);
});
</script>
<style>
#theme-toggle {
float: right;
margin-top: 15px;
margin-right: 20px;
}
.theme-btn {
cursor: pointer;
margin-left: 10px;
font-size: 18px;
opacity: 0.5;
transition: opacity 0.2s;
}
.theme-btn:hover, .theme-btn.active {
opacity: 1;
}
</style>
<script>
function applyTheme(theme) {
if (theme === 'dark' || (theme === 'auto' && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
document.body.classList.add('dark-mode');
} else {
document.body.classList.remove('dark-mode');
}
if (document.getElementById('theme-' + theme)) {
document.querySelectorAll('.theme-btn').forEach(function(btn) { btn.classList.remove('active'); });
document.getElementById('theme-' + theme).classList.add('active');
}
}
var savedTheme = localStorage.getItem('livescore-theme') || 'auto';
function setTheme(theme) {
localStorage.setItem('livescore-theme', theme);
applyTheme(theme);
}
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', function(e) {
if (localStorage.getItem('livescore-theme') === 'auto' || !localStorage.getItem('livescore-theme')) {
applyTheme('auto');
}
});
document.addEventListener('DOMContentLoaded', function() {
applyTheme(localStorage.getItem('livescore-theme') || 'auto');
});
</script>
</head>
<body onResize="resizee()">
@@ -433,6 +478,11 @@ class HTML_sportsmanager_ticker
<div id="left_page_header">
<h1 id="pagetitle_text">LIVE-TICKER</h1>
</div>
<div id="theme-toggle">
<span id="theme-auto" class="theme-btn" onclick="setTheme('auto')" title="Auto Theme">&#x1F4BB;</span>
<span id="theme-light" class="theme-btn" onclick="setTheme('light')" title="Light Theme">&#x2600;&#xFE0F;</span>
<span id="theme-dark" class="theme-btn" onclick="setTheme('dark')" title="Dark Theme">&#x1F319;</span>
</div>
<a href="<?php echo SportsManagerURL(); ?>" id="homeicon">Home</a>
<div style="clear:both;"></div>
<div id="left_menu">
@@ -1838,6 +1888,99 @@ class HTML_sportsmanager_ticker
height: 39px;
}
}
body.dark-mode {
background: #121212;
}
body.dark-mode #right_page {
background: #1e1e1e;
}
body.dark-mode h1#pagetitle_text {
color: #ffffff;
}
body.dark-mode a#homeicon {
color: #cccccc;
}
body.dark-mode #left_menu ul li a {
color: #ffffff;
}
body.dark-mode #tbl th {
color: #eeeeee;
background-color: #333333;
}
body.dark-mode #tbl tr td {
color: #dddddd;
}
body.dark-mode #detailedresults #tbl tr.odd td {
background-color: #242424;
}
body.dark-mode #detailedresults #tbl tr.even td {
background-color: #2a2a2a;
}
body.dark-mode #tbl tr.tablehead td {
background-color: #333333;
}
body.dark-mode tr.finished.odd {
background-color: #2a2a2a;
}
body.dark-mode tr.finished.even {
background-color: #2e2e2e;
}
body.dark-mode tr.updated {
background-color: #2d2616;
}
body.dark-mode tr.livenow {
background-color: #173824;
}
body.dark-mode tr.upcoming.odd {
background-color: #1a222f;
}
body.dark-mode tr.upcoming.even {
background-color: #1e2a3b;
}
body.dark-mode #tbl tr td.finished_winner {
color: #ffffff;
}
body.dark-mode tr.last_row {
background-color: #333333;
}
body.dark-mode #tbl tr td#last_row {
background-color: #333333;
}
body.dark-mode tr.upcoming.odd #resultat_holder,
body.dark-mode tr.upcoming.even #resultat_holder {
color: #eeeeee;
}
body.dark-mode #sponsorz {
background: #333333;
}
body.dark-mode .grey_button {
background: #333333;
}
body.dark-mode .grey_button a {
color: #eeeeee;
}
body.dark-mode .place_final {
color: #ffffff;
}
body.dark-mode .field_score {
color: #ffffff;
}
body.dark-mode .field_team {
color: #dddddd;
}
body.dark-mode .field_team.winner_bold {
color: #ffffff;
}
body.dark-mode #winner_area_positions span {
color: #dddddd;
}
body.dark-mode #winner_area_positions span#winner {
color: #ffffff;
}
body.dark-mode #winner_area_positions span#winner_name {
color: #ffffff;
}
<?php
}
@@ -39,6 +39,7 @@ COM_SPORTSMANAGER_ORGANISATION="Organisation"
COM_SPORTSMANAGER_TEAM_MEMBERS="Mannschaftsmitglieder"
COM_SPORTSMANAGER_TEAM_MEMBERS2=Vereinsmitglieder
COM_SPORTSMANAGER_MEMBERS="Mitglieder"
COM_SPORTSMANAGER_MEMBER="Mitglied"
COM_SPORTSMANAGER_ACTIVE_MEMBERS="Aktive Mitglieder"
COM_SPORTSMANAGER_TEAMS="Mannschaften"
COM_SPORTSMANAGER_CURRENT_TEAMS="Aktuelle Mannschaften"
@@ -72,7 +73,6 @@ COM_SPORTSMANAGER_CONFIRM_REMOVE_RANKING="Willst du die Rangliste wirklich entfe
COM_SPORTSMANAGER_ADD_INDIVIDUAL_COMPETITION="Individualwettbewerb hinzuf&uuml;gen"
COM_SPORTSMANAGER_INDIVIDUAL_COMPETITION="Individualwettbewerb"
COM_SPORTSMANAGER_INDIVIDUAL_COMPETITIONS="Individualwettbewerbe"
COM_SPORTSMANAGER_ASSOCIATION_BODIES="Verbandsorgane"
COM_SPORTSMANAGER_GAME="Spiel"
COM_SPORTSMANAGER_GAMES="Spiele"
COM_SPORTSMANAGER_GAMES_SHORTCUT="S"
@@ -157,6 +157,7 @@ COM_SPORTSMANAGER_DEFEAT="Niederlage"
COM_SPORTSMANAGER_DEFEATS="Niederlagen"
COM_SPORTSMANAGER_DEFEATS_SHORTCUT="N"
COM_SPORTSMANAGER_GOALS="Tore"
COM_SPORTSMANAGER_GOALS_SHORTCUT="T"
COM_SPORTSMANAGER_SETS="S&auml;tze"
COM_SPORTSMANAGER_POINT="Punkt"
COM_SPORTSMANAGER_POINTS="Punkte"
@@ -169,9 +170,9 @@ COM_SPORTSMANAGER_GAME_POINTS="Spielpunkte"
COM_SPORTSMANAGER_GAME_POINTS_SHORTCUT="SP"
COM_SPORTSMANAGER_SUFFIX_ONE_TEAM=" (eine Mannschaft)"
COM_SPORTSMANAGER_SUFFIX_TEAMS_TOGETHER=" (Mannschaften zusammen)"
COM_SPORTSMANAGER_DIFFERENCE="Differenz"
COM_SPORTSMANAGER_DIFFERENCE_IN_POINTS="Punktedifferenz"
COM_SPORTSMANAGER_POINTS_RATIO="Punkteverh&auml;ltnis"
COM_SPORTSMANAGER_DIFFERENCE="Differenz Tore"
COM_SPORTSMANAGER_DIFFERENCE_IN_POINTS="Differenz Spielpunkte"
COM_SPORTSMANAGER_POINTS_RATIO="Verh&auml;ltnis Spielpunkte"
COM_SPORTSMANAGER_SCHEDULE_DATE="Zeitpunkt"
COM_SPORTSMANAGER_TEAM_HOME="Heim"
COM_SPORTSMANAGER_TEAM_VISITOR="Gast"
@@ -296,10 +297,9 @@ COM_SPORTSMANAGER_REQUEST_MESSAGE_PLURAL="Es m&uuml;ssen mindestens %d Termine v
COM_SPORTSMANAGER_REJECT_SHIFT="Verschiebung ablehnen"
COM_SPORTSMANAGER_TO="bis"
COM_SPORTSMANAGER_PLAYER_STATISTICS="Spielerstatistiken"
COM_SPORTSMANAGER_PERFORMANCE_INDEX_SHORTCUT="LI"
COM_SPORTSMANAGER_WON="gewonnen"
COM_SPORTSMANAGER_LOST="verloren"
COM_SPORTSMANAGER_RATE="Quote"
COM_SPORTSMANAGER_RATE="Siegquote"
COM_SPORTSMANAGER_RATE_SHORTCUT="Q"
COM_SPORTSMANAGER_NO_CLUB="Kein Verein"
COM_SPORTSMANAGER_RATING="Wertung"
@@ -321,6 +321,7 @@ COM_SPORTSMANAGER_VIEW_LEAST_MEMBER_COUNT="Mindest Mitgliederzahl zeigen (Verein
COM_SPORTSMANAGER_SHOW_ORGANISATION="Spalte Verband zeigen (Vereine)"
COM_SPORTSMANAGER_SHOW_MEMBER_COUNT="Spalte Mitglieder Zeigen (Vereine)"
COM_SPORTSMANAGER_SHOW_TOURNAMENT_BRACKET="Turnierbaum anzeigen"
COM_SPORTSMANAGER_USE_DISCIPLINARY_FINE="Ordnungsstrafen verwenden"
COM_SPORTSMANAGER_PLAYER_DETAILS="Spielerdetails"
COM_SPORTSMANAGER_PLAYER_LIST_DETAILS="Spielerliste/-details"
COM_SPORTSMANAGER_PLAYER_EDIT="Spielerdaten durch Organisations-/Vereinsansprechpartner bearbeiten"
@@ -414,7 +415,7 @@ COM_SPORTSMANAGER_EXPORT="Exportieren"
COM_SPORTSMANAGER_INTERNATIONAL_PLAYERS="Spieler (international)"
COM_SPORTSMANAGER_COUNTRY_CODE="Landeskennung"
COM_SPORTSMANAGER_IMPORT="Importieren"
COM_SPORTSMANAGER_IMPORT_MESSAGE="Im Import sind ausschlie&szlig;lich Spielerdaten zum Verein %s enthalten. Soll ausschlie&szlig;lich der Spielerbestand des einen Vereins aktualisiert werden, muss der zugeh&ouml;rige Verein unten ausgew&auml;hlt werden. Beinhaltet der Import den gesamten Spielerbestand einer Organisation, muss die zugeh&ouml;rige Organisation gew&auml;hlt werden."
COM_SPORTSMANAGER_IMPORT_MESSAGE="Im Import sind ausschlie&szlig;lich Spielerdaten zum Verein %s enthalten. Soll ausschlie&szlig;lich der Spielerbestand des einen Vereins aktualisiert werden, muss der zugeh&ouml;rige Verein unten ausgew&auml;hlt werden. Beinhaltet der Import den gesamten Spielerbestand einer Organisation, muss die zugeh&ouml;rige Organisation gew&auml;hlt werden.<br />Bei schon vorhandener Lizenznummer wird die Lizenznummer und das Geburtsjahr nicht &uuml;berschrieben!"
COM_SPORTSMANAGER_CHECK="Pr&uuml;fen"
COM_SPORTSMANAGER_IMPORT_CONFLICTS_MESSAGE="Im Import sind Fehler oder Konflikte enthalten, die im Vorfeld manuell beseitigt werden müssen."
COM_SPORTSMANAGER_IMPORT_DUPLICATE_MESSAGE="Versuch, Spielernr. auf eine bereits für einen anderen Spieler vergebene Spielernr. zu ändern"
@@ -499,6 +500,8 @@ COM_SPORTSMANAGER_WIN_1_POINT="Sieg: 1 Punkt"
COM_SPORTSMANAGER_WIN_2_POINTS="Sieg: 2 Punkte, Unentschieden: 1 Punkt"
COM_SPORTSMANAGER_WIN_3_POINTS="Sieg: 3 Punkte, Unentschieden: 1 Punkt"
COM_SPORTSMANAGER_MEETING_CONCLUDED_AT="Begegnung abgeschlossen bei"
COM_SPORTSMANAGER_GAMES_IN_STATISTIK="Spiele in Spielerstatistik"
COM_SPORTSMANAGER_GAMES_IN_STATISTIK_ALL="Alle Spiele"
COM_SPORTSMANAGER_GENERALLY="Allgemein"
COM_SPORTSMANAGER_TYPE="Typ"
COM_SPORTSMANAGER_ELO_MIN="Elo min."
@@ -515,7 +518,6 @@ COM_SPORTSMANAGER_ADD_POINTS_TABLE="Punktetabelle hinzuf&uuml;gen"
COM_SPORTSMANAGER_ADD_FUNCTION="Funktion hinzuf&uuml;gen"
COM_SPORTSMANAGER_PARTICIPANT="Teilnehmer"
COM_SPORTSMANAGER_FUNCTION="Funktion"
COM_SPORTSMANAGER_MULTIPLIER="Multiplikator"
COM_SPORTSMANAGER_MAXIMUM="maximal"
COM_SPORTSMANAGER_CONTRACTION="K&uuml;rzel"
COM_SPORTSMANAGER_ELIGIBLE_ORGANIZERS="Berechtigte f&uuml;r Veranstalter"
@@ -550,9 +552,22 @@ COM_SPORTSMANAGER_NO_RATING="Keine Wertung"
COM_SPORTSMANAGER_TEAM_COMPETITIONS="Mannschaftswettbewerbe"
COM_SPORTSMANAGER_TABLE_SUMMARY="Tabellenwertung"
COM_SPORTSMANAGER_HEAD_TO_HEAD_RECORD="Direkter Vergleich"
COM_SPORTSMANAGER_HEAD_TO_HEAD_OPT_NOT="deaktiviert"
COM_SPORTSMANAGER_HEAD_TO_HEAD_OPT_POINTS="bei gleicher Punktzahl"
COM_SPORTSMANAGER_HEAD_TO_HEAD_OPT_SETS="bei gleicher Punktzahl, Satzpunkte"
COM_SPORTSMANAGER_HEAD_TO_HEAD_OPT_GOALS="bei gleicher Punktzahl, Satzpunkte, Tore"
COM_SPORTSMANAGER_POINTS_WON_LOST_DIFFERENCE="Spielpunkte gewonnen, Spielpunkte verloren, Punktedifferenz"
COM_SPORTSMANAGER_PERFORMANCE_INDEX="Leistungsindex (SP+ * SP+ * 100) / (SP+ + SP-), Spielpunkte gewonnen, ..."
COM_SPORTSMANAGER_PERFORMANCE_INDEX0="Spielpunkte gewonnen, Spielpunkte verloren, Punktedifferenz"
COM_SPORTSMANAGER_PERFORMANCE_INDEX1="Leistungsindex (SP+ * SP+ * 100) / (SP+ + SP-), Spielpunkte gewonnen, ..."
COM_SPORTSMANAGER_PERFORMANCE_INDEX2="Leistungsindex (S * P+ * 10) / (P+ + P-), Spielpunkte gewonnen, ..."
COM_SPORTSMANAGER_PERFORMANCE_INDEX3="Race Performance Index (Siege*2 + Unentschieden + Tordifferenz)"
COM_SPORTSMANAGER_PERFORMANCE_INDEX4="Effizienzindex (T+ / (T+ + T-))"
COM_SPORTSMANAGER_PERFORMANCE_INDEX5="Punkteschnitt (P+ / Anzahl Sätze)"
COM_SPORTSMANAGER_PERFORMANCE_SHORT1="LI"
COM_SPORTSMANAGER_PERFORMANCE_SHORT2="LI"
COM_SPORTSMANAGER_PERFORMANCE_SHORT3="RPI"
COM_SPORTSMANAGER_PERFORMANCE_SHORT4="EI"
COM_SPORTSMANAGER_PERFORMANCE_SHORT5="AVG"
COM_SPORTSMANAGER_INDIVIDUAL_STATISTICS="Einzelstatistik aus allen Spielen"
COM_SPORTSMANAGER_INDIVIDUAL_STATISTICS_SINGLES="Einzelstatistik aus Einzel-Spielen"
COM_SPORTSMANAGER_INDIVIDUAL_STATISTICS_DOUBLES="Einzelstatistik aus Doppel-Spielen"
@@ -576,7 +591,7 @@ COM_SPORTSMANAGER_PRIVATE_PLAYER_DATA="Private Spielerdaten in Vereins- und Mann
COM_SPORTSMANAGER_ASSOCIATIONS_MEMBERSHIPS_MANAGE="Vereine und Mitgliedschaften verwalten"
COM_SPORTSMANAGER_MANAGE_CLASSIFICATIONS="Einstufungen verwalten"
COM_SPORTSMANAGER_MANAGE_TEAM_PLANS="Mannschaftsspielpläne verwalten"
COM_SPORTSMANAGER_MANAGE_RULES_POSTPONEMENT="Verschieberegeln verwalten"
COM_SPORTSMANAGER_MANAGE_RULES_POSTPONEMENT="Spielverlegungen/Verschieberegeln verwalten"
COM_SPORTSMANAGER_MANAGE_VENUES="Spielorte verwalten"
COM_SPORTSMANAGER_MANAGE_TEAM_COMPETITIONS="Mannschaftswettbewerbe verwalten"
COM_SPORTSMANAGER_MANAGE_PLAYER_STATISTICS="Spielerstastistiken verwalten"
@@ -811,7 +826,6 @@ COM_SPORTSMANAGER_APPLIED_FOR="Beantragt"
COM_SPORTSMANAGER_DECLINED="Abgelehnt"
COM_SPORTSMANAGER_DATE_DETAILS="Termin: Details"
COM_SPORTSMANAGER_ADDITIONS="Zus&auml;tze"
COM_SPORTSMANAGER_ADDITIONAL_INFORMATION="Weitere Informationen"
COM_SPORTSMANAGER_PUBLIC_EMAIL="E-Mail &ouml;ffentlich"
COM_SPORTSMANAGER_EMAIL_WITH_CHANGES="E-Mail bei &Auml;nderungen"
COM_SPORTSMANAGER_ACTION_TYPE="Aktionstyp"
@@ -887,6 +901,9 @@ COM_SPORTSMANAGER_INITIAL_APPOINTMENT_SUGGESTIONS="Initiale Terminvorschl&auml;g
COM_SPORTSMANAGER_REQUESTING_TEAM="Beantragende Mannschaft"
COM_SPORTSMANAGER_OPPONENT_TEAM="Gegnerische Mannschaft"
COM_SPORTSMANAGER_AGAINST_PROPOSALS_ALLOWED="Gegensvorschl&auml;ge zul&auml;ssig"
COM_SPORTSMANAGER_REASON_REQUIRED="Begr&uuml;ndung erforderlich"
COM_SPORTSMANAGER_CLUB_ENTITLEMENT="Berechtigung Vereine"
COM_SPORTSMANAGER_ASSOCIATION_ENTITLEMENT="Berechtigung Verband"
COM_SPORTSMANAGER_LEAD_TIME="Vorlaufzeit"
COM_SPORTSMANAGER_APPOINTMENT_PROPOSALS_MINIMAL="Terminvorschl&auml;ge minimal"
COM_SPORTSMANAGER_APPOINTMENT_PROPOSALS_MAXIMUM="Terminvorschl&auml;ge maximal"
@@ -898,6 +915,9 @@ COM_SPORTSMANAGER_RESULT_ONLY="Nur Ergebnis"
COM_SPORTSMANAGER_GAME_NUMBER="Spiel %d"
COM_SPORTSMANAGER_LABEL_GAME_NUMBER="Spiel Nr"
COM_SPORTSMANAGER_LABEL_GAME_TITLE="Spieltag Titel"
COM_SPORTSMANAGER_EXPLICIT_PENALTIES="Explizite Strafen"
COM_SPORTSMANAGER_EXPLICIT_PENALTIES_EMAIL_SUBJECT="%s: Strafpunkte erhalten"
COM_SPORTSMANAGER_EXPLICIT_PENALTIES_EMAIL_BODY="%s wurden %f Strafpunkte zugeteilt mit der Begründung: %s"
COM_SPORTSMANAGER_EMAIL_SHIFT_GAME_APPOINTMENT_SUBJECT="%s vs %s: Spieltermin verlegen"
COM_SPORTSMANAGER_EMAIL_SHIFT_GAME_APPOINTMENT_BODY="Zur Begegnung %s gegen %s am %s in %s wird von %s der Spieltermin verschoben.\n\nAlternative Termine:\n\n"
COM_SPORTSMANAGER_EMAIL_SHIFT_GAME_APPOINTMENT_REQUESTED_BODY="Zur Begegnung %s gegen %s am %s in %s wird von %s der Spieltermin verschoben.\n\nBitte alternative Termine vorschlagen unter %s"
@@ -1019,3 +1039,80 @@ COM_SPORTSMANAGER_NUM_REQUESTED_SHIFTS="Verschiebungen"
COM_SPORTSMANAGER_NUM_REQUESTED_SHFITS_TOOLTIP="Spielverschiebungen durch diese Mannschaft"
COM_SPORTSMANAGER_GAME_RESULT_DELAYS="Verzögerung"
COM_SPORTSMANAGER_USE_EMAIL_REMINDERS="Verwende Email Erinnerungen"
COM_SPORTSMANAGER_RULEBOOKS="Regelwerke"
COM_SPORTSMANAGER_RULEBOOK="Regelwerk"
COM_SPORTSMANAGER_RULE_SHORT="SPO"
COM_SPORTSMANAGER_FEE_SHORT="GO"
COM_SPORTSMANAGER_RULE_LONG="Paragraph SPO"
COM_SPORTSMANAGER_FEE_LONG="Paragraph GO"
COM_SPORTSMANAGER_SELECT="Auswahl"
COM_SPORTSMANAGER_NO_SELECT="keine Auswahl"
COM_SPORTSMANAGER_REALLY_REMOVE_RULEBOOK="Willst Du dieses Regelwerk wirklich entfernen?"
COM_SPORTSMANAGER_ADD_RULEBOOK="Regelwerk hinzuf&uuml;gen"
COM_SPORTSMANAGER_DISCIPLINARY_FINES="Ordnungsstrafen"
COM_SPORTSMANAGER_DISCIPLINARY_FINE="Ordnungsstrafe"
COM_SPORTSMANAGER_ISSUE_DISCIPLINARY_FINES="Ordnungsstrafe ausstellen"
COM_SPORTSMANAGER_EDIT_DISCIPLINARY_FINE="Ordnungsstrafe bearbeiten"
COM_SPORTSMANAGER_SEND_DISCIPLINARY_FINE="Ordnungsstrafe versenden"
COM_SPORTSMANAGER_REALLY_REMOVE_DISCIPLINARY_FINES="Willst Du diese Ordnungsstrafe wirklich entfernen?"
COM_SPORTSMANAGER_VIOLATIONS="Verst&ouml;&szlig;e"
COM_SPORTSMANAGER_VIOLATION="Versto&szlig;"
COM_SPORTSMANAGER_ADD_VIOLATION="Versto&szlig; hinzuf&uuml;gen"
COM_SPORTSMANAGER_REALLY_REMOVE_VIOLATION="Willst Du diesen Versto&szlig; wirklich entfernen?"
COM_SPORTSMANAGER_VIOLATION_TEXT="Text"
COM_SPORTSMANAGER_VIOLATION_ADD_TEXT="Zusatztext"
COM_SPORTSMANAGER_FEE="Geb&uuml;hr"
COM_SPORTSMANAGER_ADD_FEE="Zusatzgeb&uuml;hr"
COM_SPORTSMANAGER_SELECTABLE="Ausw&auml;hlbar"
COM_SPORTSMANAGER_TEMPLATE="Vorlage"
COM_SPORTSMANAGER_EMAIL_SUBJECT="Betreff"
COM_SPORTSMANAGER_EMAIL_MESSAGE="Nachricht"
COM_SPORTSMANAGER_EMAIL_TO="An"
COM_SPORTSMANAGER_EMAIL_SEND="E-Mail senden"
COM_SPORTSMANAGER_EMAIL_TEXT_TEMPLATE="Textvorlage"
COM_SPORTSMANAGER_ISSUER="Aussteller"
COM_SPORTSMANAGER_ISSUE_DATE="Ausstelldatum"
COM_SPORTSMANAGER_SENDER="Sender"
COM_SPORTSMANAGER_BILL_ISSUER="Rechnung erstellt"
COM_SPORTSMANAGER_MULTIPLIER="Multiplikator"
COM_SPORTSMANAGER_ADDITIONAL_INFORMATION="Weitere Angaben"
COM_SPORTSMANAGER_EMAIL_WAS_SEND="Die E-Mail wurde erfolgreich versendet"
COM_SPORTSMANAGER_EMAIL_WAS_NOT_SEND="Die E-Mail wurde nicht versendet"
COM_SPORTSMANAGER_OLD_DATE="Alter Termin"
COM_SPORTSMANAGER_NEW_DATE="Neuer Termin"
COM_SPORTSMANAGER_REASON_GAME_APPOINTMENT="Verlegungsgrund"
COM_SPORTSMANAGER_MATCH_RESCHEDULINGS="Spielverlegungen"
COM_SPORTSMANAGER_MATCH_RESCHEDULING="Spielverlegung"
COM_SPORTSMANAGER_MATCH_SWAPPING_HELP="Bei Heimrechttausch gleichen Termin eintragen"
COM_SPORTSMANAGER_NOT_VALID_TIME="Ung&uuml;ltige Uhrzeit"
COM_SPORTSMANAGER_REALLY_MATCH_RESCHEDULING="Willst Du diesen Spielverlegung wirklich entfernen?"
COM_SPORTSMANAGER_REST_DAYS="Ruhetage"
COM_SPORTSMANAGER_TRAINING_DAYS="Trainingstage"
COM_SPORTSMANAGER_NOT_ACTUALIZED_DATA="Nicht aktualisierte Daten"
COM_SPORTSMANAGER_ASSOCIATION_BODIES="Verbandsorgane"
COM_SPORTSMANAGER_ASSOCIATION_BODY="Verbandsorgan"
COM_SPORTSMANAGER_ADD_ASSOCIATION_BODY="Verbandsorgan hinzuf&uuml;gen"
COM_SPORTSMANAGER_REALLY_REMOVE_ASSOCIATION_BODY="Willst Du dieses Verbandsorgan wirklich entfernen?"
COM_SPORTSMANAGER_INVALID_ASSOCIATION_BODY_NAME="Ung&uuml;ltiger Name f&uuml;r Verbandsorgan!"
COM_SPORTSMANAGER_NAME_NOT_COMPLETE="Der Name ist nicht komplett ausgef&uuml;llt"
COM_SPORTSMANAGER_ADDITIONAL_INFO="Zusatzinfo"
COM_SPORTSMANAGER_USE_HTML="Hier sollte HTML-formatierter Text verwendet werden"
COM_SPORTSMANAGER_REALLY_REMOVE_ASSOCIATION_BODY_MEMBER="M&ouml;chtest du dieses Mitglied des Verbandsorgans wirklich entfernen?"
COM_SPORTSMANAGER_HELP_EDIT_ASSOCIATION_BODY_MEMBER="Wird ein Name aus der Spielerliste ausgew&auml;hlt, werden Nachname und Vorname &uuml;bernommen.<br>Telefon, Mobil, E-Mail werden aus der Spielerliste &uuml;bernommen, wenn sie hier nicht ausgef&uuml;llt sind."
COM_SPORTSMANAGER_HALL_OF_FAME="Hall of Fame"
COM_SPORTSMANAGER_ADD_HALL_OF_FAME="Hall of Fame hinzuf&uuml;gen"
COM_SPORTSMANAGER_INVALID_HALL_OF_FAME_NAME="Invalider Name f&uuml;r Hall of Fame"
COM_SPORTSMANAGER_REALLY_REMOVE_HALL_OF_FAME="Willst Du wirklich diese Hall of Fame mit allen Mitgliedern l&ouml;schen?"
COM_SPORTSMANAGER_MATCH_TYPE="Spielform"
COM_SPORTSMANAGER_REALLY_REMOVE_HALL_OF_FAME_YEAR="Willst Du wirklich dieses Hall of Fame Jahr l&ouml;schen?"
COM_SPORTSMANAGER_YEARS="Jahre"
COM_SPORTSMANAGER_ADD_HALL_OF_FAME_YEAR="Hall of Fame Jahr hinzuf&uuml;gen"
COM_SPORTSMANAGER_NO_ENTRY="kein Eintrag"
COM_SPORTSMANAGER_REALLY_SWAP_MATCH="Willst Du wirklich das Heimrecht tauschen?"
COM_SPORTSMANAGER_SWAP_MATCH="Heimrechttausch"
COM_SPORTSMANAGER_REALLY_DELETE_MATCH_REPORT="Der Spielbericht wird zusammen mit allen historischen Eintr&auml;gen gel&ouml;scht. Willst du den Spielbericht wirklich l&ouml;schen?"
COM_SPORTSMANAGER_MATCH_REPORT_DELETED="Spielbericht gel&ouml;scht"
COM_SPORTSMANAGER_MATCH_REPORT_WAS_DELETED="Der Spielbericht wurde erfolgreich gel&ouml;scht!"
COM_SPORTSMANAGER_MATCH_REPORT_CORRECTED="Spielberichtskorrektur"
COM_SPORTSMANAGER_MIN_MATCHES="Mindestzahl Spiele"
COM_SPORTSMANAGER_SELECT_ALL="Alle"
@@ -39,6 +39,7 @@ COM_SPORTSMANAGER_ORGANISATION="Organisation"
COM_SPORTSMANAGER_TEAM_MEMBERS="Team members"
COM_SPORTSMANAGER_TEAM_MEMBERS2="Club members"
COM_SPORTSMANAGER_MEMBERS="Members"
COM_SPORTSMANAGER_MEMBER="Member"
COM_SPORTSMANAGER_ACTIVE_MEMBERS="Active members"
COM_SPORTSMANAGER_TEAMS="Teams"
COM_SPORTSMANAGER_CURRENT_TEAMS="Current teams"
@@ -72,7 +73,6 @@ COM_SPORTSMANAGER_CONFIRM_REMOVE_RANKING="Do you really want to remove the ranki
COM_SPORTSMANAGER_ADD_INDIVIDUAL_COMPETITION="Add individual competition"
COM_SPORTSMANAGER_INDIVIDUAL_COMPETITION="Individual competition"
COM_SPORTSMANAGER_INDIVIDUAL_COMPETITIONS="Individual competitions"
COM_SPORTSMANAGER_ASSOCIATION_BODIES="Association bodies"
COM_SPORTSMANAGER_GAME="Game"
COM_SPORTSMANAGER_GAMES="Games"
COM_SPORTSMANAGER_GAMES_SHORTCUT="G"
@@ -157,6 +157,7 @@ COM_SPORTSMANAGER_DEFEAT="Defeat"
COM_SPORTSMANAGER_DEFEATS="Defeats"
COM_SPORTSMANAGER_DEFEATS_SHORTCUT="F"
COM_SPORTSMANAGER_GOALS="Goals"
COM_SPORTSMANAGER_GOALS_SHORTCUT="G"
COM_SPORTSMANAGER_SETS="Sets"
COM_SPORTSMANAGER_POINT="Point"
COM_SPORTSMANAGER_POINTS="Points"
@@ -169,9 +170,9 @@ COM_SPORTSMANAGER_GAME_POINTS="Game points"
COM_SPORTSMANAGER_GAME_POINTS_SHORTCUT="GP"
COM_SPORTSMANAGER_SUFFIX_ONE_TEAM=" (one team)"
COM_SPORTSMANAGER_SUFFIX_TEAMS_TOGETHER=" (teams together)"
COM_SPORTSMANAGER_DIFFERENCE="Difference"
COM_SPORTSMANAGER_DIFFERENCE_IN_POINTS="Difference in points"
COM_SPORTSMANAGER_POINTS_RATIO="Points ratio"
COM_SPORTSMANAGER_DIFFERENCE="Goal difference"
COM_SPORTSMANAGER_DIFFERENCE_IN_POINTS="Difference in game points"
COM_SPORTSMANAGER_POINTS_RATIO="Game points ratio"
COM_SPORTSMANAGER_SCHEDULE_DATE="Appointment date"
COM_SPORTSMANAGER_TEAM_HOME="Home"
COM_SPORTSMANAGER_TEAM_VISITOR="Visitor"
@@ -296,10 +297,9 @@ COM_SPORTSMANAGER_REQUEST_MESSAGE_PLURAL="At least %d dates must be given comple
COM_SPORTSMANAGER_REJECT_SHIFT="Reject shift"
COM_SPORTSMANAGER_TO="until"
COM_SPORTSMANAGER_PLAYER_STATISTICS="Player statistics"
COM_SPORTSMANAGER_PERFORMANCE_INDEX_SHORTCUT="PI"
COM_SPORTSMANAGER_WON="won"
COM_SPORTSMANAGER_LOST="lost"
COM_SPORTSMANAGER_RATE="Rate"
COM_SPORTSMANAGER_RATE="Winning rate"
COM_SPORTSMANAGER_RATE_SHORTCUT="R"
COM_SPORTSMANAGER_NO_CLUB="No club"
COM_SPORTSMANAGER_RATING="Rating"
@@ -321,6 +321,7 @@ COM_SPORTSMANAGER_VIEW_LEAST_MEMBER_COUNT="Show least member count (teams)"
COM_SPORTSMANAGER_SHOW_ORGANISATION="Show organisation (teams)"
COM_SPORTSMANAGER_SHOW_MEMBER_COUNT="Show member column (teams)"
COM_SPORTSMANAGER_SHOW_TOURNAMENT_BRACKET="Show Tournament Bracket"
COM_SPORTSMANAGER_USE_DISCIPLINARY_FINE="Use disciplinary fine"
COM_SPORTSMANAGER_PLAYER_DETAILS="Player details"
COM_SPORTSMANAGER_PLAYER_LIST_DETAILS="Player list details"
COM_SPORTSMANAGER_PLAYER_EDIT="Edit player data by organisation/club contacts"
@@ -414,7 +415,7 @@ COM_SPORTSMANAGER_EXPORT="Export"
COM_SPORTSMANAGER_INTERNATIONAL_PLAYERS="Players (international)"
COM_SPORTSMANAGER_COUNTRY_CODE="Country code"
COM_SPORTSMANAGER_IMPORT="Import"
COM_SPORTSMANAGER_IMPORT_MESSAGE="In the import there are only player information about club %s present. Shall only the members of that one club be updated, the associated club has to be selected down here. If the import contains all members of the organisation then the organisation must be selected."
COM_SPORTSMANAGER_IMPORT_MESSAGE="In the import there are only player information about club %s present. Shall only the members of that one club be updated, the associated club has to be selected down here. If the import contains all members of the organisation then the organisation must be selected.<br />If a license number already exists, the license number and the year of birth will not be overwritten."
COM_SPORTSMANAGER_CHECK="Check"
COM_SPORTSMANAGER_IMPORT_CONFLICTS_MESSAGE="There are faults or conflicts in the import which have to be fixed manually first."
COM_SPORTSMANAGER_IMPORT_DUPLICATE_MESSAGE="Attempt to change player number into one that is already assigned to another player."
@@ -499,6 +500,8 @@ COM_SPORTSMANAGER_WIN_1_POINT="Win: 1 point"
COM_SPORTSMANAGER_WIN_2_POINTS="Win: 2 points, draw: 1 point"
COM_SPORTSMANAGER_WIN_3_POINTS="Win: 3 points, draw: 1 point"
COM_SPORTSMANAGER_MEETING_CONCLUDED_AT="Match won at"
COM_SPORTSMANAGER_GAMES_IN_STATISTIK="Games in player statistics"
COM_SPORTSMANAGER_GAMES_IN_STATISTIK_ALL="All games"
COM_SPORTSMANAGER_GENERALLY="Generally"
COM_SPORTSMANAGER_TYPE="Type"
COM_SPORTSMANAGER_ELO_MIN="Elo min."
@@ -515,7 +518,6 @@ COM_SPORTSMANAGER_ADD_POINTS_TABLE="Add points table"
COM_SPORTSMANAGER_ADD_FUNCTION="Add function"
COM_SPORTSMANAGER_PARTICIPANT="Participants"
COM_SPORTSMANAGER_FUNCTION="Function"
COM_SPORTSMANAGER_MULTIPLIER="Multiplier"
COM_SPORTSMANAGER_MAXIMUM="maximum"
COM_SPORTSMANAGER_CONTRACTION="Contraction"
COM_SPORTSMANAGER_ELIGIBLE_ORGANIZERS="Eligible for organisation"
@@ -550,9 +552,22 @@ COM_SPORTSMANAGER_NO_RATING="No rating"
COM_SPORTSMANAGER_TEAM_COMPETITIONS="Team competitions"
COM_SPORTSMANAGER_TABLE_SUMMARY="Table rating"
COM_SPORTSMANAGER_HEAD_TO_HEAD_RECORD="Head-to-head record"
COM_SPORTSMANAGER_HEAD_TO_HEAD_OPT_NOT="Off"
COM_SPORTSMANAGER_HEAD_TO_HEAD_OPT_POINTS="Tie: pts"
COM_SPORTSMANAGER_HEAD_TO_HEAD_OPT_SETS="Tie: pts, set points"
COM_SPORTSMANAGER_HEAD_TO_HEAD_OPT_GOALS="Tie: pts, set points, goals"
COM_SPORTSMANAGER_POINTS_WON_LOST_DIFFERENCE="Game points won, game points lost, point difference"
COM_SPORTSMANAGER_PERFORMANCE_INDEX="Performance index (GP+ * GP+ * 100) / (GP+ + GP-), game points won, ..."
COM_SPORTSMANAGER_PERFORMANCE_INDEX0="Game points won, game points lost, point difference"
COM_SPORTSMANAGER_PERFORMANCE_INDEX1="Performance index (GP+ * GP+ * 100) / (GP+ + GP-), game points won, ..."
COM_SPORTSMANAGER_PERFORMANCE_INDEX2="Performance index (games * P+ * 10) / (P+ + P-), game points won, ..."
COM_SPORTSMANAGER_PERFORMANCE_INDEX3="Race Performance Index (victories*2 + draws + goal difference)"
COM_SPORTSMANAGER_PERFORMANCE_INDEX4="Efficency index (G+ / (G+ + G-))"
COM_SPORTSMANAGER_PERFORMANCE_INDEX5="Set point average (P+ / count sets)"
COM_SPORTSMANAGER_PERFORMANCE_SHORT1="LI"
COM_SPORTSMANAGER_PERFORMANCE_SHORT2="LI"
COM_SPORTSMANAGER_PERFORMANCE_SHORT3="RPI"
COM_SPORTSMANAGER_PERFORMANCE_SHORT4="EI"
COM_SPORTSMANAGER_PERFORMANCE_SHORT5="AVG"
COM_SPORTSMANAGER_INDIVIDUAL_STATISTICS="Individual statistics of all games"
COM_SPORTSMANAGER_INDIVIDUAL_STATISTICS_SINGLES="Individual statistics of singles games"
COM_SPORTSMANAGER_INDIVIDUAL_STATISTICS_DOUBLES="Individual statistics of doubles games"
@@ -576,7 +591,7 @@ COM_SPORTSMANAGER_PRIVATE_PLAYER_DATA="View privat player information in club an
COM_SPORTSMANAGER_ASSOCIATIONS_MEMBERSHIPS_MANAGE="Manage clubs and memberships"
COM_SPORTSMANAGER_MANAGE_CLASSIFICATIONS="Manage classifications"
COM_SPORTSMANAGER_MANAGE_TEAM_PLANS="Manage team plans"
COM_SPORTSMANAGER_MANAGE_RULES_POSTPONEMENT="Manage postpone rules"
COM_SPORTSMANAGER_MANAGE_RULES_POSTPONEMENT="Manage match rescheduling/postpone rules"
COM_SPORTSMANAGER_MANAGE_VENUES="Manage venues"
COM_SPORTSMANAGER_MANAGE_TEAM_COMPETITIONS="Manage team competitions"
COM_SPORTSMANAGER_MANAGE_PLAYER_STATISTICS="Manage player statitics"
@@ -811,7 +826,6 @@ COM_SPORTSMANAGER_APPLIED_FOR="Applied"
COM_SPORTSMANAGER_DECLINED="Declined"
COM_SPORTSMANAGER_DATE_DETAILS="Event: Details"
COM_SPORTSMANAGER_ADDITIONS="Additions"
COM_SPORTSMANAGER_ADDITIONAL_INFORMATION="Additional information"
COM_SPORTSMANAGER_PUBLIC_EMAIL="E-mail public"
COM_SPORTSMANAGER_EMAIL_WITH_CHANGES="E-mail for changes"
COM_SPORTSMANAGER_ACTION_TYPE="Action type"
@@ -887,6 +901,9 @@ COM_SPORTSMANAGER_INITIAL_APPOINTMENT_SUGGESTIONS="Initial appointment suggestio
COM_SPORTSMANAGER_REQUESTING_TEAM="Requesting team"
COM_SPORTSMANAGER_OPPONENT_TEAM="Opponent team"
COM_SPORTSMANAGER_AGAINST_PROPOSALS_ALLOWED="Against proposals allowed"
COM_SPORTSMANAGER_REASON_REQUIRED="Reason required"
COM_SPORTSMANAGER_CLUB_ENTITLEMENT="Club entitlement"
COM_SPORTSMANAGER_ASSOCIATION_ENTITLEMENT="Association_entitlement"
COM_SPORTSMANAGER_LEAD_TIME="Lead time"
COM_SPORTSMANAGER_APPOINTMENT_PROPOSALS_MINIMAL="Appointment proposals minimal"
COM_SPORTSMANAGER_APPOINTMENT_PROPOSALS_MAXIMUM="Appointment proposals maximum"
@@ -898,6 +915,9 @@ COM_SPORTSMANAGER_RESULT_ONLY="Result only"
COM_SPORTSMANAGER_GAME_NUMBER="Game %d"
COM_SPORTSMANAGER_LABEL_GAME_NUMBER="Game nr"
COM_SPORTSMANAGER_LABEL_GAME_TITLE="Gameday title"
COM_SPORTSMANAGER_EXPLICIT_PENALTIES="Explicit Penalties"
COM_SPORTSMANAGER_EXPLICIT_PENALTIES_EMAIL_SUBJECT="%s: received penalty"
COM_SPORTSMANAGER_EXPLICIT_PENALTIES_EMAIL_BODY="%s received a penalty of %f points based on the following justification: %s"
COM_SPORTSMANAGER_EMAIL_SHIFT_GAME_APPOINTMENT_SUBJECT="%s vs %s: Shift game appointment"
COM_SPORTSMANAGER_EMAIL_SHIFT_GAME_APPOINTMENT_BODY="For match %s versus %s on %s in %s the game appointment is shifted by %s.\n\nAlternative appointments:\n\n"
COM_SPORTSMANAGER_EMAIL_SHIFT_GAME_APPOINTMENT_REQUESTED_BODY="For match %s on %s in %s the game appointment is shifted by %s.\n\nPlease propose alternative appointments under %s"
@@ -950,8 +970,10 @@ COM_SPORTSMANAGER_NON_SMOKING_PROTECTION_MARK_NO=" (Kein Nichtraucherschutz)"
COM_SPORTSMANAGER_POINTS_TABLE="Points table"
COM_SPORTSMANAGER_EVALUATION="Auswertung"
COM_SPORTSMANAGER_FUNCTION_DESCRIPTION="Variables: n = number of participants, p = place, m = multiplier of rating and in doubles possibly additionally reduced rating<br />Functions: +, -, *, /, round(x), pow(x), if(a > b, x, y), min(x, y), max(x, y), log(x), ln(x), logn(b, x)<br />VerteilungR(r, p, n, m) := max(round((((m * r - 1) * (-log(p / n) * (1 - (p / n)))) / (-log(1 / n) * (1 - (1 / n)))) + 1), 1)<br />Verteilung(r, p, n, m) := max(round(m * round((((r - 1) * (-log(p / n) * (1 - (p / n)))) / (-log(1 / n) * (1 - (1 / n)))) + 1)), 1)<br /><br />The functions VerteilungR() and Verteilung() distribute points for place 1 (r) descending to the individual places (p) of the number of participants (n).<br />VerteilungR() applies the multiplier (m) to the points for 1st place and then distributes down to 1 point for the last place.<br />Verteilung() applies the multiplier (m) to the points after the calculation, i.e. the last place receives 1 * m points."
COM_SPORTSMANAGER_LIZENZ="License"
COM_SPORTSMANAGER_RANK="Rank"
; Edit Player
COM_SPORTSMANAGER_LIZENZ="License"
COM_SPORTSMANAGER_ARIA_LABEL_MATCHDAY_SELECT="Choose a match day"
COM_SPORTSMANAGER_ARIA_LABEL_PROPOSAL_DAY="Choose the day of the match proposal"
COM_SPORTSMANAGER_ARIA_LABEL_PROPOSAL_MONTH="Choose the month of the match proposal"
@@ -1017,3 +1039,80 @@ COM_SPORTSMANAGER_NUM_REQUESTED_SHIFTS="Shifts"
COM_SPORTSMANAGER_NUM_REQUESTED_SHFITS_TOOLTIP="Game shifts caused by this team"
COM_SPORTSMANAGER_GAME_RESULT_DELAYS="Delay"
COM_SPORTSMANAGER_USE_EMAIL_REMINDERS="Use email reminders"
COM_SPORTSMANAGER_RULEBOOKS="Rulebooks"
COM_SPORTSMANAGER_RULEBOOK="Rulebook"
COM_SPORTSMANAGER_RULE_SHORT="Rule"
COM_SPORTSMANAGER_FEE_SHORT="Section fees"
COM_SPORTSMANAGER_RULE_LONG="Section rules"
COM_SPORTSMANAGER_FEE_LONG="Fee"
COM_SPORTSMANAGER_SELECT="Select"
COM_SPORTSMANAGER_NO_SELECT="No selection"
COM_SPORTSMANAGER_REALLY_REMOVE_RULEBOOK="Do you really want to remove this rulebook?"
COM_SPORTSMANAGER_ADD_RULEBOOK="Add rulebook"
COM_SPORTSMANAGER_DISCIPLINARY_FINES="Disciplinary fines"
COM_SPORTSMANAGER_ISSUE_DISCIPLINARY_FINES="Issue disciplinary fines"
COM_SPORTSMANAGER_ISSUE_DISCIPLINARY_FINE="Issue disciplinary fine"
COM_SPORTSMANAGER_EDIT_DISCIPLINARY_FINE="Edit issue disciplinary fine"
COM_SPORTSMANAGER_SEND_DISCIPLINARY_FINE="Send issue disciplinary fine"
COM_SPORTSMANAGER_REALLY_REMOVE_DISCIPLINARY_FINES="Do you really want to remove this Issue disciplinary fine?"
COM_SPORTSMANAGER_VIOLATIONS="Violations"
COM_SPORTSMANAGER_VIOLATION="Violation"
COM_SPORTSMANAGER_ADD_VIOLATION="Add violation"
COM_SPORTSMANAGER_REALLY_REMOVE_VIOLATION="Do you really want to remove this violation?"
COM_SPORTSMANAGER_VIOLATION_TEXT="Text"
COM_SPORTSMANAGER_VIOLATION_ADD_TEXT="Additional text"
COM_SPORTSMANAGER_FEE="Fee"
COM_SPORTSMANAGER_ADD_FEE="Additional Fee"
COM_SPORTSMANAGER_SELECTABLE="Selectable"
COM_SPORTSMANAGER_TEMPLATE="Template"
COM_SPORTSMANAGER_EMAIL_SUBJECT="Subject"
COM_SPORTSMANAGER_EMAIL_MESSAGE="Message"
COM_SPORTSMANAGER_EMAIL_TO="to"
COM_SPORTSMANAGER_EMAIL_SEND="Send E-Mail"
COM_SPORTSMANAGER_EMAIL_TEXT_TEMPLATE="Text template"
COM_SPORTSMANAGER_ISSUER="Issuer"
COM_SPORTSMANAGER_SENDER="Sender"
COM_SPORTSMANAGER_ISSUE_DATE="Issue date"
COM_SPORTSMANAGER_BILL_ISSUER="Bill issuer"
COM_SPORTSMANAGER_MULTIPLIER="Multiplier"
COM_SPORTSMANAGER_ADDITIONAL_INFORMATION="Additional information"
COM_SPORTSMANAGER_EMAIL_WAS_SEND="E-Mail was succesfully sent"
COM_SPORTSMANAGER_EMAIL_WAS_NOT_SEND="E-Mail was not sent"
COM_SPORTSMANAGER_OLD_DATE="Old Date"
COM_SPORTSMANAGER_NEW_DATE="New Date"
COM_SPORTSMANAGER_REASON_GAME_APPOINTMENT="Appointment reason"
COM_SPORTSMANAGER_MATCH_RESCHEDULINGS="Match reschedulings"
COM_SPORTSMANAGER_MATCH_RESCHEDULING="Match rescheduling"
COM_SPORTSMANAGER_MATCH_SWAPPING_HELP="If home advantage is swapped, enter the same date"
COM_SPORTSMANAGER_NOT_VALID_TIME="Not valid time"
COM_SPORTSMANAGER_REALLY_MATCH_RESCHEDULING="Do you really want to remove this match rescheduling?"
COM_SPORTSMANAGER_REST_DAYS="Rest days"
COM_SPORTSMANAGER_TRAINING_DAYS="Training days"
COM_SPORTSMANAGER_NOT_ACTUALIZED_DATA="Data not updated"
COM_SPORTSMANAGER_ASSOCIATION_BODIES="Association bodies"
COM_SPORTSMANAGER_ASSOCIATION_BODY="Association body"
COM_SPORTSMANAGER_ADD_ASSOCIATION_BODY="Add association body"
COM_SPORTSMANAGER_REALLY_REMOVE_ASSOCIATION_BODY="Do you really want to remove this association body?"
COM_SPORTSMANAGER_INVALID_ASSOCIATION_BODY_NAME="Invalid association body name"
COM_SPORTSMANAGER_NAME_NOT_COMPLETE="The name is not completely filled in"
COM_SPORTSMANAGER_ADDITIONAL_INFO="Additional information"
COM_SPORTSMANAGER_USE_HTML="HTML-formatted text should be used here."
COM_SPORTSMANAGER_REALLY_REMOVE_ASSOCIATION_BODY_MEMBER="Do you really want to remove this association body member?"
COM_SPORTSMANAGER_HELP_EDIT_ASSOCIATION_BODY_MEMBER="Selecting a name from the player list will fill in the first and last name.<br>Phone, mobile, and email are filled from the player list if left blank here."
COM_SPORTSMANAGER_HALL_OF_FAME="Hall of Fame"
COM_SPORTSMANAGER_ADD_HALL_OF_FAME="Add Hall of Fame"
COM_SPORTSMANAGER_INVALID_HALL_OF_FAME_NAME="Invalid Hall of Fame name"
COM_SPORTSMANAGER_REALLY_REMOVE_HALL_OF_FAME="Are you sure you want to delete this Hall of Fame including all its members?"
COM_SPORTSMANAGER_MATCH_TYPE="Game Type"
COM_SPORTSMANAGER_REALLY_REMOVE_HALL_OF_FAME_YEAR="Are you sure you want to delete this Hall of Fame year?"
COM_SPORTSMANAGER_YEARS="Years"
COM_SPORTSMANAGER_ADD_HALL_OF_FAME_YEAR="Add Hall of Fame Year"
COM_SPORTSMANAGER_NO_ENTRY="no entry"
COM_SPORTSMANAGER_REALLY_SWAP_MATCH="Do you really want to swap the home advantage?"
COM_SPORTSMANAGER_SWAP_MATCH="Swap home advantage"
COM_SPORTSMANAGER_REALLY_DELETE_MATCH_REPORT="The match report and all its history will be deleted. Are you sure you want to delete the match report?"
COM_SPORTSMANAGER_MATCH_REPORT_DELETED="Match report deleted"
COM_SPORTSMANAGER_MATCH_REPORT_WAS_DELETED="The match report has been successfully deleted!"
COM_SPORTSMANAGER_MATCH_REPORT_CORRECTED="Match report corrected"
COM_SPORTSMANAGER_MIN_MATCHES="Min count matches"
COM_SPORTSMANAGER_SELECT_ALL="All"
+226 -1
View File
@@ -526,6 +526,7 @@ return new class () implements InstallerScriptInterface
. "\n `bestenliste_id` int(11) NOT NULL DEFAULT '0',"
. "\n `spieler_id` int(11) NOT NULL DEFAULT '0',"
. "\n `spieler_2_id` int(11) DEFAULT NULL,"
. "\n `team_id` INT(11) DEFAULT NULL,"
. "\n `siege` smallint(6) DEFAULT NULL,"
. "\n `unentschieden` smallint(6) DEFAULT NULL,"
. "\n `niederlagen` smallint(6) DEFAULT NULL,"
@@ -670,6 +671,9 @@ return new class () implements InstallerScriptInterface
. "\n `ortsname` varchar(30) DEFAULT NULL,"
. "\n `ortsteil` varchar(30) DEFAULT NULL,"
. "\n `url` varchar(150) DEFAULT NULL,"
. "\n `telefon` varchar(64) DEFAULT NULL,"
. "\n `email` varchar(64) DEFAULT NULL,"
. "\n `ruhetage` varchar(64) DEFAULT NULL,"
. "\n `beschreibung` varchar(500) DEFAULT NULL,"
. "\n `status` tinyint(1) NOT NULL DEFAULT '0',"
. "\n PRIMARY KEY (`spielort_id`)"
@@ -692,6 +696,7 @@ return new class () implements InstallerScriptInterface
. "\n `heimspiel_wochentag` tinyint(4) DEFAULT NULL,"
. "\n `heimspiel_uhrzeit` smallint(6) DEFAULT NULL,"
. "\n `heimspielort_id` int(11) DEFAULT NULL,"
. "\n `trainingstage` varchar(64) DEFAULT NULL,"
. "\n `nichtraucherschutz` tinyint(4) DEFAULT '0',"
. "\n `platz` smallint(6) DEFAULT NULL,"
. "\n `gesamtpunkte` float(6,2) DEFAULT NULL,"
@@ -718,6 +723,18 @@ return new class () implements InstallerScriptInterface
$db->setQuery( $query );
if (!$db->execute()) { die($db->stderr(true)); }
$query = "CREATE TABLE IF NOT EXISTS `#__sportsmanager_team_strafen` ("
. "\n `team_strafen_id` int(11) NOT NULL AUTO_INCREMENT,"
. "\n `zeitpunkt` datetime NOT NULL,"
. "\n `moderator_user_id` int(11) DEFAULT NULL,"
. "\n `team_id` int(11) NOT NULL DEFAULT '0',"
. "\n `strafe` float(6,2) NOT NULL DEFAULT '0',"
. "\n `beschreibung` text NOT NULL,"
. "\n PRIMARY KEY (`team_strafen_id`)"
. "\n) ENGINE=MyISAM DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;";
$db->setQuery( $query );
if (!$db->execute()) { die($db->stderr(true)); }
$query = "CREATE TABLE IF NOT EXISTS `#__sportsmanager_teamansprechpartner` ("
. "\n `kontaktperson_id` int(11) NOT NULL AUTO_INCREMENT,"
. "\n `team_id` int(11) NOT NULL DEFAULT '0',"
@@ -773,6 +790,7 @@ return new class () implements InstallerScriptInterface
. "\n `spielpunkte_bedingung` tinyint(4) NOT NULL DEFAULT '0',"
. "\n `spielernamen` tinyint(4) NOT NULL DEFAULT '0',"
. "\n `heimtausch` tinyint(4) NOT NULL DEFAULT '0',"
. "\n `spiele_in_spielerstatistik` tinyint(4) NOT NULL DEFAULT '0',"
. "\n `modus` varchar(200) NOT NULL DEFAULT '',"
. "\n `status` tinyint(4) NOT NULL DEFAULT '0',"
. "\n PRIMARY KEY (`teamspiel_modus_id`)"
@@ -1035,6 +1053,7 @@ return new class () implements InstallerScriptInterface
. "\n `saison_id` int(11) NOT NULL DEFAULT '0',"
. "\n `veranstalter_id` int(11) NOT NULL DEFAULT '0',"
. "\n `bezeichnung` varchar(50) DEFAULT NULL,"
. "\n `regelwerke_id` INT(11) NOT NULL DEFAULT '0',"
. "\n `modus_id` int(11) NOT NULL DEFAULT '0',"
. "\n `verschieberegel_id` int(11) NOT NULL DEFAULT '0',"
. "\n `tabellenwertung` tinyint(4) NOT NULL DEFAULT '0',"
@@ -1044,6 +1063,7 @@ return new class () implements InstallerScriptInterface
. "\n `erster_tag` date NOT NULL DEFAULT '0000-00-00',"
. "\n `letzter_tag` date DEFAULT NULL,"
. "\n `elo_wertung` tinyint(4) DEFAULT NULL,"
. "\n `explizite_strafen` tinyint(4) DEFAULT '0',"
. "\n `logo_url` TINYTEXT NOT NULL,"
. "\n `ticker_logo_url` TINYTEXT NOT NULL,"
. "\n `status` tinyint(4) NOT NULL DEFAULT '0',"
@@ -1104,6 +1124,9 @@ return new class () implements InstallerScriptInterface
. "\n `termine_minimal` tinyint(4) NOT NULL DEFAULT '0',"
. "\n `termine_maximal` tinyint(4) NOT NULL DEFAULT '0',"
. "\n `ablehnen` tinyint(1) NOT NULL DEFAULT '0',"
. "\n `begruendung_erforderlich` int(1) NOT NULL DEFAULT 0,"
. "\n `vereine_berechtigt` int(1) NOT NULL DEFAULT 1,"
. "\n `verband_berechtigt` int(1) NOT NULL DEFAULT 0,"
. "\n PRIMARY KEY (`verschieberegel_id`)"
. "\n) ENGINE=MyISAM DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;";
$db->setQuery( $query );
@@ -1118,7 +1141,161 @@ return new class () implements InstallerScriptInterface
$db->setQuery( $query );
if (!$db->execute()) { die($db->stderr(true)); }
$query = "INSERT IGNORE #__sportsmanager_einstellungen SET name = 'datenbank_version', wert = '108';";
$query = "CREATE TABLE IF NOT EXISTS `#__sportsmanager_regelwerke` ("
. "\n `regelwerke_id` int(11) NOT NULL AUTO_INCREMENT,"
. "\n `regelwerk` varchar(32) NOT NULL,"
. "\n PRIMARY KEY (`regelwerke_id`),"
. "\n UNIQUE KEY `regelwerk` (`regelwerk`)"
. "\n ) ENGINE=MyISAM DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;";
$db->setQuery( $query );
if (!$db->execute()) { die($db->stderr(true)); }
$query = "CREATE TABLE IF NOT EXISTS `#__sportsmanager_verstoesse` ("
. "\n `verstoesse_id` int(11) NOT NULL AUTO_INCREMENT,"
. "\n `regelwerke_id` int(11) NOT NULL DEFAULT 0,"
. "\n `paragraph_spo` varchar(32) NOT NULL DEFAULT '',"
. "\n `paragraph_go` varchar(32) NOT NULL DEFAULT '',"
. "\n `verstoss` varchar(64) NOT NULL DEFAULT '',"
. "\n `haupttext` text NOT NULL,"
. "\n `zusatztext` text NOT NULL,"
. "\n `gebuehr` smallint(3) NOT NULL DEFAULT 0,"
. "\n `zusatzgebuehr` smallint(2) NOT NULL DEFAULT 0,"
. "\n `zur_auswahl` tinyint(1) NOT NULL DEFAULT 1,"
. "\n PRIMARY KEY (`verstoesse_id`)"
. "\n ) ENGINE=MyISAM DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;";
$db->setQuery( $query );
if (!$db->execute()) { die($db->stderr(true)); }
$query = "CREATE TABLE IF NOT EXISTS `#__sportsmanager_ordnungsstrafen` ("
. "\n `ordnungsstrafen_id` int(11) NOT NULL AUTO_INCREMENT,"
. "\n `verstoesse_id` int(11) NOT NULL DEFAULT 0,"
. "\n `begegnung_id` int(11) NOT NULL DEFAULT 0,"
. "\n `team_id` int(11) NOT NULL DEFAULT 0,"
. "\n `aussteller_id` int(11) NOT NULL DEFAULT 0,"
. "\n `ausstelldatum` datetime NOT NULL DEFAULT current_timestamp(),"
. "\n `versender_id` int(11) DEFAULT NULL,"
. "\n `versendedatum` datetime DEFAULT NULL,"
. "\n `rechnungssteller_id` int(11) DEFAULT NULL,"
. "\n `rechnungsdatum` datetime DEFAULT NULL,"
. "\n `multiplikator` tinyint(1) NOT NULL DEFAULT 1,"
. "\n `weitere_angaben` text NOT NULL,"
. "\n PRIMARY KEY (`ordnungsstrafen_id`)"
. "\n ) ENGINE=MyISAM DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;";
$db->setQuery( $query );
if (!$db->execute()) { die($db->stderr(true)); }
$query = "CREATE TABLE IF NOT EXISTS `#__sportsmanager_email_vorlagen` ("
. "\n `email_vorlagen_id` int(11) NOT NULL AUTO_INCREMENT,"
. "\n `vorlage` varchar(64) NOT NULL,"
. "\n `betreff` varchar(256) NOT NULL DEFAULT '',"
. "\n `von` varchar(64) NOT NULL DEFAULT '',"
. "\n `an` varchar(256) NOT NULL DEFAULT '',"
. "\n `cc` varchar(256) NOT NULL DEFAULT '',"
. "\n `bcc` varchar(256) NOT NULL DEFAULT '',"
. "\n `email_text` text NOT NULL,"
. "\n PRIMARY KEY (`email_vorlagen_id`),"
. "\n UNIQUE KEY `vorlage` (`vorlage`)"
. "\n ) ENGINE=MyISAM DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;";
$db->setQuery( $query );
if (!$db->execute()) { die($db->stderr(true)); }
$query = "CREATE TABLE IF NOT EXISTS `#__sportsmanager_verbandsorgane` ("
. "\n `verbandsorgane_id` int(11) NOT NULL AUTO_INCREMENT,"
. "\n `veranstalter_id` int(11) DEFAULT NULL,"
. "\n `verbandsorgan` varchar(32) DEFAULT NULL,"
. "\n `kategorie` int(4) DEFAULT NULL,"
. "\n `reihenfolge` int(4) DEFAULT NULL,"
. "\n `email` varchar(64) DEFAULT NULL,"
. "\n `beschreibung` text NOT NULL,"
. "\n PRIMARY KEY (`verbandsorgane_id`),"
. "\n KEY (`veranstalter_id`)"
. "\n ) ENGINE=MyISAM DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;";
$db->setQuery( $query );
if (!$db->execute()) { die($db->stderr(true)); }
$query = "CREATE TABLE IF NOT EXISTS `#__sportsmanager_mitglied_von_verbandsorgan` ("
. "\n `mitglied_von_verbandsorgan_id` int(11) NOT NULL AUTO_INCREMENT,"
. "\n `verbandsorgane_id` int(11) DEFAULT NULL,"
. "\n `funktion` varchar(32) DEFAULT NULL,"
. "\n `zusatzinfo` varchar(64) DEFAULT NULL,"
. "\n `spieler_id` int(11) DEFAULT NULL,"
. "\n `nachname` varchar(32) DEFAULT NULL,"
. "\n `vorname` varchar(32) DEFAULT NULL,"
. "\n `email` varchar(64) DEFAULT NULL,"
. "\n `telefon` varchar(32) DEFAULT NULL,"
. "\n `mobil` varchar(32) DEFAULT NULL,"
. "\n `reihenfolge` int(11) DEFAULT NULL,"
. "\n PRIMARY KEY (`mitglied_von_verbandsorgan_id`),"
. "\n KEY (`verbandsorgane_id`),"
. "\n KEY (`spieler_id`)"
. "\n ) ENGINE=MyISAM DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;";
$db->setQuery( $query );
if (!$db->execute()) { die($db->stderr(true)); }
$query = "CREATE TABLE IF NOT EXISTS `#__sportsmanager_halloffame` ("
. "\n `halloffame_id` int(11) NOT NULL AUTO_INCREMENT,"
. "\n `veranstalter_id` int(11) DEFAULT NULL,"
. "\n `halloffame` varchar(64) DEFAULT NULL,"
. "\n `kategorie` int(4) DEFAULT NULL,"
. "\n `spielform` int(11) DEFAULT NULL,"
. "\n `reihenfolge` int(4) DEFAULT NULL,"
. "\n PRIMARY KEY (`halloffame_id`),"
. "\n KEY `veranstalter_id` (`veranstalter_id`)"
. "\n ) ENGINE=MyISAM DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;";
$db->setQuery($query);
if (!$db->execute()) {
die($db->stderr(true));
}
$query = "CREATE TABLE IF NOT EXISTS `#__sportsmanager_mitglied_von_halloffame` ("
. "\n `mitglied_halloffame_id` int(11) NOT NULL AUTO_INCREMENT,"
. "\n `halloffame_id` int(11) NOT NULL,"
. "\n `jahr` int(4) DEFAULT NULL,"
. "\n `platz` int(11) DEFAULT NULL,"
. "\n `verein_id` int(11) DEFAULT NULL,"
. "\n `teamname` varchar(64) DEFAULT NULL,"
. "\n `spieler1_id` int(11) DEFAULT NULL,"
. "\n `spieler1` varchar(64) DEFAULT NULL,"
. "\n `spieler2_id` int(11) DEFAULT NULL,"
. "\n `spieler2` varchar(64) DEFAULT NULL,"
. "\n PRIMARY KEY (`mitglied_halloffame_id`),"
. "\n UNIQUE KEY `halloffame_jahr_platz` (`halloffame_id`,`jahr`,`platz`),"
. "\n KEY `halloffame_id` (`halloffame_id`),"
. "\n KEY `verein_id` (`verein_id`),"
. "\n KEY `spieler1_id` (`spieler1_id`),"
. "\n KEY `spieler2_id` (`spieler2_id`)"
. "\n ) ENGINE=MyISAM DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;";
$db->setQuery($query);
if (!$db->execute()) {
die($db->stderr(true));
}
$query = "INSERT IGNORE #__sportsmanager_einstellungen SET name = 'verbands_kuerzel', wert = 'tbe.';";
$db->setQuery( $query );
if (!$db->execute()) { die($db->stderr(true)); }
$query = "INSERT IGNORE #__sportsmanager_einstellungen SET name = 'datenbank_version', wert = '121';";
$db->setQuery( $query );
if (!$db->execute()) { die($db->stderr(true)); }
$query = "CREATE TABLE IF NOT EXISTS `#__sportsmanager_sync_log` ("
. "\n `sync_id` INT(11) NOT NULL AUTO_INCREMENT,"
. "\n `sync_timestamp` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,"
. "\n `sync_direction` ENUM('push', 'receive') NOT NULL,"
. "\n `sync_trigger` ENUM('manual', 'cron', 'api') NOT NULL,"
. "\n `sync_status` ENUM('success', 'error') NOT NULL,"
. "\n `spieler_count` INT(11) DEFAULT 0,"
. "\n `spieler_updated` INT(11) DEFAULT 0,"
. "\n `spieler_added` INT(11) DEFAULT 0,"
. "\n `message` TEXT,"
. "\n `details` TEXT,"
. "\n PRIMARY KEY (`sync_id`),"
. "\n INDEX `idx_timestamp` (`sync_timestamp`)"
. "\n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;";
$db->setQuery( $query );
if (!$db->execute()) { die($db->stderr(true)); }
$query = "INSERT IGNORE #__sportsmanager_einstellungen SET name = 'dtfb_sync_url', wert = '';";
$db->setQuery( $query );
if (!$db->execute()) { die($db->stderr(true)); }
@@ -1150,6 +1327,10 @@ return new class () implements InstallerScriptInterface
$db->setQuery( $query );
if (!$db->execute()) { die($db->stderr(true)); }
$query = "INSERT IGNORE #__sportsmanager_einstellungen SET name = 'ordnungsstrafen_verwenden', wert = '0';";
$db->setQuery( $query );
if (!$db->execute()) { die($db->stderr(true)); }
$query = "INSERT IGNORE #__sportsmanager_einstellungen SET name = 'spalte_mitglieder_zeigen', wert = '1';";
$db->setQuery( $query );
if (!$db->execute()) { die($db->stderr(true)); }
@@ -1161,6 +1342,14 @@ return new class () implements InstallerScriptInterface
$query = "INSERT INTO #__sportsmanager_verschieberegel (bezeichnung, initial_ohne_termin, keine_gegenvorschlaege, vorlaufzeit_tage, termine_minimal, termine_maximal, ablehnen)"
. "\n VALUES ('Beliebig', '0', '0', '0', '1', '3', '0');";
$db->setQuery($query);
if (!$db->execute()) { die($db->stderr(true)); }
$query = "INSERT IGNORE INTO `#__sportsmanager_email_vorlagen` (`email_vorlagen_id`, `vorlage`) VALUES (NULL, 'Ordnungsstrafe');";
$db->setQuery($query);
if (!$db->execute()) { die($db->stderr(true)); }
$query = "INSERT IGNORE INTO `#__sportsmanager_email_vorlagen` (`email_vorlagen_id`, `vorlage`) VALUES (NULL, 'Spielverlegung');";
$db->setQuery($query);
if (!$db->execute()) { die($db->stderr(true)); }
$query = "INSERT #__sportsmanager_rangliste_system"
@@ -1595,6 +1784,10 @@ return new class () implements InstallerScriptInterface
$db->setQuery( $query );
if (!$db->execute()) { die($db->stderr(true)); }
$query = "DROP TABLE IF EXISTS `#__sportsmanager_team_strafen`;";
$db->setQuery( $query );
if (!$db->execute()) { die($db->stderr(true)); }
$query = "DROP TABLE IF EXISTS `#__sportsmanager_turnier`;";
$db->setQuery( $query );
if (!$db->execute()) { die($db->stderr(true)); }
@@ -1650,5 +1843,37 @@ return new class () implements InstallerScriptInterface
$query = "DROP TABLE IF EXISTS `#__sportsmanager_verteiler`;";
$db->setQuery( $query );
if (!$db->execute()) { die($db->stderr(true)); }
$query = "DROP TABLE IF EXISTS `#__sportsmanager_regelwerke`;";
$db->setQuery( $query );
if (!$db->execute()) { die($db->stderr(true)); }
$query = "DROP TABLE IF EXISTS `#__sportsmanager_verstoesse`;";
$db->setQuery( $query );
if (!$db->execute()) { die($db->stderr(true)); }
$query = "DROP TABLE IF EXISTS `#__sportsmanager_ordnungsstrafen`;";
$db->setQuery( $query );
if (!$db->execute()) { die($db->stderr(true)); }
$query = "DROP TABLE IF EXISTS `#__sportsmanager_email_vorlagen`;";
$db->setQuery( $query );
if (!$db->execute()) { die($db->stderr(true)); }
$query = "DROP TABLE IF EXISTS `#__sportsmanager_verbandsorgane`;";
$db->setQuery( $query );
if (!$db->execute()) { die($db->stderr(true)); }
$query = "DROP TABLE IF EXISTS `#__sportsmanager_mitglied_von_verbandsorgan`;";
$db->setQuery( $query );
if (!$db->execute()) { die($db->stderr(true)); }
$query = "DROP TABLE IF EXISTS `#__sportsmanager_halloffame`;";
$db->setQuery( $query );
if (!$db->execute()) { die($db->stderr(true)); }
$query = "DROP TABLE IF EXISTS `#__sportsmanager_mitglied_von_halloffame`;";
$db->setQuery( $query );
if (!$db->execute()) { die($db->stderr(true)); }
}
};
+23
View File
@@ -0,0 +1,23 @@
# DTFB Player Sync — QA findings & applied fixes
Concise record of the defects found while reviewing the player-sync receiver
(`syncReceiveSpielerImport()` in `sync.php`) and the six fixes applied in this PR.
The exploratory test harness used to find these has been removed — the
authoritative next test is the **staging end-to-end sync** (see the PR description).
## The six issues fixed
| # | Issue | Impact | Fix in `sync.php` |
|---|-------|--------|-------------------|
| 1 | **Receiver did not normalise input encoding.** A latin1 / Windows-1252 CSV (the legacy manual export) has `ß` as the single byte `0xDF`. Staging the org name into the utf8mb4 table truncated it at that byte, so the org lookup missed and **every row was skipped**. | Critical — silent data loss (e.g. 2964 rows parsed, 0 imported, `success=true`). | Transcode the payload to UTF-8 when it is not already valid UTF-8, before staging. |
| 2 | **A zero-effect import reported success.** When N rows parsed but nothing was added or updated, the function returned `success=true`. | Critical — masks encoding/mapping failures. | Return `success=false` with a diagnostic message when `rows>0` but `added==0 && updated==0`. |
| 3 | **Dead `$naechste_spielernr` block** referenced an undefined variable in the insert branch. | Runtime notice; incoming rows already carry their Passnummer. | Removed the block. |
| 4 | **Unconditional mass-deactivation.** A partial CSV deactivated every member not listed. | High — a broken/partial export could wipe an org's roster. | Per organisation, skip the deactivation sweep when the incoming count is below `sync_deactivation_min_ratio` (default 0.5) of the org's currently-active members; adds/updates still proceed and a warning is returned. |
| 5 | **Split clock for staging.** `session_id` came from PHP `date()` while stale-row cleanup used MySQL `NOW()`; a PHP/MySQL timezone gap could delete in-flight staging rows. | Medium — another silent 0-row path. | Derive `session_id` from the DB clock (`NOW()`), with `date()` fallback. |
| 6 | **No Passnummer format check.** The manual import UI enforces `^[0-9]{2}-[0-9]{4,6}$`; the sync receiver did not. | Medium — inconsistent data quality vs the manual flow. | Apply the same regex to `spielernr` / `spielernr_alt` (blank/reject on mismatch). |
## Confirmed by design (not bugs)
- **No contact / personal data** (email, phone, address) is ever exported or imported.
- Existing players keep their `lizenznr` and `geburtsjahr` on update; only name / sex / Passnummer-driven fields change.
- An unknown organisation aborts the whole import with no mutation.
- The sync path itself (export → cURL push → receive) is UTF-8 end-to-end; the encoding defect (#1) only affected ingesting a legacy latin1 *manual* file.