index.php 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102
  1. <?php
  2. /**
  3. * Client gallery viewer: /gallery/?g=<slug>
  4. *
  5. * - Unknown or expired galleries show the same neutral "not available" page.
  6. * - Password-protected galleries show a password form; a successful unlock
  7. * is remembered in the session for that gallery only.
  8. * - Thumbnails and full-res originals are loaded by the browser directly
  9. * from S3 via short-lived presigned URLs.
  10. *
  11. * This is the only public page outside the document root, hence SITE_BASE:
  12. * the shared partials prefix their asset and nav links with it.
  13. */
  14. define('SITE_BASE', '../');
  15. require dirname(__DIR__) . '/app/bootstrap.php';
  16. session_boot();
  17. $slug = (string)($_GET['g'] ?? '');
  18. $gallery = $slug !== '' ? gallery_load($slug) : null;
  19. if ($gallery === null || gallery_is_expired($gallery)) {
  20. http_response_code(404);
  21. public_header('Gallery not available');
  22. echo '<div class="gate"><div class="gate-card"><h1>Gallery not available</h1>'
  23. . '<p class="page-sub">This gallery does not exist or is no longer online.</p></div></div>';
  24. public_footer();
  25. exit;
  26. }
  27. $needsPassword = !empty($gallery['password_hash']);
  28. $unlocked = !$needsPassword || !empty($_SESSION['gallery_unlocked'][$slug]);
  29. if ($needsPassword && !$unlocked && $_SERVER['REQUEST_METHOD'] === 'POST') {
  30. csrf_verify();
  31. if (password_verify((string)($_POST['password'] ?? ''), $gallery['password_hash'])) {
  32. $_SESSION['gallery_unlocked'][$slug] = true;
  33. redirect('./?g=' . rawurlencode($slug));
  34. }
  35. $error = 'Wrong password.';
  36. }
  37. if ($needsPassword && !$unlocked) {
  38. public_header(e($gallery['title']));
  39. ?>
  40. <div class="gate"><div class="gate-card">
  41. <h1><?= e($gallery['title']) ?></h1>
  42. <?php if (!empty($error)): ?><div class="flash flash-error"><?= e($error) ?></div><?php endif; ?>
  43. <form method="post">
  44. <?= csrf_field() ?>
  45. <label for="pw">Password</label>
  46. <input type="password" id="pw" name="password" autofocus autocomplete="off">
  47. <button type="submit">Open gallery</button>
  48. </form>
  49. </div></div>
  50. <?php
  51. public_footer();
  52. exit;
  53. }
  54. public_header(e($gallery['title']));
  55. ?>
  56. <main class="page">
  57. <div class="page-head">
  58. <div>
  59. <h1 class="page-title"><?= e($gallery['title']) ?></h1>
  60. <p class="page-sub">
  61. <?= count($gallery['images'] ?? []) ?> photos
  62. <?php if (!empty($gallery['expires_at'])): ?>
  63. · available until <?= e($gallery['expires_at']) ?>
  64. <?php endif; ?>
  65. </p>
  66. </div>
  67. <?php if (!empty($gallery['downloads_enabled'])): ?>
  68. <?php
  69. // Outside .grid on purpose: assets/site.js binds the lightbox to
  70. // every <a> inside the grid and would swallow this link's click.
  71. $archive = $gallery['archive'] ?? null;
  72. ?>
  73. <?php if ($archive !== null && !archive_is_stale($gallery)): ?>
  74. <a class="btn btn-ghost page-action" href="download.php?g=<?= e(rawurlencode($slug)) ?>">
  75. Download all · <?= e(human_bytes((int)$archive['size'])) ?>
  76. </a>
  77. <?php else: ?>
  78. <span class="btn btn-ghost btn-disabled page-action"
  79. title="The zip archive is outdated and is being recreated. This can take up to an hour — please check back later.">
  80. Download all
  81. </span>
  82. <?php endif; ?>
  83. <?php endif; ?>
  84. </div>
  85. <div class="grid">
  86. <?php foreach ($gallery['images'] ?? [] as $img): ?>
  87. <a href="<?= e(s3_presign_get($img['key'])) ?>">
  88. <img src="<?= e(s3_presign_get($img['thumb'] ?? $img['key'])) ?>"
  89. alt="<?= e($img['name'] ?? '') ?>" loading="lazy">
  90. </a>
  91. <?php endforeach; ?>
  92. </div>
  93. </main>
  94. <?php public_footer(); ?>