Pārlūkot izejas kodu

moving gallery view into subfolder gallery

Medowar 3 stundas atpakaļ
vecāks
revīzija
0ec517044e
9 mainītis faili ar 45 papildinājumiem un 23 dzēšanām
  1. 2 1
      README.md
  2. 2 2
      admin/galleries.php
  3. 3 3
      admin/gallery-edit.php
  4. 12 0
      app/bootstrap.php
  5. 13 10
      app/partials.php
  6. 1 1
      docs/ADMIN-GUIDE.md
  7. 3 2
      docs/ARCHITECTURE.md
  8. 8 3
      gallery/index.php
  9. 1 1
      upload.php

+ 2 - 1
README.md

@@ -51,7 +51,8 @@ Upload the contents of this folder straight into your document root —
 
 ```
 index.php  landing page              ← document root
-showreel.php, gallery.php
+showreel.php
+gallery/   client galleries (/gallery/?g=<slug>)
 admin/     backoffice
 assets/    css + js
 media/     local images (hero + showreel)

+ 2 - 2
admin/galleries.php

@@ -77,7 +77,7 @@ flash_render();
     <p class="help">No galleries yet.</p>
 <?php else: ?>
 <div class="card"><table>
-    <tr><th>Title</th><th>Images</th><th>Protection</th><th>Expires</th><th>Created</th><th style="width:200px">Actions</th></tr>
+    <tr><th>Title</th><th>Images</th><th>Attributes</th><th>Expires</th><th>Created</th><th style="width:200px">Actions</th></tr>
     <?php foreach ($galleries as $g): $expired = gallery_is_expired($g); ?>
     <tr>
         <td><?= e($g['title']) ?></td>
@@ -94,7 +94,7 @@ flash_render();
         <td><?= e(substr($g['created_at'] ?? '', 0, 10)) ?></td>
         <td>
             <a href="gallery-edit.php?g=<?= e(rawurlencode($g['slug'])) ?>">Edit</a> ·
-            <a href="../gallery.php?g=<?= e(rawurlencode($g['slug'])) ?>" target="_blank" rel="noopener">View ↗</a>
+            <a href="../gallery/?g=<?= e(rawurlencode($g['slug'])) ?>" target="_blank" rel="noopener">View ↗</a>
             <form method="post" style="display:inline"
                   onsubmit="return confirm('Delete this gallery AND all its images on S3? This cannot be undone.')">
                 <?= csrf_field() ?>

+ 3 - 3
admin/gallery-edit.php

@@ -73,10 +73,10 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
 // so it matches whatever host/path this app is actually served under.
 $scheme  = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ? 'https' : 'http';
 $host    = $_SERVER['HTTP_HOST'] ?? 'localhost';
-// gallery.php lives one directory up from admin/ (cf. the "../gallery.php" link below).
+// The site root is one directory up from admin/ (cf. the "../gallery/" link below).
 $basePath = str_replace('\\', '/', dirname(dirname($_SERVER['SCRIPT_NAME'] ?? '/admin/gallery-edit.php')));
 $basePath = rtrim($basePath, '/');
-$shareUrl = $scheme . '://' . $host . $basePath . '/gallery.php?g=' . rawurlencode($slug);
+$shareUrl = $scheme . '://' . $host . $basePath . '/gallery/?g=' . rawurlencode($slug);
 // Guest upload link (only when a key is set). Same host/path derivation as above.
 $uploadUrl = !empty($gallery['upload_key'])
     ? $scheme . '://' . $host . $basePath . '/upload.php?g=' . rawurlencode($slug)
@@ -88,7 +88,7 @@ flash_render();
 ?>
 <h1><?= e($gallery['title']) ?></h1>
 <p class="help" style="margin-bottom:1.5rem">
-    Share link: <a href="../gallery.php?g=<?= e(rawurlencode($slug)) ?>" target="_blank" rel="noopener"><?= e($shareUrl) ?></a>
+    Share link: <a href="../gallery/?g=<?= e(rawurlencode($slug)) ?>" target="_blank" rel="noopener"><?= e($shareUrl) ?></a>
 </p>
 
 <div class="card">

+ 12 - 0
app/bootstrap.php

@@ -41,6 +41,18 @@ function config(string $path, mixed $default = null): mixed
     return $value;
 }
 
+/**
+ * Prefix a link or asset path with the way back to the site root, so the shared
+ * public partials work from any depth. Pages in the document root need no
+ * prefix; pages in a subfolder (gallery/) define SITE_BASE as '../' before
+ * including this file. Relative rather than absolute, because the app may be
+ * installed in a subdirectory of the domain.
+ */
+function base(string $path = ''): string
+{
+    return (defined('SITE_BASE') ? SITE_BASE : '') . $path;
+}
+
 /** HTML-escape for output. */
 function e(?string $s): string
 {

+ 13 - 10
app/partials.php

@@ -1,8 +1,9 @@
 <?php
 /**
  * Shared HTML fragments for the public site and the admin backoffice.
- * Public pages sit in the document root and admin pages in admin/, so asset
- * paths differ: public pages use 'assets/...', admin pages use '../assets/...'.
+ * Admin pages all sit in admin/ and hardcode '../assets/...'. Public pages sit
+ * in the document root except the gallery viewer in gallery/, so they prefix
+ * their links with base() — '' or '../' depending on the page (see bootstrap).
  */
 
 declare(strict_types=1);
@@ -11,21 +12,22 @@ function public_header(string $title, string $active = '', string $bodyClass = '
 {
     $site = site_get();
     $name = $site['intro_title'] ?: config('site.name', 'Portfolio');
+    $base = base();
     ?><!DOCTYPE html>
 <html lang="en">
 <head>
 <meta charset="utf-8">
 <meta name="viewport" content="width=device-width, initial-scale=1">
 <title><?= e($title) ?></title>
-<link rel="stylesheet" href="assets/site.css">
+<link rel="stylesheet" href="<?= e($base) ?>assets/site.css">
 </head>
 <body class="<?= e($bodyClass) ?>">
 <header class="nav" id="nav">
-    <a class="nav-brand" href="./"><?= e($name) ?></a>
+    <a class="nav-brand" href="<?= e($base ?: './') ?>"><?= e($name) ?></a>
     <nav class="nav-links">
-        <a href="./" class="<?= $active === 'home' ? 'active' : '' ?>">Home</a>
-        <a href="showreel.php" class="<?= $active === 'showreel' ? 'active' : '' ?>">Showreel</a>
-        <a href="contact.php" class="<?= $active === 'contact' ? 'active' : '' ?>">Contact</a>
+        <a href="<?= e($base ?: './') ?>" class="<?= $active === 'home' ? 'active' : '' ?>">Home</a>
+        <a href="<?= e($base) ?>showreel.php" class="<?= $active === 'showreel' ? 'active' : '' ?>">Showreel</a>
+        <a href="<?= e($base) ?>contact.php" class="<?= $active === 'contact' ? 'active' : '' ?>">Contact</a>
     </nav>
 </header>
 <?php
@@ -33,14 +35,15 @@ function public_header(string $title, string $active = '', string $bodyClass = '
 
 function public_footer(): void
 {
+    $base = base();
     ?><footer class="footer">
     <nav class="footer-links">
-        <a href="impressum.php">Impressum</a>
-        <a href="datenschutz.php">Datenschutz</a>
+        <a href="<?= e($base) ?>impressum.php">Impressum</a>
+        <a href="<?= e($base) ?>datenschutz.php">Datenschutz</a>
     </nav>
     <span>&copy; <?= date('Y') ?> <?= e(site_get()['intro_title'] ?: config('site.name', '')) ?></span>
 </footer>
-<script src="assets/site.js"></script>
+<script src="<?= e($base) ?>assets/site.js"></script>
 </body>
 </html>
 <?php

+ 1 - 1
docs/ADMIN-GUIDE.md

@@ -41,7 +41,7 @@ resolution affects new uploads only; images already in the gallery stay as they
 were stored.
 
 Each gallery gets an unguessable link like
-`/gallery.php?g=wedding-mueller-x7Kf3q` — copy the *Share link* from the
+`/gallery/?g=wedding-mueller-x7Kf3q` — copy the *Share link* from the
 gallery editor and send it to your client.
 
 **Upload images** by dropping them onto the upload area in the gallery editor.

+ 3 - 2
docs/ARCHITECTURE.md

@@ -12,7 +12,8 @@ Application internals sit in the same tree but are blocked from the web by
 ```
 index.php          landing page (hero + intro)            ← document root
 showreel.php       fullscreen portfolio, scroll-snap
-gallery.php        client gallery: password gate, expiry, grid + lightbox
+gallery/           client gallery viewer, served as /gallery/?g=<slug>
+  index.php        password gate, expiry, grid + lightbox
 admin/             backoffice (session-protected)
   api.php          JSON API for the uploader (presign / register)
 assets/            site.css, site.js (nav + lightbox), admin.js (uploader)
@@ -74,7 +75,7 @@ against the official AWS example vectors. Addressing style follows
 which Hetzner serves reliably; set `false` for virtual-hosted-style
 `https://<bucket>.<endpoint-host>/<key>`). Three uses:
 
-1. **Presigned GET** — `gallery.php` embeds signed image URLs
+1. **Presigned GET** — `gallery/index.php` embeds signed image URLs
    (`s3.url_ttl`, default 1 h). The browser fetches from S3 directly, so gallery
    image traffic never touches the webhost.
 2. **Signed PUT** — `s3_put_file()` streams uploaded originals and thumbnails

+ 8 - 3
gallery.php → gallery/index.php

@@ -1,14 +1,19 @@
 <?php
 /**
- * Client gallery viewer: /gallery.php?g=<slug>
+ * Client gallery viewer: /gallery/?g=<slug>
  *
  * - Unknown or expired galleries show the same neutral "not available" page.
  * - Password-protected galleries show a password form; a successful unlock
  *   is remembered in the session for that gallery only.
  * - Thumbnails and full-res originals are loaded by the browser directly
  *   from S3 via short-lived presigned URLs.
+ *
+ * This is the only public page outside the document root, hence SITE_BASE:
+ * the shared partials prefix their asset and nav links with it.
  */
-require __DIR__ . '/app/bootstrap.php';
+define('SITE_BASE', '../');
+
+require dirname(__DIR__) . '/app/bootstrap.php';
 
 session_boot();
 
@@ -31,7 +36,7 @@ if ($needsPassword && !$unlocked && $_SERVER['REQUEST_METHOD'] === 'POST') {
     csrf_verify();
     if (password_verify((string)($_POST['password'] ?? ''), $gallery['password_hash'])) {
         $_SESSION['gallery_unlocked'][$slug] = true;
-        redirect('gallery.php?g=' . rawurlencode($slug));
+        redirect('./?g=' . rawurlencode($slug));
     }
     $error = 'Wrong password.';
 }

+ 1 - 1
upload.php

@@ -7,7 +7,7 @@
  *   2. the gallery's password (if set), reusing the viewer's session unlock,
  *   3. the gallery's expiry.
  * A wrong/missing key is indistinguishable from a missing gallery — the same
- * neutral "not available" page as gallery.php, so links can't be enumerated.
+ * neutral "not available" page as the gallery viewer, so links can't be enumerated.
  */
 require __DIR__ . '/app/bootstrap.php';