storage.php 2.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105
  1. <?php
  2. declare(strict_types=1);
  3. // Flat-file storage primitives: atomic writes via a temp file plus rename, so
  4. // a crashed request can never leave a half-written index behind.
  5. function manageEnsureDirectory(string $dir): void
  6. {
  7. if (!is_dir($dir) && !mkdir($dir, 02775, true) && !is_dir($dir)) {
  8. throw new RuntimeException("Verzeichnis konnte nicht erstellt werden: " . $dir);
  9. }
  10. @chmod($dir, 02775);
  11. }
  12. function manageReadJsonFile(string $file): array
  13. {
  14. if (!is_file($file)) {
  15. return [];
  16. }
  17. $content = file_get_contents($file);
  18. if ($content === false || trim($content) === "") {
  19. return [];
  20. }
  21. $decoded = json_decode($content, true);
  22. if (!is_array($decoded)) {
  23. throw new RuntimeException("JSON-Datei ist beschädigt: " . basename($file));
  24. }
  25. return $decoded;
  26. }
  27. function manageWriteJsonFile(string $file, array $data): void
  28. {
  29. manageEnsureDirectory(dirname($file));
  30. if (is_file($file) && !is_writable($file)) {
  31. @chmod($file, 0664);
  32. }
  33. $json = json_encode(
  34. $data,
  35. JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE,
  36. );
  37. if ($json === false) {
  38. throw new RuntimeException("JSON konnte nicht kodiert werden.");
  39. }
  40. $tmpFile = $file . ".tmp";
  41. if (file_put_contents($tmpFile, $json . PHP_EOL, LOCK_EX) === false) {
  42. throw new RuntimeException("JSON konnte nicht geschrieben werden.");
  43. }
  44. @chmod($tmpFile, 0664);
  45. // rename() is atomic on the same filesystem.
  46. if (!rename($tmpFile, $file)) {
  47. @unlink($tmpFile);
  48. throw new RuntimeException("JSON konnte nicht gespeichert werden.");
  49. }
  50. @chmod($file, 0664);
  51. }
  52. function manageRemoveDirectory(string $dir): void
  53. {
  54. if (!is_dir($dir)) {
  55. return;
  56. }
  57. $items = new RecursiveIteratorIterator(
  58. new RecursiveDirectoryIterator($dir, FilesystemIterator::SKIP_DOTS),
  59. RecursiveIteratorIterator::CHILD_FIRST,
  60. );
  61. foreach ($items as $item) {
  62. if ($item->isDir()) {
  63. @rmdir($item->getPathname());
  64. } else {
  65. @unlink($item->getPathname());
  66. }
  67. }
  68. @rmdir($dir);
  69. }
  70. // Reads the first bytes of a file and reports whether they look like a ZIP
  71. // local file / empty archive / spanned archive header.
  72. function manageFileIsZip(string $path): bool
  73. {
  74. $handle = @fopen($path, "rb");
  75. if ($handle === false) {
  76. return false;
  77. }
  78. $signature = fread($handle, 4);
  79. fclose($handle);
  80. return $signature === "PK\x03\x04" ||
  81. $signature === "PK\x05\x06" ||
  82. $signature === "PK\x07\x08";
  83. }