settings.php 12 KB

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