initial commit

This commit is contained in:
2026-07-22 01:12:45 +02:00
commit 124f2746ac
357 changed files with 159669 additions and 0 deletions
+644
View File
@@ -0,0 +1,644 @@
<?php
class Data {
public $id;
private $pdo;
private $icloudCalendarUrl = "https://p144-caldav.icloud.com/18998614209/calendars/ea9819c0-518a-4f23-ba11-476f639fd66e/";
private $icloudUser = "itzanjuma@gmail.com";
private $icloudPass = "ywce-toxv-whvv-iorx";
public function __construct($pdo) {
$this->pdo = $pdo;
}
public function renderTemplate(string $template, array $vars): string {
foreach ($vars as $key => $value) {
$template = preg_replace(
'/{{\s*' . preg_quote($key, '/') . '\s*}}/',
(string)$value,
$template
);
}
return $template;
}
public function getDataByID($id, $column){
$sql = "SELECT * FROM users WHERE id = '" . $id . "'";
foreach($this->pdo->query($sql) as $row) {
return $row[$column];
}
}
public function getTerminDataById($id, $column){
$sql = "SELECT * FROM termine WHERE id = '" . $id . "'";
foreach($this->pdo->query($sql) as $row) {
return $row[$column];
}
}
public function getSettingsData($column){
$sql = "SELECT * FROM settings_calendar WHERE id = '1'";
foreach($this->pdo->query($sql) as $row) {
return $row[$column];
}
}
public function timestampToDate($timestamp){
return date('d.m.Y H:i', $timestamp);
}
/* ======================================
KALENDER HASH BERECHNEN (zentral)
====================================== */
private function buildCalendarHashFromTermin(array $t): string {
$data = [
'start' => (int)$t['startzeit'],
'end' => (int)$t['endzeit'],
'title' => trim((string)$t['location']),
'desc' => trim((string)$t['beschreibung']),
];
return hash('sha256', json_encode($data));
}
/* ======================================
KALENDER SYNC STATUS SPEICHERN
====================================== */
private function storeCalendarSyncState(int $terminId, array $terminRow): void
{
$hash = $this->buildCalendarHashFromTermin($terminRow);
$eventId = "termin-$terminId@anjuma";
$stmt = $this->pdo->prepare("
UPDATE termine SET
calendar_event_id = :eid,
calendar_hash = :hash,
calendar_synced_at = :ts
WHERE id = :id
");
$stmt->execute([
':eid' => $eventId,
':hash' => $hash,
':ts' => time(),
':id' => $terminId
]);
}
public function getTerminLabel($id) {
$sql = "SELECT status FROM termine WHERE id = :id";
$stmt = $this->pdo->prepare($sql);
$stmt->execute([':id' => $id]);
$status = $stmt->fetchColumn();
return match ((int)$status) {
0 => "status-open", // Blau
1 => "status-ready", // Gelb
2 => "status-done", // Grün
3 => "status-cancel", // Rot
default => "",
};
}
public function getOnlineStatusLabel($id){
$sql = "SELECT * FROM users WHERE id = '" . $id . "'";
foreach($this->pdo->query($sql) as $row) {
if($row['online'] == 0){
return "badge-danger";
}else{
return "badge-success";
}
}
}
public function getTodoStatusLabel($id){
$sql = "SELECT * FROM todo WHERE id = '" . $id . "'";
foreach($this->pdo->query($sql) as $row) {
if($row['status'] == 0){
return "danger";
}
if($row['status'] == 1){
return "warning";
}
if($row['status'] == 2){
return "success";
}
}
}
private function getLastEditorInfo($terminId)
{
$sql = $this->pdo->prepare("
SELECT user_id, timestamp
FROM changes
WHERE termin_id = :id
ORDER BY timestamp DESC
LIMIT 1
");
$sql->execute([':id' => $terminId]);
$row = $sql->fetch(PDO::FETCH_ASSOC);
if (!$row) {
return [
'name' => '—',
'date' => '—'
];
}
return [
'name' => $this->getDataByID($row['user_id'], "name"),
'date' => date("d.m.Y H:i", $row['timestamp'])
];
}
private function caldavPut($url, $ics, $isUpdate = false)
{
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_USERPWD, $this->icloudUser . ":" . $this->icloudPass);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2);
curl_setopt($ch, CURLOPT_HTTPAUTH, CURLAUTH_BASIC);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "PUT");
curl_setopt($ch, CURLOPT_HTTPHEADER, $isUpdate ? [
"Content-Type: text/calendar"
// KEIN If-None-Match bei Updates!
] : [
"Content-Type: text/calendar",
"If-None-Match: *" // nur bei neuen Events
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, $ics);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$err = curl_error($ch);
curl_close($ch);
// 🔍 DEBUG VOR dem return
$this->debugLog("PUT to $url | status=$status | error=$err | isUpdate=" . ($isUpdate ? "1" : "0"));
if ($status !== 201 && $status !== 204) {
$this->debugLog("Response body: " . substr((string)$response, 0, 500));
}
return $status === 201 || $status === 204;
}
public function createCalendarEvent($id, $startTs, $endTs, $location, $beschreibung)
{
$uid = "termin-$id@anjuma";
$tz = 'Europe/Berlin';
$dtstamp = gmdate('Ymd\THis\Z');
// Sicherheits-Fallback, falls endTs 0 ist
if (empty($endTs) || $endTs <= 0) {
$endTs = $startTs + 2 * 3600; // +2 Stunden
}
// Start/Ende in lokaler Zeit
$dtstartStr = (new DateTime("@$startTs"))
->setTimezone(new DateTimeZone($tz))
->format('Ymd\THis');
$dtendStr = (new DateTime("@$endTs"))
->setTimezone(new DateTimeZone($tz))
->format('Ymd\THis');
$location = $this->icsEscape($location);
$beschreibung = $this->icsEscape($beschreibung);
// Status-Name bestimmen
$statusRaw = $this->getTerminDataById($id, "status");
$statusName = [
0 => "Offen",
1 => "Bereit",
2 => "Erledigt",
3 => "Storniert"
][$statusRaw] ?? "Unbekannt";
// Ersteller-Name holen
$creatorId = $this->getTerminDataById($id, "user_id");
$creatorName = $this->getDataByID($creatorId, "name");
// Letzten Bearbeiter holen
$editInfo = $this->getLastEditorInfo($id);
// Gage holen
$gage = $this->getTerminDataById($id, "gage");
$vars = [
'id' => $id,
'status' => $statusName,
'locationtype' => $this->getTerminDataById($id, "locationtype"),
'location' => $this->getTerminDataById($id, "location"),
'startzeit' => date('d.m.Y H:i', $this->getTerminDataById($id, "startzeit")),
'endzeit' => date('H:i', $this->getTerminDataById($id, "endzeit")),
'gage' => $this->getTerminDataById($id, "gage"),
'desc' => $this->getTerminDataById($id, "beschreibung"),
'creator' => $creatorName,
'last_editor' => $editInfo['name'],
'last_edited' => $editInfo['date']
];
$summary = $this->renderTemplate($this->getSettingsData("title_template"), $vars);
$beschreibungFull = $this->renderTemplate($this->getSettingsData("description_template"), $vars);
$beschreibungFull = $this->icsEscape($beschreibungFull);
$ics =
"BEGIN:VCALENDAR\r\n" .
"VERSION:2.0\r\n" .
"PRODID:-//ANJUMA//DE\r\n" .
"CALSCALE:GREGORIAN\r\n" .
"BEGIN:VEVENT\r\n" .
"UID:$uid\r\n" .
"DTSTAMP:$dtstamp\r\n" .
"DTSTART;TZID=$tz:$dtstartStr\r\n" .
"DTEND;TZID=$tz:$dtendStr\r\n" .
"SUMMARY:$summary\r\n" .
"LOCATION:$location\r\n" .
"DESCRIPTION:$beschreibungFull\r\n" .
"URL:https://dashboard.anjuma-crew.de/termin_show.php?id=$id\r\n" .
// Erinnerung 1 Tag vorher
"BEGIN:VALARM\r\n" .
"TRIGGER:-P1D\r\n" .
"ACTION:DISPLAY\r\n" .
"DESCRIPTION:Erinnerung\r\n" .
"END:VALARM\r\n" .
"END:VEVENT\r\n" .
"END:VCALENDAR\r\n";
$url = $this->icloudCalendarUrl . "termin-$id.ics";
// Create = isUpdate=false
$success = $this->caldavPut($url, $ics, false);
if ($success) {
// Termin neu laden (für Hash)
$stmt = $this->pdo->prepare("SELECT * FROM termine WHERE id = :id");
$stmt->execute([':id' => $id]);
$termin = $stmt->fetch(PDO::FETCH_ASSOC);
if ($termin) {
$this->storeCalendarSyncState($id, $termin);
}
}
return $success;
}
public function updateCalendarEvent($id)
{
$stmt = $this->pdo->prepare("SELECT * FROM termine WHERE id = :id");
$stmt->execute([':id' => $id]);
$t = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$t) {
$this->debugLog("updateCalendarEvent: Termin $id nicht gefunden");
return false;
}
$startTs = (int)$t["startzeit"];
$endTs = (int)$t["endzeit"];
$location = $t["location"] ?? "";
$beschreibung = $t["beschreibung"] ?? "";
$uid = "termin-$id@anjuma";
$tz = 'Europe/Berlin';
$dtstamp = gmdate('Ymd\THis\Z');
if (empty($endTs) || $endTs <= 0) {
$endTs = $startTs + 2 * 3600;
}
$dtstartStr = (new DateTime("@$startTs"))
->setTimezone(new DateTimeZone($tz))
->format('Ymd\THis');
$dtendStr = (new DateTime("@$endTs"))
->setTimezone(new DateTimeZone($tz))
->format('Ymd\THis');
$locationEsc = $this->icsEscape($location);
$beschreibungEsc = $this->icsEscape($beschreibung);
$summary = "ANJUMA DJ $locationEsc ($id)";
// Status-Name bestimmen
$statusRaw = $this->getTerminDataById($id, "status");
$statusName = [
0 => "Offen",
1 => "Bereit",
2 => "Erledigt",
3 => "Storniert"
][$statusRaw] ?? "Unbekannt";
// Letzten Bearbeiter holen
$editInfo = $this->getLastEditorInfo($id);
// Gage holen
$gage = $this->getTerminDataById($id, "gage");
$creatorId = $this->getTerminDataById($id, "user_id");
$creatorName = $this->getDataByID($creatorId, "name");
$vars = [
'id' => $id,
'status' => $statusName,
'locationtype' => $this->getTerminDataById($id, "locationtype"),
'location' => $this->getTerminDataById($id, "location"),
'startzeit' => date('d.m.Y H:i', $this->getTerminDataById($id, "startzeit")),
'endzeit' => date('H:i', $this->getTerminDataById($id, "endzeit")),
'gage' => $this->getTerminDataById($id, "gage"),
'desc' => $this->getTerminDataById($id, "beschreibung"),
'creator' => $creatorName,
'last_editor' => $editInfo['name'],
'last_edited' => $editInfo['date']
];
$summary = $this->renderTemplate($this->getSettingsData("title_template"), $vars);
$beschreibungFull = $this->renderTemplate($this->getSettingsData("description_template"), $vars);
$beschreibungFull = $this->icsEscape($beschreibungFull);
$ics =
"BEGIN:VCALENDAR\r\n" .
"VERSION:2.0\r\n" .
"PRODID:-//ANJUMA//DE\r\n" .
"CALSCALE:GREGORIAN\r\n" .
"BEGIN:VEVENT\r\n" .
"UID:$uid\r\n" .
"DTSTAMP:$dtstamp\r\n" .
"DTSTART;TZID=$tz:$dtstartStr\r\n" .
"DTEND;TZID=$tz:$dtendStr\r\n" .
"SUMMARY:$summary\r\n" .
"LOCATION:$locationEsc\r\n" .
"DESCRIPTION:$beschreibungFull\r\n" .
"URL:https://dashboard.anjuma-crew.de/termin_show.php?id=$id\r\n" .
"BEGIN:VALARM\r\n" .
"TRIGGER:-P1D\r\n" .
"ACTION:DISPLAY\r\n" .
"DESCRIPTION:Erinnerung\r\n" .
"END:VALARM\r\n" .
"BEGIN:VALARM\r\n" .
"TRIGGER:-PT2H\r\n" .
"ACTION:DISPLAY\r\n" .
"DESCRIPTION:Erinnerung\r\n" .
"END:VALARM\r\n" .
"END:VEVENT\r\n" .
"END:VCALENDAR\r\n";
$url = $this->icloudCalendarUrl . "termin-$id.ics";
// Update = isUpdate=true
$success = $this->caldavPut($url, $ics, true);
if ($success) {
$this->storeCalendarSyncState($id, $t);
}
return $success;
}
public function deleteCalendarEvent($id)
{
$url = $this->icloudCalendarUrl . "termin-$id.ics";
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "DELETE");
curl_setopt($ch, CURLOPT_USERPWD, $this->icloudUser . ":" . $this->icloudPass);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
return $status === 200 || $status === 204;
}
public function changeTermin($id, $was, $value){
$this->pdo->query("UPDATE termine SET $was = '" . $value . "' WHERE id = '" . $id . "'");
//$this->updateCalendarEvent($id, $startzeit, $endzeit, $summary, $location, $beschreibung);
return;
}
private function icsEscape($text)
{
// Normalize line endings (Windows/Mac/Linux)
$text = str_replace(["\r\n", "\r"], "\n", $text);
// ICS requires escaping BACKSLASH first!
$text = str_replace("\\", "\\\\", $text);
// Then escape comma and semicolon
$text = str_replace(",", "\\,", $text);
$text = str_replace(";", "\\;", $text);
// Only now convert \n to ICS linebreaks
$text = str_replace("\n", "\\n", $text);
return $text;
}
public function createTermin($startzeit, $endzeit, $location, $user_id, $gage, $locationtype)
{
if ($gage === "" || $gage === null) $gage = 0;
$sql = "INSERT INTO termine
(startzeit, endzeit, locationtype, location, user_id, gage, status, anwesend, beschreibung)
VALUES (:startzeit, :endzeit, :locationtype, :location, :user_id, :gage, 0, '[]', '')";
$stmt = $this->pdo->prepare($sql);
$stmt->execute([
':startzeit' => $startzeit,
':endzeit' => $endzeit,
':locationtype' => $locationtype,
':location' => $location,
':user_id' => $user_id,
':gage' => $gage
]);
$id = $this->pdo->lastInsertId();
$this->createCalendarEvent($id, $startzeit, $endzeit, $location, "");
return $id;
}
public function createChanges($id, $user_id, $was, $alt, $neu)
{
// =============================================
// 1) CHANGE SPEICHERN
// =============================================
$stmt = $this->pdo->prepare("
INSERT INTO changes (termin_id, user_id, was, alt, neu, timestamp)
VALUES (:id, :user, :was, :alt, :neu, :ts)
");
$stmt->execute([
':id' => $id,
':user' => $user_id,
':was' => $was,
':alt' => $alt,
':neu' => $neu,
':ts' => time()
]);
// -------------------------------------------
// NTFY BENACHRICHTIGUNG (schön formatiert)
// -------------------------------------------
// === NTFY CHANNEL ===
/*$ntfy_url = "https://ntfy.anjuma-crew.de/anjuma-dashboard"; // <<< anpassen
// === Basic Auth ===
$auth = "Basic anVzdGluOm5ZZ0dwNnNNRWdMRWwx";
// === Nachricht bauen ===
$username = $this->getDataByID($user_id, "name");
if ($was === "anwesend") {
$altDecoded = json_decode($alt, true) ?: [];
$neuDecoded = json_decode($neu, true) ?: [];
// IDs zu Namen umwandeln
$altNames = array_map(fn($u) => $this->getDataByID($u, "name"), $altDecoded);
$neuNames = array_map(fn($u) => $this->getDataByID($u, "name"), $neuDecoded);
$alt = implode(", ", $altNames);
$neu = implode(", ", $neuNames);
}
// Mapping für schönere Bezeichnungen syfe-xapm-vcjx-dnbi
$labelMap = [
"status" => "Status",
"anwesend" => "Anwesende Personen",
"location" => "Location",
"ansprechpartner" => "Ansprechpartner",
"gage" => "Gage",
"preisprostunde" => "Preis pro Stunde",
"startzeit" => "Startzeit",
"endzeit" => "Endzeit",
"realendzeit" => "Tatsächliche Endzeit",
"create" => "Termin Erstellt"
];
$label = $labelMap[$was] ?? ucfirst($was);
// Titel
$title = "Änderung in Termin #$id $label";
// Nachricht
$body =
"$username hat $label geändert.\n\n" .
"🔻 Alt: $alt\n" .
"🔺 Neu: $neu";
// === CURL Request ===
$ch = curl_init($ntfy_url);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $body);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
"Authorization: $auth",
"X-Title: $title",
"X-Priority: 3",
"Content-Type: text/plain"
]);
curl_setopt($ch, CURLOPT_TIMEOUT, 1);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_exec($ch);
curl_close($ch);*/
return;
}
public function setTerminStatus($id, $status){
$this->pdo->query("UPDATE termine SET status = '" . $status . "' WHERE id = '" . $id . "'");
return;
}
public function setTodoTitle($id, $title){
$this->pdo->query("UPDATE todo SET title = '" . $title . "' WHERE id = '" . $id . "'");
return;
}
public function setTodoDescription($id, $description){
$this->pdo->query("UPDATE todo SET description = '" . $description . "' WHERE id = '" . $id . "'");
return;
}
public function deleteTodoById($id){
$this->pdo->query("DELETE FROM todo WHERE id = '" . $id . "'");
return;
}
public function createTodo($id, $title, $description){
$this->pdo->query("INSERT INTO todo (creator, title, description) VALUES ('" . $id . "', '" . $title . "', '" . $description . "')");
return;
}
public function getTodoDataById($id, $column){
$sql = "SELECT * FROM todo WHERE id = '" . $id . "'";
foreach($this->pdo->query($sql) as $row) {
return $row[$column];
}
}
public function getAllReadyGigs(){
$nRows = $this->pdo->query('select count(*) from termine WHERE status = 1')->fetchColumn();
return $nRows;
}
public function getAllProcessingGigs(){
$nRows = $this->pdo->query('select count(*) from termine WHERE status = 0')->fetchColumn();
return $nRows;
}
public function getAllCompletedGigs(){
$nRows = $this->pdo->query('select count(*) from termine WHERE status = 2')->fetchColumn();
return $nRows;
}
private function debugLog($msg) {
$file = __DIR__ . "/debug/caldav_debug.log";
file_put_contents($file, date("[Y-m-d H:i:s] ") . $msg . "\n", FILE_APPEND);
}
}
?>