414 lines
16 KiB
PHP
414 lines
16 KiB
PHP
<?php
|
||
declare(strict_types=1);
|
||
|
||
/**
|
||
* Config-Editor für EPI Webview
|
||
* - Liest ../config.php (falls vorhanden) und ../example.config.php
|
||
* - Zeigt editierbare Felder für alle Keys aus example.config.php
|
||
* - Zeigt veraltete (nur in config.php existierende) Keys rot & disabled
|
||
* - Speichert Werte zurück nach ../config.php
|
||
*/
|
||
|
||
error_reporting(E_ALL);
|
||
|
||
// ---- Dateipfade anpassen falls nötig ----
|
||
$CONFIG_FILE = realpath(__DIR__ . '/../config.php') ?: (__DIR__ . '/../config.php');
|
||
$EXAMPLE_FILE = realpath(__DIR__ . '/../example.config.php') ?: (__DIR__ . '/../example.config.php');
|
||
|
||
// ---- Hilfsfunktionen: Parsing ----
|
||
function parse_define_file(string $file): array {
|
||
if (!is_file($file)) return ['defines' => [], 'order' => [], 'ui' => []];
|
||
|
||
$lines = file($file, FILE_IGNORE_NEW_LINES);
|
||
$defines = [];
|
||
$order = [];
|
||
$ui = [];
|
||
|
||
// BOM entfernen, falls vorhanden
|
||
if (isset($lines[0])) {
|
||
$lines[0] = preg_replace('/^\xEF\xBB\xBF/', '', $lines[0]);
|
||
}
|
||
|
||
// Regex
|
||
$reDefine = '/^\s*define\(\s*([\'"])([^\'"]+)\1\s*,\s*(.+?)\s*\)\s*;\s*(?:(?:\/\/|#)\s*(.*))?$/u';
|
||
$reSection = '/^\s*\/\/\s*@section\s*:\s*(.+)\s*$/iu';
|
||
$reHR = '/^\s*\/\/\s*@hr\s*$/iu';
|
||
$reNote = '/^\s*\/\/\s*@note\s*:\s*(.+)\s*$/iu';
|
||
|
||
foreach ($lines as $ln) {
|
||
// Meta-Kommentare zuerst prüfen (UI-Struktur)
|
||
if (preg_match($reSection, $ln, $m)) {
|
||
$ui[] = ['kind' => 'section', 'title' => trim($m[1])];
|
||
continue;
|
||
}
|
||
if (preg_match($reHR, $ln)) {
|
||
$ui[] = ['kind' => 'hr'];
|
||
continue;
|
||
}
|
||
if (preg_match($reNote, $ln, $m)) {
|
||
$ui[] = ['kind' => 'note', 'text' => trim($m[1])];
|
||
continue;
|
||
}
|
||
|
||
// define(…) Zeilen
|
||
if (!preg_match($reDefine, $ln, $m)) {
|
||
continue;
|
||
}
|
||
|
||
$name = $m[2];
|
||
$rawValueExpr = trim($m[3]);
|
||
$inlineC = isset($m[4]) ? trim($m[4]) : '';
|
||
|
||
// Typ bestimmen
|
||
$type = 'string';
|
||
$valForForm = '';
|
||
$raw = rtrim($rawValueExpr, ',');
|
||
|
||
if (preg_match('/^(true|false)$/i', $raw)) {
|
||
$type = 'bool';
|
||
$valForForm = (strtolower($raw) === 'true');
|
||
} elseif (preg_match('/^[+-]?\d+$/', $raw)) {
|
||
$type = 'int';
|
||
$valForForm = (int)$raw;
|
||
} elseif (preg_match('/^([\'"])(.*)\1$/s', $raw, $mm)) {
|
||
$type = 'string';
|
||
$valForForm = stripcslashes($mm[2]);
|
||
} else {
|
||
$type = 'string';
|
||
$valForForm = $raw;
|
||
}
|
||
|
||
$defines[$name] = [
|
||
'name' => $name,
|
||
'type' => $type,
|
||
'value' => $valForForm,
|
||
'raw' => $rawValueExpr,
|
||
'comment' => $inlineC,
|
||
'line' => $ln,
|
||
];
|
||
$order[] = $name;
|
||
// Wichtig: define auch in die UI-Reihenfolge aufnehmen
|
||
$ui[] = ['kind' => 'define', 'name' => $name];
|
||
}
|
||
|
||
return ['defines' => $defines, 'order' => $order, 'ui' => $ui];
|
||
}
|
||
|
||
|
||
function php_export_define_value(string $type, $value): string {
|
||
// Baut den rechten Teil von define('X', <HIER>);
|
||
if ($type === 'bool') {
|
||
return $value ? 'true' : 'false';
|
||
}
|
||
if ($type === 'int') {
|
||
return (string)intval($value);
|
||
}
|
||
// String:
|
||
// einfache Quotes nehmen und escapen
|
||
$escaped = str_replace("'", "\\'", (string)$value);
|
||
return "'" . $escaped . "'";
|
||
}
|
||
|
||
function h(?string $s): string { return htmlspecialchars((string)$s ?? '', ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'); }
|
||
|
||
// ---- Dateien parsen ----
|
||
$example = parse_define_file($EXAMPLE_FILE);
|
||
$current = parse_define_file($CONFIG_FILE);
|
||
|
||
// Keys klassifizieren
|
||
$exampleKeys = array_keys($example['defines']);
|
||
$currentKeys = array_keys($current['defines']);
|
||
|
||
$deprecatedKeys = array_values(array_diff($currentKeys, $exampleKeys)); // nur in config.php
|
||
$newKeys = array_values(array_diff($exampleKeys, $currentKeys)); // nur in example (neu)
|
||
$sharedKeys = array_values(array_intersect($exampleKeys, $currentKeys));
|
||
|
||
// Beim Rendern: Reihenfolge aus example beibehalten
|
||
$renderKeys = $example['order'];
|
||
|
||
// ---- POST: Speichern ----
|
||
$message = '';
|
||
$err = '';
|
||
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['__save_config']) && isset($_POST['cfg']) && is_array($_POST['cfg'])) {
|
||
|
||
$incoming = $_POST['cfg']; // ['NAME' => valueString/checkbox etc.]
|
||
|
||
// Build neuer config.php Inhalt:
|
||
$out = [];
|
||
$out[] = "<?php";
|
||
$out[] = "// Diese Datei wurde durch den Epi Webview Config-Editor erzeugt/aktualisiert.";
|
||
$out[] = "// Bearbeite Werte bevorzugt über die Weboberfläche.";
|
||
$out[] = "";
|
||
|
||
// --- Inhalt in der Reihenfolge & Struktur der example.config.php ---
|
||
foreach ($example['ui'] as $item) {
|
||
if ($item['kind'] === 'section') {
|
||
$out[] = "// @section: " . $item['title'];
|
||
continue;
|
||
}
|
||
if ($item['kind'] === 'hr') {
|
||
$out[] = "// @hr";
|
||
continue;
|
||
}
|
||
if ($item['kind'] === 'note') {
|
||
$out[] = "// @note: " . $item['text'];
|
||
continue;
|
||
}
|
||
if ($item['kind'] !== 'define') {
|
||
continue;
|
||
}
|
||
|
||
$label = $item['name'];
|
||
$ex = $example['defines'][$label] ?? null;
|
||
if (!$ex) continue;
|
||
|
||
$type = $ex['type']; // Typ an example orientieren
|
||
$tooltip = $ex['comment'] ?? '';
|
||
|
||
// Wert aus POST übernehmen (Checkbox-Fix: existiert das Feld?)
|
||
if ($type === 'bool') {
|
||
$bool = array_key_exists($label, $incoming);
|
||
$valueForDefine = php_export_define_value('bool', $bool);
|
||
} elseif ($type === 'int') {
|
||
$raw = $incoming[$label] ?? '';
|
||
$valueForDefine = php_export_define_value('int', $raw);
|
||
} else {
|
||
$raw = $incoming[$label] ?? '';
|
||
$valueForDefine = php_export_define_value('string', $raw);
|
||
}
|
||
|
||
$line = "define('{$label}', {$valueForDefine});";
|
||
if (!empty($tooltip)) {
|
||
$line .= " // " . $tooltip; // Kommentare als Tooltip auch in config.php mitführen
|
||
}
|
||
$out[] = $line;
|
||
}
|
||
|
||
// Deprecated Block (nur in aktueller config.php vorhanden, nicht in example)
|
||
if (!empty($deprecatedKeys)) {
|
||
$out[] = "";
|
||
$out[] = "// --- Veraltete/entfernte Parameter (nur Referenz, werden nicht mehr genutzt) ---";
|
||
foreach ($deprecatedKeys as $key) {
|
||
$c = $current['defines'][$key];
|
||
$valueForDefine = php_export_define_value($c['type'], $c['value']);
|
||
$line = "// define('{$c['name']}', {$valueForDefine});";
|
||
if (!empty($c['comment'])) {
|
||
$line .= " // " . $c['comment'];
|
||
}
|
||
$out[] = $line;
|
||
}
|
||
}
|
||
|
||
$out[] = "?>";
|
||
$content = implode("\n", $out) . "\n";
|
||
|
||
try {
|
||
if (false === file_put_contents($CONFIG_FILE, $content)) {
|
||
$err = "Konnte config.php nicht schreiben: " . h($CONFIG_FILE);
|
||
} else {
|
||
$message = "Konfiguration gespeichert.";
|
||
// Nach dem Speichern neu parsen, damit UI aktuell ist
|
||
$current = parse_define_file($CONFIG_FILE);
|
||
$currentKeys = array_keys($current['defines']);
|
||
$deprecatedKeys = array_values(array_diff($currentKeys, $exampleKeys));
|
||
$sharedKeys = array_values(array_intersect($exampleKeys, $currentKeys));
|
||
}
|
||
} catch (Throwable $e) {
|
||
$err = "Fehler beim Schreiben: " . h($e->getMessage());
|
||
}
|
||
}
|
||
|
||
|
||
// ---- Werte für Formular vorbereiten ----
|
||
// Für jeden example-Key: Formularwert = aus aktueller config (falls vorhanden), sonst example default
|
||
function valueForForm(array $example, array $current, string $key) {
|
||
$ex = $example['defines'][$key] ?? null;
|
||
$cu = $current['defines'][$key] ?? null;
|
||
$type = $ex ? $ex['type'] : ($cu ? $cu['type'] : 'string');
|
||
$comment = $ex && $ex['comment'] !== '' ? $ex['comment'] : ($cu['comment'] ?? '');
|
||
|
||
if ($cu) {
|
||
$val = $cu['value'];
|
||
} else {
|
||
$val = $ex['value'];
|
||
}
|
||
return [$type, $val, $comment];
|
||
}
|
||
|
||
?>
|
||
<!DOCTYPE html>
|
||
<html lang="de">
|
||
<head>
|
||
<meta charset="utf-8" />
|
||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
|
||
<meta name="description" content="Konfiguration bearbeiten" />
|
||
<meta name="author" content="" />
|
||
<title>Konfiguration – Epi Webview</title>
|
||
|
||
<link href="css/styles.css" rel="stylesheet" />
|
||
<link href="https://cdn.datatables.net/1.10.20/css/dataTables.bootstrap4.min.css" rel="stylesheet" crossorigin="anonymous" />
|
||
<script src="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.1/js/all.min.js" crossorigin="anonymous"></script>
|
||
<script src="js/jquery-3.5.1.min.js"></script>
|
||
|
||
<style>
|
||
.deprecated { color: #c62828; }
|
||
.cfg-help { margin-left: .35rem; cursor: help; }
|
||
.form-group { margin-bottom: .75rem; }
|
||
.small-muted { font-size:.85rem; color:#6c757d; }
|
||
.checkbox-inline { display:flex; align-items:center; gap:.5rem; }
|
||
.cfg-label { font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; }
|
||
</style>
|
||
|
||
<script>
|
||
$(function(){
|
||
// Tooltips (Bootstrap 4)
|
||
$('[data-toggle="tooltip"]').tooltip();
|
||
|
||
// Side UI nachladen wie in deinem Template
|
||
loadSidenav();
|
||
loadFooter();
|
||
});
|
||
function loadSidenav(){
|
||
$('#layoutSidenav_nav').load('../sources/getSidenav.php');
|
||
}
|
||
function loadFooter(){
|
||
$('#footerholder').load('../sources/getFooter.php');
|
||
}
|
||
</script>
|
||
</head>
|
||
<body class="sb-nav-fixed">
|
||
<nav class="sb-topnav navbar navbar-expand navbar-dark bg-dark">
|
||
<a class="navbar-brand" href="index.php">Epi Webview</a>
|
||
<button class="btn btn-link btn-sm order-1 order-lg-0" id="sidebarToggle" type="button"><i class="fas fa-bars"></i></button>
|
||
<ul class="navbar-nav ml-auto ml-md-0"></ul>
|
||
</nav>
|
||
|
||
<div id="layoutSidenav">
|
||
<div id="layoutSidenav_nav"></div>
|
||
<div id="layoutSidenav_content">
|
||
<main>
|
||
<div class="container-fluid">
|
||
<h1 class="mt-4">Konfiguration</h1>
|
||
<ol class="breadcrumb mb-4">
|
||
<li class="breadcrumb-item active">Dashboard / Konfiguration</li>
|
||
</ol>
|
||
|
||
<?php if ($message): ?>
|
||
<div class="alert alert-success"><?= h($message) ?></div>
|
||
<?php endif; ?>
|
||
<?php if ($err): ?>
|
||
<div class="alert alert-danger"><?= h($err) ?></div>
|
||
<?php endif; ?>
|
||
|
||
<div class="card mb-4">
|
||
<div class="card-header">
|
||
<i class="fas fa-sliders-h mr-1"></i>
|
||
Einstellungen
|
||
<span class="small-muted">( <code><?=h(basename($CONFIG_FILE))?></code> Source: <code><?=h(basename($EXAMPLE_FILE))?></code>)</span>
|
||
</div>
|
||
<div class="card-body">
|
||
<form method="post">
|
||
<input type="hidden" name="__save_config" value="1" />
|
||
|
||
<?php foreach ($example['ui'] as $item): ?>
|
||
<?php
|
||
if ($item['kind'] === 'section') {
|
||
echo '<h5 class="mt-3">'.h($item['title']).'</h5>';
|
||
continue;
|
||
}
|
||
if ($item['kind'] === 'hr') {
|
||
echo '<hr />';
|
||
continue;
|
||
}
|
||
if ($item['kind'] === 'note') {
|
||
echo '<p class="small-muted">'.h($item['text']).'</p>';
|
||
continue;
|
||
}
|
||
if ($item['kind'] !== 'define') {
|
||
continue;
|
||
}
|
||
|
||
$key = $item['name'];
|
||
// Nur Keys rendern, die in example vorkommen
|
||
if (!isset($example['defines'][$key])) continue;
|
||
|
||
[$type, $val, $tooltip] = valueForForm($example, $current, $key);
|
||
?>
|
||
|
||
<div class="form-group">
|
||
<label class="cfg-label" for="cfg_<?=h($key)?>">
|
||
<?= h($key) ?>
|
||
<?php if ($tooltip): ?>
|
||
<i class="far fa-question-circle cfg-help"
|
||
data-toggle="tooltip" data-placement="right"
|
||
title="<?=h($tooltip)?>"></i>
|
||
<?php endif; ?>
|
||
</label>
|
||
|
||
<?php if ($type === 'bool'): ?>
|
||
<div class="checkbox-inline">
|
||
<input type="checkbox"
|
||
id="cfg_<?=h($key)?>"
|
||
name="cfg[<?=h($key)?>]"
|
||
value="1" <?= $val ? 'checked' : '' ?> />
|
||
<span><?= $val ? 'Aktiviert' : 'Deaktiviert' ?></span>
|
||
</div>
|
||
<?php elseif ($type === 'int'): ?>
|
||
<input type="number" step="1" class="form-control"
|
||
id="cfg_<?=h($key)?>"
|
||
name="cfg[<?=h($key)?>]"
|
||
value="<?=h((string)$val)?>" />
|
||
<?php else: ?>
|
||
<input type="text" class="form-control"
|
||
id="cfg_<?=h($key)?>"
|
||
name="cfg[<?=h($key)?>]"
|
||
value="<?=h((string)$val)?>" />
|
||
<?php endif; ?>
|
||
</div>
|
||
|
||
<?php endforeach; ?>
|
||
|
||
<?php if (!empty($deprecatedKeys)): ?>
|
||
<hr />
|
||
<h5 class="deprecated">Veraltete Parameter (nur in <code><?=h(basename($CONFIG_FILE))?></code>, nicht mehr in <code><?=h(basename($EXAMPLE_FILE))?></code>)</h5>
|
||
<?php foreach ($deprecatedKeys as $key): ?>
|
||
<?php $c = $current['defines'][$key]; ?>
|
||
<div class="form-group">
|
||
<label class="cfg-label deprecated" for="dep_<?=h($key)?>">
|
||
<?= h($key) ?>
|
||
<?php if (!empty($c['comment'])): ?>
|
||
<i class="far fa-question-circle cfg-help" data-toggle="tooltip" data-placement="right" title="<?=h($c['comment'])?>"></i>
|
||
<?php endif; ?>
|
||
</label>
|
||
<input type="text" class="form-control is-invalid" id="dep_<?=h($key)?>" value="<?=
|
||
h($c['type']==='bool' ? ($c['value']?'true':'false') : (string)$c['value'])
|
||
?>" disabled />
|
||
<div class="invalid-feedback">Dieser Parameter ist in der aktuellen Version nicht mehr vorgesehen.</div>
|
||
</div>
|
||
<?php endforeach; ?>
|
||
<?php endif; ?>
|
||
|
||
<div class="mt-4">
|
||
<button type="submit" class="btn btn-primary"><i class="fas fa-save mr-1"></i> Speichern</button>
|
||
<span class="small-muted ml-2">Speichert nach <code><?=h($CONFIG_FILE)?></code></span>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
|
||
</div>
|
||
</main>
|
||
<div id="footerholder"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@4.5.3/dist/js/bootstrap.bundle.min.js" crossorigin="anonymous"></script>
|
||
<script src="js/scripts.js"></script>
|
||
<script>
|
||
// Sidebar Toggle (falls benötigt)
|
||
$('#sidebarToggle').on('click', function(){ $('body').toggleClass('sb-sidenav-toggled'); });
|
||
</script>
|
||
</body>
|
||
</html>
|