added photo school
This commit is contained in:
parent
aba16ce7f3
commit
2734679938
Binary file not shown.
|
After Width: | Height: | Size: 19 KiB |
BIN
public/userarea/photoschool/1_1769673301_dsc_2857.jpg
Normal file
BIN
public/userarea/photoschool/1_1769673301_dsc_2857.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 49 KiB |
BIN
public/userarea/photoschool/1_1769673304_giocattolo-975apd-1.jpg
Normal file
BIN
public/userarea/photoschool/1_1769673304_giocattolo-975apd-1.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 212 KiB |
@ -55,6 +55,11 @@ if ($is_new) {
|
|||||||
} else {
|
} else {
|
||||||
// se esiste, sincronizza school_id in sessione
|
// se esiste, sincronizza school_id in sessione
|
||||||
$_SESSION['school_id'] = (int)$school['id'];
|
$_SESSION['school_id'] = (int)$school['id'];
|
||||||
|
|
||||||
|
// Carica foto esistenti
|
||||||
|
$stmtPhotos = $pdo->prepare("SELECT id, filename FROM school_photos WHERE school_id = ? ORDER BY sort_order ASC, id ASC");
|
||||||
|
$stmtPhotos->execute([$school['id']]);
|
||||||
|
$existingPhotos = $stmtPhotos->fetchAll(PDO::FETCH_ASSOC);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -69,10 +74,69 @@ function generateSlug($string)
|
|||||||
return $slug;
|
return $slug;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ridimensiona immagine con GD (max 1920 px lato lungo, qualità 82%)
|
||||||
|
function resizeAndSaveImage($tmp_name, $target_path, $maxDimension = 1920, $quality = 82)
|
||||||
|
{
|
||||||
|
list($width, $height, $type) = getimagesize($tmp_name);
|
||||||
|
|
||||||
|
if ($width <= $maxDimension && $height <= $maxDimension) {
|
||||||
|
return move_uploaded_file($tmp_name, $target_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
$ratio = min($maxDimension / $width, $maxDimension / $height);
|
||||||
|
$newW = (int)($width * $ratio);
|
||||||
|
$newH = (int)($height * $ratio);
|
||||||
|
|
||||||
|
$src = null;
|
||||||
|
switch ($type) {
|
||||||
|
case IMAGETYPE_JPEG:
|
||||||
|
$src = imagecreatefromjpeg($tmp_name);
|
||||||
|
break;
|
||||||
|
case IMAGETYPE_PNG:
|
||||||
|
$src = imagecreatefrompng($tmp_name);
|
||||||
|
break;
|
||||||
|
case IMAGETYPE_GIF:
|
||||||
|
$src = imagecreatefromgif($tmp_name);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!$src) return false;
|
||||||
|
|
||||||
|
$dst = imagecreatetruecolor($newW, $newH);
|
||||||
|
|
||||||
|
// Trasparenza per PNG
|
||||||
|
if ($type == IMAGETYPE_PNG) {
|
||||||
|
imagealphablending($dst, false);
|
||||||
|
imagesavealpha($dst, true);
|
||||||
|
$transparent = imagecolorallocatealpha($dst, 255, 255, 255, 127);
|
||||||
|
imagefilledrectangle($dst, 0, 0, $newW, $newH, $transparent);
|
||||||
|
}
|
||||||
|
|
||||||
|
imagecopyresampled($dst, $src, 0, 0, 0, 0, $newW, $newH, $width, $height);
|
||||||
|
|
||||||
|
$success = false;
|
||||||
|
switch ($type) {
|
||||||
|
case IMAGETYPE_JPEG:
|
||||||
|
$success = imagejpeg($dst, $target_path, $quality);
|
||||||
|
break;
|
||||||
|
case IMAGETYPE_PNG:
|
||||||
|
$success = imagepng($dst, $target_path, (int)(9 - ($quality / 10)));
|
||||||
|
break;
|
||||||
|
case IMAGETYPE_GIF:
|
||||||
|
$success = imagegif($dst, $target_path);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
imagedestroy($src);
|
||||||
|
imagedestroy($dst);
|
||||||
|
return $success;
|
||||||
|
}
|
||||||
|
|
||||||
// POST - Salvataggio
|
// POST - Salvataggio
|
||||||
$success_message = $error = null;
|
$success_message = $error = null;
|
||||||
|
|
||||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
if ($_SERVER['REQUEST_METHOD'] === 'POST' && !isset($_POST['action'])) {
|
||||||
$name = trim($_POST['name'] ?? '');
|
$name = trim($_POST['name'] ?? '');
|
||||||
$slug = generateSlug(trim($_POST['slug'] ?? $name));
|
$slug = generateSlug(trim($_POST['slug'] ?? $name));
|
||||||
$website = trim($_POST['website'] ?? '');
|
$website = trim($_POST['website'] ?? '');
|
||||||
@ -88,7 +152,8 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|||||||
$longitude = !empty($_POST['longitude']) ? floatval($_POST['longitude']) : null;
|
$longitude = !empty($_POST['longitude']) ? floatval($_POST['longitude']) : null;
|
||||||
$owner_name = trim($_POST['owner_name'] ?? '');
|
$owner_name = trim($_POST['owner_name'] ?? '');
|
||||||
$vat_number = trim($_POST['vat_number'] ?? '');
|
$vat_number = trim($_POST['vat_number'] ?? '');
|
||||||
$status = in_array($_POST['status'] ?? 'active', ['active', 'inactive', 'suspended']) ? $_POST['status'] : 'active';
|
$rawStatus = $_POST['status'] ?? 'active';
|
||||||
|
$status = in_array($rawStatus, ['active', 'inactive', 'suspended'], true) ? $rawStatus : 'active';
|
||||||
|
|
||||||
// Validazioni
|
// Validazioni
|
||||||
if (empty($name)) $error = "Il nome della scuola è obbligatorio.";
|
if (empty($name)) $error = "Il nome della scuola è obbligatorio.";
|
||||||
@ -199,6 +264,92 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AJAX per gestione foto
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action'])) {
|
||||||
|
header('Content-Type: application/json; charset=utf-8');
|
||||||
|
|
||||||
|
if ($is_new || empty($school['id'])) {
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Prima salva il profilo scuola']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$school_id = (int)$school['id'];
|
||||||
|
|
||||||
|
if ($_POST['action'] === 'upload_photos') {
|
||||||
|
$currentCount = $pdo->query("SELECT COUNT(*) FROM school_photos WHERE school_id = $school_id")->fetchColumn();
|
||||||
|
$canAdd = 5 - $currentCount;
|
||||||
|
|
||||||
|
if ($canAdd <= 0) {
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Limite di 5 foto raggiunto']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$uploaded = [];
|
||||||
|
$errors = [];
|
||||||
|
|
||||||
|
foreach ($_FILES['photos']['tmp_name'] ?? [] as $i => $tmp) {
|
||||||
|
if ($canAdd <= count($uploaded)) break;
|
||||||
|
if (empty($tmp) || $_FILES['photos']['error'][$i] !== 0) continue;
|
||||||
|
|
||||||
|
$origName = $_FILES['photos']['name'][$i];
|
||||||
|
$ext = strtolower(pathinfo($origName, PATHINFO_EXTENSION));
|
||||||
|
if (!in_array($ext, ['jpg', 'jpeg', 'png', 'gif'])) {
|
||||||
|
$errors[] = $origName . ' - formato non supportato';
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$safeName = preg_replace('/[^a-z0-9._-]/i', '', pathinfo($origName, PATHINFO_FILENAME));
|
||||||
|
$newFilename = "photoschool/{$school_id}_" . time() . "_{$safeName}.{$ext}";
|
||||||
|
|
||||||
|
if (resizeAndSaveImage($tmp, $newFilename)) {
|
||||||
|
$stmt = $pdo->prepare("INSERT INTO school_photos
|
||||||
|
(school_id, filename, original_name, mime_type, file_size, sort_order)
|
||||||
|
VALUES (?, ?, ?, ?, ?, ?)");
|
||||||
|
$stmt->execute([
|
||||||
|
$school_id,
|
||||||
|
$newFilename,
|
||||||
|
$origName,
|
||||||
|
$_FILES['photos']['type'][$i] ?: 'image/jpeg',
|
||||||
|
(int)$_FILES['photos']['size'][$i],
|
||||||
|
$currentCount + count($uploaded)
|
||||||
|
]);
|
||||||
|
|
||||||
|
$uploaded[] = [
|
||||||
|
'id' => $pdo->lastInsertId(),
|
||||||
|
'filename' => $newFilename
|
||||||
|
];
|
||||||
|
} else {
|
||||||
|
$errors[] = $origName . ' - errore elaborazione';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
echo json_encode([
|
||||||
|
'success' => !empty($uploaded),
|
||||||
|
'uploaded' => $uploaded,
|
||||||
|
'errors' => $errors,
|
||||||
|
'remaining' => 5 - ($currentCount + count($uploaded))
|
||||||
|
]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($_POST['action'] === 'delete_photo' && !empty($_POST['photo_id'])) {
|
||||||
|
$photoId = (int)$_POST['photo_id'];
|
||||||
|
$stmt = $pdo->prepare("SELECT filename FROM school_photos WHERE id = ? AND school_id = ?");
|
||||||
|
$stmt->execute([$photoId, $school_id]);
|
||||||
|
$row = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
if ($row) {
|
||||||
|
if (file_exists($row['filename'])) @unlink($row['filename']);
|
||||||
|
$pdo->prepare("DELETE FROM school_photos WHERE id = ?")->execute([$photoId]);
|
||||||
|
}
|
||||||
|
echo json_encode(['success' => true]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Azione non valida']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
?>
|
?>
|
||||||
|
|
||||||
<!doctype html>
|
<!doctype html>
|
||||||
@ -277,6 +428,18 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|||||||
letter-spacing: 1px;
|
letter-spacing: 1px;
|
||||||
margin: 0 auto 1rem;
|
margin: 0 auto 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Thumbnails foto scuola */
|
||||||
|
.school-photo-thumb {
|
||||||
|
cursor: zoom-in;
|
||||||
|
transition: transform .15s ease, box-shadow .15s ease;
|
||||||
|
box-shadow: 0 2px 10px rgba(0, 0, 0, .08);
|
||||||
|
}
|
||||||
|
|
||||||
|
.school-photo-thumb:hover {
|
||||||
|
transform: scale(1.03);
|
||||||
|
box-shadow: 0 10px 25px rgba(0, 0, 0, .16);
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
@ -463,6 +626,49 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<!-- Sezione foto scuole -->
|
||||||
|
<?php if (!$is_new): ?>
|
||||||
|
<div class="row mt-5">
|
||||||
|
<div class="col-12">
|
||||||
|
<hr class="my-4">
|
||||||
|
<h5>Foto della scuola (max 5)</h5>
|
||||||
|
|
||||||
|
<div id="photos-dropzone" style="border: 2px dashed #adb5bd; border-radius: 10px; padding: 30px; text-align: center; background: #f8f9fa; min-height: 160px; cursor: pointer;">
|
||||||
|
<p class="mb-2">Trascina le immagini qui oppure</p>
|
||||||
|
<button type="button" class="btn btn-outline-primary btn-sm" id="btn-select-photos">Seleziona file</button>
|
||||||
|
<input type="file" id="photos-input" name="photos[]" multiple accept="image/jpeg,image/png,image/gif" style="display:none;">
|
||||||
|
<div class="mt-3 text-muted">Foto rimanenti: <strong id="photos-remaining"><?php echo 5 - count($existingPhotos ?? []); ?></strong></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="photos-preview" class="mt-4 d-flex flex-wrap gap-3">
|
||||||
|
<?php foreach ($existingPhotos ?? [] as $photo): ?>
|
||||||
|
<div class="position-relative" style="width:160px; height:160px;" data-photo-id="<?php echo $photo['id']; ?>">
|
||||||
|
<img class="school-photo-thumb"
|
||||||
|
src="<?php echo htmlspecialchars($photo['filename']); ?>"
|
||||||
|
alt="Foto scuola"
|
||||||
|
style="width:100%; height:100%; object-fit:cover; border-radius:8px; border:1px solid #dee2e6;">
|
||||||
|
|
||||||
|
<button type="button" class="btn btn-danger btn-sm position-absolute top-0 end-0 translate-middle rounded-circle p-0" style="width:28px; height:28px; font-size:18px; line-height:1;" data-delete-id="<?php echo $photo['id']; ?>">×</button>
|
||||||
|
</div>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</div>
|
||||||
|
<!-- Modal zoom foto -->
|
||||||
|
<div class="modal fade" id="photoZoomModal" tabindex="-1" aria-hidden="true">
|
||||||
|
<div class="modal-dialog modal-dialog-centered modal-xl">
|
||||||
|
<div class="modal-content bg-dark border-0">
|
||||||
|
<div class="modal-header border-0">
|
||||||
|
<button type="button" class="btn-close btn-close-white ms-auto" data-bs-dismiss="modal" aria-label="Chiudi"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body text-center p-0 pb-4">
|
||||||
|
<img id="photoZoomImg" src="" alt="Foto scuola" style="max-width:100%; max-height:80vh; object-fit:contain;">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
@ -645,6 +851,167 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|||||||
reader.readAsDataURL(file);
|
reader.readAsDataURL(file);
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
<script>
|
||||||
|
// Gestione foto
|
||||||
|
const dropzone = document.getElementById('photos-dropzone');
|
||||||
|
const fileInput = document.getElementById('photos-input');
|
||||||
|
const btnSelect = document.getElementById('btn-select-photos');
|
||||||
|
const previewContainer = document.getElementById('photos-preview');
|
||||||
|
const remainingSpan = document.getElementById('photos-remaining');
|
||||||
|
|
||||||
|
let remainingPhotos = parseInt(remainingSpan?.innerText || '0');
|
||||||
|
|
||||||
|
function updateRemainingCount() {
|
||||||
|
if (remainingSpan) remainingSpan.innerText = remainingPhotos;
|
||||||
|
if (remainingPhotos <= 0 && dropzone) {
|
||||||
|
dropzone.style.opacity = '0.5';
|
||||||
|
dropzone.style.pointerEvents = 'none';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function addPhotoPreview(url, photoId) {
|
||||||
|
const wrapper = document.createElement('div');
|
||||||
|
wrapper.className = 'position-relative';
|
||||||
|
wrapper.style.width = '160px';
|
||||||
|
wrapper.style.height = '160px';
|
||||||
|
wrapper.dataset.photoId = photoId;
|
||||||
|
|
||||||
|
wrapper.innerHTML = `
|
||||||
|
<img class="school-photo-thumb" src="${url}" alt="Foto" style="width:100%; height:100%; object-fit:cover; border-radius:8px; border:1px solid #dee2e6;">
|
||||||
|
<button type="button" class="btn btn-danger btn-sm position-absolute top-0 end-0 translate-middle rounded-circle p-0" style="width:28px;height:28px;font-size:18px;line-height:1;" data-delete-id="${photoId}">×</button>
|
||||||
|
`;
|
||||||
|
previewContainer.appendChild(wrapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
function deletePhoto(btn) {
|
||||||
|
const wrapper = btn.closest('.position-relative');
|
||||||
|
const photoId = wrapper.dataset.photoId;
|
||||||
|
|
||||||
|
if (!photoId) {
|
||||||
|
wrapper.remove();
|
||||||
|
remainingPhotos++;
|
||||||
|
updateRemainingCount();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!confirm('Vuoi eliminare questa foto?')) return;
|
||||||
|
|
||||||
|
fetch('', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/x-www-form-urlencoded'
|
||||||
|
},
|
||||||
|
body: 'action=delete_photo&photo_id=' + photoId
|
||||||
|
})
|
||||||
|
.then(r => r.json())
|
||||||
|
.then(data => {
|
||||||
|
if (data.success) {
|
||||||
|
wrapper.remove();
|
||||||
|
remainingPhotos++;
|
||||||
|
updateRemainingCount();
|
||||||
|
} else {
|
||||||
|
alert('Errore durante l\'eliminazione');
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(() => alert('Errore di connessione'));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Eventi
|
||||||
|
if (dropzone) {
|
||||||
|
dropzone.addEventListener('click', (e) => {
|
||||||
|
// se clicco sul bottone (o dentro al bottone), NON devo aprire due volte
|
||||||
|
if (e.target.closest('#btn-select-photos')) return;
|
||||||
|
fileInput.click();
|
||||||
|
});
|
||||||
|
|
||||||
|
btnSelect?.addEventListener('click', (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation(); // IMPORTANTISSIMO: evita il click anche sulla dropzone
|
||||||
|
fileInput.click();
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
['dragover', 'dragenter'].forEach(ev => dropzone.addEventListener(ev, e => {
|
||||||
|
e.preventDefault();
|
||||||
|
dropzone.classList.add('bg-light', 'border-primary');
|
||||||
|
}));
|
||||||
|
|
||||||
|
['dragleave', 'drop'].forEach(ev => dropzone.addEventListener(ev, e => {
|
||||||
|
e.preventDefault();
|
||||||
|
dropzone.classList.remove('bg-light', 'border-primary');
|
||||||
|
}));
|
||||||
|
|
||||||
|
dropzone.addEventListener('drop', e => handleFiles(e.dataTransfer.files));
|
||||||
|
|
||||||
|
fileInput.addEventListener('change', e => {
|
||||||
|
handleFiles(e.target.files);
|
||||||
|
fileInput.value = '';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleFiles(files) {
|
||||||
|
if (remainingPhotos <= 0) return alert('Limite di 5 foto raggiunto');
|
||||||
|
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append('action', 'upload_photos');
|
||||||
|
|
||||||
|
let added = 0;
|
||||||
|
for (let file of files) {
|
||||||
|
if (added >= remainingPhotos) break;
|
||||||
|
if (!file.type.startsWith('image/')) continue;
|
||||||
|
formData.append('photos[]', file);
|
||||||
|
added++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (added === 0) return;
|
||||||
|
|
||||||
|
fetch('', {
|
||||||
|
method: 'POST',
|
||||||
|
body: formData
|
||||||
|
})
|
||||||
|
.then(r => r.json())
|
||||||
|
.then(data => {
|
||||||
|
if (data.success) {
|
||||||
|
data.uploaded.forEach(item => {
|
||||||
|
addPhotoPreview(item.filename, item.id);
|
||||||
|
});
|
||||||
|
remainingPhotos -= data.uploaded.length;
|
||||||
|
updateRemainingCount();
|
||||||
|
}
|
||||||
|
if (data.errors && data.errors.length) {
|
||||||
|
alert('Problemi con alcuni file:\n' + data.errors.join('\n'));
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
console.error(err);
|
||||||
|
alert('Errore durante il caricamento');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
previewContainer?.addEventListener('click', e => {
|
||||||
|
// Se clicco la X: elimina
|
||||||
|
if (e.target.hasAttribute('data-delete-id')) {
|
||||||
|
e.preventDefault();
|
||||||
|
deletePhoto(e.target);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Se clicco una foto: zoom
|
||||||
|
if (e.target && e.target.tagName === 'IMG') {
|
||||||
|
const modalEl = document.getElementById('photoZoomModal');
|
||||||
|
const zoomImg = document.getElementById('photoZoomImg');
|
||||||
|
if (!modalEl || !zoomImg) return;
|
||||||
|
|
||||||
|
zoomImg.src = e.target.src;
|
||||||
|
|
||||||
|
// Bootstrap modal
|
||||||
|
const modal = new bootstrap.Modal(modalEl);
|
||||||
|
modal.show();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
updateRemainingCount();
|
||||||
|
</script>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
Loading…
x
Reference in New Issue
Block a user