cyla/admin.php
2025-01-15 19:27:38 +01:00

441 lines
17 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
// debug :
ob_start();
ini_set('display_errors', 1);
error_reporting(E_ALL);
define('CYLA_CORE', true);
require_once 'core.php';
// Vérifions si des headers ont déjà été envoyés
if (headers_sent($filename, $linenum)) {
exit("Headers already sent in $filename on line $linenum");
}
// Vérifier si l'utilisateur est connecté
if (!Cyla::isLoggedIn()) {
header('Location: login.php');
exit;
}
$error = $_GET['error'] ?? null;
$success = $_GET['success'] ?? null;
// Traitement du changement de mot de passe
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'change_password') {
if (!isset($_POST['csrf_token']) || !Cyla::verifyCSRFToken($_POST['csrf_token'])) {
$error = 'Token de sécurité invalide';
} else {
$current_password = $_POST['current_password'] ?? '';
$new_password = $_POST['new_password'] ?? '';
$confirm_password = $_POST['confirm_password'] ?? '';
if (empty($current_password) || empty($new_password) || empty($confirm_password)) {
$error = 'Tous les champs sont requis';
} else if ($new_password !== $confirm_password) {
$error = 'Les nouveaux mots de passe ne correspondent pas';
} else {
global $ADMIN_USERS;
$username = $_SESSION['user'];
$user = $ADMIN_USERS[$username];
// Vérifier l'ancien mot de passe
if (hash(HASH_ALGO, $current_password . $user['salt']) !== $user['password']) {
$error = 'Mot de passe actuel incorrect';
} else {
// Mettre à jour le mot de passe
$ADMIN_USERS[$username]['password'] = hash(HASH_ALGO, $new_password . $user['salt']);
// Sauvegarder dans le fichier de configuration
$config_content = file_get_contents('config.php');
$config_content = preg_replace(
'/\'password\' => \'' . preg_quote($user['password'], '/') . '\'/',
'\'password\' => \'' . $ADMIN_USERS[$username]['password'] . '\'',
$config_content
);
file_put_contents('config.php', $config_content);
$success = 'Mot de passe modifié avec succès';
}
}
}
}
// Traitement de l'upload de fichier
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'upload') {
if (!isset($_POST['csrf_token']) || !Cyla::verifyCSRFToken($_POST['csrf_token'])) {
$error = 'Token de sécurité invalide';
} else if (!isset($_FILES['file'])) {
$error = 'Aucun fichier n\'a été envoyé';
} else {
$validation = Cyla::validateUpload($_FILES['file']);
if (!$validation['valid']) {
$error = $validation['error'];
} else {
$filename = Cyla::generateUniqueFilename($_FILES['file']['name']);
$destination = UPLOAD_DIR . $filename;
if (move_uploaded_file($_FILES['file']['tmp_name'], $destination)) {
$success = 'Fichier uploadé avec succès';
} else {
$error = 'Erreur lors de l\'upload du fichier';
}
}
}
}
// Liste des fichiers
$files = Cyla::listFiles();
// Contenu de la page
$pageTitle = 'Administration';
ob_start(); ?>
<div class="admin-container">
<div class="card">
<h2>Téléversement de fichier</h2>
<div class="upload-zone" id="uploadZone">
<input type="file" id="fileInput" multiple class="file-input" name="files[]">
<div class="upload-content">
<div class="upload-icon">
<svg width="50" height="50" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
<polyline points="17 8 12 3 7 8" />
<line x1="12" y1="3" x2="12" y2="15" />
</svg>
</div>
<p class="upload-text">
Glissez-déposez vos fichiers ici<br>
<span class="upload-text-sub">ou cliquez pour sélectionner</span>
</p>
<p class="upload-limit">
Taille maximale : 100 Mo<br>
Extensions : <?php echo implode(', ', ALLOWED_EXTENSIONS); ?>
</p>
</div>
</div>
<div id="fileList" class="file-list"></div>
</div>
<script>
const uploadZone = document.getElementById('uploadZone');
const fileInput = document.getElementById('fileInput');
const fileList = document.getElementById('fileList');
const maxFileSize = <?php echo MAX_FILE_SIZE; ?>;
const allowedExtensions = <?php echo json_encode(ALLOWED_EXTENSIONS); ?>;
// Gestion du drag & drop
['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
uploadZone.addEventListener(eventName, preventDefaults, false);
document.body.addEventListener(eventName, preventDefaults, false);
});
function preventDefaults(e) {
e.preventDefault();
e.stopPropagation();
}
['dragenter', 'dragover'].forEach(eventName => {
uploadZone.addEventListener(eventName, highlight, false);
});
['dragleave', 'drop'].forEach(eventName => {
uploadZone.addEventListener(eventName, unhighlight, false);
});
function highlight(e) {
uploadZone.classList.add('upload-zone-active');
}
function unhighlight(e) {
uploadZone.classList.remove('upload-zone-active');
}
// Gestion du drop
uploadZone.addEventListener('drop', handleDrop, false);
fileInput.addEventListener('change', handleFiles, false);
uploadZone.addEventListener('click', () => fileInput.click());
function handleDrop(e) {
const dt = e.dataTransfer;
const files = dt.files;
handleFiles({ target: { files: files } });
}
function isValidFile(file) {
const ext = file.name.split('.').pop().toLowerCase();
if (!allowedExtensions.includes(ext)) {
return { valid: false, error: `Extension .${ext} non autorisée` };
}
if (file.size > maxFileSize) {
return { valid: false, error: 'Fichier trop volumineux (max 100 Mo)' };
}
return { valid: true };
}
function handleFiles(e) {
const files = Array.from(e.target.files);
// Vider la liste précédente
fileList.innerHTML = '';
files.forEach(file => {
const validation = isValidFile(file);
const fileElement = createFileElement(file, validation);
fileList.appendChild(fileElement);
if (validation.valid) {
uploadFile(file, fileElement);
}
});
}
function createFileElement(file, validation) {
const div = document.createElement('div');
div.className = 'file-item ' + (validation.valid ? '' : 'file-item-error');
div.innerHTML = `
<div class="file-item-content">
<div class="file-item-name">${file.name}</div>
<div class="file-item-size">${(file.size / 1024 / 1024).toFixed(2)} Mo</div>
${validation.valid ?
'<div class="file-item-progress"><div class="progress-bar"></div></div>' :
`<div class="file-item-error-message">${validation.error}</div>`
}
</div>
`;
return div;
}
async function uploadFile(file, element) {
const formData = new FormData();
formData.append('file', file);
formData.append('csrf_token', '<?php echo Cyla::generateCSRFToken(); ?>');
try {
const response = await fetch('api.php', {
method: 'POST',
body: formData
});
if (!response.ok) {
throw new Error('Erreur lors de l\'upload');
}
const result = await response.json();
if (result.success) {
element.classList.add('file-item-success');
} else {
element.classList.add('file-item-error');
}
} catch (error) {
element.classList.add('file-item-error');
console.error('Error:', error);
}
}
</script>
<?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="<?php echo $file['path'] . Cyla::escape($file['name']); ?>"
alt="<?php echo Cyla::escape($file['name']); ?>">
<?php else: ?>
<div class="preview-placeholder">
<?php echo strtoupper($file['extension']); ?>
</div>
<?php endif; ?>
</div>
<div class="file-info">
<p class="file-name"><?php echo Cyla::escape($file['name']); ?></p>
<p class="file-meta">
<?php echo Cyla::escape(round($file['size'] / 1024, 2)); ?> Ko
· <?php echo date('d/m/Y H:i', $file['uploaded']); ?>
· <?php echo $file['path']; ?>
</p>
<div class="file-actions">
<a href="share.php?file=<?php echo urlencode($file['name']); ?>&path=<?php echo urlencode($file['path']); ?>"
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']); ?>&path=<?php echo urlencode($file['path']); ?>')">
Copier le lien
</button>
<button class="btn btn-danger"
onclick="confirmDelete('<?php echo Cyla::escape($file['name']); ?>', '<?php echo Cyla::escape($file['path']); ?>')">
×
</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="path" value="<?php echo Cyla::escape($file['path']); ?>">
<input type="hidden" name="csrf_token" value="<?php echo Cyla::generateCSRFToken(); ?>">
</form>
<?php endforeach; ?>
<?php if ($fileData['totalPages'] > 1): ?>
<div class="pagination">
<?php if ($fileData['currentPage'] > 1): ?>
<a href="?page=<?php echo $fileData['currentPage'] - 1; ?>"
class="pagination-link">&laquo;</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">&raquo;</a>
<?php endif; ?>
</div>
<?php endif; ?>
<?php endif; ?>
</div>
<script>
function copyShareLink(url) {
navigator.clipboard.writeText(url).then(() => {
// 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);
// 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>
<script>
window.CSRF_TOKEN = "<?php echo Cyla::generateCSRFToken(); ?>";
</script>
<script src="js/password-modal.js"></script>
<?php
$content = ob_get_clean();
require 'layout.php';
ob_end_flush();