amélioration de la liste des fichiers
This commit is contained in:
parent
5d901cb8b2
commit
9f54cc1519
207
admin.php
207
admin.php
@ -240,17 +240,59 @@ ob_start(); ?>
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="card">
|
||||
<?php
|
||||
// Traitement de la suppression de fichier
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'delete_file') {
|
||||
if (!isset($_POST['csrf_token']) || !Cyla::verifyCSRFToken($_POST['csrf_token'])) {
|
||||
header('Location: admin.php?error=' . urlencode('Token de sécurité invalide'));
|
||||
exit;
|
||||
} else if (!isset($_POST['filename'])) {
|
||||
header('Location: admin.php?error=' . urlencode('Nom de fichier manquant'));
|
||||
exit;
|
||||
} else {
|
||||
$result = Cyla::deleteFile($_POST['filename']);
|
||||
if ($result['success']) {
|
||||
header('Location: admin.php?success=' . urlencode('Fichier supprimé avec succès'));
|
||||
exit;
|
||||
} else {
|
||||
header('Location: admin.php?error=' . urlencode($result['error']));
|
||||
exit;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Récupération de la page courante depuis l'URL
|
||||
$currentPage = isset($_GET['page']) ? max(1, intval($_GET['page'])) : 1;
|
||||
$filesPerPage = 20;
|
||||
|
||||
// Récupération des fichiers avec pagination
|
||||
$fileData = Cyla::listFiles($currentPage, $filesPerPage);
|
||||
$files = $fileData['files'];
|
||||
?>
|
||||
|
||||
<div class="card">
|
||||
<h2>Fichiers hébergés</h2>
|
||||
|
||||
<?php if (empty($files)): ?>
|
||||
<p class="text-muted">Aucun fichier hébergé</p>
|
||||
<?php else: ?>
|
||||
<div class="files-header">
|
||||
<p class="files-count">
|
||||
<?php
|
||||
$start = (($fileData['currentPage'] - 1) * $fileData['perPage']) + 1;
|
||||
$end = min($start + count($files) - 1, $fileData['total']);
|
||||
echo "Affichage de $start-$end sur {$fileData['total']} fichiers";
|
||||
?>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="file-list">
|
||||
<?php foreach ($files as $file): ?>
|
||||
<div class="file-item">
|
||||
<div class="file-preview">
|
||||
<?php if ($file['preview_type'] === 'image'): ?>
|
||||
<img src="fichiers/<?php echo Cyla::escape($file['name']); ?>" alt="<?php echo Cyla::escape($file['name']); ?>">
|
||||
<img src="fichiers/<?php echo Cyla::escape($file['name']); ?>"
|
||||
alt="<?php echo Cyla::escape($file['name']); ?>">
|
||||
<?php else: ?>
|
||||
<div class="preview-placeholder">
|
||||
<?php echo strtoupper($file['extension']); ?>
|
||||
@ -265,78 +307,129 @@ ob_start(); ?>
|
||||
· <?php echo date('d/m/Y H:i', $file['uploaded']); ?>
|
||||
</p>
|
||||
<div class="file-actions">
|
||||
<a href="share.php?file=<?php echo urlencode($file['name']); ?>" class="btn btn-secondary" target="_blank">Voir</a>
|
||||
<button class="btn" onclick="copyShareLink('<?php echo SITE_URL; ?>share.php?file=<?php echo urlencode($file['name']); ?>')">Copier le lien</button>
|
||||
<a href="share.php?file=<?php echo urlencode($file['name']); ?>"
|
||||
class="btn btn-secondary"
|
||||
target="_blank">Voir</a>
|
||||
<button class="btn"
|
||||
onclick="copyShareLink('<?php echo SITE_URL; ?>share.php?file=<?php echo urlencode($file['name']); ?>')">
|
||||
Copier le lien
|
||||
</button>
|
||||
<button class="btn btn-danger"
|
||||
onclick="confirmDelete('<?php echo Cyla::escape($file['name']); ?>')">
|
||||
×
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Formulaire caché pour la suppression -->
|
||||
<form id="deleteForm-<?php echo Cyla::escape($file['name']); ?>"
|
||||
action="admin.php"
|
||||
method="POST"
|
||||
style="display: none;">
|
||||
<input type="hidden" name="action" value="delete_file">
|
||||
<input type="hidden" name="filename" value="<?php echo Cyla::escape($file['name']); ?>">
|
||||
<input type="hidden" name="csrf_token" value="<?php echo Cyla::generateCSRFToken(); ?>">
|
||||
</form>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
|
||||
<?php if ($fileData['totalPages'] > 1): ?>
|
||||
<div class="pagination">
|
||||
<?php if ($fileData['currentPage'] > 1): ?>
|
||||
<a href="?page=<?php echo $fileData['currentPage'] - 1; ?>"
|
||||
class="pagination-link">« Précédent</a>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php
|
||||
// Afficher max 5 pages autour de la page courante
|
||||
$start = max(1, $fileData['currentPage'] - 2);
|
||||
$end = min($fileData['totalPages'], $fileData['currentPage'] + 2);
|
||||
|
||||
if ($start > 1) {
|
||||
echo '<a href="?page=1" class="pagination-link">1</a>';
|
||||
if ($start > 2) echo '<span class="pagination-ellipsis">...</span>';
|
||||
}
|
||||
|
||||
for ($i = $start; $i <= $end; $i++) {
|
||||
$isActive = $i === $fileData['currentPage'];
|
||||
echo '<a href="?page=' . $i . '" class="pagination-link ' .
|
||||
($isActive ? 'pagination-active' : '') . '">' . $i . '</a>';
|
||||
}
|
||||
|
||||
if ($end < $fileData['totalPages']) {
|
||||
if ($end < $fileData['totalPages'] - 1) echo '<span class="pagination-ellipsis">...</span>';
|
||||
echo '<a href="?page=' . $fileData['totalPages'] .
|
||||
'" class="pagination-link">' . $fileData['totalPages'] . '</a>';
|
||||
}
|
||||
|
||||
if ($fileData['currentPage'] < $fileData['totalPages']): ?>
|
||||
<a href="?page=<?php echo $fileData['currentPage'] + 1; ?>"
|
||||
class="pagination-link">Suivant »</a>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
/* Styles spécifiques à la page d'administration */
|
||||
.admin-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-lg);
|
||||
}
|
||||
|
||||
.admin-container h2 {
|
||||
color: var(--color-primary);
|
||||
margin-bottom: var(--spacing-md);
|
||||
}
|
||||
|
||||
.help-text {
|
||||
font-size: 0.9rem;
|
||||
color: var(--color-text-muted);
|
||||
margin-top: var(--spacing-xs);
|
||||
}
|
||||
|
||||
.preview-placeholder {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: var(--color-bg);
|
||||
color: var(--color-text-muted);
|
||||
font-size: 2rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.file-name {
|
||||
font-weight: bold;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.file-meta {
|
||||
font-size: 0.9rem;
|
||||
color: var(--color-text-muted);
|
||||
margin: var(--spacing-xs) 0;
|
||||
}
|
||||
|
||||
.file-actions {
|
||||
display: flex;
|
||||
gap: var(--spacing-sm);
|
||||
margin-top: var(--spacing-sm);
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
// Fonction pour copier le lien de partage
|
||||
function copyShareLink(url) {
|
||||
navigator.clipboard.writeText(url).then(() => {
|
||||
alert('Lien copié dans le presse-papier');
|
||||
// Créer et afficher la notification
|
||||
const notification = document.createElement('div');
|
||||
notification.className = 'notification';
|
||||
notification.textContent = 'Lien copié dans le presse-papier !';
|
||||
document.body.appendChild(notification);
|
||||
|
||||
// Supprimer la notification après l'animation
|
||||
setTimeout(() => {
|
||||
notification.remove();
|
||||
}, 3000);
|
||||
}).catch(err => {
|
||||
console.error('Erreur lors de la copie :', err);
|
||||
alert('Erreur lors de la copie du lien');
|
||||
// En cas d'erreur, afficher une notification d'erreur
|
||||
const notification = document.createElement('div');
|
||||
notification.className = 'notification error';
|
||||
notification.textContent = 'Erreur lors de la copie';
|
||||
document.body.appendChild(notification);
|
||||
|
||||
setTimeout(() => {
|
||||
notification.remove();
|
||||
}, 3000);
|
||||
});
|
||||
}
|
||||
// Fonction de confirmation de suppression
|
||||
function confirmDelete(filename) {
|
||||
const overlay = document.createElement('div');
|
||||
overlay.className = 'confirmation-overlay';
|
||||
|
||||
const dialog = document.createElement('div');
|
||||
dialog.className = 'confirmation-dialog';
|
||||
dialog.innerHTML = `
|
||||
<h3>Confirmer la suppression</h3>
|
||||
<p>Voulez-vous vraiment supprimer le fichier "${filename}" ?</p>
|
||||
<div class="confirmation-actions">
|
||||
<button class="btn btn-secondary" onclick="closeConfirmDialog()">Annuler</button>
|
||||
<button class="btn" onclick="submitDelete('${filename}')">Supprimer</button>
|
||||
</div>
|
||||
`;
|
||||
|
||||
document.body.appendChild(overlay);
|
||||
document.body.appendChild(dialog);
|
||||
}
|
||||
|
||||
// Fonction pour fermer la modale
|
||||
function closeConfirmDialog() {
|
||||
document.querySelector('.confirmation-overlay')?.remove();
|
||||
document.querySelector('.confirmation-dialog')?.remove();
|
||||
}
|
||||
|
||||
// Fonction pour soumettre la suppression
|
||||
function submitDelete(filename) {
|
||||
document.getElementById('deleteForm-' + filename).submit();
|
||||
closeConfirmDialog();
|
||||
}
|
||||
</script>
|
||||
<div id="passwordModalRoot"></div>
|
||||
<script>
|
||||
window.CSRF_TOKEN = "<?php echo Cyla::generateCSRFToken(); ?>";
|
||||
</script>
|
||||
|
62
core.php
62
core.php
@ -131,12 +131,30 @@ class Cyla {
|
||||
}
|
||||
|
||||
/**
|
||||
* Liste les fichiers uploadés
|
||||
* @return array
|
||||
* Liste les fichiers uploadés avec pagination
|
||||
* @param int $page Numéro de la page (commence à 1)
|
||||
* @param int $perPage Nombre d'éléments par page
|
||||
* @return array ['files' => array, 'total' => int, 'totalPages' => int]
|
||||
*/
|
||||
public static function listFiles() {
|
||||
public static function listFiles($page = 1, $perPage = 20) {
|
||||
$files = [];
|
||||
foreach (glob(UPLOAD_DIR . '*') as $file) {
|
||||
$allFiles = glob(UPLOAD_DIR . '*');
|
||||
|
||||
// Trier les fichiers par date de modification (plus récent en premier)
|
||||
usort($allFiles, function($a, $b) {
|
||||
return filemtime($b) - filemtime($a);
|
||||
});
|
||||
|
||||
// Calculer la pagination
|
||||
$total = count($allFiles);
|
||||
$totalPages = ceil($total / $perPage);
|
||||
$page = max(1, min($page, $totalPages)); // Garantir que la page est dans les limites
|
||||
$offset = ($page - 1) * $perPage;
|
||||
|
||||
// Récupérer uniquement les fichiers de la page courante
|
||||
$pageFiles = array_slice($allFiles, $offset, $perPage);
|
||||
|
||||
foreach ($pageFiles as $file) {
|
||||
$info = pathinfo($file);
|
||||
$files[] = [
|
||||
'name' => basename($file),
|
||||
@ -146,7 +164,41 @@ class Cyla {
|
||||
'preview_type' => getPreviewType($info['extension'])
|
||||
];
|
||||
}
|
||||
return $files;
|
||||
|
||||
return [
|
||||
'files' => $files,
|
||||
'total' => $total,
|
||||
'totalPages' => $totalPages,
|
||||
'currentPage' => $page,
|
||||
'perPage' => $perPage
|
||||
];
|
||||
}
|
||||
/**
|
||||
* Supprime un fichier
|
||||
* @param string $filename Nom du fichier à supprimer
|
||||
* @return array ['success' => bool, 'error' => string|null]
|
||||
*/
|
||||
public static function deleteFile($filename) {
|
||||
$filepath = UPLOAD_DIR . $filename;
|
||||
|
||||
// Vérifier que le fichier existe et est dans le dossier d'upload
|
||||
if (!file_exists($filepath) || !is_file($filepath)) {
|
||||
return ['success' => false, 'error' => 'Fichier introuvable'];
|
||||
}
|
||||
|
||||
// Vérifier que le fichier est bien dans le dossier d'upload
|
||||
$realpath = realpath($filepath);
|
||||
$uploadDir = realpath(UPLOAD_DIR);
|
||||
if (strpos($realpath, $uploadDir) !== 0) {
|
||||
return ['success' => false, 'error' => 'Chemin de fichier non autorisé'];
|
||||
}
|
||||
|
||||
// Supprimer le fichier
|
||||
if (unlink($filepath)) {
|
||||
return ['success' => true];
|
||||
} else {
|
||||
return ['success' => false, 'error' => 'Erreur lors de la suppression du fichier'];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
204
css/style.css
204
css/style.css
@ -387,3 +387,207 @@ footer {
|
||||
transform: scale(1) translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
/* Styles spécifiques à la page d'administration */
|
||||
.admin-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-lg);
|
||||
}
|
||||
|
||||
.admin-container h2 {
|
||||
color: var(--color-primary);
|
||||
margin-bottom: var(--spacing-md);
|
||||
}
|
||||
|
||||
.help-text {
|
||||
font-size: 0.9rem;
|
||||
color: var(--color-text-muted);
|
||||
margin-top: var(--spacing-xs);
|
||||
}
|
||||
|
||||
.preview-placeholder {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: var(--color-bg);
|
||||
color: var(--color-text-muted);
|
||||
font-size: 2rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.file-name {
|
||||
font-weight: bold;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.file-meta {
|
||||
font-size: 0.9rem;
|
||||
color: var(--color-text-muted);
|
||||
margin: var(--spacing-xs) 0;
|
||||
}
|
||||
|
||||
.file-actions {
|
||||
display: flex;
|
||||
gap: var(--spacing-sm);
|
||||
margin-top: var(--spacing-sm);
|
||||
}
|
||||
|
||||
|
||||
/* Styles pour la pagination et l'affichage des fichiers */
|
||||
.files-header {
|
||||
margin-bottom: var(--spacing-md);
|
||||
color: var(--color-text-muted);
|
||||
}
|
||||
|
||||
.files-count {
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.pagination {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: var(--spacing-sm);
|
||||
margin-top: var(--spacing-lg);
|
||||
padding-top: var(--spacing-md);
|
||||
border-top: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
.pagination-link {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-width: 2rem;
|
||||
height: 2rem;
|
||||
padding: 0 var(--spacing-sm);
|
||||
color: var(--color-text);
|
||||
text-decoration: none;
|
||||
background-color: var(--color-bg);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--border-radius);
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.pagination-link:hover {
|
||||
background-color: var(--color-border);
|
||||
}
|
||||
|
||||
.pagination-active {
|
||||
background-color: var(--color-primary);
|
||||
border-color: var(--color-primary);
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
.pagination-ellipsis {
|
||||
color: var(--color-text-muted);
|
||||
padding: 0 var(--spacing-xs);
|
||||
}
|
||||
|
||||
/* Bouton de suppression */
|
||||
.btn-danger {
|
||||
background-color: var(--color-accent);
|
||||
width: 2.5rem;
|
||||
height: 2.5rem;
|
||||
padding: 0;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 1.5rem;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.btn-danger:hover {
|
||||
background-color: #a85651;
|
||||
}
|
||||
|
||||
/* Boîte de dialogue de confirmation */
|
||||
.confirmation-dialog {
|
||||
position: fixed;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background-color: var(--color-bg-alt);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--border-radius);
|
||||
padding: var(--spacing-lg);
|
||||
max-width: 400px;
|
||||
width: 90%;
|
||||
z-index: 1000;
|
||||
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.confirmation-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
z-index: 999;
|
||||
}
|
||||
|
||||
.confirmation-dialog h3 {
|
||||
color: var(--color-primary);
|
||||
margin-bottom: var(--spacing-md);
|
||||
}
|
||||
|
||||
.confirmation-dialog p {
|
||||
margin-bottom: var(--spacing-lg);
|
||||
}
|
||||
|
||||
.confirmation-actions {
|
||||
display: flex;
|
||||
gap: var(--spacing-sm);
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.confirmation-actions .btn {
|
||||
min-width: 100px;
|
||||
padding: var(--spacing-sm) var(--spacing-md);
|
||||
}
|
||||
|
||||
/* Responsive */
|
||||
@media (max-width: 768px) {
|
||||
.pagination {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.file-actions {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.btn-danger {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
/* Styles de notification */
|
||||
.notification {
|
||||
position: fixed;
|
||||
bottom: var(--spacing-lg);
|
||||
right: var(--spacing-lg);
|
||||
background-color: var(--color-success);
|
||||
color: white;
|
||||
padding: var(--spacing-md) var(--spacing-lg);
|
||||
border-radius: var(--border-radius);
|
||||
animation: slideIn 0.3s ease-out, fadeOut 0.3s ease-in 2.7s forwards;
|
||||
z-index: 1000;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.notification.error {
|
||||
background-color: var(--color-error);
|
||||
}
|
||||
|
||||
@keyframes slideIn {
|
||||
from { transform: translateY(100%); opacity: 0; }
|
||||
to { transform: translateY(0); opacity: 1; }
|
||||
}
|
||||
|
||||
@keyframes fadeOut {
|
||||
from { opacity: 1; }
|
||||
to { opacity: 0; }
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user