diff options
| author | polo <ordipolo@gmx.fr> | 2025-06-19 18:11:20 +0200 |
|---|---|---|
| committer | polo <ordipolo@gmx.fr> | 2025-06-19 18:11:20 +0200 |
| commit | 8cf5ac1abf9e2a6134cb82d4582aecaa99b1331a (patch) | |
| tree | 24b69c10b8a553c96323542153bd7e4597a6e049 | |
| parent | b61c918e05124ddb0bb3102a626ca913a0ab4f3a (diff) | |
| download | cms-8cf5ac1abf9e2a6134cb82d4582aecaa99b1331a.tar.gz cms-8cf5ac1abf9e2a6134cb82d4582aecaa99b1331a.tar.bz2 cms-8cf5ac1abf9e2a6134cb82d4582aecaa99b1331a.zip | |
upload image éditeur: téléchargement automatique d'images base64 non encapsulées dans du HTML
| -rw-r--r-- | public/js/tinymce.js | 50 | ||||
| -rw-r--r-- | src/controller/ajax.php | 39 |
2 files changed, 86 insertions, 3 deletions
diff --git a/public/js/tinymce.js b/public/js/tinymce.js index f8d78c8..18974af 100644 --- a/public/js/tinymce.js +++ b/public/js/tinymce.js | |||
| @@ -48,6 +48,51 @@ 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 | let skipPastePreProcess = false; | ||
| 52 | editor.on('Paste', function (e){ // déclenchement AVANT PastePreProcess et quelque que soit le contenu collé | ||
| 53 | const clipboardData = (e.clipboardData || e.originalEvent.clipboardData); | ||
| 54 | if(!clipboardData){ | ||
| 55 | return; | ||
| 56 | } | ||
| 57 | const items = clipboardData.items; | ||
| 58 | let foundImage = false; | ||
| 59 | |||
| 60 | for(let i = 0; i < items.length; i++){ | ||
| 61 | let item = items[i]; | ||
| 62 | |||
| 63 | if(item.type.indexOf('image') !== -1){ // test type MIME contenant image | ||
| 64 | foundImage = true; | ||
| 65 | |||
| 66 | const file = item.getAsFile(); // presse-papier => fichier lisible | ||
| 67 | const reader = new FileReader(); | ||
| 68 | |||
| 69 | reader.onload = function (event){ // fonction exécutée lorsque reader.readAsDataURL(file) est terminée | ||
| 70 | const base64Data = event.target.result; // données de l'image | ||
| 71 | |||
| 72 | fetch('index.php?action=upload_image_base64', { | ||
| 73 | method: 'POST', | ||
| 74 | headers: { 'Content-Type': 'application/json' }, | ||
| 75 | body: JSON.stringify({ image_base64: base64Data }) | ||
| 76 | }) | ||
| 77 | .then(response => response.json()) | ||
| 78 | .then(data => { | ||
| 79 | if(data.location){ | ||
| 80 | editor.insertContent('<img src="' + data.location + '">'); | ||
| 81 | } | ||
| 82 | }) | ||
| 83 | .catch(error => { | ||
| 84 | console.error('Erreur lors de l’upload de l’image base64 :', error); | ||
| 85 | }); | ||
| 86 | }; | ||
| 87 | reader.readAsDataURL(file); // lecture asynchrone du fichier | ||
| 88 | } | ||
| 89 | } | ||
| 90 | |||
| 91 | if(foundImage){ | ||
| 92 | e.preventDefault(); // supprime le collage automatiue | ||
| 93 | skipPastePreProcess = true; // désactiver le PastePreProcess pour ce collage | ||
| 94 | } | ||
| 95 | }); | ||
| 51 | editor.on('PastePreProcess', function (e){ // déclenchement au collage AVANT insertion dans l'éditeur | 96 | editor.on('PastePreProcess', function (e){ // déclenchement au collage AVANT insertion dans l'éditeur |
| 52 | const parser = new DOMParser(); | 97 | const parser = new DOMParser(); |
| 53 | const doc = parser.parseFromString(e.content, 'text/html'); | 98 | const doc = parser.parseFromString(e.content, 'text/html'); |
| @@ -58,9 +103,8 @@ function openEditor(id, page = '') { | |||
| 58 | images.forEach(img => { | 103 | images.forEach(img => { |
| 59 | if(img.src.startsWith('file://')){ // détection d'images non insérables | 104 | if(img.src.startsWith('file://')){ // détection d'images non insérables |
| 60 | console.warn('Image locale non insérable dans tinymce :', img.src); | 105 | 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;">' + | 106 | 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 (vient-elle de LibreOffice ?). Effacez cet encadré et copiez-collez l'image seule. Ensuite cliquez sur le bouton Insérer une image puis dans la nouvelle fenêtre sur Enregistrer." + | 107 | "Image locale non insérée (vient-elle d'un document LibreOffice ?). Effacez ce message rouge et copiez-collez l'image seule.</div>`; |
| 63 | '</div>'; | ||
| 64 | } | 108 | } |
| 65 | else if(img.src.startsWith('http')){ // détection d'images web | 109 | else if(img.src.startsWith('http')){ // détection d'images web |
| 66 | const promise = fetch('index.php?action=upload_image_url', { // promesse d'un fichier téléchargeable sur le serveur | 110 | const promise = fetch('index.php?action=upload_image_url', { // promesse d'un fichier téléchargeable sur le serveur |
diff --git a/src/controller/ajax.php b/src/controller/ajax.php index 6813d45..a462921 100644 --- a/src/controller/ajax.php +++ b/src/controller/ajax.php | |||
| @@ -142,7 +142,46 @@ elseif(isset($_GET['action']) && $_GET['action'] == 'upload_image_url'){ | |||
| 142 | } | 142 | } |
| 143 | die; | 143 | die; |
| 144 | } | 144 | } |
| 145 | // cas du collage d'une image (code base64) non encapsulée dans du HTML | ||
| 146 | elseif(isset($_GET['action']) && $_GET['action'] == 'upload_image_base64'){ | ||
| 147 | $json = json_decode(file_get_contents('php://input'), true); | ||
| 148 | $dest = 'images/'; | ||
| 149 | |||
| 150 | if(!is_dir('images')){ | ||
| 151 | mkdir('images', 0777, true); | ||
| 152 | } | ||
| 145 | 153 | ||
| 154 | // détection de data:image/ et de ;base64, et capture du format dans $type | ||
| 155 | if(!isset($json['image_base64']) || !preg_match('/^data:image\/(\w+);base64,/', $json['image_base64'], $type)){ | ||
| 156 | http_response_code(400); | ||
| 157 | echo json_encode(['message' => 'Données image base64 manquantes ou invalides']); | ||
| 158 | die; | ||
| 159 | } | ||
| 160 | |||
| 161 | $allowed_extensions = ['jpg', 'jpeg', 'png', 'gif', 'webp', 'tiff', 'tif']; | ||
| 162 | $extension = strtolower($type[1]); | ||
| 163 | if(!in_array($extension, $allowed_extensions) || $extension === 'jpg'){ | ||
| 164 | $extension = 'jpeg'; | ||
| 165 | } | ||
| 166 | |||
| 167 | $image_data = base64_decode(substr($json['image_base64'], strpos($json['image_base64'], ',') + 1)); // découpe la chaine à la virgule puis convertit en binaire | ||
| 168 | if($image_data === false){ | ||
| 169 | http_response_code(400); | ||
| 170 | echo json_encode(['message' => 'Décodage base64 invalide']); | ||
| 171 | die; | ||
| 172 | } | ||
| 173 | |||
| 174 | $local_path = $dest . 'pasted_image_' . uniqid() . '.' . $extension; | ||
| 175 | |||
| 176 | if(imagickCleanImage($image_data, $local_path)){ | ||
| 177 | echo json_encode(['location' => $local_path]); | ||
| 178 | } | ||
| 179 | else{ | ||
| 180 | http_response_code(500); | ||
| 181 | echo json_encode(['message' => 'Erreur image non valide']); | ||
| 182 | } | ||
| 183 | die; | ||
| 184 | } | ||
| 146 | 185 | ||
| 147 | // détection des requêtes de type XHR, y en a pas à priori | 186 | // détection des requêtes de type XHR, y en a pas à priori |
| 148 | /*elseif(isset($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest'){ | 187 | /*elseif(isset($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest'){ |
