Code: Alles auswählen
<?php
declare(strict_types=1);
header('Content-Type: application/json; charset=utf-8');
date_default_timezone_set('Europe/Berlin');
if (session_status() === PHP_SESSION_NONE) {
session_start();
}
/*
* Datenverzeichnis
*/
$dataDir = __DIR__ . '/data';
if (!is_dir($dataDir)) {
mkdir($dataDir, 0755, true);
}
file_put_contents(
$dataDir . '/.htaccess',
"Deny from all\n",
LOCK_EX
);
/*
* Galerie-/Formular-ID bereinigen
*/
$gid = preg_replace(
'/[^a-z0-9_-]/i',
'',
$_GET['gid'] ?? 'default'
);
if ($gid === '') {
$gid = 'default';
}
$dbFile = $dataDir . '/' . $gid . '.sqlite';
$pwFile = $dataDir . '/' . $gid . '.hash';
/*
* JSON-Antwort ausgeben
*/
function respond(bool $ok, array $extra = []): void
{
echo json_encode(
array_merge(['success' => $ok], $extra),
JSON_UNESCAPED_UNICODE
);
exit;
}
/*
* Adminstatus prüfen
*/
function isAdmin(string $gid): bool
{
return !empty($_SESSION['vndf_admin_' . $gid]);
}
/*
* Öffentlichen URL-Pfad des aktuellen PHP-Verzeichnisses ermitteln.
*
* Beispiel:
* SCRIPT_NAME:
* /VN-Block-Galerie/assets/vn-dataform/dataform.php
*
* Ergebnis:
* /VN-Block-Galerie/assets/vn-dataform
*/
function getPublicScriptDirectory(): string
{
$scriptName = $_SERVER['SCRIPT_NAME']
?? $_SERVER['PHP_SELF']
?? '';
$scriptName = str_replace('\\', '/', $scriptName);
/*
* Querystrings vorsorglich entfernen.
*/
$scriptPath = parse_url($scriptName, PHP_URL_PATH);
if (!is_string($scriptPath) || $scriptPath === '') {
return '';
}
$directory = dirname($scriptPath);
$directory = str_replace('\\', '/', $directory);
/*
* dirname('/') kann "/" ergeben.
*/
if ($directory === '/' || $directory === '.') {
return '';
}
return '/' . trim($directory, '/');
}
/*
* Datenbank initialisieren
*/
$db = new PDO('sqlite:' . $dbFile);
$db->setAttribute(
PDO::ATTR_ERRMODE,
PDO::ERRMODE_EXCEPTION
);
$db->exec(
"CREATE TABLE IF NOT EXISTS vndf_fields (
id INTEGER PRIMARY KEY,
sort_order INTEGER DEFAULT 0,
field_key TEXT UNIQUE,
label TEXT,
type TEXT DEFAULT 'text',
required INTEGER DEFAULT 0,
searchable INTEGER DEFAULT 1,
show_in_list INTEGER DEFAULT 1
)"
);
$db->exec(
"CREATE TABLE IF NOT EXISTS vndf_records (
id INTEGER PRIMARY KEY AUTOINCREMENT,
sort_order INTEGER DEFAULT 0,
created_at TEXT DEFAULT (datetime('now','localtime')),
updated_at TEXT DEFAULT (datetime('now','localtime')),
data TEXT
)"
);
/*
* sort_order-Spalte bei älteren Datenbanken ergänzen.
*/
try {
$db->exec(
"ALTER TABLE vndf_records
ADD COLUMN sort_order INTEGER DEFAULT 0"
);
} catch (Exception $e) {
/*
* Die Spalte existiert bereits.
*/
}
/*
* JSON-Eingabe lesen
*/
$rawInput = file_get_contents('php://input');
$input = json_decode($rawInput ?: '', true);
if (!is_array($input)) {
$input = [];
}
$action = $_GET['action']
?? $input['action']
?? '';
/*
* ─────────────────────────────────────────────
* Authentifizierung
* ─────────────────────────────────────────────
*/
if ($action === 'check') {
respond(
true,
[
'loggedIn' => isAdmin($gid),
'firstRun' => !file_exists($pwFile)
]
);
}
if ($action === 'login') {
$pw = (string)($input['password'] ?? '');
/*
* Beim ersten Aufruf Passwort anlegen.
*/
if (!file_exists($pwFile)) {
if (strlen($pw) < 4) {
respond(
false,
['error' => 'Min. 4 Zeichen.']
);
}
$passwordHash = password_hash(
$pw,
PASSWORD_BCRYPT
);
if ($passwordHash === false) {
respond(
false,
['error' => 'Passwort konnte nicht gespeichert werden.']
);
}
if (
file_put_contents(
$pwFile,
$passwordHash,
LOCK_EX
) === false
) {
respond(
false,
['error' => 'Passwortdatei konnte nicht geschrieben werden.']
);
}
$_SESSION['vndf_admin_' . $gid] = true;
respond(
true,
['firstRun' => true]
);
}
$storedHash = trim(
(string)file_get_contents($pwFile)
);
if (
$storedHash !== ''
&& password_verify($pw, $storedHash)
) {
$_SESSION['vndf_admin_' . $gid] = true;
respond(true);
}
respond(
false,
['error' => 'Falsches Passwort.']
);
}
if ($action === 'logout') {
unset($_SESSION['vndf_admin_' . $gid]);
respond(true);
}
/*
* ─────────────────────────────────────────────
* Felder
* ─────────────────────────────────────────────
*/
if ($action === 'get_fields') {
$rows = $db
->query(
"SELECT *
FROM vndf_fields
ORDER BY sort_order, id"
)
->fetchAll(PDO::FETCH_ASSOC);
respond(
true,
['fields' => $rows]
);
}
if ($action === 'save_fields') {
if (!isAdmin($gid)) {
respond(
false,
['error' => 'Nicht autorisiert.']
);
}
$fields = $input['fields'] ?? [];
if (!is_array($fields)) {
$fields = [];
}
$db->beginTransaction();
try {
$db->exec(
"DELETE FROM vndf_fields"
);
$stmt = $db->prepare(
"INSERT INTO vndf_fields (
sort_order,
field_key,
label,
type,
required,
searchable,
show_in_list
) VALUES (?, ?, ?, ?, ?, ?, ?)"
);
foreach ($fields as $i => $f) {
if (!is_array($f)) {
continue;
}
$label = trim(
(string)($f['label'] ?? '')
);
if ($label === '') {
$label = 'Feld ' . ($i + 1);
}
$key = preg_replace(
'/[^a-z0-9_]/i',
'_',
strtolower($label)
);
if ($key === null || $key === '') {
$key = 'feld_' . $i;
}
$stmt->execute(
[
$i,
$key,
$label,
$f['type'] ?? 'text',
(int)($f['required'] ?? 0),
(int)($f['searchable'] ?? 1),
(int)($f['show_in_list'] ?? 1)
]
);
}
$db->commit();
} catch (Throwable $e) {
if ($db->inTransaction()) {
$db->rollBack();
}
respond(
false,
['error' => 'Felder konnten nicht gespeichert werden.']
);
}
respond(
true,
['message' => '✅ Felder gespeichert!']
);
}
/*
* ─────────────────────────────────────────────
* Datensätze
* ─────────────────────────────────────────────
*/
if ($action === 'get_records') {
$search = trim(
(string)($input['search'] ?? '')
);
$page = max(
0,
(int)($input['page'] ?? 0)
);
$limit = max(
5,
min(
100,
(int)($input['limit'] ?? 20)
)
);
$all = $db
->query(
"SELECT *
FROM vndf_records
ORDER BY sort_order ASC, id ASC"
)
->fetchAll(PDO::FETCH_ASSOC);
$rows = [];
foreach ($all as $row) {
$data = json_decode(
$row['data'] ?? '{}',
true
);
if (!is_array($data)) {
$data = [];
}
if ($search !== '') {
$found = false;
foreach ($data as $value) {
if (
stripos(
(string)$value,
$search
) !== false
) {
$found = true;
break;
}
}
if (!$found) {
continue;
}
}
$rows[] = [
'id' => $row['id'],
'created_at' => $row['created_at'],
'data' => $data
];
}
$total = count($rows);
$slice = array_slice(
$rows,
$page * $limit,
$limit
);
respond(
true,
[
'records' => $slice,
'total' => $total,
'page' => $page,
'limit' => $limit
]
);
}
if ($action === 'save_record') {
if (!isAdmin($gid)) {
respond(
false,
['error' => 'Nicht autorisiert.']
);
}
$data = $input['data'] ?? [];
if (!is_array($data)) {
$data = [];
}
$id = (int)($input['id'] ?? 0);
$json = json_encode(
$data,
JSON_UNESCAPED_UNICODE
);
if ($json === false) {
respond(
false,
['error' => 'Datensatz konnte nicht verarbeitet werden.']
);
}
if ($id > 0) {
$stmt = $db->prepare(
"UPDATE vndf_records
SET data = ?,
updated_at = datetime('now','localtime')
WHERE id = ?"
);
$stmt->execute(
[$json, $id]
);
} else {
$stmt = $db->prepare(
"INSERT INTO vndf_records (data)
VALUES (?)"
);
$stmt->execute(
[$json]
);
$id = (int)$db->lastInsertId();
}
respond(
true,
[
'message' => '✅ Gespeichert!',
'id' => $id
]
);
}
if ($action === 'delete_record') {
if (!isAdmin($gid)) {
respond(
false,
['error' => 'Nicht autorisiert.']
);
}
$id = (int)($input['id'] ?? 0);
if ($id > 0) {
$stmt = $db->prepare(
"DELETE FROM vndf_records
WHERE id = ?"
);
$stmt->execute(
[$id]
);
}
respond(
true,
['message' => '🗑 Gelöscht!']
);
}
/*
* ─────────────────────────────────────────────
* CSV-Export
* ─────────────────────────────────────────────
*/
if ($action === 'export_csv') {
if (!isAdmin($gid)) {
respond(
false,
['error' => 'Nicht autorisiert.']
);
}
$fields = $db
->query(
"SELECT *
FROM vndf_fields
ORDER BY sort_order, id"
)
->fetchAll(PDO::FETCH_ASSOC);
$records = $db
->query(
"SELECT *
FROM vndf_records
ORDER BY sort_order ASC, id ASC"
)
->fetchAll(PDO::FETCH_ASSOC);
header(
'Content-Type: text/csv; charset=utf-8'
);
header(
'Content-Disposition: attachment; filename="' .
$gid .
'_export.csv"'
);
$out = fopen(
'php://output',
'w'
);
if ($out === false) {
respond(
false,
['error' => 'CSV-Datei konnte nicht erstellt werden.']
);
}
/*
* UTF-8-BOM für Excel.
*/
fprintf(
$out,
chr(0xEF) .
chr(0xBB) .
chr(0xBF)
);
fputcsv(
$out,
array_column($fields, 'label'),
';'
);
foreach ($records as $record) {
$data = json_decode(
$record['data'] ?? '{}',
true
);
if (!is_array($data)) {
$data = [];
}
$csvRow = array_map(
static function (array $field) use ($data): string {
return (string)(
$data[$field['field_key']] ?? ''
);
},
$fields
);
fputcsv(
$out,
$csvRow,
';'
);
}
fclose($out);
exit;
}
/*
* ─────────────────────────────────────────────
* Bild hochladen
* ─────────────────────────────────────────────
*/
if ($action === 'upload_image') {
if (!isAdmin($gid)) {
respond(
false,
['error' => 'Nicht autorisiert.']
);
}
if (
!isset($_FILES['img'])
|| !is_array($_FILES['img'])
|| empty($_FILES['img']['tmp_name'])
) {
respond(
false,
['error' => 'Keine Datei.']
);
}
$uploadError = (int)(
$_FILES['img']['error']
?? UPLOAD_ERR_NO_FILE
);
if ($uploadError !== UPLOAD_ERR_OK) {
respond(
false,
['error' => 'Fehler beim Hochladen der Datei. Fehlercode: ' . $uploadError]
);
}
$tmpFile = (string)$_FILES['img']['tmp_name'];
if (!is_uploaded_file($tmpFile)) {
respond(
false,
['error' => 'Ungültige Upload-Datei.']
);
}
/*
* Dateityp anhand des tatsächlichen MIME-Typs prüfen.
*/
$allowedTypes = [
'image/jpeg' => 'jpg',
'image/png' => 'png',
'image/gif' => 'gif',
'image/webp' => 'webp'
];
$mimeType = '';
if (function_exists('finfo_open')) {
$finfo = finfo_open(FILEINFO_MIME_TYPE);
if ($finfo !== false) {
$detectedType = finfo_file(
$finfo,
$tmpFile
);
if (is_string($detectedType)) {
$mimeType = $detectedType;
}
finfo_close($finfo);
}
}
/*
* Fallback, falls Fileinfo nicht verfügbar ist.
*/
if ($mimeType === '') {
$imageInfo = @getimagesize($tmpFile);
if (
is_array($imageInfo)
&& isset($imageInfo['mime'])
) {
$mimeType = (string)$imageInfo['mime'];
}
}
if (!isset($allowedTypes[$mimeType])) {
respond(
false,
['error' => 'Ungültiges Bildformat.']
);
}
$extension = $allowedTypes[$mimeType];
/*
* Physischer Upload-Ordner:
* assets/vn-dataform/uploads/<gid>/
*/
$uploadDir = __DIR__
. '/uploads/'
. $gid;
if (
!is_dir($uploadDir)
&& !mkdir($uploadDir, 0755, true)
&& !is_dir($uploadDir)
) {
respond(
false,
['error' => 'Upload-Ordner konnte nicht angelegt werden.']
);
}
/*
* Sicheren, eindeutigen Dateinamen erzeugen.
*/
try {
$randomName = bin2hex(
random_bytes(8)
);
} catch (Throwable $e) {
$randomName = uniqid('', true);
$randomName = preg_replace(
'/[^a-z0-9]/i',
'',
$randomName
);
}
$fileName = 'img_'
. $randomName
. '.'
. $extension;
$target = $uploadDir
. '/'
. $fileName;
if (
!move_uploaded_file(
$tmpFile,
$target
)
) {
respond(
false,
['error' => 'Upload fehlgeschlagen.']
);
}
/*
* Öffentliche URL aus dem URL-Pfad des PHP-Scripts erzeugen.
*
* Es wird ausdrücklich NICHT mehr mit DOCUMENT_ROOT
* oder realpath() gearbeitet.
*/
$scriptDirectory = getPublicScriptDirectory();
$publicUrl = $scriptDirectory
. '/uploads/'
. rawurlencode($gid)
. '/'
. rawurlencode($fileName);
/*
* Sicherstellen, dass die URL mit genau einem Slash beginnt.
*/
$publicUrl = '/'
. ltrim($publicUrl, '/');
respond(
true,
[
'url' => $publicUrl,
'message' => '✅ Hochgeladen!'
]
);
}
/*
* ─────────────────────────────────────────────
* Passwort ändern
* ─────────────────────────────────────────────
*/
if ($action === 'change_password') {
if (!isAdmin($gid)) {
respond(
false,
['error' => 'Nicht autorisiert.']
);
}
$oldPw = (string)(
$input['old_password'] ?? ''
);
$newPw = (string)(
$input['new_password'] ?? ''
);
$hash = file_exists($pwFile)
? trim((string)file_get_contents($pwFile))
: '';
if (
$hash !== ''
&& !password_verify($oldPw, $hash)
) {
respond(
false,
['error' => 'Altes Passwort falsch.']
);
}
if (strlen($newPw) < 4) {
respond(
false,
['error' => 'Min. 4 Zeichen.']
);
}
$newHash = password_hash(
$newPw,
PASSWORD_BCRYPT
);
if ($newHash === false) {
respond(
false,
['error' => 'Neues Passwort konnte nicht erzeugt werden.']
);
}
if (
file_put_contents(
$pwFile,
$newHash,
LOCK_EX
) === false
) {
respond(
false,
['error' => 'Neues Passwort konnte nicht gespeichert werden.']
);
}
respond(
true,
['message' => '✅ Passwort geändert!']
);
}
/*
* ─────────────────────────────────────────────
* Reihenfolge speichern
* ─────────────────────────────────────────────
*/
if ($action === 'reorder') {
if (!isAdmin($gid)) {
respond(
false,
['error' => 'Nicht autorisiert.']
);
}
$ids = $input['ids'] ?? [];
if (!is_array($ids)) {
$ids = [];
}
$stmt = $db->prepare(
"UPDATE vndf_records
SET sort_order = ?
WHERE id = ?"
);
$db->beginTransaction();
try {
foreach ($ids as $i => $id) {
$stmt->execute(
[
$i,
(int)$id
]
);
}
$db->commit();
} catch (Throwable $e) {
if ($db->inTransaction()) {
$db->rollBack();
}
respond(
false,
['error' => 'Reihenfolge konnte nicht gespeichert werden.']
);
}
respond(
true,
['message' => '✅ Reihenfolge gespeichert!']
);
}
/*
* Keine bekannte Aktion gefunden.
*/
respond(
false,
['error' => 'Unbekannte Aktion: ' . $action]
);