Compare commits

3 Commits
252 changed files with 3915 additions and 7390 deletions
+15 -12
View File
@@ -1,22 +1,25 @@
config/.env.db config/.env.db
config/.env.db-tables
config/.env.db-guest config/.env.db-guest
config/.env.redis config/.env.redis
config/.env.pw-encryption-key config/.env.pw-encryption-key
config/.env.wk-id
config/.env.mail config/.env.mail
config/.env.db-wkvs-user
config/.env.docker.db
websocket/node_modules websocket/node_modules
composer/vendor websocket/package-lock.json
websocket/package.json
composer
private-files/rechnungen/* private-files/rechnungen/*
private-files/config-uploads/* private-files/config-uploads/*
.php-ini .php-ini
.php-version .php-version
html/externe-geraete/json/* www/externe-geraete/json/*
html/files/ranglisten/* www/files/ranglisten/*
html/sponsoren.php www/sponsoren.php
html/css/sponsoren.css www/css/sponsoren.css
html/scripts www/scripts
html/files/music/* www/files/music/*
!html/files/music/piep.mp3 !www/files/music/piep.mp3
html/.well-known www/.well-known
html/intern/kampfrichter/ajax/ajax-neu_dynamic-rangliste.php www/intern/kampfrichter/ajax/ajax-neu_dynamic-rangliste.php
setup/database/*
+310
View File
@@ -0,0 +1,310 @@
#!/bin/bash
# ==========================================
# WKVS Full Stack Installation Script
# Autor: Euria
# Datum: 2026-04-20
# ==========================================
set -e # Stoppt das Skript bei jedem Fehler
export DEBIAN_FRONTEND=noninteractive
echo ""
echo ""
echo "=========================================="
echo " WKVS Full Stack Installation"
echo "=========================================="
echo ""
echo ""
read -p "Dieses Skript funktioniert nur auf einer leeren Linux Instalation. Fohrtfahren? [y/n/c]: " choiceInstall
case $choiceInstall in
[yY]*) ;;
[nN]*)
echo "Installation abgebrochen."
exit 0
;;
[cC]*)
echo "Installation abgebrochen."
exit 0
;;
*)
echo "Ungültige Eingabe."
exit 1
;;
esac
# --- 1. System Update & Basisinstallation ---
echo "Schritt 1: System aktualisieren und Basis-Pakete installieren..."
sudo apt update
sudo apt install -y mariadb-server apache2 nginx curl dig certbot python3-certbot-nginx
# --- 2. MariaDB Installation & Konfiguration ---
echo "Schritt 2: MariaDB Konfiguration..."
read -p "Willst du die Installation komplett automatisch abschließen? [y/n/c]: " choice
case $choice in
[yY]*)
PREFIX=$(openssl rand -hex 3)
DB_NAME="${PREFIX}_wkvs_db"
DB_USER_NORMAL="${PREFIX}_wkvs_user"
DB_USER_GUEST="${PREFIX}_wkvs_guest"
DB_PASS_NORMAL=$(openssl rand -base64 24)
DB_PASS_GUEST=$(openssl rand -base64 24)
DB_PORT="3306"
AUTO_INSTALL="true"
;;
[nN]*)
read -p "Name der Datenbank: " DB_NAME
read -sp "Passwort für $DB_USER_NORMAL: " DB_PASS_NORMAL
echo ""
read -p "Name des Gastbenutzers: " DB_USER_GUEST
read -sp "Passwort für $DB_USER_GUEST: " DB_PASS_GUEST
echo ""
read -p "Custom Port (Standard 3306): " DB_PORT
DB_PORT=${DB_PORT:-3306}
AUTO_INSTALL="false"
;;
[cC]*)
echo "Installation abgebrochen."
exit 0
;;
*)
echo "Ungültige Eingabe."
exit 1
;;
esac
# MariaDB Port umstellen (falls Custom)
if [ "$DB_PORT" != "3306" ]; then
echo " Stelle MariaDB auf Port $DB_PORT um..."
if [ -f /etc/mysql/mariadb.conf.d/50-server.cnf ]; then
sudo sed -i "s/.*port\s*=\s*3306/port = $DB_PORT/" /etc/mysql/mariadb.conf.d/50-server.cnf
sudo systemctl restart mariadb
else
echo " Fehler: Konfigurationsdatei nicht gefunden."
exit 1
fi
else
sudo systemctl restart mariadb
fi
# --- 3. Datenbank Setup ---
echo "Schritt 3: Datenbank und Benutzer erstellen..."
sudo mysql -P "$DB_PORT" <<EOF
CREATE DATABASE IF NOT EXISTS \`${DB_NAME}\`;
-- Normaler User
CREATE USER IF NOT EXISTS \`$DB_USER_NORMAL\`@'127.0.0.1' IDENTIFIED BY '$DB_PASS_NORMAL';
GRANT SELECT, INSERT, UPDATE, DELETE ON \`${DB_NAME}\`.* TO \`${DB_USER_NORMAL}\`@'127.0.0.1';
-- Gast User (Nur SELECT)
CREATE USER IF NOT EXISTS \`$DB_USER_GUEST\`@'127.0.0.1' IDENTIFIED BY '$DB_PASS_GUEST';
GRANT SELECT ON \`${DB_NAME}\`.* TO \`${DB_USER_GUEST}\`@'127.0.0.1';
FLUSH PRIVILEGES;
EOF
# --- 4. Environment Files & SQL Import ---
mkdir -p config
cat <<EOT > config/.env.db
DB_USER=$DB_USER_NORMAL
DB_PASSWORD=$DB_PASS_NORMAL
DB_NAME=$DB_NAME
DB_HOST=127.0.0.1
DB_PORT=$DB_PORT
EOT
cat <<EOT > config/.env.db-guest
DB_GUEST_USER=$DB_USER_GUEST
DB_GUEST_PASSWORD=$DB_PASS_GUEST
DB_NAME=$DB_NAME
DB_HOST=127.0.0.1
DB_PORT=$DB_PORT
EOT
# SQL Import (falls vorhanden)
if [ -f "setup/database/generate-sql.php" ]; then
echo " Generiere SQL-Struktur..."
php setup/database/generate-sql.php
fi
if [ -f "setup/database/sql-database.sql" ]; then
echo " Importiere Datenbank-Struktur..."
sudo mysql -P "$DB_PORT" "$DB_NAME" < setup/database/sql-database.sql
echo " Datenbank-Schema erfolgreich importiert."
else
echo " Warnung: SQL-Datei nicht gefunden. Import übersprungen."
fi
# --- 5. Verschlüsselungsschlüssel ---
if [ "$AUTO_INSTALL" = "true" ]; then
ENCRYPTION_KEY=$(openssl rand -base64 32)
echo " Automatischer Modus: Verschlüsselungsschlüssel generiert."
else
read -sp "Schlüssel zur symmetrischen Verschlüsselung (durch WK-Leitung): " ENCRYPTION_KEY
echo ""
fi
cat <<EOT > config/.env.pw-encryption-key
PW_ENCRYPTION_KEY=$ENCRYPTION_KEY
EOT
# --- 6. Composer & PHP Dependencies ---
if ! command -v composer &> /dev/null; then
echo " Composer wird installiert..."
sudo apt install -y composer
fi
echo " Installiere Composer Pakete..."
composer require vlucas/phpdotenv
composer require phpmailer/phpmailer
composer require shuchkin/simplexlsx
composer require sprain/swiss-qr-bill
composer require james-heinrich/getid3
composer require chriskonnertz/string-calc
composer require tecnickcom/tcpdf
# --- 7. Node.js WebSocket ---
cd websocket || { echo "Fehler: websocket Ordner nicht gefunden!"; exit 1; }
npm init -y
npm install redis ws dotenv
npm install -g pm2
cd ..
echo " WebSocket (Node.js) Installation abgeschlossen."
# --- 8. Apache & Nginx Konfiguration ---
echo "Schritt 8: Webserver konfigurieren..."
# Apache auf internen Port umstellen
APACHE_INTERNAL_PORT=8080
if grep -q "Listen 80" /etc/apache2/ports.conf 2>/dev/null; then
sudo sed -i "s/^Listen 80$/Listen $APACHE_INTERNAL_PORT/" /etc/apache2/ports.conf
# Falls Default VHost auch 80 hat
if [ -f /etc/apache2/sites-enabled/000-default.conf ]; then
sudo sed -i "s/Listen 80/Listen $APACHE_INTERNAL_PORT/" /etc/apache2/sites-enabled/000-default.conf 2>/dev/null || true
fi
fi
sudo systemctl restart apache2
echo " Apache auf internen Port $APACHE_INTERNAL_PORT umgestellt."
# --- 9. Nginx Reverse Proxy & SSL ---
echo "Schritt 9: Nginx Reverse Proxy & HTTPS einrichten..."
read -p "Domain der Webseite: (Beispiel: wk.wkvs.ch) " DOMAIN
if [ -z "$DOMAIN" ]; then
echo "❌ FEHLER: Die Domain darf nicht leer sein!"
exit 1
fi
DOMAIN=$(echo "$DOMAIN" | xargs)
# DNS Prüfung
echo " DNS-Prüfung für $DOMAIN..."
RESOLVED_IP=$(dig +short $DOMAIN 2>/dev/null | head -n1)
SERVER_IP=$(curl -s https://api.ipify.org 2>/dev/null)
if [ -z "$RESOLVED_IP" ]; then
echo "❌ FEHLER: Domain '$DOMAIN' kann nicht aufgelöst werden."
echo " Bitte warten Sie, bis die DNS-Einträge aktiv sind."
exit 1
fi
if [ -n "$SERVER_IP" ] && [ "$RESOLVED_IP" != "$SERVER_IP" ]; then
echo "⚠️ WARNUNG: Domain zeigt auf $RESOLVED_IP, Server ist $SERVER_IP."
echo " Bitte prüfen Sie Ihre DNS-Einträge."
read -p "Möchten Sie trotzdem fortfahren? [y/n]: " confirm
if [ "$confirm" != "y" ]; then
exit 0
fi
fi
# Nginx Konfiguration erstellen
NGINX_CONF="/etc/nginx/sites-available/$DOMAIN"
NGINX_LINK="/etc/nginx/sites-enabled/$DOMAIN"
rm -f /etc/nginx/sites-enabled/default 2>/dev/null || true
rm -f "$NGINX_CONF" 2>/dev/null || true
cat > "$NGINX_CONF" <<EOF
# HTTP -> HTTPS Weiterleitung
server {
listen 80;
server_name $DOMAIN;
return 301 https://\$host\$request_uri;
}
# HTTPS Server
server {
listen 443 ssl http2;
server_name $DOMAIN;
ssl_certificate /etc/letsencrypt/live/$DOMAIN/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/$DOMAIN/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;
# WebSocket Route
location /ws/ {
proxy_pass http://127.0.0.1:8082;
proxy_http_version 1.1;
proxy_set_header Upgrade \$http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host \$host;
proxy_set_header X-Real-IP \$remote_addr;
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto \$scheme;
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
proxy_connect_timeout 75s;
}
# Versteckte Dateien
location ~ /\. {
deny all;
access_log off;
log_not_found off;
}
# Apache Proxy
location / {
proxy_pass http://127.0.0.1:$APACHE_INTERNAL_PORT;
proxy_set_header Host \$host;
proxy_set_header X-Real-IP \$remote_addr;
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto \$scheme;
}
}
EOF
sudo ln -sf "$NGINX_CONF" "$NGINX_LINK"
# Let's Encrypt Zertifikat
echo " Starte Let's Encrypt Zertifikatsanfrage..."
read -p "E-Mail für Let's Encrypt: " EMAIL
sudo certbot --nginx -d $DOMAIN --agree-tos --no-eff-email --redirect --email "$EMAIL" --non-interactive
# Dienste neu starten
sudo systemctl reload nginx
sudo systemctl restart apache2
echo "=========================================="
echo "✅ Installation erfolgreich abgeschlossen!"
echo "=========================================="
echo "Zusammenfassung:"
echo "- Datenbank: $DB_NAME (Port: $DB_PORT)"
echo "- Apache intern: Port $APACHE_INTERNAL_PORT"
echo "- Node.js WS: Port 8082 (unter /ws/)"
echo "- HTTPS: https://$DOMAIN/intern/wk-leitung/logindata"
echo ""
echo "Nächste Schritte:"
echo "1. Starten Sie den WebSocket Server: cd websocket && pm2 start app.js"
echo "2. Testen Sie die Seite: https://$DOMAIN/intern/wk-leitung/logindata"
-55
View File
@@ -1,55 +0,0 @@
services:
nginx:
image: nginx:latest
ports:
- "127.0.0.1:8081:80"
depends_on:
- apache
- node
volumes:
- ./docker/nginx/nginx.conf:/etc/nginx/nginx.conf:ro
networks:
- app-network
apache:
build: ./docker/apache
volumes:
- .:/var/www/wkvs
networks:
- app-network
mariadb:
image: mariadb:11
container_name: test-mariadb
restart: unless-stopped
env_file:
- ./config/.env.docker.db
volumes:
- db_data:/var/lib/mysql
networks:
- app-network
node:
build:
context: .
dockerfile: docker/node/Dockerfile
expose:
- "8082"
volumes:
- .:/var/www/wkvs
- /var/www/wkvs/websocket/node_modules
networks:
- app-network
redis:
image: redis:7
container_name: wkvs-redis
networks:
- app-network
volumes:
db_data:
networks:
app-network:
driver: bridge
-12
View File
@@ -1,12 +0,0 @@
{
"require": {
"vlucas/phpdotenv": "^5.6",
"phpmailer/phpmailer": "^7.0",
"shuchkin/simplexlsx": "^1.1",
"sprain/swiss-qr-bill": "^5.3",
"james-heinrich/getid3": "^1.9",
"chriskonnertz/string-calc": "^2.0",
"tecnickcom/tcpdf": "^6.11",
"predis/predis": "^3.5"
}
}
-1583
View File
File diff suppressed because it is too large Load Diff
+7
View File
@@ -0,0 +1,7 @@
# This Account must have have permissions SELECT, INSERT, UPDATE !
DB_USER=DB_GUESTUSER_USERNAME
DB_PASSWORD=DB_GUESTUSER_PASSWORD
DB_NAME=DATABASE_NAME # Same as .env.db
DB_HOST=DB_HOST # Same as .env.db, An IP without Port, often 127.0.0.1 or localhost
DB_PORT=3306 # default Port is 3306
-28
View File
@@ -1,28 +0,0 @@
# Tabellennamen
DB_TABLE_TEILNEHMENDE=teilnehmende
DB_TABLE_TEILNEHMENDE_GRUPPEN=teilnehmende_gruppen
DB_TABLE_TEILNEHMENDE_AUDIOFILES=teilnehmende_audiofiles
DB_TABLE_VEREINE=vereine
DB_TABLE_DISZIPLINEN=disziplinen
DB_TABLE_KATEGORIEN=kategorien
DB_TABLE_WERTUNGEN=wertungen
DB_TABLE_WERTUNGEN_LOG=wertungen_log
DB_TABLE_WERTUNGSTYPEN=wertungstypen
DB_TABLE_GRUPPEN=gruppen
DB_TABLE_GRUPPEN_ZEITEN=gruppen_zeiten
DB_TABLE_WARENKORB_STARTGEBUEREN=warenkorb_startgebueren
DB_TABLE_VERBUCHTE_STARTGEBUEREN=verbuchte_startgebueren
DB_TABLE_VERBUCHTE_STARTGEBUEREN_LOG=verbuchte_startgebueren_log
DB_TABLE_ZEITPLAN_TYPES=zeitplan_types
DB_TABLE_TABELLEN_KONFIGURATION=tabellen_konfiguration
DB_TABLE_VARIABLES=variables
DB_EINMAL_LINKS=einmal_links
DB_TABLE_INTERN_BENUTZENDE=intern_benutzende
DB_TABLE_AUDIOFILES=audiofiles
DB_TABLE_WETTKAEMPFE=wettkaempfe
+24
View File
@@ -0,0 +1,24 @@
DB_PREFIX=PREFIX
# Tabellennamen
DB_TABLE_TEILNEHMENDE=teilnehmende
DB_TABLE_ORDERS=orders
DB_TABLE_BASKET_ITEMS=basket_items
DB_TABLE_VARIABLES=variables
DB_TABLE_OTL=onetimeloginlinks
DB_TABLE_KR_PROTOKOLL=kampfricherinnen_protokoll
DB_TABLE_PROGRAMME=programme
DB_TABLE_INTERN_USERS=intern_users
DB_TABLE_VEREINE=vereine
DB_TABLE_ABTEILUNGEN=abteilungen
DB_TABLE_TEILNEHMENDE_ABTEILUNGEN=teilnehmende_abteilungen
DB_TABLE_GERAETE=geraete
DB_TABLE_AUDIOFILES=audiofiles
DB_TABLE_TEILNEHMENDE_AUDIOFILES=teilnehmende_audiofiles
DB_TABLE_NOTEN=noten
DB_TABLE_NOTEN_CHANGES=noten_changes
DB_TABLE_NOTEN_BEZEICHNUNGEN=noten_bezeichnungen
DB_TABLE_ORDERS_LOG=orders_log
DB_TABLE_ZEITPLAN_TYPES=zeitplan_types
DB_TABLE_ABTEILUNGEN_ZEITEN=abteilungen_zeiten
DB_TABLE_RANKLIVE_CONFIGS=rankLive_configs
+7
View File
@@ -0,0 +1,7 @@
# This Account must have at least have permissions SELECT, INSERT, UPDATE, DELETE !
DB_USER=DB_USER_USERNAME
DB_PASSWORD=DB_USER_PASSWORD
DB_NAME=DATABASE_NAME
DB_HOST=DB_HOST # An IP with Port 127.0.0.1 or localhost
DB_PORT=3306 # default Port is 3306
+1
View File
@@ -0,0 +1 @@
PW_ENCRYPTION_KEY=YOUR_CUSTOM_PASSWORD_ENCRYPTION_KEY
+3
View File
@@ -0,0 +1,3 @@
REDDIS_PASSWORD=YOUR_REDDIS_PASSWORD
REDDIS_HOST=YOUR_REDDIS_IP # An IP, often 127.0.0.1 or localhost
REDDIS_PORT=YOUR_REDDIS_PORT # Default 6379
-138
View File
@@ -1,138 +0,0 @@
#!/usr/bin/env bash
set -e
# 1. Reset containers and volumes
docker compose down -v
# Ensure the config directory exists
mkdir -p config
# 2. Generate Random Variables correctly using $(...)
MYSQL_USER=$(openssl rand -hex 16)
MYSQL_PASSWORD=$(openssl rand -base64 32)
MYSQL_DATABASE="wkvs_database"
MYSQL_ROOT_PASSWORD=$(openssl rand -base64 32)
cat <<EOF > config/.env.docker.db
MYSQL_ROOT_PASSWORD="$MYSQL_ROOT_PASSWORD"
MYSQL_DATABASE=$MYSQL_DATABASE
MYSQL_USER=$MYSQL_USER
MYSQL_PASSWORD="$MYSQL_PASSWORD"
EOF
# 3. Handle Admin Hashing
KEYWORD=$(openssl rand -hex 16)
HASHED_KEYWORD=$(php -r "echo password_hash('$KEYWORD', PASSWORD_ARGON2ID);")
# Replace placeholder in SQL template
sed "s|__ARGON_HASH_PLACEHOLDER__|$HASHED_KEYWORD|g" docker/database/wkvs-database-template.sql > docker/database/db-sql.sql
# 1. Generate password
REDIS_PASSWORD=$(openssl rand -hex 32)
# 3. Save variables to your .env file
cat <<EOF > config/.env.redis
REDIS_PASSWORD="$REDIS_PASSWORD"
REDIS_HOST=redis
REDIS_PORT=6379
EOF
# 4. Start Docker Containers
docker compose up -d --build
# 5. Wait until MariaDB is fully up and accepting connections
echo "Waiting for MariaDB to start..."
sleep 5
until docker compose exec -T mariadb mariadb-admin ping -h localhost -u root -p"$MYSQL_ROOT_PASSWORD" --silent >/dev/null 2>&1; do
sleep 2
done
# 6. Execute Primary Schema Import
docker compose exec -T mariadb mariadb -u root -p"$MYSQL_ROOT_PASSWORD" "$MYSQL_DATABASE" < docker/database/db-sql.sql
rm docker/database/db-sql.sql
# 7. Generate Additional Application Users & Passwords
MYSQL_WKVS_USERNAME="wkvs_normal_user"
MYSQL_GUEST_USERNAME="wkvs_guest_user"
MYSQL_WKVS_PASSWORD=$(openssl rand -hex 32)
MYSQL_GUEST_PASSWORD=$(openssl rand -hex 16)
# Write app user configuration files
cat <<EOF > config/.env.db-wkvs-user
DB_USER=$MYSQL_WKVS_USERNAME
DB_PASSWORD="$MYSQL_WKVS_PASSWORD"
DB_NAME=$MYSQL_DATABASE
DB_HOST=mariadb
DB_PORT=3306
EOF
cat <<EOF > config/.env.db-guest
DB_GUEST_USER=$MYSQL_GUEST_USERNAME
DB_GUEST_PASSWORD="$MYSQL_GUEST_PASSWORD"
DB_NAME=$MYSQL_DATABASE
DB_HOST=mariadb
DB_PORT=3306
EOF
# 8. Create Additional Users & Grant Permissions in MariaDB
echo "Creating application users and setting permissions..."
docker compose exec -T mariadb mariadb -u root -p"$MYSQL_ROOT_PASSWORD" <<EOF
-- Create Normal User
CREATE USER IF NOT EXISTS '$MYSQL_WKVS_USERNAME'@'%' IDENTIFIED BY '$MYSQL_WKVS_PASSWORD';
GRANT SELECT, INSERT, UPDATE, DELETE ON \`$MYSQL_DATABASE\`.* TO '$MYSQL_WKVS_USERNAME'@'%';
-- Create Guest User
CREATE USER IF NOT EXISTS '$MYSQL_GUEST_USERNAME'@'%' IDENTIFIED BY '$MYSQL_GUEST_PASSWORD';
GRANT SELECT, INSERT, UPDATE ON \`$MYSQL_DATABASE\`.* TO '$MYSQL_GUEST_USERNAME'@'%';
FLUSH PRIVILEGES;
EOF
SERVICE_NAME="apache"
COMPOSER_DIR="/var/www/wkvs/composer"
echo "Running Composer setup via Docker Compose..."
# 1. Download Composer inside the service container if not installed globally
docker compose exec "$SERVICE_NAME" bash -c '
if ! command -v composer &> /dev/null && [ ! -f "composer.phar" ]; then
php -r "copy('\''https://getcomposer.org/installer'\'', '\''composer-setup.php'\'');"
php composer-setup.php --quiet
php -r "unlink('\''composer-setup.php'\'');"
fi
'
# 2. Run composer install against the specified directory
docker compose exec "$SERVICE_NAME" bash -c "
COMPOSER_CMD=\$(command -v composer || echo \"php \$(pwd)/composer.phar\")
TARGET_DIR=\"$COMPOSER_DIR\"
if [ -f \"\$TARGET_DIR/composer.lock\" ] || [ -f \"\$TARGET_DIR/composer.json\" ]; then
\$COMPOSER_CMD install --working-dir=\"\$TARGET_DIR\" --no-interaction --prefer-dist --quiet
else
echo \"Error: Neither composer.json nor composer.lock found in '\$TARGET_DIR'.\" >&2
exit 1
fi
"
echo "Konfiguriere Redis..."
# 2. Set password in the currently running Redis container
docker compose exec redis redis-cli CONFIG SET requirepass "$REDIS_PASSWORD"
PASSWORD_KEY=openssl rand -base64 32
cat <<EOF > config/.env.pw-encryption-key
PW_ENCRYPTION_KEY=$PASSWORD_KEY
EOF
echo "------------------------------------------------------"
echo "Setup Complete!"
echo "Admin Plain Keyword: $KEYWORD"
echo "------------------------------------------------------"
-27
View File
@@ -1,27 +0,0 @@
FROM php:8.5-apache
# Enable Apache rewrite module
RUN a2enmod rewrite
# Install system dependencies, PHP extensions, and clean up apt cache
RUN apt-get update && apt-get install -y \
unzip \
libzip-dev \
libicu-dev \
git \
&& docker-php-ext-install \
bcmath \
mysqli \
pdo_mysql \
zip \
intl \
&& pecl install redis \
&& docker-php-ext-enable redis \
&& rm -rf /var/lib/apt/lists/*
# Update Apache DocumentRoot to /var/www/wkvs/html/ and enable .htaccess overrides
ENV APACHE_DOCUMENT_ROOT=/var/www/wkvs/html/
RUN sed -ri -e 's!/var/www/html!${APACHE_DOCUMENT_ROOT}!g' /etc/apache2/sites-available/*.conf \
&& sed -ri -e 's!/var/www/html!${APACHE_DOCUMENT_ROOT}!g' /etc/apache2/apache2.conf /etc/apache2/conf-available/*.conf \
&& sed -i 's/AllowOverride None/AllowOverride All/g' /etc/apache2/apache2.conf
-1
View File
@@ -1 +0,0 @@
include_path=".:/var/www/html:/usr/local/lib/php"
-335
View File
@@ -1,335 +0,0 @@
SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO";
START TRANSACTION;
SET time_zone = "+00:00";
-- --------------------------------------------------------
-- Tabellenstruktur für Tabelle `audiofiles`
-- --------------------------------------------------------
CREATE TABLE `audiofiles` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`file_name` varchar(1024) NOT NULL,
`file_path` varchar(1024) NOT NULL,
`edited_by` int(11) DEFAULT NULL,
`timestamp` timestamp NOT NULL DEFAULT current_timestamp(),
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci;
-- --------------------------------------------------------
-- Tabellenstruktur für Tabelle `disziplinen`
-- --------------------------------------------------------
CREATE TABLE `disziplinen` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(1024) DEFAULT NULL,
`start_index` int(11) DEFAULT NULL,
`color_kampfrichter` char(7) NOT NULL DEFAULT '#424242',
`audiofile` tinyint(1) DEFAULT NULL,
`audiofile_max_duration` int(11) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci;
-- --------------------------------------------------------
-- Tabellenstruktur für Tabelle `einmal_links`
-- --------------------------------------------------------
CREATE TABLE `einmal_links` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`url` varchar(1000) DEFAULT NULL,
`user_id` int(11) NOT NULL DEFAULT 0,
`type` enum('login','pwreset','create_profile','') NOT NULL,
`timestamp` timestamp NOT NULL DEFAULT current_timestamp(),
`expires_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `url` (`url`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci;
DELIMITER $$
CREATE TRIGGER `before_insert_login_tokens`
BEFORE INSERT ON `einmal_links`
FOR EACH ROW
BEGIN
IF NEW.url IS NULL THEN
SET NEW.url = HEX(RANDOM_BYTES(32));
END IF;
END$$
DELIMITER ;
-- --------------------------------------------------------
-- Tabellenstruktur für Tabelle `gruppen`
-- --------------------------------------------------------
CREATE TABLE `gruppen` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(100) NOT NULL DEFAULT 'KEIN NAME',
`order_index` int(11) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci;
-- --------------------------------------------------------
-- Tabellenstruktur für Tabelle `gruppen_zeiten`
-- --------------------------------------------------------
CREATE TABLE `gruppen_zeiten` (
`zeitplan_id` int(11) NOT NULL,
`start_time` time NOT NULL,
`end_time` time DEFAULT NULL,
`abt_id` int(11) NOT NULL,
UNIQUE KEY `unique_index` (`zeitplan_id`,`abt_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci;
-- --------------------------------------------------------
-- Tabellenstruktur für Tabelle `intern_benutzende`
-- --------------------------------------------------------
CREATE TABLE `intern_benutzende` (
`id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT,
`username` varchar(191) DEFAULT NULL,
`name_person` varchar(255) DEFAULT NULL,
`password_hash` varchar(255) DEFAULT NULL,
`password_cipher` text DEFAULT NULL,
`created_at` datetime NOT NULL DEFAULT current_timestamp(),
`updated_at` datetime NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(),
`edited_by` varchar(20) NOT NULL DEFAULT '0',
`freigabe` varchar(500) NOT NULL DEFAULT 'keine',
`login_active` tinyint(1) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `username_page_unique` (`username`,`password_hash`) USING BTREE,
KEY `indexFreigabe` (`freigabe`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- Admin Seed Entry (Uses Argon2id placeholder for replacement via bash sed script)
INSERT INTO `intern_benutzende` (`username`, `password_hash`, `password_cipher`, `freigabe`, `login_active`)
VALUES ('admin', '__ARGON_HASH_PLACEHOLDER__', 'not_set', '{"types":["wk_leitung","trainer","kampfrichter"],"freigabenTrainer":["admin"],"freigabenKampfrichter":["A"]}', 1);
-- --------------------------------------------------------
-- Tabellenstruktur für Tabelle `kategorien`
-- --------------------------------------------------------
CREATE TABLE `kategorien` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`programm` text NOT NULL,
`order_index` int(11) DEFAULT NULL,
`preis` decimal(10,2) DEFAULT 0.00,
`aktiv` tinyint(1) NOT NULL DEFAULT 0,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci;
-- --------------------------------------------------------
-- Tabellenstruktur für Tabelle `tabellen_konfiguration`
-- --------------------------------------------------------
CREATE TABLE `tabellen_konfiguration` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`type_slug` varchar(128) NOT NULL,
`json` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL CHECK (json_valid(`json`)),
`all_noten_json` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL CHECK (json_valid(`all_noten_json`)),
`created_at` timestamp NOT NULL DEFAULT current_timestamp(),
`updated_at` timestamp NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(),
`created_by` int(11) DEFAULT NULL,
`updated_by` int(11) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `unique_slug` (`type_slug`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci;
-- --------------------------------------------------------
-- Tabellenstruktur für Tabelle `teilnehmende`
-- --------------------------------------------------------
CREATE TABLE `teilnehmende` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`wk_id` int(11) DEFAULT NULL,
`name` text NOT NULL,
`vorname` text NOT NULL,
`geburtsdatum` date NOT NULL,
`programm` text NOT NULL,
`verein` text NOT NULL,
`bezahlt` int(11) NOT NULL DEFAULT 1,
`bezahltoverride` int(11) NOT NULL DEFAULT 0,
`betrag_bezahlt` decimal(12,2) NOT NULL DEFAULT 0.00,
PRIMARY KEY (`id`),
KEY `wk_index` (`wk_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci;
-- --------------------------------------------------------
-- Tabellenstruktur für Tabelle `teilnehmende_audiofiles`
-- --------------------------------------------------------
CREATE TABLE `teilnehmende_audiofiles` (
`person_id` int(7) NOT NULL,
`geraet_id` int(7) NOT NULL,
`audiofile_id` int(7) NOT NULL,
UNIQUE KEY `unique_combo` (`person_id`,`geraet_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci;
-- --------------------------------------------------------
-- Tabellenstruktur für Tabelle `teilnehmende_gruppen`
-- --------------------------------------------------------
CREATE TABLE `teilnehmende_gruppen` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`turnerin_id` int(11) NOT NULL,
`abteilung_id` int(11) NOT NULL,
`geraet_id` int(11) NOT NULL,
`turnerin_index` int(11) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `unique_turnerin` (`turnerin_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci;
-- --------------------------------------------------------
-- Tabellenstruktur für Tabelle `variables`
-- --------------------------------------------------------
CREATE TABLE `variables` (
`name` varchar(255) NOT NULL DEFAULT 'NaN',
`value` varchar(255) DEFAULT NULL,
UNIQUE KEY `uniqueIndex` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci;
-- --------------------------------------------------------
-- Tabellenstruktur für Tabelle `verbuchte_startgebueren`
-- --------------------------------------------------------
CREATE TABLE `verbuchte_startgebueren` (
`order_id` int(11) NOT NULL,
`order_type` varchar(100) DEFAULT NULL,
`preis` float DEFAULT NULL,
`user_id` int(11) NOT NULL,
`item_ids` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL CHECK (json_valid(`item_ids`)),
`used_verein_konten` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL,
`order_status` int(11) NOT NULL DEFAULT 0,
`timestamp` timestamp NOT NULL DEFAULT current_timestamp(),
`edited_at` timestamp NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(),
PRIMARY KEY (`order_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci;
-- --------------------------------------------------------
-- Tabellenstruktur für Tabelle `verbuchte_startgebueren_log`
-- --------------------------------------------------------
CREATE TABLE `verbuchte_startgebueren_log` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`order_id` int(11) NOT NULL,
`old_order_status` int(11) NOT NULL,
`new_order_status` int(11) NOT NULL,
`edited_by` int(11) NOT NULL,
`edited_at` timestamp NOT NULL DEFAULT current_timestamp(),
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci;
-- --------------------------------------------------------
-- Tabellenstruktur für Tabelle `vereine`
-- --------------------------------------------------------
CREATE TABLE `vereine` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`verein` text NOT NULL,
`email` varchar(100) NOT NULL DEFAULT 'forms@testseite-fh.ch',
`konto` decimal(12,2) NOT NULL DEFAULT 0.00,
PRIMARY KEY (`id`),
UNIQUE KEY `indexturnerinnen` (`id`,`verein`(31))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci;
-- --------------------------------------------------------
-- Tabellenstruktur für Tabelle `warenkorb_startgebueren`
-- --------------------------------------------------------
CREATE TABLE `warenkorb_startgebueren` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_id` bigint(20) UNSIGNED NOT NULL,
`item_id` int(11) NOT NULL,
PRIMARY KEY (`id`),
KEY `basket_items_ibfk_1` (`user_id`),
KEY `basket_items_ibfk_2` (`item_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci;
-- --------------------------------------------------------
-- Tabellenstruktur für Tabelle `wertungen`
-- --------------------------------------------------------
CREATE TABLE `wertungen` (
`person_id` int(11) NOT NULL,
`note_bezeichnung_id` int(11) NOT NULL,
`geraet_id` int(11) NOT NULL,
`wk_id` int(11) NOT NULL,
`run_number` tinyint(3) NOT NULL DEFAULT 1,
`value` float DEFAULT NULL,
`is_public` tinyint(1) DEFAULT NULL,
`public_value` float DEFAULT NULL,
`timestamp` timestamp NOT NULL DEFAULT current_timestamp(),
UNIQUE KEY `uniquePersonNoteJahrGereat` (`person_id`,`note_bezeichnung_id`,`wk_id`,`geraet_id`,`run_number`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci;
-- --------------------------------------------------------
-- Tabellenstruktur für Tabelle `wertungen_log`
-- --------------------------------------------------------
CREATE TABLE `wertungen_log` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`person_id` int(11) NOT NULL,
`note_bezeichnung_id` int(11) NOT NULL,
`geraet_id` int(11) NOT NULL,
`wk_id` int(11) NOT NULL,
`run_number` tinyint(3) NOT NULL DEFAULT 1,
`old_value` float DEFAULT NULL,
`new_value` float DEFAULT NULL,
`edited_by` int(11) NOT NULL,
`timestamp` timestamp NOT NULL DEFAULT current_timestamp(),
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci;
-- --------------------------------------------------------
-- Tabellenstruktur für Tabelle `wertungstypen`
-- --------------------------------------------------------
CREATE TABLE `wertungstypen` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(255) NOT NULL,
`default_value` float DEFAULT NULL,
`type` enum('input','berechnung') NOT NULL DEFAULT 'input',
`berechnung` varchar(255) DEFAULT NULL,
`berechnung_json` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL CHECK (json_valid(`berechnung_json`)),
`max_value` float DEFAULT NULL,
`min_value` float DEFAULT NULL,
`pro_geraet` tinyint(1) NOT NULL DEFAULT 1,
`anzahl_laeufe_json` text DEFAULT NULL,
`geraete_json` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT '[]',
`zeige_auf_rangliste` float DEFAULT NULL,
`groesse_auf_rangliste` int(11) NOT NULL DEFAULT 0,
`nullstellen` int(11) NOT NULL DEFAULT 2,
`display_string` varchar(63) NOT NULL DEFAULT '${note}',
PRIMARY KEY (`id`),
UNIQUE KEY `uniqueName` (`name`) USING BTREE,
KEY `indexMaxValue` (`max_value`),
KEY `indexMinValue` (`min_value`),
KEY `indexBerechnungJson` (`berechnung_json`(768)),
KEY `indexDefaultValue` (`default_value`),
KEY `indexType` (`type`),
KEY `indexNullstellen` (`nullstellen`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci;
-- --------------------------------------------------------
-- Tabellenstruktur für Tabelle `wettkaempfe`
-- --------------------------------------------------------
CREATE TABLE `wettkaempfe` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(256) NOT NULL,
`slug` varchar(64) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `slug` (`slug`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci;
-- --------------------------------------------------------
-- Tabellenstruktur für Tabelle `zeitplan_types`
-- --------------------------------------------------------
CREATE TABLE `zeitplan_types` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(2048) NOT NULL,
`has_endtime` tinyint(1) DEFAULT NULL,
`display_index` int(11) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci;
COMMIT;
-51
View File
@@ -1,51 +0,0 @@
events {
worker_connections 1024;
}
http {
# Dynamically set Connection header (handles 'upgrade' and 'Upgrade')
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
server {
listen 80;
# Standard HTTP Proxy to Apache
location / {
proxy_pass http://apache:80;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
# WebSocket Proxy to Node
location /ws/ {
# Note: Add trailing slash if you want Nginx to strip '/ws/' before reaching Node
proxy_pass http://node:8082/;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade; # Dynamic connection header
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# Keep-alive settings for long-lived WebSocket connections
proxy_read_timeout 36000s;
proxy_send_timeout 36000s;
proxy_connect_timeout 75s;
}
# Block access to hidden files (.htaccess, .git, .env, etc.)
location ~ /\. {
deny all;
access_log off;
log_not_found off;
}
}
}
-23
View File
@@ -1,23 +0,0 @@
FROM node:20-alpine
WORKDIR /var/www/wkvs/websocket
# Install pm2 globally
RUN npm install -g pm2
# Copy package files from the websocket folder
COPY websocket/package*.json ./
# Install dependencies strictly following lockfile
RUN npm ci
# Copy the rest of the application source code from websocket folder
COPY websocket/ ./
# Change ownership to the non-root node user for security
RUN chown -R node:node /var/www/wkvs/websocket
USER node
EXPOSE 3000
CMD ["pm2-runtime", "start", "index.js"]
BIN
View File
Binary file not shown.
-315
View File
@@ -1,315 +0,0 @@
:root {
--header-bg: #f8fcffcc;
--header-border: 1px solid #ffffff4d;
--header-shadow: 0 8px 32px rgba(0, 0, 0, 0.05);
--header-top: 40px;
--header-clamp: clamp(0.5px, 0.225vw, 1px);
--accent: #391c36;
--menu-box-shadows: rgba(0, 0, 0, 0.1);
--menu-dropdown-bg: #fff;
--dropdown-el-hover: #f0f0f0;
--sidebar-dropdown: rgba(255, 255, 255, 0.4);
--sidebar-dropdown-shadow: rgba(0, 0, 0, 0.02);
--sidebar-dropdown-el-hover: rgba(0, 0, 0, 0.05);
}
@media (prefers-color-scheme: dark) {
:root {
--header-bg: #000000cc; /* Dark, semi-transparent background with blur support */
--header-border: 1px solid #ffffff10; /* Subtle, soft border for dark interfaces */
--header-shadow: 0 8px 32px rgba(0, 0, 0, 0.3); /* Stronger shadow to stand out against dark backgrounds */
--header-top: 40px;
--header-clamp: clamp(0.5px, 0.225vw, 1px);
--accent: #f9d8f7; /* Brightened, vibrant purple/pink accent to pop on dark backgrounds */
--menu-box-shadows: rgba(0, 0, 0, 0.5); /* Deeper shadow for menu items */
--menu-dropdown-bg: #1a1a1a; /* Dark solid background for dropdowns */
--dropdown-el-hover: #2a2a2a; /* Slightly lighter dark tone for hover states */
--sidebar-dropdown: rgba(30, 30, 30, 0.6); /* Semi-transparent dark background for sidebar dropdowns */
--sidebar-dropdown-shadow: rgba(0, 0, 0, 0.2);
--sidebar-dropdown-el-hover: rgba(255, 255, 255, 0.08); /* Soft white highlight on hover */
}
}
header {
position: fixed;
top: calc(var(--header-clamp) * 40);
left: 50%;
transform: translateX(-50%);
width: calc(100% - 2 * var(--header-clamp) * 40);
max-width: var(--max-width-content);
display: flex;
justify-content: space-between;
align-items: center;
border-radius: calc(var(--header-clamp) * 24);
padding: calc(var(--header-clamp) * 16) calc(var(--header-clamp) * 40);
background-color: var(--header-bg);
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
border: var(--heder-border);
box-shadow: var(--header-shadow);
z-index: 100;
transition: all 0.3s ease;
}
.rankLiveHeaderTitle {
display: inline-flex;
align-items: center;
text-decoration: none;
color: var(--accent);
font-size: 24px;
letter-spacing: 1.5px;
}
.menuLinksDiv {
display: flex;
flex-direction: row;
gap: 8px;
align-items: center;
}
.menuLinksDiv>a,
.menuLinksDiv>div>span {
padding: 8px 16px;
color: var(--accent);
text-decoration: none;
font-size: 18px;
font-weight: 400;
line-height: 1.25;
position: relative;
cursor: pointer;
transition: color 0.2s ease;
display: inline-flex;
align-items: center;
}
.menuLinksDiv .menu-icon {
margin-right: 8px;
width: 18px;
height: 18px;
opacity: 0.85;
}
.menuLinksDiv>div {
position: relative;
}
.menuLinksDiv>a::after,
.menuLinksDiv>div>span::after {
content: "";
position: absolute;
left: 16px;
right: 16px;
bottom: 2px;
height: 2px;
background-color: var(--accent);
transform: scaleX(0);
transition: transform 0.3s ease;
}
.menuLinksDiv>div>span {
display: inline-flex;
align-items: center;
gap: 8px;
}
.menuLinksDiv>div>span>svg.menu-chevron {
transition: transform 0.3s ease;
}
.menuLinksDiv>div>span:hover>svg.menu-chevron,
.menuLinksDiv>div.open>span>svg.menu-chevron {
transform: rotate(180deg);
}
.menuLinksDiv>a:hover::after,
.menuLinksDiv>div>span:hover::after,
.menuLinksDiv>div.open>span::after {
transform: scaleX(1);
}
.menuLinksDiv .dropdown {
position: absolute;
top: 100%;
left: 50%;
transform: translateX(-50%);
background-color: var(--menu-dropdown-bg);
border-radius: 8px;
box-shadow: 0 4px 12px var(--menu-box-shadows);
padding: 8px 0;
display: none;
min-width: 160px;
z-index: 101;
}
.menuLinksDiv .dropdown::before {
content: "";
position: absolute;
top: -15px;
left: 0;
right: 0;
height: 15px;
background: transparent;
}
.menuLinksDiv .dropdown a {
display: block;
padding: 8px 16px;
color: var(--accent);
text-decoration: none;
font-size: 16px;
transition: background-color 0.2s ease;
}
.dropdown a:hover, .dropdown form:hover {
background-color: var(--dropdown-el-hover);
}
.sidebar {
display: flex;
flex-direction: column;
height: 100vh;
background-color: var(--header-bg);
backdrop-filter: blur(16px);
-webkit-backdrop-filter: blur(16px);
border-right: 1px solid rgba(255, 255, 255, 0.3);
box-shadow: 4px 0 24px rgba(0, 0, 0, 0.06);
padding: calc(var(--header-clamp) * 60) 24px 32px 24px;
gap: 8px;
width: 288px;
}
.sidebar>a,
.sidebar>div>.menu-item {
color: var(--accent);
text-decoration: none;
font-size: 18px;
font-weight: 500;
padding: 14px 16px;
border-radius: 12px;
transition: all 0.2s ease;
display: flex;
align-items: center;
cursor: pointer;
}
.sidebar>a:hover,
.sidebar>a:active,
.sidebar>div>.menu-item:hover,
.sidebar>div.open>.menu-item {
background-color: rgba(0, 0, 0, 0.014);
transform: translateX(4px);
}
.sidebar .menu-icon {
margin-right: 14px;
opacity: 0.85;
flex-shrink: 0;
}
.sidebar>div>.menu-item>.menu-chevron {
height: 16px;
width: 16px;
transition: transform 0.3s ease;
}
.sidebar>div.open>.menu-item>.menu-chevron {
transform: rotate(180deg);
}
.sidebar .dropdown {
background-color: var(--sidebar-dropdown);
border-radius: 12px;
box-shadow: inset 0 2px 8px var(--sidebar-dropdown-shadow);
padding: 8px;
margin: 4px 16px 8px 16px;
display: none;
min-width: 160px;
}
.sidebar .dropdown a, .sidebar button {
display: block;
padding: 10px 16px;
color: var(--accent);
text-decoration: none;
font-size: 16px;
border-radius: 8px;
transition: all 0.2s ease;
}
.sidebar .dropdown a:hover {
background-color: var(--sidebar-dropdown-el-hover);
transform: translateX(2px);
}
.sidebar a,
.sidebar>div {
-webkit-tap-highlight-color: transparent !important;
outline: none;
}
@media (max-width: 920px) {
.burgerMenuDiv {
display: flex !important;
flex-direction: column;
justify-content: space-between;
width: calc(var(--header-clamp) * 30);
height: calc(var(--header-clamp) * 21);
cursor: pointer;
z-index: 99;
-webkit-tap-highlight-color: transparent;
position: relative;
}
.burgerMenuLine {
height: calc(var(--header-clamp) * 3);
width: 100%;
background-color: var(--accent);
border-radius: calc(var(--header-clamp) * 3);
transition: all 0.3s ease;
}
.burgerMenuDiv.active .burgerMenuLine:nth-child(1) {
transform: translateY(calc(var(--header-clamp) * 9)) rotate(45deg);
}
.burgerMenuDiv.active .burgerMenuLine:nth-child(2) {
opacity: 0;
}
.burgerMenuDiv.active .burgerMenuLine:nth-child(3) {
transform: translateY(calc(var(--header-clamp) * -9)) rotate(-45deg);
}
.sidebar {
position: fixed;
left: 0px;
top: 0px;
height: 100vh;
opacity: 0;
transform: translateX(-100%);
transition: all 0.5s cubic-bezier(0.25, 1, 0.5, 1);
z-index: 1000;
}
.sidebar.active {
opacity: 1;
transform: translateX(0%);
}
.menuLinksDiv {
display: none;
}
}
@media (min-width: 921px) {
.burgerMenuDiv {
display: none;
}
.sidebar {
display: none;
}
}
-100
View File
@@ -1,100 +0,0 @@
:root {
--main: #2d2726;
--bg: #FDFDFD;
--border-subtle: #d4d7e1;
--text-main: #191919;
--text-muted: #5e5e5e;
--paddingSite: 20px;
}
@media (prefers-color-scheme: dark) {
:root {
--main: #f0f0f0; /* Flipped to a bright off-white for main accents/highlights */
--bg: #121212; /* Dark background (replacing the near-white #FDFDFD) */
--border-subtle: #2d2d2d; /* Soft, dark border that won't clip harshly against the background */
--text-main: #f5f5f5; /* Light grey/white for high readability */
--text-muted: #a0a0a0; /* Mid-tone grey for secondary text */
--paddingSite: 20px; /* Kept identical for layout consistency */
}
}
/* --- BASE & TYPOGRAPHY --- */
*,
*::before,
*::after {
box-sizing: border-box;
-webkit-tap-highlight-color: transparent;
}
b {
font-weight: 600;
}
body {
margin: 0;
width: 100vw;
background: var(--bg);
color: var(--text-main);
overflow-x: hidden;
overscroll-behavior: none;
font-optical-sizing: auto;
}
.bgSection {
position: relative;
width: 100vw;
margin: 100px auto 80px auto;
padding: var(--paddingSite);
max-width: 1800px;
}
@media (max-width: 600px) {
.bgSection {
margin-top: 100px;
}
}
.headerWrapper {
margin: 40px 0;
color: var(--main);
text-align: center;
}
.headerMain {
margin: 0 0 10px 0;
font-weight: 200;
font-size: clamp(34px, 6vw, 3.5rem);
letter-spacing: 12px;
position: relative;
z-index: 4;
}
.headerWrapper > h4 {
margin: 0 0 10px 0;
letter-spacing: 3px;
font-weight: 300;
}
.headerAbt {
margin: 0;
font-weight: 400;
color: var(--main);
font-size: 1.5rem;
}
.tableCutter {
height: 120px;
z-index: 3;
width: 100%;
position: fixed;
top: 0;
background: var(--bg);
}
.tableWraper {
margin: 20px 0 80px 0;
}
.customDisplayEditorTable th {
top: 120px !important;
}
BIN
View File
Binary file not shown.
@@ -1,670 +0,0 @@
<?php
use Sprain\SwissQrBill\PaymentPart\Output\DisplayOptions;
use Sprain\SwissQrBill\PaymentPart\Output\TcPdfOutput\TcPdfOutput;
use TCPDF;
use Sprain\SwissQrBill as QrBill;
header('Content-Type: application/json');
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);
if (!isset($baseDir)) $baseDir = $_SERVER['DOCUMENT_ROOT'];
require_once $baseDir . '/../scripts/session_functions.php';
ini_wkvs_session();
check_user_permission('trainer');
verify_csrf();
if (!isset($_POST['name']) || !isset($_POST['vorname']) || !isset($_POST['strasse']) || !isset($_POST['plz']) || !isset($_POST['ort'])) {
http_response_code(400);
echo json_encode(['success' => false, 'message' => 'Daten für die Rechnungserstellung fehlen.']);
exit;
}
function generateInvoiceNumber() : int {
return random_int(10000000, 99999999);
}
$orderType = 'Startgebühr';
function createInvoice(mysqli $conn, $db_tabelle_verbuchte_startgebueren, $orderType, $userId, $jsonIds, $used_verein_konten, $order_status): int
{
$maxRetries = 25;
for ($i = 0; $i < $maxRetries; $i++) {
$invoiceNumber = generateInvoiceNumber();
$stmt = $conn->prepare(
"INSERT INTO `$db_tabelle_verbuchte_startgebueren` (order_id, order_type, user_id, item_ids, used_verein_konten, order_status)
VALUES (?, ?, ?, ?, ?, ?)"
);
$stmt->bind_param(
"isissi",
$invoiceNumber, $orderType, $userId, $jsonIds, $used_verein_konten, $order_status
);
if ($stmt->execute()) {
return $invoiceNumber;
}
// Duplicate key error → retry
if ($conn->errno !== 1062) {
throw new RuntimeException(
"Database error ({$conn->errno}): {$conn->error}"
);
}
}
throw new RuntimeException('Failed to generate unique invoice number');
}
$userId = $_SESSION['user_id_trainer'];
$type = 'tr';
$data = include $baseDir . '/../scripts/db/db-verbindung-script.php';
if ($data['success'] === false){
echo json_encode(['success' => false, 'message' => $data['message']]);
exit;
}
require $baseDir . '/../scripts/db/db-tables.php';
require $baseDir . '/../scripts/db/db-functions.php';
require $baseDir . '/../composer/vendor/autoload.php';
$sql = "SELECT
bi.id AS basket_id,
bi.item_id,
p.programm AS programm_name,
p.preis,
bi.id,
t.betrag_bezahlt
FROM $db_tabelle_warenkorb_startgebueren bi
LEFT JOIN $db_tabelle_teilnehmende t ON bi.item_id = t.id
LEFT JOIN $db_tabelle_kategorien p ON p.programm = t.programm
WHERE bi.user_id = ?";
$stmt = $mysqli->prepare($sql);
$stmt->bind_param("i", $userId);
$stmt->execute();
$result = $stmt->get_result();
$rows = $result->fetch_all(MYSQLI_ASSOC);
if (!$rows || count($rows) < 1) {
http_response_code(422);
exit;
}
$used_konten_json = $_POST['usedKonten'] ?? '{}';
$used_konten = json_decode($used_konten_json, true) ?? [];
$konten_save = [];
if (count($used_konten) > 0) {
$db_konten = db_select($mysqli, $db_tabelle_vereine, '`verein`, `konto`');
$indexed_konten = array_column($db_konten, 'konto', 'verein');
$db_user_permissions = db_get_var($mysqli, "SELECT `freigabe` FROM $db_tabelle_intern_benutzende WHERE `id` = ?", [$userId]);
$array_permissions = json_decode($db_user_permissions ?? '', true) ?? [];
$trainer_permissions = $array_permissions['freigabenTrainer'] ?? [];
foreach ($used_konten as $verein => $betrag) {
if (!isset($indexed_konten[$verein])) {
http_response_code(400);
echo json_encode(['success' => false, 'message' => 'Invalider Verein ' . htmlspecialchars($verein)]);
exit;
} elseif (!isset($trainer_permissions[$verein]) && !in_array('admin', $trainer_permissions)) {
http_response_code(403);
echo json_encode(['success' => false, 'message' => 'Keine Berechtigung auf den Zugriff des Kontos des Vereins ' . htmlspecialchars($verein)]);
exit;
} elseif ((float) $indexed_konten[$verein] < (float) $betrag) {
http_response_code(403);
echo json_encode(['success' => false, 'message' => 'Der Abzug des Vereins ' . htmlspecialchars($verein) . ' übersteigt das Guthaben dieses Vereins.']);
exit;
}
$konten_save[$verein] = (float) $betrag;
}
}
$used_verein_konten = json_encode($konten_save);
$preis = 0;
$ids = [];
foreach ($rows as $r) {
$r_preis = max(floatval($r['preis']) - floatval($r['betrag_bezahlt']), 0);
$preis += $r_preis;
$ids[$r['item_id']] = $r_preis;
}
$preis = min($preis - array_sum($konten_save), 0);
$jsonIds = json_encode($ids);
$all_teilnehmende_ids = array_keys($ids);
if (json_last_error() !== JSON_ERROR_NONE) {
http_response_code(400);
echo json_encode(['success' => false, 'message' => 'Invalid JSON']);
exit;
}
// --- Check for existing open order ---
$order_id = db_get_var($mysqli, "SELECT order_id FROM `$db_tabelle_verbuchte_startgebueren` WHERE user_id = ? AND order_status = 0 LIMIT 1", [$userId]);
$orderId = null;
if ($order_id === null) {
// --- INSERT new order ---
$stmt->close();
$order_status = 0;
$new_id = createInvoice($mysqli, $db_tabelle_verbuchte_startgebueren, $orderType, $userId, $jsonIds, $used_verein_konten, $order_status);
$orderId = $new_id;
} else {
// --- UPDATE existing order ---
$stmt->close();
$orderId = $order_id;
$sql = "
UPDATE `$db_tabelle_verbuchte_startgebueren`
SET item_ids = ?, used_verein_konten = ?
WHERE order_id = ?
";
$stmt = $mysqli->prepare($sql);
$stmt->bind_param("ssi", $jsonIds, $used_verein_konten, $order_id);
$stmt->execute();
}
$keys = [
'wkName', 'rechnungenName', 'rechnungenVorname', 'rechnungenStrasse',
'rechnungenHausnummer', 'rechnungenPostleitzahl', 'rechnungenOrt',
'rechnungenIBAN', 'linkWebseite', 'rechnungenPostversand', 'rechnungenPostversandKosten'
];
$placeholders = implode(',', array_fill(0, count($keys), '?'));
$stmt = $mysqli->prepare("SELECT `name`, `value` FROM $db_tabelle_var WHERE `name` IN ($placeholders)");
$stmt->bind_param(str_repeat('s', count($keys)), ...$keys);
$stmt->execute();
$variables = array_column($stmt->get_result()->fetch_all(MYSQLI_ASSOC), 'value', 'name');
$kats = db_select($mysqli, $db_tabelle_kategorien, '`programm`, `preis`');
$indexed_kats_preis = array_column($kats, 'preis', 'programm');
$current_wk_id = (int) db_get_variable($mysqli, $db_tabelle_var, ['current_wk_id']) ?: 0;
$current_wk_name = db_get_var($mysqli, "SELECT `name` FROM $db_tabelle_wettkaempfe WHERE `id` = ? LIMIT 1", [$current_wk_id]) ?: ($wkName . ' ' . $current_year);
class MYPDF extends TCPDF
{
public $columns;
public $headerBottomY = 0;
public $firstPageDone = false; // track first page
public $abzuegeHeader = false; // track first page
public $printfooter;
public function Header()
{
// Logo always
$image_file = $_SERVER['DOCUMENT_ROOT'] . '/intern/img/logo-normal.png';
$this->Image($image_file, 180, 15, 15);
// Table header only for subsequent pages
if ($this->abzuegeHeader) {
// Use same top margin as table
$this->SetY(35); // or another fixed Y
$this->SetX(15);
$this->SetFont('GoogleSansFlex-Regular', '', 11);
$this->setCellPaddings(2, 0, 2, 0);
$this->Cell($this->columns['name']['max_width'] + $this->columns['programm']['max_width'] + $this->columns['verein']['max_width'], 10, 'Beschreibung', 0, 0, 'L');
$this->Cell($this->columns['preis']['max_width'], 10, 'Abzug', 0, 0, 'C');
$this->Ln();
$this->headerBottomY = $this->GetY();
$this->SetLineWidth(0.6);
$this->Line(15, $this->GetY(), 210 - 15, $this->GetY());
$this->SetLineWidth(0.2);
} elseif ($this->firstPageDone && !empty($this->columns)) {
// Use same top margin as table
$this->SetY(35); // or another fixed Y
$this->SetX(15);
$this->SetFont('GoogleSansFlex-Regular', '', 11);
$this->setCellPaddings(2, 0, 2, 0);
foreach ($this->columns as $c) {
$this->Cell($c['max_width'], 10, $c['header'], 0, 0, 'L');
}
$this->Ln();
$this->headerBottomY = $this->GetY();
$this->SetLineWidth(0.6);
$this->Line(15, $this->GetY(), 210 - 15, $this->GetY());
$this->SetLineWidth(0.2);
}
}
public function Footer()
{
}
}
// --- Create PDF ---
$pdf = new MYPDF('P', 'mm', 'A4', true, 'UTF-8', false);
$pdf->current_year = (date('n') > 6) ? date('Y') + 1 : date('Y');
$pdf->wkp_name = trim(($variables['rechnungenVorname'] ?? '') . ' ' . ($variables['rechnungenName'] ?? ''));
$pdf->wkp_adresse = trim(($variables['rechnungenStrasse'] ?? '') . ' ' . ($variables['rechnungenHausnummer'] ?? ''));
$pdf->wkp_plz_ort = trim(($variables['rechnungenPostleitzahl'] ?? '') . ' ' . ($variables['rechnungenOrt'] ?? ''));
$pdf->wkp_url = $variables['linkWebseite'] ?? '';
$pdf->wk_name = $variables['wkName'] ?? '';
$pdf->pers_name = trim(($_POST['vorname'] ?? '') . ' ' . ($_POST['name'] ?? ''));
$pdf->pers_adresse = trim(($_POST['strasse'] ?? '') . ' ' . ($_POST['hausnummer'] ?? ''));
$pdf->pers_plz_ort = trim(($_POST['plz'] ?? '') . ' ' . ($_POST['ort'] ?? ''));
$pdf->printfooter = true;
$pdf->current_wk_name = $current_wk_name;
// Mark first page done so Header() prints table headers on subsequent pages
$pdf->firstPageDone = false;
$pdf->setCellPaddings(2, 0, 2, 0);
$pdf->SetMargins(15, 35, 15);
// Fonts
$pdf->AddFont('GoogleSansFlex9pt-Bold', '', $_SERVER['DOCUMENT_ROOT'] . '/../private-files/tcpdf-fonts/googlesansflex_9ptb.php');
$pdf->AddFont('GoogleSansFlex-Regular', '', $_SERVER['DOCUMENT_ROOT'] . '/../private-files/tcpdf-fonts/gsf.php');
$pdf->SetCreator(PDF_CREATOR);
$pdf->SetAuthor('WKVS');
$pdf->SetAutoPageBreak(TRUE, 10);
$pdf->SetFont('GoogleSansFlex-Regular', '', 11);
// --- Add a page ---
$pdf->AddPage();
// --- Sender block (left) ---
$pdf->SetX(15);
$pdf->Cell(0, 0, $pdf->wkp_name, 0, 1);
$pdf->Cell(0, 0, $pdf->wkp_adresse, 0, 1);
$pdf->Cell(0, 0, $pdf->wkp_plz_ort, 0, 1);
$pdf->Ln(7);
$pdf->Cell(0, 0, $pdf->wkp_url, 0, 1);
// --- Recipient block (right / window) ---
$x = 110; // X coordinate for right window
$y = 50; // Y coordinate
$w = 80; // Width of recipient block
$address = implode("\n", [
$pdf->pers_name,
$pdf->pers_adresse,
$pdf->pers_plz_ort
]);
$pdf->MultiCell($w, 5, $address, 0, 'L', false, 1, $x, $y, true);
date_default_timezone_set('UTC');
// --- Invoice title ---
$pdf->Ln(20); // space below recipient
$pdf->SetFont('GoogleSansFlex9pt-Bold', '', 15);
$pdf->Cell(0, 10, 'Startgebührenrechnung ' . $pdf->current_wk_name, 0, 1, 'L');
$pdf->SetFont('GoogleSansFlex-Regular', '', 10);
$pdf->Cell(0, 9, "Rechnungsnummer: " . $orderId, 0, 1, 'L');
$pdf->SetFont('GoogleSansFlex-Regular', '', 7);
$pdf->Cell(0, 0, "Ausstellungsdatum: " . date("d.m.y"), 0, 1, 'L');
$pdf->Ln(10); // space below title
$pdf->SetFont('GoogleSansFlex9pt-Bold', '', 13);
//$turnerinnnenIds = [];
$placeholders = implode(', ', array_fill(0, count($all_teilnehmende_ids), '?'));
$all_teilnehmende = db_select($mysqli, $db_tabelle_teilnehmende, '`id`, name, vorname, programm, verein, `betrag_bezahlt`', "id IN ($placeholders)", $all_teilnehmende_ids);
$all_teilnehmende_indexed = array_column($all_teilnehmende, null, 'id');
$columns = ['name' => ['header' => 'Name'],
'programm' => ['header' => 'Programm'],
'verein' => ['header' => 'Verein'],
'preis' => ['header' => 'Startgebühr']];
foreach ($columns as $key => $column){
$columns[$key]['max_width'] = $pdf->GetStringWidth($column['header']);
}
$totalPreis = 0.00;
$dbdata = [];
foreach ($ids as $singleid => $s_preis){
if (!isset($all_teilnehmende_indexed[$singleid])) continue;
$row = &$all_teilnehmende_indexed[$singleid];
if (count($row) !== 6) continue;
$float_preis = (float) $s_preis;
$formated_preis = number_format($float_preis, 2, '.', "'");
$totalPreis += $float_preis;
$row['formated_preis'] = $formated_preis;
$programm = $row['programm'] ?? '';
$kosten_kat_normal = (float) ($indexed_kats_preis[$programm] ?? 0.00);
$preis_text = $float_preis !== $kosten_kat_normal ? 'Die Startgebühren dieser Person wurden reduziert, da diese Person bereits CHF ' . number_format($row['betrag_bezahlt'], 2, '.', "'") . ' an Startgebühren zahlte' : '';
$row['preis_text'] = $preis_text;
$pdf->SetFont('', '', 10);
$text = 'Startgebühr '. ($row['name'] ?? '') .' '. ($row['vorname'] ?? '');
$text_width = $pdf->GetStringWidth($text);
$kat_width = $pdf->GetStringWidth($row['programm'] ?? '');
$verein_width = $pdf->GetStringWidth($row['verein'] ?? '');
$preis_width = $pdf->GetStringWidth('CHF '. $formated_preis);
$columns['name']['max_width'] = max($columns['name']['max_width'] ?? 0, $text_width);
$columns['programm']['max_width'] = max($columns['programm']['max_width'] ?? 0, $kat_width);
$columns['verein']['max_width'] = max($columns['verein']['max_width'] ?? 0, $verein_width);
$columns['preis']['max_width'] = max($columns['preis']['max_width'] ?? 0, $preis_width);
}
unset($row);
foreach ($columns as $key => $column){
$columns[$key]['max_width'] += 2; // Add some padding
}
$maxWidth = 210 - 30; // A4 width minus margins
$totalColumnWidth = 0;
foreach ($columns as $column){
$totalColumnWidth += $column['max_width'];
}
if ($totalColumnWidth < $maxWidth){
$scalingFactor = $maxWidth / $totalColumnWidth;
foreach ($columns as $key => $column){
$columns[$key]['max_width'] = $column['max_width'] * $scalingFactor;
}
}
foreach ($columns as $c) {
$pdf->Cell($c['max_width'], 10, $c['header'], 0, 0, 'L');
}
$pdf->Ln();
$pdf->SetLineWidth(0.6);
$pdf->Line(15, $pdf->GetY(), 210 - 15, $pdf->GetY());
$pdf->SetLineWidth(0.2);
$pdf->headerBottomY = $pdf->GetY();
// --- Set top margin for table below title ---
// Mark first page done so Header() prints table headers on subsequent pages
$pdf->firstPageDone = true;
$pdf->SetFont('GoogleSansFlex-Regular', '', 11);
$pdf->columns = $columns;
$margin_top = $pdf->headerBottomY;
$pdf->SetMargins(15, 45, 15); // +5 mm padding below header
$pdf->SetY($margin_top); // Move cursor below header manually
foreach ($all_teilnehmende_indexed as $s_id => $row){
$pdf->SetFont('', '', 10);
$text = 'Startgebühr '. ($row['name'] ?? '') .' '. ($row['vorname'] ?? '');
$pdf->Cell($columns['name']['max_width'], 10, $text, 0, 0, 'L');
$pdf->Cell($columns['programm']['max_width'], 10, $row['programm'] ?? '', 0, 0, 'L');
$pdf->Cell($columns['verein']['max_width'], 10, $row['verein'] ?? '', 0, 0, 'L');
$pdf->SetFillColor(100, 100, 100);
$formated_preis = $row['formated_preis'] ?? '';
$pdf->Cell($columns['preis']['max_width'], 10, 'CHF ' . $formated_preis, 0, 1, 'C');
if ($row['preis_text'] !== '') {
$pdf->SetY($pdf->getY() - 3);
$pdf->SetFont('', '', 6);
$pdf->Cell(195, 6, $row['preis_text'], 0, 1, 'L');
$pdf->SetFont('', '', 10);
}
$pdf->SetDrawColor(100, 100, 100);
$pdf->Line(15, $pdf->getY(), 210 - 15, $pdf->getY());
$pdf->SetDrawColor(0, 0, 0);
}
if ($variables['rechnungenPostversand'] && isset($_POST['postversand'])) {
$pdf->SetFont('', '', 10);
$text = 'Postversand der Rechnung';
$pdf->Cell($columns['name']['max_width'], 10, $text, 0, 0, 'L');
$pdf->Cell($columns['programm']['max_width'], 10, '', 0, 0, 'L');
$pdf->Cell($columns['verein']['max_width'], 10, '', 0, 0, 'L');
$pdf->SetFillColor(100, 100, 100);
$pdf->Cell($columns['preis']['max_width'], 10, 'CHF '. number_format(floatval($variables['rechnungenPostversandKosten'] ?? 0), 2, ".", "'"), 0, 1, 'C');
$pdf->SetDrawColor(100, 100, 100);
$pdf->Line(15, $pdf->getY(), 210 - 15, $pdf->getY());
$pdf->SetDrawColor(0, 0, 0);
$totalPreis += floatval($variables['rechnungenPostversandKosten'] ?? 0);
}
if (count($konten_save) > 0) {
$pdf->Ln(8);
$pdf->SetFont('GoogleSansFlex9pt-Bold', '', 10);
$pdf->Cell(0, 10, 'Abzüge (Vereinsguthaben)', 0, 1, 'L');
$pdf->SetFont('GoogleSansFlex-Regular', '', 10);
$pdf->SetFont('GoogleSansFlex9pt-Bold', '', 11);
$pdf->Cell($columns['name']['max_width'] + $columns['programm']['max_width'] + $columns['verein']['max_width'], 10, 'Beschreibung', 0, 0, 'L');
$pdf->Cell($columns['preis']['max_width'], 10, 'Abzug', 0, 0, 'C');
$pdf->Ln();
$pdf->SetLineWidth(0.6);
$pdf->Line(15, $pdf->GetY(), 210 - 15, $pdf->GetY());
$pdf->SetLineWidth(0.2);
$pdf->abzuegeHeader = true;
foreach ($konten_save as $verein => $betrag) {
$pdf->SetFont('', '', 10);
$text = 'Guthaben '. $verein;
$pdf->Cell($columns['name']['max_width'] + $columns['programm']['max_width'] + $columns['verein']['max_width'], 10, $text, 0, 0, 'L');
$pdf->SetFillColor(100, 100, 100);
$formated_preis = number_format(floatval($betrag ?? 0), 2, ".", "'");
$pdf->Cell($columns['preis']['max_width'], 10, '- CHF ' . $formated_preis, 0, 1, 'C');
$pdf->SetDrawColor(100, 100, 100);
$pdf->Line(15, $pdf->getY(), 210 - 15, $pdf->getY());
$pdf->SetDrawColor(0, 0, 0);
$totalPreis -= (float) $betrag;
}
$pdf->abzuegeHeader = false;
}
$pdf->Ln(3);
$pdf->SetFont('GoogleSansFlex9pt-Bold', '', 10);
$pdf->Cell(100, 10, "Gesamt:", 0, 0, 'L');
$pdf->SetX($columns['name']['max_width'] + $columns['programm']['max_width'] + $columns['verein']['max_width'] + 15);
$pdf->Cell($columns['preis']['max_width'], 10, 'CHF ' . number_format($totalPreis, 2, ".", "'"), 0, 1, 'C');
$pdf->SetFont('GoogleSansFlex-Regular', '', 10);
$cent_total_preis = (int) round($totalPreis * 100);
$rechnung_is_null = $cent_total_preis === 0;
if ($rechnung_is_null) {
$pdf->Ln(10);
$pdf->SetTextColor(90, 103, 39);
$pdf->MultiCell(0, 8, 'Diese Rechnung wurde als bezahlt eingetragen, da der Betrag CHF 0.00 beträgt', 0, 'L');
$pdf->SetTextColor(0, 0, 0);
} else {
$qrBill = QrBill\QrBill::create();
$qrBill->setCreditor(
QrBill\DataGroup\Element\StructuredAddress::createWithStreet(
$pdf->wkp_name,
trim($variables['rechnungenStrasse'] ?? ''),
trim($variables['rechnungenHausnummer'] ?? ''),
trim($variables['rechnungenPostleitzahl'] ?? ''),
trim($variables['rechnungenOrt'] ?? ''),
'CH'
)
);
$iban = strtoupper(str_replace(' ', '', (string)($variables['rechnungenIBAN'] ?? '')));
$qrBill->setCreditorInformation(
QrBill\DataGroup\Element\CreditorInformation::create($iban)
);
// Add debtor information
// Who has to pay the invoice? This part is optional.
$qrBill->setUltimateDebtor(
QrBill\DataGroup\Element\StructuredAddress::createWithStreet(
$_POST['vorname'] . ' ' . $_POST['name'],
$_POST['strasse'],
$_POST['hausnummer'],
$_POST['plz'],
$_POST['ort'],
'CH'
)
);
// Add payment amount information
// What amount is to be paid?
$qrBill->setPaymentAmountInformation(
QrBill\DataGroup\Element\PaymentAmountInformation::create(
'CHF',
$totalPreis
)
);
// Add payment reference
// This is what you will need to identify incoming payments.
$qrBill->setPaymentReference(
QrBill\DataGroup\Element\PaymentReference::create(
QrBill\DataGroup\Element\PaymentReference::TYPE_SCOR,
QrBill\Reference\RfCreditorReferenceGenerator::generate($orderId)
)
);
$referenz = "Startgebührenrechnung ". $pdf->wkp_name;
// Optionally, add some human-readable information about what the bill is for.
$qrBill->setAdditionalInformation(
QrBill\DataGroup\Element\AdditionalInformation::create(
$referenz
)
);
// 3. Create a full payment part for TcPDF
$output = new TcPdfOutput($qrBill, 'de', $pdf);
// 4. Optional, set layout options
if (class_exists(DisplayOptions::class)) {
$displayOptions = new DisplayOptions();
$displayOptions
->setPrintable(false) // true to remove lines for printing on a perforated stationery
->setDisplayTextDownArrows(false) // true to show arrows next to separation text, if shown
->setDisplayScissors(false) // true to show scissors instead of separation text
->setPositionScissorsAtBottom(false) // true to place scissors at the bottom, if shown
;
// 5. Generate the output, applying display options when supported
if (method_exists($output, 'setDisplayOptions')) {
$output->setDisplayOptions($displayOptions);
}
}
if ($pdf->getY() > 297 - 120) {
$pdf->firstPageDone = false;
$pdf->printfooter = false;
$pdf->addPage();
}
$output->getPaymentPart();
}
$filename = 'Rechnung Startgebuehren '.$current_wk_name.' '.date('YmdHis').'.pdf';
$pdf->SetTitle($filename);
$savePath = $baseDir . '/../private-files/rechnungen/' . $orderId . '.pdf';
// Save PDF to disk
$pdf->Output($savePath, 'F');
$orderStatus = $rechnung_is_null ? 2 : 1 ;
$sql = "UPDATE $db_tabelle_verbuchte_startgebueren SET order_status = ?, `preis` = ? WHERE order_id = ?";
$stmt = $mysqli->prepare($sql);
$stmt->bind_param("idi", $orderStatus, $totalPreis, $orderId);
$stmt->execute();
$stmt->close();
require $baseDir . '/../scripts/rechnungen/rechnungs-functions.php';
update_teilnehmende($ids, 0, $orderStatus, true);
update_konten_vereine($konten_save, 0, $orderStatus, true);
// 2. DELETE basket items
db_delete($mysqli, $db_tabelle_warenkorb_startgebueren, ['user_id' => intval($userId)]);
$mysqli->close();
// Send headers manually
header('Content-Type: application/pdf');
header('Content-Disposition: attachment; filename="' . $dirFileName . '"');
header('Content-Length: ' . filesize($savePath));
// Send file contents
readfile($savePath);
exit;
@@ -1,202 +0,0 @@
<?php
header('Content-Type: application/json');
if (!isset($baseDir))
$baseDir = $_SERVER['DOCUMENT_ROOT'];
require_once $baseDir . '/../scripts/session_functions.php';
ini_wkvs_session();
check_user_permission('trainer');
verify_csrf();
$personen_ids_json = $_POST['personen_ids'] ?? '';
$personen_ids = json_decode($personen_ids_json) ?? [];
$personen_ids = !is_array($personen_ids) ? array(intval($personen_ids)) : array_map('intval', $personen_ids);
if (count($personen_ids) < 1) {
echo json_encode(['success' => false, 'message' => "Keine Personen angegeben"]);
http_response_code(400);
exit;
}
$neu_kat_id = (int) $_POST['kat_id'] ?? 0;
if ($neu_kat_id < 1) {
echo json_encode(['success' => false, 'message' => "Keine neue Kategorie angegeben"]);
http_response_code(400);
exit;
}
require $baseDir . '/../scripts/db/db-functions.php';
require $baseDir . '/../scripts/db/db-tables.php';
$type = 'tr';
$dbconnection = require $baseDir . '/../scripts/db/db-verbindung-script.php';
if ($dbconnection['success'] !== true) {
echo 'Critical DB Error.';
exit;
}
$all_kats = db_select($mysqli, $db_tabelle_kategorien, "`id`, `programm`, `preis`");
$all_kats_id_indexed = array_column($all_kats, null, 'id');
$preise_kats = array_column($all_kats, 'preis', 'programm');
if (!isset($all_kats_id_indexed[$neu_kat_id])) {
echo json_encode(['success' => false, 'message' => "Nicht valide Kategorie"]);
http_response_code(400);
exit;
}
$new_kat_name = $all_kats_id_indexed[$neu_kat_id]['programm'];
$new_kat_preis = $all_kats_id_indexed[$neu_kat_id]['preis'];
$vereine_plus_array = [];
function get_kat_price_diff(array $old_data, string $new_kat): array {
global $preise_kats;
global $vereine_plus_array;
$return_array = [];
$old_kat = $old_data['programm'] ?? '';
$verein = $old_data['verein'] ?? '';
$bezahlt = (int) ($old_data['bezahlt'] ?? 1);
var_dump(!isset($preise_kats[$new_kat]), !in_array($bezahlt, [2, 4], true), $old_kat === $new_kat, $old_kat === '', $old_kat, $new_kat);
if ($old_kat === '' || $old_kat === $new_kat || !in_array($bezahlt, [2, 4], true) || !isset($preise_kats[$new_kat])) {
return $return_array;
}
$betrag_bezahlt = (float) ($old_data['betrag_bezahlt'] ?? 0.00);
$new_kat_preis = (float) $preise_kats[$new_kat];
if ($new_kat_preis > $betrag_bezahlt) {
$return_array['bezahlt'] = ($betrag_bezahlt === 0.00) ? 1 : 2;
} elseif ($new_kat_preis < $betrag_bezahlt) {
$return_array['bezahlt'] = 4;
$return_array['betrag_bezahlt'] = $betrag_bezahlt - $new_kat_preis;
$vereine_plus_array[$verein] = ((float) ($vereine_plus_array[$verein] ?? 0.00)) + $betrag_bezahlt - $new_kat_preis;
} else {
$return_array['bezahlt'] = 4;
}
return $return_array;
}
function update_verein_balances(array $vereine_plus_array_funct) {
global $mysqli;
global $db_tabelle_vereine;
if (empty($vereine_plus_array_funct)) {
return;
}
$stmt = $mysqli->prepare("UPDATE $db_tabelle_vereine SET `konto` = `konto` + ? WHERE `verein` = ?");
if ($stmt) {
foreach ($vereine_plus_array_funct as $verein => $betrag) {
if ($betrag <= 0.00) {
continue;
}
$stmt->bind_param("ds", $betrag, $verein);
$stmt->execute();
}
$stmt->close();
}
}
function remove_from_basket(array $ids_array = []) : bool {
global $mysqli;
global $db_tabelle_warenkorb_startgebueren;
if (empty($ids_array)) return true;
$tplaceholders = implode(", ", array_fill(0, count($ids_array), "?"));
$stmt = $mysqli->prepare("DELETE FROM $db_tabelle_warenkorb_startgebueren WHERE `item_id` IN ($tplaceholders)");
$stmt->bind_param(str_repeat('i', count($ids_array)), ...$ids_array);
$ret = $stmt->execute();
$stmt->close();
return $ret;
}
// 4. Data Processing & Transaction Block
$placeholders = implode(", ", array_fill(0, count($personen_ids), "?"));
$entries = db_select($mysqli, $db_tabelle_teilnehmende, "`id`, `programm`, `bezahlt`, `betrag_bezahlt`, `verein`", "`id` IN ($placeholders)", $personen_ids);
$mysqli->begin_transaction();
try {
$stmtbez = $mysqli->prepare("UPDATE $db_tabelle_teilnehmende SET `bezahlt` = ?, `programm` = ? WHERE `id` = ?");
$stmtbezbet = $mysqli->prepare("UPDATE $db_tabelle_teilnehmende SET `bezahlt` = ?, `programm` = ?, `betrag_bezahlt` = ? WHERE `id` = ?");
$stmtnorm = $mysqli->prepare("UPDATE $db_tabelle_teilnehmende SET `programm` = ? WHERE `id` = ?");
$status_update_bez = null;
$current_id_bez = null;
$stmtbez->bind_param("isi", $status_update_bez, $new_kat_name, $current_id_bez);
$status_update_bet_bez = null;
$current_id_bet_bez = null;
$betrag_bezahlt = null;
$stmtbezbet->bind_param("isdi", $status_update_bet_bez, $new_kat_name, $betrag_bezahlt, $current_id_bet_bez);
$current_id_norm = null;
$stmtnorm->bind_param("si", $new_kat_name, $current_id_norm);
foreach ($entries as $s_entry) {
$res = get_kat_price_diff($s_entry, $new_kat_name);
if (count($res) < 1) {
$current_id_norm = $s_entry['id'];
$stmtnorm->execute();
} elseif (isset($res['betrag_bezahlt'])){
$status_update_bet_bez = $res['bezahlt'];
$current_id_bet_bez = $s_entry['id'];
$betrag_bezahlt = $res['betrag_bezahlt'];
$stmtbezbet->execute();
} else {
$status_update_bez = $res['bezahlt'];
$current_id_bez = $s_entry['id'];
$stmtbez->execute();
}
}
$stmtnorm->close();
$stmtbez->close();
remove_from_basket($personen_ids);
var_dump($vereine_plus_array);
update_verein_balances($vereine_plus_array);
$mysqli->commit();
// Fix #1: Added explicit echo for output tracking
http_response_code(200);
echo json_encode(['success' => true, 'message' => "Kategorie erfolgreich geändert"]);
exit;
} catch (Exception $e) {
// Rollback instantly if anything fails
$mysqli->rollback();
// Fix #1 & #3: Proper error output formatting
http_response_code(500);
echo json_encode(['success' => false, 'message' => "Datenbankfehler"]);
exit;
}
@@ -1,85 +0,0 @@
<?php
header('Content-Type: application/json');
if (!isset($baseDir)) {
$baseDir = $_SERVER['DOCUMENT_ROOT'];
}
require_once $baseDir . '/../scripts/session_functions.php';
ini_wkvs_session();
check_user_permission('wk_leitung');
verify_csrf();
$name = trim($_POST['name'] ?? '');
$slug = trim($_POST['slug'] ?? '');
if ($name === '') {
http_response_code(400);
echo json_encode(['success' => false, 'message' => 'Name ist leer']);
exit;
}
if ($slug === ''){
http_response_code(400);
echo json_encode(['success' => false, 'message' => 'Slug ist leer']);
exit;
}
if (preg_match('/\s/', $slug)) {
http_response_code(400);
echo json_encode(['success' => false, 'message' => 'Der Slug darf keine Leerzeichen enthalten']);
exit;
}
// Optional: Ensure the slug only contains lowercase letters, numbers, and hyphens
if (!preg_match('/^[a-z0-9-]+$/', $slug)) {
http_response_code(400);
echo json_encode(['success' => false, 'message' => 'Der Slug darf nur Kleinbuchstaben, Zahlen und Bindestriche enthaltenn']);
exit;
}
$type = 'wkl';
$data = include $baseDir . '/../scripts/db/db-verbindung-script.php';
if ($data['success'] === false){
echo json_encode(['success' => false, 'message' => $data['message']]);
exit;
}
require $baseDir . '/../scripts/db/db-tables.php';
require $baseDir . '/../scripts/db/db-functions.php';
$stmt = $mysqli->prepare("INSERT INTO $db_tabelle_wettkaempfe (`name`, `slug`) VALUES (?, ?)");
$stmt->bind_param("ss", $name, $slug);
try {
$stmt->execute();
} catch (mysqli_sql_exception $e) {
if ($e->getCode() === 1062) {
http_response_code(400);
echo json_encode(['success' => false, 'message' => 'Dieser Slug existiert bereits']);
exit;
}
http_response_code(500);
echo json_encode(['success' => false, 'message' => 'Ein Datenbankfehler ist aufgetreten']);
exit;
}
$stmt->close();
$insert_id = $mysqli->insert_id;
// Return JSON
echo json_encode([
'success' => true,
'message' => 'Wettkampf hinzugefügt',
'id' => $insert_id
]);
exit;
@@ -1,50 +0,0 @@
<?php
header('Content-Type: application/json');
if (!isset($baseDir)) {
$baseDir = $_SERVER['DOCUMENT_ROOT'];
}
require_once $baseDir . '/../scripts/session_functions.php';
ini_wkvs_session();
check_user_permission('wk_leitung');
require_once $baseDir . '/../scripts/delete_request_type_functions.php';
parse_input_to_delete();
verify_delete_csrf($_DELETE);
$id = (int) ($_DELETE['id'] ?? 0);
if ($id <= 0) {
echo json_encode(['success' => false, 'message' => 'No valid ID']);
}
require $baseDir . '/../scripts/db/db-functions.php';
require $baseDir . '/../scripts/db/db-tables.php';
$type = 'wkl';
$dbconnection = require $baseDir . '/../scripts/db/db-verbindung-script.php';
if ($dbconnection['success'] !== true){
echo 'Critical DB Error.';
exit;
}
$entrys = db_select($mysqli, $db_tabelle_wettkaempfe, "1", '`id` = ?', [$id]);
if (count($entrys) < 1) {
echo json_encode(['success' => true, 'message' => 'Wettkampf bereits gelöscht']);
http_response_code(410);
exit;
}
db_delete($mysqli, $db_tabelle_wettkaempfe, ['id' => $id]);
echo json_encode(['success' => true, 'message' => 'Wettkampf gelöscht']);
http_response_code(200);
@@ -1,62 +0,0 @@
<?php
header('Content-Type: application/json');
ini_set('display_errors', 1);
if (!isset($baseDir)) {
$baseDir = $_SERVER['DOCUMENT_ROOT'];
}
require_once $baseDir . '/../scripts/session_functions.php';
ini_wkvs_session();
check_user_permission('wk_leitung');
verify_csrf();
$id = (int) $_POST['id'] ?? 0;
if ($id < 1) {
echo json_encode(['success' => false, 'message' => 'Nicht valide ID']);
http_response_code(400);
exit;
}
require $baseDir . '/../scripts/db/db-functions.php';
require $baseDir . '/../scripts/db/db-tables.php';
$type = 'wkl';
$dbconnection = require $baseDir . '/../scripts/db/db-verbindung-script.php';
$id_row = db_select($mysqli, $db_tabelle_wettkaempfe, "1", "`id` = ?", [$id]);
if (count($id_row) !== 1) {
echo json_encode(['success' => false, 'message' => 'Diese ID existiert nicht']);
http_response_code(400);
exit;
}
$stmt = $mysqli->prepare("INSERT INTO $db_tabelle_var (`name`, `value`) VALUES ('current_wk_id', ?) ON DUPLICATE KEY UPDATE `value` = VALUES(`value`)");
$stmt->bind_param("i", $id);
try {
$stmt->execute();
} catch (mysqli_sql_exception $e) {
echo json_encode(['success' => false, 'message' => 'Ein Datenbankfehler ist aufgetreten']);
http_response_code(500);
exit;
}
$stmt->close();
// Return JSON
echo json_encode([
'success' => true,
'message' => 'Wert geändert'
]);
exit;
@@ -1,106 +0,0 @@
<?php
header('Content-Type: application/json');
if (!isset($baseDir)) {
$baseDir = $_SERVER['DOCUMENT_ROOT'];
}
require_once $baseDir . '/../scripts/session_functions.php';
ini_wkvs_session();
check_user_permission('wk_leitung');
verify_csrf();
$id = (int) $_POST['id'] ?? 0;
$field_name = trim($_POST['inputType'] ?? '');
$value = trim($_POST['value'] ?? '');
$allowed_field_names = ['name', 'slug'];
if (!in_array($field_name, $allowed_field_names)) {
echo json_encode(['success' => false, 'message' => 'Nicht valides Feld']);
http_response_code(400);
exit;
}
if ($id < 1) {
echo json_encode(['success' => false, 'message' => 'Nicht valide ID']);
http_response_code(400);
exit;
}
if ($value === '') {
echo json_encode(['success' => false, 'message' => 'Inhalt ist leer']);
http_response_code(400);
exit;
}
if ($field_name === 'slug'){
if (preg_match('/\s/', $slug)) {
echo json_encode(['success' => false, 'message' => 'Der Slug darf keine Leerzeichen enthalten']);
http_response_code(400);
exit;
}
// Optional: Ensure the slug only contains lowercase letters, numbers, and hyphens
if (!preg_match('/^[a-z0-9-]+$/', $slug)) {
echo json_encode(['success' => false, 'message' => 'Der Slug darf nur Kleinbuchstaben, Zahlen und Bindestriche enthaltenn']);
http_response_code(400);
exit;
}
}
require $baseDir . '/../scripts/db/db-functions.php';
require $baseDir . '/../scripts/db/db-tables.php';
$type = 'wkl';
$dbconnection = require $baseDir . '/../scripts/db/db-verbindung-script.php';
$id_row = db_select($mysqli, $db_tabelle_wettkaempfe, "1", "`id` = ?", [$id]);
if (count($id_row) !== 1) {
echo json_encode(['success' => false, 'message' => 'Diese ID existiert nicht']);
http_response_code(400);
exit;
}
$stmt = $mysqli->prepare("UPDATE $db_tabelle_wettkaempfe SET `$field_name` = ? WHERE `id` = ?");
$stmt->bind_param("si", $value, $id);
try {
$stmt->execute();
} catch (mysqli_sql_exception $e) {
// Error 1062: Duplicate entry
if ($e->getCode() === 1062) {
echo json_encode(['success' => false, 'message' => 'Dieser Slug existiert bereits']);
http_response_code(400);
exit;
}
// Error 1054: Unknown column (e.g., $field_name doesn't exist)
if ($e->getCode() === 1054) {
echo json_encode(['success' => false, 'message' => "Das Feld '$field_name' existiert nicht"]);
http_response_code(400);
exit;
}
// Fallback for other database errors
echo json_encode(['success' => false, 'message' => 'Ein Datenbankfehler ist aufgetreten']);
http_response_code(500);
exit;
}
$stmt->close();
// Return JSON
echo json_encode([
'success' => true,
'message' => 'Wert geändert'
]);
exit;
-431
View File
@@ -1,431 +0,0 @@
<?php
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
if (!isset($baseDir)) {
$baseDir = $_SERVER['DOCUMENT_ROOT'];
}
require_once $baseDir . '/../scripts/session_functions.php';
ini_wkvs_session(true);
$csrf_token = $_SESSION['csrf_token'] ?? '';
$access_granted_wkl = check_user_permission('wk_leitung', true) ?? false;
if (!$access_granted_wkl):
$logintype = 'wk_leitung';
require $baseDir . '/../scripts/login/login.php';
$logintype = '';
else:
?>
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="icon" type="png" href="/intern/img/icon.png">
<title>WK-leitung - Wettkämpfe</title>
<link rel="stylesheet" href="/intern/css/einstellungen-ai.css">
<link rel="stylesheet" href="/intern/css/sidebar.css">
<script src="/intern/js/jquery/jquery-3.7.1.min.js"></script>
<script src="/intern/js/custom-msg-display.js"></script>
<link rel="stylesheet" href="/intern/css/custom-msg-display.css">
<script src="/intern/js/custom-select.js"></script>
<link rel="stylesheet" href="/intern/css/custom-select.css">
<link rel="stylesheet" href="/files/fonts/fonts.css">
<link rel="stylesheet" href="/intern/css/user.css">
<link rel="stylesheet" href="/externe-geraete/css/display.css">
<script src="/intern/js/ace/ace.js"></script>
<link rel="stylesheet" href="/intern/css/coloris/coloris.min.css" />
<script src="/intern/js/coloris/coloris.min.js"></script>
</head>
<body class="wettkaempfe">
<?php
require $baseDir . '/../scripts/db/db-functions.php';
require $baseDir . '/../scripts/db/db-tables.php';
require $baseDir . '/../scripts/websocket/ws-create-token.php';
$type = 'wkl';
$dbconnection = require $baseDir . '/../scripts/db/db-verbindung-script.php';
if ($dbconnection['success'] !== true) {
echo 'Critical DB Error.';
exit;
}
$currentPage = 'wettkaempfe';
require $baseDir . '/../scripts/sidebar/sidebar.php';
$wettkaempfe = db_select($mysqli, $db_tabelle_wettkaempfe, '*', '', [], 'id ASC');
$current_wk_id = (int) db_get_variable($mysqli, $db_tabelle_var, ['current_wk_id']) ?: 0;
?>
<section class="bgSection">
<?php sidebarRender('modal'); ?>
<div class="headerDivTrainer">
<div class="headingPanelDiv">
<h2 class="headingPanel">Wettkämpfe</h2>
</div>
<div class="menuWrapper">
<?php sidebarRender('button'); ?>
</div>
</div>
<div class="dashboardGrid">
<div class="containerDiv">
<div>
<h3 class="containerHeading">Wettkämpfe</h3>
<table id="programmTable" class="wkvsTabelle">
<thead>
<tr>
<th>Wettkampf ID</th>
<th>Name</th>
<th>Slug</th>
<th>Status</th>
<th>Löschen</th>
</tr>
</thead>
<tbody>
<?php foreach ($wettkaempfe as $s_wk) :
$aktiv = ((int) $s_wk['id'] === $current_wk_id); ?>
<tr>
<td><?= (int) $s_wk['id'] ?></td>
<td>
<input class="ajaxInput" data-type="name" data-id="<?= (int) $s_wk['id'] ?>" value="<?= htmlspecialchars($s_wk['name'] ?? '') ?>">
</td>
<td>
<input class="ajaxInput" data-type="slug" data-id="<?= (int) $s_wk['id'] ?>" value="<?= htmlspecialchars($s_wk['slug'] ?? '') ?>">
</td>
<td>
<span class="statusSpan <?= $aktiv ? 'activeWk' : 'inactiveWk' ?>"><?= $aktiv ? 'Aktiv' : 'Inaktiv' ?></span>
<span class="activateSpan <?= $aktiv ? 'hidden' : '' ?>" data-id="<?= (int) $s_wk['id'] ?>">Aktivieren</span>
</td>
<td>
<button class="deleteWk deleteBtn" data-id="<?= (int) $s_wk['id'] ?>">
<svg xmlns="http://www.w3.org/2000/svg" width="22" height="22"
viewBox="0 0 24 24" fill="none" stroke="var(--ui-danger)"
stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round">
<polyline points="3 6 5 6 21 6"></polyline>
<path
d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2">
</path>
<line x1="10" y1="11" x2="10" y2="17"></line>
<line x1="14" y1="11" x2="14" y2="17"></line>
</svg>
</button>
</td>
</tr>
<?php endforeach; ?>
<tr class="addRow">
<td></td>
<td>
<input type="text" required placeholder="Bezeichnung" class="inputNewWkName">
</td>
<td>
<input type="text" required placeholder="Bsp. wkvs-2026" class="inputNewWkSlug">
</td>
<td colspan="2">
<button type="submit" class="blackBtn neuWkBtn">Neuen Wettkampf erstellen</button>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<div class="msgDiv"></div>
</section>
<script>
const csrf_token = "<?= $csrf_token ?>";
$('.neuWkBtn').on('click', function () {
const $btn = $(this);
const $row = $btn.closest(".addRow");
const $nameInput = $row.find(".inputNewWkName");
const $slugInput = $row.find(".inputNewWkSlug");
if ($nameInput.length !== 1 || $slugInput.length !== 1) return;
const name = $nameInput.val().trim();
if (name === '') {
displayMsg(2, "Der Wettkampfname darf nicht leer sein");
return;
}
const slug = $slugInput.val().trim();
if (slug === '') {
displayMsg(2, "Der Slug darf nicht leer sein");
return;
}
if (/\s/.test(slug)) {
displayMsg(2, "Der Slug darf keine Leerzeichen enthalten");
return;
}
if (!/^[a-z0-9-]+$/.test(slug)) {
displayMsg(2, "Der Slug darf nur Kleinbuchstaben, Zahlen und Bindestriche enthalten");
return;
}
$.ajax({
url: '/intern/scripts/wettkaempfe/ajax-add-wettkampf.php',
type: "POST",
data: {
csrf_token,
name,
slug
},
success: function(response) {
if (!response.success) {
displayMsg(0, response.message || "Der Wettkampf konnte nicht hinzugefügt werden");
return;
}
const $newRow = $('<tr>', {});
// 1. ID Column (Plain text)
const $tdId = $('<td>', { text: response.id });
// 2. Name Column (with nested <input>)
const $tdName = $('<td>');
const $inputName = $('<input>', {
class: 'ajaxInput',
'data-type': 'name',
'data-id': response.id,
val: name // Sets the input value securely
});
$tdName.append($inputName);
// 3. Slug Column (with nested <input>)
const $tdSlug = $('<td>');
const $inputSlug = $('<input>', {
class: 'ajaxInput',
'data-type': 'slug',
'data-id': response.id,
val: slug // Sets the input value securely
});
$tdSlug.append($inputSlug);
// 4. Empty Column
const $tdStatus = $('<td>', {});
const $spanStatus = $('<span>', {
text: 'Inaktiv',
class: "statusSpan inactiveWk"
});
const $spanActivate = $('<span>', {
text: 'aktivieren',
'data-id': response.id,
class: "activateSpan"
});
$tdStatus.append($spanStatus, $spanActivate);
// 5. Action Column (Delete button with SVG)
const $tdAction = $('<td>', {});
const $deleteBtn = $('<button>', {
class: 'deleteWk deleteBtn',
'data-id': response.id
});
const $svgIcon = $(`
<svg xmlns="http://www.w3.org/2000/svg" width="22" height="22"
viewBox="0 0 24 24" fill="none" stroke="var(--ui-danger)"
stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round">
<polyline points="3 6 5 6 21 6"></polyline>
<path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path>
<line x1="10" y1="11" x2="10" y2="17"></line>
<line x1="14" y1="11" x2="14" y2="17"></line>
</svg>
`);
$deleteBtn.append($svgIcon);
$tdAction.append($deleteBtn);
// 6. Assemble everything
$newRow.append($tdId, $tdName, $tdSlug, $tdStatus, $tdAction);
// 7. Insert into the DOM
// Note: If you want to insert the new row *before* an existing row ($row), use this:
$newRow.insertBefore($row);
displayMsg(1, "Neuer Wettkampf hinzugefügt");
},
error: () => {
displayMsg(0, "Fehler beim Hinzufügen des Wettkampfes");
}
});
});
$(document).on('click', '.deleteWk', async function () {
const $btn = $(this);
const $row = $btn.closest("tr");
const name = $row.find('.ajaxInput[data-type="name"]').val() || '';
if (!await displayConfirm(`Wettkampf "${name}" wirklich unwiederruflich löschen? Diese Aktion löscht alle Ergebnisse dieses Wettkampfes!`)) return;
const dataId = $btn.data('id');
$.ajax({
url: '/intern/scripts/wettkaempfe/ajax-delete-wettkampf.php',
type: "DELETE",
data: {
csrf_token,
id: dataId
},
success: function(response) {
if (!response.success) {
displayMsg(0, response.message || "Der Wettkampf konnte nicht gelöscht werden");
return;
}
$row.remove();
displayMsg(1, "Wettkampf gelöscht");
},
error: () => {
displayMsg(0, "Fehler beim Löschen des Wettkampfes");
}
});
});
$(document).on('change', '.ajaxInput', function () {
const $input = $(this);
const value = $input.val().trim();
const inputType = $input.data('type');
const dataId = $input.data('id');
if (value === '') {
displayMsg(2, "Dieses Feld darf nicht leer sein");
return;
}
if (inputType === "slug") {
if (/\s/.test(value)) {
displayMsg(2, "Der Slug darf keine Leerzeichen enthalten");
return;
}
if (!/^[a-z0-9-]+$/.test(value)) {
displayMsg(2, "Der Slug darf nur Kleinbuchstaben, Zahlen und Bindestriche enthalten");
return;
}
}
$.ajax({
url: '/intern/scripts/wettkaempfe/ajax-update-wettkampf.php',
type: "POST",
data: {
csrf_token,
id: dataId,
value,
inputType
},
success: function(response) {
if (!response.success) {
displayMsg(0, response.message || "Wert konnte nicht geändert werden");
return;
}
displayMsg(1, "Wert geändert");
},
error: () => {
displayMsg(0, "Fehler beim Ändern des Wertes");
}
});
});
$(document).on('click', '.activateSpan', async function() {
const $btn = $(this);
const dataId = $btn.data('id');
if (!await displayConfirm(`Das wechseln des Wettkampfes lädt alle Daten in der Kampfrichteransicht und RankLive neu.`)) return;
$.ajax({
url: '/intern/scripts/wettkaempfe/ajax-update-aktiv-wettkampf.php',
type: "POST",
data: {
csrf_token,
id: dataId
},
success: function(response) {
if (!response.success) {
displayMsg(0, response.message || "Aktiver Wettkampf konnte nicht geändert werden");
return;
}
const $tbody = $btn.closest('tbody');
// 1. Show ALL activation triggers in the table again
$tbody.find('.activateSpan').removeClass('hidden');
// 2. Hide only the trigger that was just clicked
$btn.addClass('hidden');
// 3. Find the single currently active swatch and turn it inactive
$tbody.find('.statusSpan.activeWk')
.removeClass('activeWk')
.addClass('inactiveWk')
.text('Inaktiv');
// 4. Find the swatch in the CURRENT row and turn it active
// (Using .closest('tr') ensures we look inside the same row)
$btn.closest('tr')
.find('.statusSpan')
.removeClass('inactiveWk')
.addClass('activeWk')
.text('Aktiv');
displayMsg(1, "Aktiver Wettkampf wurde gewechselt");
},
error: () => {
displayMsg(0, "Fehler beim Ändern des aktiven Wettkampfes");
}
});
});
</script>
</body>
</html>
<?php endif; ?>
-52
View File
@@ -1,52 +0,0 @@
$(document).ready(function () {
$('.menuLinksDiv>div').hover(function () {
const $this = $(this);
clearTimeout($this.data('hoverTimeout'));
$this.find('.dropdown').stop(true, false).slideDown(200);
$this.addClass('open');
}, function () {
const $this = $(this);
const timeout = setTimeout(function () {
$this.find('.dropdown').stop(true, false).slideUp(200);
$this.removeClass('open');
}, 200); // Small delay to prevent flickering
$this.data('hoverTimeout', timeout);
});
$('.sidebar>div').on('click', function (e) {
if ($(e.target).closest('.dropdown').length) {
return;
}
if (!$(this).hasClass('open')) {
$('.sidebar>div.open').removeClass('open').find('.dropdown').slideUp();
$(this).addClass('open');
$(this).find('.dropdown').slideDown();
} else {
$(this).find('.dropdown').slideUp();
$(this).removeClass('open');
}
});
$(document).on('click', function (e) {
// If sidebar is not active, do nothing
if ($(e.target).closest('.burgerMenuDiv').length) {
$('.burgerMenuDiv').toggleClass('active');
$('.sidebar').toggleClass('active');
return;
}
if (!$('.sidebar').hasClass('active')) {
return;
}
// If click is inside the sidebar or burger menu, do nothing
if (
$(e.target).closest('.sidebar').length ||
$(e.target).closest('.burgerMenuDiv').length
) {
return;
}
// Otherwise, close the sidebar
$('.sidebar').removeClass('active');
$('.burgerMenuDiv').removeClass('active');
});
});
@@ -1,24 +1,24 @@
<?php <?php
function generateIntersectCache($mysqli, $baseDir) { function generateIntersectCache($mysqli, $baseDir) {
global $db_tabelle_kategorien; global $tableProgramme;
global $db_tabelle_disziplinen; global $tableGeraete;
global $db_tabelle_wertungstypen; global $tableNotenBezeichnungen;
global $db_tabelle_tabellen_konfiguration; global $tableRankLiveConfigs;
global $db_tabelle_var; global $tableVar;
// 1. Fetch all unique IDs you need to loop through // 1. Fetch all unique IDs you need to loop through
$all_programms = array_column(db_select($mysqli, $db_tabelle_kategorien, '`id`', '`aktiv` = ?', [1]) ?? [], 'id'); $all_programms = array_column(db_select($mysqli, $tableProgramme, '`id`', '`aktiv` = ?', [1]) ?? [], 'id');
$all_geraete = array_column(db_select($mysqli, $db_tabelle_disziplinen, 'id') ?? [], 'id'); $all_geraete = array_column(db_select($mysqli, $tableGeraete, 'id') ?? [], 'id');
// 2. Fetch the static DB configs ONCE before starting the massive loop // 2. Fetch the static DB configs ONCE before starting the massive loop
$rang_note_id = db_get_variable($mysqli, $db_tabelle_var, ['rangNote']); $rang_note_id = db_get_variable($mysqli, $tableVar, ['rangNote']);
$json_geraet = db_get_var($mysqli, "SELECT `all_noten_json` FROM $db_tabelle_tabellen_konfiguration WHERE `type_slug` = ?", ["rankLive-geraet"]) ?: ''; $json_geraet = db_get_var($mysqli, "SELECT `all_noten_json` FROM $tableRankLiveConfigs WHERE `type_slug` = ?", ["rankLive-geraet"]) ?: '';
$json_overview = db_get_var($mysqli, "SELECT `all_noten_json` FROM $db_tabelle_tabellen_konfiguration WHERE `type_slug` = ?", ["rankLive-overview"]) ?: ''; $json_overview = db_get_var($mysqli, "SELECT `all_noten_json` FROM $tableRankLiveConfigs WHERE `type_slug` = ?", ["rankLive-overview"]) ?: '';
$noten_rank_live_geraet = json_decode($json_geraet, true) ?? []; $noten_rank_live_geraet = json_decode($json_geraet, true) ?? [];
$noten_rank_live_overview = json_decode($json_overview, true) ?? []; $noten_rank_live_overview = json_decode($json_overview, true) ?? [];
$notenConfig_db = db_select($mysqli, $db_tabelle_wertungstypen, '`berechnung_json`, `id`, `type`, `anzahl_laeufe_json`'); $notenConfig_db = db_select($mysqli, $tableNotenBezeichnungen, '`berechnung_json`, `id`, `type`, `anzahl_laeufe_json`');
$noten_config = array_column($notenConfig_db, null, 'id'); $noten_config = array_column($notenConfig_db, null, 'id');
$following_config_array = array_column($notenConfig_db, 'berechnung_json', 'id'); $following_config_array = array_column($notenConfig_db, 'berechnung_json', 'id');
$all_inputs_noten_ids = array_filter(array_column($notenConfig_db, 'type', 'id'), fn($type) => $type === 'input'); $all_inputs_noten_ids = array_filter(array_column($notenConfig_db, 'type', 'id'), fn($type) => $type === 'input');
+2 -2
View File
@@ -1,10 +1,10 @@
<?php <?php
function regenerate_noten_cache($mysqli, $db_tabelle_wertungstypen, $baseDir) { function regenerate_noten_cache($mysqli, $tableNotenBezeichnungen, $baseDir) {
if (!function_exists('db_select')) require $baseDir . '/../scripts/db/db-functions.php'; if (!function_exists('db_select')) require $baseDir . '/../scripts/db/db-functions.php';
$notenRechnerCache = new NotenRechner(); $notenRechnerCache = new NotenRechner();
$all = db_select($mysqli, $db_tabelle_wertungstypen, "id, default_value, pro_geraet, geraete_json, anzahl_laeufe_json, berechnung, nullstellen, display_string"); $all = db_select($mysqli, $tableNotenBezeichnungen, "id, default_value, pro_geraet, geraete_json, anzahl_laeufe_json, berechnung, nullstellen, zeige_in_rank_live, display_string");
$ascArrayDefaultValues = array_column($all, 'default_value', 'id'); $ascArrayDefaultValues = array_column($all, 'default_value', 'id');
$ascArrayProGeraet = array_column($all, 'pro_geraet', 'id'); $ascArrayProGeraet = array_column($all, 'pro_geraet', 'id');
+658
View File
@@ -1,3 +1,661 @@
<?php <?php
return array ( return array (
4 =>
array (
1 =>
array (
1 =>
array (
'SQL' => '(`note_bezeichnung_id` = ? AND `geraet_id` = ? AND `run_number` = ?) OR (`note_bezeichnung_id` = ? AND `geraet_id` = ? AND `run_number` = ?) OR (`note_bezeichnung_id` = ? AND `geraet_id` = ? AND `run_number` = ?) OR (`note_bezeichnung_id` = ? AND `geraet_id` = ? AND `run_number` = ?)',
'values' =>
array (
0 => 60,
1 => 1,
2 => 1,
3 => 70,
4 => 1,
5 => 1,
6 => 80,
7 => 0,
8 => 1,
9 => 40,
10 => 1,
11 => 1,
),
'intersect_noten' =>
array (
'60|1|1' =>
array (
'r' => 1,
'g_id' => 1,
'n_id' => 60,
),
'70|1|1' =>
array (
'r' => 1,
'g_id' => 1,
'n_id' => 70,
),
'80|0|1' =>
array (
'r' => 1,
'g_id' => 0,
'n_id' => 80,
),
'40|1|1' =>
array (
'r' => 1,
'g_id' => 1,
'n_id' => 40,
),
),
),
2 =>
array (
'SQL' => '(`note_bezeichnung_id` = ? AND `geraet_id` = ? AND `run_number` = ?) OR (`note_bezeichnung_id` = ? AND `geraet_id` = ? AND `run_number` = ?) OR (`note_bezeichnung_id` = ? AND `geraet_id` = ? AND `run_number` = ?) OR (`note_bezeichnung_id` = ? AND `geraet_id` = ? AND `run_number` = ?)',
'values' =>
array (
0 => 60,
1 => 1,
2 => 2,
3 => 70,
4 => 1,
5 => 1,
6 => 80,
7 => 0,
8 => 1,
9 => 40,
10 => 1,
11 => 2,
),
'intersect_noten' =>
array (
'60|1|2' =>
array (
'r' => 2,
'g_id' => 1,
'n_id' => 60,
),
'70|1|1' =>
array (
'r' => 1,
'g_id' => 1,
'n_id' => 70,
),
'80|0|1' =>
array (
'r' => 1,
'g_id' => 0,
'n_id' => 80,
),
'40|1|2' =>
array (
'r' => 2,
'g_id' => 1,
'n_id' => 40,
),
),
),
),
2 =>
array (
1 =>
array (
'SQL' => '(`note_bezeichnung_id` = ? AND `geraet_id` = ? AND `run_number` = ?) OR (`note_bezeichnung_id` = ? AND `geraet_id` = ? AND `run_number` = ?) OR (`note_bezeichnung_id` = ? AND `geraet_id` = ? AND `run_number` = ?) OR (`note_bezeichnung_id` = ? AND `geraet_id` = ? AND `run_number` = ?)',
'values' =>
array (
0 => 60,
1 => 2,
2 => 1,
3 => 70,
4 => 2,
5 => 1,
6 => 80,
7 => 0,
8 => 1,
9 => 40,
10 => 2,
11 => 1,
),
'intersect_noten' =>
array (
'60|2|1' =>
array (
'r' => 1,
'g_id' => 2,
'n_id' => 60,
),
'70|2|1' =>
array (
'r' => 1,
'g_id' => 2,
'n_id' => 70,
),
'80|0|1' =>
array (
'r' => 1,
'g_id' => 0,
'n_id' => 80,
),
'40|2|1' =>
array (
'r' => 1,
'g_id' => 2,
'n_id' => 40,
),
),
),
2 =>
array (
'SQL' => '(`note_bezeichnung_id` = ? AND `geraet_id` = ? AND `run_number` = ?) OR (`note_bezeichnung_id` = ? AND `geraet_id` = ? AND `run_number` = ?)',
'values' =>
array (
0 => 70,
1 => 2,
2 => 1,
3 => 80,
4 => 0,
5 => 1,
),
'intersect_noten' =>
array (
'70|2|1' =>
array (
'r' => 1,
'g_id' => 2,
'n_id' => 70,
),
'80|0|1' =>
array (
'r' => 1,
'g_id' => 0,
'n_id' => 80,
),
),
),
),
3 =>
array (
1 =>
array (
'SQL' => '(`note_bezeichnung_id` = ? AND `geraet_id` = ? AND `run_number` = ?) OR (`note_bezeichnung_id` = ? AND `geraet_id` = ? AND `run_number` = ?) OR (`note_bezeichnung_id` = ? AND `geraet_id` = ? AND `run_number` = ?) OR (`note_bezeichnung_id` = ? AND `geraet_id` = ? AND `run_number` = ?)',
'values' =>
array (
0 => 60,
1 => 3,
2 => 1,
3 => 70,
4 => 3,
5 => 1,
6 => 80,
7 => 0,
8 => 1,
9 => 40,
10 => 3,
11 => 1,
),
'intersect_noten' =>
array (
'60|3|1' =>
array (
'r' => 1,
'g_id' => 3,
'n_id' => 60,
),
'70|3|1' =>
array (
'r' => 1,
'g_id' => 3,
'n_id' => 70,
),
'80|0|1' =>
array (
'r' => 1,
'g_id' => 0,
'n_id' => 80,
),
'40|3|1' =>
array (
'r' => 1,
'g_id' => 3,
'n_id' => 40,
),
),
),
2 =>
array (
'SQL' => '(`note_bezeichnung_id` = ? AND `geraet_id` = ? AND `run_number` = ?) OR (`note_bezeichnung_id` = ? AND `geraet_id` = ? AND `run_number` = ?)',
'values' =>
array (
0 => 70,
1 => 3,
2 => 1,
3 => 80,
4 => 0,
5 => 1,
),
'intersect_noten' =>
array (
'70|3|1' =>
array (
'r' => 1,
'g_id' => 3,
'n_id' => 70,
),
'80|0|1' =>
array (
'r' => 1,
'g_id' => 0,
'n_id' => 80,
),
),
),
),
4 =>
array (
1 =>
array (
'SQL' => '(`note_bezeichnung_id` = ? AND `geraet_id` = ? AND `run_number` = ?) OR (`note_bezeichnung_id` = ? AND `geraet_id` = ? AND `run_number` = ?) OR (`note_bezeichnung_id` = ? AND `geraet_id` = ? AND `run_number` = ?) OR (`note_bezeichnung_id` = ? AND `geraet_id` = ? AND `run_number` = ?)',
'values' =>
array (
0 => 60,
1 => 4,
2 => 1,
3 => 70,
4 => 4,
5 => 1,
6 => 80,
7 => 0,
8 => 1,
9 => 40,
10 => 4,
11 => 1,
),
'intersect_noten' =>
array (
'60|4|1' =>
array (
'r' => 1,
'g_id' => 4,
'n_id' => 60,
),
'70|4|1' =>
array (
'r' => 1,
'g_id' => 4,
'n_id' => 70,
),
'80|0|1' =>
array (
'r' => 1,
'g_id' => 0,
'n_id' => 80,
),
'40|4|1' =>
array (
'r' => 1,
'g_id' => 4,
'n_id' => 40,
),
),
),
2 =>
array (
'SQL' => '(`note_bezeichnung_id` = ? AND `geraet_id` = ? AND `run_number` = ?) OR (`note_bezeichnung_id` = ? AND `geraet_id` = ? AND `run_number` = ?)',
'values' =>
array (
0 => 70,
1 => 4,
2 => 1,
3 => 80,
4 => 0,
5 => 1,
),
'intersect_noten' =>
array (
'70|4|1' =>
array (
'r' => 1,
'g_id' => 4,
'n_id' => 70,
),
'80|0|1' =>
array (
'r' => 1,
'g_id' => 0,
'n_id' => 80,
),
),
),
),
),
13 =>
array (
1 =>
array (
1 =>
array (
'SQL' => '(`note_bezeichnung_id` = ? AND `geraet_id` = ? AND `run_number` = ?) OR (`note_bezeichnung_id` = ? AND `geraet_id` = ? AND `run_number` = ?) OR (`note_bezeichnung_id` = ? AND `geraet_id` = ? AND `run_number` = ?) OR (`note_bezeichnung_id` = ? AND `geraet_id` = ? AND `run_number` = ?)',
'values' =>
array (
0 => 60,
1 => 1,
2 => 1,
3 => 70,
4 => 1,
5 => 1,
6 => 80,
7 => 0,
8 => 1,
9 => 40,
10 => 1,
11 => 1,
),
'intersect_noten' =>
array (
'60|1|1' =>
array (
'r' => 1,
'g_id' => 1,
'n_id' => 60,
),
'70|1|1' =>
array (
'r' => 1,
'g_id' => 1,
'n_id' => 70,
),
'80|0|1' =>
array (
'r' => 1,
'g_id' => 0,
'n_id' => 80,
),
'40|1|1' =>
array (
'r' => 1,
'g_id' => 1,
'n_id' => 40,
),
),
),
2 =>
array (
'SQL' => '(`note_bezeichnung_id` = ? AND `geraet_id` = ? AND `run_number` = ?) OR (`note_bezeichnung_id` = ? AND `geraet_id` = ? AND `run_number` = ?) OR (`note_bezeichnung_id` = ? AND `geraet_id` = ? AND `run_number` = ?) OR (`note_bezeichnung_id` = ? AND `geraet_id` = ? AND `run_number` = ?)',
'values' =>
array (
0 => 60,
1 => 1,
2 => 2,
3 => 70,
4 => 1,
5 => 1,
6 => 80,
7 => 0,
8 => 1,
9 => 40,
10 => 1,
11 => 2,
),
'intersect_noten' =>
array (
'60|1|2' =>
array (
'r' => 2,
'g_id' => 1,
'n_id' => 60,
),
'70|1|1' =>
array (
'r' => 1,
'g_id' => 1,
'n_id' => 70,
),
'80|0|1' =>
array (
'r' => 1,
'g_id' => 0,
'n_id' => 80,
),
'40|1|2' =>
array (
'r' => 2,
'g_id' => 1,
'n_id' => 40,
),
),
),
),
2 =>
array (
1 =>
array (
'SQL' => '(`note_bezeichnung_id` = ? AND `geraet_id` = ? AND `run_number` = ?) OR (`note_bezeichnung_id` = ? AND `geraet_id` = ? AND `run_number` = ?) OR (`note_bezeichnung_id` = ? AND `geraet_id` = ? AND `run_number` = ?) OR (`note_bezeichnung_id` = ? AND `geraet_id` = ? AND `run_number` = ?)',
'values' =>
array (
0 => 60,
1 => 2,
2 => 1,
3 => 70,
4 => 2,
5 => 1,
6 => 80,
7 => 0,
8 => 1,
9 => 40,
10 => 2,
11 => 1,
),
'intersect_noten' =>
array (
'60|2|1' =>
array (
'r' => 1,
'g_id' => 2,
'n_id' => 60,
),
'70|2|1' =>
array (
'r' => 1,
'g_id' => 2,
'n_id' => 70,
),
'80|0|1' =>
array (
'r' => 1,
'g_id' => 0,
'n_id' => 80,
),
'40|2|1' =>
array (
'r' => 1,
'g_id' => 2,
'n_id' => 40,
),
),
),
2 =>
array (
'SQL' => '(`note_bezeichnung_id` = ? AND `geraet_id` = ? AND `run_number` = ?) OR (`note_bezeichnung_id` = ? AND `geraet_id` = ? AND `run_number` = ?)',
'values' =>
array (
0 => 70,
1 => 2,
2 => 1,
3 => 80,
4 => 0,
5 => 1,
),
'intersect_noten' =>
array (
'70|2|1' =>
array (
'r' => 1,
'g_id' => 2,
'n_id' => 70,
),
'80|0|1' =>
array (
'r' => 1,
'g_id' => 0,
'n_id' => 80,
),
),
),
),
3 =>
array (
1 =>
array (
'SQL' => '(`note_bezeichnung_id` = ? AND `geraet_id` = ? AND `run_number` = ?) OR (`note_bezeichnung_id` = ? AND `geraet_id` = ? AND `run_number` = ?) OR (`note_bezeichnung_id` = ? AND `geraet_id` = ? AND `run_number` = ?) OR (`note_bezeichnung_id` = ? AND `geraet_id` = ? AND `run_number` = ?)',
'values' =>
array (
0 => 60,
1 => 3,
2 => 1,
3 => 70,
4 => 3,
5 => 1,
6 => 80,
7 => 0,
8 => 1,
9 => 40,
10 => 3,
11 => 1,
),
'intersect_noten' =>
array (
'60|3|1' =>
array (
'r' => 1,
'g_id' => 3,
'n_id' => 60,
),
'70|3|1' =>
array (
'r' => 1,
'g_id' => 3,
'n_id' => 70,
),
'80|0|1' =>
array (
'r' => 1,
'g_id' => 0,
'n_id' => 80,
),
'40|3|1' =>
array (
'r' => 1,
'g_id' => 3,
'n_id' => 40,
),
),
),
2 =>
array (
'SQL' => '(`note_bezeichnung_id` = ? AND `geraet_id` = ? AND `run_number` = ?) OR (`note_bezeichnung_id` = ? AND `geraet_id` = ? AND `run_number` = ?)',
'values' =>
array (
0 => 70,
1 => 3,
2 => 1,
3 => 80,
4 => 0,
5 => 1,
),
'intersect_noten' =>
array (
'70|3|1' =>
array (
'r' => 1,
'g_id' => 3,
'n_id' => 70,
),
'80|0|1' =>
array (
'r' => 1,
'g_id' => 0,
'n_id' => 80,
),
),
),
),
4 =>
array (
1 =>
array (
'SQL' => '(`note_bezeichnung_id` = ? AND `geraet_id` = ? AND `run_number` = ?) OR (`note_bezeichnung_id` = ? AND `geraet_id` = ? AND `run_number` = ?) OR (`note_bezeichnung_id` = ? AND `geraet_id` = ? AND `run_number` = ?) OR (`note_bezeichnung_id` = ? AND `geraet_id` = ? AND `run_number` = ?)',
'values' =>
array (
0 => 60,
1 => 4,
2 => 1,
3 => 70,
4 => 4,
5 => 1,
6 => 80,
7 => 0,
8 => 1,
9 => 40,
10 => 4,
11 => 1,
),
'intersect_noten' =>
array (
'60|4|1' =>
array (
'r' => 1,
'g_id' => 4,
'n_id' => 60,
),
'70|4|1' =>
array (
'r' => 1,
'g_id' => 4,
'n_id' => 70,
),
'80|0|1' =>
array (
'r' => 1,
'g_id' => 0,
'n_id' => 80,
),
'40|4|1' =>
array (
'r' => 1,
'g_id' => 4,
'n_id' => 40,
),
),
),
2 =>
array (
'SQL' => '(`note_bezeichnung_id` = ? AND `geraet_id` = ? AND `run_number` = ?) OR (`note_bezeichnung_id` = ? AND `geraet_id` = ? AND `run_number` = ?)',
'values' =>
array (
0 => 70,
1 => 4,
2 => 1,
3 => 80,
4 => 0,
5 => 1,
),
'intersect_noten' =>
array (
'70|4|1' =>
array (
'r' => 1,
'g_id' => 4,
'n_id' => 70,
),
'80|0|1' =>
array (
'r' => 1,
'g_id' => 0,
'n_id' => 80,
),
),
),
),
),
); );
+10
View File
@@ -119,6 +119,16 @@ return [
60 => 2, 60 => 2,
70 => 2, 70 => 2,
80 => 2, 80 => 2,
),
'zeige_in_rank_live' => array (
10 => 1,
11 => 1,
20 => 1,
40 => 1,
50 => 1,
60 => 1,
70 => 1,
80 => 1,
), ),
'display_string' => array ( 'display_string' => array (
10 => '${note}', 10 => '${note}',
+4 -4
View File
@@ -131,9 +131,9 @@ function db_get_var($mysqli, $sql, $params = []) {
return $value; return $value;
} }
function db_get_variable($mysqli, $db_tabelle_var, $params = []) { function db_get_variable($mysqli, $tableVar, $params = []) {
if (count($params) !== 1) return null; if (count($params) !== 1) return null;
$stmt = $mysqli->prepare("SELECT `value` FROM $db_tabelle_var WHERE `name` = ?"); $stmt = $mysqli->prepare("SELECT `value` FROM $tableVar WHERE `name` = ?");
$stmt->bind_param("s", ...$params); $stmt->bind_param("s", ...$params);
$stmt->execute(); $stmt->execute();
$stmt->bind_result($value); $stmt->bind_result($value);
@@ -142,9 +142,9 @@ function db_get_variable($mysqli, $db_tabelle_var, $params = []) {
return $value; return $value;
} }
function db_update_variable($mysqli, $db_tabelle_var, $params = ['name', 'value']) { function db_update_variable($mysqli, $tableVar, $params = ['name', 'value']) {
if (count($params) !== 2) return false; if (count($params) !== 2) return false;
$stmt = $mysqli->prepare("INSERT INTO $db_tabelle_var (`name`, `value`) VALUES (?, ?) ON DUPLICATE KEY UPDATE `value` = VALUES(`value`)"); $stmt = $mysqli->prepare("INSERT INTO $tableVar (`name`, `value`) VALUES (?, ?) ON DUPLICATE KEY UPDATE `value` = VALUES(`value`)");
$stmt->bind_param("ss", ...$params); $stmt->bind_param("ss", ...$params);
$stmt->execute(); $stmt->execute();
$stmt->close(); $stmt->close();
+29 -23
View File
@@ -29,28 +29,32 @@ try {
]); ]);
} }
$prefix = $_ENV['DB_PREFIX'] ?? '';
$tableDefinitions = [ $tableDefinitions = [
'teilnehmende' => 'DB_TABLE_TEILNEHMENDE', 'Teilnehmende' => 'DB_TABLE_TEILNEHMENDE',
'verbuchte_startgebueren' => 'DB_TABLE_VERBUCHTE_STARTGEBUEREN', 'Orders' => 'DB_TABLE_ORDERS',
'warenkorb_startgebueren' => 'DB_TABLE_WARENKORB_STARTGEBUEREN', 'BasketItems' => 'DB_TABLE_BASKET_ITEMS',
'var' => 'DB_TABLE_VARIABLES', 'Var' => 'DB_TABLE_VARIABLES',
'einmal_links' => 'DB_EINMAL_LINKS', 'OTL' => 'DB_TABLE_OTL',
'kategorien' => 'DB_TABLE_KATEGORIEN', 'KrProtokoll' => 'DB_TABLE_KR_PROTOKOLL',
'intern_benutzende' => 'DB_TABLE_INTERN_BENUTZENDE', 'Programme' => 'DB_TABLE_PROGRAMME',
'vereine' => 'DB_TABLE_VEREINE', 'InternUsers' => 'DB_TABLE_INTERN_USERS',
'gruppen' => 'DB_TABLE_GRUPPEN', 'Vereine' => 'DB_TABLE_VEREINE',
'teilnehmende_gruppen' => 'DB_TABLE_TEILNEHMENDE_GRUPPEN', 'Abt' => 'DB_TABLE_ABTEILUNGEN',
'disziplinen' => 'DB_TABLE_DISZIPLINEN', 'TeilnehmendeAbt' => 'DB_TABLE_TEILNEHMENDE_ABTEILUNGEN',
'audiofiles' => 'DB_TABLE_AUDIOFILES', 'Geraete' => 'DB_TABLE_GERAETE',
'teilnehmende_audiofiles' => 'DB_TABLE_TEILNEHMENDE_AUDIOFILES', 'Audiofiles' => 'DB_TABLE_AUDIOFILES',
'wertungen' => 'DB_TABLE_WERTUNGEN', 'TeilnehmendeAudiofiles' => 'DB_TABLE_TEILNEHMENDE_AUDIOFILES',
'wertungen_log' => 'DB_TABLE_WERTUNGEN_LOG', 'Noten' => 'DB_TABLE_NOTEN',
'wertungstypen' => 'DB_TABLE_WERTUNGSTYPEN', 'NotenChanges' => 'DB_TABLE_NOTEN_CHANGES',
'verbuchte_startgebueren_log' => 'DB_TABLE_VERBUCHTE_STARTGEBUEREN_LOG', 'NotenBezeichnungen' => 'DB_TABLE_NOTEN_BEZEICHNUNGEN',
'zeitplan_types' => 'DB_TABLE_ZEITPLAN_TYPES', 'OrdersLog' => 'DB_TABLE_ORDERS_LOG',
'gruppen_zeiten' => 'DB_TABLE_GRUPPEN_ZEITEN', 'ZeitplanTypes' => 'DB_TABLE_ZEITPLAN_TYPES',
'tabellen_konfiguration' => 'DB_TABLE_TABELLEN_KONFIGURATION', 'AbtZeiten' => 'DB_TABLE_ABTEILUNGEN_ZEITEN',
'wettkaempfe' => 'DB_TABLE_WETTKAEMPFE' 'RankLiveConfigs' => 'DB_TABLE_RANKLIVE_CONFIGS'
]; ];
@@ -58,7 +62,9 @@ foreach ($tableDefinitions as $baseName => $envVarKey) {
$rawTableName = $_ENV[$envVarKey] ?? ''; $rawTableName = $_ENV[$envVarKey] ?? '';
$variableName = 'db_tabelle_' . $baseName; $fullTableName = $prefix . $rawTableName;
$$variableName = $rawTableName; $variableName = 'table' . ucfirst($baseName);
$$variableName = $fullTableName;
} }
+2 -2
View File
@@ -31,7 +31,7 @@ if ($type === 'kr'){
require __DIR__ . '/../../composer/vendor/autoload.php'; require __DIR__ . '/../../composer/vendor/autoload.php';
$envFile = realpath(__DIR__ . '/../../config/.env.db-wkvs-user'); $envFile = realpath(__DIR__ . '/../../config/.env.db');
if ($envFile === false) { if ($envFile === false) {
http_response_code(500); http_response_code(500);
@@ -45,7 +45,7 @@ if ($envFile === false) {
try { try {
$envDir = dirname($envFile); $envDir = dirname($envFile);
$dotenv = Dotenv::createImmutable($envDir, '.env.db-wkvs-user'); $dotenv = Dotenv::createImmutable($envDir, '.env.db');
$dotenv->load(); $dotenv->load();
} catch (Throwable $e) { } catch (Throwable $e) {
@@ -4,7 +4,7 @@ $userid = intval($_SESSION['user_id_kampfrichter'] ?? 0);
$arrayfreigaben = []; $arrayfreigaben = [];
if ($userid > 0) { if ($userid > 0) {
$stmt = $mysqli->prepare("SELECT freigabe, username FROM $db_tabelle_intern_benutzende WHERE id = ?"); $stmt = $mysqli->prepare("SELECT freigabe, username FROM $tableInternUsers WHERE id = ?");
$stmt->bind_param("s", $userid); $stmt->bind_param("s", $userid);
$stmt->execute(); $stmt->execute();
$result = $stmt->get_result(); $result = $stmt->get_result();
+5 -5
View File
@@ -9,7 +9,7 @@ if (isset($_POST['prev_abt_submit'])) {
$value = $aktabt; $value = $aktabt;
if ($value > 1){ if ($value > 1){
$value -= 1; $value -= 1;
db_update_variable($mysqli, $db_tabelle_var, ['wk_panel_current_abt', $value]); db_update_variable($mysqli, $tableVar, ['wk_panel_current_abt', $value]);
} }
header("Location: /intern/kampfrichter"); header("Location: /intern/kampfrichter");
exit; exit;
@@ -18,11 +18,11 @@ if (isset($_POST['prev_abt_submit'])) {
if (isset($_POST['next_abt_submit'])) { if (isset($_POST['next_abt_submit'])) {
verify_csrf(); verify_csrf();
$value = $aktabt; $value = $aktabt;
$maxvalue = (int) (db_get_var($mysqli, "SELECT `order_index` FROM $db_tabelle_gruppen ORDER BY `order_index` DESC LIMIT 1") ?? 1); $maxvalue = (int) (db_get_var($mysqli, "SELECT name FROM $tableAbt ORDER BY name DESC LIMIT 1") ?? 1);
if ($value < $maxvalue){ if ($value < $maxvalue){
$value += 1; $value += 1;
db_update_variable($mysqli, $db_tabelle_var, ['wk_panel_current_abt', $value]); db_update_variable($mysqli, $tableVar, ['wk_panel_current_abt', $value]);
} }
header("Location: /intern/kampfrichter"); header("Location: /intern/kampfrichter");
exit; exit;
@@ -33,7 +33,7 @@ if (isset($_POST['prev_subabt_admin_submit']) && $isAdmin) {
$value = $akt_subabt_admin; $value = $akt_subabt_admin;
if ($value > 1){ if ($value > 1){
$value -= 1; $value -= 1;
db_update_variable($mysqli, $db_tabelle_var, ['wk_panel_current_subabt_admin', $value]); db_update_variable($mysqli, $tableVar, ['wk_panel_current_subabt_admin', $value]);
$_SESSION['ws_Message_json'] = json_encode(['type' => 'UPDATE_RANKLIVE_C_SUBABT', 'payload' => []]); $_SESSION['ws_Message_json'] = json_encode(['type' => 'UPDATE_RANKLIVE_C_SUBABT', 'payload' => []]);
} }
header("Location: /intern/kampfrichter"); header("Location: /intern/kampfrichter");
@@ -47,7 +47,7 @@ if (isset($_POST['next_subabt_admin_submit']) && $isAdmin) {
if ($value < $max_value_admin_subabt){ if ($value < $max_value_admin_subabt){
$value += 1; $value += 1;
db_update_variable($mysqli, $db_tabelle_var, ['wk_panel_current_subabt_admin', $value]); db_update_variable($mysqli, $tableVar, ['wk_panel_current_subabt_admin', $value]);
$_SESSION['ws_Message_json'] = json_encode(['type' => 'UPDATE_RANKLIVE_C_SUBABT', 'payload' => []]); $_SESSION['ws_Message_json'] = json_encode(['type' => 'UPDATE_RANKLIVE_C_SUBABT', 'payload' => []]);
} }
+1 -1
View File
@@ -50,7 +50,7 @@ if ($_SESSION['lockout_time_'. $logintype] > time()) {
$password = trim($_POST['access_passcode']); $password = trim($_POST['access_passcode']);
// Prepare statement // Prepare statement
$stmt = $guest->prepare("SELECT * FROM $db_tabelle_intern_benutzende WHERE username = ? AND login_active = ? LIMIT 1"); $stmt = $guest->prepare("SELECT * FROM $tableInternUsers WHERE username = ? AND login_active = ? LIMIT 1");
$loginActive = 1; $loginActive = 1;
$stmt->bind_param("ss", $username, $loginActive); $stmt->bind_param("ss", $username, $loginActive);
$stmt->execute(); $stmt->execute();
@@ -1,16 +0,0 @@
<header>
<a class="rankLiveHeaderTitle" href="/RankLive">
RankLive
</a>
<div class="menuLinksDiv">
<?php include __DIR__ ."/rankLive-menu.php"?>
</div>
<div class="burgerMenuDiv">
<div class="burgerMenuLine"></div>
<div class="burgerMenuLine"></div>
<div class="burgerMenuLine"></div>
</div>
</header>
<div class="sidebar">
<?php include __DIR__ ."/rankLive-menu.php"?>
</div>
-55
View File
@@ -1,55 +0,0 @@
<?php
if (!isset($guest) || !$guest instanceof mysqli) {
require $_SERVER['DOCUMENT_ROOT'] . "/../scripts/db/db-verbindung-script-guest.ph";
}
$result = $guest->query("SELECT `programm` FROM $db_tabelle_kategorien WHERE `aktiv` = '1'");
$yearsGalleryArray = [];
while ($row = $result->fetch_assoc()) {
$kategorien[str_replace(' ', '-', strtolower(htmlspecialchars($row['programm'])))] = htmlspecialchars($row['programm']);
}
?>
<a href="/RankLive/live" class="menu-item">
<svg class="menu-icon" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="10"/>
<polygon points="10 8 16 12 10 16 10 8"/>
</svg>
Aktueller Durchgang
</a>
<a href="/RankLive/" class="menu-item">
<svg class="menu-icon" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<line x1="18" y1="20" x2="18" y2="10"/>
<line x1="12" y1="20" x2="12" y2="4"/>
<line x1="6" y1="20" x2="6" y2="14"/>
</svg>
Alle Ergebnisse
</a>
<?php if ($kategorien !== []): ?>
<div>
<span class="menu-item">
<svg class="menu-icon" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<rect x="3" y="3" width="7" height="7"/>
<rect x="14" y="3" width="7" height="7"/>
<rect x="14" y="14" width="7" height="7"/>
<rect x="3" y="14" width="7" height="7"/>
</svg>
Kategorien
<svg class="menu-chevron" viewBox="0 0 16 16" width="14" height="14" aria-hidden="true" focusable="false" style="margin-left: auto;">
<path fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" d="M2.5 5.5L8 11l5.5-5.5"></path>
</svg>
</span>
<div class="dropdown">
<?php foreach ($kategorien as $kat_slug => $kat_name) : ?>
<a href="/RankLive/programm/<?= $kat_slug ?>"><?= $kat_name ?></a>
<?php endforeach; ?>
</div>
</div>
<?php endif; ?>
+3 -3
View File
@@ -33,9 +33,9 @@ try {
// Create a Redis connection // Create a Redis connection
$redis = new PredisClient([ $redis = new PredisClient([
'scheme' => 'tcp', 'scheme' => 'tcp',
'host' => $_ENV['REDIS_HOST'], 'host' => $_ENV['REDDIS_HOST'],
'port' => $_ENV['REDIS_PORT'], 'port' => $_ENV['REDDIS_PORT'],
'password' => $_ENV['REDIS_PASSWORD'] 'password' => $_ENV['REDDIS_PASSWORD']
]); ]);
/** /**
-154
View File
@@ -1,154 +0,0 @@
<?php
function update_teilnehmende($ids_json = '', $old_status, $new_status, bool $ids_are_array = false) {
global $mysqli;
global $db_tabelle_teilnehmende;
global $db_tabelle_kategorien;
// 1. Quick exits
if (intval($old_status) === intval($new_status)) return false;
if (!$ids_are_array) {
$programm_ids = json_decode($ids_json, true) ?? [];
} else {
if (!is_array($ids_json)) return false;
$programm_ids = $ids_json;
}
if (count($programm_ids) < 1) return false;
// 2. Extract IDs (which are the keys of the associative array)
$ids = array_keys($programm_ids);
$tparams = implode(", ", array_fill(0, count($ids), "?"));
// 3. Fetch current paid amounts
$all_teilnehmede_stmt = $mysqli->prepare("SELECT `id`, `betrag_bezahlt`, `programm` FROM $db_tabelle_teilnehmende WHERE `id` IN ($tparams)");
$all_teilnehmede_stmt->bind_param(str_repeat("i", count($ids)), ...$ids);
$all_teilnehmede_stmt->execute();
$all_teilnehmede_res = $all_teilnehmede_stmt->get_result();
$all_teilnehmede = $all_teilnehmede_res->fetch_all(MYSQLI_ASSOC);
$all_teilnehmede_stmt->close();
$all_preise_db = db_select($mysqli, $db_tabelle_kategorien, "`programm`, `preis`");
$all_preise = array_column($all_preise_db, 'preis', 'programm');
// Creates a clean map of [id => betrag_bezahlt]
$all_teilnehmede_indexed = array_column($all_teilnehmede, 'betrag_bezahlt', 'id');
$all_teilnehmede_indexed_kat = array_column($all_teilnehmede, 'programm', 'id');
// 4. Prepare the update statement ONCE
$update_teiln_stmt = $mysqli->prepare("UPDATE $db_tabelle_teilnehmende SET `betrag_bezahlt` = ?, `bezahlt` = ? WHERE `id` = ?");
// Bind variables by reference once
$n_preis = 0.0;
$normal_order_status = match ($new_status) {
1 => 3,
2 => 4,
3 => 1,
default => 1
};
$status_to_set = $normal_order_status;
$s_teiln_id = 0;
$update_teiln_stmt->bind_param("dii", $n_preis, $status_to_set, $s_teiln_id);
// 5. Loop through and execute updates
foreach ($programm_ids as $id_key => $preis) {
$s_teiln_id = (int) $id_key;
// FIX: Check directly against the flat ID key map
if (!isset($all_teilnehmede_indexed[$s_teiln_id])) continue;
$eff_preis = 0.0;
if (intval($new_status) === 2) {
$eff_preis = (float) $preis;
} elseif (intval($new_status) !== 2 && intval($old_status) === 2) {
$eff_preis = ((float) $preis) * -1;
}
// Calculate the new absolute value
$n_preis = max(((float) $all_teilnehmede_indexed[$s_teiln_id]) + $eff_preis, 0);
if ($normal_order_status === 1) {
$kat = $all_teilnehmede_indexed_kat[$s_teiln_id];
$preis_kat = (float) $all_preise[$kat] ?? PHP_INT_MAX;
if ($n_preis >= $preis_kat) {
$status_to_set = 4;
} elseif ($n_preis !== 0.00) {
$status_to_set = 2;
} else {
$status_to_set = 1;
}
} else {
$status_to_set = $normal_order_status;
}
// Execute using the bound references
$update_teiln_stmt->execute();
}
$update_teiln_stmt->close();
return true;
}
function update_konten_vereine($benutzte_konten = '', $old_status, $new_status, bool $ids_are_array = false): bool {
global $mysqli, $db_tabelle_vereine;
$int_old_status = (int) $old_status;
$int_new_status = (int) $new_status;
// 1. Quick exit: No status change
if ($int_old_status === $int_new_status) {
return false;
}
// 2. Validate status transitions (Fixed $old_status === 0 || $old_status === 1 bug)
$isValidTransition =
(in_array($int_old_status, [0, 1, 3]) && $int_new_status === 2) ||
(in_array($int_new_status, [0, 1, 3]) && $int_old_status === 2) ||
($int_old_status === 0 && $int_new_status === 1);
if (!$isValidTransition) {
return false;
}
if (!$ids_are_array) {
$benutzte_konten_array = json_decode($benutzte_konten, true) ?? [];
} else {
if (!is_array($benutzte_konten)) {
return false;
}
$benutzte_konten_array = $benutzte_konten;
}
if (empty($benutzte_konten_array)) {
return false;
}
$faktor = ($int_new_status === 3) ? 1 : -1;
$stmt = $mysqli->prepare("UPDATE `$db_tabelle_vereine` SET `konto` = `konto` + ? WHERE `verein` = ?");
$konto_change = 0.0;
$verein = '';
$stmt->bind_param("ds", $konto_change, $verein);
foreach ($benutzte_konten_array as $verein_key => $konto_change_absolute) {
$verein = (string) $verein_key;
$konto_change = (float) $konto_change_absolute * $faktor;
if ($konto_change == 0) {
continue;
}
$stmt->execute();
}
$stmt->close();
return true;
}
+2 -2
View File
@@ -38,11 +38,11 @@ function connectToRedis(): bool
$redis = new Redis(); $redis = new Redis();
if (!$redis->connect($_ENV['REDIS_HOST'], $_ENV['REDIS_PORT'])) { if (!$redis->connect($_ENV['REDDIS_HOST'], $_ENV['REDDIS_PORT'])) {
return false; return false;
} }
if (!$redis->auth($_ENV['REDIS_PASSWORD'])) { if (!$redis->auth($_ENV['REDDIS_PASSWORD'])) {
return false; return false;
} }
return true; return true;
+2 -2
View File
@@ -77,7 +77,7 @@ function check_user_permission(string $type, bool $return = false, bool $display
} else { } else {
if ($display) { if ($display) {
http_response_code(403); http_response_code(403);
include __DIR__ . "/../html/error-pages/403.html"; include __DIR__ . "/../www/error-pages/403.html";
exit; exit;
} else { } else {
http_response_code(403); http_response_code(403);
@@ -98,7 +98,7 @@ function check_user_permission(string $type, bool $return = false, bool $display
} else { } else {
if ($display) { if ($display) {
http_response_code(403); http_response_code(403);
include __DIR__ . "/../html/error-pages/403.html"; include __DIR__ . "/../www/error-pages/403.html";
exit; exit;
} else { } else {
http_response_code(403); http_response_code(403);
+3 -5
View File
@@ -32,14 +32,13 @@ $icons = [
'rechnungen' => '<svg viewBox="0 0 24 24"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="16" y1="13" x2="8" y2="13"/><line x1="16" y1="17" x2="8" y2="17"/><polyline points="10 9 9 9 8 9"/></svg>', 'rechnungen' => '<svg viewBox="0 0 24 24"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="16" y1="13" x2="8" y2="13"/><line x1="16" y1="17" x2="8" y2="17"/><polyline points="10 9 9 9 8 9"/></svg>',
'logindata' => '<svg viewBox="0 0 24 24"><rect x="3" y="11" width="18" height="11" rx="2" ry="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/></svg>', 'logindata' => '<svg viewBox="0 0 24 24"><rect x="3" y="11" width="18" height="11" rx="2" ry="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/></svg>',
'displaycontrol' => '<svg viewBox="0 0 24 24"><rect x="2" y="3" width="20" height="14" rx="2" ry="2"/><line x1="8" y1="21" x2="16" y2="21"/><line x1="12" y1="17" x2="12" y2="21"/></svg>', 'displaycontrol' => '<svg viewBox="0 0 24 24"><rect x="2" y="3" width="20" height="14" rx="2" ry="2"/><line x1="8" y1="21" x2="16" y2="21"/><line x1="12" y1="17" x2="12" y2="21"/></svg>',
//'kalender' => '<svg viewBox="0 0 24 24"><rect x="3" y="4" width="18" height="18" rx="2" ry="2"/><line x1="16" y1="2" x2="16" y2="6"/><line x1="8" y1="2" x2="8" y2="6"/><line x1="3" y1="10" x2="21" y2="10"/></svg>', 'kalender' => '<svg viewBox="0 0 24 24"><rect x="3" y="4" width="18" height="18" rx="2" ry="2"/><line x1="16" y1="2" x2="16" y2="6"/><line x1="8" y1="2" x2="8" y2="6"/><line x1="3" y1="10" x2="21" y2="10"/></svg>',
'rankLive' => '<svg viewBox="0 0 24 24"><path d="M12 4h6a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h6"/><line x1="9" y1="9" x2="15" y2="9"/><line x1="9" y1="13" x2="15" y2="13"/><line x1="9" y1="17" x2="15" y2="17"/></svg>', 'rankLive' => '<svg viewBox="0 0 24 24"><path d="M12 4h6a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h6"/><line x1="9" y1="9" x2="15" y2="9"/><line x1="9" y1="13" x2="15" y2="13"/><line x1="9" y1="17" x2="15" y2="17"/></svg>',
'wettkaempfe' => '<svg viewBox="0 0 24 24"><path d="M12 4h6a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h6"/><line x1="9" y1="9" x2="15" y2="9"/><line x1="9" y1="13" x2="15" y2="13"/><line x1="9" y1="17" x2="15" y2="17"/></svg>',
'riegeneinteilung' => '<svg viewBox="0 0 24 24"><path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2"/><rect x="8" y="2" width="8" height="4" rx="1" ry="1"/><line x1="9" y1="12" x2="15" y2="12"/><line x1="9" y1="16" x2="15" y2="16"/></svg>', 'riegeneinteilung' => '<svg viewBox="0 0 24 24"><path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2"/><rect x="8" y="2" width="8" height="4" rx="1" ry="1"/><line x1="9" y1="12" x2="15" y2="12"/><line x1="9" y1="16" x2="15" y2="16"/></svg>',
'einstellungen' => '<svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="3"></circle><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"></path></svg>', 'einstellungen' => '<svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="3"></circle><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"></path></svg>',
]; ];
if (isset($mysqli) && isset($db_tabelle_intern_benutzende)) { if (isset($mysqli) && isset($tableInternUsers)) {
if ($currentPage === 'kampfrichter' && checkIfUserHasSessionId('kampfrichter')): if ($currentPage === 'kampfrichter' && checkIfUserHasSessionId('kampfrichter')):
$userDispId = intval($_SESSION['user_id_kampfrichter']); $userDispId = intval($_SESSION['user_id_kampfrichter']);
@@ -49,7 +48,7 @@ if (isset($mysqli) && isset($db_tabelle_intern_benutzende)) {
$userDispId = intval($_SESSION['user_id_wk_leitung']); $userDispId = intval($_SESSION['user_id_wk_leitung']);
endif; endif;
$sql = "SELECT `username`, `freigabe` FROM $db_tabelle_intern_benutzende WHERE id = ?"; $sql = "SELECT `username`, `freigabe` FROM $tableInternUsers WHERE id = ?";
$stmt = $mysqli->prepare($sql); $stmt = $mysqli->prepare($sql);
$stmt->bind_param('i', $userDispId); $stmt->bind_param('i', $userDispId);
@@ -87,7 +86,6 @@ if ($isWKL) {
$links[] = ['key' => 'rankLive', 'label' => 'Rank Live', 'url' => '/intern/wk-leitung/rank-live', 'freigaben' => false]; $links[] = ['key' => 'rankLive', 'label' => 'Rank Live', 'url' => '/intern/wk-leitung/rank-live', 'freigaben' => false];
$links[] = ['key' => 'riegeneinteilung', 'label' => 'Riegeneinteilung', 'url' => '/intern/wk-leitung/riegeneinteilung', 'freigaben' => false]; $links[] = ['key' => 'riegeneinteilung', 'label' => 'Riegeneinteilung', 'url' => '/intern/wk-leitung/riegeneinteilung', 'freigaben' => false];
$links[] = ['key' => 'einstellungen', 'label' => 'Einstellungen', 'url' => '/intern/wk-leitung/einstellungen', 'freigaben' => false]; $links[] = ['key' => 'einstellungen', 'label' => 'Einstellungen', 'url' => '/intern/wk-leitung/einstellungen', 'freigaben' => false];
$links[] = ['key' => 'wettkaempfe', 'label' => 'Wettkämpfe', 'url' => '/intern/wk-leitung/wettkaempfe', 'freigaben' => false];
} }
function checkIfUserHasSessionId($type) : bool { function checkIfUserHasSessionId($type) : bool {
+93 -86
View File
@@ -1,77 +1,95 @@
<?php <?php
function get_kat_price_diff(array $old_data, string $new_kat): array {
global $preise_kats;
global $vereine_plus_array;
$return_array = []; if (isset($_POST['apply_bulk_action'])) {
verify_csrf();
$old_kat = $old_data['programm'] ?? ''; if (empty($_POST['turnerin_ids']) || !is_array($_POST['turnerin_ids'])) {
$verein = $old_data['verein'] ?? ''; $_SESSION['form_message'] = 'Keine ' . $personMehrzahl . ' für die Aktion ausgewählt.';
$bezahlt = (int) ($old_data['bezahlt'] ?? 1); $_SESSION['form_message_type'] = 0;
} elseif (!isset($_POST['bulk_action_programm']) && !isset($_POST['bulk_action_bezahlt'])) {
if ($old_kat === '' || $old_kat === $new_kat || !in_array($bezahlt, [2, 4], true) || !isset($preise_kats[$new_kat])) { $_SESSION['form_message'] = 'Kein Programm für die Massenänderung ausgewählt.';
return $return_array; $_SESSION['form_message_type'] = 0;
}
$betrag_bezahlt = (float) ($old_data['betrag_bezahlt'] ?? 0.00);
$new_kat_preis = (float) $preise_kats[$new_kat];
if ($new_kat_preis > $betrag_bezahlt) {
$return_array['bezahlt'] = ($betrag_bezahlt === 0.00) ? 1 : 2;
} elseif ($new_kat_preis < $betrag_bezahlt) {
$return_array['bezahlt'] = 4;
$vereine_plus_array[$verein] = ((float) ($vereine_plus_array[$verein] ?? 0.00)) + $betrag_bezahlt - $new_kat_preis;
} else { } else {
$return_array['bezahlt'] = 4; $ids_to_update = array_map('intval', $_POST['turnerin_ids'] ?? []);
} $new_programm = isset($_POST['bulk_action_programm']) ? trim($_POST['bulk_action_programm']) : '';
$bezahlt_update = $_POST['bulk_action_bezahlt'] ?? null;
return $return_array; if (empty($ids_to_update)) {
} $_SESSION['form_message'] = 'Keine Einträge ausgewählt.';
$_SESSION['form_message_type'] = 0;
function update_verein_balances(array $vereine_plus_array_funct) { header("Location: " . $_SERVER['REQUEST_URI']);
global $mysqli; exit;
global $db_tabelle_vereine;
if (empty($vereine_plus_array_funct)) {
return;
}
$stmt = $mysqli->prepare("UPDATE $db_tabelle_vereine SET `konto` = `konto` + ? WHERE `verein` = ?");
if ($stmt) {
foreach ($vereine_plus_array_funct as $verein => $betrag) {
if ($betrag <= 0.00) {
continue;
}
$stmt->bind_param("ds", $betrag, $verein);
$stmt->execute();
} }
$set_clauses = [];
$params = [];
$types = '';
if ($new_programm !== '') {
$set_clauses[] = 'programm = ?';
$params[] = $new_programm;
$types .= 's';
}
if (in_array($bezahlt_update, ['0', '3', '4', '5'], true)) {
$set_clauses[] = 'bezahltoverride = ?';
$params[] = (int) $bezahlt_update;
$types .= 'i';
}
if (empty($set_clauses)) {
$_SESSION['form_message'] = 'Keine gültigen Änderungen gewählt.';
$_SESSION['form_message_type'] = 0;
header("Location: " . $_SERVER['REQUEST_URI']);
exit;
}
if (strlen($types) !== count($params) || count($params) !== count($set_clauses)) {
die('Type/value mismatch: ' . strlen($types) . ' vs ' . count($params));
}
/* WHERE id IN (?, ?, ...) */
$placeholders = implode(',', array_fill(0, count($ids_to_update), '?'));
$sql = "UPDATE $tableTeilnehmende SET " . implode(', ', $set_clauses) . " WHERE id IN ($placeholders)";
$stmt = $mysqli->prepare($sql);
/* add ID params */
foreach ($ids_to_update as $id) {
$params[] = $id;
$types .= 'i';
}
$stmt->bind_param($types, ...$params);
if (!$stmt->execute()) {
throw new RuntimeException('DB error: ' . $stmt->error);
}
$updated_count = $stmt->affected_rows;
$stmt->close(); $stmt->close();
if ($updated_count === -1) {
$_SESSION['form_message'] = 'Ein Fehler ist bei der Aktualisierung aufgetreten.';
$_SESSION['form_message_type'] = 0;
} elseif ($updated_count > 0) {
$_SESSION['form_message'] = $updated_count . ' Einträge erfolgreich aktualisiert.';
$_SESSION['form_message_type'] = 1;
} else {
$_SESSION['form_message'] = 'Keine Änderungen vorgenommen.';
$_SESSION['form_message_type'] = 0;
}
} }
header('Location: ' . $_SERVER['REQUEST_URI']);
exit;
} }
function remove_from_basket(array $ids_array = []) : bool {
global $mysqli;
global $db_tabelle_warenkorb_startgebueren;
$tplaceholders = implode(", ", array_fill(0, count($ids_array), "?"));
$stmt = $mysqli->prepare("DELETE FROM $db_tabelle_warenkorb_startgebueren WHERE `item_id` IN ($tplaceholders)");
$stmt->bind_param(str_repeat('i', count($ids_array)), ...$ids_array);
$ret = $stmt->execute();
$stmt->close();
return $ret;
}
$vereine_plus_array = [];
if (isset($_POST['delete_id'])) { if (isset($_POST['delete_id'])) {
verify_csrf(); verify_csrf();
$delete_id = intval($_POST['delete_id']); $delete_id = intval($_POST['delete_id']);
$stmt = $mysqli->prepare("DELETE FROM $db_tabelle_teilnehmendee where id = ?"); $stmt = $mysqli->prepare("DELETE FROM $tableTeilnehmendee where id = ?");
$stmt->bind_param('i', $delete_id); $stmt->bind_param('i', $delete_id);
@@ -91,21 +109,19 @@ if (isset($_POST['delete_id'])) {
$edit_row = null; $edit_row = null;
if ($access_granted_trainer && isset($_GET['edit_id']) && is_numeric($_GET['edit_id']) && !isset($_POST['submit_turnerinnen_form'])) { if ($access_granted_trainer && isset($_GET['edit_id']) && is_numeric($_GET['edit_id']) && !isset($_POST['submit_turnerinnen_form'])) {
$edit_id = intval($_GET['edit_id']); $edit_id = intval($_GET['edit_id']);
$edit_rows = db_select($mysqli, $db_tabelle_teilnehmende, "*", 'id = ?', [$edit_id]); $edit_rows = db_select($mysqli, $tableTeilnehmende, "*", 'id = ?', [$edit_id]);
if (!isset($edit_rows) || !is_array($edit_rows) || count($edit_rows) !== 1) { if (!isset($edit_rows) || !is_array($edit_rows) || count($edit_rows) !== 1) {
http_response_code(422); http_response_code(422);
exit; exit;
} }
$edit_row = $edit_rows[0]; $edit_row = $edit_rows[0];
if ($edit_row && ($edit_row['verein'] === $selectedverein || $is_admin)) { if ($edit_row && ($edit_row['verein'] === $selectedverein || $isAdmin)) {
$_POST['nachname'] = $edit_row['name'] ?? ''; $_POST['nachname'] = $edit_row['name'] ?? '';
$_POST['vorname'] = $edit_row['vorname'] ?? ''; $_POST['vorname'] = $edit_row['vorname'] ?? '';
$_POST['geburtsdatum'] = $edit_row['geburtsdatum'] ?? ''; $_POST['geburtsdatum'] = $edit_row['geburtsdatum'] ?? '';
$kat = $edit_row['programm'] ?? ''; $_POST['programm'] = $edit_row['programm'] ?? '';
$kat_id = db_get_var($mysqli, "SELECT `id` FROM $db_tabelle_kategorien WHERE `programm` = ?", [$kat]) ?: '';
$_POST['programm'] = $kat_id;
$_POST['edit_id'] = $edit_id; $_POST['edit_id'] = $edit_id;
if ($is_admin) { if ($isAdmin) {
$_POST['verein'] = $edit_row['verein'] ?? ''; $_POST['verein'] = $edit_row['verein'] ?? '';
$_POST['bezahltOverride'] = $edit_row['bezahltoverride'] ?? 0; $_POST['bezahltOverride'] = $edit_row['bezahltoverride'] ?? 0;
} }
@@ -125,31 +141,29 @@ if ($access_granted_trainer && isset($_POST['submit_turnerinnen_form'])) {
$name = htmlspecialchars($_POST['nachname']); $name = htmlspecialchars($_POST['nachname']);
$vorname = htmlspecialchars($_POST['vorname']); $vorname = htmlspecialchars($_POST['vorname']);
$geburtsdatum = trim($_POST['geburtsdatum']); $geburtsdatum = trim($_POST['geburtsdatum']);
$kat_id = (int) ($_POST['programm'] ?? 0); $programm = htmlspecialchars($_POST['programm']);
if (!$is_admin) { if (!$isAdmin) {
$verein = $selectedverein; $verein = $selectedverein;
} else { } else {
$verein = htmlspecialchars($_POST['verein']); $verein = htmlspecialchars($_POST['verein']);
$bezahlt = intval($_POST['bezahltOverride']); $bezahlt = intval($_POST['bezahltOverride']);
} }
$kat = db_get_var($mysqli, "SELECT `programm` FROM $db_tabelle_kategorien WHERE `id` = ?", [$kat_id]) ?: ''; if (empty($name) || empty($vorname) || empty($geburtsdatum) || empty($programm)) {
if (empty($name) || empty($vorname) || empty($geburtsdatum) || empty($kat_id) || empty($kat)) {
$_SESSION['form_message'] = 'Bitte füllen Sie alle erforderlichen Felder aus.'; $_SESSION['form_message'] = 'Bitte füllen Sie alle erforderlichen Felder aus.';
$_SESSION['form_message_type'] = 0; $_SESSION['form_message_type'] = 0;
} else { } else {
$data_to_insert = [ $data_to_insert = [
'name' => $name, 'name' => $name,
'vorname' => $vorname, 'vorname' => $vorname,
'geburtsdatum' => $geburtsdatum, 'geburtsdatum' => $geburtsdatum,
'programm' => $kat, 'programm' => $programm,
'verein' => $verein, 'verein' => $verein,
]; ];
if ($is_admin) { if ($isAdmin) {
$data_to_insert['bezahltoverride'] = $bezahlt; $data_to_insert['bezahltoverride'] = $bezahlt;
} }
@@ -160,16 +174,9 @@ if ($access_granted_trainer && isset($_POST['submit_turnerinnen_form'])) {
if ($is_editing) { if ($is_editing) {
$edit_id = intval($_POST['edit_id']); $edit_id = intval($_POST['edit_id']);
$entry = db_select($mysqli, $db_tabelle_teilnehmende, '*', 'id = ?', [$edit_id], '', '1'); $entries = db_select($mysqli, $tableTeilnehmende, '*', 'id = ?', [$edit_id], 'rang ASC');
if (count($entry) === 1) { $entry = $entries[0]; // since you're fetching by ID, this should return exactly one row
$old_data = $entry[0];
$data_to_insert = array_merge($data_to_insert, get_kat_price_diff($old_data, $kat));
remove_from_basket([$edit_id]);
update_verein_balances($vereine_plus_array);
}
var_dump($data_to_insert);
$columns = array_keys($data_to_insert); $columns = array_keys($data_to_insert);
@@ -178,7 +185,7 @@ if ($access_granted_trainer && isset($_POST['submit_turnerinnen_form'])) {
array_map(fn($col) => "$col = ?", $columns) array_map(fn($col) => "$col = ?", $columns)
); );
$sql = "UPDATE $db_tabelle_teilnehmende SET $set WHERE id = ?"; $sql = "UPDATE $tableTeilnehmende SET $set WHERE id = ?";
var_dump($sql); var_dump($sql);
$stmt = $mysqli->prepare($sql); $stmt = $mysqli->prepare($sql);
@@ -216,7 +223,7 @@ if ($access_granted_trainer && isset($_POST['submit_turnerinnen_form'])) {
array_map(fn($col) => "$col = ?", $columns) array_map(fn($col) => "$col = ?", $columns)
); );
$sql = "INSERT INTO $db_tabelle_teilnehmende SET $set"; $sql = "INSERT INTO $tableTeilnehmende SET $set";
$stmt = $mysqli->prepare($sql); $stmt = $mysqli->prepare($sql);
BIN
View File
Binary file not shown.
+3 -3
View File
@@ -9,10 +9,10 @@ const envPath = path.resolve(__dirname, '..', 'config', '.env.redis');
dotenv.config({ path: envPath }); dotenv.config({ path: envPath });
const redisClient = createClient({ const redisClient = createClient({
password: process.env.REDIS_PASSWORD, password: process.env.REDDIS_PASSWORD,
socket: { socket: {
host: process.env.REDIS_HOST || 'redis', host: process.env.REDDIS_HOST,
port: process.env.REDIS_PORT || 6379 port: process.env.REDDIS_PORT
} }
}); });
-135
View File
@@ -1,135 +0,0 @@
{
"name": "websocket",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "websocket",
"version": "1.0.0",
"license": "ISC",
"dependencies": {
"dotenv": "^17.4.1",
"redis": "^5.11.0",
"ws": "^8.20.0"
}
},
"node_modules/@redis/bloom": {
"version": "5.11.0",
"resolved": "https://registry.npmjs.org/@redis/bloom/-/bloom-5.11.0.tgz",
"integrity": "sha512-KYiVilAhAFN3057afUb/tfYJpsEyTkQB+tQcn5gVVA7DgcNOAj8lLxe4j8ov8BF6I9C1Fe/kwlbuAICcTMX8Lw==",
"engines": {
"node": ">= 18"
},
"peerDependencies": {
"@redis/client": "^5.11.0"
}
},
"node_modules/@redis/client": {
"version": "5.11.0",
"resolved": "https://registry.npmjs.org/@redis/client/-/client-5.11.0.tgz",
"integrity": "sha512-GHoprlNQD51Xq2Ztd94HHV94MdFZQ3CVrpA04Fz8MVoHM0B7SlbmPEVIjwTbcv58z8QyjnrOuikS0rWF03k5dQ==",
"dependencies": {
"cluster-key-slot": "1.1.2"
},
"engines": {
"node": ">= 18"
},
"peerDependencies": {
"@node-rs/xxhash": "^1.1.0"
},
"peerDependenciesMeta": {
"@node-rs/xxhash": {
"optional": true
}
}
},
"node_modules/@redis/json": {
"version": "5.11.0",
"resolved": "https://registry.npmjs.org/@redis/json/-/json-5.11.0.tgz",
"integrity": "sha512-1iAy9kAtcD0quB21RbPTbUqqy+T2Uu2JxucwE+B4A+VaDbIRvpZR6DMqV8Iqaws2YxJYB3GC5JVNzPYio2ErUg==",
"engines": {
"node": ">= 18"
},
"peerDependencies": {
"@redis/client": "^5.11.0"
}
},
"node_modules/@redis/search": {
"version": "5.11.0",
"resolved": "https://registry.npmjs.org/@redis/search/-/search-5.11.0.tgz",
"integrity": "sha512-g1l7f3Rnyk/xI99oGHIgWHSKFl45Re5YTIcO8j/JE8olz389yUFyz2+A6nqVy/Zi031VgPDWscbbgOk8hlhZ3g==",
"engines": {
"node": ">= 18"
},
"peerDependencies": {
"@redis/client": "^5.11.0"
}
},
"node_modules/@redis/time-series": {
"version": "5.11.0",
"resolved": "https://registry.npmjs.org/@redis/time-series/-/time-series-5.11.0.tgz",
"integrity": "sha512-TWFeOcU4xkj0DkndnOyhtxvX1KWD+78UHT3XX3x3XRBUGWeQrKo3jqzDsZwxbggUgf9yLJr/akFHXru66X5UQA==",
"engines": {
"node": ">= 18"
},
"peerDependencies": {
"@redis/client": "^5.11.0"
}
},
"node_modules/cluster-key-slot": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz",
"integrity": "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/dotenv": {
"version": "17.4.1",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.4.1.tgz",
"integrity": "sha512-k8DaKGP6r1G30Lx8V4+pCsLzKr8vLmV2paqEj1Y55GdAgJuIqpRp5FfajGF8KtwMxCz9qJc6wUIJnm053d/WCw==",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://dotenvx.com"
}
},
"node_modules/redis": {
"version": "5.11.0",
"resolved": "https://registry.npmjs.org/redis/-/redis-5.11.0.tgz",
"integrity": "sha512-YwXjATVDT+AuxcyfOwZn046aml9jMlQPvU1VXIlLDVAExe0u93aTfPYSeRgG4p9Q/Jlkj+LXJ1XEoFV+j2JKcQ==",
"dependencies": {
"@redis/bloom": "5.11.0",
"@redis/client": "5.11.0",
"@redis/json": "5.11.0",
"@redis/search": "5.11.0",
"@redis/time-series": "5.11.0"
},
"engines": {
"node": ">= 18"
}
},
"node_modules/ws": {
"version": "8.20.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.20.0.tgz",
"integrity": "sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA==",
"engines": {
"node": ">=10.0.0"
},
"peerDependencies": {
"bufferutil": "^4.0.1",
"utf-8-validate": ">=5.0.2"
},
"peerDependenciesMeta": {
"bufferutil": {
"optional": true
},
"utf-8-validate": {
"optional": true
}
}
}
}
}
-17
View File
@@ -1,17 +0,0 @@
{
"name": "websocket",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"dotenv": "^17.4.1",
"redis": "^5.11.0",
"ws": "^8.20.0"
}
}
View File
@@ -316,6 +316,7 @@ function initConditionalVisibility() {
$rows.each((ind, el) => { $rows.each((ind, el) => {
const $row = $(el); const $row = $(el);
$row.find('.singleValueSpan[data-linked-el-uid]').each(function() { $row.find('.singleValueSpan[data-linked-el-uid]').each(function() {
console.log('ja');
const linkedUid = $(this).data('linked-el-uid'); const linkedUid = $(this).data('linked-el-uid');
const $linkedEl = $row.find(`.singleValueSpan[data-uid="${linkedUid}"]`); const $linkedEl = $row.find(`.singleValueSpan[data-uid="${linkedUid}"]`);
@@ -26,7 +26,7 @@ require $baseDir . '/../scripts/db/db-verbindung-script-guest.php';
require $baseDir . "/../scripts/db/db-tables.php"; require $baseDir . "/../scripts/db/db-tables.php";
require $baseDir . "/../scripts/db/db-functions.php"; require $baseDir . "/../scripts/db/db-functions.php";
$rankLive_public = db_get_variable($guest, $db_tabelle_var, ['rankLivePublic']) ?? 0; $rankLive_public = db_get_variable($guest, $tableVar, ['rankLivePublic']) ?? 0;
require $baseDir . '/../scripts/websocket/ws-create-token.php'; require $baseDir . '/../scripts/websocket/ws-create-token.php';
@@ -44,7 +44,7 @@ $is_live = $type === 'live';
if ($slug !== null && !$is_live) { if ($slug !== null && !$is_live) {
$alle_programme_db = db_select($guest, $db_tabelle_kategorien, '`programm`', 'aktiv = ?', ['1']); $alle_programme_db = db_select($guest, $tableProgramme, '`programm`', 'aktiv = ?', ['1']);
$raw_programme = array_column($alle_programme_db, 'programm', 'programm'); $raw_programme = array_column($alle_programme_db, 'programm', 'programm');
$alle_programme = []; $alle_programme = [];
@@ -69,14 +69,20 @@ $selectedFreigabeId = 'A';
$json_type = $is_live ? 'rankLive-geraet' : 'rankLive-overview'; $json_type = $is_live ? 'rankLive-geraet' : 'rankLive-overview';
$jsonstr = db_select($guest, $db_tabelle_tabellen_konfiguration, 'json', 'type_slug = ?', [$json_type], '', 1); $jsonstr = db_select($guest, $tableRankLiveConfigs, 'json', 'type_slug = ?', [$json_type], '', 1);
$data = json_decode($jsonstr[0]['json'], true); $data = json_decode($jsonstr[0]['json'], true);
$headers = $data['header'] ?? []; $headers = $data['header'] ?? [];
$bodyColumns = $data['body'] ?? []; $bodyColumns = $data['body'] ?? [];
$alle_programme_db = db_select($guest, $db_tabelle_kategorien, '`programm`, `id`', 'aktiv = ?', ['1']); $current_year = date('Y');
$monat = date('n');
if ($monat > 6) {
$current_year = $current_year + 1;
}
$alle_programme_db = db_select($guest, $tableProgramme, '`programm`, `id`', 'aktiv = ?', ['1']);
$indexedProgrammes = array_column($alle_programme_db, 'programm', 'id'); $indexedProgrammes = array_column($alle_programme_db, 'programm', 'id');
$personData = [ $personData = [
@@ -87,7 +93,7 @@ $personData = [
'programm' => 'Programm' 'programm' => 'Programm'
]; ];
$disciplines = db_select($guest, $db_tabelle_disziplinen, '*', '', [], 'start_index ASC'); $disciplines = db_select($guest, $tableGeraete, '*', '', [], 'start_index ASC');
$indexedArrayGeraete = array_column($disciplines, 'name', 'id'); $indexedArrayGeraete = array_column($disciplines, 'name', 'id');
@@ -96,8 +102,8 @@ if ($is_live) {
$indexed_array_geraete_index_id = array_flip($indexed_array_geraete_id_index); $indexed_array_geraete_index_id = array_flip($indexed_array_geraete_id_index);
$abt = (int) (db_get_variable($guest, $db_tabelle_var, ['wk_panel_current_abt']) ?? 1); $abt = (int) (db_get_variable($guest, $tableVar, ['wk_panel_current_abt']) ?? 1);
$akt_subabt = (int) (db_get_variable($guest, $db_tabelle_var, ['wk_panel_current_subabt_admin']) ?? 1); $akt_subabt = (int) (db_get_variable($guest, $tableVar, ['wk_panel_current_subabt_admin']) ?? 1);
$max_subabt = count($disciplines); $max_subabt = count($disciplines);
@@ -112,11 +118,11 @@ if ($is_live) {
tabt.`turnerin_index` AS start_index, tabt.`turnerin_index` AS start_index,
tabt.`geraet_id` AS startgeraet, tabt.`geraet_id` AS startgeraet,
tabt.`abteilung_id` AS abt_id tabt.`abteilung_id` AS abt_id
FROM $db_tabelle_teilnehmende t FROM $tableTeilnehmende t
LEFT JOIN $db_tabelle_kategorien p ON p.programm = t.programm LEFT JOIN $tableProgramme p ON p.programm = t.programm
INNER JOIN $db_tabelle_teilnehmende_gruppen tabt ON tabt.`turnerin_id` = t.`id` INNER JOIN $tableTeilnehmendeAbt tabt ON tabt.`turnerin_id` = t.`id`
INNER JOIN $db_tabelle_gruppen abt ON abt.`id` = tabt.`abteilung_id` INNER JOIN $tableAbt abt ON abt.`id` = tabt.`abteilung_id`
WHERE (t.bezahlt = 4 OR t.bezahltoverride = 4) AND abt.`order_index` = ?"); WHERE (t.bezahlt = 2 OR t.bezahltoverride = 5) AND abt.`order_index` = ?");
$stmt->bind_param("i", $abt); $stmt->bind_param("i", $abt);
} elseif ($type === 'programm' && $slug !== null) { } elseif ($type === 'programm' && $slug !== null) {
@@ -128,9 +134,9 @@ if ($is_live) {
u.verein, u.verein,
u.geburtsdatum, u.geburtsdatum,
p.id AS programm_id p.id AS programm_id
FROM $db_tabelle_teilnehmende u FROM $tableTeilnehmende u
LEFT JOIN $db_tabelle_kategorien p ON p.programm = u.programm LEFT JOIN $tableProgramme p ON p.programm = u.programm
WHERE (u.bezahlt = 4 OR u.bezahltoverride = 4) AND u.programm = ?"); WHERE (u.bezahlt = 2 OR u.bezahltoverride = 5) AND u.programm = ?");
$stmt->bind_param("s", $validated_programm_name); $stmt->bind_param("s", $validated_programm_name);
} else { } else {
@@ -142,9 +148,9 @@ if ($is_live) {
u.verein, u.verein,
u.geburtsdatum, u.geburtsdatum,
p.id AS programm_id p.id AS programm_id
FROM $db_tabelle_teilnehmende u FROM $tableTeilnehmende u
LEFT JOIN $db_tabelle_kategorien p ON p.programm = u.programm LEFT JOIN $tableProgramme p ON p.programm = u.programm
WHERE (u.bezahlt = 4 OR u.bezahltoverride = 4)"); WHERE (u.bezahlt = 2 OR u.bezahltoverride = 5)");
} }
$stmt->execute(); $stmt->execute();
@@ -160,14 +166,12 @@ foreach ($tures as $s_tures) {
$indexedTures[$s_tures['programm_id']][$s_tures['id']] = $s_tures; $indexedTures[$s_tures['programm_id']][$s_tures['id']] = $s_tures;
} }
$current_wk_id = (int) db_get_variable($guest, $db_tabelle_var, ['current_wk_id']) ?: 0;
$stmt = $guest->prepare("SELECT `note_bezeichnung_id`, `geraet_id`, `person_id`, `run_number`, `public_value` $stmt = $guest->prepare("SELECT `note_bezeichnung_id`, `geraet_id`, `person_id`, `run_number`, `public_value`
FROM $db_tabelle_wertungen FROM $tableNoten
WHERE `is_public` = 1 WHERE `is_public` = 1
AND `wk_id` = ?"); AND `jahr` = ?");
$stmt->bind_param("i", $current_wk_id); $stmt->bind_param("i", $current_year);
$stmt->execute(); $stmt->execute();
@@ -178,7 +182,7 @@ $stmt->close();
$indexedNoten = []; $indexedNoten = [];
$stmt = $guest->prepare("SELECT `id`, `geraete_json`, `pro_geraet`, `anzahl_laeufe_json`, `name`, `default_value` FROM $db_tabelle_wertungstypen"); $stmt = $guest->prepare("SELECT `id`, `geraete_json`, `pro_geraet`, `anzahl_laeufe_json`, `name`, `default_value` FROM $tableNotenBezeichnungen");
$stmt->execute(); $stmt->execute();
@@ -187,13 +191,13 @@ $notenConfig = $result->fetch_all(MYSQLI_ASSOC);
$stmt->close(); $stmt->close();
$disciplines = db_select($guest, $db_tabelle_disziplinen, '*', '', [], 'start_index ASC'); $disciplines = db_select($guest, $tableGeraete, '*', '', [], 'start_index ASC');
$indexedNotenConfig = array_column($notenConfig, null, 'id'); $indexedNotenConfig = array_column($notenConfig, null, 'id');
$rangNote = intval(db_get_var($guest, "SELECT `value` FROM $db_tabelle_var WHERE `name` = ?", ['rangNote'])); $rangNote = intval(db_get_var($guest, "SELECT `value` FROM $tableVar WHERE `name` = ?", ['rangNote']));
$orderBestRang = db_get_var($guest, "SELECT `value` FROM $db_tabelle_var WHERE `name` = ?", ['orderBestRang']); $orderBestRang = db_get_var($guest, "SELECT `value` FROM $tableVar WHERE `name` = ?", ['orderBestRang']);
$okValuesOrderBestRang = ["ASC", "DESC"]; $okValuesOrderBestRang = ["ASC", "DESC"];
@@ -213,13 +217,12 @@ $disciplinesExtended = array_merge(
$arrayIndexedNoten = []; $arrayIndexedNoten = [];
$wkName = db_get_var($guest, "SELECT `value` FROM $db_tabelle_var WHERE `name` = ?", ['wkName']); $wkName = db_get_var($guest, "SELECT `value` FROM $tableVar WHERE `name` = ?", ['wkName']);
$grouped = []; $grouped = [];
function groop_by_key($array, $array_key, bool $int = false): array function groop_by_key($array, $array_key, bool $int = false): array
{ {
$grouped = [];
if ($int) { if ($int) {
foreach ($array as $entry) { foreach ($array as $entry) {
$key = (int) $entry[$array_key]; $key = (int) $entry[$array_key];
@@ -436,207 +439,192 @@ function constructSingleValueSpanPublic($token, $type, $row, $selected_geraet_id
<link rel="stylesheet" href="/intern/css/custom-msg-display.css"> <link rel="stylesheet" href="/intern/css/custom-msg-display.css">
<link href="/files/fonts/fonts.css" rel="stylesheet"> <link href="/files/fonts/fonts.css" rel="stylesheet">
<link rel="stylesheet" href="/css/custom-display-editor-required-css.css"> <link rel="stylesheet" href="/css/custom-display-editor-required-css.css">
<link rel="stylesheet" href="/css/rank-live.css">
<link rel="stylesheet" href="/css/header.css">
<script src="/intern/js/jquery/jquery-3.7.1.min.js"></script> <script src="/intern/js/jquery/jquery-3.7.1.min.js"></script>
<script src="/intern/js/custom-msg-display.js"></script> <script src="/intern/js/custom-msg-display.js"></script>
<script src="/js/header.js"></script>
<title>RankLive - <?= htmlspecialchars($wkName) ?></title> <title>RankLive - <?= htmlspecialchars($wkName) ?></title>
</head> </head>
<body> <body>
<section class="bgSection"> <?php if (count($indexedTures) < 1) : ?>
<?php include $baseDir . '/../scripts/public-elements/rankLive-header.php'; ?> <h3>Noch keine Daten verfügbar</h3>
<?php if (count($indexedTures) < 1) : ?> <?php endif; ?>
<h3>Noch keine Daten verfügbar</h3> <div class="allAbtContainer">
<?php exit; ?> <?php foreach ($grouped as $entry_key => $entries_group):
<?php endif; ?>
<div class="headerWrapper">
<h2 class="headerMain">RankLive</h2>
<?php if ($is_live) : ?>
<h4>Gruppe: <?= (int) $abt ?></h4>
<h4>Rotation: <?= (int) $akt_subabt ?></h4>
<?php elseif ($type === 'programm' && $slug !== null) : ?>
<h4><?= htmlspecialchars($validated_programm_name) ?></h4>
<?php endif; ?>
</div>
<div class="allAbtContainer">
<?php foreach ($grouped as $entry_key => $entries_group):
$table_data_geraet_label = ''; $table_data_geraet_label = '';
$extra_table_classes = ''; $extra_table_classes = '';
if ($is_live) { if ($is_live) {
$entries_with_calculated_index = []; $entries_with_calculated_index = [];
// Optional: Vorab-Caching der MAX-Indices, um DB-Abfragen im Loop zu vermeiden // Optional: Vorab-Caching der MAX-Indices, um DB-Abfragen im Loop zu vermeiden
// (Nur aktivieren, wenn $entries_group sehr groß ist und Performance-Probleme auftreten) // (Nur aktivieren, wenn $entries_group sehr groß ist und Performance-Probleme auftreten)
$maxStartIndexCache = []; $maxStartIndexCache = [];
foreach ($entries_group as $row) { foreach ($entries_group as $row) {
$old_geraet_id = $row['startgeraet']; $old_geraet_id = $row['startgeraet'];
$old_geraet_index = $indexed_array_geraete_id_index[$row['startgeraet']]; $old_geraet_index = $indexed_array_geraete_id_index[$row['startgeraet']];
$shifted_geraet_index = $old_geraet_index + $akt_subabt - 1; $shifted_geraet_index = $old_geraet_index + $akt_subabt - 1;
if ($shifted_geraet_index > $max_subabt) {
$shifted_geraet_index -= $max_subabt;
}
$row['startgeraet'] = $indexed_array_geraete_index_id[$shifted_geraet_index];
$rohstartindex = intval($row['start_index']);
$abtId = intval($row['abt_id']);
// Hole maxStartIndex (mit Caching-Logik optional)
if (!isset($maxStartIndexCache["$abtId-$old_geraet_id "])) {
$maxStartIndexCache["$abtId-$old_geraet_id"] = db_get_var($guest, "SELECT COUNT(*) FROM `$db_tabelle_teilnehmende_gruppen` WHERE abteilung_id = ? AND geraet_id = ?", [$abtId, $old_geraet_id]);
}
$maxstartindex = $maxStartIndexCache["$abtId-$old_geraet_id"];
// Sicherheit: Vermeiden Sie Division durch Null oder Modulo durch 0
if ($maxstartindex < 1) {
$maxstartindex = 1;
}
// The order shift depends on the number of rotations the group has made
$rotation_shift = $akt_subabt - 1;
$calculedstartindex = $rohstartindex - $rotation_shift;
// Sicherstellen, dass das Ergebnis positiv ist (PHP Modulo kann negative Ergebnisse liefern)
// Wenn das Ergebnis negativ ist, addieren wir maxstartindex
if ($calculedstartindex < 1) {
$calculedstartindex += $maxstartindex;
}
// Kopie der Zeile erstellen, um Originaldaten nicht zu verändern
$row['calculedstartindex'] = $calculedstartindex;
$entries_with_calculated_index[] = $row;
}
// Sortieren nach dem berechneten Startindex
usort($entries_with_calculated_index, fn($a, $b) => $a['calculedstartindex'] <=> $b['calculedstartindex']);
$entries_to_display = $entries_with_calculated_index;
$shifted_geraet_index = $indexed_array_geraete_id_index[$entry_key] + $akt_subabt - 1;
if ($shifted_geraet_index > $max_subabt) { if ($shifted_geraet_index > $max_subabt) {
$shifted_geraet_index -= $max_subabt; $shifted_geraet_index -= $max_subabt;
} }
$shifted_geraet_id = $indexed_array_geraete_index_id[$shifted_geraet_index]; $row['startgeraet'] = $indexed_array_geraete_index_id[$shifted_geraet_index];
$rohstartindex = intval($row['start_index']);
$abtId = intval($row['abt_id']);
$table_data_geraet_label = ' data-geraet-index="' . $shifted_geraet_index . '"'; // Hole maxStartIndex (mit Caching-Logik optional)
$extra_table_classes = ' shiftedGeraetTable'; if (!isset($maxStartIndexCache["$abtId-$old_geraet_id "])) {
} else { $maxStartIndexCache["$abtId-$old_geraet_id"] = db_get_var($guest, "SELECT COUNT(*) FROM `$tableTeilnehmendeAbt` WHERE abteilung_id = ? AND geraet_id = ?", [$abtId, $old_geraet_id]);
$entries_to_display = $entries_group; }
$maxstartindex = $maxStartIndexCache["$abtId-$old_geraet_id"];
// Sicherheit: Vermeiden Sie Division durch Null oder Modulo durch 0
if ($maxstartindex < 1) {
$maxstartindex = 1;
}
// The order shift depends on the number of rotations the group has made
$rotation_shift = $akt_subabt - 1;
$calculedstartindex = $rohstartindex - $rotation_shift;
// Sicherstellen, dass das Ergebnis positiv ist (PHP Modulo kann negative Ergebnisse liefern)
// Wenn das Ergebnis negativ ist, addieren wir maxstartindex
if ($calculedstartindex < 1) {
$calculedstartindex += $maxstartindex;
}
// Kopie der Zeile erstellen, um Originaldaten nicht zu verändern
$row['calculedstartindex'] = $calculedstartindex;
$entries_with_calculated_index[] = $row;
} }
foreach ($entries_to_display as $row): // Sortieren nach dem berechneten Startindex
$rangNotenArray[$entry_key][$row['id']] = isset($notenIndexed[$row['id']][0][$rangNote][1]) ? floatval($notenIndexed[$row['id']][0][$rangNote][1]) : null; usort($entries_with_calculated_index, fn($a, $b) => $a['calculedstartindex'] <=> $b['calculedstartindex']);
foreach ($disciplinesExtended as $discipline):
foreach ($notenConfig as $snC):
if (intval($snC['pro_geraet']) === 1 && intval($discipline['id']) === 0) { $entries_to_display = $entries_with_calculated_index;
$shifted_geraet_index = $indexed_array_geraete_id_index[$entry_key] + $akt_subabt - 1;
if ($shifted_geraet_index > $max_subabt) {
$shifted_geraet_index -= $max_subabt;
}
$shifted_geraet_id = $indexed_array_geraete_index_id[$shifted_geraet_index];
$table_data_geraet_label = ' data-geraet-index="' . $shifted_geraet_index . '"';
$extra_table_classes = ' shiftedGeraetTable';
} else {
$entries_to_display = $entries_group;
}
foreach ($entries_to_display as $row):
$rangNotenArray[$entry_key][$row['id']] = isset($notenIndexed[$row['id']][0][$rangNote][1]) ? floatval($notenIndexed[$row['id']][0][$rangNote][1]) : null;
foreach ($disciplinesExtended as $discipline):
foreach ($notenConfig as $snC):
if (intval($snC['pro_geraet']) === 1 && intval($discipline['id']) === 0) {
continue;
}
if (intval($snC['pro_geraet']) !== 1) {
$allowedGeraete = !empty($snC['geraete_json']) ? json_decode($snC['geraete_json'], true) : [];
if (!in_array($discipline['id'], $allowedGeraete)) {
continue; continue;
} }
}
if (intval($snC['pro_geraet']) !== 1) { $runsJSON = !empty($snC['anzahl_laeufe_json']) ? json_decode($snC['anzahl_laeufe_json'], true) : [];
$allowedGeraete = !empty($snC['geraete_json']) ? json_decode($snC['geraete_json'], true) : [];
if (!in_array($discipline['id'], $allowedGeraete)) {
continue;
}
}
$runsJSON = !empty($snC['anzahl_laeufe_json']) ? json_decode($snC['anzahl_laeufe_json'], true) : []; $runs = $runsJSON[$discipline['id']][$entry_key] ?? $runsJSON[$discipline['id']]['all'] ?? $runsJSON["default"] ?? 1;
$runs = $runsJSON[$discipline['id']][$entry_key] ?? $runsJSON[$discipline['id']]['all'] ?? $runsJSON["default"] ?? 1; for ($r = 1; $r <= $runs; $r++):
$note = $notenIndexed[$row['id']][$discipline['id']][$snC['id']][$r] ?? null;
$normalizedNote = ($note !== null) ? number_format($note, $snC['nullstellen'] ?? 2) : null;
for ($r = 1; $r <= $runs; $r++): $arrayIndexedNoten[intval($row['id'])][intval($discipline['id'])][intval($snC['id'])][intval($r)] = ["value" => $normalizedNote];
$note = $notenIndexed[$row['id']][$discipline['id']][$snC['id']][$r] ?? null; endfor;
$normalizedNote = ($note !== null) ? number_format($note, $snC['nullstellen'] ?? 2) : null;
$arrayIndexedNoten[intval($row['id'])][intval($discipline['id'])][intval($snC['id'])][intval($r)] = ["value" => $normalizedNote];
endfor;
endforeach;
endforeach; endforeach;
endforeach; ?> endforeach;
<div class="singleAbtDiv<?= $extra_table_classes ?>" <?= $table_data_geraet_label ?>> endforeach; ?>
<?php if (!($type === 'programm' && $slug !== null)) : ?> <div class="singleAbtDiv<?= $extra_table_classes ?>" <?= $table_data_geraet_label ?>>
<h2 class="headerAbt"><?= ($is_live) ? $indexedArrayGeraete[$shifted_geraet_id] ?? '' : $entry_key ?? '' ?></h2> <?php if ($is_live) : ?>
<?php endif; ?> <h3><?= $indexedArrayGeraete[$shifted_geraet_id] ?? '' ?></h3>
<?php $geraet_id_for_live_view = $is_live ? $shifted_geraet_id : 0 ?> <?php else: ?>
<div class="tableWraper"> <h3><?= $entry_key ?? '' ?></h3>
<table class="customDisplayEditorTable"> <?php endif; ?>
<thead> <?php $geraet_id_for_live_view = $is_live ? $shifted_geraet_id : 0 ?>
<tr> <div class="scoreboard-container">
<?php foreach ($headers as $header_data): ?> <table class="customDisplayEditorTable">
<thead>
<tr>
<?php foreach ($headers as $header_data): ?>
<?php
$min_display_width = ($header_data['minDisplayWidth'] ?? 1) - 1;
$class = "";
if ($min_display_width > 0) {
$class = 'displayOver' . $min_display_width . 'px';
$display_over_array[] = $min_display_width;
}
?>
<th class="<?= $class ?>">
<?= htmlspecialchars($header_data['title']) ?>
</th>
<?php endforeach; ?>
</tr>
</thead>
<tbody <?= !$is_live ? 'data-programm-id="'. $entry_key .'"' : ''?>>
<?php foreach ($entries_to_display as $row): ?>
<tr data-person-id="<?= $row['id'] ?>">
<?php foreach ($bodyColumns as $ind => $columnTokens): ?>
<?php <?php
$min_display_width = ($header_data['minDisplayWidth'] ?? 1) - 1; $class = '';
$class = ""; if (isset($headers[$ind]['minDisplayWidth']) && $headers[$ind]['minDisplayWidth'] > 1) {
$min_display_width = ($headers[$ind]['minDisplayWidth'] ?? 1) - 1;
if ($min_display_width > 0) {
$class = 'displayOver' . $min_display_width . 'px'; $class = 'displayOver' . $min_display_width . 'px';
$display_over_array[] = $min_display_width; $display_over_array[] = $min_display_width;
} }
?> ?>
<td class=<?= $class ?>>
<th class="<?= $class ?>"> <span class="tdSpan">
<?= htmlspecialchars($header_data['title']) ?>
</th> <?php
foreach ($columnTokens as $token):
$type = $token['type'] ?? '';
if ($type === 'layoutEls') :
$column = $token['column'];
$content = $token['content'] ?? []; ?>
<div class="<?= $column ?>LayoutEl">
<?php foreach ($content as $layout_row) : ?>
<span class="<?= $column ?>LayoutElRow">
<?php foreach ($layout_row as $el) :
constructSingleValueSpanPublic($el, $el['type'], $row, $geraet_id_for_live_view);
endforeach; ?>
</span>
<?php endforeach; ?>
</div>
<?php else:
constructSingleValueSpanPublic($token, $type, $row, $geraet_id_for_live_view);
endif;
endforeach; ?>
</span>
</td>
<?php endforeach; ?> <?php endforeach; ?>
</tr> </tr>
</thead> <?php endforeach; ?>
<tbody <?= !$is_live ? 'data-programm-id="'. $entry_key .'"' : ''?>> </tbody>
<?php foreach ($entries_to_display as $row): ?>
<tr data-person-id="<?= $row['id'] ?>">
<?php foreach ($bodyColumns as $ind => $columnTokens): ?>
<?php
$class = '';
if (isset($headers[$ind]['minDisplayWidth']) && $headers[$ind]['minDisplayWidth'] > 1) { </table>
$min_display_width = ($headers[$ind]['minDisplayWidth'] ?? 1) - 1;
$class = 'displayOver' . $min_display_width . 'px';
$display_over_array[] = $min_display_width;
}
?>
<td class=<?= $class ?>>
<span class="tdSpan">
<?php
foreach ($columnTokens as $token):
$type = $token['type'] ?? '';
if ($type === 'layoutEls') :
$column = $token['column'];
$content = $token['content'] ?? []; ?>
<div class="<?= $column ?>LayoutEl">
<?php foreach ($content as $layout_row) : ?>
<span class="<?= $column ?>LayoutElRow">
<?php foreach ($layout_row as $el) :
constructSingleValueSpanPublic($el, $el['type'], $row, $geraet_id_for_live_view);
endforeach; ?>
</span>
<?php endforeach; ?>
</div>
<?php else:
constructSingleValueSpanPublic($token, $type, $row, $geraet_id_for_live_view);
endif;
endforeach; ?>
</span>
</td>
<?php endforeach; ?>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</div> </div>
<?php endforeach; ?> </div>
</div> <?php endforeach; ?>
<div class="tableCutter"></div> </div>
</section>
<style> <style>
<?php <?php
$unique_display_over_array = array_unique($display_over_array); $unique_display_over_array = array_unique($display_over_array);
@@ -655,7 +643,7 @@ function constructSingleValueSpanPublic($token, $type, $row, $selected_geraet_id
window.FREIGABE = "<?= $selectedFreigabeId; ?>"; window.FREIGABE = "<?= $selectedFreigabeId; ?>";
window.CSDR_TOKEN = "<?= $csrf_token; ?>"; window.CSDR_TOKEN = "<?= $csrf_token; ?>";
window.WS_ACCESS_TOKEN = "<?= generateWSReadToken('rankLive', $selectedFreigabeId) ?>"; window.WS_ACCESS_TOKEN = "<?= generateWSReadToken('rankLive', $selectedFreigabeId) ?>";
window.AKTUELLES_JAHR = "<?= $current_wk_id ?>"; window.AKTUELLES_JAHR = "<?= $current_year ?>";
window.RANG_NOTEN_ARRAY = '<?= json_encode($rangNotenArray, JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT); ?>'; window.RANG_NOTEN_ARRAY = '<?= json_encode($rangNotenArray, JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT); ?>';
window.RANG_SORT = "<?= $orderBestRang ?>"; window.RANG_SORT = "<?= $orderBestRang ?>";
window.RANG_NOTE_ID = <?= $rangNote ?>; window.RANG_NOTE_ID = <?= $rangNote ?>;
@@ -1,48 +1,19 @@
:root { :root {
--sb-bg: #f8fafc; --sb-bg: #f8fafc;
--sb-card-bg: #ffffff; --sb-card-bg: #ffffff;
--sb-text-main: #36454F; --sb-text-main: #36454F;
--sb-text-muted: #64748b; --sb-text-muted: #64748b;
--sb-primary: #1e40af; /* Deep athletic blue */
--sb-accent: #2563eb; /* Vivid score blue */
--sb-border: #e2e8f0; --sb-border: #e2e8f0;
--sb-hover: #f3f3f3; --sb-hover: #f3f3f3;
--sb-radius: 12px; --sb-radius: 12px;
--font-numeric: 'Courier New', Courier, monospace; /* Clean alignment for scores */
/* Newly extracted color variables */
--sb-th-text: #1e293b;
--sb-th-border: #1e293b;
--sb-th-bg: #ffffff;
--sb-tr-even-bg: #f8f8f860;
--sb-changeable-bg: rgba(37, 99, 235, 0.02);
--sb-flash-highlight: #00ff99; /* Green flash for updates */
--font-numeric: 'Courier New', Courier, monospace;
--padding-td: clamp(3px, 2.5vh, 12px) clamp(4px, 2vw, 20px); --padding-td: clamp(3px, 2.5vh, 12px) clamp(4px, 2vw, 20px);
--padding-th: clamp(2px, 1.5vh, 6px) clamp(4px, 2vw, 20px); --padding-th: clamp(2px, 1.5vh, 6px) clamp(4px, 2vw, 20px);
} }
@media (prefers-color-scheme: dark){
:root {
--sb-bg: #121212; /* Dark main background */
--sb-card-bg: #1a1a1a; /* Dark card surfaces */
--sb-text-main: #f5f5f5; /* Light grey/white text */
--sb-text-muted: #a0a0a0; /* Muted secondary text */
--sb-border: #2d2d2d; /* Soft dark border line */
--sb-hover: #252525; /* Subtle highlight on row hover */
--sb-radius: 12px;
/* Dark mode specific variables */
--sb-th-text: #ffffff; /* Crisp white headers */
--sb-th-border: #3d3d3d; /* Distinct header separation */
--sb-th-bg: #121212; /* Matches dark body background */
--sb-tr-even-bg: #1a1a1a40; /* Very soft zebra striping */
--sb-changeable-bg: rgba(240, 163, 236, 0.05); /* Soft background using the pink/purple accent */
--sb-flash-highlight: #39ff14; /* Vibrant neon green for live updates on dark backgrounds */
--font-numeric: 'Courier New', Courier, monospace;
--padding-td: clamp(3px, 2.5vh, 12px) clamp(4px, 2vw, 20px);
--padding-th: clamp(2px, 1.5vh, 6px) clamp(4px, 2vw, 20px);
}
}
/* Base Table Styling */ /* Base Table Styling */
.customDisplayEditorTable { .customDisplayEditorTable {
@@ -56,15 +27,15 @@
} }
.customDisplayEditorTable th { .customDisplayEditorTable th {
color: var(--sb-th-text); color: #1e293b;
border-bottom: solid 1px var(--sb-th-border); border-bottom: solid 1px #1e293b;
font-weight: 600; font-weight: 600;
font-size: 0.9rem; font-size: 0.9rem;
letter-spacing: 0.05em; letter-spacing: 0.05em;
padding: var(--padding-th); padding: var(--padding-th);
white-space: nowrap; white-space: nowrap;
position: sticky; position: sticky;
background-color: var(--sb-th-bg); background-color: #fff;
top: 0; top: 0;
z-index: 2; z-index: 2;
} }
@@ -83,23 +54,25 @@
} }
.customDisplayEditorTable tbody > tr:nth-child(even) td { .customDisplayEditorTable tbody > tr:nth-child(even) td {
background-color: var(--sb-tr-even-bg); background-color: #f8f8f8;
} }
.customDisplayEditorTable tbody > tr:hover td { .customDisplayEditorTable tbody > tr:hover td {
background-color: var(--sb-hover) !important; background-color: var(--sb-hover) !important;
} }
.customDisplayEditorTable td { .customDisplayEditorTable td {
color: var(--sb-text-main); color: var(--sb-text-main);
font-size: 0.9rem; font-size: 0.9rem;
} }
.customDisplayEditorTable td.changebleValue { .customDisplayEditorTable td.changebleValue {
font-family: var(--font-numeric); font-family: var(--font-numeric);
font-size: 1.05rem; font-size: 1.05rem;
color: var(--sb-text-main); color: var(--sb-text-main);
background-color: var(--sb-changeable-bg); background-color: rgb(37 99 235 / 0.02);
} }
.customDisplayEditorTable tr:last-child td { .customDisplayEditorTable tr:last-child td {
@@ -111,7 +84,7 @@
} }
@keyframes flashHighlight { @keyframes flashHighlight {
0% { color: var(--sb-flash-highlight); } 0% { color: #00ff99; }
100% { color: var(--sb-text-main); } 100% { color: var(--sb-text-main); }
} }
@@ -34,7 +34,7 @@ require_once $baseDir . '/../scripts/db/db-verbindung-script-guest.php';
require_once $baseDir . '/../scripts/db/db-functions.php'; require_once $baseDir . '/../scripts/db/db-functions.php';
require_once $baseDir . '/../scripts/db/db-tables.php'; require_once $baseDir . '/../scripts/db/db-tables.php';
$stmt = $guest->prepare("SELECT `name`, `id` FROM $db_tabelle_disziplinen WHERE `audiofile` = '1' ORDER BY start_index ASC"); $stmt = $guest->prepare("SELECT `name`, `id` FROM $tableGeraete WHERE `audiofile` = '1' ORDER BY start_index ASC");
if (!$stmt->execute()) { if (!$stmt->execute()) {
http_response_code(500); http_response_code(500);
@@ -34,7 +34,7 @@ require_once $baseDir . '/../scripts/db/db-verbindung-script-guest.php';
require_once $baseDir . '/../scripts/db/db-functions.php'; require_once $baseDir . '/../scripts/db/db-functions.php';
require_once $baseDir . '/../scripts/db/db-tables.php'; require_once $baseDir . '/../scripts/db/db-tables.php';
$stmt = $guest->prepare("SELECT `name`, `id` FROM $db_tabelle_disziplinen ORDER BY start_index ASC"); $stmt = $guest->prepare("SELECT `name`, `id` FROM $tableGeraete ORDER BY start_index ASC");
if (!$stmt->execute()) { if (!$stmt->execute()) {
http_response_code(500); http_response_code(500);
@@ -84,7 +84,7 @@ $keysNeeded = [
$placeholders = implode(',', array_fill(0, count($keysNeeded), '?')); $placeholders = implode(',', array_fill(0, count($keysNeeded), '?'));
$query = "SELECT `name`, `value` FROM $db_tabelle_var WHERE `name` IN ($placeholders)"; $query = "SELECT `name`, `value` FROM $tableVar WHERE `name` IN ($placeholders)";
$stmt = $guest->prepare($query); $stmt = $guest->prepare($query);
$params = str_repeat("s", count($keysNeeded)); $params = str_repeat("s", count($keysNeeded));
@@ -1,91 +1,91 @@
This Font Software is licensed under the SIL Open Font License, Version 1.1. This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at: This license is copied below, and is also available with a FAQ at:
https://openfontlicense.org https://openfontlicense.org
----------------------------------------------------------- -----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
----------------------------------------------------------- -----------------------------------------------------------
PREAMBLE PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership open framework in which fonts may be shared and improved in partnership
with others. with others.
The OFL allows the licensed fonts to be used, studied, modified and The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded, fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives, names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives. to any document created using the fonts or their derivatives.
DEFINITIONS DEFINITIONS
"Font Software" refers to the set of files released by the Copyright "Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation. include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the "Reserved Font Name" refers to any names specified as such after the
copyright statement(s). copyright statement(s).
"Original Version" refers to the collection of Font Software components as "Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s). distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting, "Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a Original Version, by changing formats or by porting the Font Software to a
new environment. new environment.
"Author" refers to any designer, engineer, programmer, technical "Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software. writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify, a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions: Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components, 1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself. in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled, 2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user. binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font 3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as Copyright Holder. This restriction only applies to the primary font name as
presented to the users. presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written Copyright Holder(s) and the Author(s) or with their explicit written
permission. permission.
5) The Font Software, modified or unmodified, in part or in whole, 5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created remain under this license does not apply to any document created
using the Font Software. using the Font Software.
TERMINATION TERMINATION
This license becomes null and void if any of the above conditions are This license becomes null and void if any of the above conditions are
not met. not met.
DISCLAIMER DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE. OTHER DEALINGS IN THE FONT SOFTWARE.

Some files were not shown because too many files have changed in this diff Show More