Files
Hochzeit-Gaestebuch/index.php
2026-04-10 11:00:13 +02:00

640 lines
23 KiB
PHP

<?php
$brautpaar_namen = "Damian & Melissa";
$upload_verzeichnis = __DIR__ . '/uploads/';
$thumbnail_verzeichnis = __DIR__ . '/thumbnails/';
$deleted_verzeichnis = __DIR__ . '/deleted_uploads/';
// Sicherstellen, dass alle Verzeichnisse existieren
if (!is_dir($upload_verzeichnis)) mkdir($upload_verzeichnis, 0777, true);
if (!is_dir($thumbnail_verzeichnis)) mkdir($thumbnail_verzeichnis, 0777, true);
if (!is_dir($deleted_verzeichnis)) mkdir($deleted_verzeichnis, 0777, true);
function get_image_count($dir) {
if (!is_dir($dir)) return 0;
$files = scandir($dir);
return count(array_filter($files, function($f) use ($dir) {
return is_file($dir . $f) && !str_starts_with($f, '.');
}));
}
// Funktion zum Erstellen eines komprimierten Vorschaubildes inkl. EXIF-Rotationskorrektur
function createThumbnail($sourcePath, $destPath, $maxWidth = 400, $maxHeight = 400) {
if (!function_exists('imagecreatefromjpeg')) return false; // Fallback, falls GD-Lib fehlt
$info = getimagesize($sourcePath);
if (!$info) return false;
$mime = $info['mime'];
$sourceImage = null;
// Bild laden basierend auf MIME-Typ
switch ($mime) {
case 'image/jpeg': $sourceImage = imagecreatefromjpeg($sourcePath); break;
case 'image/png': $sourceImage = imagecreatefrompng($sourcePath); break;
case 'image/gif': $sourceImage = imagecreatefromgif($sourcePath); break;
case 'image/webp': $sourceImage = imagecreatefromwebp($sourcePath); break;
default: return false;
}
if (!$sourceImage) return false;
// --- NEU: EXIF Orientierung korrigieren (nur für JPEG) ---
if ($mime == 'image/jpeg' && function_exists('exif_read_data')) {
@$exif = exif_read_data($sourcePath); // @ unterdrückt Warnungen, falls keine EXIF-Daten vorhanden
if (!empty($exif['Orientation'])) {
switch ($exif['Orientation']) {
case 3: // 180 Grad drehen
$sourceImage = imagerotate($sourceImage, 180, 0);
break;
case 6: // 90 Grad nach rechts drehen (war vermutlich dein Problem)
$sourceImage = imagerotate($sourceImage, -90, 0);
break;
case 8: // 90 Grad nach links drehen
$sourceImage = imagerotate($sourceImage, 90, 0);
break;
}
}
}
// --------------------------------------------------------
// Dimensionen NACH möglicher Drehung holen
$srcWidth = imagesx($sourceImage);
$srcHeight = imagesy($sourceImage);
// Proportionale Skalierung berechnen
$ratio = min($maxWidth / $srcWidth, $maxHeight / $srcHeight);
if ($ratio >= 1) {
$newWidth = $srcWidth;
$newHeight = $srcHeight;
} else {
$newWidth = (int)($srcWidth * $ratio);
$newHeight = (int)($srcHeight * $ratio);
}
$destImage = imagecreatetruecolor($newWidth, $newHeight);
// Transparenz für PNG und WEBP beibehalten
if ($mime == 'image/png' || $mime == 'image/webp') {
imagealphablending($destImage, false);
imagesavealpha($destImage, true);
$transparent = imagecolorallocatealpha($destImage, 255, 255, 255, 127);
imagefilledrectangle($destImage, 0, 0, $newWidth, $newHeight, $transparent);
}
imagecopyresampled($destImage, $sourceImage, 0, 0, 0, 0, $newWidth, $newHeight, $srcWidth, $srcHeight);
// Thumbnail speichern
switch ($mime) {
case 'image/jpeg': imagejpeg($destImage, $destPath, 80); break;
case 'image/png': imagepng($destImage, $destPath, 8); break;
case 'image/gif': imagegif($destImage, $destPath); break;
case 'image/webp': imagewebp($destImage, $destPath, 80); break;
}
imagedestroy($sourceImage);
imagedestroy($destImage);
return true;
}
// -- LÖSCHEN LOGIK --
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'delete') {
$fileToDelete = basename($_POST['filename']);
if (is_file($upload_verzeichnis . $fileToDelete)) {
// Verschiebe Originalbild
rename($upload_verzeichnis . $fileToDelete, $deleted_verzeichnis . $fileToDelete);
// Verschiebe Thumbnail, falls vorhanden
if (is_file($thumbnail_verzeichnis . $fileToDelete)) {
rename($thumbnail_verzeichnis . $fileToDelete, $deleted_verzeichnis . 'thumb_' . $fileToDelete);
}
echo json_encode(['success' => true]);
} else {
echo json_encode(['success' => false, 'error' => 'Datei nicht gefunden.']);
}
exit;
}
// -- UPLOAD LOGIK --
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['photos'])) {
// Limit auf 15 Bilder pro Upload serverseitig prüfen
if (count($_FILES['photos']['tmp_name']) > 15) {
echo json_encode(['success' => false, 'error' => 'Maximal 15 Bilder auf einmal erlaubt.']);
exit;
}
$cleanName = 'Foto';
$uploadedCount = 0;
$uploadedFiles = []; // Speichert die Namen für das LocalStorage des Nutzers
foreach ($_FILES['photos']['tmp_name'] as $key => $tmpName) {
if ($tmpName != "") {
$extension = strtolower(pathinfo($_FILES['photos']['name'][$key], PATHINFO_EXTENSION));
$newName = $cleanName . '_' . time() . '_' . substr(md5(uniqid()), 0, 6) . '.' . $extension;
$destination = $upload_verzeichnis . $newName;
$thumbDestination = $thumbnail_verzeichnis . $newName;
if (move_uploaded_file($tmpName, $destination)) {
$uploadedCount++;
$uploadedFiles[] = $newName;
// Erstelle Thumbnail (jetzt mit Rotationskorrektur)
createThumbnail($destination, $thumbDestination);
}
}
}
echo json_encode([
'success' => true,
'count' => $uploadedCount,
'newTotal' => get_image_count($upload_verzeichnis),
'uploadedFiles' => $uploadedFiles
]);
exit;
}
$current_count = get_image_count($upload_verzeichnis);
// Bilder für die Galerie laden (nur die neuesten 30)
$gallery_images = [];
if (is_dir($upload_verzeichnis)) {
$files = scandir($upload_verzeichnis);
$valid_files = [];
foreach ($files as $f) {
if (is_file($upload_verzeichnis . $f) && !str_starts_with($f, '.')) {
$valid_files[] = $f;
}
}
// ZUVERLÄSSIGE SORTIERUNG: Alphabetisch absteigend, da Dateiname = Zeitstempel
rsort($valid_files);
$gallery_images = array_slice($valid_files, 0, 30);
}
?>
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Fotobox - <?php echo $brautpaar_namen; ?></title>
<link href="https://fonts.googleapis.com/css2?family=Playfair+Display:ital,wght@0,400;0,700;1,400&family=Montserrat:wght@300;400;600&display=swap" rel="stylesheet">
<style>
:root {
--dark-green: #484D46;
--olive-green: #5F6251;
--brown: #856856;
--light-brown: #A88F79;
--beige: #BEB7AD;
--bg-page: #FAF9F7;
--white: #ffffff;
--red: #d9534f;
}
* { box-sizing: border-box; }
body {
font-family: 'Montserrat', sans-serif;
background-color: var(--bg-page);
color: var(--dark-green);
margin: 0;
display: flex;
justify-content: center;
align-items: flex-start;
min-height: 100vh;
padding: 20px;
transition: align-items 0.3s ease;
}
.container {
max-width: 500px;
width: 100%;
text-align: center;
background: var(--white);
padding: 40px 30px;
border-radius: 12px;
box-shadow: 0 10px 40px rgba(72, 77, 70, 0.1);
overflow: hidden;
margin-top: 20px;
transition: margin-top 0.3s ease;
}
h1 {
font-family: 'Playfair Display', serif;
font-size: clamp(1.2rem, 7vw, 2.5rem);
white-space: nowrap;
margin-bottom: 5px;
font-weight: 400;
letter-spacing: 1px;
color: var(--dark-green);
}
.sub-header {
font-size: 0.85rem;
text-transform: uppercase;
letter-spacing: 3px;
margin-bottom: 20px;
border-bottom: 1px solid var(--beige);
padding-bottom: 20px;
color: var(--light-brown);
}
.counter-box {
margin: 0 0 20px 0;
padding: 15px;
background: var(--beige);
border-radius: 8px;
color: var(--dark-green);
}
.counter-number {
display: block;
font-family: 'Playfair Display', serif;
font-size: 2.2rem;
font-weight: bold;
}
.counter-label {
font-size: 0.75rem;
text-transform: uppercase;
letter-spacing: 1px;
opacity: 0.85;
}
.tabs {
display: flex;
justify-content: center;
gap: 10px;
margin-bottom: 30px;
}
.tab-btn {
background: var(--bg-page);
color: var(--light-brown);
border: 1px solid var(--beige);
padding: 10px 20px;
border-radius: 6px;
cursor: pointer;
font-family: 'Montserrat', sans-serif;
font-weight: 600;
text-transform: uppercase;
font-size: 0.8rem;
transition: all 0.3s ease;
flex: 1;
}
.tab-btn.active {
background: var(--olive-green);
color: var(--white);
border-color: var(--olive-green);
}
.form-group { margin-bottom: 25px; text-align: left; }
.upload-area {
border: 2px dashed var(--light-brown);
border-radius: 8px;
padding: 40px 30px;
cursor: pointer;
background-color: var(--white);
transition: all 0.3s;
color: var(--olive-green);
text-align: center;
}
.upload-area:hover {
background: var(--bg-page);
border-color: var(--olive-green);
}
.upload-area.uploading {
pointer-events: none;
opacity: 0.7;
}
#progress-wrapper { display: none; margin-top: 20px; text-align: left; }
.progress-bar-container {
width: 100%; height: 6px; background: var(--beige);
margin-top: 10px; border-radius: 3px; overflow: hidden;
}
#progress-bar { height: 100%; background: var(--olive-green); width: 0%; transition: width 0.2s; }
#gallery-section { display: none; }
.gallery-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 10px;
}
.gallery-item {
width: 100%;
aspect-ratio: 1 / 1;
object-fit: cover;
border-radius: 6px;
cursor: pointer;
transition: transform 0.2s;
background-color: var(--beige);
}
.gallery-item:hover { transform: scale(1.03); }
.empty-gallery {
grid-column: 1 / -1;
color: var(--light-brown);
font-size: 0.9rem;
padding: 20px;
}
.lightbox {
display: none;
position: fixed;
z-index: 9999;
top: 0; left: 0; width: 100%; height: 100%;
background-color: rgba(72, 77, 70, 0.95);
justify-content: center;
align-items: center;
padding: 20px;
opacity: 0;
transition: opacity 0.3s ease;
flex-direction: column;
}
.lightbox.show { display: flex; opacity: 1; }
.lightbox img {
max-width: 100%;
max-height: 80vh;
border-radius: 8px;
box-shadow: 0 10px 30px rgba(0,0,0,0.5);
margin-bottom: 20px;
}
.close-lightbox {
position: absolute;
top: 20px; right: 30px;
color: var(--white);
font-size: 40px;
cursor: pointer;
font-family: sans-serif;
line-height: 1;
}
.delete-btn {
background: var(--red);
color: white;
border: none;
padding: 12px 24px;
border-radius: 6px;
font-family: 'Montserrat', sans-serif;
font-weight: 600;
text-transform: uppercase;
cursor: pointer;
display: none; /* Wird via JS angezeigt */
transition: background 0.3s;
}
.delete-btn:hover { background: #c9302c; }
.success-screen { display: none; animation: fadeIn 0.5s ease; }
@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
.heart { font-size: 2.5rem; margin: 20px 0; color: var(--brown); }
.btn-primary {
background: var(--olive-green); color: var(--white); border: none; border-radius: 6px;
padding: 16px 30px; width: 100%; text-transform: uppercase; letter-spacing: 2px;
font-weight: 600; cursor: pointer; transition: background 0.3s;
}
.btn-primary:hover { background: var(--dark-green); }
.btn-outline { background: none; color: var(--dark-green); border: 1px solid var(--dark-green); margin-top: 20px; }
.btn-outline:hover { background: var(--bg-page); }
</style>
</head>
<body>
<div class="container">
<div id="main-ui">
<h1><?php echo $brautpaar_namen; ?></h1>
<div class="sub-header">Unsere Hochzeitsgalerie</div>
<div class="counter-box">
<span class="counter-number" id="live-counter"><?php echo $current_count; ?></span>
<span class="counter-label">Bilder wurden geteilt</span>
</div>
<div class="tabs">
<button class="tab-btn active" id="btn-upload" onclick="switchTab('upload')">Hochladen</button>
<button class="tab-btn" id="btn-gallery" onclick="switchTab('gallery')">Galerie</button>
</div>
<div id="upload-section">
<form id="upload-form">
<div class="form-group">
<div class="upload-area" id="drop-area" onclick="document.getElementById('photos').click()">
<span style="display:block; margin-bottom: 10px; font-size: 2rem;">📸</span>
<span>Klicken zum Auswählen<br>oder Kamera öffnen<br><small>(Max. 15 Bilder)</small></span>
<input type="file" id="photos" name="photos[]" accept="image/*" multiple style="display:none">
</div>
</div>
</form>
<div id="progress-wrapper">
<div id="status-text" style="font-size: 0.9rem; font-weight: 600; text-align: center;">Upload startet...</div>
<div class="progress-bar-container">
<div id="progress-bar"></div>
</div>
</div>
</div>
<div id="gallery-section">
<div class="gallery-grid">
<?php if (count($gallery_images) > 0): ?>
<?php foreach ($gallery_images as $img): ?>
<?php
// Prüfen, ob Thumbnail existiert, sonst Original nehmen
$thumbSrc = is_file($thumbnail_verzeichnis . $img) ? "thumbnails/" . urlencode($img) : "uploads/" . urlencode($img);
$fullSrc = "uploads/" . urlencode($img);
?>
<img src="<?php echo $thumbSrc; ?>"
data-full="<?php echo $fullSrc; ?>"
data-filename="<?php echo htmlspecialchars($img); ?>"
class="gallery-item"
loading="lazy"
onclick="openLightbox(this.dataset.full, this.dataset.filename)"
alt="Hochzeitsfoto">
<?php endforeach; ?>
<?php else: ?>
<div class="empty-gallery">Noch keine Bilder hochgeladen. Sei der oder die Erste!</div>
<?php endif; ?>
</div>
</div>
</div>
<div id="success-ui" class="success-screen">
<div class="heart">♥</div>
<h2 style="font-family: 'Playfair Display', serif;">Vielen Dank!</h2>
<p>Deine Bilder wurden erfolgreich zur Galerie hinzugefügt.</p>
<button class="btn-primary btn-outline" onclick="location.reload()">Zur Galerie & Mehr teilen</button>
</div>
</div>
<div id="lightbox" class="lightbox" onclick="closeLightbox(event)">
<span class="close-lightbox" onclick="closeLightbox(event)">&times;</span>
<img id="lightbox-img" src="" alt="Vollbild">
<button id="lightbox-delete-btn" class="delete-btn">Bild löschen</button>
</div>
<script>
// --- Upload Logik ---
const fileInput = document.getElementById('photos');
const dropArea = document.getElementById('drop-area');
const counterDisplay = document.getElementById('live-counter');
const progressWrapper = document.getElementById('progress-wrapper');
const progressBar = document.getElementById('progress-bar');
const statusText = document.getElementById('status-text');
fileInput.onchange = () => {
const files = fileInput.files;
if(files.length === 0) return;
// Validierung: Max 15 Bilder
if (files.length > 15) {
alert("Bitte wähle maximal 15 Bilder auf einmal aus.");
fileInput.value = ''; // Input zurücksetzen
return;
}
dropArea.classList.add('uploading');
dropArea.innerHTML = `<span style="display:block; margin-bottom: 10px; font-size: 2rem;">⏳</span><span>Wird verarbeitet...</span>`;
progressWrapper.style.display = 'block';
const formData = new FormData();
for (let i = 0; i < files.length; i++) {
formData.append('photos[]', files[i]);
}
const xhr = new XMLHttpRequest();
xhr.open('POST', '', true);
xhr.upload.onprogress = (e) => {
if (e.lengthComputable) {
const percent = Math.round((e.loaded / e.total) * 100);
progressBar.style.width = percent + '%';
statusText.innerText = percent + '% hochgeladen...';
}
};
xhr.onload = () => {
if (xhr.status === 200) {
try {
const resp = JSON.parse(xhr.responseText);
if(resp.success) {
// Speicher hochgeladene Dateien im LocalStorage
let myUploads = JSON.parse(localStorage.getItem('myUploads') || '[]');
myUploads = myUploads.concat(resp.uploadedFiles);
localStorage.setItem('myUploads', JSON.stringify(myUploads));
counterDisplay.innerText = resp.newTotal;
document.getElementById('main-ui').style.display = 'none';
document.getElementById('success-ui').style.display = 'block';
document.body.style.alignItems = 'center';
document.querySelector('.container').style.marginTop = '0';
} else {
alert(resp.error || "Fehler beim Upload.");
resetUploadArea();
}
} catch(e) {
alert("Fehler beim Verarbeiten der Serverantwort.");
resetUploadArea();
}
} else {
alert("Ein Serverfehler ist aufgetreten.");
resetUploadArea();
}
};
xhr.send(formData);
};
function resetUploadArea() {
dropArea.classList.remove('uploading');
dropArea.innerHTML = `<span style="display:block; margin-bottom: 10px; font-size: 2rem;">📸</span><span>Klicken zum Auswählen<br>oder Kamera öffnen<br><small>(Max. 15 Bilder)</small></span>`;
progressWrapper.style.display = 'none';
fileInput.value = '';
}
// --- Tabs Navigation ---
function switchTab(tab) {
document.getElementById('upload-section').style.display = tab === 'upload' ? 'block' : 'none';
document.getElementById('gallery-section').style.display = tab === 'gallery' ? 'block' : 'none';
document.getElementById('btn-upload').classList.toggle('active', tab === 'upload');
document.getElementById('btn-gallery').classList.toggle('active', tab === 'gallery');
}
// --- Lightbox & Löschen Logik ---
const lightbox = document.getElementById('lightbox');
const lightboxImg = document.getElementById('lightbox-img');
const deleteBtn = document.getElementById('lightbox-delete-btn');
function openLightbox(imageFullSrc, filename) {
lightboxImg.src = imageFullSrc;
lightbox.classList.add('show');
document.body.style.overflow = 'hidden';
// Prüfen, ob Bild dem Nutzer gehört
const myUploads = JSON.parse(localStorage.getItem('myUploads') || '[]');
if (myUploads.includes(filename)) {
deleteBtn.style.display = 'inline-block';
deleteBtn.onclick = (e) => {
e.stopPropagation();
deleteImage(filename);
};
} else {
deleteBtn.style.display = 'none';
}
}
function closeLightbox(e) {
if (e.target === lightbox || e.target.className === 'close-lightbox') {
lightbox.classList.remove('show');
document.body.style.overflow = 'auto';
setTimeout(() => { lightboxImg.src = ''; }, 300);
}
}
function deleteImage(filename) {
if (!confirm("Möchtest du dein Bild wirklich entfernen?")) return;
const formData = new FormData();
formData.append('action', 'delete');
formData.append('filename', filename);
fetch('', {
method: 'POST',
body: formData
})
.then(response => response.json())
.then(data => {
if (data.success) {
// Aus dem LocalStorage entfernen
let myUploads = JSON.parse(localStorage.getItem('myUploads') || '[]');
myUploads = myUploads.filter(f => f !== filename);
localStorage.setItem('myUploads', JSON.stringify(myUploads));
// Seite neuladen um die Galerie zu aktualisieren
location.reload();
} else {
alert(data.error || "Fehler beim Löschen.");
}
})
.catch(error => {
console.error('Error:', error);
alert("Netzwerkfehler beim Löschen.");
});
}
</script>
</body>
</html>