settings.php 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370
  1. <?php
  2. require_once __DIR__ . "/../config.php";
  3. require_once __DIR__ . "/../includes/functions.php";
  4. require_once __DIR__ . "/../includes/version.php";
  5. require_once __DIR__ . "/../includes/backup.php";
  6. if (!defined("UPDATE_MANIFEST_URL")) {
  7. define("UPDATE_MANIFEST_URL", "");
  8. }
  9. function settingsUpdaterVersionCompareValue(string $version): string
  10. {
  11. return ltrim(trim($version), "vV");
  12. }
  13. function settingsGetUpdaterStatus(): array
  14. {
  15. $manifestUrl = trim((string) UPDATE_MANIFEST_URL);
  16. if ($manifestUrl === "") {
  17. return [
  18. "label" => "Update-Ziel ist nicht konfiguriert.",
  19. "available" => false,
  20. "version" => "",
  21. ];
  22. }
  23. $context = stream_context_create([
  24. "http" => [
  25. "method" => "GET",
  26. "timeout" => 3,
  27. "ignore_errors" => true,
  28. "header" => "User-Agent: " .
  29. (defined("UPDATE_USER_AGENT") ? UPDATE_USER_AGENT : "Simple-PHP-Updater/" . APP_VERSION) .
  30. "\r\n",
  31. ],
  32. ]);
  33. $body = @file_get_contents($manifestUrl, false, $context);
  34. if ($body === false) {
  35. return [
  36. "label" => "Update-Status konnte nicht geladen werden.",
  37. "available" => false,
  38. "version" => "",
  39. ];
  40. }
  41. $manifest = json_decode($body, true);
  42. if (!is_array($manifest)) {
  43. return [
  44. "label" => "Update-Status ist ungültig.",
  45. "available" => false,
  46. "version" => "",
  47. ];
  48. }
  49. $version = trim((string) ($manifest["version"] ?? $manifest["latest"] ?? ""));
  50. if (!preg_match('/^v\d+\.\d+\.\d+$/', $version)) {
  51. return [
  52. "label" => "Update-Version ist ungültig.",
  53. "available" => false,
  54. "version" => "",
  55. ];
  56. }
  57. $available =
  58. version_compare(
  59. settingsUpdaterVersionCompareValue($version),
  60. settingsUpdaterVersionCompareValue(APP_VERSION),
  61. ">",
  62. );
  63. return [
  64. "label" => $available
  65. ? "Update verfügbar: " . $version
  66. : "Kein Update verfügbar.",
  67. "available" => $available,
  68. "version" => $version,
  69. ];
  70. }
  71. function settingsFormatBackupDate(string $date): string
  72. {
  73. $timestamp = strtotime($date);
  74. if ($timestamp === false) {
  75. return $date;
  76. }
  77. return date("d.m.Y H:i", $timestamp);
  78. }
  79. function settingsGetBackupUploadLabel(array $backup): string
  80. {
  81. $uploads =
  82. isset($backup["remote_uploads"]) && is_array($backup["remote_uploads"])
  83. ? $backup["remote_uploads"]
  84. : [];
  85. if (empty($uploads)) {
  86. return "Nur lokal";
  87. }
  88. $successful = 0;
  89. foreach ($uploads as $upload) {
  90. if (is_array($upload) && !empty($upload["success"])) {
  91. $successful++;
  92. }
  93. }
  94. if ($successful === count($uploads)) {
  95. return "Remote erfolgreich (" . $successful . ")";
  96. }
  97. if ($successful > 0) {
  98. return "Teilweise erfolgreich (" . $successful . "/" . count($uploads) . ")";
  99. }
  100. return "Remote fehlgeschlagen";
  101. }
  102. function settingsGetBackupCapabilityLabel(array $capability): string
  103. {
  104. if (empty($capability["configured"])) {
  105. return "nicht konfiguriert";
  106. }
  107. return !empty($capability["available"]) ? "bereit" : "nicht verfügbar";
  108. }
  109. function settingsFindBackupByFilename(string $filename): ?array
  110. {
  111. if ($filename === "" || basename($filename) !== $filename) {
  112. return null;
  113. }
  114. foreach (backupListBackups() as $backup) {
  115. if (($backup["filename"] ?? "") === $filename) {
  116. return $backup;
  117. }
  118. }
  119. return null;
  120. }
  121. function settingsSendBackupDownload(array $backup): void
  122. {
  123. $filename = basename((string) ($backup["filename"] ?? ""));
  124. $path = backupGetDirectory() . $filename;
  125. if ($filename === "" || !is_file($path) || !is_readable($path)) {
  126. throw new RuntimeException("Backup-Datei wurde nicht gefunden.");
  127. }
  128. $size = filesize($path);
  129. if ($size === false) {
  130. throw new RuntimeException("Backup-Dateigröße konnte nicht gelesen werden.");
  131. }
  132. logAccess("Backup downloaded", [
  133. "filename" => $filename,
  134. ]);
  135. header("Content-Type: application/zip");
  136. header('Content-Disposition: attachment; filename="' . $filename . '"');
  137. header("Content-Length: " . (string) $size);
  138. header("X-Content-Type-Options: nosniff");
  139. readfile($path);
  140. exit();
  141. }
  142. if (empty($_SESSION['admin_logged_in'])) {
  143. header("Location: login.php");
  144. exit();
  145. }
  146. $pageTitle = "Einstellungen";
  147. $message = "";
  148. $messageType = "";
  149. $backupAutoMessage = "";
  150. $backupAutoMessageType = "";
  151. if ($_SERVER['REQUEST_METHOD'] === "POST" && isset($_POST['save_settings'])) {
  152. // Validate CSRF token
  153. if (!validateCsrfToken($_POST['csrf_token'] ?? "")) {
  154. $message = "Ungültiges Token. Bitte versuchen Sie es erneut.";
  155. $messageType = "error";
  156. } else {
  157. $settings = array_merge(getSystemSettings(), [
  158. "order_recipient_email" => $_POST['order_recipient_email'] ?? "",
  159. "attach_order_pdf_to_admin_email" => isset(
  160. $_POST['attach_order_pdf_to_admin_email'],
  161. ),
  162. ]);
  163. if (saveSystemSettings($settings)) {
  164. logAccess("Admin updated system settings");
  165. $message = "Einstellungen wurden gespeichert.";
  166. $messageType = "success";
  167. } else {
  168. $message = "Einstellungen konnten nicht gespeichert werden.";
  169. $messageType = "error";
  170. }
  171. }
  172. } elseif ($_SERVER['REQUEST_METHOD'] === "POST" && isset($_POST['create_backup'])) {
  173. if (!validateCsrfToken($_POST['csrf_token'] ?? "")) {
  174. $message = "Ungültiges Token. Bitte versuchen Sie es erneut.";
  175. $messageType = "error";
  176. } else {
  177. try {
  178. $backup = backupCreate("manual");
  179. $message =
  180. "Backup wurde erstellt: " .
  181. $backup["filename"] .
  182. " (" .
  183. backupFormatBytes((int) $backup["size"]) .
  184. ").";
  185. $messageType = "success";
  186. } catch (Throwable $exception) {
  187. $message = "Backup konnte nicht erstellt werden: " . $exception->getMessage();
  188. $messageType = "error";
  189. }
  190. }
  191. } elseif ($_SERVER['REQUEST_METHOD'] === "POST" && isset($_POST['download_backup'])) {
  192. if (!validateCsrfToken($_POST['csrf_token'] ?? "")) {
  193. $message = "Ungültiges Token. Bitte versuchen Sie es erneut.";
  194. $messageType = "error";
  195. } else {
  196. try {
  197. $backup = settingsFindBackupByFilename((string) ($_POST["backup_filename"] ?? ""));
  198. if ($backup === null) {
  199. throw new RuntimeException("Backup wurde nicht gefunden.");
  200. }
  201. settingsSendBackupDownload($backup);
  202. } catch (Throwable $exception) {
  203. $message = "Backup konnte nicht heruntergeladen werden: " . $exception->getMessage();
  204. $messageType = "error";
  205. }
  206. }
  207. } elseif ($_SERVER['REQUEST_METHOD'] === "GET") {
  208. try {
  209. $backup = backupCreateAutomaticIfDue();
  210. if ($backup !== null) {
  211. $backupAutoMessage =
  212. "Automatisches Backup wurde erstellt: " .
  213. $backup["filename"] .
  214. ".";
  215. $backupAutoMessageType = "success";
  216. }
  217. } catch (Throwable $exception) {
  218. $backupAutoMessage =
  219. "Automatisches Backup konnte nicht erstellt werden: " .
  220. $exception->getMessage();
  221. $backupAutoMessageType = "warning";
  222. }
  223. }
  224. $settings = getSystemSettings();
  225. $updaterStatus = settingsGetUpdaterStatus();
  226. $backupCapabilities = backupRemoteCapabilities();
  227. $backups = backupListBackups();
  228. $bodyClass = "admin-page";
  229. include __DIR__ . "/../includes/header.php";
  230. ?>
  231. <div class="admin-header">
  232. <h2>Einstellungen</h2>
  233. <div>
  234. <a href="index.php" class="btn btn-secondary">Zurück zum Dashboard</a>
  235. </div>
  236. </div>
  237. <?php if ($message !== ""): ?>
  238. <div class="alert alert-<?php echo escape($messageType); ?>">
  239. <?php echo escape($message); ?>
  240. </div>
  241. <?php endif; ?>
  242. <?php if ($backupAutoMessage !== ""): ?>
  243. <div class="alert alert-<?php echo escape($backupAutoMessageType); ?>">
  244. <?php echo escape($backupAutoMessage); ?>
  245. </div>
  246. <?php endif; ?>
  247. <div class="panel panel-lg">
  248. <form method="POST">
  249. <?php echo csrfField(); ?>
  250. <div class="form-group">
  251. <label for="order_recipient_email">Empfängeradresse für interne Bestellungen *</label>
  252. <input type="email" id="order_recipient_email" name="order_recipient_email" required value="<?php echo escape(
  253. $settings["order_recipient_email"],
  254. ); ?>">
  255. </div>
  256. <div class="form-group">
  257. <label class="checkbox-label">
  258. <input type="checkbox" name="attach_order_pdf_to_admin_email" value="1" <?php echo !empty(
  259. $settings["attach_order_pdf_to_admin_email"]
  260. )
  261. ? "checked"
  262. : ""; ?>>
  263. PDF an interne Bestell-E-Mails anhängen
  264. </label>
  265. </div>
  266. <button type="submit" name="save_settings" class="btn">Speichern</button>
  267. </form>
  268. </div>
  269. <div class="panel panel-lg mt-4">
  270. <h3>Backups</h3>
  271. <p>Lokale Aufbewahrung: <?php echo (int) backupGetRetentionLimit(); ?> Backups</p>
  272. <p>Automatisches Intervall: <?php echo (int) floor(((int) BACKUP_AUTO_INTERVAL_SECONDS) / 86400); ?> Tage</p>
  273. <p>
  274. S3: <?php echo escape(settingsGetBackupCapabilityLabel($backupCapabilities["s3"])); ?> ·
  275. SFTP: <?php echo escape(settingsGetBackupCapabilityLabel($backupCapabilities["sftp"])); ?> ·
  276. Custom: <?php echo escape(settingsGetBackupCapabilityLabel($backupCapabilities["custom"])); ?> ·
  277. Managed: <?php echo escape(settingsGetBackupCapabilityLabel($backupCapabilities["managed"])); ?>
  278. </p>
  279. <form method="POST" class="inline-form">
  280. <?php echo csrfField(); ?>
  281. <button type="submit" name="create_backup" class="btn">Backup erstellen</button>
  282. </form>
  283. <h4 class="mt-4">Letzte Backups</h4>
  284. <?php if (empty($backups)): ?>
  285. <p>Es wurden noch keine Backups erstellt.</p>
  286. <?php else: ?>
  287. <div class="table-responsive">
  288. <table class="responsive-table">
  289. <thead>
  290. <tr>
  291. <th>Erstellt</th>
  292. <th>Auslöser</th>
  293. <th>Größe</th>
  294. <th>Dateien</th>
  295. <th>Remote</th>
  296. <th>Aktionen</th>
  297. </tr>
  298. </thead>
  299. <tbody>
  300. <?php foreach ($backups as $backup): ?>
  301. <tr>
  302. <td data-label="Erstellt"><?php echo escape(settingsFormatBackupDate((string) ($backup["created_at"] ?? ""))); ?></td>
  303. <td data-label="Auslöser"><?php echo (($backup["trigger"] ?? "") === "automatic") ? "Automatisch" : "Manuell"; ?></td>
  304. <td data-label="Größe"><?php echo escape(backupFormatBytes((int) ($backup["size"] ?? 0))); ?></td>
  305. <td data-label="Dateien"><?php echo (int) ($backup["file_count"] ?? 0); ?></td>
  306. <td data-label="Remote"><?php echo escape(settingsGetBackupUploadLabel($backup)); ?></td>
  307. <td data-label="Aktionen">
  308. <form method="POST" class="inline-form">
  309. <?php echo csrfField(); ?>
  310. <input type="hidden" name="backup_filename" value="<?php echo escape($backup["filename"] ?? ""); ?>">
  311. <button type="submit" name="download_backup" class="btn btn-secondary btn-small">Download</button>
  312. </form>
  313. </td>
  314. </tr>
  315. <?php endforeach; ?>
  316. </tbody>
  317. </table>
  318. </div>
  319. <?php endif; ?>
  320. </div>
  321. <div class="panel panel-lg mt-4">
  322. <h3>Updater</h3>
  323. <p>Installierte Version: <?php echo escape(APP_VERSION); ?></p>
  324. <p>Update-Status: <?php echo escape($updaterStatus["label"]); ?></p>
  325. <p><a href="updater.php" class="btn btn-secondary">Updater öffnen</a></p>
  326. </div>
  327. <?php include __DIR__ . "/../includes/footer.php"; ?>