base de la v3
This commit is contained in:
parent
41c79b7ec3
commit
b3c9c43142
60
.htaccess
Normal file
60
.htaccess
Normal file
@ -0,0 +1,60 @@
|
||||
# Empêcher l'accès direct aux fichiers PHP
|
||||
<FilesMatch "\.php$">
|
||||
<IfModule !mod_authz_core.c>
|
||||
Order Allow,Deny
|
||||
Deny from all
|
||||
</IfModule>
|
||||
<IfModule mod_authz_core.c>
|
||||
Require all denied
|
||||
</IfModule>
|
||||
</FilesMatch>
|
||||
<Files "api.php">
|
||||
<IfModule !mod_authz_core.c>
|
||||
Order Allow,Deny
|
||||
Allow from all
|
||||
</IfModule>
|
||||
<IfModule mod_authz_core.c>
|
||||
Require all granted
|
||||
</IfModule>
|
||||
</Files>
|
||||
# Autoriser l'accès aux points d'entrée spécifiques
|
||||
<Files ~ "^(index|admin|login|logout|share)\.php$">
|
||||
<IfModule !mod_authz_core.c>
|
||||
Order Allow,Deny
|
||||
Allow from all
|
||||
</IfModule>
|
||||
<IfModule mod_authz_core.c>
|
||||
Require all granted
|
||||
</IfModule>
|
||||
</Files>
|
||||
|
||||
# Définir les types MIME corrects
|
||||
AddType text/css .css
|
||||
AddType application/javascript .js
|
||||
AddType image/x-icon .ico
|
||||
AddType image/svg+xml .svg
|
||||
AddType application/x-font-woff .woff
|
||||
AddType application/x-font-woff2 .woff2
|
||||
|
||||
# Activer la compression
|
||||
<IfModule mod_deflate.c>
|
||||
AddOutputFilterByType DEFLATE text/plain
|
||||
AddOutputFilterByType DEFLATE text/html
|
||||
AddOutputFilterByType DEFLATE text/css
|
||||
AddOutputFilterByType DEFLATE application/javascript
|
||||
AddOutputFilterByType DEFLATE application/x-javascript
|
||||
AddOutputFilterByType DEFLATE text/javascript
|
||||
</IfModule>
|
||||
|
||||
# Cache headers
|
||||
<IfModule mod_expires.c>
|
||||
ExpiresActive On
|
||||
ExpiresDefault "access plus 1 month"
|
||||
|
||||
ExpiresByType text/css "access plus 1 year"
|
||||
ExpiresByType application/javascript "access plus 1 year"
|
||||
ExpiresByType image/x-icon "access plus 1 year"
|
||||
</IfModule>
|
||||
|
||||
# Protection des dossiers
|
||||
Options -Indexes
|
368
admin.php
Normal file
368
admin.php
Normal file
@ -0,0 +1,368 @@
|
||||
<?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>
|
||||
|
||||
<div class="card">
|
||||
<h2>Changer le mot de passe</h2>
|
||||
<form method="POST" class="password-form">
|
||||
<input type="hidden" name="csrf_token" value="<?php echo Cyla::generateCSRFToken(); ?>">
|
||||
<input type="hidden" name="action" value="change_password">
|
||||
|
||||
<div class="form-group">
|
||||
<label for="current_password">Mot de passe actuel</label>
|
||||
<input type="password" id="current_password" name="current_password" required>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="new_password">Nouveau mot de passe</label>
|
||||
<input type="password" id="new_password" name="new_password" required>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="confirm_password">Confirmer le nouveau mot de passe</label>
|
||||
<input type="password" id="confirm_password" name="confirm_password" required>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn">Changer le mot de passe</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<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="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']); ?>">
|
||||
<?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']); ?>
|
||||
</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>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</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');
|
||||
}).catch(err => {
|
||||
console.error('Erreur lors de la copie :', err);
|
||||
alert('Erreur lors de la copie du lien');
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<?php
|
||||
$content = ob_get_clean();
|
||||
require 'layout.php';
|
||||
ob_end_flush();
|
50
api.php
Normal file
50
api.php
Normal file
@ -0,0 +1,50 @@
|
||||
<?php
|
||||
define('CYLA_CORE', true);
|
||||
require_once 'core.php';
|
||||
|
||||
// Vérifier si l'utilisateur est connecté
|
||||
if (!Cyla::isLoggedIn()) {
|
||||
http_response_code(401);
|
||||
echo json_encode(['error' => 'Non autorisé']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Vérifier le token CSRF
|
||||
if (!isset($_POST['csrf_token']) || !Cyla::verifyCSRFToken($_POST['csrf_token'])) {
|
||||
http_response_code(403);
|
||||
echo json_encode(['error' => 'Token CSRF invalide']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Gérer l'upload de fichier
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['file'])) {
|
||||
$validation = Cyla::validateUpload($_FILES['file']);
|
||||
|
||||
if (!$validation['valid']) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['error' => $validation['error']]);
|
||||
exit;
|
||||
}
|
||||
|
||||
$filename = Cyla::generateUniqueFilename($_FILES['file']['name']);
|
||||
$destination = UPLOAD_DIR . $filename;
|
||||
|
||||
if (move_uploaded_file($_FILES['file']['tmp_name'], $destination)) {
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'file' => [
|
||||
'name' => $filename,
|
||||
'size' => filesize($destination),
|
||||
'url' => 'share.php?file=' . urlencode($filename)
|
||||
]
|
||||
]);
|
||||
} else {
|
||||
http_response_code(500);
|
||||
echo json_encode(['error' => 'Erreur lors de l\'upload du fichier']);
|
||||
}
|
||||
exit;
|
||||
}
|
||||
|
||||
// Méthode non autorisée
|
||||
http_response_code(405);
|
||||
echo json_encode(['error' => 'Méthode non autorisée']);
|
76
config.php
Normal file
76
config.php
Normal file
@ -0,0 +1,76 @@
|
||||
<?php
|
||||
// Prevent direct access to this file
|
||||
if (!defined('CYLA_CORE')) {
|
||||
header('HTTP/1.0 403 Forbidden');
|
||||
exit('Accès direct interdit');
|
||||
}
|
||||
|
||||
// Site configuration
|
||||
define('SITE_NAME', 'Cyla');
|
||||
define('SITE_VERSION', '3.0.0');
|
||||
define('SITE_URL', 'https://concepts.esenjin.xyz/cyla/');
|
||||
|
||||
// Files configuration
|
||||
define('UPLOAD_DIR', __DIR__ . '/fichiers/');
|
||||
define('MAX_FILE_SIZE', 100 * 1024 * 1024); // 100 Mo en octets
|
||||
define('ALLOWED_EXTENSIONS', [
|
||||
'png', 'jpg', 'jpeg', 'gif', 'webm', 'mp4', 'wmv',
|
||||
'mp3', 'flac', 'ogg', 'zip', 'css', 'pdf',
|
||||
'zip', 'rar', 'm3u', 'm3u8', 'txt'
|
||||
]);
|
||||
|
||||
// Preview configuration
|
||||
define('PREVIEW_IMAGES', ['png', 'jpg', 'jpeg', 'gif']);
|
||||
define('PREVIEW_VIDEOS', ['webm', 'mp4', 'wmv']);
|
||||
define('PREVIEW_AUDIOS', ['mp3', 'flac', 'ogg']);
|
||||
define('PREVIEW_TEXTS', ['txt', 'css', 'm3u', 'm3u8']);
|
||||
|
||||
// Security configuration
|
||||
define('HASH_ALGO', 'sha256'); // Algorithme de hachage pour les mots de passe
|
||||
define('SALT_LENGTH', 32); // Longueur du sel pour le hachage
|
||||
|
||||
// Admin users (format: 'username' => ['password' => 'hashed_password', 'salt' => 'random_salt'])
|
||||
$ADMIN_USERS = [
|
||||
'admin' => [
|
||||
'password' => 'a94637ad7685d8a3e64c97eddd7751a0ff55434a607361b7304edf41b39ab7f8', // Default: 'password'
|
||||
'salt' => 'defaultsalt123'
|
||||
]
|
||||
];
|
||||
|
||||
// Session configuration
|
||||
define('SESSION_LIFETIME', 3600); // Durée de vie de la session en secondes (1 heure)
|
||||
define('SESSION_NAME', 'CYLA_SESSION');
|
||||
|
||||
// Error reporting
|
||||
define('DEBUG_MODE', false); // À mettre à false en production
|
||||
if (DEBUG_MODE) {
|
||||
error_reporting(E_ALL);
|
||||
ini_set('display_errors', 1);
|
||||
} else {
|
||||
error_reporting(0);
|
||||
ini_set('display_errors', 0);
|
||||
}
|
||||
|
||||
// Fonction pour vérifier si une extension est autorisée
|
||||
function isAllowedExtension($extension) {
|
||||
return in_array(strtolower($extension), ALLOWED_EXTENSIONS);
|
||||
}
|
||||
|
||||
// Fonction pour vérifier si un fichier peut avoir un aperçu
|
||||
function canPreview($extension) {
|
||||
$extension = strtolower($extension);
|
||||
return in_array($extension, PREVIEW_IMAGES) ||
|
||||
in_array($extension, PREVIEW_VIDEOS) ||
|
||||
in_array($extension, PREVIEW_AUDIOS) ||
|
||||
in_array($extension, PREVIEW_TEXTS);
|
||||
}
|
||||
|
||||
// Fonction pour obtenir le type d'aperçu
|
||||
function getPreviewType($extension) {
|
||||
$extension = strtolower($extension);
|
||||
if (in_array($extension, PREVIEW_IMAGES)) return 'image';
|
||||
if (in_array($extension, PREVIEW_VIDEOS)) return 'video';
|
||||
if (in_array($extension, PREVIEW_AUDIOS)) return 'audio';
|
||||
if (in_array($extension, PREVIEW_TEXTS)) return 'text';
|
||||
return 'none';
|
||||
}
|
156
core.php
Normal file
156
core.php
Normal file
@ -0,0 +1,156 @@
|
||||
<?php
|
||||
// Charger la configuration avant tout
|
||||
require_once __DIR__ . '/config.php';
|
||||
|
||||
// Démarrer la session
|
||||
session_name(SESSION_NAME);
|
||||
session_start();
|
||||
|
||||
/**
|
||||
* Classe principale de Cyla
|
||||
*/
|
||||
class Cyla {
|
||||
/**
|
||||
* Vérifie si un utilisateur est connecté
|
||||
* @return bool
|
||||
*/
|
||||
public static function isLoggedIn() {
|
||||
return isset($_SESSION['user']) && isset($_SESSION['last_activity']) &&
|
||||
(time() - $_SESSION['last_activity']) < SESSION_LIFETIME;
|
||||
}
|
||||
|
||||
/**
|
||||
* Met à jour le timestamp de dernière activité
|
||||
*/
|
||||
public static function updateActivity() {
|
||||
$_SESSION['last_activity'] = time();
|
||||
}
|
||||
|
||||
/**
|
||||
* Authentifie un utilisateur
|
||||
* @param string $username Nom d'utilisateur
|
||||
* @param string $password Mot de passe
|
||||
* @return bool
|
||||
*/
|
||||
public static function authenticate($username, $password) {
|
||||
global $ADMIN_USERS;
|
||||
|
||||
if (!isset($ADMIN_USERS[$username])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$user = $ADMIN_USERS[$username];
|
||||
$hashed = hash(HASH_ALGO, $password . $user['salt']);
|
||||
|
||||
if ($hashed === $user['password']) {
|
||||
$_SESSION['user'] = $username;
|
||||
self::updateActivity();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Déconnecte l'utilisateur
|
||||
*/
|
||||
public static function logout() {
|
||||
session_destroy();
|
||||
}
|
||||
|
||||
/**
|
||||
* Génère un nom de fichier unique
|
||||
* @param string $originalName Nom original du fichier
|
||||
* @return string
|
||||
*/
|
||||
public static function generateUniqueFilename($originalName) {
|
||||
$extension = pathinfo($originalName, PATHINFO_EXTENSION);
|
||||
return uniqid() . '_' . time() . '.' . $extension;
|
||||
}
|
||||
|
||||
/**
|
||||
* Vérifie si un fichier peut être uploadé
|
||||
* @param array $file Informations du fichier ($_FILES)
|
||||
* @return array ['valid' => bool, 'error' => string|null]
|
||||
*/
|
||||
public static function validateUpload($file) {
|
||||
$result = ['valid' => true, 'error' => null];
|
||||
|
||||
// Vérifier les erreurs d'upload PHP
|
||||
if ($file['error'] !== UPLOAD_ERR_OK) {
|
||||
$result['valid'] = false;
|
||||
$result['error'] = 'Erreur lors de l\'upload';
|
||||
return $result;
|
||||
}
|
||||
|
||||
// Vérifier la taille
|
||||
if ($file['size'] > MAX_FILE_SIZE) {
|
||||
$result['valid'] = false;
|
||||
$result['error'] = 'Le fichier dépasse la taille maximale autorisée';
|
||||
return $result;
|
||||
}
|
||||
|
||||
// Vérifier l'extension
|
||||
$extension = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
|
||||
if (!isAllowedExtension($extension)) {
|
||||
$result['valid'] = false;
|
||||
$result['error'] = 'Extension de fichier non autorisée';
|
||||
return $result;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sécurise les sorties HTML
|
||||
* @param string $text Texte à sécuriser
|
||||
* @return string
|
||||
*/
|
||||
public static function escape($text) {
|
||||
return htmlspecialchars($text, ENT_QUOTES, 'UTF-8');
|
||||
}
|
||||
|
||||
/**
|
||||
* Génère un token CSRF
|
||||
* @return string
|
||||
*/
|
||||
public static function generateCSRFToken() {
|
||||
if (!isset($_SESSION['csrf_token'])) {
|
||||
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
|
||||
}
|
||||
return $_SESSION['csrf_token'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Vérifie un token CSRF
|
||||
* @param string $token Token à vérifier
|
||||
* @return bool
|
||||
*/
|
||||
public static function verifyCSRFToken($token) {
|
||||
return isset($_SESSION['csrf_token']) && hash_equals($_SESSION['csrf_token'], $token);
|
||||
}
|
||||
|
||||
/**
|
||||
* Liste les fichiers uploadés
|
||||
* @return array
|
||||
*/
|
||||
public static function listFiles() {
|
||||
$files = [];
|
||||
foreach (glob(UPLOAD_DIR . '*') as $file) {
|
||||
$info = pathinfo($file);
|
||||
$files[] = [
|
||||
'name' => basename($file),
|
||||
'size' => filesize($file),
|
||||
'extension' => strtolower($info['extension']),
|
||||
'uploaded' => filemtime($file),
|
||||
'preview_type' => getPreviewType($info['extension'])
|
||||
];
|
||||
}
|
||||
return $files;
|
||||
}
|
||||
}
|
||||
|
||||
// Vérifier et mettre à jour l'activité si l'utilisateur est connecté
|
||||
if (Cyla::isLoggedIn()) {
|
||||
Cyla::updateActivity();
|
||||
}
|
345
css/style.css
Normal file
345
css/style.css
Normal file
@ -0,0 +1,345 @@
|
||||
/* Variables */
|
||||
:root {
|
||||
/* Couleurs principales */
|
||||
--color-bg: #1a1716;
|
||||
--color-bg-alt: #231f1d;
|
||||
--color-text: #e6d5c5;
|
||||
--color-text-muted: #a18d7a;
|
||||
--color-primary: #d4846a;
|
||||
--color-primary-hover: #e6977c;
|
||||
--color-accent: #8b4b45;
|
||||
--color-border: #382f2a;
|
||||
|
||||
/* Alertes */
|
||||
--color-error: #ff6b6b;
|
||||
--color-success: #51cf66;
|
||||
|
||||
/* Espacements */
|
||||
--spacing-xs: 0.25rem;
|
||||
--spacing-sm: 0.5rem;
|
||||
--spacing-md: 1rem;
|
||||
--spacing-lg: 2rem;
|
||||
|
||||
/* Bordures */
|
||||
--border-radius: 4px;
|
||||
}
|
||||
|
||||
/* Reset et base */
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
||||
background-color: var(--color-bg);
|
||||
color: var(--color-text);
|
||||
line-height: 1.6;
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* Navigation */
|
||||
header {
|
||||
background-color: var(--color-bg-alt);
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
padding: var(--spacing-md) var(--spacing-lg);
|
||||
}
|
||||
|
||||
nav {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.nav-brand a {
|
||||
color: var(--color-primary);
|
||||
font-size: 1.5rem;
|
||||
font-weight: bold;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.nav-menu {
|
||||
display: flex;
|
||||
gap: var(--spacing-md);
|
||||
}
|
||||
|
||||
.nav-link {
|
||||
color: var(--color-text);
|
||||
text-decoration: none;
|
||||
padding: var(--spacing-sm) var(--spacing-md);
|
||||
border-radius: var(--border-radius);
|
||||
transition: background-color 0.3s ease;
|
||||
}
|
||||
|
||||
.nav-link:hover {
|
||||
background-color: var(--color-border);
|
||||
}
|
||||
|
||||
/* Main content */
|
||||
main {
|
||||
flex: 1;
|
||||
padding: var(--spacing-lg);
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* Cards */
|
||||
.card {
|
||||
background-color: var(--color-bg-alt);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--border-radius);
|
||||
padding: var(--spacing-lg);
|
||||
margin-bottom: var(--spacing-lg);
|
||||
}
|
||||
|
||||
/* Forms */
|
||||
.form-group {
|
||||
margin-bottom: var(--spacing-md);
|
||||
}
|
||||
|
||||
label {
|
||||
display: block;
|
||||
margin-bottom: var(--spacing-xs);
|
||||
color: var(--color-text-muted);
|
||||
}
|
||||
|
||||
input[type="text"],
|
||||
input[type="password"],
|
||||
textarea {
|
||||
width: 100%;
|
||||
padding: var(--spacing-sm);
|
||||
background-color: var(--color-bg);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--border-radius);
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
input[type="text"]:focus,
|
||||
input[type="password"]:focus,
|
||||
textarea:focus {
|
||||
outline: none;
|
||||
border-color: var(--color-primary);
|
||||
}
|
||||
|
||||
/* Buttons */
|
||||
.btn {
|
||||
display: inline-block;
|
||||
padding: var(--spacing-sm) var(--spacing-md);
|
||||
background-color: var(--color-primary);
|
||||
color: var(--color-text);
|
||||
border: none;
|
||||
border-radius: var(--border-radius);
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
transition: background-color 0.3s ease;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
background-color: var(--color-primary-hover);
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background-color: var(--color-border);
|
||||
}
|
||||
|
||||
.btn-secondary:hover {
|
||||
background-color: var(--color-text-muted);
|
||||
}
|
||||
|
||||
/* Alerts */
|
||||
.alert {
|
||||
padding: var(--spacing-md);
|
||||
border-radius: var(--border-radius);
|
||||
margin-bottom: var(--spacing-md);
|
||||
}
|
||||
|
||||
.alert-error {
|
||||
background-color: var(--color-error);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.alert-success {
|
||||
background-color: var(--color-success);
|
||||
color: white;
|
||||
}
|
||||
|
||||
/* File list */
|
||||
.file-list {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
|
||||
gap: var(--spacing-md);
|
||||
}
|
||||
|
||||
.file-item {
|
||||
background-color: var(--color-bg-alt);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--border-radius);
|
||||
padding: var(--spacing-md);
|
||||
}
|
||||
|
||||
.file-preview {
|
||||
aspect-ratio: 16/9;
|
||||
background-color: var(--color-bg);
|
||||
border-radius: var(--border-radius);
|
||||
margin-bottom: var(--spacing-sm);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.file-preview img,
|
||||
.file-preview video {
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.file-info {
|
||||
color: var(--color-text-muted);
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
/* Footer */
|
||||
footer {
|
||||
background-color: var(--color-bg-alt);
|
||||
border-top: 1px solid var(--color-border);
|
||||
padding: var(--spacing-lg);
|
||||
text-align: center;
|
||||
color: var(--color-text-muted);
|
||||
}
|
||||
|
||||
/* Zone d'upload */
|
||||
.upload-zone {
|
||||
position: relative;
|
||||
border: 2px dashed var(--color-border);
|
||||
border-radius: var(--border-radius);
|
||||
padding: 2rem;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.upload-zone:hover {
|
||||
border-color: var(--color-primary);
|
||||
}
|
||||
|
||||
.upload-zone-active {
|
||||
border-color: var(--color-primary);
|
||||
background-color: rgba(212, 132, 106, 0.1);
|
||||
}
|
||||
|
||||
.file-input {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
top: 0;
|
||||
left: 0;
|
||||
opacity: 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.upload-content {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.upload-icon {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.upload-icon svg {
|
||||
stroke: var(--color-text-muted);
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.upload-text {
|
||||
margin-bottom: 0.5rem;
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
.upload-text-sub {
|
||||
font-size: 0.9em;
|
||||
color: var(--color-text-muted);
|
||||
}
|
||||
|
||||
.upload-limit {
|
||||
font-size: 0.8em;
|
||||
color: var(--color-text-muted);
|
||||
}
|
||||
|
||||
/* Liste des fichiers */
|
||||
.file-list {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.file-item {
|
||||
background: var(--color-bg-alt);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--border-radius);
|
||||
padding: 1rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.file-item-content {
|
||||
display: grid;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.file-item-name {
|
||||
font-weight: 500;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.file-item-size {
|
||||
color: var(--color-text-muted);
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.file-item-progress {
|
||||
height: 4px;
|
||||
background: var(--color-border);
|
||||
border-radius: 2px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
height: 100%;
|
||||
background: var(--color-primary);
|
||||
width: 0;
|
||||
transition: width 0.3s ease;
|
||||
}
|
||||
|
||||
.file-item-error {
|
||||
border-color: var(--color-error);
|
||||
}
|
||||
|
||||
.file-item-error .file-item-error-message {
|
||||
color: var(--color-error);
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.file-item-success {
|
||||
border-color: var(--color-success);
|
||||
}
|
||||
|
||||
/* Responsive */
|
||||
@media (max-width: 768px) {
|
||||
.nav-menu {
|
||||
display: none;
|
||||
}
|
||||
|
||||
main {
|
||||
padding: var(--spacing-md);
|
||||
}
|
||||
|
||||
.file-list {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
32
cyla2api.php
32
cyla2api.php
@ -1,32 +0,0 @@
|
||||
<?php
|
||||
|
||||
// EDIT THIS: your auth parameters
|
||||
$signature = 'a2eefde33d';
|
||||
|
||||
// EDIT THIS: the query parameters
|
||||
$url = '<?php echo $filelocation;?>'; // URL to shrink
|
||||
$format = 'simple'; // output format: 'json', 'xml' or 'simple'
|
||||
|
||||
// EDIT THIS: the URL of the API file
|
||||
$api_url = 'http://ersatz.xyz/yourls-api.php';
|
||||
|
||||
// Init the CURL session
|
||||
$ch = curl_init();
|
||||
curl_setopt( $ch, CURLOPT_URL, $api_url );
|
||||
curl_setopt( $ch, CURLOPT_HEADER, 0 ); // No header in the result
|
||||
curl_setopt( $ch, CURLOPT_RETURNTRANSFER, true ); // Return, do not echo result
|
||||
curl_setopt( $ch, CURLOPT_POST, 1 ); // This is a POST request
|
||||
curl_setopt( $ch, CURLOPT_POSTFIELDS, array( // Data to POST
|
||||
'url' => $url,
|
||||
'format' => $format,
|
||||
'action' => 'shorturl',
|
||||
'signature' => $signature,
|
||||
) );
|
||||
|
||||
// Fetch and return content
|
||||
$data = curl_exec($ch);
|
||||
curl_close($ch);
|
||||
|
||||
// Do something with the result. Here, we just echo it.
|
||||
echo $data;
|
||||
|
@ -1 +0,0 @@
|
||||
Les fichiers seront stockés dans ce dossier.
|
311
gallery.php
311
gallery.php
@ -1,311 +0,0 @@
|
||||
<?php
|
||||
// Configuration
|
||||
define('PASSWORD', 'votre_mot_de_passe');
|
||||
define('FILES_DIR', './file');
|
||||
define('FILES_PER_PAGE', 50);
|
||||
|
||||
session_start();
|
||||
$authenticated = isset($_SESSION['authenticated']) && $_SESSION['authenticated'];
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
if (isset($_POST['password']) && $_POST['password'] === PASSWORD) {
|
||||
$_SESSION['authenticated'] = true;
|
||||
$authenticated = true;
|
||||
} elseif (isset($_POST['page'])) {
|
||||
// API pour le chargement progressif
|
||||
$page = intval($_POST['page']);
|
||||
$sortBy = $_POST['sortBy'] ?? 'date';
|
||||
$sortDesc = $_POST['sortDesc'] ?? 'true';
|
||||
|
||||
$files = getFiles($page, $sortBy, $sortDesc === 'true');
|
||||
header('Content-Type: application/json');
|
||||
echo json_encode($files);
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
function getFiles($page = 0, $sortBy = 'date', $sortDesc = true) {
|
||||
$allFiles = array_diff(scandir(FILES_DIR), ['.', '..']);
|
||||
$files = [];
|
||||
|
||||
foreach ($allFiles as $file) {
|
||||
$path = FILES_DIR . '/' . $file;
|
||||
if (is_file($path)) {
|
||||
$files[] = [
|
||||
'name' => $file,
|
||||
'url' => 'file/' . rawurlencode($file),
|
||||
'shareUrl' => 'share/' . rawurlencode($file),
|
||||
'date' => filemtime($path),
|
||||
'type' => strtolower(pathinfo($file, PATHINFO_EXTENSION))
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// Tri
|
||||
usort($files, function($a, $b) use ($sortBy, $sortDesc) {
|
||||
$result = $sortBy === 'date'
|
||||
? $b['date'] - $a['date']
|
||||
: strcasecmp($a['name'], $b['name']);
|
||||
return $sortDesc ? $result : -$result;
|
||||
});
|
||||
|
||||
// Pagination
|
||||
$offset = $page * FILES_PER_PAGE;
|
||||
return array_slice($files, $offset, FILES_PER_PAGE);
|
||||
}
|
||||
|
||||
if (!$authenticated) {
|
||||
// Afficher le formulaire de connexion
|
||||
include 'header.php';
|
||||
?>
|
||||
<form class="login-form" method="POST">
|
||||
<input type="password" name="password" placeholder="Mot de passe">
|
||||
<button type="submit">Accéder</button>
|
||||
</form>
|
||||
<?php
|
||||
include 'footer.php';
|
||||
exit;
|
||||
}
|
||||
|
||||
$initialFiles = getFiles();
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Galerie de fichiers</title>
|
||||
<style>
|
||||
:root {
|
||||
--bg-dark: #1a1a1a;
|
||||
--bg-card: #2d2d2d;
|
||||
--color-beige: #d4c4a8;
|
||||
--color-beige-dark: #8b7355;
|
||||
}
|
||||
|
||||
body {
|
||||
background: var(--bg-dark);
|
||||
color: var(--color-beige);
|
||||
font-family: Arial, sans-serif;
|
||||
margin: 0;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.controls {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
background: var(--bg-dark);
|
||||
padding: 10px 0;
|
||||
margin-bottom: 20px;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.file-card {
|
||||
background: var(--bg-card);
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.preview {
|
||||
height: 150px;
|
||||
position: relative;
|
||||
background: #000;
|
||||
}
|
||||
|
||||
.preview img, .preview video {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.preview audio {
|
||||
position: absolute;
|
||||
bottom: 10px;
|
||||
left: 5%;
|
||||
width: 90%;
|
||||
}
|
||||
|
||||
.info {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.date {
|
||||
font-size: 0.8em;
|
||||
color: var(--color-beige-dark);
|
||||
}
|
||||
|
||||
#loading {
|
||||
text-align: center;
|
||||
padding: 20px;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.file-icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
padding: 10px;
|
||||
text-align: center;
|
||||
word-break: break-word;
|
||||
font-size: 1.2em;
|
||||
}
|
||||
|
||||
.info .name {
|
||||
color: var(--color-beige);
|
||||
text-decoration: none;
|
||||
word-break: break-all;
|
||||
display: block;
|
||||
padding: 5px 0;
|
||||
}
|
||||
|
||||
.info .name:hover {
|
||||
color: white;
|
||||
text-decoration: underline;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="controls">
|
||||
<button onclick="changeSortBy('date')" id="sortDate">
|
||||
Date <?= $sortBy === 'date' ? ($sortDesc ? '↓' : '↑') : '' ?>
|
||||
</button>
|
||||
<button onclick="changeSortBy('name')" id="sortName">
|
||||
Nom <?= $sortBy === 'name' ? ($sortDesc ? '↓' : '↑') : '' ?>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="grid" id="fileGrid">
|
||||
</div>
|
||||
<div id="loading">Chargement...</div>
|
||||
|
||||
<script>
|
||||
let page = 0;
|
||||
let loading = false;
|
||||
let sortBy = 'date';
|
||||
let sortDesc = true;
|
||||
let noMoreFiles = false;
|
||||
|
||||
function createFileCard(file) {
|
||||
const card = document.createElement('div');
|
||||
card.className = 'file-card';
|
||||
|
||||
const preview = document.createElement('div');
|
||||
preview.className = 'preview';
|
||||
|
||||
const ext = file.type.toLowerCase();
|
||||
let previewContent = '';
|
||||
|
||||
if (['jpg', 'jpeg', 'png', 'gif'].includes(ext)) {
|
||||
previewContent = `<img src="${file.url}" loading="lazy" alt="${file.name}">`;
|
||||
} else if (['mp3', 'flac', 'ogg'].includes(ext)) {
|
||||
previewContent = `<audio controls src="${file.url}"></audio>`;
|
||||
} else if (['mp4', 'webm', 'wmv'].includes(ext)) {
|
||||
previewContent = `<video controls loading="lazy"><source src="${file.url}" type="video/${ext}"></video>`;
|
||||
} else {
|
||||
const icon = getFileIcon(ext);
|
||||
previewContent = `<div class="file-icon">${icon} ${file.name}</div>`;
|
||||
}
|
||||
|
||||
card.innerHTML = `
|
||||
<div class="preview">${previewContent}</div>
|
||||
<div class="info">
|
||||
<a href="${file.shareUrl}" target="_blank" class="name" title="Ouvrir dans un nouvel onglet">${file.name}</a>
|
||||
<div class="date">${new Date(file.date * 1000).toLocaleString()}</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
return card;
|
||||
}
|
||||
|
||||
function getFileIcon(ext) {
|
||||
const icons = {
|
||||
'pdf': '📄',
|
||||
'zip': '📦',
|
||||
'rar': '📦',
|
||||
'txt': '📝',
|
||||
'm3u': '🎵',
|
||||
'm3u8': '🎵',
|
||||
'css': '📰'
|
||||
};
|
||||
return icons[ext] || '📎';
|
||||
}
|
||||
|
||||
async function loadFiles() {
|
||||
if (loading || noMoreFiles) return;
|
||||
|
||||
loading = true;
|
||||
document.getElementById('loading').style.display = 'block';
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append('page', page);
|
||||
formData.append('sortBy', sortBy);
|
||||
formData.append('sortDesc', sortDesc);
|
||||
|
||||
try {
|
||||
const response = await fetch('', {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
});
|
||||
|
||||
const files = await response.json();
|
||||
|
||||
if (files.length < <?= FILES_PER_PAGE ?>) {
|
||||
noMoreFiles = true;
|
||||
}
|
||||
|
||||
const grid = document.getElementById('fileGrid');
|
||||
files.forEach(file => {
|
||||
grid.appendChild(createFileCard(file));
|
||||
});
|
||||
|
||||
page++;
|
||||
} catch (error) {
|
||||
console.error('Erreur de chargement:', error);
|
||||
} finally {
|
||||
loading = false;
|
||||
document.getElementById('loading').style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
function changeSortBy(newSortBy) {
|
||||
if (newSortBy === sortBy) {
|
||||
sortDesc = !sortDesc;
|
||||
} else {
|
||||
sortBy = newSortBy;
|
||||
sortDesc = true;
|
||||
}
|
||||
|
||||
// Réinitialiser
|
||||
page = 0;
|
||||
noMoreFiles = false;
|
||||
document.getElementById('fileGrid').innerHTML = '';
|
||||
loadFiles();
|
||||
|
||||
// Mettre à jour les boutons
|
||||
document.getElementById('sortDate').textContent =
|
||||
`Date ${sortBy === 'date' ? (sortDesc ? '↓' : '↑') : ''}`;
|
||||
document.getElementById('sortName').textContent =
|
||||
`Nom ${sortBy === 'name' ? (sortDesc ? '↓' : '↑') : ''}`;
|
||||
}
|
||||
|
||||
// Détecter le scroll pour charger plus de fichiers
|
||||
window.addEventListener('scroll', () => {
|
||||
if ((window.innerHeight + window.scrollY) >= document.documentElement.scrollHeight - 500) {
|
||||
loadFiles();
|
||||
}
|
||||
});
|
||||
|
||||
// Charger les premiers fichiers
|
||||
loadFiles();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
57
index.php
57
index.php
@ -1,57 +0,0 @@
|
||||
<html>
|
||||
|
||||
<head>
|
||||
|
||||
<title>Cyla - hébergement de fichiers</title>
|
||||
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
||||
|
||||
<link rel="stylesheet" href="static/CSS/uploadpage.css" media="screen" title="no title" charset="utf-8">
|
||||
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<p class="dltext title">
|
||||
Cyla
|
||||
</p>
|
||||
|
||||
<p class="dltext subtle">
|
||||
Un fichier ne peut dépasser 100 Mo. Les extensions autorisées sont 'png', 'jpg', 'jpeg', 'gif', 'webm', 'mp4', 'wmv', 'mp3', 'flac', 'ogg', 'zip', 'css', 'pdf', 'zip', 'rar', 'm3u', 'm3u8', 'txt'.
|
||||
</p>
|
||||
|
||||
<!-- File upload form -->
|
||||
<form class="uploadinterface" id="uploadform" action="upload.php" method="post" enctype="multipart/form-data">
|
||||
<input type="file" name="file" class="fileselector">
|
||||
<input type="submit" name="uploadbutton" value="héberger" class="submitbutton">
|
||||
</form>
|
||||
|
||||
<p class="dltext subtle">
|
||||
En utilisant ce service, vous acceptez les limites situées à la page <a href="md/info.html">info</a>.
|
||||
</p>
|
||||
|
||||
<div class="sidebar">
|
||||
|
||||
<div class="separator"><p class="buttontxt">Liens utiles</p></div>
|
||||
|
||||
<a href="md/info.html">
|
||||
<div class="button orangeBG"><p class="buttontxt">Info</p></div>
|
||||
</a>
|
||||
|
||||
<a href="md/faq.html">
|
||||
<div class="button orangeBG"><p class="buttontxt">F.A.Q</p></div>
|
||||
</a>
|
||||
|
||||
<a href="http://paypal.me/esenjin" target="blank">
|
||||
<div class="button orangeBG"><p class="buttontxt">Don</p></div>
|
||||
</a>
|
||||
|
||||
<a href="gallery.php">
|
||||
<div class="button orangeBG"><p class="buttontxt">Admin</p></div>
|
||||
</a>
|
||||
|
||||
</div>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
207
js/UploadZone.jsx
Normal file
207
js/UploadZone.jsx
Normal file
@ -0,0 +1,207 @@
|
||||
import React, { useState, useRef } from 'react';
|
||||
import { X, Upload, CheckCircle, AlertCircle } from 'lucide-react';
|
||||
|
||||
const UploadZone = () => {
|
||||
const [isDragging, setIsDragging] = useState(false);
|
||||
const [files, setFiles] = useState([]);
|
||||
const fileInputRef = useRef(null);
|
||||
|
||||
const MAX_FILE_SIZE = 100 * 1024 * 1024; // 100 Mo
|
||||
const ALLOWED_EXTENSIONS = ['png', 'jpg', 'jpeg', 'gif', 'webm', 'mp4', 'wmv', 'mp3', 'flac', 'ogg', 'zip', 'css', 'pdf', 'rar', 'm3u', 'm3u8', 'txt'];
|
||||
|
||||
const handleDragOver = (e) => {
|
||||
e.preventDefault();
|
||||
setIsDragging(true);
|
||||
};
|
||||
|
||||
const handleDragLeave = (e) => {
|
||||
e.preventDefault();
|
||||
setIsDragging(false);
|
||||
};
|
||||
|
||||
const validateFile = (file) => {
|
||||
const extension = file.name.split('.').pop().toLowerCase();
|
||||
if (!ALLOWED_EXTENSIONS.includes(ext |