summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorpolo <ordipolo@gmx.fr>2025-01-13 00:44:49 +0100
committerpolo <ordipolo@gmx.fr>2025-01-13 00:44:49 +0100
commite9a5da044f63851aa37d401ef37c8102b08ae274 (patch)
tree12ebf8a0aefd7a63d6d7dcbe4ad7d74e6ee3bb92 /src
parent0023ec83932beddb0f1c604f3c210189113c9b7a (diff)
downloadckeditor5-e9a5da044f63851aa37d401ef37c8102b08ae274.zip
chemins changés, dossier integration
Diffstat (limited to 'src')
-rw-r--r--src/integration/ckeditor5/article_hors_editeur.css38
-rw-r--r--src/integration/ckeditor5/clean_html.php40
-rw-r--r--src/integration/ckeditor5/config.php22
-rw-r--r--src/integration/ckeditor5/create.php18
-rw-r--r--src/integration/ckeditor5/image_upload.php64
-rw-r--r--src/integration/ckeditor5/init.php13
-rw-r--r--src/integration/ckeditor5/view.php173
-rw-r--r--src/view/templates/page.php (renamed from src/templates/page.php)2
8 files changed, 369 insertions, 1 deletions
diff --git a/src/integration/ckeditor5/article_hors_editeur.css b/src/integration/ckeditor5/article_hors_editeur.css
new file mode 100644
index 0000000..a38b384
--- /dev/null
+++ b/src/integration/ckeditor5/article_hors_editeur.css
@@ -0,0 +1,38 @@
1.conteneur_article{width: 630px;}
2.conteneur_article:after{content: ""; display: block; clear: both;}
3
4img{vertical-align: bottom;}
5@media screen and (max-width: 1000px)
6{img{max-width: 900px;}}
7
8.text-tiny{font-size: x-small;}
9.text-small{font-size: small;}
10.text-big{font-size: large;}
11.text-huge{font-size: x-large;}
12
13blockquote{border-left: 5px #cccccc solid; margin: 14px 0px; padding: 2px 25px; font-style: italic;}
14
15.marker-yellow{background-color: #fdfd77;}
16.marker-green{background-color: #62f962;}
17.marker-pink{background-color: #fc7899;}
18.marker-blue{background-color: #72ccfd;}
19.pen-red{background-color: white; color: red;}
20.pen-green{background-color: white; color: green;}
21
22ul{padding-left: 10px;}
23.todo-list>li{list-style-type : none;}
24input[type=checkbox]{-webkit-appearance: none;-moz-appearance: none;-ms-appearance: none;
25height: 16px; width: 16px; border: 1px solid black; border-radius: 2px; position: relative; top: 5px; margin-right: 10px;}
26input[type="checkbox"]:checked{border: none; background: #26ab33;}
27
28.table>table{border-collapse: collapse;}
29.table td{border: 1px grey solid; padding: 7px; min-width: 30px;}
30td p{margin: 0px;}
31
32.image{margin: 0px; text-align: center; display: inline-block;}
33.image-style-side{float: right;}
34.image img{max-width: 630px;}
35.image-style-side img{max-width: 315px;}
36.image>figcaption{margin: 0px 10px; padding: 7px; text-align: center; font-size: small; background-color: #f7f7f7;}
37
38iframe{min-width: 400px; min-height: 300px; max-width: 1200px; max-height: 900px;}
diff --git a/src/integration/ckeditor5/clean_html.php b/src/integration/ckeditor5/clean_html.php
new file mode 100644
index 0000000..e56f49c
--- /dev/null
+++ b/src/integration/ckeditor5/clean_html.php
@@ -0,0 +1,40 @@
1<?php
2// src/integration/ckeditor5/clean_html.php
3
4function getAndCleanEditorOutput(): string
5{
6 // bugs possibles sans trim() lorsqu'on insère le HTML dans l'éditeur
7 $html = trim($_POST["contenu"]);
8
9 //checkContentInFile($html, 'avant');
10
11 // sécurisation du HTML (faille XSS)
12 require 'vendor/htmlawed/htmlawed/htmLawed.php';
13 $configHtmLawed = array(
14 'safe'=>1, // protection contre les élements et attributs dangereux
15
16 // balises autorisées
17 'elements'=>'h2, h3, h4, p, span, i, strong, u, s, mark, blockquote, li, ol, ul, a, figure, hr, img, figcaption, table, tbody, tr, td',
18 // note: change <s></s> en <span style="text-decoration: line-through;"></span>
19
20 // attributs interdits
21 'deny_attribute'=>'id', // 'class' et 'style' sont conservés pour le ckeditor
22 );
23 $specHtmLawed = ''; // optionnel: faire qu'un certain élément puisse n'avoir que certains attributs
24 $html = htmLawed($html, $configHtmLawed, $specHtmLawed);
25
26 //checkContentInFile($html, 'après');
27
28 return $html;
29}
30
31
32function checkContentInFile($html, $file_name)
33{
34 $page = 'page';
35 $nom_fichier = $file_name . ".html";
36 $fichier = fopen('data/' . $page . '/' . $nom_fichier, 'w'); // w peut créer un fichier, si il existe déjà, il est effacé par le nouveau contenu
37 fputs($fichier, $html);
38 fclose($fichier);
39 chmod('data/' . $page . '/' . $nom_fichier, 0666);
40} \ No newline at end of file
diff --git a/src/integration/ckeditor5/config.php b/src/integration/ckeditor5/config.php
new file mode 100644
index 0000000..822884f
--- /dev/null
+++ b/src/integration/ckeditor5/config.php
@@ -0,0 +1,22 @@
1<?php
2// src/integration/ckeditor5/config.php
3
4$ckeditor_integration_path = 'src/integration/ckeditor5/';
5$css_hors_editeur = '<link rel="stylesheet" href="' . $ckeditor_integration_path . 'article_hors_editeur.css" />';
6
7$server_root = $_SERVER['SERVER_NAME'] . '/ckeditor5/';
8// pour l'importmap: j'ai modifié la version "installation avec CDN de la doc pour utiliser les fichiers locaux
9// l'"importmap" permet d'utiliser "import" (ça ressemble pas mal au python) dans le navigateur comme n'importe quel langage de programmation normal
10
11$previous_page = 'index.php';
12$open_editor_link = 'index.php?page=editor';
13$form_action_file = 'index.php?action=submit';
14$upload_ajax_url = 'index.php?action=upload_image';
15
16$toolbar_language = 'fr';
17
18$storage = 'files'; // choisir 'files' ou 'database'
19$page = 'page';
20$nom_article = "article";
21
22$php_ini_max_size = ini_get('upload_max_filesize'); // = 2M par défaut dans le php.ini
diff --git a/src/integration/ckeditor5/create.php b/src/integration/ckeditor5/create.php
new file mode 100644
index 0000000..863c420
--- /dev/null
+++ b/src/integration/ckeditor5/create.php
@@ -0,0 +1,18 @@
1<?php
2// src/integration/ckeditor5/create.php
3
4// modèle
5if($storage === 'database')
6{}
7elseif($storage === 'files')
8{
9 // modèle
10 $texte = file_get_contents('data/' . $page . '/html/' . $nom_article . '.html');
11
12 $texte = trim(addcslashes($texte, "'")); // échapper les simples quotes pour javascript
13 //$texte = trim(addslashes($texte)); // échappe ', ", \ et NULL, je sais pas si c'est bien
14}
15
16// vue
17require $ckeditor_integration_path . 'view.php'; // html + JS
18$contenu = $editeurHTML;
diff --git a/src/integration/ckeditor5/image_upload.php b/src/integration/ckeditor5/image_upload.php
new file mode 100644
index 0000000..9090ddf
--- /dev/null
+++ b/src/integration/ckeditor5/image_upload.php
@@ -0,0 +1,64 @@
1<?php
2// src/integration/ckeditor5/image_upload.php
3
4// script récupérant les images téléchargée en AJAX par l'éditeur et c'est tout
5// on récupère les données, on renvoie au navigateur la réponse qu'il attend et stop!
6
7// le "simple upload adapter" envoie un POST appelé: $_FILES['upload']
8// en retour il attend impérativement des données au format JSON du genre: {"url": "data/page/images/monfichier.jpg"}
9// cette adresse doit permettre à l'éditeur de télécharger l'image afficher de manière normale: <img scr="data/page/images/monfichier.jpg">
10
11// pour voir cette réponse, les messages d'erreur ou tout affichage avec echo ou var_dump:
12// outils de développement (F12) => réseau => trouver la requête (xhr) => cliquer dessus puis sur réponse
13
14// rappel: le téléchargement de fichier avec PHP nécessite un dossier temporaire et que le serveur y soit autorisé en écriture
15
16
17$erreur = '';
18if(isset($_FILES['upload']) AND $_FILES['upload']['error'] == 0) // 0 signifie ok
19{
20 if($_FILES['upload']['size'] <= $php_ini_max_size )
21 {
22 $infos = pathinfo ($_FILES['upload']['name']);
23 $extension = $infos['extension'];
24 $extautorisées = ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp', 'tiff'];
25 // on prend la même liste que celle côté javascript, le SVG est bloqué pour raison de sécurité (javascript à l'intérieur)
26 if(in_array($extension, $extautorisées))
27 {
28 move_uploaded_file ($_FILES['upload']['tmp_name'], 'data/' . $page . '/images/' . $_FILES['upload']['name']);
29 chmod('data/' . $page . '/images/' . $_FILES['upload']['name'], 0666);
30 }
31 else
32 {
33 $erreur = 'mauvais format, veuillez utiliser une image comportant un de ces formats: jpg ou jpeg, png, gif, bmp, webp, tiff<br />le format svg n\'est pas supporté';
34 }
35 }
36 else
37 {
38 $erreur = 'fichier trop lourd';
39 }
40}
41else
42{
43 $erreur = $_FILES['upload']['error'];
44}
45/* les erreurs retournées avec $_FILES['upload']['error']:
460 pas d'erreur
471 taille du fichier supérieure à la valeur de upload_max_filesize dans le fichier php.ini (par défaut = 2 MO)
482 taille du fichier supérieure à la valeur de MAX_FILE_SIZE dans le formulaire HTML
493 fichier partiellement téléchargé
504 pas de fichier du tout
516 pas de dossier temporaire pour mettre le fichier
527 echec de l'écriture sur le DD
538 envoi arrêté par une extension de PHP mais on ne nous dit pas pourquoi => diagnostic avec la fonction phpinfo() */
54
55if(empty($erreur))
56{
57 $chemin = '{"url": "data/' . $page . '/images/' . $_FILES['upload']['name'] . '"}';
58 echo $chemin;
59}
60else
61{
62 echo $erreur;
63}
64die;
diff --git a/src/integration/ckeditor5/init.php b/src/integration/ckeditor5/init.php
new file mode 100644
index 0000000..4d77cba
--- /dev/null
+++ b/src/integration/ckeditor5/init.php
@@ -0,0 +1,13 @@
1<?php
2// src/integration/ckeditor5/init.php
3
4// lien symbolique des traductions
5function makeTranslationSymLink(): void
6{
7 $target = '../translations';
8 $link = 'node_modules/ckeditor5/dist/browser/translations';
9 if (!file_exists($link))
10 {
11 symlink($target, $link);
12 }
13}
diff --git a/src/integration/ckeditor5/view.php b/src/integration/ckeditor5/view.php
new file mode 100644
index 0000000..794e55f
--- /dev/null
+++ b/src/integration/ckeditor5/view.php
@@ -0,0 +1,173 @@
1<?php
2// src/integration/ckeditor5/view.php
3
4//$css_editeur = '<link rel="stylesheet" href="node_modules/ckeditor5/dist/ckeditor5.css" />'; // version normale aérée et commentée
5$css_editeur = '<link rel="stylesheet" href="node_modules/ckeditor5/dist/browser/ckeditor5.css" />'; // version "minifiée"
6
7ob_start();
8?>
9<div class="conteneur_article" >
10 <form action="<?= $form_action_file ?>" method="POST" enctype="multipart/form-data" >
11 <textarea id="editor" name="contenu" ></textarea>
12 <input class="boutonSubmitEditeur" type="submit" value="Valider">
13 <a class="boutonAnnuler" href="<?= $previous_page ?>" >
14 <input type="button" value="Annuler"></a>
15 <script type="importmap">
16 {
17 "imports": {
18 "ckeditor5": "http://<?= $server_root ?>node_modules/ckeditor5/dist/browser/ckeditor5.js",
19 "ckeditor5/": "http://<?= $server_root ?>node_modules/ckeditor5/dist/browser/"
20 }
21 }
22 </script>
23 <script type="module">
24 import { // nécessite type="module"
25 ClassicEditor, Essentials, Heading, Paragraph, Alignment, List,
26 BlockQuote, HorizontalLine, Bold, Italic, Underline, Strikethrough,
27 Font, FontFamily, Highlight, FontBackgroundColor, SimpleUploadAdapter,
28 Image, ImageInsert, ImageToolbar, ImageStyle, ImageCaption, LinkImage,
29 Link, Table, TableColumnResize, TableToolbar, TableProperties, TableCellProperties, TextPartLanguage
30 } from "ckeditor5";
31
32 import coreTranslations from 'ckeditor5/translations/<?= $toolbar_language ?>.js';
33 // n'utilise pas le bon chemin à cause d'un bug? solution = créer un lien symbolique à l'endroit attendu:
34 // ln -s /srv/http/ckeditor5/node_modules/ckeditor5/dist/translations /srv/http/ckeditor5/node_modules/ckeditor5/dist/browser/
35
36 //import 'ckeditor5/ckeditor5.css'; // marche pas chez moi
37
38 let editor;
39 let html_existant = '<?= $texte ?>'; // $texte doit avoir été sécurisé: simple quotes échappées au minimum
40 let upload_url = '<?= $upload_ajax_url ?>';
41
42 // ATTENTION: si l'éditeur ne fonctionne pas, empêcher qu'on puisse cliquer sur Valider!
43 // Il y a aussi des paramètres dans le fichier de config: ckeditor/webpack.config.js
44 ClassicEditor
45 .create( document.querySelector( '#editor' ),{
46
47 licenseKey: 'GPL',
48
49 plugins: [ Essentials, Heading, Paragraph, Alignment, List,
50 BlockQuote, HorizontalLine, Underline, Strikethrough,
51 Bold, Italic, Font, FontFamily, Highlight, FontBackgroundColor, SimpleUploadAdapter,
52 Image, ImageInsert, ImageToolbar, ImageStyle, ImageCaption, LinkImage,
53 Link, Table, TableColumnResize, TableToolbar, TableProperties, TableCellProperties, TextPartLanguage],
54
55 toolbar: {
56 items: [
57 'undo', 'redo', 'selectAll', '|', 'heading', '|', 'alignment', 'bulletedList', 'numberedList',
58 //'todoList',// marche pas, ne crée pas de HTML
59 'blockQuote', 'horizontalLine', '|', 'textPartLanguage', '-', 'bold', 'italic', 'underline', 'strikethrough', '|',
60 'fontFamily', // polices microsoft uniquement
61 'fontColor', 'fontSize', 'highlight', 'fontBackgroundColor', // un peu comme highlight
62 '|', 'imageInsert', 'link',
63 //'htmlEmbed', // marche pas, ne crée pas de HTML
64 //'mediaEmbed',
65 'insertTable',
66 ],
67 // multiligne automatique (le '-' dans la liste permet de choisir l'endroit où couper)
68 // pour les plugins indiqués "marche pas", envoient-ils un requête AJAX quelque part?
69
70 shouldNotGroupWhenFull: true
71 },
72
73 language: '<?= $toolbar_language ?>',
74 translations: [coreTranslations],
75
76 // barre d'outils dans une image
77 image: {
78 toolbar: [
79 //'imageStyle:full', //inutile?
80 'imageStyle:block',
81 'imageStyle:inline', // complexe, on peut aussi placer l'image à la souris
82 'imageStyle:side',
83 /*'imageStyle:align-left',
84 'imageStyle:align-right',*/
85 //'imageResize', // optionnel? on a les poignées dans les coins de l'image
86 'linkImage',
87 'toggleImageCaption',
88 'imageTextAlternative'
89 ]
90 },
91
92 // barre d'outils dans un tableau
93 table: {
94 contentToolbar: [
95 'tableColumn', // manipulation sur les colonnes et lignes
96 'tableRow',
97 'mergeTableCells',
98 'tableProperties', // style sur la table
99 'tableCellProperties' // style sur une cellule
100 ]
101 },
102
103 // langues dispo pour TextPartLanguage
104 language: {
105 textPartLanguage: [
106 { title: 'Arabic', languageCode: 'ar' },
107 { title: 'English', languageCode: 'en' },
108 { title: 'French', languageCode: 'fr' },
109 { title: 'German', languageCode: 'de' },
110 { title: 'Hebrew', languageCode: 'he' },
111 { title: 'Spanish', languageCode: 'es' }
112 ]
113 },
114
115 // plugin code HTML
116 // voir doc
117
118 // media embarqué (audio, vidéo, carte)
119 //mediaEmbed: {
120 //previewsInData: true,
121 // vaut "false" par defaut, on crée la balise non standard <oembed url="" > // https://oembed.com/
122 // qui nécessite un traitement (en JS ou côté serveur) en utilisant le lien à l'intérieur
123 // avec "true", on crée la balise <iframe> qui sert à insérer une page HTML dans une autre,
124 // notre "embarquement de média" devrait donc réussir quelque soit le site
125 // c'est plus simple, il ne reste plus qu'à ajuster le contenu avec du CSS (important)
126 // on doit supprimer le positionnement absolu qui fait que l'iframe se place par dessus le reste et adapter la taille de l'élément
127
128 // en outre, le plugin mediaEmbed (dans l'éditeur), tout comme la balise "iframe" (hors éditeur),
129 // permettent d'afficher un aperçu (preview): une image avec un bouton lecture dessus
130 // cette image est envoyée imédiatement par le serveur, et le contenu après un clic dessus
131 //
132 //},
133
134 // images
135 // le plugin "simple upload adapter" communique avec le serveur au moyen de requêtes AJAX
136 // il attend du serveur une réponse au format .json contenant l'adresse où est stockée l'image:
137 // {"url": "https://example.com/images/foo.jpg"}
138 simpleUpload: {
139 uploadUrl: upload_url,
140
141 // Headers supplémentaires envoyés avec la requête
142 // c'est ici qu'on installe les mécanismes de sécurités comme l'authentification et la protection au CSRF
143 headers: {
144 //'X-CSRF-TOKEN': 'CSRF-Token',
145 //Authorization: 'Bearer <JSON Web Token>'
146 }
147 },
148 // formats acceptés par défaut: jpeg, png, gif, bmp, webp, tiff
149 // le svg n'est pas dans la liste, pour raison de sécurité apparemment, il parait qu'on peut mettre du javascript à l'intérieur
150
151 // plugin autosave
152 // voir doc
153 } )
154 .then( newEditor => {
155 editor = newEditor;
156 // fin de ClassicEditor
157
158 // obtenir la liste des éléments disponibles pour la barre d'outils
159 //alert(Array.from( editor.ui.componentFactory.names() ));
160 // obtenir la liste des plugins disponibles:
161 //alert(ClassicEditor.builtinPlugins.map( plugin => plugin.pluginName ));
162
163 // insertion du HTML existant
164 editor.setData(html_existant);
165 } )
166 .catch( error => {
167 console.error( error );
168 } );
169 </script>
170 </form>
171</div>
172<?php
173$editeurHTML = ob_get_clean();
diff --git a/src/templates/page.php b/src/view/templates/page.php
index 98e7979..3153ced 100644
--- a/src/templates/page.php
+++ b/src/view/templates/page.php
@@ -4,7 +4,7 @@
4 <meta charset="utf-8"> 4 <meta charset="utf-8">
5 <title></title> 5 <title></title>
6 <link rel="icon" type="image/png" href=""> 6 <link rel="icon" type="image/png" href="">
7 <link rel="stylesheet" href="lib/ckeditor5/article_hors_editeur.css" /> 7 <?= $css_hors_editeur ?>
8 <?= $css_editeur ?> 8 <?= $css_editeur ?>
9</head> 9</head>
10<body> 10<body>