New Filestructure for Docker
This commit is contained in:
@@ -0,0 +1,30 @@
|
||||
RewriteEngine On
|
||||
|
||||
ErrorDocument 503 /503.html
|
||||
|
||||
RewriteCond %{REQUEST_URI} !^/503\.html$
|
||||
|
||||
RewriteRule ^(index)$ $1.php [L]
|
||||
RewriteRule ^(404|503)$ $1.html [L]
|
||||
|
||||
# ----------------------------------------
|
||||
# 1) Root → index.php
|
||||
# ----------------------------------------
|
||||
RewriteRule ^$ router.php [L]
|
||||
|
||||
# ----------------------------------------
|
||||
# 2) Allow existing files
|
||||
# ----------------------------------------
|
||||
RewriteCond %{REQUEST_FILENAME} -f
|
||||
RewriteRule ^ - [L]
|
||||
|
||||
# ----------------------------------------
|
||||
# 3) Allow existing directories
|
||||
# ----------------------------------------
|
||||
RewriteCond %{REQUEST_FILENAME} -d
|
||||
RewriteRule ^ - [L]
|
||||
|
||||
# ----------------------------------------
|
||||
# 4) Everything else → router.php
|
||||
# ----------------------------------------
|
||||
RewriteRule ^ router.php [QSA,L]
|
||||
@@ -0,0 +1,344 @@
|
||||
const selecteddiscipline = window.FREIGABE;
|
||||
|
||||
const csrf_token = window.CSDR_TOKEN;
|
||||
|
||||
let ws;
|
||||
let firstConnect = true;
|
||||
let wsOpen = false;
|
||||
const RETRY_DELAY = 5000;
|
||||
|
||||
const WSaccesstype = "rankLive";
|
||||
const WSaccess = window.FREIGABE;
|
||||
const WSaccessPermission = "R";
|
||||
|
||||
const urlAjaxNewWSToken = '/intern/scripts/ajax-create-ws-token.php';
|
||||
|
||||
async function fetchNewWSToken(accesstype, access, accessPermission) {
|
||||
try {
|
||||
const response = await fetch(urlAjaxNewWSToken, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
},
|
||||
body: new URLSearchParams({
|
||||
csrf_token,
|
||||
accesstype,
|
||||
access,
|
||||
accessPermission
|
||||
})
|
||||
});
|
||||
|
||||
if (response.status === 403) {
|
||||
console.warn("Please Re-Autenithicate. Reloading page...");
|
||||
location.reload();
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!response.ok) return null;
|
||||
|
||||
const data = await response.json();
|
||||
return data.success ? data.token : null;
|
||||
} catch (error) {
|
||||
console.error("Token fetch failed:", error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async function startWebSocket() {
|
||||
console.log("Attempting WebSocket connection...");
|
||||
|
||||
let token;
|
||||
if (firstConnect) {
|
||||
token = window.WS_ACCESS_TOKEN;
|
||||
} else {
|
||||
token = await fetchNewWSToken(WSaccesstype, WSaccess, WSaccessPermission);
|
||||
}
|
||||
|
||||
if (!token) {
|
||||
console.error("No valid token available. Retrying...");
|
||||
scheduleRetry();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
ws = new WebSocket(`wss://${window.location.hostname}/ws/?access=token&token=${token}`);
|
||||
} catch (err) {
|
||||
console.error("Malformed WebSocket URL", err);
|
||||
scheduleRetry();
|
||||
return;
|
||||
}
|
||||
|
||||
ws.onopen = () => {
|
||||
console.log("WebSocket connected!");
|
||||
if (!firstConnect) {
|
||||
displayMsg(1, "Live Syncronisation wieder verfügbar");
|
||||
}
|
||||
firstConnect = true;
|
||||
wsOpen = true;
|
||||
};
|
||||
|
||||
ws.onerror = (event) => {
|
||||
console.error("WebSocket error observed." + JSON.stringify(event));
|
||||
};
|
||||
|
||||
ws.onclose = (event) => {
|
||||
|
||||
if (firstConnect) {
|
||||
displayMsg(0, "Live Syncronisation verloren");
|
||||
}
|
||||
firstConnect = false;
|
||||
wsOpen = false;
|
||||
|
||||
scheduleRetry();
|
||||
};
|
||||
}
|
||||
|
||||
function scheduleRetry() {
|
||||
console.log(`Retrying in ${RETRY_DELAY}ms...`);
|
||||
setTimeout(startWebSocket, RETRY_DELAY);
|
||||
}
|
||||
|
||||
// Start the initial connection attempt safely
|
||||
startWebSocket();
|
||||
|
||||
let rangNotenArray = JSON.parse(window.RANG_NOTEN_ARRAY) ?? [];
|
||||
|
||||
const rangNotenId = window.RANG_NOTE_ID ?? 0;
|
||||
|
||||
const rangSort = window.RANG_SORT ?? '';
|
||||
const isLive = parseInt(window.LIVE ?? 0);
|
||||
|
||||
ws.addEventListener("message", async function(event) {
|
||||
let msgOBJ;
|
||||
|
||||
try {
|
||||
msgOBJ = JSON.parse(event.data);
|
||||
} catch (error) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Ensure it's an UPDATE type (matches your sendToGroup logic)
|
||||
if (msgOBJ?.type === "UPDATE") {
|
||||
const data = msgOBJ.payload;
|
||||
|
||||
// Check access rights
|
||||
if (data.discipline === selecteddiscipline.toLowerCase() || selecteddiscipline === 'A') {
|
||||
const noten = data.rankLive;
|
||||
|
||||
const programmId = $(`tr[data-person-id="${data.personId}"]`).closest('tbody').attr('data-programm-id') ?? null;
|
||||
|
||||
if (!isLive && noten[0][1][rangNotenId] !== undefined && rangNotenArray[programmId][data.personId] !== undefined) {
|
||||
rangNotenArray[programmId][data.personId] = noten[0][1][rangNotenId];
|
||||
applyRanks(rankProgramm(rangNotenArray, programmId, rangSort), true);
|
||||
}
|
||||
|
||||
for (const [keyG, noteGroup] of Object.entries(noten)) {
|
||||
|
||||
for (const [run, runGroup] of Object.entries(noteGroup)) {
|
||||
|
||||
for (const [key, value] of Object.entries(runGroup)) {
|
||||
|
||||
const $elements = $(`.changebleValue[data-field-type-id="${key}"][data-geraet-id="${keyG}"][data-person-id="${data.personId}"][data-run="${run}"]`);
|
||||
$elements.each(function() {
|
||||
const $el = $(this);
|
||||
|
||||
const isNullable = ($el.attr('data-noten-nullable') ?? 'false') === 'true';
|
||||
const floatValZero = parseFloat(value.replace(",", ".").replace("'", "")) === 0.0;
|
||||
|
||||
if (isNullable && floatValZero || value === '') {
|
||||
$el.text('-').addClass('emtyPlaceholder');
|
||||
} else {
|
||||
$el.text(value).removeClass('emtyPlaceholder');
|
||||
}
|
||||
|
||||
$el.removeClass('updated');
|
||||
$el.addClass('updated');
|
||||
|
||||
updateDependentVisibility($el);
|
||||
|
||||
setTimeout(() => {
|
||||
$el.removeClass('updated');
|
||||
}, 2000);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (msgOBJ?.type === "UPDATE_RANKLIVE_C_SUBABT") {
|
||||
if (!(await displayConfirm('Eine neue Gruppe ist aktiv. Fenster aktualisieren?'))) return;
|
||||
location.reload();
|
||||
}
|
||||
});
|
||||
|
||||
function rankProgramm(dataObj, programmId, order = 'DESC') {
|
||||
if (!dataObj[programmId]) {
|
||||
console.error(`ID ${programmId} not found in data.`);
|
||||
return [];
|
||||
}
|
||||
|
||||
const values = dataObj[programmId];
|
||||
|
||||
// 1. Transform and Sort
|
||||
const sorted = Object.entries(values)
|
||||
.map(([id, val]) => ({ id, val }))
|
||||
.sort((a, b) => {
|
||||
// 1. Handle null values first (Always push them to the bottom)
|
||||
if (a.val === null && b.val === null) return 0;
|
||||
if (a.val === null) return 1; // Move 'a' to a higher index (down)
|
||||
if (b.val === null) return -1; // Move 'b' to a higher index (down)
|
||||
|
||||
// 2. Normal sorting logic for valid numbers
|
||||
return order.toUpperCase() === 'ASC'
|
||||
? a.val - b.val
|
||||
: b.val - a.val;
|
||||
});
|
||||
|
||||
let lastVal = null;
|
||||
let lastRank = 0;
|
||||
|
||||
let sortedMap = [];
|
||||
|
||||
sortedMap[programmId] = sorted.map((item, index) => {
|
||||
if (item.val !== lastVal) {
|
||||
lastRank = index + 1;
|
||||
}
|
||||
|
||||
lastVal = item.val;
|
||||
|
||||
return {
|
||||
rang: lastRank,
|
||||
...item
|
||||
};
|
||||
});
|
||||
|
||||
return sortedMap;
|
||||
}
|
||||
|
||||
function rankAll(dataObj, order = 'DESC') {
|
||||
return Object.fromEntries(
|
||||
Object.entries(dataObj).map(([groupKey, values]) => {
|
||||
const sorted = Object.entries(values)
|
||||
.map(([id, val]) => ({ id, val }))
|
||||
.sort((a, b) => {
|
||||
// 1. Handle null values first (Always push them to the bottom)
|
||||
if (a.val === null && b.val === null) return 0;
|
||||
if (a.val === null) return 1; // Move 'a' to a higher index (down)
|
||||
if (b.val === null) return -1; // Move 'b' to a higher index (down)
|
||||
|
||||
// 2. Normal sorting logic for valid numbers
|
||||
return order.toUpperCase() === 'ASC'
|
||||
? a.val - b.val
|
||||
: b.val - a.val;
|
||||
});
|
||||
|
||||
let lastVal = null;
|
||||
let lastRank = 0;
|
||||
|
||||
const ranked = sorted.map((item, index) => {
|
||||
if (item.val !== lastVal) {
|
||||
lastRank = index + 1;
|
||||
}
|
||||
lastVal = item.val;
|
||||
|
||||
return {
|
||||
rang: lastRank,
|
||||
...item
|
||||
};
|
||||
});
|
||||
|
||||
return [groupKey, ranked];
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
function applyRanks(array, sort = false) {
|
||||
for (const [key, entries] of Object.entries(array)) {
|
||||
const $tbody = $(`tbody[data-programm-id="${key}"]`);
|
||||
|
||||
if ($tbody.length === 0) continue;
|
||||
|
||||
for (const [skey, entry] of Object.entries(entries)) {
|
||||
const $row = $tbody.find(`tr[data-person-id="${entry.id}"]`);
|
||||
|
||||
if ($row.length === 0) continue;
|
||||
|
||||
$row.attr('data-sort-rank', entry.rang);
|
||||
|
||||
const $cell = $row.find('.rangField');
|
||||
|
||||
const points = parseFloat(rangNotenArray[key][entry.id]);
|
||||
|
||||
const text = !isNaN(points) ? entry.rang : '';
|
||||
|
||||
$cell.text(text);
|
||||
}
|
||||
|
||||
if (sort) {
|
||||
const rows = $tbody.find('tr').get();
|
||||
|
||||
rows.sort((a, b) => {
|
||||
const rankA = parseFloat($(a).attr('data-sort-rank')) || 999;
|
||||
const rankB = parseFloat($(b).attr('data-sort-rank')) || 999;
|
||||
return rankA - rankB;
|
||||
});
|
||||
|
||||
// Re-append the sorted rows back into the tbody
|
||||
$.each(rows, (index, row) => {
|
||||
$tbody.append(row);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
applyRanks(rankAll(rangNotenArray, rangSort), true);
|
||||
|
||||
function updateDependentVisibility($changedEl) {
|
||||
const uid = $changedEl.attr('data-uid');
|
||||
if (uid === undefined || uid === null) return;
|
||||
|
||||
const $row = $changedEl.closest('tr');
|
||||
|
||||
if ($row.length !== 1) return;
|
||||
|
||||
const isVisible = $changedEl.hasClass('emtyPlaceholder');
|
||||
const doesExist = $changedEl.hasClass('nonExistentEl');
|
||||
|
||||
const $linkedEl = $row.find(`.singleValueSpan[data-linked-el-uid="${uid}"]`)
|
||||
|
||||
$linkedEl.toggleClass('emtyPlaceholder', isVisible).toggleClass('nonExistentEl', doesExist);
|
||||
}
|
||||
|
||||
function initConditionalVisibility() {
|
||||
const $table = $('table.customDisplayEditorTable');
|
||||
|
||||
const $rows = $table.find('tr');
|
||||
|
||||
$rows.each((ind, el) => {
|
||||
const $row = $(el);
|
||||
$row.find('.singleValueSpan[data-linked-el-uid]').each(function() {
|
||||
const linkedUid = $(this).data('linked-el-uid');
|
||||
|
||||
const $linkedEl = $row.find(`.singleValueSpan[data-uid="${linkedUid}"]`);
|
||||
|
||||
if ($linkedEl.length === 1) {
|
||||
updateDependentVisibility($linkedEl);
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
initConditionalVisibility();
|
||||
initConditionalVisibility();
|
||||
|
||||
const $parent = $('.allAbtContainer');
|
||||
const $children = $parent.children('.shiftedGeraetTable');
|
||||
|
||||
$children.sort((a, b) => {
|
||||
const idA = parseFloat($(a).data('geraet-index'));
|
||||
const idB = parseFloat($(b).data('geraet-index'));
|
||||
|
||||
return idA - idB;
|
||||
});
|
||||
|
||||
$parent.append($children);
|
||||
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
$request = trim(parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH), '/');
|
||||
$segments = $request === '' ? [] : explode('/', $request);
|
||||
|
||||
$segments = array_map('urldecode', $segments);
|
||||
|
||||
$type = $segments[1] ?? null;
|
||||
$slug = $segments[2] ?? null;
|
||||
|
||||
$allowed_types = ["programm", "live"];
|
||||
|
||||
if (($type !== null && !in_array($type, $allowed_types)) || count($segments) > 3) {
|
||||
http_response_code(400);
|
||||
include $baseDir . "/error-pages/400.html";
|
||||
exit;
|
||||
}
|
||||
|
||||
|
||||
define('APP_ROUTER', true);
|
||||
|
||||
require __DIR__ . "/script.php";
|
||||
@@ -0,0 +1,667 @@
|
||||
<?php
|
||||
|
||||
ini_set('display_errors', 1);
|
||||
|
||||
if (!isset($segments) || !defined('APP_ROUTER')) {
|
||||
http_response_code(403);
|
||||
include $baseDir . "/error-pages/403.php";
|
||||
exit;
|
||||
}
|
||||
|
||||
$type = $segments[1] ?? null;
|
||||
$slug = $segments[2] ?? null;
|
||||
|
||||
$allowed_types = ["programm", "live"];
|
||||
|
||||
if ($type !== null && !in_array($type, $allowed_types)) {
|
||||
http_response_code(400);
|
||||
include $baseDir . "/error-pages/400.html";
|
||||
exit;
|
||||
}
|
||||
|
||||
$baseDir = $_SERVER['DOCUMENT_ROOT'];
|
||||
|
||||
require $baseDir . '/../scripts/db/db-verbindung-script-guest.php';
|
||||
|
||||
require $baseDir . "/../scripts/db/db-tables.php";
|
||||
require $baseDir . "/../scripts/db/db-functions.php";
|
||||
|
||||
$rankLive_public = db_get_variable($guest, $db_tabelle_var, ['rankLivePublic']) ?? 0;
|
||||
|
||||
require $baseDir . '/../scripts/websocket/ws-create-token.php';
|
||||
|
||||
require_once $baseDir . '/../scripts/session_functions.php';
|
||||
|
||||
ini_wkvs_session(true);
|
||||
|
||||
$csrf_token = $_SESSION['csrf_token'] ?? '';
|
||||
|
||||
if (!$rankLive_public) {
|
||||
check_user_permission('wk_leitung', false, true);
|
||||
}
|
||||
|
||||
$is_live = $type === 'live';
|
||||
|
||||
if ($slug !== null && !$is_live) {
|
||||
|
||||
$alle_programme_db = db_select($guest, $db_tabelle_kategorien, '`programm`', 'aktiv = ?', ['1']);
|
||||
$raw_programme = array_column($alle_programme_db, 'programm', 'programm');
|
||||
|
||||
$alle_programme = [];
|
||||
foreach ($raw_programme as $prog) {
|
||||
$normalized_key = str_replace(' ', '-', strtolower($prog));
|
||||
$alle_programme[$normalized_key] = $prog;
|
||||
}
|
||||
|
||||
$req_prog = str_replace(' ', '-', strtolower($slug));
|
||||
|
||||
if (!isset($alle_programme[$req_prog])) {
|
||||
http_response_code(400);
|
||||
$message = 'Das gesuchte Programm kann nicht gefunden werden';
|
||||
include $baseDir . "/error-pages/400.php";
|
||||
exit;
|
||||
}
|
||||
|
||||
$validated_programm_name = $alle_programme[$req_prog];
|
||||
}
|
||||
|
||||
$selectedFreigabeId = 'A';
|
||||
|
||||
$json_type = $is_live ? 'rankLive-geraet' : 'rankLive-overview';
|
||||
|
||||
$jsonstr = db_select($guest, $db_tabelle_tabellen_konfiguration, 'json', 'type_slug = ?', [$json_type], '', 1);
|
||||
|
||||
$data = json_decode($jsonstr[0]['json'], true);
|
||||
|
||||
$headers = $data['header'] ?? [];
|
||||
$bodyColumns = $data['body'] ?? [];
|
||||
|
||||
$alle_programme_db = db_select($guest, $db_tabelle_kategorien, '`programm`, `id`', 'aktiv = ?', ['1']);
|
||||
$indexedProgrammes = array_column($alle_programme_db, 'programm', 'id');
|
||||
|
||||
$personData = [
|
||||
'geburtsdatum' => 'Geburtsdatum',
|
||||
'name' => 'Name',
|
||||
'vorname' => 'Vorname',
|
||||
'verein' => 'Verein',
|
||||
'programm' => 'Programm'
|
||||
];
|
||||
|
||||
$disciplines = db_select($guest, $db_tabelle_disziplinen, '*', '', [], 'start_index ASC');
|
||||
|
||||
$indexedArrayGeraete = array_column($disciplines, 'name', 'id');
|
||||
|
||||
if ($is_live) {
|
||||
$indexed_array_geraete_id_index = array_column($disciplines, 'start_index', 'id');
|
||||
|
||||
$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);
|
||||
$akt_subabt = (int) (db_get_variable($guest, $db_tabelle_var, ['wk_panel_current_subabt_admin']) ?? 1);
|
||||
|
||||
$max_subabt = count($disciplines);
|
||||
|
||||
$stmt = $guest->prepare("SELECT
|
||||
t.id,
|
||||
t.name,
|
||||
t.vorname,
|
||||
t.programm,
|
||||
t.verein,
|
||||
t.geburtsdatum,
|
||||
p.id AS programm_id,
|
||||
tabt.`turnerin_index` AS start_index,
|
||||
tabt.`geraet_id` AS startgeraet,
|
||||
tabt.`abteilung_id` AS abt_id
|
||||
FROM $db_tabelle_teilnehmende t
|
||||
LEFT JOIN $db_tabelle_kategorien p ON p.programm = t.programm
|
||||
INNER JOIN $db_tabelle_teilnehmende_gruppen tabt ON tabt.`turnerin_id` = t.`id`
|
||||
INNER JOIN $db_tabelle_gruppen abt ON abt.`id` = tabt.`abteilung_id`
|
||||
WHERE (t.bezahlt = 4 OR t.bezahltoverride = 4) AND abt.`order_index` = ?");
|
||||
|
||||
$stmt->bind_param("i", $abt);
|
||||
} elseif ($type === 'programm' && $slug !== null) {
|
||||
$stmt = $guest->prepare("SELECT
|
||||
u.id,
|
||||
u.name,
|
||||
u.vorname,
|
||||
u.programm,
|
||||
u.verein,
|
||||
u.geburtsdatum,
|
||||
p.id AS programm_id
|
||||
FROM $db_tabelle_teilnehmende u
|
||||
LEFT JOIN $db_tabelle_kategorien p ON p.programm = u.programm
|
||||
WHERE (u.bezahlt = 4 OR u.bezahltoverride = 4) AND u.programm = ?");
|
||||
|
||||
$stmt->bind_param("s", $validated_programm_name);
|
||||
} else {
|
||||
$stmt = $guest->prepare("SELECT
|
||||
u.id,
|
||||
u.name,
|
||||
u.vorname,
|
||||
u.programm,
|
||||
u.verein,
|
||||
u.geburtsdatum,
|
||||
p.id AS programm_id
|
||||
FROM $db_tabelle_teilnehmende u
|
||||
LEFT JOIN $db_tabelle_kategorien p ON p.programm = u.programm
|
||||
WHERE (u.bezahlt = 4 OR u.bezahltoverride = 4)");
|
||||
}
|
||||
|
||||
$stmt->execute();
|
||||
|
||||
$result = $stmt->get_result();
|
||||
$tures = $result->fetch_all(MYSQLI_ASSOC);
|
||||
|
||||
$stmt->close();
|
||||
|
||||
$indexedTures = [];
|
||||
|
||||
foreach ($tures as $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`
|
||||
FROM $db_tabelle_wertungen
|
||||
WHERE `is_public` = 1
|
||||
AND `wk_id` = ?");
|
||||
|
||||
$stmt->bind_param("i", $current_wk_id);
|
||||
|
||||
$stmt->execute();
|
||||
|
||||
$result = $stmt->get_result();
|
||||
$noten = $result->fetch_all(MYSQLI_ASSOC);
|
||||
|
||||
$stmt->close();
|
||||
|
||||
$indexedNoten = [];
|
||||
|
||||
$stmt = $guest->prepare("SELECT `id`, `geraete_json`, `pro_geraet`, `anzahl_laeufe_json`, `name`, `default_value` FROM $db_tabelle_wertungstypen");
|
||||
|
||||
$stmt->execute();
|
||||
|
||||
$result = $stmt->get_result();
|
||||
$notenConfig = $result->fetch_all(MYSQLI_ASSOC);
|
||||
|
||||
$stmt->close();
|
||||
|
||||
$disciplines = db_select($guest, $db_tabelle_disziplinen, '*', '', [], 'start_index ASC');
|
||||
|
||||
$indexedNotenConfig = array_column($notenConfig, null, 'id');
|
||||
|
||||
$rangNote = intval(db_get_var($guest, "SELECT `value` FROM $db_tabelle_var WHERE `name` = ?", ['rangNote']));
|
||||
|
||||
$orderBestRang = db_get_var($guest, "SELECT `value` FROM $db_tabelle_var WHERE `name` = ?", ['orderBestRang']);
|
||||
|
||||
$okValuesOrderBestRang = ["ASC", "DESC"];
|
||||
|
||||
$rangOrderOk = in_array($orderBestRang, $okValuesOrderBestRang) && intval($rangNote) > 0;
|
||||
|
||||
$notenIndexed = [];
|
||||
|
||||
foreach ($noten as $sn) {
|
||||
$notenIndexed[$sn['person_id']][$sn['geraet_id']][$sn['note_bezeichnung_id']][$sn['run_number']] = $sn['public_value'];
|
||||
}
|
||||
|
||||
$disciplinesExtended = array_merge(
|
||||
[["id" => 0, "name" => "None"]],
|
||||
$disciplines
|
||||
);
|
||||
|
||||
$arrayIndexedNoten = [];
|
||||
|
||||
|
||||
$wkName = db_get_var($guest, "SELECT `value` FROM $db_tabelle_var WHERE `name` = ?", ['wkName']);
|
||||
|
||||
$grouped = [];
|
||||
|
||||
function groop_by_key($array, $array_key, bool $int = false): array
|
||||
{
|
||||
$grouped = [];
|
||||
if ($int) {
|
||||
foreach ($array as $entry) {
|
||||
$key = (int) $entry[$array_key];
|
||||
$grouped[$key][] = $entry;
|
||||
}
|
||||
} else {
|
||||
foreach ($array as $entry) {
|
||||
$key = $entry[$array_key];
|
||||
$grouped[$key][] = $entry;
|
||||
}
|
||||
}
|
||||
return $grouped;
|
||||
}
|
||||
|
||||
if ($is_live) {
|
||||
|
||||
$grouped = groop_by_key($tures, 'startgeraet', true);
|
||||
|
||||
} else {
|
||||
|
||||
$grouped = groop_by_key($tures, 'programm');
|
||||
|
||||
foreach ($grouped as $key => $group) {
|
||||
$orderIndexes[$key] = $indexedProgramme[$key]['order_index'] ?? 10000;
|
||||
}
|
||||
|
||||
if (isset($orderIndexes)) {
|
||||
uksort($grouped, function ($a, $b) use ($orderIndexes) {
|
||||
return $orderIndexes[$a] <=> $orderIndexes[$b];
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
foreach ($grouped as $entry_key => $entries_group):
|
||||
|
||||
|
||||
endforeach;
|
||||
|
||||
$timeZone = new \DateTimeZone('Europe/Zurich');
|
||||
|
||||
$formatter = new \IntlDateFormatter(
|
||||
'de_CH',
|
||||
\IntlDateFormatter::FULL,
|
||||
\IntlDateFormatter::NONE,
|
||||
$timeZone
|
||||
);
|
||||
|
||||
$display_over_array = [];
|
||||
|
||||
function constructSingleValueSpanPublic($token, $type, $row, $selected_geraet_id = 0)
|
||||
{
|
||||
global $formatter;
|
||||
global $personData;
|
||||
global $arrayIndexedNoten;
|
||||
global $timeZone;
|
||||
global $display_over_array;
|
||||
global $indexedNotenConfig;
|
||||
global $indexedProgramme;
|
||||
|
||||
$text = '';
|
||||
$classes = 'singleValueSpan';
|
||||
|
||||
$bold = $token['bold'] ?? false;
|
||||
|
||||
$font_size = intval($token['fontSize'] ?? 0);
|
||||
|
||||
$min_display_width = intval($token['minDisplayWidth'] ?? 1) - 1;
|
||||
|
||||
$linked_el_uid = (int) ($token['linkedElUid'] ?? 0);
|
||||
|
||||
$uid = (int) ($token['uid'] ?? 0);
|
||||
|
||||
|
||||
$style = '';
|
||||
|
||||
if ($font_size > 0) {
|
||||
$style = ' style="font-size: ' . $font_size . 'px"';
|
||||
}
|
||||
|
||||
if ($min_display_width > 0) {
|
||||
$classes .= ' displayOver' . $min_display_width . 'px';
|
||||
$display_over_array[] = $min_display_width;
|
||||
}
|
||||
|
||||
$dataAttributes = 'data-bold="' . htmlspecialchars($bold) . '" data-uid="'. $uid .'"';
|
||||
|
||||
if ($linked_el_uid > 0) {
|
||||
$dataAttributes .= ' data-linked-el-uid="' . $linked_el_uid . '"';
|
||||
}
|
||||
|
||||
switch ($type) {
|
||||
case 'text':
|
||||
$text = $token['text'] ?? '';
|
||||
$classes .= ' staticText';
|
||||
break;
|
||||
|
||||
case 'sortData':
|
||||
$column = $token['column'] ?? '';
|
||||
switch ($column) {
|
||||
case "rang":
|
||||
$classes .= ' rangField';
|
||||
break;
|
||||
case "startindex":
|
||||
$text = $row['calculedstartindex'];
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'personData':
|
||||
$column = $token['column'] ?? '';
|
||||
|
||||
if ($column === 'jahrgang') {
|
||||
$geburtsdatum = $row['geburtsdatum'] ?? '';
|
||||
$date = new \DateTimeImmutable($geburtsdatum, $timeZone);
|
||||
$formatter->setPattern('yyyy');
|
||||
|
||||
$text = $formatter->format($date);
|
||||
} else {
|
||||
$text = array_key_exists($column, $personData) ? $row[$column] ?? '' : '';
|
||||
|
||||
if ($column === 'geburtsdatum') {
|
||||
$date = new \DateTimeImmutable($text, $timeZone);
|
||||
$formatter->setPattern(($token['phpDateFormat'] ?? '' !== '') ? $token['phpDateFormat'] : 'd. MMMM YYYY');
|
||||
|
||||
$text = $formatter->format($date);
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case 'notenGeraetData':
|
||||
|
||||
$field_type_id = intval($token['field-type-id'] ?? 0);
|
||||
$run = intval($token['run'] ?? 0);
|
||||
$person_id = $row['id'];
|
||||
|
||||
$programm_id = $indexedProgramme[$row['programm']]['id'] ?? 0;
|
||||
|
||||
$runsJSON = json_decode($indexedNotenConfig[$field_type_id]['anzahl_laeufe_json'] ?? '', true) ?? [];
|
||||
|
||||
$max_run = $runsJSON[$selected_geraet_id][$programm_id] ?? $runsJSON[$selected_geraet_id]['all'] ?? $runsJSON['default'] ?? 0;
|
||||
|
||||
if ($run > $max_run) {
|
||||
$classes .= ' nonExistentEl';
|
||||
|
||||
} else {
|
||||
$notenNullable = ($token['notenNullable'] ?? 'false') === 'true';
|
||||
|
||||
$dataAttributes .= ' data-field-type-id="' . $field_type_id . '"';
|
||||
$dataAttributes .= ' data-geraet-id="' . $selected_geraet_id . '"';
|
||||
$dataAttributes .= ' data-person-id="' . $person_id . '"';
|
||||
$dataAttributes .= ' data-run="' . $run . '"';
|
||||
$dataAttributes .= ' data-noten-nullable="' . (($notenNullable) ? "true" : "false") . '"';
|
||||
|
||||
$text = $arrayIndexedNoten[$person_id][$selected_geraet_id][$field_type_id][$run]['value'] ?? null;
|
||||
|
||||
if ($notenNullable && $text !== null && floatval($text) === 0.0) {
|
||||
$text = ' ';
|
||||
}
|
||||
|
||||
if ($text === null) {
|
||||
$classes .= ' emtyPlaceholder';
|
||||
$text = '-';
|
||||
}
|
||||
|
||||
$classes .= ' changebleValue';
|
||||
}
|
||||
|
||||
|
||||
break;
|
||||
|
||||
|
||||
case 'notenData':
|
||||
$field_type_id = intval($token['field-type-id'] ?? 0);
|
||||
$geraet_id = intval($token['geraet-id'] ?? 0);
|
||||
$run = intval($token['run'] ?? 0);
|
||||
$person_id = $row['id'];
|
||||
$notenNullable = ($token['notenNullable'] ?? 'false') === 'true';
|
||||
|
||||
$dataAttributes .= ' data-field-type-id="' . $field_type_id . '"';
|
||||
$dataAttributes .= ' data-geraet-id="' . $geraet_id . '"';
|
||||
$dataAttributes .= ' data-person-id="' . $person_id . '"';
|
||||
$dataAttributes .= ' data-run="' . $run . '"';
|
||||
$dataAttributes .= ' data-noten-nullable="' . (($notenNullable) ? "true" : "false") . '"';
|
||||
|
||||
$text = $arrayIndexedNoten[$person_id][$geraet_id][$field_type_id][$run]['value'] ?? null;
|
||||
|
||||
if ($notenNullable && $text !== null && floatval($text) === 0.0) {
|
||||
$text = ' ';
|
||||
}
|
||||
|
||||
if ($text === null) {
|
||||
$classes .= ' emtyPlaceholder';
|
||||
$text = '-';
|
||||
}
|
||||
|
||||
$classes .= ' changebleValue';
|
||||
|
||||
break;
|
||||
}
|
||||
?>
|
||||
<span class="<?= $classes ?>" <?= $dataAttributes; ?><?= $style ?>><?= htmlspecialchars($text); ?></span>
|
||||
<?php }
|
||||
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="de-ch">
|
||||
|
||||
<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">
|
||||
<link rel="stylesheet" href="/intern/css/custom-msg-display.css">
|
||||
<link href="/files/fonts/fonts.css" rel="stylesheet">
|
||||
<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/custom-msg-display.js"></script>
|
||||
<script src="/js/header.js"></script>
|
||||
<title>RankLive - <?= htmlspecialchars($wkName) ?></title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<section class="bgSection">
|
||||
<?php include $baseDir . '/../scripts/public-elements/rankLive-header.php'; ?>
|
||||
<?php if (count($indexedTures) < 1) : ?>
|
||||
<h3>Noch keine Daten verfügbar</h3>
|
||||
<?php exit; ?>
|
||||
<?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 = '';
|
||||
$extra_table_classes = '';
|
||||
|
||||
if ($is_live) {
|
||||
$entries_with_calculated_index = [];
|
||||
|
||||
// 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)
|
||||
$maxStartIndexCache = [];
|
||||
|
||||
foreach ($entries_group as $row) {
|
||||
$old_geraet_id = $row['startgeraet'];
|
||||
|
||||
$old_geraet_index = $indexed_array_geraete_id_index[$row['startgeraet']];
|
||||
$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) {
|
||||
$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;
|
||||
}
|
||||
}
|
||||
|
||||
$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;
|
||||
|
||||
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;
|
||||
|
||||
$arrayIndexedNoten[intval($row['id'])][intval($discipline['id'])][intval($snC['id'])][intval($r)] = ["value" => $normalizedNote];
|
||||
endfor;
|
||||
endforeach;
|
||||
endforeach;
|
||||
endforeach; ?>
|
||||
<div class="singleAbtDiv<?= $extra_table_classes ?>" <?= $table_data_geraet_label ?>>
|
||||
<?php if (!($type === 'programm' && $slug !== null)) : ?>
|
||||
<h2 class="headerAbt"><?= ($is_live) ? $indexedArrayGeraete[$shifted_geraet_id] ?? '' : $entry_key ?? '' ?></h2>
|
||||
<?php endif; ?>
|
||||
<?php $geraet_id_for_live_view = $is_live ? $shifted_geraet_id : 0 ?>
|
||||
<div class="tableWraper">
|
||||
<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
|
||||
$class = '';
|
||||
|
||||
if (isset($headers[$ind]['minDisplayWidth']) && $headers[$ind]['minDisplayWidth'] > 1) {
|
||||
$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>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<div class="tableCutter"></div>
|
||||
</section>
|
||||
<style>
|
||||
<?php
|
||||
$unique_display_over_array = array_unique($display_over_array);
|
||||
|
||||
foreach ($unique_display_over_array as $s_size):
|
||||
$s_size = (int)$s_size;
|
||||
?>
|
||||
@media (max-width: <?= $s_size ?>px) {
|
||||
.displayOver<?= $s_size ?>px {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
<?php endforeach; ?>
|
||||
</style>
|
||||
<script>
|
||||
window.FREIGABE = "<?= $selectedFreigabeId; ?>";
|
||||
window.CSDR_TOKEN = "<?= $csrf_token; ?>";
|
||||
window.WS_ACCESS_TOKEN = "<?= generateWSReadToken('rankLive', $selectedFreigabeId) ?>";
|
||||
window.AKTUELLES_JAHR = "<?= $current_wk_id ?>";
|
||||
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_NOTE_ID = <?= $rangNote ?>;
|
||||
window.LIVE = <?= (int) $is_live ?>;
|
||||
</script>
|
||||
|
||||
<script src="/RankLive/js/script.js"></script>
|
||||
|
||||
</body>
|
||||
Reference in New Issue
Block a user