Upload files to "includes"

This commit is contained in:
2026-01-30 14:32:00 +00:00
parent 5049d22824
commit 95cb79cf0c
4 changed files with 1736 additions and 0 deletions

View File

@@ -0,0 +1,421 @@
<?php
// Zuteilungslogik für Konficastle Workshopwahl
function kc_run_zuteilung($wahl_id) {
global $wpdb;
$prefix = $wpdb->prefix;
$wahl_id = intval($wahl_id);
error_log('kc_run_zuteilung: Start für Wahl '.$wahl_id);
// 0. Entferne alte Zuteilungen für diese Wahl
$wpdb->delete("{$prefix}kc_zuteilung", ['wahl_id' => $wahl_id]);
// 1. Teilnehmer einlesen (alle Phasen werden getrennt verarbeitet)
$phasen = intval($wpdb->get_var($wpdb->prepare("SELECT anzahl_einheiten FROM {$prefix}kc_wahlen WHERE id=%d", $wahl_id)));
if($phasen < 1) $phasen = 1;
// 2. Workshops einlesen (inkl. Kapazität)
$workshops_rows = $wpdb->get_results($wpdb->prepare(
"SELECT ws.id, ws.name, ws.max_teilnehmer FROM {$prefix}kc_workshops ws JOIN {$prefix}kc_wahl_workshops ww ON ws.id = ww.workshop_id WHERE ww.wahl_id=%d",
$wahl_id
));
$workshops = [];
$workshop_caps = [];
foreach($workshops_rows as $w) {
$workshops[$w->id] = $w;
$workshop_caps[$w->id] = intval($w->max_teilnehmer);
}
// 3. Force-Zuteilungen lesen (global, nach Phase filtern)
$forces_all = $wpdb->get_results($wpdb->prepare("SELECT * FROM {$prefix}kc_force_zuteilung WHERE wahl_id=%d", $wahl_id));
// Prozessiere jede Phase einzeln
for($phase=1; $phase<=$phasen; $phase++) {
error_log("kc_run_zuteilung: Verarbeite Phase $phase");
// 1) Teilnehmer einlesen (alle TN dieser Wahl+Phase)
$teilnehmer = $wpdb->get_results($wpdb->prepare(
"SELECT * FROM {$prefix}kc_teilnehmer WHERE wahl_id=%d AND phase=%d",
$wahl_id, $phase
));
// Prepare helper maps
$teilnehmer_by_id = [];
foreach($teilnehmer as $t) $teilnehmer_by_id[$t->id] = $t;
// keep a full copy for later reassignments if needed
$all_teilnehmer_by_id = $teilnehmer_by_id;
// 2) Kopie der Kapazitäten für diese Phase (werden während Zuordnung reduziert)
$caps = $workshop_caps;
// 3) Force-Zuteilungen anwenden (nur für diese Phase)
foreach($forces_all as $f) {
if(intval($f->phase) !== $phase) continue;
$tn_id = intval($f->teilnehmer_id);
$ws_id = intval($f->workshop_id);
if(!isset($teilnehmer_by_id[$tn_id])) continue; // Teilnehmer nicht in dieser Phase
// Nur wenn Workshop existiert und noch Kapazität >=1
if(isset($caps[$ws_id]) && $caps[$ws_id] > 0) {
$t = $teilnehmer_by_id[$tn_id];
$wpdb->insert("{$prefix}kc_zuteilung", [
'wahl_id' => $wahl_id,
'teilnehmer_id' => $t->id,
'vorname' => $t->vorname,
'nachname' => $t->nachname,
'phase' => $phase,
'workshop_id' => $ws_id,
'wunsch_rang' => 0
]);
$caps[$ws_id]--;
// Entferne TN aus Liste (er ist bereits zugeteilt)
unset($teilnehmer_by_id[$tn_id]);
}
}
// 4) Teilnehmer mischen (zufällige Reihenfolge)
$remaining = array_values($teilnehmer_by_id);
if(count($remaining) > 1) shuffle($remaining);
// Hilfsfunktion: Try assign participant to workshop if cap>0
$assign = function($tn, $ws_id, $rang) use (&$caps, $wpdb, $prefix, $wahl_id, $phase, &$assigned_ids) {
if(!isset($caps[$ws_id]) || $caps[$ws_id] <= 0) return false;
$wpdb->insert("{$prefix}kc_zuteilung", [
'wahl_id' => $wahl_id,
'teilnehmer_id' => $tn->id,
'vorname' => $tn->vorname,
'nachname' => $tn->nachname,
'phase' => $phase,
'workshop_id' => $ws_id,
'wunsch_rang' => $rang
]);
$caps[$ws_id]--;
return true;
};
// 5) Wunschrunden 1..3
for($wunsch=1;$wunsch<=3;$wunsch++) {
$not_assigned = [];
foreach($remaining as $tn) {
$ws_choice = intval($tn->{"wunsch$wunsch"});
if($ws_choice > 0 && isset($caps[$ws_choice]) && $caps[$ws_choice] > 0) {
$assigned = $assign($tn, $ws_choice, $wunsch);
if(!$assigned) $not_assigned[] = $tn;
} else {
$not_assigned[] = $tn;
}
}
$remaining = $not_assigned;
// optional: reshuffle after each round to keep fairness
if(count($remaining) > 1) shuffle($remaining);
}
// 6) Restliche zufällig verteilen (auf alle freie Workshops)
$freie = array_keys(array_filter($caps, function($c){return $c>0;}));
foreach($remaining as $tn) {
$freie = array_keys(array_filter($caps, function($c){return $c>0;}));
if(count($freie) === 0) {
// Kein Platz mehr: TN bleibt unzugeordnet
$wpdb->insert("{$prefix}kc_zuteilung", [
'wahl_id' => $wahl_id,
'teilnehmer_id' => $tn->id,
'vorname' => $tn->vorname,
'nachname' => $tn->nachname,
'phase' => $phase,
'workshop_id' => null,
'wunsch_rang' => -1
]);
continue;
}
$ws_id = $freie[array_rand($freie)];
$assign($tn, $ws_id, 99);
}
// 6.5 Consolidate workshops that did not reach their minimal Teilnehmerzahl
// Load minimal requirements for workshops in this Wahl
$ws_ids_in_wahl = array_keys($workshops);
if (!empty($ws_ids_in_wahl)) {
$placeholders = implode(',', array_fill(0, count($ws_ids_in_wahl), '%d'));
$min_rows = $wpdb->get_results($wpdb->prepare("SELECT id, min_teilnehmer FROM {$prefix}kc_workshops WHERE id IN ($placeholders)", $ws_ids_in_wahl));
$min_map = [];
foreach($min_rows as $r) $min_map[intval($r->id)] = intval($r->min_teilnehmer);
// Count current assignments per workshop
$assigned_counts_raw = $wpdb->get_results($wpdb->prepare("SELECT workshop_id, COUNT(*) AS cnt FROM {$prefix}kc_zuteilung WHERE wahl_id=%d AND phase=%d AND workshop_id IS NOT NULL GROUP BY workshop_id", $wahl_id, $phase), ARRAY_A);
$assigned_counts = [];
foreach($assigned_counts_raw as $ar) $assigned_counts[intval($ar['workshop_id'])] = intval($ar['cnt']);
// Find failing workshops (assigned >0 but < min)
$failing = [];
foreach($ws_ids_in_wahl as $wsid) {
$min_req = intval($min_map[$wsid] ?? 0);
$cnt = intval($assigned_counts[$wsid] ?? 0);
if ($cnt > 0 && $min_req > 0 && $cnt < $min_req) {
$failing[] = $wsid;
}
}
if (!empty($failing)) {
// collect participants from failing workshops
$to_reassign = [];
foreach($failing as $fw) {
$rows = $wpdb->get_results($wpdb->prepare("SELECT teilnehmer_id FROM {$prefix}kc_zuteilung WHERE wahl_id=%d AND phase=%d AND workshop_id=%d", $wahl_id, $phase, $fw));
foreach($rows as $r) $to_reassign[] = intval($r->teilnehmer_id);
}
if (!empty($to_reassign)) {
// remove those assignments
$fw_placeholders = implode(',', array_map('intval', $failing));
$wpdb->query("DELETE FROM {$prefix}kc_zuteilung WHERE wahl_id=".intval($wahl_id)." AND phase=".intval($phase)." AND workshop_id IN ($fw_placeholders)");
// free capacity for the failing workshops
foreach($failing as $fw) {
$freed = intval($assigned_counts[$fw] ?? 0);
if (!isset($caps[$fw])) $caps[$fw] = 0;
$caps[$fw] += $freed;
}
// Try to reassign each participant preferring their wishes 1..3
foreach($to_reassign as $tid) {
$tn = isset($all_teilnehmer_by_id[$tid]) ? $all_teilnehmer_by_id[$tid] : $wpdb->get_row($wpdb->prepare("SELECT * FROM {$prefix}kc_teilnehmer WHERE id=%d", $tid));
if (!$tn) continue;
$reassigned = false;
for($w=1;$w<=3;$w++) {
$choice = intval($tn->{"wunsch$w"});
if ($choice > 0 && isset($caps[$choice]) && $caps[$choice] > 0) {
$assigned = $assign($tn, $choice, $w);
if ($assigned) { $reassigned = true; break; }
}
}
if ($reassigned) continue;
// otherwise assign to any workshop with free capacity
$available = array_keys(array_filter($caps, function($c){return $c>0;}));
if (!empty($available)) {
$target = $available[array_rand($available)];
$assign($tn, $target, 99);
continue;
}
// lastly, mark as unassigned
$wpdb->insert("{$prefix}kc_zuteilung", [
'wahl_id' => $wahl_id,
'teilnehmer_id' => $tn->id,
'vorname' => $tn->vorname,
'nachname' => $tn->nachname,
'phase' => $phase,
'workshop_id' => null,
'wunsch_rang' => -1
]);
}
}
}
}
// 7) Kapazitätsprüfung (Debug / Log)
foreach($caps as $wsid=>$capleft) {
if($capleft < 0) {
error_log("kc_run_zuteilung: Überbuchung Workshop $wsid in Wahl $wahl_id Phase $phase (restcap=$capleft)");
}
}
// 8) Ergebnis: alles in DB geschrieben (kc_zuteilung). Logge Zusammenfassung
$total_assigned = $wpdb->get_var($wpdb->prepare("SELECT COUNT(*) FROM {$prefix}kc_zuteilung WHERE wahl_id=%d AND phase=%d AND workshop_id IS NOT NULL", $wahl_id, $phase));
$total_unassigned = $wpdb->get_var($wpdb->prepare("SELECT COUNT(*) FROM {$prefix}kc_zuteilung WHERE wahl_id=%d AND phase=%d AND workshop_id IS NULL", $wahl_id, $phase));
error_log("kc_run_zuteilung: Phase $phase - zugeteilt: $total_assigned, ohne Platz: $total_unassigned");
}
error_log('kc_run_zuteilung: Fertig für Wahl '.$wahl_id);
}
function kc_execute_blocks($blocks, &$context, &$i = 0) {
$count = count($blocks);
while($i < $count) {
$block = $blocks[$i];
$block_name = is_array($block) && isset($block['block']) ? $block['block'] : (is_string($block) && strpos($block, ':') !== false ? explode(':', $block, 2)[0] : $block);
switch($block_name) {
case 'repeat':
$repeat_count = 3;
if(is_array($block) && isset($block['repeat_count'])) {
$repeat_count = intval($block['repeat_count']);
} elseif(is_string($block) && strpos($block, ':') !== false) {
$parts = explode(':', $block, 2);
$repeat_count = intval($parts[1]);
}
$i++;
$start = $i;
$inner = [];
$depth = 1;
while($i < $count && $depth > 0) {
$inner_block = $blocks[$i];
$inner_block_name = is_array($inner_block) && isset($inner_block['block']) ? $inner_block['block'] : (is_string($inner_block) && strpos($inner_block, ':') !== false ? explode(':', $inner_block, 2)[0] : $inner_block);
if($inner_block_name === 'repeat') $depth++;
if($inner_block_name === 'endrepeat') $depth--;
if($depth > 0) $inner[] = $inner_block;
$i++;
}
for($r=0;$r<$repeat_count;$r++) {
$j = 0;
kc_execute_blocks($inner, $context, $j);
}
break;
case 'for_teilnehmer':
$i++;
$start = $i;
$inner = [];
$depth = 1;
while($i < $count && $depth > 0) {
if($blocks[$i] === 'for_teilnehmer') $depth++;
if($blocks[$i] === 'endfor_teilnehmer') $depth--;
if($depth > 0) $inner[] = $blocks[$i];
$i++;
}
foreach($context['teilnehmer'] as $tn) {
$context['tn'] = $tn;
$j = 0;
kc_execute_blocks($inner, $context, $j);
}
break;
case 'for_workshop':
$i++;
$start = $i;
$inner = [];
$depth = 1;
while($i < $count && $depth > 0) {
if($blocks[$i] === 'for_workshop') $depth++;
if($blocks[$i] === 'endfor_workshop') $depth--;
if($depth > 0) $inner[] = $blocks[$i];
$i++;
}
foreach($context['workshops'] as $ws) {
$context['ws'] = $ws;
$j = 0;
kc_execute_blocks($inner, $context, $j);
}
break;
case 'for_wunsch':
$i++;
$start = $i;
$inner = [];
$depth = 1;
while($i < $count && $depth > 0) {
if($blocks[$i] === 'for_wunsch') $depth++;
if($blocks[$i] === 'endfor_wunsch') $depth--;
if($depth > 0) $inner[] = $blocks[$i];
$i++;
}
for($w=1;$w<=3;$w++) {
$context['wunsch_nr'] = $w;
$j = 0;
kc_execute_blocks($inner, $context, $j);
}
break;
// --- Hier die eigentliche Blocklogik einfügen ---
case 'shuffle':
$rest = array_filter($context['teilnehmer'], function($tn) use ($context) {
return !in_array($tn->id, $context['zugeteilt_ids']);
});
$rest = array_values($rest);
if(count($rest) > 1) {
shuffle($rest);
$neu = [];
$i2 = 0;
foreach($context['teilnehmer'] as $tn) {
if(!in_array($tn->id, $context['zugeteilt_ids'])) {
$neu[] = $rest[$i2++];
} else {
$neu[] = $tn;
}
}
$context['teilnehmer'] = $neu;
}
$i++;
break;
case 'force':
foreach($context['forces'] as $f) {
$tn = $context['wpdb']->get_row("SELECT * FROM {$context['prefix']}kc_teilnehmer WHERE id=".intval($f->teilnehmer_id));
if(!$tn) continue;
if(isset($context['workshop_caps'][$f->workshop_id]) && $context['workshop_caps'][$f->workshop_id] > 0 && !in_array($tn->id, $context['zugeteilt_ids'])) {
$context['wpdb']->insert("{$context['prefix']}kc_zuteilung", [
'wahl_id' => $context['wahl_id'],
'teilnehmer_id' => $tn->id,
'vorname' => $tn->vorname,
'nachname' => $tn->nachname,
'phase' => $context['phase'],
'workshop_id' => $f->workshop_id,
'wunsch_rang' => 0
]);
$context['workshop_caps'][$f->workshop_id]--;
$context['zugeteilt_ids'][] = $tn->id;
}
}
$i++;
break;
case 'wunsch1':
case 'wunsch1_kapazitaet':
case 'wunsch2':
case 'wunsch2_kapazitaet':
case 'wunsch3':
case 'wunsch3_kapazitaet':
$wunsch_num = ($block === 'wunsch1' || $block === 'wunsch1_kapazitaet') ? 1 : (($block === 'wunsch2' || $block === 'wunsch2_kapazitaet') ? 2 : 3);
foreach($context['teilnehmer'] as $tn) {
if(in_array($tn->id, $context['zugeteilt_ids'])) continue;
$ws_id = intval($tn->{"wunsch$wunsch_num"});
if(isset($context['workshop_caps'][$ws_id]) && $context['workshop_caps'][$ws_id] > 0) {
$context['wpdb']->insert("{$context['prefix']}kc_zuteilung", [
'wahl_id' => $context['wahl_id'],
'teilnehmer_id' => $tn->id,
'vorname' => $tn->vorname,
'nachname' => $tn->nachname,
'phase' => $context['phase'],
'workshop_id' => $ws_id,
'wunsch_rang' => $wunsch_num
]);
$context['workshop_caps'][$ws_id]--;
$context['zugeteilt_ids'][] = $tn->id;
}
}
$i++;
break;
case 'random':
$freie_workshops = array_keys(array_filter($context['workshop_caps'], function($cap){return $cap>0;}));
$rest = array_filter($context['teilnehmer'], function($tn) use ($context) {
return !in_array($tn->id, $context['zugeteilt_ids']);
});
$rest = array_values($rest);
foreach($rest as $tn) {
if(count($freie_workshops)>0) {
$ws_id = $freie_workshops[array_rand($freie_workshops)];
$context['wpdb']->insert("{$context['prefix']}kc_zuteilung", [
'wahl_id' => $context['wahl_id'],
'teilnehmer_id' => $tn->id,
'vorname' => $tn->vorname,
'nachname' => $tn->nachname,
'phase' => $context['phase'],
'workshop_id' => $ws_id,
'wunsch_rang' => 99
]);
$context['workshop_caps'][$ws_id]--;
$context['zugeteilt_ids'][] = $tn->id;
$freie_workshops = array_keys(array_filter($context['workshop_caps'], function($cap){return $cap>0;}));
} else {
$context['wpdb']->insert("{$context['prefix']}kc_zuteilung", [
'wahl_id' => $context['wahl_id'],
'teilnehmer_id' => $tn->id,
'vorname' => $tn->vorname,
'nachname' => $tn->nachname,
'phase' => $context['phase'],
'workshop_id' => null,
'wunsch_rang' => -1
]);
}
}
$i++;
break;
default:
$i++;
break;
}
}
}