Compare commits
3 Commits
b7e7c54ecf
...
dd154c632d
Author | SHA1 | Date | |
---|---|---|---|
dd154c632d | |||
31d611267a | |||
313edb9c28 |
@ -39,7 +39,7 @@ $siteStats = $stats->getStats();
|
||||
<!-- Contenu principal -->
|
||||
<div class="about-content">
|
||||
<div class="about-description">
|
||||
<?= $about['content'] // Le contenu est déjà en HTML ?>
|
||||
<?= $about['content'] = Config::fixImagePaths($about['content']); ?>
|
||||
</div>
|
||||
|
||||
<aside class="sidebar">
|
||||
|
193
admin/api/about-image-upload.php
Normal file
193
admin/api/about-image-upload.php
Normal file
@ -0,0 +1,193 @@
|
||||
<?php
|
||||
require_once '../../includes/config.php';
|
||||
require_once '../../includes/auth.php';
|
||||
|
||||
class AboutImageUploadHandler {
|
||||
private $uploadDir;
|
||||
private $allowedTypes = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'];
|
||||
private $maxFileSize = 5242880; // 5MB
|
||||
private $maxWidth = 1200;
|
||||
private $maxHeight = 1200;
|
||||
|
||||
public function __construct() {
|
||||
$this->uploadDir = __DIR__ . '/../../assets/images/about/';
|
||||
$this->ensureUploadDirectory();
|
||||
}
|
||||
|
||||
public function handleUpload($file) {
|
||||
try {
|
||||
// Vérifications de base
|
||||
if ($file['error'] !== UPLOAD_ERR_OK) {
|
||||
throw new Exception($this->getUploadErrorMessage($file['error']));
|
||||
}
|
||||
|
||||
// Vérification du type MIME
|
||||
$finfo = new finfo(FILEINFO_MIME_TYPE);
|
||||
$mimeType = $finfo->file($file['tmp_name']);
|
||||
if (!in_array($mimeType, $this->allowedTypes)) {
|
||||
throw new Exception('Type de fichier non autorisé. Types acceptés : JPG, PNG, GIF, WEBP');
|
||||
}
|
||||
|
||||
// Vérification de la taille
|
||||
if ($file['size'] > $this->maxFileSize) {
|
||||
throw new Exception('Fichier trop volumineux. Taille maximum : 5MB');
|
||||
}
|
||||
|
||||
// Vérification et redimensionnement de l'image
|
||||
[$width, $height, $type] = getimagesize($file['tmp_name']);
|
||||
$needsResize = $width > $this->maxWidth || $height > $this->maxHeight;
|
||||
|
||||
// Génération d'un nom de fichier unique
|
||||
$extension = $this->getExtensionFromMimeType($mimeType);
|
||||
$filename = uniqid() . '.' . $extension;
|
||||
$targetPath = $this->uploadDir . $filename;
|
||||
|
||||
if ($needsResize) {
|
||||
// Calcul des nouvelles dimensions en conservant le ratio
|
||||
$ratio = min($this->maxWidth / $width, $this->maxHeight / $height);
|
||||
$newWidth = round($width * $ratio);
|
||||
$newHeight = round($height * $ratio);
|
||||
|
||||
// Création de la nouvelle image
|
||||
$sourceImage = $this->createImageFromFile($file['tmp_name'], $mimeType);
|
||||
$newImage = imagecreatetruecolor($newWidth, $newHeight);
|
||||
|
||||
// Préservation de la transparence pour PNG
|
||||
if ($mimeType === 'image/png') {
|
||||
imagealphablending($newImage, false);
|
||||
imagesavealpha($newImage, true);
|
||||
}
|
||||
|
||||
// Redimensionnement
|
||||
imagecopyresampled(
|
||||
$newImage, $sourceImage,
|
||||
0, 0, 0, 0,
|
||||
$newWidth, $newHeight,
|
||||
$width, $height
|
||||
);
|
||||
|
||||
// Sauvegarde de l'image redimensionnée
|
||||
$this->saveImage($newImage, $targetPath, $mimeType);
|
||||
|
||||
// Libération de la mémoire
|
||||
imagedestroy($sourceImage);
|
||||
imagedestroy($newImage);
|
||||
} else {
|
||||
// Déplacement du fichier original si pas besoin de redimensionnement
|
||||
if (!move_uploaded_file($file['tmp_name'], $targetPath)) {
|
||||
throw new Exception('Erreur lors du déplacement du fichier uploadé');
|
||||
}
|
||||
}
|
||||
|
||||
// Retourner le chemin relatif pour l'éditeur
|
||||
return [
|
||||
'success' => true,
|
||||
'url' => 'assets/images/about/' . $filename,
|
||||
'width' => $needsResize ? $newWidth : $width,
|
||||
'height' => $needsResize ? $newHeight : $height
|
||||
];
|
||||
|
||||
} catch (Exception $e) {
|
||||
return [
|
||||
'success' => false,
|
||||
'error' => $e->getMessage()
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
private function ensureUploadDirectory() {
|
||||
if (!file_exists($this->uploadDir)) {
|
||||
if (!mkdir($this->uploadDir, 0755, true)) {
|
||||
throw new Exception('Impossible de créer le dossier d\'upload');
|
||||
}
|
||||
}
|
||||
|
||||
if (!is_writable($this->uploadDir)) {
|
||||
throw new Exception('Le dossier d\'upload n\'est pas accessible en écriture');
|
||||
}
|
||||
}
|
||||
|
||||
private function createImageFromFile($file, $mimeType) {
|
||||
switch ($mimeType) {
|
||||
case 'image/jpeg':
|
||||
return imagecreatefromjpeg($file);
|
||||
case 'image/png':
|
||||
return imagecreatefrompng($file);
|
||||
case 'image/gif':
|
||||
return imagecreatefromgif($file);
|
||||
case 'image/webp':
|
||||
return imagecreatefromwebp($file);
|
||||
default:
|
||||
throw new Exception('Type d\'image non supporté');
|
||||
}
|
||||
}
|
||||
|
||||
private function saveImage($image, $path, $mimeType) {
|
||||
switch ($mimeType) {
|
||||
case 'image/jpeg':
|
||||
return imagejpeg($image, $path, 85);
|
||||
case 'image/png':
|
||||
return imagepng($image, $path, 8);
|
||||
case 'image/gif':
|
||||
return imagegif($image, $path);
|
||||
case 'image/webp':
|
||||
return imagewebp($image, $path, 85);
|
||||
default:
|
||||
throw new Exception('Type d\'image non supporté pour la sauvegarde');
|
||||
}
|
||||
}
|
||||
|
||||
private function getExtensionFromMimeType($mimeType) {
|
||||
$map = [
|
||||
'image/jpeg' => 'jpg',
|
||||
'image/png' => 'png',
|
||||
'image/gif' => 'gif',
|
||||
'image/webp' => 'webp'
|
||||
];
|
||||
return $map[$mimeType] ?? 'jpg';
|
||||
}
|
||||
|
||||
private function getUploadErrorMessage($error) {
|
||||
$errors = [
|
||||
UPLOAD_ERR_INI_SIZE => 'Le fichier dépasse la taille maximale autorisée par PHP',
|
||||
UPLOAD_ERR_FORM_SIZE => 'Le fichier dépasse la taille maximale autorisée par le formulaire',
|
||||
UPLOAD_ERR_PARTIAL => 'Le fichier n\'a été que partiellement uploadé',
|
||||
UPLOAD_ERR_NO_FILE => 'Aucun fichier n\'a été uploadé',
|
||||
UPLOAD_ERR_NO_TMP_DIR => 'Dossier temporaire manquant',
|
||||
UPLOAD_ERR_CANT_WRITE => 'Échec de l\'écriture du fichier sur le disque',
|
||||
UPLOAD_ERR_EXTENSION => 'Une extension PHP a arrêté l\'upload'
|
||||
];
|
||||
return $errors[$error] ?? 'Erreur inconnue lors de l\'upload';
|
||||
}
|
||||
}
|
||||
|
||||
// Point d'entrée du script
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||
http_response_code(405);
|
||||
exit(json_encode(['error' => 'Méthode non autorisée']));
|
||||
}
|
||||
|
||||
// Vérification de l'authentification
|
||||
if (!Auth::check()) {
|
||||
http_response_code(401);
|
||||
exit(json_encode(['error' => 'Non autorisé']));
|
||||
}
|
||||
|
||||
// Traitement de l'upload
|
||||
try {
|
||||
$handler = new AboutImageUploadHandler();
|
||||
$result = $handler->handleUpload($_FILES['image']);
|
||||
|
||||
if (!$result['success']) {
|
||||
http_response_code(400);
|
||||
}
|
||||
|
||||
header('Content-Type: application/json');
|
||||
echo json_encode($result);
|
||||
} catch (Exception $e) {
|
||||
http_response_code(500);
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'error' => 'Erreur serveur : ' . $e->getMessage()
|
||||
]);
|
||||
}
|
@ -81,9 +81,12 @@ class ImageUploadHandler {
|
||||
}
|
||||
|
||||
// Retourner le chemin relatif pour l'éditeur
|
||||
$relativePath = $this->getRelativePath($targetPath);
|
||||
$adminPreviewPath = '../' . $relativePath;
|
||||
return [
|
||||
'success' => true,
|
||||
'url' => $this->getRelativePath($targetPath),
|
||||
'url' => $adminPreviewPath,
|
||||
'storage_url' => $relativePath,
|
||||
'width' => $needsResize ? $newWidth : $width,
|
||||
'height' => $needsResize ? $newHeight : $height
|
||||
];
|
||||
@ -149,9 +152,7 @@ class ImageUploadHandler {
|
||||
}
|
||||
|
||||
private function getRelativePath($absolutePath) {
|
||||
$relativePath = str_replace(__DIR__ . '/../../', '', $absolutePath);
|
||||
// Ajout de '../' car on est dans admin/api/
|
||||
return '../' . str_replace('\\', '/', $relativePath); // Pour la compatibilité Windows
|
||||
return str_replace(__DIR__ . '/../../', '', $absolutePath);
|
||||
}
|
||||
|
||||
private function getUploadErrorMessage($error) {
|
||||
|
@ -51,7 +51,7 @@
|
||||
/* Paragraphes et espacement */
|
||||
.chapter-content p,
|
||||
.novel-description p {
|
||||
margin: 0 0 1.5em 0;
|
||||
margin: 0 0 0em 0;
|
||||
min-height: 1.5em;
|
||||
}
|
||||
|
||||
|
@ -537,6 +537,15 @@ body {
|
||||
padding: var(--spacing-lg);
|
||||
border-radius: var(--radius-md);
|
||||
border: 1px solid var(--border-color);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.about-description img {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
display: block;
|
||||
margin: var(--spacing-md) 0;
|
||||
border-radius: var(--radius-sm);
|
||||
}
|
||||
|
||||
/* Sidebar */
|
||||
|
@ -19,50 +19,47 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
['clean']
|
||||
],
|
||||
handlers: {
|
||||
image: handleImageUpload
|
||||
image: function() {
|
||||
const input = document.createElement('input');
|
||||
input.setAttribute('type', 'file');
|
||||
input.setAttribute('accept', 'image/*');
|
||||
input.click();
|
||||
|
||||
input.onchange = async () => {
|
||||
const file = input.files[0];
|
||||
if (file) {
|
||||
const formData = new FormData();
|
||||
formData.append('image', file);
|
||||
|
||||
try {
|
||||
const response = await fetch('api/about-image-upload.php', {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
});
|
||||
|
||||
if (!response.ok) throw new Error('Upload failed');
|
||||
|
||||
const result = await response.json();
|
||||
if (result.success) {
|
||||
const range = aboutEditor.getSelection(true);
|
||||
aboutEditor.insertEmbed(range.index, 'image', result.url);
|
||||
aboutEditor.setSelection(range.index + 1);
|
||||
} else {
|
||||
showNotification(result.error || 'Erreur lors de l\'upload', 'error');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error:', error);
|
||||
showNotification('Erreur lors de l\'upload de l\'image', 'error');
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
placeholder: 'Commencez à écrire le contenu de la page À propos...'
|
||||
});
|
||||
|
||||
// Gestion de l'upload d'images
|
||||
function handleImageUpload() {
|
||||
const input = document.createElement('input');
|
||||
input.setAttribute('type', 'file');
|
||||
input.setAttribute('accept', 'image/*');
|
||||
input.click();
|
||||
|
||||
input.onchange = async () => {
|
||||
const file = input.files[0];
|
||||
if (file) {
|
||||
const formData = new FormData();
|
||||
formData.append('image', file);
|
||||
|
||||
try {
|
||||
const response = await fetch('api/upload-image.php', {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
});
|
||||
|
||||
if (!response.ok) throw new Error('Upload failed');
|
||||
|
||||
const result = await response.json();
|
||||
if (result.success) {
|
||||
const range = aboutEditor.getSelection(true);
|
||||
aboutEditor.insertEmbed(range.index, 'image', result.url);
|
||||
aboutEditor.setSelection(range.index + 1);
|
||||
} else {
|
||||
showNotification(result.error || 'Erreur lors de l\'upload', 'error');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error:', error);
|
||||
showNotification('Erreur lors de l\'upload de l\'image', 'error');
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Initialisation du contenu si existant
|
||||
const editorElement = document.getElementById('aboutEditor');
|
||||
const initialContent = editorElement.getAttribute('data-initial-content');
|
||||
|
@ -1,6 +1,7 @@
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const storyId = document.querySelector('input[name="id"]')?.value || urlParams.get('id');
|
||||
let currentChapterId = null;
|
||||
const storyId = document.querySelector('input[name="id"]')?.value;
|
||||
|
||||
// Fonction de notification
|
||||
function showNotification(message, type = 'success') {
|
||||
@ -96,6 +97,12 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
if (result.success) {
|
||||
const range = quill.getSelection(true);
|
||||
quill.insertEmbed(range.index, 'image', result.url);
|
||||
const insertOp = quill.getContents().ops.find(op =>
|
||||
op.insert && op.insert.image === result.url
|
||||
);
|
||||
if (insertOp) {
|
||||
insertOp.insert.image = result.storage_url;
|
||||
}
|
||||
quill.setSelection(range.index + 1);
|
||||
} else {
|
||||
showNotification(result.error || 'Erreur lors de l\'upload', 'error');
|
||||
|
@ -68,7 +68,7 @@ $config = Config::load();
|
||||
<!-- Contenu principal -->
|
||||
<div class="novel-content">
|
||||
<div class="novel-description chapter-content">
|
||||
<?= $currentChapter['content'] ?>
|
||||
<?= $currentChapter['content'] = Config::fixImagePaths($currentChapter['content']); ?>
|
||||
|
||||
<!-- Navigation entre chapitres -->
|
||||
<div class="chapter-navigation">
|
||||
|
@ -31,6 +31,10 @@ class Config {
|
||||
return $config[$key] ?? $default;
|
||||
}
|
||||
|
||||
public static function fixImagePaths($content) {
|
||||
return preg_replace('/(src|url)=(["\'])\.\.\//i', '$1=$2', $content);
|
||||
}
|
||||
|
||||
public static function save($newConfig) {
|
||||
$configFile = __DIR__ . '/../config.json';
|
||||
|
||||
|
@ -46,7 +46,7 @@ $config = Config::load();
|
||||
<!-- Contenu principal -->
|
||||
<div class="novel-content">
|
||||
<div class="novel-description">
|
||||
<?= $story['description'] ?>
|
||||
<?= $story['description'] = Config::fixImagePaths($story['description']); ?>
|
||||
</div>
|
||||
|
||||
<aside class="chapters-menu">
|
||||
|
Loading…
x
Reference in New Issue
Block a user