mirror of
https://github.com/Deutscher-Tischfussballbund/com_sportsmanager.git
synced 2026-09-11 18:41:50 +00:00
Compare commits
47
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
983083d761 | ||
|
|
2a0ccfd98f | ||
|
|
668f917a25 | ||
|
|
ab6b91f2b5 | ||
|
|
7b8fbe6f45 | ||
|
|
cc07d7d53a | ||
|
|
1a111b792f | ||
|
|
fa63e1e2d6 | ||
|
|
111fdeb306 | ||
|
|
49ce079900 | ||
|
|
64588f71ac | ||
|
|
79f3abc01a | ||
|
|
e1a0637795 | ||
|
|
5d1df8579d | ||
|
|
7f1bedc41e | ||
|
|
35384a5c43 | ||
|
|
2c4b3e0908 | ||
|
|
84211e9f9e | ||
|
|
099174c0ef | ||
|
|
554ad25156 | ||
|
|
09ae1c1609 | ||
|
|
5aee02830d | ||
|
|
cb2e614604 | ||
|
|
5d126b0d28 | ||
|
|
69cf0e46ab | ||
|
|
df794966df | ||
|
|
12da0a9c00 | ||
|
|
ca827a049f | ||
|
|
70fb4d97b7 | ||
|
|
d39dc45cec | ||
|
|
c290986bac | ||
|
|
9541bc0652 | ||
|
|
50e6872214 | ||
|
|
a953ebf130 | ||
|
|
e8051bbcf6 | ||
|
|
a01efae2e9 | ||
|
|
04e4ea6cdf | ||
|
|
f787681fec | ||
|
|
541af8fb7f | ||
|
|
cc92b938be | ||
|
|
4c21d66fbe | ||
|
|
afb89dba6e | ||
|
|
c4d599834a | ||
|
|
dcea759ab2 | ||
|
|
19037d0729 | ||
|
|
bfc65d6030 | ||
|
|
7d47b3ed24 |
@@ -0,0 +1,84 @@
|
||||
# Builds the codebase and publishes a rolling dev-preview release (no version bump).
|
||||
# The release uses a fixed tag "dev-preview" so each run replaces the previous one.
|
||||
|
||||
name: Sportsmanager Dev Preview
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
branch:
|
||||
description: 'Branch to build'
|
||||
required: true
|
||||
default: 'sportsmanager2-prod'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ inputs.branch }}
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
|
||||
- name: Install npm dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Run build script
|
||||
run: npm run release
|
||||
|
||||
- name: Delete existing dev-preview release (if any)
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
REPO: ${{ github.repository }}
|
||||
run: |
|
||||
release_id=$(curl -s -H "Authorization: token $GITHUB_TOKEN" \
|
||||
"https://api.github.com/repos/$REPO/releases/tags/dev-preview" \
|
||||
| jq -r '.id // empty')
|
||||
|
||||
if [ -n "$release_id" ]; then
|
||||
curl -s -X DELETE -H "Authorization: token $GITHUB_TOKEN" \
|
||||
"https://api.github.com/repos/$REPO/releases/$release_id"
|
||||
echo "Deleted existing dev-preview release (ID: $release_id)"
|
||||
else
|
||||
echo "No existing dev-preview release found"
|
||||
fi
|
||||
|
||||
- name: Delete existing dev-preview tag (if any)
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
REPO: ${{ github.repository }}
|
||||
run: |
|
||||
status=$(curl -s -o /dev/null -w "%{http_code}" \
|
||||
-H "Authorization: token $GITHUB_TOKEN" \
|
||||
"https://api.github.com/repos/$REPO/git/refs/tags/dev-preview")
|
||||
|
||||
if [ "$status" = "200" ]; then
|
||||
curl -s -X DELETE -H "Authorization: token $GITHUB_TOKEN" \
|
||||
"https://api.github.com/repos/$REPO/git/refs/tags/dev-preview"
|
||||
echo "Deleted existing dev-preview tag"
|
||||
else
|
||||
echo "No existing dev-preview tag found"
|
||||
fi
|
||||
|
||||
- name: Publish dev-preview release
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
tag_name: dev-preview
|
||||
name: "Dev Preview (${{ github.run_number }})"
|
||||
body: |
|
||||
Automated dev preview build from branch `${{ inputs.branch }}`.
|
||||
Commit: ${{ github.sha }}
|
||||
Run: ${{ github.run_number }}
|
||||
|
||||
> This is a temporary preview release and will be replaced on the next run.
|
||||
files: package/packages/com_sportsmanager.zip
|
||||
draft: false
|
||||
prerelease: true
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -1,22 +0,0 @@
|
||||
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"
|
||||
Generated
+19
-5
@@ -52,7 +52,6 @@
|
||||
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.12.0.tgz",
|
||||
"integrity": "sha512-iV7Gwg0DePKvdDZZWRTkj4MW+6/AbVWd4ZCg+zk8H1RVt5xBpUZS6vLQWwb3pyLg4BFTaGiQCPoJ4Ibmbne4fA==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/code-frame": "^7.10.4",
|
||||
"@babel/generator": "^7.12.0",
|
||||
@@ -2695,10 +2694,25 @@
|
||||
}
|
||||
},
|
||||
"node_modules/caniuse-lite": {
|
||||
"version": "1.0.30001148",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001148.tgz",
|
||||
"integrity": "sha512-E66qcd0KMKZHNJQt9hiLZGE3J4zuTqE1OnU53miEVtylFbwOEmeA5OsRu90noZful+XGSQOni1aT2tiqu/9yYw==",
|
||||
"dev": true
|
||||
"version": "1.0.30001810",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001810.tgz",
|
||||
"integrity": "sha512-TITQPUkaz+aVk5GL6NhOdwk1aEaNTSDPsGFWrTuhKGtjTF70jL/Oht2W4c6rXUe5fu7Ie19VIahAXHIIiWWNeg==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/browserslist"
|
||||
},
|
||||
{
|
||||
"type": "tidelift",
|
||||
"url": "https://tidelift.com/funding/github/npm/caniuse-lite"
|
||||
},
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ai"
|
||||
}
|
||||
],
|
||||
"license": "CC-BY-4.0"
|
||||
},
|
||||
"node_modules/chalk": {
|
||||
"version": "1.1.3",
|
||||
|
||||
@@ -30,6 +30,8 @@ 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 von Kategorienummern durch Kommata oder Spiegelstrich getrennt"
|
||||
COM_SPORTSMANAGER_LAYOUT_GENERAL_CONTENT_OPTION_EXTRA_PARAMS="Zusätzliche Parameter"
|
||||
COM_SPORTSMANAGER_LAYOUT_GENERAL_CONTENT_OPTION_EXTRA_PARAMS_DESC="Optionale zusätzliche Parameter für den Link in der Form Name1=Wert1&Name2=Wert2, die bei Aufruf dieses Menüpunkts wie GET-Parameter zur Verfügung stehen"
|
||||
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"
|
||||
|
||||
@@ -30,6 +30,8 @@ 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 or bullet point"
|
||||
COM_SPORTSMANAGER_LAYOUT_GENERAL_CONTENT_OPTION_EXTRA_PARAMS="Additional parameters"
|
||||
COM_SPORTSMANAGER_LAYOUT_GENERAL_CONTENT_OPTION_EXTRA_PARAMS_DESC="Optional additional parameters for the link in the form name1=value1&name2=value2, made available as GET parameters whenever this menu item is called"
|
||||
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
@@ -5709,6 +5709,7 @@ function updateDatabase(): void
|
||||
}
|
||||
|
||||
if ($datenbank_version < 120) {
|
||||
|
||||
$columns = $db->getTableColumns('#__sportsmanager_teamspiel_modus');
|
||||
if (!array_key_exists('spiele_in_spielerstatistik', $columns)){
|
||||
$query = "ALTER TABLE `#__sportsmanager_teamspiel_modus`"
|
||||
@@ -5741,33 +5742,180 @@ function updateDatabase(): void
|
||||
}
|
||||
|
||||
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;";
|
||||
|
||||
$columns = $db->getTableColumns('#__sportsmanager_mitglied_von_halloffame');
|
||||
if (!array_key_exists('teamspieler', $columns)){
|
||||
$query = "ALTER TABLE `#__sportsmanager_mitglied_von_halloffame`"
|
||||
. "\n ADD `teamspieler` TEXT NULL DEFAULT NULL AFTER `teamname`;";
|
||||
$db->setQuery($query);
|
||||
if (!$db->execute()) {
|
||||
die($db->stderr(true));
|
||||
}
|
||||
}
|
||||
|
||||
if (!array_key_exists('nicht_ausgespielt', $columns)){
|
||||
$query = "ALTER TABLE `#__sportsmanager_mitglied_von_halloffame`"
|
||||
. "\n ADD `nicht_ausgespielt` TINYINT(1) NOT NULL DEFAULT '0' AFTER `jahr`;";
|
||||
$db->setQuery($query);
|
||||
if (!$db->execute()) {
|
||||
die($db->stderr(true));
|
||||
}
|
||||
}
|
||||
|
||||
$columns = $db->getTableColumns('#__sportsmanager_spielort');
|
||||
if (!array_key_exists('zusatzinfo', $columns)){
|
||||
$query = "ALTER TABLE `#__sportsmanager_spielort`"
|
||||
. "\n ADD `zusatzinfo` TEXT NULL DEFAULT NULL AFTER `ruhetage`;";
|
||||
$db->setQuery($query);
|
||||
if (!$db->execute()) {
|
||||
die($db->stderr(true));
|
||||
}
|
||||
}
|
||||
|
||||
$zielpfad = JPATH_ROOT . DIRECTORY_SEPARATOR . 'images' . DIRECTORY_SEPARATOR . 'sportsmanager' . DIRECTORY_SEPARATOR . 'spieler' . DIRECTORY_SEPARATOR . 'n.png';
|
||||
if (!is_file($zielpfad)) {
|
||||
$quellpfad = JPATH_SITE . DIRECTORY_SEPARATOR . 'components' . DIRECTORY_SEPARATOR . 'com_sportsmanager' . DIRECTORY_SEPARATOR . 'images' . DIRECTORY_SEPARATOR . 'spieler-n.png';
|
||||
bildKopierenAngepasst($quellpfad, $zielpfad, 180, 240, 1);
|
||||
}
|
||||
|
||||
$query = "UPDATE #__sportsmanager_einstellungen"
|
||||
. "\n SET wert = '121'"
|
||||
. "\n WHERE name = 'datenbank_version'";
|
||||
$db->setQuery($query);
|
||||
if (!$db->execute()) {
|
||||
die($db->stderr(true));
|
||||
}
|
||||
}
|
||||
|
||||
$query = "INSERT IGNORE #__sportsmanager_einstellungen SET name = 'dtfb_sync_url', wert = '';";
|
||||
if ($datenbank_version < 122) {
|
||||
|
||||
$columns = $db->getTableColumns('#__sportsmanager_teamspiel_modus');
|
||||
if (!array_key_exists('satzergebnisse', $columns)) {
|
||||
$query = "ALTER TABLE `#__sportsmanager_teamspiel_modus`"
|
||||
. "\n ADD `satzergebnisse` TINYINT(4) NOT NULL DEFAULT '0' AFTER `punktetyp`,"
|
||||
. "\n ADD `punkte_vorsprung_einzel` TINYINT(4) NOT NULL DEFAULT '1' AFTER `punkte_sieg_doppel`,"
|
||||
. "\n ADD `punkte_vorsprung_doppel` TINYINT(4) NOT NULL DEFAULT '1' AFTER `punkte_vorsprung_einzel`,"
|
||||
. "\n ADD `punkte_deckel_einzel` TINYINT(4) NOT NULL DEFAULT '0' AFTER `punkte_vorsprung_doppel`,"
|
||||
. "\n ADD `punkte_deckel_doppel` TINYINT(4) NOT NULL DEFAULT '0' AFTER `punkte_deckel_einzel`,"
|
||||
. "\n ADD `satz_sieg_einzel` TINYINT(4) NOT NULL DEFAULT '0' AFTER `punkte_deckel_doppel`,"
|
||||
. "\n ADD `satz_sieg_doppel` TINYINT(4) NOT NULL DEFAULT '0' AFTER `satz_sieg_einzel`,"
|
||||
. "\n ADD `satz_vorsprung_einzel` TINYINT(4) NOT NULL DEFAULT '1' AFTER `satz_sieg_doppel`,"
|
||||
. "\n ADD `satz_vorsprung_doppel` TINYINT(4) NOT NULL DEFAULT '1' AFTER `satz_vorsprung_einzel`,"
|
||||
. "\n ADD `satz_deckel_einzel` TINYINT(4) NOT NULL DEFAULT '0' AFTER `satz_vorsprung_doppel`,"
|
||||
. "\n ADD `satz_deckel_doppel` TINYINT(4) NOT NULL DEFAULT '0' AFTER `satz_deckel_einzel`,"
|
||||
. "\n ADD `saetze_modus_einzel` TINYINT(4) NOT NULL DEFAULT '0' AFTER `satz_deckel_doppel`,"
|
||||
. "\n ADD `saetze_modus_doppel` TINYINT(4) NOT NULL DEFAULT '0' AFTER `saetze_modus_einzel`,"
|
||||
. "\n ADD `entscheidung_sieg_einzel` TINYINT(4) NOT NULL DEFAULT '0' AFTER `saetze_modus_doppel`,"
|
||||
. "\n ADD `entscheidung_sieg_doppel` TINYINT(4) NOT NULL DEFAULT '0' AFTER `entscheidung_sieg_einzel`,"
|
||||
. "\n ADD `entscheidung_vorsprung_einzel` TINYINT(4) NOT NULL DEFAULT '1' AFTER `entscheidung_sieg_doppel`,"
|
||||
. "\n ADD `entscheidung_vorsprung_doppel` TINYINT(4) NOT NULL DEFAULT '1' AFTER `entscheidung_vorsprung_einzel`,"
|
||||
. "\n ADD `entscheidung_deckel_einzel` TINYINT(4) NOT NULL DEFAULT '0' AFTER `entscheidung_vorsprung_doppel`,"
|
||||
. "\n ADD `entscheidung_deckel_doppel` TINYINT(4) NOT NULL DEFAULT '0' AFTER `entscheidung_deckel_einzel`,"
|
||||
. "\n ADD `zaehlweise` TINYINT(4) NOT NULL DEFAULT '0' AFTER `entscheidung_deckel_doppel`,"
|
||||
. "\n ADD `race_schritt` TINYINT(4) NOT NULL DEFAULT '0' AFTER `zaehlweise`,"
|
||||
. "\n ADD `race_vorsprung` TINYINT(4) NOT NULL DEFAULT '1' AFTER `race_schritt`,"
|
||||
. "\n ADD `race_deckel` TINYINT(4) NOT NULL DEFAULT '0' AFTER `race_vorsprung`,"
|
||||
. "\n ADD `race_remis` TINYINT(4) NOT NULL DEFAULT '0' AFTER `race_deckel`;";
|
||||
$db->setQuery($query);
|
||||
if (!$db->execute()) {
|
||||
die($db->stderr(true));
|
||||
}
|
||||
}
|
||||
|
||||
$query = "UPDATE #__sportsmanager_teamspiel_modus"
|
||||
. "\n SET saetze_modus_einzel = CASE"
|
||||
. "\n WHEN punktetyp = 0 THEN 0"
|
||||
. "\n WHEN punkte_sieg_einzel = 1 THEN 1"
|
||||
. "\n WHEN punkte_sieg_einzel >= 2 THEN -punkte_sieg_einzel"
|
||||
. "\n WHEN punkte_sieg_einzel <= -2 THEN 2 * (-punkte_sieg_einzel - 1)"
|
||||
. "\n ELSE 0 END,"
|
||||
. "\n saetze_modus_doppel = CASE"
|
||||
. "\n WHEN punktetyp = 0 THEN 0"
|
||||
. "\n WHEN punkte_sieg_doppel = 1 THEN 1"
|
||||
. "\n WHEN punkte_sieg_doppel >= 2 THEN -punkte_sieg_doppel"
|
||||
. "\n WHEN punkte_sieg_doppel <= -2 THEN 2 * (-punkte_sieg_doppel - 1)"
|
||||
. "\n ELSE 0 END"
|
||||
. "\n WHERE saetze_modus_einzel = 0 AND saetze_modus_doppel = 0";
|
||||
$db->setQuery($query);
|
||||
if (!$db->execute()) {
|
||||
die($db->stderr(true));
|
||||
}
|
||||
|
||||
$query = "UPDATE #__sportsmanager_einstellungen"
|
||||
. "\n SET wert = '121'"
|
||||
. "\n SET wert = '122'"
|
||||
. "\n WHERE name = 'datenbank_version'";
|
||||
$db->setQuery($query);
|
||||
if (!$db->execute()) {
|
||||
die($db->stderr(true));
|
||||
}
|
||||
}
|
||||
|
||||
if ($datenbank_version < 123) {
|
||||
|
||||
$columns = $db->getTableColumns('#__sportsmanager_teamspiel');
|
||||
if (!array_key_exists('teamspiel_heim_tore', $columns)) {
|
||||
$query = "ALTER TABLE `#__sportsmanager_teamspiel`"
|
||||
. "\n ADD `teamspiel_heim_tore` SMALLINT(6) NULL DEFAULT NULL AFTER `ergebnis_detailliert`,"
|
||||
. "\n ADD `teamspiel_gast_tore` SMALLINT(6) NULL DEFAULT NULL AFTER `teamspiel_heim_tore`;";
|
||||
$db->setQuery($query);
|
||||
if (!$db->execute()) {
|
||||
die($db->stderr(true));
|
||||
}
|
||||
}
|
||||
|
||||
if (!array_key_exists('teamspiel_saetze_remis', $columns)) {
|
||||
$query = "ALTER TABLE `#__sportsmanager_teamspiel`"
|
||||
. "\n ADD `teamspiel_saetze_remis` SMALLINT(6) NULL DEFAULT NULL AFTER `teamspiel_gast_tore`;";
|
||||
$db->setQuery($query);
|
||||
if (!$db->execute()) {
|
||||
die($db->stderr(true));
|
||||
}
|
||||
}
|
||||
|
||||
$columns = $db->getTableColumns('#__sportsmanager_begegnung');
|
||||
if (!array_key_exists('heim_tore', $columns)) {
|
||||
$query = "ALTER TABLE `#__sportsmanager_begegnung`"
|
||||
. "\n ADD `heim_tore` SMALLINT(6) NULL DEFAULT NULL AFTER `gast_spielpunkte`,"
|
||||
. "\n ADD `gast_tore` SMALLINT(6) NULL DEFAULT NULL AFTER `heim_tore`;";
|
||||
$db->setQuery($query);
|
||||
if (!$db->execute()) {
|
||||
die($db->stderr(true));
|
||||
}
|
||||
}
|
||||
|
||||
if (!array_key_exists('saetze_remis', $columns)) {
|
||||
$query = "ALTER TABLE `#__sportsmanager_begegnung`"
|
||||
. "\n ADD `saetze_remis` SMALLINT(6) NULL DEFAULT NULL AFTER `gast_tore`;";
|
||||
$db->setQuery($query);
|
||||
if (!$db->execute()) {
|
||||
die($db->stderr(true));
|
||||
}
|
||||
}
|
||||
|
||||
$columns = $db->getTableColumns('#__sportsmanager_team');
|
||||
if (!array_key_exists('tore_gewonnen', $columns)) {
|
||||
$query = "ALTER TABLE `#__sportsmanager_team`"
|
||||
. "\n ADD `tore_gewonnen` SMALLINT(6) NULL DEFAULT NULL AFTER `punkte_quotient`,"
|
||||
. "\n ADD `tore_verloren` SMALLINT(6) NULL DEFAULT NULL AFTER `tore_gewonnen`,"
|
||||
. "\n ADD `tore_differenz` SMALLINT(6) NULL DEFAULT NULL AFTER `tore_verloren`,"
|
||||
. "\n ADD `tore_quotient` FLOAT(8,2) NULL DEFAULT NULL AFTER `tore_differenz`;";
|
||||
$db->setQuery($query);
|
||||
if (!$db->execute()) {
|
||||
die($db->stderr(true));
|
||||
}
|
||||
}
|
||||
|
||||
if (!array_key_exists('saetze_remis', $columns)) {
|
||||
$query = "ALTER TABLE `#__sportsmanager_team`"
|
||||
. "\n ADD `saetze_remis` SMALLINT(6) NULL DEFAULT NULL AFTER `tore_quotient`;";
|
||||
$db->setQuery($query);
|
||||
if (!$db->execute()) {
|
||||
die($db->stderr(true));
|
||||
}
|
||||
}
|
||||
|
||||
$query = "UPDATE #__sportsmanager_einstellungen"
|
||||
. "\n SET wert = '123'"
|
||||
. "\n WHERE name = 'datenbank_version'";
|
||||
$db->setQuery($query);
|
||||
if (!$db->execute()) {
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 1.2 KiB |
File diff suppressed because it is too large
Load Diff
@@ -1,899 +0,0 @@
|
||||
<?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;
|
||||
}
|
||||
@@ -19,11 +19,17 @@ require_once JPATH_SITE . '/components/com_sportsmanager/database/init.php';
|
||||
|
||||
/** @noinspection PhpUnused */
|
||||
function mathParserVerteilung($rohpunkte, $platz, $teilnehmer, $multiplikator) {
|
||||
if ($teilnehmer <= 1){
|
||||
return 0;
|
||||
}
|
||||
return max(round($multiplikator * round(((($rohpunkte - 1) * (-log($platz / $teilnehmer) * (1 - ($platz / $teilnehmer)))) / (-log(1 / $teilnehmer) * (1 - (1 / $teilnehmer)))) + 1)), 1);
|
||||
}
|
||||
|
||||
/** @noinspection PhpUnused */
|
||||
function mathParserVerteilungR($rohpunkte, $platz, $teilnehmer, $multiplikator) {
|
||||
if ($teilnehmer <= 1){
|
||||
return 0;
|
||||
}
|
||||
return max(round(((($multiplikator * $rohpunkte - 1) * (-log($platz / $teilnehmer) * (1 - ($platz / $teilnehmer)))) / (-log(1 / $teilnehmer) * (1 - (1 / $teilnehmer)))) + 1), 1);
|
||||
}
|
||||
|
||||
@@ -226,6 +232,217 @@ function kategorieFilter($prefix, $suffix = ""): string
|
||||
return " $prefix (" . implode(", ", $filter) . ") $suffix";
|
||||
}
|
||||
|
||||
function idListeParameterFilter(string $parameterName, string $prefix, string $suffix = ""): string
|
||||
{
|
||||
$jInput = Factory::getContainer()->get(SiteApplication::class)->input;
|
||||
$werte = $jInput->get($parameterName, '', 'STRING');
|
||||
|
||||
$result = [];
|
||||
foreach (explode(",", $werte) as $item) {
|
||||
$item = trim($item);
|
||||
if ($item === '') continue;
|
||||
|
||||
$num = intval($item);
|
||||
if ($num > 0) {
|
||||
$result[$num] = true; // Duplikate vermeiden
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($result)) {
|
||||
return "";
|
||||
}
|
||||
|
||||
$filter = array_keys($result);
|
||||
sort($filter, SORT_NUMERIC);
|
||||
|
||||
return " $prefix (" . implode(", ", $filter) . ") $suffix";
|
||||
}
|
||||
|
||||
function halloffameIdFilter($prefix): string
|
||||
{
|
||||
// Keine Bereichssuche bei der Halloffame-Id, nur einzelne Werte
|
||||
return idListeParameterFilter('halloffame_id', $prefix);
|
||||
}
|
||||
|
||||
function halloffameJahrFilter($prefix): string
|
||||
{
|
||||
$minJahr = 1900;
|
||||
$maxJahr = (int) date('Y') + 5;
|
||||
|
||||
$jInput = Factory::getContainer()->get(SiteApplication::class)->input;
|
||||
$werte = $jInput->get('jahr', '', 'STRING');
|
||||
|
||||
$result = [];
|
||||
foreach (explode(",", $werte) 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) continue;
|
||||
|
||||
// Werte vertauschen, falls der 2. Wert kleiner als der 1. ist
|
||||
if ($end < $start) {
|
||||
[$start, $end] = [$end, $start];
|
||||
}
|
||||
|
||||
// Bereich auf 1900 .. aktuelles Jahr+5 begrenzen
|
||||
$start = max($start, $minJahr);
|
||||
$end = min($end, $maxJahr);
|
||||
|
||||
if ($start > $end) continue;
|
||||
|
||||
for ($i = $start; $i <= $end; $i++) {
|
||||
$result[$i] = true; // Duplikate vermeiden
|
||||
}
|
||||
} else {
|
||||
$num = intval($item);
|
||||
if ($num <= 0) continue;
|
||||
|
||||
// Einzelwert auf 1900 .. aktuelles Jahr+5 begrenzen
|
||||
$num = max($minJahr, min($maxJahr, $num));
|
||||
$result[$num] = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($result)) {
|
||||
return "";
|
||||
}
|
||||
|
||||
$filter = array_keys($result);
|
||||
sort($filter, SORT_NUMERIC);
|
||||
|
||||
return " $prefix (" . implode(", ", $filter) . ")";
|
||||
}
|
||||
|
||||
function halloffameAlleTeams($db): array
|
||||
{
|
||||
return loadObjectList($db, "SELECT team_id, teamname FROM #__sportsmanager_team ORDER BY team_id DESC");
|
||||
}
|
||||
|
||||
function halloffameTeamIdFuerTeamname(array $alleTeams, ?string $teamname)
|
||||
{
|
||||
if (empty($teamname))
|
||||
return null;
|
||||
foreach ($alleTeams as $team) {
|
||||
if (str_starts_with($team->teamname, $teamname))
|
||||
return $team->team_id;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function halloffameAlleSpielerInfo($db): array
|
||||
{
|
||||
// "Aktiv" = hat einen aktuellen Verein; ohne aktuellen Verein liefert spieler_details ohnehin keine Daten
|
||||
$rows = loadObjectList($db, "SELECT spieler_id, geschlecht, aktueller_verein_id FROM #__sportsmanager_spieler");
|
||||
$result = [];
|
||||
foreach ($rows as $row) {
|
||||
$result[$row->spieler_id] = (object) [
|
||||
'geschlecht' => $row->geschlecht,
|
||||
'aktiv' => !empty($row->aktueller_verein_id),
|
||||
];
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
function halloffameSpielerAktiv(array $alleSpielerInfo, $spieler_id): bool
|
||||
{
|
||||
return empty($spieler_id) || !isset($alleSpielerInfo[$spieler_id]) || $alleSpielerInfo[$spieler_id]->aktiv;
|
||||
}
|
||||
|
||||
function halloffameSpielerBildId(array $alleSpielerInfo, $spieler_id)
|
||||
{
|
||||
return halloffameSpielerAktiv($alleSpielerInfo, $spieler_id) ? $spieler_id : '';
|
||||
}
|
||||
|
||||
function halloffameSpielerBildAlternativ(array $alleSpielerInfo, $spieler_id, string $geschaetztesGeschlecht = ''): string
|
||||
{
|
||||
if (!empty($spieler_id) && isset($alleSpielerInfo[$spieler_id]))
|
||||
return $alleSpielerInfo[$spieler_id]->geschlecht == 'M' ? 'm' : 'w';
|
||||
if ($geschaetztesGeschlecht === 'M')
|
||||
return 'm';
|
||||
if ($geschaetztesGeschlecht === 'W')
|
||||
return 'w';
|
||||
return 'n';
|
||||
}
|
||||
|
||||
function halloffameGeschlechtZaehlen(object $ziel, array $alleSpielerInfo, $spieler1_id, $spieler2_id): void
|
||||
{
|
||||
foreach ([$spieler1_id, $spieler2_id] as $id) {
|
||||
if (empty($id) || !isset($alleSpielerInfo[$id]))
|
||||
continue;
|
||||
if ($alleSpielerInfo[$id]->geschlecht == 'M')
|
||||
$ziel->anzahl_m++;
|
||||
else
|
||||
$ziel->anzahl_w++;
|
||||
}
|
||||
}
|
||||
|
||||
function halloffameGeschaetztesGeschlecht($anzahl_m, $anzahl_w): string
|
||||
{
|
||||
if ($anzahl_m > $anzahl_w)
|
||||
return 'M';
|
||||
if ($anzahl_w > $anzahl_m)
|
||||
return 'W';
|
||||
return '';
|
||||
}
|
||||
|
||||
function halloffameAlleVereineInfo($db): array
|
||||
{
|
||||
$rows = loadObjectList($db, "SELECT verein_id, ausgetreten FROM #__sportsmanager_verein");
|
||||
$result = [];
|
||||
foreach ($rows as $row) {
|
||||
$result[$row->verein_id] = (object) [
|
||||
'aktiv' => empty($row->ausgetreten),
|
||||
];
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
function halloffameVereinAktiv(array $alleVereineInfo, $verein_id): bool
|
||||
{
|
||||
return empty($verein_id) || !isset($alleVereineInfo[$verein_id]) || $alleVereineInfo[$verein_id]->aktiv;
|
||||
}
|
||||
|
||||
function halloffameNameLink(string $bild_type, $id, array $alleSpielerInfo, array $alleVereineInfo, string $nameHtml): string
|
||||
{
|
||||
if (empty($id))
|
||||
return $nameHtml;
|
||||
if ($bild_type === 'spieler' && !halloffameSpielerAktiv($alleSpielerInfo, $id))
|
||||
return $nameHtml;
|
||||
if ($bild_type === 'vereine' && !halloffameVereinAktiv($alleVereineInfo, $id))
|
||||
return $nameHtml;
|
||||
|
||||
$tasks = ['spieler' => 'spieler_details', 'vereine' => 'verein_details', 'mannschaften' => 'team_details'];
|
||||
if (!isset($tasks[$bild_type]))
|
||||
return $nameHtml;
|
||||
|
||||
return '<a href="' . SportsManagerURL('&task=' . $tasks[$bild_type] . '&id=' . $id) . '">' . $nameHtml . '</a>';
|
||||
}
|
||||
|
||||
function halloffameTitel(): string
|
||||
{
|
||||
global $params;
|
||||
|
||||
$titel = trim((string) $params->get('titel', ''));
|
||||
if ($titel !== '')
|
||||
return $titel;
|
||||
|
||||
$menu = Factory::getContainer()->get(SiteApplication::class)->getMenu()->getActive();
|
||||
if ($menu !== null && trim((string) $menu->title) !== '')
|
||||
return $menu->title;
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
function turnierFilter($prefix): string
|
||||
{
|
||||
$user_id = isExternalDatabase() ? 0 : Factory::getContainer()->get(SiteApplication::class)->getIdentity()->id;
|
||||
@@ -411,6 +628,65 @@ function runden_detailliert_auswertung($runden): array
|
||||
return array($ergebnis, $heim_saetze, $unentschieden_saetze, $gast_saetze, $heim_punkte, $gast_punkte);
|
||||
}
|
||||
|
||||
function spielpunkteFuerSpiel($wertung, $heim_punkte, $gast_punkte, $ergebnis_detailliert = null): array
|
||||
{
|
||||
if ($wertung == 2)
|
||||
return array($heim_punkte, $gast_punkte);
|
||||
$je_satz = $wertung >= 10;
|
||||
$grund = $wertung % 10;
|
||||
$sieg = $grund == 0 ? 2 : ($grund == 1 ? 3 : 1);
|
||||
$remis = $grund == 3 ? 0 : 1;
|
||||
if ($je_satz) {
|
||||
if (empty($ergebnis_detailliert))
|
||||
return array($sieg * (int)$heim_punkte, $sieg * (int)$gast_punkte);
|
||||
$auswertung = runden_detailliert_auswertung($ergebnis_detailliert);
|
||||
return array($sieg * $auswertung[1] + $remis * $auswertung[2],
|
||||
$sieg * $auswertung[3] + $remis * $auswertung[2]);
|
||||
}
|
||||
if ($heim_punkte > $gast_punkte)
|
||||
return array($sieg, 0);
|
||||
if ($heim_punkte < $gast_punkte)
|
||||
return array(0, $sieg);
|
||||
if ($heim_punkte == 0 && $gast_punkte == 0)
|
||||
return array(0, 0);
|
||||
return array($remis, $remis);
|
||||
}
|
||||
|
||||
function toreFuerSpiel($punktetyp, $satzergebnisse, $heim_punkte, $gast_punkte, $ergebnis_detailliert): array
|
||||
{
|
||||
if ($punktetyp == 0)
|
||||
return array((int)$heim_punkte, (int)$gast_punkte);
|
||||
if (!empty($satzergebnisse) && !empty($ergebnis_detailliert)) {
|
||||
$auswertung = runden_detailliert_auswertung($ergebnis_detailliert);
|
||||
return array($auswertung[4], $auswertung[5]);
|
||||
}
|
||||
return array(null, null);
|
||||
}
|
||||
|
||||
function jeSatzWertung($wertung_einzel, $wertung_doppel): bool
|
||||
{
|
||||
return $wertung_einzel >= 10 || $wertung_doppel >= 10;
|
||||
}
|
||||
|
||||
function ausgetragenBedingung($je_satz, $alias): string
|
||||
{
|
||||
return "($alias.heim_punkte != 0 OR $alias.gast_punkte != 0"
|
||||
. ($je_satz ? " OR NOT ISNULL($alias.heim_tore)" : "") . ")";
|
||||
}
|
||||
|
||||
function ergebnisVorhanden($je_satz, $heim_punkte, $gast_punkte, $heim_tore): bool
|
||||
{
|
||||
return $heim_punkte != null || $gast_punkte != null || ($je_satz && $heim_tore !== null);
|
||||
}
|
||||
|
||||
function saetzeRemisFuerSpiel($punktetyp, $satzergebnisse, $ergebnis_detailliert): ?int
|
||||
{
|
||||
if ($punktetyp == 0 || empty($satzergebnisse) || empty($ergebnis_detailliert))
|
||||
return null;
|
||||
$auswertung = runden_detailliert_auswertung($ergebnis_detailliert);
|
||||
return $auswertung[2];
|
||||
}
|
||||
|
||||
// pass two file names
|
||||
// returns TRUE if files are the same, FALSE otherwise
|
||||
function files_identical($fn1, $fn2): bool
|
||||
|
||||
@@ -45,6 +45,11 @@
|
||||
default=""
|
||||
label="COM_SPORTSMANAGER_LAYOUT_GENERAL_CONTENT_OPTION_CATEGORIES"
|
||||
description="COM_SPORTSMANAGER_LAYOUT_GENERAL_CONTENT_OPTION_CATEGORIES_DESC" />
|
||||
<field name="zusatzparameter"
|
||||
type="text"
|
||||
default=""
|
||||
label="COM_SPORTSMANAGER_LAYOUT_GENERAL_CONTENT_OPTION_EXTRA_PARAMS"
|
||||
description="COM_SPORTSMANAGER_LAYOUT_GENERAL_CONTENT_OPTION_EXTRA_PARAMS_DESC" />
|
||||
</fieldset>
|
||||
</fields>
|
||||
</metadata>
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -34,7 +34,7 @@ class HTML_sportsmanager_ticker
|
||||
$zeit = date("H:i", $zeitpunkt_ts);
|
||||
if ($begegnung->zwischenergebnis)
|
||||
$status = 'livenow';
|
||||
elseif (!empty($begegnung->heim_spielpunkte) || !empty($begegnung->gast_spielpunkte))
|
||||
elseif (!empty($begegnung->heim_spielpunkte) || !empty($begegnung->gast_spielpunkte) || $begegnung->heim_tore !== null)
|
||||
$status = 'finished';
|
||||
elseif ($begegnung->verlegung_eingetragen)
|
||||
continue;
|
||||
@@ -111,7 +111,7 @@ class HTML_sportsmanager_ticker
|
||||
continue;
|
||||
if ($begegnung->zwischenergebnis)
|
||||
$status = 'livenow';
|
||||
elseif (!empty($begegnung->heim_spielpunkte) || !empty($begegnung->gast_spielpunkte))
|
||||
elseif (!empty($begegnung->heim_spielpunkte) || !empty($begegnung->gast_spielpunkte) || $begegnung->heim_tore !== null)
|
||||
$status = 'finished';
|
||||
elseif ($begegnung->verlegung_eingetragen)
|
||||
continue;
|
||||
@@ -216,7 +216,7 @@ class HTML_sportsmanager_ticker
|
||||
<div id="match_resultat">
|
||||
<div id="team_heim_logo"><?php if (!empty($heim_logo)) echo $heim_logo; ?></div>
|
||||
<div
|
||||
id="resultat_holder"><?php if (!$begegnung->zwischenergebnis && empty($begegnung->heim_spielpunkte) && empty($begegnung->gast_spielpunkte)) echo "--|--"; else echo $begegnung->heim_spielpunkte . ":" . $begegnung->gast_spielpunkte; ?></div>
|
||||
id="resultat_holder"><?php if (!$begegnung->zwischenergebnis && empty($begegnung->heim_spielpunkte) && empty($begegnung->gast_spielpunkte) && $begegnung->heim_tore === null) echo "--|--"; else echo $begegnung->heim_spielpunkte . ":" . $begegnung->gast_spielpunkte; ?></div>
|
||||
<div id="team_gast_logo"><?php if (!empty($gast_logo)) echo $gast_logo; ?></div>
|
||||
</div>
|
||||
</td>
|
||||
@@ -224,7 +224,10 @@ class HTML_sportsmanager_ticker
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="3" class="matchdate"><?php echo FormatiertesDatum($begegnung->zeitpunkt); ?>
|
||||
- <?php echo Rundenbezeichnung($begegnung->spieltag, $begegnung->unterteilung == 0); ?></td>
|
||||
- <?php echo Rundenbezeichnung($begegnung->spieltag, $begegnung->unterteilung == 0);
|
||||
if ($begegnung->heim_tore !== null
|
||||
&& ($begegnung->heim_tore != $begegnung->heim_spielpunkte || $begegnung->gast_tore != $begegnung->gast_spielpunkte))
|
||||
echo " - " . Text::_('COM_SPORTSMANAGER_GOALS') . " " . $begegnung->heim_tore . ":" . $begegnung->gast_tore; ?></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
@@ -257,7 +260,12 @@ class HTML_sportsmanager_ticker
|
||||
<!-- <tr class="<?php echo ($n % 2 == 0) ? "even" : "odd"; ?>" title="5:2 | 3:5 | 5:4 | 5:1"><td class="num"><?php //echo $n++; ?></td> -->
|
||||
<td class="heim"><?php if ($sieger == 1) echo "<b>"; ?><?php if (!empty($spiel->heim_spieler_1_id)) echo $spiel->heim_spieler_1_nachname . ", " . $spiel->heim_spieler_1_vorname;
|
||||
if (!empty($spiel->heim_spieler_2_id)) echo "<br />" . $spiel->heim_spieler_2_nachname . ", " . $spiel->heim_spieler_2_vorname; ?><?php if ($sieger == 1) echo "</b>"; ?></td>
|
||||
<td class="ergeb"><?php if ($spiel->teamspiel_heim_punkte != 0 || $spiel->teamspiel_gast_punkte != 0) echo $spiel->teamspiel_heim_punkte . ":" . $spiel->teamspiel_gast_punkte; ?></td>
|
||||
<td class="ergeb"><?php if ($spiel->teamspiel_heim_punkte != 0 || $spiel->teamspiel_gast_punkte != 0 || !empty($spiel->ergebnis_detailliert)) {
|
||||
echo $spiel->teamspiel_heim_punkte . ":" . $spiel->teamspiel_gast_punkte;
|
||||
if ($spiel->teamspiel_heim_tore !== null
|
||||
&& ($spiel->teamspiel_heim_tore != $spiel->teamspiel_heim_punkte || $spiel->teamspiel_gast_tore != $spiel->teamspiel_gast_punkte))
|
||||
echo "<br/><small>" . $spiel->teamspiel_heim_tore . ":" . $spiel->teamspiel_gast_tore . "</small>";
|
||||
} ?></td>
|
||||
<td class="gast"><?php if ($sieger == 2) echo "<b>"; ?><?php if (!empty($spiel->gast_spieler_1_id)) echo $spiel->gast_spieler_1_nachname . ", " . $spiel->gast_spieler_1_vorname;
|
||||
if (!empty($spiel->gast_spieler_2_id)) echo "<br />" . $spiel->gast_spieler_2_nachname . ", " . $spiel->gast_spieler_2_vorname; ?><?php if ($sieger == 2) echo "</b>"; ?></td>
|
||||
</tr>
|
||||
@@ -411,51 +419,6 @@ 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()">
|
||||
@@ -478,11 +441,6 @@ 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">💻</span>
|
||||
<span id="theme-light" class="theme-btn" onclick="setTheme('light')" title="Light Theme">☀️</span>
|
||||
<span id="theme-dark" class="theme-btn" onclick="setTheme('dark')" title="Dark Theme">🌙</span>
|
||||
</div>
|
||||
<a href="<?php echo SportsManagerURL(); ?>" id="homeicon">Home</a>
|
||||
<div style="clear:both;"></div>
|
||||
<div id="left_menu">
|
||||
@@ -1888,99 +1846,6 @@ 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
|
||||
}
|
||||
|
||||
|
||||
@@ -158,9 +158,11 @@ COM_SPORTSMANAGER_DEFEATS="Niederlagen"
|
||||
COM_SPORTSMANAGER_DEFEATS_SHORTCUT="N"
|
||||
COM_SPORTSMANAGER_GOALS="Tore"
|
||||
COM_SPORTSMANAGER_GOALS_SHORTCUT="T"
|
||||
COM_SPORTSMANAGER_SETS="Sätze"
|
||||
COM_SPORTSMANAGER_SETS="Sätze"
|
||||
COM_SPORTSMANAGER_SETS_DRAWN="Unentschiedene Sätze: %d"
|
||||
COM_SPORTSMANAGER_POINT="Punkt"
|
||||
COM_SPORTSMANAGER_POINTS="Punkte"
|
||||
COM_SPORTSMANAGER_SETS_SHORTCUT="S"
|
||||
COM_SPORTSMANAGER_POINTS_SHORTCUT="P"
|
||||
COM_SPORTSMANAGER_ONE_POINT="1 Punkt"
|
||||
COM_SPORTSMANAGER_NUMBER_POINTS="%d Punkte"
|
||||
@@ -170,7 +172,7 @@ 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 Tore"
|
||||
COM_SPORTSMANAGER_DIFFERENCE="Differenz"
|
||||
COM_SPORTSMANAGER_DIFFERENCE_IN_POINTS="Differenz Spielpunkte"
|
||||
COM_SPORTSMANAGER_POINTS_RATIO="Verhältnis Spielpunkte"
|
||||
COM_SPORTSMANAGER_SCHEDULE_DATE="Zeitpunkt"
|
||||
@@ -395,6 +397,8 @@ COM_SPORTSMANAGER_BEATEN="Ausgetreten"
|
||||
COM_SPORTSMANAGER_HIDE="Verstecken"
|
||||
COM_SPORTSMANAGER_PASSIVE="Passiv"
|
||||
COM_SPORTSMANAGER_BEATEN_CLUB="Verein ausgetreten"
|
||||
COM_SPORTSMANAGER_ACTIVE_CLUBS="Aktive Vereine"
|
||||
COM_SPORTSMANAGER_INACTIVE_CLUBS="Inaktive Vereine"
|
||||
COM_SPORTSMANAGER_SINGLE_SEED="Elo-Startwert Einzel"
|
||||
COM_SPORTSMANAGER_DOUBLE_SEED="Elo-Startwert Doppel"
|
||||
COM_SPORTSMANAGER_PLAYER_EXPORT="Spieler: Exportieren"
|
||||
@@ -488,16 +492,21 @@ COM_SPORTSMANAGER_GAME_DOUBLES2="Doppel-Spiel"
|
||||
COM_SPORTSMANAGER_GAME_DOUBLES_IDENTICAL="Doppel-Spiel identisch"
|
||||
COM_SPORTSMANAGER_GAME_DOUBLES_SEPARATELY="Doppel-Spiel separat"
|
||||
COM_SPORTSMANAGER_ONE="Einer"
|
||||
COM_SPORTSMANAGER_MULTIPLE="Mehrere"
|
||||
COM_SPORTSMANAGER_MULTIPLE="Mehrere – nur der Satzstand"
|
||||
COM_SPORTSMANAGER_HOME_EXCHANGE="Heimtausch"
|
||||
COM_SPORTSMANAGER_NOT_ALLOWED="Nicht zulässig"
|
||||
COM_SPORTSMANAGER_ALLOWED="Zulässig"
|
||||
COM_SPORTSMANAGER_PLAYER_NAME="Spielername"
|
||||
COM_SPORTSMANAGER_PLAYERS_SEARCH_SPECIAL="(Spezialsuche)"
|
||||
COM_SPORTSMANAGER_ADD_SEASON="Saison hinzufügen"
|
||||
COM_SPORTSMANAGER_GAME_POINTS_GAME="Spielpunkte pro Spiel"
|
||||
COM_SPORTSMANAGER_GAME_POINTS_GAME="Spielpunkte"
|
||||
COM_SPORTSMANAGER_WIN_1_POINT="Sieg: 1 Punkt"
|
||||
COM_SPORTSMANAGER_WIN_2_POINTS="Sieg: 2 Punkte, Unentschieden: 1 Punkt"
|
||||
COM_SPORTSMANAGER_WIN_1_POINT_PER_SET="Je Satz: Sieg 1 Punkt"
|
||||
COM_SPORTSMANAGER_WIN_2_POINTS_PER_SET="Je Satz: Sieg 2 Punkte, Unentschieden 1 Punkt"
|
||||
COM_SPORTSMANAGER_WIN_3_POINTS_PER_SET="Je Satz: Sieg 3 Punkte, Unentschieden 1 Punkt"
|
||||
COM_SPORTSMANAGER_GAME_POINTS_FOR_GAME="Für das ganze Spiel"
|
||||
COM_SPORTSMANAGER_GAME_POINTS_FOR_SET="Für jeden Satz einzeln"
|
||||
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"
|
||||
@@ -1087,6 +1096,7 @@ COM_SPORTSMANAGER_MATCH_SWAPPING_HELP="Bei Heimrechttausch gleichen Termin eintr
|
||||
COM_SPORTSMANAGER_NOT_VALID_TIME="Ungültige Uhrzeit"
|
||||
COM_SPORTSMANAGER_REALLY_MATCH_RESCHEDULING="Willst Du diesen Spielverlegung wirklich entfernen?"
|
||||
COM_SPORTSMANAGER_REST_DAYS="Ruhetage"
|
||||
COM_SPORTSMANAGER_ADDITIONAL_INFO="Zusatzinfo"
|
||||
COM_SPORTSMANAGER_TRAINING_DAYS="Trainingstage"
|
||||
COM_SPORTSMANAGER_NOT_ACTUALIZED_DATA="Nicht aktualisierte Daten"
|
||||
COM_SPORTSMANAGER_ASSOCIATION_BODIES="Verbandsorgane"
|
||||
@@ -1107,6 +1117,10 @@ COM_SPORTSMANAGER_MATCH_TYPE="Spielform"
|
||||
COM_SPORTSMANAGER_REALLY_REMOVE_HALL_OF_FAME_YEAR="Willst Du wirklich dieses Hall of Fame Jahr löschen?"
|
||||
COM_SPORTSMANAGER_YEARS="Jahre"
|
||||
COM_SPORTSMANAGER_ADD_HALL_OF_FAME_YEAR="Hall of Fame Jahr hinzufügen"
|
||||
COM_SPORTSMANAGER_HALL_OF_FAME_TITLE_COUNT="Anzahl"
|
||||
COM_SPORTSMANAGER_HALL_OF_FAME_TITLES="Titel"
|
||||
COM_SPORTSMANAGER_HALL_OF_FAME_TEAM_PLAYERS="Mannschaftsspieler"
|
||||
COM_SPORTSMANAGER_HALL_OF_FAME_NOT_HELD="Nicht ausgespielt"
|
||||
COM_SPORTSMANAGER_NO_ENTRY="kein Eintrag"
|
||||
COM_SPORTSMANAGER_REALLY_SWAP_MATCH="Willst Du wirklich das Heimrecht tauschen?"
|
||||
COM_SPORTSMANAGER_SWAP_MATCH="Heimrechttausch"
|
||||
@@ -1116,3 +1130,56 @@ COM_SPORTSMANAGER_MATCH_REPORT_WAS_DELETED="Der Spielbericht wurde erfolgreich g
|
||||
COM_SPORTSMANAGER_MATCH_REPORT_CORRECTED="Spielberichtskorrektur"
|
||||
COM_SPORTSMANAGER_MIN_MATCHES="Mindestzahl Spiele"
|
||||
COM_SPORTSMANAGER_SELECT_ALL="Alle"
|
||||
COM_SPORTSMANAGER_MAIL_SEND_ERROR="Die Benachrichtigungs-E-Mail zum Ergebnis konnte nicht versendet werden."
|
||||
COM_SPORTSMANAGER_TERMIN_MAIL_SEND_ERROR="Mindestens eine Benachrichtigungs-E-Mail zum Termin konnte nicht versendet werden."
|
||||
COM_SPORTSMANAGER_GAME_MARGIN="Mindestvorsprung"
|
||||
COM_SPORTSMANAGER_GAME_CAP="Spätestens gewonnen bei"
|
||||
COM_SPORTSMANAGER_GAME_CAP_NONE="Keine Begrenzung"
|
||||
COM_SPORTSMANAGER_NO_MARGIN="Keiner"
|
||||
COM_SPORTSMANAGER_ONE_GOAL="1 Tor"
|
||||
COM_SPORTSMANAGER_NUMBER_GOALS="%d Tore"
|
||||
COM_SPORTSMANAGER_MULTIPLE_WITH_SETS="Mehrere – mit Ergebnis je Satz"
|
||||
COM_SPORTSMANAGER_SET_NUMBER="Satz %d"
|
||||
COM_SPORTSMANAGER_SET_COUNT="Sätze je Spiel"
|
||||
COM_SPORTSMANAGER_GROUP_GENERAL="Allgemein"
|
||||
COM_SPORTSMANAGER_GROUP_GAME="Spiel"
|
||||
COM_SPORTSMANAGER_GROUP_SET="Satz"
|
||||
COM_SPORTSMANAGER_GROUP_MATCH="Begegnung"
|
||||
COM_SPORTSMANAGER_GROUP_PLAYERS="Spieler und Statistik"
|
||||
COM_SPORTSMANAGER_DOUBLES_SETTINGS="Doppel-Spiele"
|
||||
COM_SPORTSMANAGER_ONE_FIXED_SET="1 Satz (wird gespielt)"
|
||||
COM_SPORTSMANAGER_NUMBER_FIXED_SETS="%d Sätze (alle werden gespielt)"
|
||||
COM_SPORTSMANAGER_NUMBER_WINNING_SETS="%d Gewinnsätze"
|
||||
COM_SPORTSMANAGER_DECIDING_SET="Entscheidungssatz"
|
||||
COM_SPORTSMANAGER_DECIDING_SET_SAME="Wie die übrigen Sätze"
|
||||
COM_SPORTSMANAGER_SETS_PER_GAME_HELP="Mit „Ergebnis je Satz" werden die Tore der einzelnen Sätze mit erfasst und stehen im Spielbericht; ohne bleibt nur der Satzstand."
|
||||
COM_SPORTSMANAGER_SET_COUNT_HELP="Die Angaben beschreiben den Wettbewerb; sie lehnen keine Eingabe ab. Auswertungen und Apps erkennen daran, ob ein Spiel noch läuft oder schon entschieden ist."
|
||||
COM_SPORTSMANAGER_MODE_GLOSSARY="Eine <strong>Begegnung</strong> ist die Partie zweier Mannschaften. Sie besteht aus mehreren <strong>Spielen</strong>: Einzel und Doppel, in der Reihenfolge der Spielfolge. Jedes Spiel bringt Spielpunkte für die Begegnung. Ein <strong>Spiel</strong> besteht aus einem Satz („Einer") oder aus mehreren. In einem <strong>Satz</strong> fallen <strong>Tore</strong>. Die Einstellungen unten sind in dieser Reihenfolge geordnet: von der Begegnung über das Spiel zum Satz."
|
||||
COM_SPORTSMANAGER_RESULT_AS_GAME_POINTS="Ohne Umrechnung – Tore bzw. Sätze sind die Spielpunkte"
|
||||
COM_SPORTSMANAGER_RESULT_AS_GAME_POINTS_GOALS="Ohne Umrechnung – Tore sind die Spielpunkte"
|
||||
COM_SPORTSMANAGER_RESULT_AS_GAME_POINTS_SETS="Ohne Umrechnung – Sätze sind die Spielpunkte"
|
||||
COM_SPORTSMANAGER_COUNTING="Zählweise"
|
||||
COM_SPORTSMANAGER_COUNTING_PER_GAME="Je Spiel – jedes Spiel beginnt bei 0:0"
|
||||
COM_SPORTSMANAGER_COUNTING_RACE="Race-Modus – die Tore addieren sich über alle Spiele"
|
||||
COM_SPORTSMANAGER_RACE_STEP="Schritt je Spiel"
|
||||
COM_SPORTSMANAGER_RACE_TARGET="Ziel"
|
||||
COM_SPORTSMANAGER_RACE_TARGET_FORMULA="Anzahl der Spiele aus der Spielfolge × Schritt je Spiel"
|
||||
COM_SPORTSMANAGER_RACE_TARGET_CURRENT="derzeit %1$d Spiele × %2$d = %3$d Tore"
|
||||
COM_SPORTSMANAGER_RACE_MARGIN="Mindestvorsprung am Ziel"
|
||||
COM_SPORTSMANAGER_RACE_CAP="Spätestens gewonnen bei"
|
||||
COM_SPORTSMANAGER_RACE_DRAW="Unentschieden am Ziel"
|
||||
COM_SPORTSMANAGER_RACE_DRAW_NO="Nein"
|
||||
COM_SPORTSMANAGER_RACE_DRAW_YES="Ja – bei einem Tor unter dem Ziel"
|
||||
COM_SPORTSMANAGER_RACE_HELP="Der Stand läuft über die ganze Begegnung durch: Spiel 1 endet beim Schritt, Spiel 2 beim doppelten Schritt, und so fort bis zum Ziel. Mindestvorsprung und Grenze gelten nur für das letzte Spiel. Der Race-Modus setzt „Spielergebnis als Spielpunkte" und einen Satz je Spiel voraus – beides wird mitgesetzt."
|
||||
COM_SPORTSMANAGER_ADD_GAME="Spiel hinzufügen"
|
||||
COM_SPORTSMANAGER_ADD_LINK="Verknüpfung hinzufügen"
|
||||
COM_SPORTSMANAGER_REMOVE_GAME="Dieses Spiel entfernen"
|
||||
COM_SPORTSMANAGER_REMOVE_LINK="Diese Verknüpfung entfernen"
|
||||
COM_SPORTSMANAGER_LINK_NUMBER="Verknüpfung Nr. %d"
|
||||
COM_SPORTSMANAGER_GROUP_SEQUENCE="Spielfolge"
|
||||
COM_SPORTSMANAGER_GAMES_IN_STATISTIK_FIRST_ONE="nur das erste Spiel"
|
||||
COM_SPORTSMANAGER_GAMES_IN_STATISTIK_FIRST="nur die ersten %d Spiele"
|
||||
COM_SPORTSMANAGER_POINT_TYPE_CHANGED_WARNING="Für diesen Mannschaftsspielplan sind bereits Ergebnisse eingetragen. Die gespeicherten Zahlen werden NICHT umgerechnet – sie bedeuten danach etwas anderes (Tore statt Sätze bzw. umgekehrt). Trotzdem speichern?"
|
||||
COM_SPORTSMANAGER_PER_SET_VALUATION_CHANGED_WARNING="Für diesen Mannschaftsspielplan sind bereits Ergebnisse eingetragen. Beim Wechsel der Wertung „je Satz" wird nichts umgerechnet: Spiele ohne gespeicherte Satzergebnisse werden mit dem vollen Siegwert je gewonnenem Satz gewertet, ihre Satz-Unentschieden gehen verloren. Und wo unter „Mehrere" in Wahrheit Tore in den Punkte-Feldern stehen (statt Sätzen), zählt ab jetzt jedes Tor wie ein gewonnener Satz doppelt. Trotzdem speichern?"
|
||||
COM_SPORTSMANAGER_RACE_RUNNING="Stand"
|
||||
COM_SPORTSMANAGER_TOTAL_SCORE="Gesamtstand"
|
||||
|
||||
@@ -159,8 +159,10 @@ COM_SPORTSMANAGER_DEFEATS_SHORTCUT="F"
|
||||
COM_SPORTSMANAGER_GOALS="Goals"
|
||||
COM_SPORTSMANAGER_GOALS_SHORTCUT="G"
|
||||
COM_SPORTSMANAGER_SETS="Sets"
|
||||
COM_SPORTSMANAGER_SETS_DRAWN="Drawn sets: %d"
|
||||
COM_SPORTSMANAGER_POINT="Point"
|
||||
COM_SPORTSMANAGER_POINTS="Points"
|
||||
COM_SPORTSMANAGER_SETS_SHORTCUT="S"
|
||||
COM_SPORTSMANAGER_POINTS_SHORTCUT="P"
|
||||
COM_SPORTSMANAGER_ONE_POINT="1 point"
|
||||
COM_SPORTSMANAGER_NUMBER_POINTS="%d points"
|
||||
@@ -170,7 +172,7 @@ 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="Goal difference"
|
||||
COM_SPORTSMANAGER_DIFFERENCE="Difference"
|
||||
COM_SPORTSMANAGER_DIFFERENCE_IN_POINTS="Difference in game points"
|
||||
COM_SPORTSMANAGER_POINTS_RATIO="Game points ratio"
|
||||
COM_SPORTSMANAGER_SCHEDULE_DATE="Appointment date"
|
||||
@@ -395,6 +397,8 @@ COM_SPORTSMANAGER_BEATEN="Excreted"
|
||||
COM_SPORTSMANAGER_HIDE="Hide"
|
||||
COM_SPORTSMANAGER_PASSIVE="Passive"
|
||||
COM_SPORTSMANAGER_BEATEN_CLUB="Club excreted"
|
||||
COM_SPORTSMANAGER_ACTIVE_CLUBS="Active clubs"
|
||||
COM_SPORTSMANAGER_INACTIVE_CLUBS="Inactive clubs"
|
||||
COM_SPORTSMANAGER_SINGLE_SEED="Elo starting value singles"
|
||||
COM_SPORTSMANAGER_DOUBLE_SEED="Elo starting value doubles"
|
||||
COM_SPORTSMANAGER_PLAYER_EXPORT="Players: Export"
|
||||
@@ -488,16 +492,21 @@ COM_SPORTSMANAGER_GAME_DOUBLES2="Double games"
|
||||
COM_SPORTSMANAGER_GAME_DOUBLES_IDENTICAL="Doubles game identical"
|
||||
COM_SPORTSMANAGER_GAME_DOUBLES_SEPARATELY="Doubles game seperately"
|
||||
COM_SPORTSMANAGER_ONE="One"
|
||||
COM_SPORTSMANAGER_MULTIPLE="Multiple"
|
||||
COM_SPORTSMANAGER_MULTIPLE="Multiple – set score only"
|
||||
COM_SPORTSMANAGER_HOME_EXCHANGE="Home exchange"
|
||||
COM_SPORTSMANAGER_NOT_ALLOWED="Not allowed"
|
||||
COM_SPORTSMANAGER_ALLOWED="Allowed"
|
||||
COM_SPORTSMANAGER_PLAYER_NAME="Player name"
|
||||
COM_SPORTSMANAGER_PLAYERS_SEARCH_SPECIAL="(Special search)"
|
||||
COM_SPORTSMANAGER_ADD_SEASON="Add season"
|
||||
COM_SPORTSMANAGER_GAME_POINTS_GAME="Game points per game"
|
||||
COM_SPORTSMANAGER_GAME_POINTS_GAME="Game points"
|
||||
COM_SPORTSMANAGER_WIN_1_POINT="Win: 1 point"
|
||||
COM_SPORTSMANAGER_WIN_2_POINTS="Win: 2 points, draw: 1 point"
|
||||
COM_SPORTSMANAGER_WIN_1_POINT_PER_SET="Per set: win 1 point"
|
||||
COM_SPORTSMANAGER_WIN_2_POINTS_PER_SET="Per set: win 2 points, draw 1 point"
|
||||
COM_SPORTSMANAGER_WIN_3_POINTS_PER_SET="Per set: win 3 points, draw 1 point"
|
||||
COM_SPORTSMANAGER_GAME_POINTS_FOR_GAME="For the whole game"
|
||||
COM_SPORTSMANAGER_GAME_POINTS_FOR_SET="For each set separately"
|
||||
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"
|
||||
@@ -1087,6 +1096,7 @@ COM_SPORTSMANAGER_MATCH_SWAPPING_HELP="If home advantage is swapped, enter the s
|
||||
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_ADDITIONAL_INFO="Additional info"
|
||||
COM_SPORTSMANAGER_TRAINING_DAYS="Training days"
|
||||
COM_SPORTSMANAGER_NOT_ACTUALIZED_DATA="Data not updated"
|
||||
COM_SPORTSMANAGER_ASSOCIATION_BODIES="Association bodies"
|
||||
@@ -1107,6 +1117,10 @@ 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_HALL_OF_FAME_TITLE_COUNT="Number"
|
||||
COM_SPORTSMANAGER_HALL_OF_FAME_TITLES="Titles"
|
||||
COM_SPORTSMANAGER_HALL_OF_FAME_TEAM_PLAYERS="Team Players"
|
||||
COM_SPORTSMANAGER_HALL_OF_FAME_NOT_HELD="Not held"
|
||||
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"
|
||||
@@ -1116,3 +1130,56 @@ COM_SPORTSMANAGER_MATCH_REPORT_WAS_DELETED="The match report has been successful
|
||||
COM_SPORTSMANAGER_MATCH_REPORT_CORRECTED="Match report corrected"
|
||||
COM_SPORTSMANAGER_MIN_MATCHES="Min count matches"
|
||||
COM_SPORTSMANAGER_SELECT_ALL="All"
|
||||
COM_SPORTSMANAGER_MAIL_SEND_ERROR="The notification e-mail for the result could not be sent."
|
||||
COM_SPORTSMANAGER_TERMIN_MAIL_SEND_ERROR="At least one notification e-mail for the appointment could not be sent."
|
||||
COM_SPORTSMANAGER_GAME_MARGIN="Minimum margin"
|
||||
COM_SPORTSMANAGER_GAME_CAP="Won at the latest at"
|
||||
COM_SPORTSMANAGER_GAME_CAP_NONE="No limit"
|
||||
COM_SPORTSMANAGER_NO_MARGIN="None"
|
||||
COM_SPORTSMANAGER_ONE_GOAL="1 goal"
|
||||
COM_SPORTSMANAGER_NUMBER_GOALS="%d goals"
|
||||
COM_SPORTSMANAGER_MULTIPLE_WITH_SETS="Multiple – with each set's result"
|
||||
COM_SPORTSMANAGER_SET_NUMBER="Set %d"
|
||||
COM_SPORTSMANAGER_SET_COUNT="Sets per game"
|
||||
COM_SPORTSMANAGER_GROUP_GENERAL="General"
|
||||
COM_SPORTSMANAGER_GROUP_GAME="Game"
|
||||
COM_SPORTSMANAGER_GROUP_SET="Set"
|
||||
COM_SPORTSMANAGER_GROUP_MATCH="Match"
|
||||
COM_SPORTSMANAGER_GROUP_PLAYERS="Players and statistics"
|
||||
COM_SPORTSMANAGER_DOUBLES_SETTINGS="Doubles games"
|
||||
COM_SPORTSMANAGER_ONE_FIXED_SET="1 set (played out)"
|
||||
COM_SPORTSMANAGER_NUMBER_FIXED_SETS="%d sets (all played out)"
|
||||
COM_SPORTSMANAGER_NUMBER_WINNING_SETS="Best of %d wins"
|
||||
COM_SPORTSMANAGER_DECIDING_SET="Deciding set"
|
||||
COM_SPORTSMANAGER_DECIDING_SET_SAME="As the other sets"
|
||||
COM_SPORTSMANAGER_SETS_PER_GAME_HELP="With “each set’s result” the goals of the individual sets are recorded too and appear in the match report; without it only the set score remains."
|
||||
COM_SPORTSMANAGER_SET_COUNT_HELP="These settings describe the competition; they never reject an entry. Evaluations and apps use them to tell a game still running from one already decided."
|
||||
COM_SPORTSMANAGER_MODE_GLOSSARY="A <strong>match</strong> is the fixture between two teams. It consists of several <strong>games</strong> – singles and doubles in the order of the game sequence – and each game yields game points for the match. A <strong>game</strong> consists of one set (“One”) or of several. Within a <strong>set</strong>, <strong>goals</strong> are scored. The settings below follow that order: from the match through the game down to the set."
|
||||
COM_SPORTSMANAGER_RESULT_AS_GAME_POINTS="Without conversion – goals or sets are the game points"
|
||||
COM_SPORTSMANAGER_RESULT_AS_GAME_POINTS_GOALS="Without conversion – goals are the game points"
|
||||
COM_SPORTSMANAGER_RESULT_AS_GAME_POINTS_SETS="Without conversion – sets are the game points"
|
||||
COM_SPORTSMANAGER_COUNTING="Counting"
|
||||
COM_SPORTSMANAGER_COUNTING_PER_GAME="Per game – every game starts at 0:0"
|
||||
COM_SPORTSMANAGER_COUNTING_RACE="Race – goals add up across all games"
|
||||
COM_SPORTSMANAGER_RACE_STEP="Step per game"
|
||||
COM_SPORTSMANAGER_RACE_TARGET="Target"
|
||||
COM_SPORTSMANAGER_RACE_TARGET_FORMULA="Number of games in the sequence × step per game"
|
||||
COM_SPORTSMANAGER_RACE_TARGET_CURRENT="currently %1$d games × %2$d = %3$d goals"
|
||||
COM_SPORTSMANAGER_RACE_MARGIN="Minimum margin at the target"
|
||||
COM_SPORTSMANAGER_RACE_CAP="Won at the latest at"
|
||||
COM_SPORTSMANAGER_RACE_DRAW="Draw at the target"
|
||||
COM_SPORTSMANAGER_RACE_DRAW_NO="No"
|
||||
COM_SPORTSMANAGER_RACE_DRAW_YES="Yes – one goal below the target"
|
||||
COM_SPORTSMANAGER_RACE_HELP="The score runs through the whole match: game 1 ends at the step, game 2 at twice the step, and so on up to the target. Minimum margin and limit apply to the last game only. Race requires “game result as game points” and one set per game – both are set along with it."
|
||||
COM_SPORTSMANAGER_ADD_GAME="Add a game"
|
||||
COM_SPORTSMANAGER_ADD_LINK="Add a link"
|
||||
COM_SPORTSMANAGER_REMOVE_GAME="Remove this game"
|
||||
COM_SPORTSMANAGER_REMOVE_LINK="Remove this link"
|
||||
COM_SPORTSMANAGER_LINK_NUMBER="Link no. %d"
|
||||
COM_SPORTSMANAGER_GROUP_SEQUENCE="Game sequence"
|
||||
COM_SPORTSMANAGER_GAMES_IN_STATISTIK_FIRST_ONE="the first game only"
|
||||
COM_SPORTSMANAGER_GAMES_IN_STATISTIK_FIRST="the first %d games only"
|
||||
COM_SPORTSMANAGER_POINT_TYPE_CHANGED_WARNING="Results have already been entered for this team schedule. The stored numbers are NOT converted – afterwards they mean something else (goals instead of sets, or the other way round). Save anyway?"
|
||||
COM_SPORTSMANAGER_PER_SET_VALUATION_CHANGED_WARNING="Results have already been entered for this team schedule. Switching the per-set valuation converts nothing: games without stored set results are valued at the full win value per set won, and their drawn sets are lost. And where a 'multiple sets' schedule in fact holds goals in its points fields (instead of sets), every goal will from now on count double, like a set won. Save anyway?"
|
||||
COM_SPORTSMANAGER_RACE_RUNNING="Score"
|
||||
COM_SPORTSMANAGER_TOTAL_SCORE="Total score"
|
||||
|
||||
+45
-22
@@ -94,6 +94,12 @@ return new class () implements InstallerScriptInterface
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!bildKopierenAngepasst(JPATH_ROOT.DIRECTORY_SEPARATOR.'components'.DIRECTORY_SEPARATOR.'com_sportsmanager'.DIRECTORY_SEPARATOR.'images'.DIRECTORY_SEPARATOR.'spieler-n.png', JPATH_ROOT.DIRECTORY_SEPARATOR.'images'.DIRECTORY_SEPARATOR.'sportsmanager'.DIRECTORY_SEPARATOR.'spieler'.DIRECTORY_SEPARATOR.'n.png', 180, 240, 1)) {
|
||||
Log::add('Image /components/com_sportsmanager/images/spieler-n.png could not be copied to /images/sportsmanager/spieler/n.png', Log::ERROR);
|
||||
echo '<p>Fehler: Bild spieler-n.png konnte nicht nach /images/sportsmanager/spieler/n.png verschoben werden</p>';
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
$adapter->getParent()->setRedirectURL('index.php?option=com_sportsmanager');
|
||||
return true;
|
||||
@@ -143,6 +149,9 @@ return new class () implements InstallerScriptInterface
|
||||
. "\n `gast_punkte` smallint(6) DEFAULT NULL,"
|
||||
. "\n `heim_spielpunkte` smallint(6) DEFAULT NULL,"
|
||||
. "\n `gast_spielpunkte` smallint(6) DEFAULT NULL,"
|
||||
. "\n `heim_tore` smallint(6) DEFAULT NULL,"
|
||||
. "\n `gast_tore` smallint(6) DEFAULT NULL,"
|
||||
. "\n `saetze_remis` smallint(6) DEFAULT NULL,"
|
||||
. "\n `nichtraucherschutz` tinyint(4) DEFAULT '0',"
|
||||
. "\n PRIMARY KEY (`begegnung_id`),"
|
||||
. "\n KEY `heim_team_id` (`heim_team_id`),"
|
||||
@@ -674,6 +683,7 @@ return new class () implements InstallerScriptInterface
|
||||
. "\n `telefon` varchar(64) DEFAULT NULL,"
|
||||
. "\n `email` varchar(64) DEFAULT NULL,"
|
||||
. "\n `ruhetage` varchar(64) DEFAULT NULL,"
|
||||
. "\n `zusatzinfo` text DEFAULT NULL,"
|
||||
. "\n `beschreibung` varchar(500) DEFAULT NULL,"
|
||||
. "\n `status` tinyint(1) NOT NULL DEFAULT '0',"
|
||||
. "\n PRIMARY KEY (`spielort_id`)"
|
||||
@@ -712,6 +722,11 @@ return new class () implements InstallerScriptInterface
|
||||
. "\n `punkte_verloren` smallint(6) DEFAULT NULL,"
|
||||
. "\n `punkte_differenz` smallint(6) DEFAULT NULL,"
|
||||
. "\n `punkte_quotient` float(8,2) DEFAULT NULL,"
|
||||
. "\n `tore_gewonnen` smallint(6) DEFAULT NULL,"
|
||||
. "\n `tore_verloren` smallint(6) DEFAULT NULL,"
|
||||
. "\n `tore_differenz` smallint(6) DEFAULT NULL,"
|
||||
. "\n `tore_quotient` float(8,2) DEFAULT NULL,"
|
||||
. "\n `saetze_remis` smallint(6) DEFAULT NULL,"
|
||||
. "\n `buchholz1` smallint(6) DEFAULT NULL,"
|
||||
. "\n `buchholz2` smallint(6) DEFAULT NULL,"
|
||||
. "\n PRIMARY KEY (`team_id`),"
|
||||
@@ -769,6 +784,9 @@ return new class () implements InstallerScriptInterface
|
||||
. "\n `teamspiel_heim_spielpunkte` smallint(6) DEFAULT NULL,"
|
||||
. "\n `teamspiel_gast_spielpunkte` smallint(6) DEFAULT NULL,"
|
||||
. "\n `ergebnis_detailliert` tinytext DEFAULT NULL,"
|
||||
. "\n `teamspiel_heim_tore` smallint(6) DEFAULT NULL,"
|
||||
. "\n `teamspiel_gast_tore` smallint(6) DEFAULT NULL,"
|
||||
. "\n `teamspiel_saetze_remis` smallint(6) DEFAULT NULL,"
|
||||
. "\n PRIMARY KEY (`teamspiel_id`),"
|
||||
. "\n KEY `begegnung_id` (`begegnung_id`),"
|
||||
. "\n KEY `heim_spieler_1_id` (`heim_spieler_1_id`),"
|
||||
@@ -783,8 +801,32 @@ return new class () implements InstallerScriptInterface
|
||||
. "\n `teamspiel_modus_id` int(11) NOT NULL AUTO_INCREMENT,"
|
||||
. "\n `bezeichnung` varchar(50) NOT NULL DEFAULT '',"
|
||||
. "\n `punktetyp` tinyint(4) NOT NULL DEFAULT '0',"
|
||||
. "\n `satzergebnisse` tinyint(4) NOT NULL DEFAULT '0',"
|
||||
. "\n `punkte_sieg_einzel` tinyint(4) NOT NULL DEFAULT '0',"
|
||||
. "\n `punkte_sieg_doppel` tinyint(4) NOT NULL DEFAULT '0',"
|
||||
. "\n `punkte_vorsprung_einzel` tinyint(4) NOT NULL DEFAULT '1',"
|
||||
. "\n `punkte_vorsprung_doppel` tinyint(4) NOT NULL DEFAULT '1',"
|
||||
. "\n `punkte_deckel_einzel` tinyint(4) NOT NULL DEFAULT '0',"
|
||||
. "\n `punkte_deckel_doppel` tinyint(4) NOT NULL DEFAULT '0',"
|
||||
. "\n `satz_sieg_einzel` tinyint(4) NOT NULL DEFAULT '0',"
|
||||
. "\n `satz_sieg_doppel` tinyint(4) NOT NULL DEFAULT '0',"
|
||||
. "\n `satz_vorsprung_einzel` tinyint(4) NOT NULL DEFAULT '1',"
|
||||
. "\n `satz_vorsprung_doppel` tinyint(4) NOT NULL DEFAULT '1',"
|
||||
. "\n `satz_deckel_einzel` tinyint(4) NOT NULL DEFAULT '0',"
|
||||
. "\n `satz_deckel_doppel` tinyint(4) NOT NULL DEFAULT '0',"
|
||||
. "\n `saetze_modus_einzel` tinyint(4) NOT NULL DEFAULT '0',"
|
||||
. "\n `saetze_modus_doppel` tinyint(4) NOT NULL DEFAULT '0',"
|
||||
. "\n `entscheidung_sieg_einzel` tinyint(4) NOT NULL DEFAULT '0',"
|
||||
. "\n `entscheidung_sieg_doppel` tinyint(4) NOT NULL DEFAULT '0',"
|
||||
. "\n `entscheidung_vorsprung_einzel` tinyint(4) NOT NULL DEFAULT '1',"
|
||||
. "\n `entscheidung_vorsprung_doppel` tinyint(4) NOT NULL DEFAULT '1',"
|
||||
. "\n `entscheidung_deckel_einzel` tinyint(4) NOT NULL DEFAULT '0',"
|
||||
. "\n `entscheidung_deckel_doppel` tinyint(4) NOT NULL DEFAULT '0',"
|
||||
. "\n `zaehlweise` tinyint(4) NOT NULL DEFAULT '0',"
|
||||
. "\n `race_schritt` tinyint(4) NOT NULL DEFAULT '0',"
|
||||
. "\n `race_vorsprung` tinyint(4) NOT NULL DEFAULT '1',"
|
||||
. "\n `race_deckel` tinyint(4) NOT NULL DEFAULT '0',"
|
||||
. "\n `race_remis` tinyint(4) NOT NULL DEFAULT '0',"
|
||||
. "\n `spielpunkte_wertung_einzel` tinyint(4) NOT NULL DEFAULT '0',"
|
||||
. "\n `spielpunkte_wertung_doppel` tinyint(4) NOT NULL DEFAULT '0',"
|
||||
. "\n `spielpunkte_bedingung` tinyint(4) NOT NULL DEFAULT '0',"
|
||||
@@ -1251,9 +1293,11 @@ return new class () implements InstallerScriptInterface
|
||||
. "\n `mitglied_halloffame_id` int(11) NOT NULL AUTO_INCREMENT,"
|
||||
. "\n `halloffame_id` int(11) NOT NULL,"
|
||||
. "\n `jahr` int(4) DEFAULT NULL,"
|
||||
. "\n `nicht_ausgespielt` tinyint(1) NOT NULL DEFAULT '0',"
|
||||
. "\n `platz` int(11) DEFAULT NULL,"
|
||||
. "\n `verein_id` int(11) DEFAULT NULL,"
|
||||
. "\n `teamname` varchar(64) DEFAULT NULL,"
|
||||
. "\n `teamspieler` text DEFAULT NULL,"
|
||||
. "\n `spieler1_id` int(11) DEFAULT NULL,"
|
||||
. "\n `spieler1` varchar(64) DEFAULT NULL,"
|
||||
. "\n `spieler2_id` int(11) DEFAULT NULL,"
|
||||
@@ -1274,28 +1318,7 @@ 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 = '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 = '';";
|
||||
$query = "INSERT IGNORE #__sportsmanager_einstellungen SET name = 'datenbank_version', wert = '123';";
|
||||
$db->setQuery( $query );
|
||||
if (!$db->execute()) { die($db->stderr(true)); }
|
||||
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
# 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.
|
||||
Reference in New Issue
Block a user