summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--public/js/tinymce.js53
-rw-r--r--src/controller/ajax.php82
2 files changed, 130 insertions, 5 deletions
diff --git a/public/js/tinymce.js b/public/js/tinymce.js
index 307c384..55a37cc 100644
--- a/public/js/tinymce.js
+++ b/public/js/tinymce.js
@@ -30,7 +30,7 @@ function openEditor(id, page = '') {
30 toolbar_mode: 'wrap', 30 toolbar_mode: 'wrap',
31 statusbar: false, 31 statusbar: false,
32 setup: function (editor) { 32 setup: function (editor) {
33 editor.on('init', function () { 33 editor.on('init', function (){
34 editors[id] = editor; 34 editors[id] = editor;
35 35
36 // boutons "Modifier", "Supprimer", "déplacer vers le haut", "déplacer vers le bas", "Annuler" et "Soumettre" 36 // boutons "Modifier", "Supprimer", "déplacer vers le haut", "déplacer vers le bas", "Annuler" et "Soumettre"
@@ -48,6 +48,53 @@ function openEditor(id, page = '') {
48 document.querySelector(`#new-${id}`).classList.add('hidden'); // id = new-new-id_node 48 document.querySelector(`#new-${id}`).classList.add('hidden'); // id = new-new-id_node
49 } 49 }
50 }); 50 });
51 editor.on('PastePreProcess', function (e){ // déclenchement au collage AVANT insertion dans l'éditeur
52 let parser = new DOMParser();
53 let doc = parser.parseFromString(e.content, 'text/html');
54 let images = doc.querySelectorAll('img');
55
56 let downloads_in_progress = [];
57
58 images.forEach(img => {
59 if(img.src.startsWith('file://')){ // détection d'images non insérables
60 console.warn('Image locale non insérable dans tinymce :', img.src);
61 img.outerHTML = '<div style="border:1px solid red; padding:10px; margin:5px 0; background-color:#ffe6e6; color:#a94442; font-size:14px;">' +
62"Image locale non insérée. Pour insérer une image depuis LibreOffice, copiez l'image seule et recoller." +
63'</div>';
64 }
65 else if(img.src.startsWith('http')){ // détection d'images web
66 let promise = fetch('index.php?action=upload_image_url', { // promesse d'un fichier téléchargeable sur le serveur
67 method: 'POST',
68 headers: { 'Content-Type': 'application/json' },
69 body: JSON.stringify({ image_url: img.src })
70 })
71 .then(response => response.json())
72 .then(data => {
73 if(data.location){
74 img.src = data.location; // remplacer l'image par celle du serveur
75 }
76 })
77 .catch(error => {
78 console.error('Erreur lors de l’upload de l’image :', error);
79 });
80
81 downloads_in_progress.push(promise);
82 }
83 });
84
85 // une image web ou plus: différer l'insertion dans l'éditeur le temps que le serveur télécharge les images
86 if(downloads_in_progress.length > 0){
87 e.preventDefault();
88
89 Promise.all(downloads_in_progress).then(() => {
90 e.content = doc.body.innerHTML; // remplacement du HTML dans l'éditeur par la copie modifiée (doc)
91 editor.insertContent(e.content);
92 });
93 }
94 else{
95 e.content = doc.body.innerHTML; // remplacement du HTML dans l'éditeur par la copie modifiée (doc)
96 }
97 }); // fin editor.on('PastePreProcess'...
51 }, 98 },
52 // upload d'image 99 // upload d'image
53 images_upload_handler: (blobInfo, progress) => new Promise((resolve, reject) => { 100 images_upload_handler: (blobInfo, progress) => new Promise((resolve, reject) => {
@@ -86,9 +133,7 @@ function deleteArticle(id, page = '') {
86 // Envoyer une requête au serveur pour supprimer l'article 133 // Envoyer une requête au serveur pour supprimer l'article
87 fetch('index.php?action=delete_article', { 134 fetch('index.php?action=delete_article', {
88 method: 'POST', 135 method: 'POST',
89 headers: { 136 headers: {'Content-Type': 'application/json'},
90 'Content-Type': 'application/json'
91 },
92 body: JSON.stringify({ id: id }) 137 body: JSON.stringify({ id: id })
93 }) 138 })
94 .then(response => response.json()) 139 .then(response => response.json())
diff --git a/src/controller/ajax.php b/src/controller/ajax.php
index 18f303b..943c027 100644
--- a/src/controller/ajax.php
+++ b/src/controller/ajax.php
@@ -7,10 +7,62 @@ use App\Entity\Page;
7use App\Entity\Node; 7use App\Entity\Node;
8use App\Entity\Article; 8use App\Entity\Article;
9 9
10
11// mettre ça ailleurs
12function imagickCleanImage(string $image_data, string $local_path): bool // "string" parce que file_get_contents...
13{
14 try{
15 $imagick = new Imagick();
16 $imagick->readImageBlob($image_data);
17 $imagick->stripImage(); // nettoyage métadonnées
18 $imagick->setImageFormat('jpeg');
19 $imagick->setImageCompression(Imagick::COMPRESSION_JPEG);
20 $imagick->setImageCompressionQuality(85); // optionnel
21 $imagick->writeImage($local_path); // enregistrement
22 $imagick->clear();
23 $imagick->destroy();
24 return true;
25 }
26 catch(Exception $e){
27 return false;
28 }
29}
30function curlDownloadImage(string $url, $maxRetries = 3, $timeout = 10): string|false
31{
32 $attempt = 0;
33 $imageData = false;
34
35 while($attempt < $maxRetries){
36 $ch = curl_init($url); // instance de CurlHandle
37 curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
38 curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
39 curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $timeout);
40 curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
41 curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true);
42 curl_setopt($ch, CURLOPT_USERAGENT, 'TinyMCE-Image-Downloader');
43
44 $imageData = curl_exec($ch);
45 $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
46 //$curlError = curl_error($ch);
47
48 curl_close($ch);
49
50 if($imageData !== false && $httpCode >= 200 && $httpCode < 300){
51 return $imageData;
52 }
53
54 $attempt++;
55 sleep(1);
56 }
57
58 return false; // échec après trois tentatives
59}
60
61
10// détection des requêtes d'upload d'image de tinymce 62// détection des requêtes d'upload d'image de tinymce
11if(strpos($_SERVER['CONTENT_TYPE'], 'multipart/form-data') !== false && isset($_GET['action']) && $_GET['action'] === 'upload_image') 63if(strpos($_SERVER['CONTENT_TYPE'], 'multipart/form-data') !== false && isset($_GET['action']) && $_GET['action'] === 'upload_image')
12{ 64{
13 if (isset($_FILES['file'])) { 65 if(isset($_FILES['file'])){
14 $file = $_FILES['file']; 66 $file = $_FILES['file'];
15 $dest = 'images/'; 67 $dest = 'images/';
16 $dest_mini = 'images-mini/'; 68 $dest_mini = 'images-mini/';
@@ -26,6 +78,7 @@ if(strpos($_SERVER['CONTENT_TYPE'], 'multipart/form-data') !== false && isset($_
26 $filePath = $dest . basename($file['name']); 78 $filePath = $dest . basename($file['name']);
27 79
28 // créer une miniature de l'image 80 // créer une miniature de l'image
81 //
29 82
30 if(move_uploaded_file($file['tmp_name'], $filePath)) { 83 if(move_uploaded_file($file['tmp_name'], $filePath)) {
31 $image_url = str_replace(basename($_SERVER['SCRIPT_NAME']), '', $_SERVER['SCRIPT_NAME']); 84 $image_url = str_replace(basename($_SERVER['SCRIPT_NAME']), '', $_SERVER['SCRIPT_NAME']);
@@ -42,6 +95,33 @@ if(strpos($_SERVER['CONTENT_TYPE'], 'multipart/form-data') !== false && isset($_
42 } 95 }
43 die; 96 die;
44} 97}
98// cas du collage d'un contenu HTML, réception d'une URL, téléchargement par le serveur et renvoie de l'adresse sur le serveur
99elseif(isset($_GET['action']) && $_GET['action'] == 'upload_image_url'){
100 $json = json_decode(file_get_contents('php://input'), true);
101
102 if(isset($json['image_url'])){
103 $image_data = curlDownloadImage($json['image_url']); // téléchargement de l’image par le serveur avec cURL au lieu de file_get_contents
104
105 if($image_data === false){
106 http_response_code(400);
107 echo json_encode(['message' => "Erreur, le serveur n'a pas réussi à télécharger l'image."]);
108 die;
109 }
110
111 $local_path = 'images/' . uniqid() . '.jpg';
112 if(imagickCleanImage($image_data, $local_path)){ // recréer l’image pour la nettoyer
113 echo json_encode(['location' => $local_path]); // nouvelle adresse
114 }
115 else{
116 http_response_code(500);
117 echo json_encode(['message' => 'Erreur image non valide']);
118 }
119 }
120 else{
121 echo json_encode(['message' => 'Erreur 400: Bad Request']);
122 }
123 die;
124}
45 125
46 126
47// détection des requêtes de type XHR, y en a pas à priori 127// détection des requêtes de type XHR, y en a pas à priori