galleries.php 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124
  1. <?php
  2. /**
  3. * Gallery overview: create new galleries, list and delete existing ones.
  4. */
  5. require dirname(__DIR__) . '/app/bootstrap.php';
  6. auth_require();
  7. if ($_SERVER['REQUEST_METHOD'] === 'POST') {
  8. csrf_verify();
  9. $action = $_POST['action'] ?? '';
  10. if ($action === 'create') {
  11. $title = trim((string)($_POST['title'] ?? ''));
  12. if ($title === '') {
  13. flash_set('Please enter a gallery title.', 'error');
  14. redirect('galleries.php');
  15. }
  16. $slug = slugify($title) . '-' . random_token(6);
  17. $password = (string)($_POST['password'] ?? '');
  18. $gallery = [
  19. 'slug' => $slug,
  20. 'title' => $title,
  21. 'created_at' => date('Y-m-d H:i:s'),
  22. 'password_hash' => $password !== '' ? password_hash($password, PASSWORD_DEFAULT) : null,
  23. 'expires_at' => trim((string)($_POST['expires_at'] ?? '')) ?: null,
  24. // Longest edge the browser downscales uploads to; null = original.
  25. 'max_resolution' => parse_max_resolution($_POST),
  26. // Drop EXIF/XMP/IPTC from uploads on the way to S3 (app/exif.php).
  27. 'strip_exif' => !empty($_POST['strip_exif']),
  28. 'images' => [],
  29. ];
  30. // A guest upload link is just a per-gallery secret in the URL; presence
  31. // of upload_key = guest uploads enabled (revocable from the edit page).
  32. if (!empty($_POST['allow_uploads'])) {
  33. $gallery['upload_key'] = random_token(24);
  34. }
  35. gallery_save($gallery);
  36. flash_set('Gallery created. Now add images.');
  37. redirect('gallery-edit.php?g=' . rawurlencode($slug));
  38. }
  39. if ($action === 'delete') {
  40. $gallery = gallery_load((string)($_POST['slug'] ?? ''));
  41. if ($gallery !== null) {
  42. s3_delete_gallery_objects($gallery);
  43. gallery_delete($gallery['slug']);
  44. flash_set('Gallery and its S3 images deleted.');
  45. }
  46. redirect('galleries.php');
  47. }
  48. }
  49. $galleries = galleries_all();
  50. admin_header('Galleries', 'galleries');
  51. flash_render();
  52. ?>
  53. <h1>Galleries</h1>
  54. <form method="post" class="card">
  55. <?= csrf_field() ?>
  56. <input type="hidden" name="action" value="create">
  57. <h2 style="margin-top:0">New gallery</h2>
  58. <label for="t">Title</label>
  59. <input type="text" id="t" name="title" placeholder="Wedding Miller — June 2026" required>
  60. <label for="p">Password <span style="text-transform:none;letter-spacing:0">(optional — leave blank for a public link)</span></label>
  61. <input type="text" id="p" name="password" autocomplete="off">
  62. <label for="ex">Expiry date <span style="text-transform:none;letter-spacing:0">(optional — gallery is hidden after this day)</span></label>
  63. <input type="date" id="ex" name="expires_at">
  64. <?php resolution_field() ?>
  65. <?php strip_exif_field() ?>
  66. <p class="help"><label style="display:inline;text-transform:none;letter-spacing:0">
  67. <input type="checkbox" name="allow_uploads" value="1"> Allow guest uploads via a shared link
  68. </label></p>
  69. <button type="submit">Create gallery</button>
  70. </form>
  71. <h2>Existing galleries (<?= count($galleries) ?>)</h2>
  72. <?php if (!$galleries): ?>
  73. <p class="help">No galleries yet.</p>
  74. <?php else: ?>
  75. <div class="card"><table>
  76. <tr><th>Title</th><th>Images</th><th>Views</th><th>Downloads</th><th>Attributes</th><th>Expires</th><th>Created</th><th style="width:200px">Actions</th></tr>
  77. <?php foreach ($galleries as $g):
  78. $expired = gallery_is_expired($g);
  79. $stats = gallery_stats($g['slug']);
  80. ?>
  81. <tr>
  82. <td><?= e($g['title']) ?></td>
  83. <td><?= count($g['images'] ?? []) ?></td>
  84. <td title="<?= $stats['last_viewed_at'] ? 'Last opened ' . e($stats['last_viewed_at']) : 'Never opened' ?>">
  85. <?= $stats['views'] ?>
  86. </td>
  87. <td title="<?= $stats['last_download_at'] ? 'Last download ' . e($stats['last_download_at']) : 'Never downloaded' ?>">
  88. <?php /* "—" only while downloads never ran: a count survives them being switched off. */ ?>
  89. <?= empty($g['downloads_enabled']) && $stats['downloads'] === 0 ? '—' : $stats['downloads'] ?>
  90. </td>
  91. <td>
  92. <?= !empty($g['password_hash']) ? '<span class="tag tag-lock">password</span>' : '<span class="tag">open</span>' ?>
  93. <?= !empty($g['upload_key']) ? ' <span class="tag">uploads</span>' : '' ?>
  94. <?= isset($g['max_resolution']) ? ' <span class="tag">' . e(resolution_label((int)$g['max_resolution'])) . '</span>' : '' ?>
  95. <?= !empty($g['strip_exif']) ? ' <span class="tag">no exif</span>' : '' ?>
  96. </td>
  97. <td>
  98. <?= e($g['expires_at'] ?? '—') ?>
  99. <?= $expired ? ' <span class="tag tag-expired">expired</span>' : '' ?>
  100. </td>
  101. <td><?= e(substr($g['created_at'] ?? '', 0, 10)) ?></td>
  102. <td>
  103. <a href="gallery-edit.php?g=<?= e(rawurlencode($g['slug'])) ?>">Edit</a> ·
  104. <a href="../gallery/?g=<?= e(rawurlencode($g['slug'])) ?>" target="_blank" rel="noopener">View ↗</a>
  105. <form method="post" style="display:inline"
  106. onsubmit="return confirm('Delete this gallery AND all its images on S3? This cannot be undone.')">
  107. <?= csrf_field() ?>
  108. <input type="hidden" name="action" value="delete">
  109. <input type="hidden" name="slug" value="<?= e($g['slug']) ?>">
  110. <button class="btn-danger" style="margin:0;padding:.25rem .7rem">Delete</button>
  111. </form>
  112. </td>
  113. </tr>
  114. <?php endforeach; ?>
  115. </table></div>
  116. <?php endif; ?>
  117. <?php admin_footer(); ?>