diff --git a/albums.php b/albums.php new file mode 100644 index 0000000..80449b5 --- /dev/null +++ b/albums.php @@ -0,0 +1,100 @@ +isDot()) continue; + if ($item->isDir()) { + $albumPath = $item->getPathname(); + $info = getAlbumInfo($albumPath); + + $images = hasSubfolders($albumPath) ? + getImagesRecursively($albumPath) : + getLatestImages($albumPath); + + $albums[] = [ + 'path' => str_replace('\\', '/', $albumPath), + 'title' => $info['title'], + 'description' => $info['description'], + 'images' => $images, + 'hasSubfolders' => hasSubfolders($albumPath), + 'hasImages' => hasImages($albumPath) + ]; + } +} + +// Déterminer le chemin parent pour le bouton retour +$parentPath = dirname($currentPath); +if (!isSecurePath($parentPath)) { + $parentPath = null; +} +?> + + + + + Albums - ICO + + + + + + Retour + + Retour + + +
+ + +
+ +
+ + $image): ?> +
+ + +
+ + +
+
+

+
+
+ +
+ + + + \ No newline at end of file diff --git a/favicon.png b/favicon.png new file mode 100644 index 0000000..d7a4106 Binary files /dev/null and b/favicon.png differ diff --git a/fonctions.php b/fonctions.php new file mode 100644 index 0000000..0d4704c --- /dev/null +++ b/fonctions.php @@ -0,0 +1,204 @@ + basename($albumPath), + 'description' => '' + ]; + + if (file_exists($infoFile)) { + $content = file_get_contents($infoFile); + $lines = explode("\n", $content); + if (isset($lines[0])) $info['title'] = trim($lines[0]); + if (isset($lines[1])) $info['description'] = trim($lines[1]); + } + + return $info; +} + +/** + * Récupère les dernières images d'un dossier + * @param string $albumPath Chemin vers l'album + * @param int $limit Nombre d'images à récupérer + * @return array Tableau des URLs des images + */ +function getLatestImages($albumPath, $limit = 4) { + $images = []; + $baseUrl = getBaseUrl(); + + if (!is_dir($albumPath)) return $images; + + foreach (new DirectoryIterator($albumPath) as $file) { + if ($file->isDot()) continue; + if ($file->isFile()) { + $extension = strtolower($file->getExtension()); + if (in_array($extension, ALLOWED_EXTENSIONS)) { + $relativePath = str_replace('\\', '/', substr($file->getPathname(), strlen(realpath('./')))); + $images[] = $baseUrl . '/' . ltrim($relativePath, '/'); + } + } + } + + usort($images, function($a, $b) { + $pathA = realpath('.') . str_replace(getBaseUrl(), '', $a); + $pathB = realpath('.') . str_replace(getBaseUrl(), '', $b); + return filectime($pathB) - filectime($pathA); + }); + + return array_slice($images, 0, $limit); +} + +/** + * Récupère les images de manière récursive dans tous les sous-dossiers + * @param string $albumPath Chemin vers l'album + * @param int $limit Nombre d'images à récupérer + * @return array Tableau des URLs des images + */ +function getImagesRecursively($albumPath, $limit = 4) { + $images = []; + $baseUrl = getBaseUrl(); + + if (!is_dir($albumPath)) return $images; + + $iterator = new RecursiveIteratorIterator( + new RecursiveDirectoryIterator($albumPath), + RecursiveIteratorIterator::SELF_FIRST + ); + + foreach ($iterator as $file) { + if ($file->isFile()) { + $extension = strtolower($file->getExtension()); + if (in_array($extension, ALLOWED_EXTENSIONS)) { + $relativePath = str_replace('\\', '/', substr($file->getPathname(), strlen(realpath('./')))); + $images[] = $baseUrl . '/' . ltrim($relativePath, '/'); + } + } + } + + usort($images, function($a, $b) { + $pathA = realpath('.') . str_replace(getBaseUrl(), '', $a); + $pathB = realpath('.') . str_replace(getBaseUrl(), '', $b); + return filectime($pathB) - filectime($pathA); + }); + + return array_slice($images, 0, $limit); +} + +/** + * Vérifie si un dossier contient des sous-dossiers + * @param string $path Chemin du dossier + * @return bool True si le dossier contient des sous-dossiers + */ +function hasSubfolders($path) { + if (!is_dir($path)) return false; + + foreach (new DirectoryIterator($path) as $item) { + if ($item->isDot()) continue; + if ($item->isDir()) return true; + } + return false; +} + +/** + * Vérifie si un dossier contient des images + * @param string $path Chemin du dossier + * @return bool True si le dossier contient des images + */ +function hasImages($path) { + if (!is_dir($path)) return false; + + foreach (new DirectoryIterator($path) as $item) { + if ($item->isDot()) continue; + if ($item->isFile()) { + $extension = strtolower($item->getExtension()); + if (in_array($extension, ALLOWED_EXTENSIONS)) return true; + } + } + return false; +} + +/** + * Vérifie si le chemin est sécurisé (dans le dossier liste_albums) + * @param string $path Chemin à vérifier + * @return bool True si le chemin est sécurisé + */ +function isSecurePath($path) { + $realPath = realpath($path); + $rootPath = realpath('./liste_albums'); + return $realPath && strpos($realPath, $rootPath) === 0; +} + +/** + * Formate la taille d'un fichier en format lisible + * @param int $bytes Taille en octets + * @return string Taille formatée (ex: "1.2 MB") + */ +function formatFileSize($bytes) { + $units = ['B', 'KB', 'MB', 'GB', 'TB']; + $bytes = max($bytes, 0); + $pow = floor(($bytes ? log($bytes) : 0) / log(1024)); + $pow = min($pow, count($units) - 1); + $bytes /= pow(1024, $pow); + return round($bytes, 1) . ' ' . $units[$pow]; +} + +/** + * Récupère les dimensions d'une image de manière sécurisée + * @param string $path Chemin vers l'image + * @return array|false Tableau contenant width et height ou false en cas d'erreur + */ +function getSecureImageSize($path) { + try { + if (!file_exists($path)) return false; + $imageInfo = getimagesize($path); + if ($imageInfo === false) return false; + return [ + 'width' => $imageInfo[0], + 'height' => $imageInfo[1] + ]; + } catch (Exception $e) { + return false; + } +} + +/** + * Génère un identifiant unique sécurisé + * @param int $length Longueur de l'identifiant + * @return string Identifiant unique + */ +function generateSecureId($length = 32) { + return bin2hex(random_bytes($length / 2)); +} + +/** + * Nettoie et sécurise un nom de fichier + * @param string $filename Nom du fichier à nettoyer + * @return string Nom de fichier sécurisé + */ +function sanitizeFilename($filename) { + // Supprime les caractères spéciaux + $filename = preg_replace('/[^a-zA-Z0-9._-]/', '-', $filename); + // Évite les noms de fichiers commençant par un point + $filename = ltrim($filename, '.'); + // Limite la longueur du nom de fichier + return substr($filename, 0, 255); +} +?> \ No newline at end of file diff --git a/galeries.php b/galeries.php new file mode 100644 index 0000000..188ba13 --- /dev/null +++ b/galeries.php @@ -0,0 +1,110 @@ +isDot()) continue; + if ($file->isFile()) { + $extension = strtolower($file->getExtension()); + if (in_array($extension, ['jpg', 'jpeg', 'png', 'gif'])) { + $relativePath = str_replace('\\', '/', substr($file->getPathname(), strlen(realpath('./')))); + $url = $baseUrl . '/' . ltrim($relativePath, '/'); + $images[] = $url; + } + } +} + +usort($images, function($a, $b) { + $pathA = realpath('.') . str_replace(getBaseUrl(), '', $a); + $pathB = realpath('.') . str_replace(getBaseUrl(), '', $b); + return filectime($pathB) - filectime($pathA); +}); + +$headerImage = !empty($images) ? $images[0] : null; + +$parentPath = dirname($currentPath); +if (!isSecurePath($parentPath)) { + $parentPath = './liste_albums'; +} +?> + + + + + <?php echo htmlspecialchars($albumInfo['title']); ?> - ICO + + + + + Retour + + + + + + + + + + + + + + \ No newline at end of file diff --git a/index.php b/index.php new file mode 100644 index 0000000..c2c4d82 --- /dev/null +++ b/index.php @@ -0,0 +1,79 @@ +isFile()) { + $extension = strtolower($file->getExtension()); + if (in_array($extension, ['jpg', 'jpeg', 'png', 'gif'])) { + $images[] = str_replace('\\', '/', $file->getPathname()); + } + } + } + + usort($images, function($a, $b) { + return filectime($b) - filectime($a); + }); + + return array_slice($images, 0, $limit); +} + +$latestImages = getLatestImages(); +?> + + + + + + + ICO - Galerie d'images + + + + + + +
+

ICO

+

ICO est la galerie d'images de l'association Camélia Studio.

+ Accéder aux galeries +
+ + + + \ No newline at end of file diff --git a/liste_albums/infos.txt b/liste_albums/infos.txt new file mode 100644 index 0000000..22a4746 --- /dev/null +++ b/liste_albums/infos.txt @@ -0,0 +1,2 @@ +Titre +Description sur la seconde ligne. \ No newline at end of file diff --git a/partage.php b/partage.php new file mode 100644 index 0000000..fd5daff --- /dev/null +++ b/partage.php @@ -0,0 +1,200 @@ + + + + + + + + Image - ICO + + + + +
+
+ Image partagée +
+ +
+ + + + + + + + + + + Télécharger + +
+
+ + + + + + + + + + + Partager l'image - ICO + + + + + + +
+
+ Image partagée +
+ +
+ + + + + + + + + + + Télécharger + +
+
+ + + + \ No newline at end of file diff --git a/styles.css b/styles.css new file mode 100644 index 0000000..1b85ee9 --- /dev/null +++ b/styles.css @@ -0,0 +1,460 @@ +/* Accessibilité */ +@media (prefers-reduced-motion: reduce) { + * { + animation-duration: 0.01ms !important; + animation-iteration-count: 1 !important; + transition-duration: 0.01ms !important; + scroll-behavior: auto !important; + } +}/* Reset et styles de base */ +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +body { + font-family: 'Segoe UI', system-ui, sans-serif; + background-color: #121212; + color: #ffffff; + min-height: 100vh; + overflow-x: hidden; + position: relative; +} + +/* Styles de la page d'accueil */ +.carousel { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100vh; + z-index: 1; +} + +.carousel-slide { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + opacity: 0; + transition: opacity 1s ease-in-out; + pointer-events: none; +} + +.carousel-slide.active { + opacity: 1; + z-index: 2; +} + +.carousel-slide img { + width: 100%; + height: 100vh; + object-fit: cover; + filter: brightness(0.6); +} + +/* Overlay contenu */ +.overlay { + position: fixed; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + text-align: center; + z-index: 20; + width: 90%; + max-width: 600px; + padding: 2rem; + background-color: rgba(0, 0, 0, 0.7); + border-radius: 1rem; + backdrop-filter: blur(10px); +} + +h1 { + font-size: 4rem; + margin-bottom: 1rem; + font-weight: 700; + letter-spacing: 0.2rem; +} + +p { + font-size: 1.2rem; + margin-bottom: 2rem; + line-height: 1.6; + color: #e0e0e0; +} + +.cta-button { + display: inline-block; + padding: 1rem 2rem; + background-color: #2196f3; + color: white; + text-decoration: none; + border-radius: 2rem; + font-size: 1.1rem; + font-weight: 500; + transition: all 0.3s ease; + border: 2px solid transparent; +} + +.cta-button:hover { + background-color: #1976d2; + transform: translateY(-2px); + box-shadow: 0 4px 12px rgba(33, 150, 243, 0.3); +} + +/* Styles pour la page des albums */ +.albums-page { + background-color: #121212; + min-height: 100vh; + padding: 2rem; + overflow-y: auto; +} + +.albums-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); + gap: 2rem; + max-width: 1800px; + margin: 4rem auto 2rem auto; +} + +.album-card { + background-color: #1e1e1e; + border-radius: 1rem; + overflow: hidden; + text-decoration: none; + color: white; + opacity: 0; + transform: translateY(20px); + transition: opacity 0.5s ease, transform 0.5s ease; +} + +.album-card.visible { + opacity: 1; + transform: translateY(0); +} + +.album-card:hover { + transform: translateY(-5px); + box-shadow: 0 8px 16px rgba(0, 0, 0, 0.3); +} + +.album-images { + position: relative; + display: grid; + grid-template-columns: repeat(2, 1fr); + grid-template-rows: repeat(2, 1fr); + aspect-ratio: 1; + gap: 2px; + background-color: #2a2a2a; +} + +.album-image { + width: 100%; + height: 100%; + background-size: cover; + background-position: center; + background-repeat: no-repeat; +} + +.empty-image { + width: 100%; + height: 100%; + background-color: #2a2a2a; +} + +.empty-album { + width: 100%; + height: 100%; + background-color: #2a2a2a; + grid-column: 1 / -1; + grid-row: 1 / -1; +} + +.album-info { + padding: 1rem; +} + +.album-info h2 { + font-size: 1.2rem; + margin: 0; + font-weight: 500; +} + +/* Styles pour la page galerie */ +.gallery-page { + background-color: #121212; + min-height: 100vh; + padding: 0; + margin: 0; +} + +.gallery-header { + width: 100%; + height: 500px; + overflow: hidden; + position: relative; +} + +.header-image { + width: 100%; + height: 100%; + object-fit: cover; +} + +.gallery-info { + padding: 2rem; + max-width: 1800px; + margin: 0 auto; +} + +.gallery-grid { + padding: 2rem; + max-width: 1800px; + margin: 0 auto; +} + +.gallery-grid.is-loading .gallery-item { + opacity: 0; +} + +.gallery-grid .gallery-item { + opacity: 1; + transition: opacity 0.3s ease; +} + +.gallery-item { + width: calc((100% - (7 * 20px)) / 8); + min-width: 200px; + margin-bottom: 20px; + border-radius: 8px; + overflow: hidden; + background-color: #1e1e1e; + transition: transform 0.3s ease; +} + +.gallery-item:hover { + transform: translateY(-5px); +} + +.gallery-item img { + width: 100%; + height: auto; + min-height: 150px; + object-fit: cover; + display: block; +} + +/* Bouton retour commun */ +.back-button { + position: fixed; + top: 2rem; + left: 2rem; + padding: 0.8rem 1.5rem; + background-color: rgba(0, 0, 0, 0.7); + color: white; + text-decoration: none; + border-radius: 2rem; + backdrop-filter: blur(10px); + z-index: 100; + transition: all 0.3s ease; +} + +.back-button:hover { + background-color: rgba(0, 0, 0, 0.9); + transform: translateY(-2px); +} + +/* Media Queries */ +@media (min-width: 1400px) { + .albums-grid { + grid-template-columns: repeat(5, 1fr); + } +} + +@media (max-width: 1600px) { + .gallery-item { + width: calc((100% - (5 * 20px)) / 6); + } +} + +@media (max-width: 1200px) { + .albums-grid { + grid-template-columns: repeat(4, 1fr); + } + .gallery-item { + width: calc((100% - (3 * 20px)) / 5); + } +} + +@media (max-width: 992px) { + .albums-grid { + grid-template-columns: repeat(3, 1fr); + } + .gallery-item { + width: calc((100% - (3 * 20px)) / 4); + } +} + +@media (max-width: 768px) { + .albums-grid { + grid-template-columns: repeat(2, 1fr); + gap: 1rem; + } + + .albums-page { + padding: 1rem; + } + + .gallery-item { + width: calc((100% - (2 * 20px)) / 3); + } + + .gallery-header { + height: 300px; + } + + .gallery-info { + padding: 1.5rem; + } + + .gallery-info h1 { + font-size: 2rem; + } + + h1 { + font-size: 3rem; + } + + p { + font-size: 1rem; + } + + .cta-button { + padding: 0.8rem 1.6rem; + font-size: 1rem; + } +} + +@media (max-width: 480px) { + .albums-grid { + grid-template-columns: 1fr; + } + + .gallery-item { + width: calc((100% - 20px) / 2); + } + + .gallery-grid { + padding: 0.5rem; + } + + .gallery-header { + height: 200px; + } + + .back-button { + top: 1rem; + left: 1rem; + padding: 0.6rem 1.2rem; + font-size: 0.9rem; + } +} + +.share-page { + background-color: rgba(0, 0, 0, 0.9); + min-height: 100vh; + display: flex; + justify-content: center; + align-items: center; +} + +.share-container { + max-width: 90vw; + max-height: 90vh; + position: relative; +} + +.share-image { + border-radius: 8px; + overflow: hidden; + background-color: #1e1e1e; + box-shadow: 0 4px 24px rgba(0, 0, 0, 0.5); +} + +.share-image img { + max-width: 100%; + max-height: 90vh; + display: block; + object-fit: contain; +} + +.share-actions { + position: fixed; + bottom: 2rem; + left: 50%; + transform: translateX(-50%); + display: flex; + gap: 1rem; + background-color: rgba(0, 0, 0, 0.8); + padding: 1rem; + border-radius: 2rem; + backdrop-filter: blur(10px); +} + +/* Ajuster .action-button pour la page de partage */ +.share-actions .action-button { + display: inline-flex; + align-items: center; + gap: 0.5rem; + padding: 0.8rem 1.5rem; + background-color: #2196f3; + color: white; + border: none; + border-radius: 2rem; + cursor: pointer; + font-size: 1rem; + text-decoration: none; + transition: all 0.3s ease; +} + +.share-actions .action-button:hover { + background-color: #1976d2; + transform: translateY(-2px); +} + +.share-actions .action-button .icon { + width: 1.2em; + height: 1.2em; +} + +/* Protection par mot de passe */ +.protected-indicator { + display: inline-block; + margin-left: 0.5rem; + font-size: 0.8em; + vertical-align: middle; +} + +/* Media queries pour la page de partage */ +@media (max-width: 768px) { + .share-actions { + flex-direction: column; + padding: 0.8rem; + } + + .share-actions .action-button { + padding: 0.6rem 1.2rem; + font-size: 0.9rem; + } +} +@media (prefers-reduced-motion: reduce) { + * { + animation-duration: 0.01ms !important; + animation-iteration-count: 1 !important; + transition-duration: 0.01ms !important; + scroll-behavior: auto !important; + } +} \ No newline at end of file