From 787d03e48471ba62cd830379428f04d996f0b74b Mon Sep 17 00:00:00 2001 From: polo Date: Thu, 17 Feb 2022 18:13:00 +0100 Subject: model update --- .htaccess | 22 +- controller/Security.php | 2 +- controller/admin.php | 372 +-- controller/backup.php | 158 +- controller/ckeditor.php | 290 +- controller/installation.php | 190 +- controller/media.php | 424 +-- controller/visitor.php | 275 +- erreur404.php | 48 +- index.php | 570 ++-- lib/HtmlFormatter.php | 252 +- lib/ckeditor5/LICENSE.md | 98 +- lib/ckeditor5/README.md | 136 +- lib/ckeditor5/build/ckeditor.js | 10 +- lib/ckeditor5/package.json | 86 +- lib/ckeditor5/sample/index.html | 326 +-- lib/ckeditor5/sample/styles.css | 930 +++---- lib/ckeditor5/src/ckeditor.js | 158 +- lib/ckeditor5/webpack.config.js | 192 +- lib/htmlawed/htmLawed.php | 1458 +++++----- lib/htmlawed/htmLawedTest.php | 1354 +++++----- lib/htmlawed/htmLawed_README.htm | 4576 ++++++++++++++++---------------- lib/htmlawed/htmLawed_README.txt | 3634 ++++++++++++------------- lib/htmlawed/htmLawed_TESTCASE.txt | 910 +++---- lisez-moi.txt | 100 +- model/Album.php | 111 +- model/Article.php | 125 + model/Image.php | 2 + model/Page.php | 104 - "protection CSRF - \303\240 faire.txt" | 21 + public/accueil.css | 991 +++---- public/discographie.css | 79 +- public/donnees_hors_editeur.css | 74 +- public/icone_supprimer.png | Bin 0 -> 7277 bytes public/main.js | 163 +- public/melaine.css | 214 +- public/menu.css | 678 ++--- public/menu/redimensionnement.sh | 50 +- public/normalize.css | 698 ++--- view/accueil.php | 46 +- view/album.php | 9 +- view/courriel.php | 18 +- view/discographie.php | 103 +- view/melaine.php | 255 +- view/menu.php | 146 +- view/nav.php | 24 +- view/password.php | 212 +- view/template-formulaires.php | 375 ++- view/template.php | 182 +- 49 files changed, 10782 insertions(+), 10469 deletions(-) create mode 100644 model/Article.php delete mode 100644 model/Page.php create mode 100644 "protection CSRF - \303\240 faire.txt" create mode 100644 public/icone_supprimer.png diff --git a/.htaccess b/.htaccess index f4a4cf8..63bea00 100644 --- a/.htaccess +++ b/.htaccess @@ -1,11 +1,11 @@ -# redirection en cas d'erreur 404 - -# site simple -#ErrorDocument 404 http://localhost/melaine/erreur404.php - -# site "hôte virtuel" (cas des hébergeurs et d'une debian par défaut) -ErrorDocument 404 http://melaine.localhost/erreur404.php -#ErrorDocument 404 "retour au site" - -# Interdire exploration du répertoire -#Options All -Indexes +# redirection en cas d'erreur 404 + +# site simple +#ErrorDocument 404 http://localhost/melaine/erreur404.php + +# site "hôte virtuel" (cas des hébergeurs et d'une debian par défaut) +ErrorDocument 404 http://melaine.localhost/erreur404.php +#ErrorDocument 404 "retour au site" + +# Interdire exploration du répertoire +#Options All -Indexes diff --git a/controller/Security.php b/controller/Security.php index 98d2e74..6ea121c 100644 --- a/controller/Security.php +++ b/controller/Security.php @@ -4,7 +4,7 @@ // sécurité faille XSS avec htmLawed require('lib/htmlawed/htmLawed.php'); -class Security // toute statique, inutile de l'instancier +class Security { private static $configHtmLawed = array( 'safe'=>1, // protection contre les élements et attributs dangereux diff --git a/controller/admin.php b/controller/admin.php index e319c80..7f22711 100644 --- a/controller/admin.php +++ b/controller/admin.php @@ -1,165 +1,207 @@ -findFileName($numArticle); - $_SESSION['target'] = $Article->fileName; - - // suppression - if($suppression) - { - $Article->delete(); - header('Location: index.php?page=' . $page_actuelle); - exit(); - } - // modification - else - { - $title = "Modifier un article"; - $texte = $Article->readOne(); // entrée de l'éditeur - } - } - - // pour l'affichage des articles - $articles = array_reverse($Article->readAll()); // lourd - - // sécurisation du contenu pré-existant inséré dans l'éditeur - if($numArticle > 0 && !$suppression) - { - require('controller/ckeditor.php'); - $texte = preparationCKeditor($texte); - } - - // NB: penser à ajouter au template la fonctionnalité "autosave" - // https://ckeditor.com/docs/ckeditor5/latest/builds/guides/integration/saving-data.html - - // morceaux en HTML à assembler - // variable $editeurHTML, contient $texte - require('view/template-formulaires.php'); - // variables $css, $js, $header et $content, - // $content contient $articles et $editeurHTML - require('view/melaine.php'); - // fin de l'assemblage - require('view/template.php'); -} - -function discoEdit($albumCode, $suppression) -{ - $page_actuelle = "discographie"; - $title = "Discographie"; - - // modèle - $Albums = new Album($page_actuelle); - $Albums->makeFileList(); - - // nouvel album - if($albumCode == '') - { - unset($_SESSION['target']); - $title = "Ajouter un album"; - - // formulaires vides - $vignette = ['', '', '']; - $texte = ''; - } - else - { - // album ciblé - $Albums->albumCode = $albumCode; - - // suppression - if($suppression) - { - $Albums->delete(); - header('Location: index.php?page=' . $page_actuelle); - exit(); - } - // modification - else - { - $_SESSION['target'] = $Albums->albumCode; - $title = "Modifier un album"; - $texte = $Albums->readOneHTML($albumCode); - $vignette = $Albums->getVignetteJSON(); - } - } - - // traitements PHP pour l'éditeur - // sécurisation du contenu pré-existant inséré dans l'éditeur - if(!empty($albumCode) && !$suppression && !empty($texte)) - { - require('controller/ckeditor.php'); - $texte = preparationCKeditor($texte); - } - - // préremplir le champ - // c'est normallement impossible - - // contenu de tous les fichiers JSON (= tableau de chaines) - $albumsJSON = $Albums->readAll(); - // noms des fichiers JSON - $albumNamesJSON = $Albums->fileList; - - $annees = []; - for($i = 0; $i < count($albumsJSON); $i++) - { - // une case supplémentaire contient le "albumCode" - $albumsJSON[$i][3] = pathinfo($albumNamesJSON[$i])['filename']; - - $annees[$i] = $albumsJSON[$i][1]; - } - - // tri d'un tableau multidimensionnel - array_multisort($annees, $albumsJSON); - - // lien vers le HTML ou ancre? - $avecLien = []; - $linkDiscoChrono = []; - for($i = 0; $i < count($albumsJSON); $i++) - { - // cas particulier où le dossier "html" est vide - if(!empty($Albums->fileList)) - { - if(file_exists('data/discographie/html/' . $albumsJSON[$i][3] . '.html')) - { - $lienAlbum[$i] = 'page=album&album_name=' . $albumsJSON[$i][0] . '&album_code=' . $albumsJSON[$i][3]; - $avecLien[$i] = true; - $linkDiscoChrono[$i] = 'linkChrono'; // css - } - else - { - $lienAlbum[$i] = 'page=discographie#' . $albumsJSON[$i][0]; - $avecLien[$i] = false; - $linkDiscoChrono[$i] = 'noLinkChrono'; // css - } - $lienBoutonModif[$i] = 'page=discographie&action=edition&album_code=' . $albumsJSON[$i][3]; - } - } - - // morceaux en HTML à assembler - // variable $editeurHTML, contient $texte - require('view/template-formulaires.php'); - // variables $css, $js, $header et $content, - // $content contient ???? et $editeurHTML - require('view/discographie.php'); - // fin de l'assemblage - require('view/template.php'); -} +fileCode = $fileCode; + $_SESSION['target'] = $Articles->fileCode; + + // suppression + if($suppression) + { + $Articles->delete(); + + // redirection immédiate avant affichage + header('Location: index.php?page=' . $page_actuelle); + exit(); + } + // modification + else + { + $title = "Modifier un article"; + $texte = $Articles->readOne(); // entrée de l'éditeur + } + } + + // ajout des données dans $Articles->fileList['content'] + if(!empty($Articles->fileList)) + { + $Articles->readAll(); + $Articles->fileList = array_reverse($Articles->fileList); + } + + // sécurisation du contenu pré-existant inséré dans l'éditeur + if(!empty($fileCode) && !$suppression) + { + $fileCodeArgument = '&article=' . $fileCode; + require('controller/ckeditor.php'); + $texte = preparationCKeditor($texte); + } + else + { + $fileCodeArgument = ''; + } + + + // NB: penser à ajouter au template la fonctionnalité "autosave" + // https://ckeditor.com/docs/ckeditor5/latest/builds/guides/integration/saving-data.html + + // morceaux en HTML à assembler + // variable $editeurHTML, contient $texte + require('view/template-formulaires.php'); + // variables $css, $js, $header et $content, + // $content contient $articles et $editeurHTML + require('view/melaine.php'); + // fin de l'assemblage + require('view/template.php'); +} + +function discoEdit($fileCode, $suppression) +{ + $page_actuelle = "discographie"; + $title = "Discographie"; + + // modèle + $Albums = new Album($page_actuelle); + $Albums->makeFileList(); + //var_dump($Albums); + // contenu: JSON, HTML, noms et chemins des fichiers + $Albums->readAll(); + $Albums->fileList = array_reverse($Albums->fileList); + //var_dump($Albums); + //var_dump($Albums->fileList); + + // nouvel album + if($fileCode == '') + { + //echo($_SESSION['target'] . "
"); + //unset($_SESSION['target']); + $title = "Ajouter un album"; + + // formulaires vides + $vignette = ['', '', '']; + $texte = ''; + } + else + { + // album ciblé + $Albums->fileCode = $fileCode; + + // suppression + if($suppression) + { + $Albums->delete(); + header('Location: index.php?page=' . $page_actuelle); + exit(); + } + // modification + else + { + $_SESSION['target'] = $fileCode; + $title = "Modifier un album"; + + for($i = 0; $i < $Albums->fileListCount; $i++) + { + if($fileCode == $Albums->fileList[$i]['fileCode']) + { + // json + $vignette[0] = $Albums->fileList[$i]['titre']; + $vignette[1] = $Albums->fileList[$i]['annee']; + $vignette[2] = $Albums->fileList[$i]['pochette']; + // html + $texte = $Albums->fileList[$i]['HTMLcontent']; + + // image affichée à côté des formulaires + $imageFormulaire = 'data/' . $page_actuelle . '/images/' . $vignette[2]; + } + } + } + } + + // traitements PHP pour l'éditeur + // sécurisation du contenu pré-existant inséré dans l'éditeur + if(!empty($fileCode) && !$suppression && !empty($texte)) + { + // dans l'adresse dans la balise
+ $fileCodeArgument = '&article=' . $fileCode; + + require('controller/ckeditor.php'); + $texte = preparationCKeditor($texte); + } + else + { + $fileCodeArgument = ''; + } + + // préremplir le champ + // c'est normallement impossible + + + // tri des albums avec l'année dans le .json + + // noms des fichiers JSON + //$albumNamesJSON = $Albums->fileList; + //$annees = []; + for($i = 0; $i < $Albums->fileListCount; $i++) + { + // une case supplémentaire contient le "fileCode" + //$albumsJSON[$i][3] = pathinfo($albumNamesJSON[$i])['filename']; + //$albumsJSON[$i][3] = pathinfo($Albums->fileList[$i]['fileCode'])['filename']; + + //$annees[$i] = $albumsJSON[$i][1]; + } + //var_dump($annee); die(); + + // tri d'un tableau multidimensionnel + //array_multisort($annees, $albumsJSON); + + + + // liens dans la page et le menu chronologie + // ce dernier comporte une ancre lorsque le html n'existe pas + /*$lienAlbum = []; + $avecLien = []; + $linkDiscoChrono = [];*/ + for($i = 0; $i < $Albums->fileListCount; $i++) + { + if(file_exists('data/discographie/html/' . $Albums->fileList[$i]['fileCode'] . '.html')) + { + $lienAlbum[$i] = 'page=album&album_name=' . $Albums->fileList[$i]['titre'] . '&file_code=' . $Albums->fileList[$i]['fileCode']; + $avecLien[$i] = true; + $linkDiscoChrono[$i] = 'linkChrono'; // css + } + else + { + $lienAlbum[$i] = 'page=discographie#' . $Albums->fileList[$i]['titre']; + $avecLien[$i] = false; + $linkDiscoChrono[$i] = 'noLinkChrono'; // css + } + $lienBoutonModif[$i] = 'page=discographie&action=edition&file_code=' . $Albums->fileList[$i]['fileCode']; + } + + // morceaux en HTML à assembler + // variable $editeurHTML, contient $texte + require('view/template-formulaires.php'); + // variables $css, $js, $header et $content, + // $content contient ???? et $editeurHTML + require('view/discographie.php'); + // fin de l'assemblage + require('view/template.php'); +} diff --git a/controller/backup.php b/controller/backup.php index bc84a86..d0060c9 100644 --- a/controller/backup.php +++ b/controller/backup.php @@ -1,80 +1,80 @@ - - Veuillez prévenir l\'administrateur.
- Vous pouvez aussi récupérer manuellement les fichiers en vous connectant au serveur avec un client FTP, il suffit de récupérer tout le dossier "data".

- Retour au site'); - die(); - } - - // création du fichier zip vide - // j'ai utilisé l'option ZipArchive::OVERWRITE, - // ZipArchive::FL_OVERWRITE n'apparaît qu'avec PHP 8 - if ($zip->open($chemin . $nomFichier, ZipArchive::CREATE | ZipArchive::OVERWRITE)!==TRUE) { - exit("Impossible d'ouvrir le fichier " . $chemin . $nomFichier . ".\n"); - } - - // ajout des fichiers - // chemin complet = data/"pages"/"html ou images"/"tous les fichiers" - $zip->addGlob($chemin . '*/*/*', 0, array('')); - $zip->close(); - - // pour pouvoir manipuler le fichier depuis filezilla ou ssh - chmod($chemin . $nomFichier, 0666); // écriture 4 chiffres - -?> - - - extraction des données - - - -
- Toutes les données du sites ont été rassemblées dans un gros fichier que vous pouvez garder par exemple sur votre ordinateur.
- Vous pourrez l'utiliser plus tard pour restaurer le site dans l'état où il se trouve aujourd'hui. Ceci est utile dans le cas d'un changement d'hébergement ou dans le cas d'un problème affectant le serveur.
- Ce fichier se nomme sauvegarde_site_melaine.zip.

- Cliquez ici pour télécharger

- Retour au site -
- - - - - Restauration des données avec un backup - - - -
- Restauration des données à partir d'une sauvegarde.

- Vous devez avoir créé un fichier nommé sauvegarde_site_melaine.zip
- en cliquant sur Extraire les données.

- - -
- -

- Retour au site - -
- - + Veuillez prévenir l\'administrateur.
+ Vous pouvez aussi récupérer manuellement les fichiers en vous connectant au serveur avec un client FTP, il suffit de récupérer tout le dossier "data".

+ Retour au site'); + die(); + } + + // création du fichier zip vide + // j'ai utilisé l'option ZipArchive::OVERWRITE, + // ZipArchive::FL_OVERWRITE n'apparaît qu'avec PHP 8 + if ($zip->open($chemin . $nomFichier, ZipArchive::CREATE | ZipArchive::OVERWRITE)!==TRUE) { + exit("Impossible d'ouvrir le fichier " . $chemin . $nomFichier . ".\n"); + } + + // ajout des fichiers + // chemin complet = data/"pages"/"html ou images"/"tous les fichiers" + $zip->addGlob($chemin . '*/*/*', 0, array('')); + $zip->close(); + + // pour pouvoir manipuler le fichier depuis filezilla ou ssh + chmod($chemin . $nomFichier, 0666); // écriture 4 chiffres + +?> + + + extraction des données + + + +
+ Toutes les données du sites ont été rassemblées dans un gros fichier que vous pouvez garder par exemple sur votre ordinateur.
+ Vous pourrez l'utiliser plus tard pour restaurer le site dans l'état où il se trouve aujourd'hui. Ceci est utile dans le cas d'un changement d'hébergement ou dans le cas d'un problème affectant le serveur.
+ Ce fichier se nomme sauvegarde_site_melaine.zip.

+ Cliquez ici pour télécharger

+ Retour au site +
+ + + + + Restauration des données avec un backup + + + +
+ Restauration des données à partir d'une sauvegarde.

+ Vous devez avoir créé un fichier nommé sauvegarde_site_melaine.zip
+ en cliquant sur Extraire les données.

+ +
+
+ +

+ Retour au site +
+
+ +createVignette($titre, $annee, $pochette); - - // page de l'album - if(!empty($_POST['contenu'])) - { - $Album->create($contenu); - } - } - else - { - $Album->albumCode = $target; - - // page disco - $_SESSION['erreur'] = $Album->updateVignetteJSON($titre, $annee, $pochette); - - // page de l'album - if(isset($_POST['contenu'])) - { - $Album->format = 'html'; - $Album->setFileName(); - if(!empty($Album->fileName)) - { - $Album->update($_POST['contenu']); - } - } - } - } - // autres pages - else - { - $Article = new Page($page); - - if($target == '') - { - $Article->create($contenu); - } - else - { - $Article->fileName = $target; - $Article->update($contenu); - } - } -} - -// lien sans http:// -// un clic sur un lien dans l'éditeur affiche une infobulle montrant l'adresse cible du lien si celle-ci a déjà été précisée -// il est possible de cliquer sur ce lien, ce qui ouvre un onglet avec le site demandé -// toutefois si cette adresse est de type "domaine.fr" (sans http:// devant), le navigateur ne va pas rechercher un site mais un fichier comme si mon adresse était de type file:///fichier -// tomber ainsi sur une page d'erreur est déroutant: -// "ai-je perdu le texte que j'étais en train de taper?"" -// solution 1 (mauvaise): activer la redirection en cas d'erreur 404 dans le .htaccess -// solution 2 (façon pop-up): fermer ce nouvel onglet avec echo ''; -// pour faire passer par le .htaccess l'info comme quoi la page précédente comportait un éditeur ouvert... - -function cleanHTML($contenu) -{} +create($contenu); + } + else + { + $Article->fileCode = $_SESSION['target']; // erreur ici? + $Article->update($contenu); + } + } + // page discographie + else if($page == 'discographie') + { + $Album = new Album($page); + + // on retrouve les données si nécessaire + if(isset($_SESSION['target']) && $_SESSION['target'] != '') + { + $Album->fileCode = $_SESSION['target']; + $Album->readOne(); + } + + $nouveauTitre = Security::secureString($_POST['titre']); + $nouvelleAnnee = Security::secureString($_POST['annee']); + $nouvellePochette = $_FILES['upload']['name']; + + // enregistrement nouvel album + if(!isset($_SESSION['target']) || $_SESSION['target'] == '') + { + // page disco + $Album->createVignette($nouveauTitre, $nouvelleAnnee, $nouvellePochette); + + // page de l'album + if(!empty($_POST['contenu'])) + { + $Album->format = 'html'; + $Album->create($contenu); + } + } + // modification + else + { + $Album->albumCode = $_SESSION['target']; + + // page disco + $Album->format = 'json'; + + // garder l'image si aucune n'est sélectionnée + // (on ne peut pas préremplir un ) + if($nouvellePochette == '' && $Album->oneAlbum['pochette'] != '') + { + $nouvellePochette = $Album->oneAlbum['pochette']; + } + + $Album->updateVignetteJSON($nouveauTitre, $nouvelleAnnee, $nouvellePochette); + + // page de l'album + if(isset($_POST['contenu'])) + { + //var_dump($Album->oneAlbum); die(); + $Album->format = 'html'; + //$Album->setFileName(); + if(!empty($Album->oneAlbum['HTMLfileName'])) + { + $Album->update($_POST['contenu']); + } + } + } + } + + // nettoyage + unset($_SESSION['target']); + unset($_GET['action']); + unset($_POST['contenu']); + header('Location: index.php?page=' . $_GET['page']); + exit(); +} + + +// lien sans http:// +// un clic sur un lien dans l'éditeur affiche une infobulle montrant l'adresse cible du lien si celle-ci a déjà été précisée +// il est possible de cliquer sur ce lien, ce qui ouvre un onglet avec le site demandé +// toutefois si cette adresse est de type "domaine.fr" (sans http:// devant), le navigateur ne va pas rechercher un site mais un fichier comme si mon adresse était de type file:///fichier +// tomber ainsi sur une page d'erreur est déroutant: +// "ai-je perdu le texte que j'étais en train de taper?"" +// solution 1 (mauvaise): activer la redirection en cas d'erreur 404 dans le .htaccess +// solution 2 (façon pop-up): fermer ce nouvel onglet avec echo ''; +// pour faire passer par le .htaccess l'info comme quoi la page précédente comportait un éditeur ouvert... + +function cleanHTML($contenu) +{} diff --git a/controller/installation.php b/controller/installation.php index fc8c900..91521e2 100644 --- a/controller/installation.php +++ b/controller/installation.php @@ -1,95 +1,95 @@ -'); - //echo('server name: ' . $_SERVER['SERVER_NAME'] . '
'); - // echo('dirname: ' . dirname(__FILE__) . '
'); - // echo('file: ' . __FILE__ . '
'); - // echo('dir: ' . __DIR__. '
'); - - //$lien_htaccess = $_SERVER['SERVER_NAME'] . "/.htaccess"; - //$nomDuSite = "melaine"; - - // créer le .htaccess - if(!file_exists(".htaccess")) - { - echo('

Le .htaccess semble absent ce qui rend le site vulnérable aux attaques, veuillez contacter l\'administrateur.

'); - //$contenu = "# redirection en cas d'erreur 404\nErrorDocument 404 http://" . $_SERVER['SERVER_NAME'] . "/" . $nomDuSite . "/erreur404.php\n# Interdire exploration du répertoire\nOptions All -Indexes"; - // $fichier = fopen('.htaccess', 'w'); - // fputs($fichier, $contenu); - // fclose($fichier); - // chmod('.htaccess', 0666); // mettre 0644 en production - } - - // droits en lecture et écriture dans le cas d'une migration - // tester si le site est "vide" et prévoir un formulaire permettant d'upload le fichier .zip - // contenant toutes les données qu'il est possible de créer depuis le mode admin - // cette méthode a deux intérêts: - // - facilité pour l'utilisateur - // - toutes les données sont la "possession" du serveur apache, - // on prévient ainsi tous les futurs problèmes de droits - - // dans le cas de l'utilisation d'une base de données, le fichiers zip devrait contenir un "dump" - // (un fichier .sql), à voir si php est capable d'obtenir ou redonner ce fichier à la BDD - - // créer les dossiers (fait également à l'extraction du zip de données) - // attention: ne fonctionne pas sans une manipulation préalable - // - modifier soit-même les droits du dossier data (777), quoique 111 serait pas mal non plus - // - modifier ceux du dossier parent (nom du site) avec son client FTP à la mise en ligne chez l'hébergeur et ensuite remettre tout comme avant - - // le 0 devant signifie que le nombre est en octal - $droitsDossiers = 0777; // mettre 0700 à la fin - - $listePages = array('melaine', 'discographie', 'concerts', 'presse', 'ateliers', 'liens', 'peinture', 'archives'); - foreach ($listePages as $page) - { - if(!file_exists('data/' . $page)) - { - mkdir('data/' . $page); - chmod('data/' . $page, $droitsDossiers); - } - if(!file_exists('data/' . $page . '/html')) - { - mkdir('data/' . $page . '/html'); - chmod('data/' . $page . '/html', $droitsDossiers); - } - if(!file_exists('data/' . $page . '/images')) - { - mkdir('data/' . $page . '/images'); - chmod('data/' . $page . '/images', $droitsDossiers); - } - // if(!file_exists('data/' . $page . '/multimedia')) - // { - // mkdir('data/' . $page . '/multimedia'); - // chmod('data/' . $page . '/multimedia', 0777); - // } - } - // dossier supplémentaire dans discographie - if(!file_exists('data/discographie/json')) - { - mkdir('data/discographie/json'); - chmod('data/discographie/json', $droitsDossiers); - } - // fichier password.txt - if(!file_exists('data/password.txt')) - { - touch('data/password.txt'); - chmod('data/password.txt', 0600); - } - - // le modèle donnera les droits 0666 (octal) aux nouveaux fichiers à l'intérieur des dossiers - - // création d'un mot de passe si password.txt est vide - createPassword(); -} +'); + //echo('server name: ' . $_SERVER['SERVER_NAME'] . '
'); + // echo('dirname: ' . dirname(__FILE__) . '
'); + // echo('file: ' . __FILE__ . '
'); + // echo('dir: ' . __DIR__. '
'); + + //$lien_htaccess = $_SERVER['SERVER_NAME'] . "/.htaccess"; + //$nomDuSite = "melaine"; + + // créer le .htaccess + if(!file_exists(".htaccess")) + { + echo('

Le .htaccess semble absent ce qui rend le site vulnérable aux attaques, veuillez contacter l\'administrateur.

'); + //$contenu = "# redirection en cas d'erreur 404\nErrorDocument 404 http://" . $_SERVER['SERVER_NAME'] . "/" . $nomDuSite . "/erreur404.php\n# Interdire exploration du répertoire\nOptions All -Indexes"; + // $fichier = fopen('.htaccess', 'w'); + // fputs($fichier, $contenu); + // fclose($fichier); + // chmod('.htaccess', 0666); // mettre 0644 en production + } + + // droits en lecture et écriture dans le cas d'une migration + // tester si le site est "vide" et prévoir un formulaire permettant d'upload le fichier .zip + // contenant toutes les données qu'il est possible de créer depuis le mode admin + // cette méthode a deux intérêts: + // - facilité pour l'utilisateur + // - toutes les données sont la "possession" du serveur apache, + // on prévient ainsi tous les futurs problèmes de droits + + // dans le cas de l'utilisation d'une base de données, le fichiers zip devrait contenir un "dump" + // (un fichier .sql), à voir si php est capable d'obtenir ou redonner ce fichier à la BDD + + // créer les dossiers (fait également à l'extraction du zip de données) + // attention: ne fonctionne pas sans une manipulation préalable + // - modifier soit-même les droits du dossier data (777), quoique 111 serait pas mal non plus + // - modifier ceux du dossier parent (nom du site) avec son client FTP à la mise en ligne chez l'hébergeur et ensuite remettre tout comme avant + + // le 0 devant signifie que le nombre est en octal + $droitsDossiers = 0777; // mettre 0700 à la fin + + $listePages = array('melaine', 'discographie', 'concerts', 'presse', 'ateliers', 'liens', 'peinture', 'archives'); + foreach ($listePages as $page) + { + if(!file_exists('data/' . $page)) + { + mkdir('data/' . $page); + chmod('data/' . $page, $droitsDossiers); + } + if(!file_exists('data/' . $page . '/html')) + { + mkdir('data/' . $page . '/html'); + chmod('data/' . $page . '/html', $droitsDossiers); + } + if(!file_exists('data/' . $page . '/images')) + { + mkdir('data/' . $page . '/images'); + chmod('data/' . $page . '/images', $droitsDossiers); + } + // if(!file_exists('data/' . $page . '/multimedia')) + // { + // mkdir('data/' . $page . '/multimedia'); + // chmod('data/' . $page . '/multimedia', 0777); + // } + } + // dossier supplémentaire dans discographie + if(!file_exists('data/discographie/json')) + { + mkdir('data/discographie/json'); + chmod('data/discographie/json', $droitsDossiers); + } + // fichier password.txt + if(!file_exists('data/password.txt')) + { + touch('data/password.txt'); + chmod('data/password.txt', 0600); + } + + // le modèle donnera les droits 0666 (octal) aux nouveaux fichiers à l'intérieur des dossiers + + // création d'un mot de passe si password.txt est vide + createPassword(); +} diff --git a/controller/media.php b/controller/media.php index 8105b4d..c6682df 100644 --- a/controller/media.php +++ b/controller/media.php @@ -1,212 +1,212 @@ - - - // on vire les balises et ce qu'il y a entre les deux pour ne garder que les liens - // on pourra ensuite traiter ensemble les url avec et sans balises - // (?:) permet de matcher sans capturer, donc de retirer des choses - // pour la variable $tolere, on met: - // \w = [a-zA-Z0-9_], espace ' ', tabulation \t, CR et LF \r\n, - // /, @, %, deux-points ';', virgule ',' et point-virgule ; - // et devant être échappés: . # ( ) ] { } ? ! | et le tiret qui est placé au début ou la fin - $tolere = '\w\s\t\n\r/,;:éèàçù€\#@%\.\(\)[\]\{\}\?\!\|-'; - $pattern = '#(?:[' . $tolere . ']+)#'; - $chaine = preg_replace($pattern, '$2', $chaine); - //echo($chaine . "\n"); - - // on met les balises - $pattern = '#' . $regexListeNonGeree . '#'; - $remplacement = '$0'; - $chaine = preg_replace($pattern, $remplacement, $chaine); - - // c'est quoi cette merde? - // l'API DOM de PHP bien sur!! pour manipuler le "document object model" - // c'est du complètement calqué sur le javascript, avec une syntaxe PHP - // et en plus elle est super vieille, elle ne connaît pas les balises HTML5 ! - // mais une balise c'est une balise donc ça marche quand même - // la doc de PHP est comme toujours plutôt bonne, elle indique les types de données manipulées - // https://www.php.net/manual/fr/book.dom.php - $dom = new DOMDocument; - - // pour ne pas s'arrêter sur les erreurs à cause des "nouvelles" balises - libxml_use_internal_errors(true); - - if($dom->loadHTML($chaine)) // et non load() tout court qui sert à charger du XML - { - //
- // détecter
- // puis extraire le lien et supprimer les balises autour - if(preg_match("#
#", $chaine)) - { - $nbFigures = $dom->getElementsByTagName('figure')->length; - - $j = 0; - for($i = 0; $i < $nbFigures ; $i++) // boucle foreach impossible, une
disparaît à chaque tour - { - $balisesFigure = $dom->getElementsByTagName('figure'); - $figure = $balisesFigure->item($j); // l'item 1 devient 0 au deuxième passage, etc - $parent = $figure->parentNode; - - if($figure->getAttribute("class") == 'media') - { - $oembed = $figure->getElementsByTagName('oembed'); // tableau d'une seule case - $div = $figure->getElementsByTagName('div'); // tableau d'une seule case - if($oembed->length > 0) // si taille = 0, c'est que la balise figure contient autre chose qu'une oembed - { - $lien = $oembed->item(0)->getAttribute('url') . ' '; // un espace pour ne pas coller deux adresses - $span = $dom->createElement('span', $lien); - $class = $dom->createAttribute('class'); - $class->value = 'media_embed'; - $span->appendChild($class); - $parent->replaceChild($span, $figure); - } - elseif($div->length > 0) // cette div créée par le ckeditor contient notre lien, plus bas se trouve une iframe - { - $lien = $div->item(0)->getAttribute('data-oembed-url') . ' '; - $span = $dom->createElement('span', $lien); - $class = $dom->createAttribute('class'); - $class->value = 'media_embed'; - $span->appendChild($class); - $parent->replaceChild($span, $figure); - } - else - { - $j++; // la balise
n'est pas supprimée, on incrémente l'indice du tableau pour ne pas boucler à l'infini - } - } - } - } - - // #", $chaine)) - { - $nbIframes = $dom->getElementsByTagName('iframe')->length; - - for($i = 0; $i < $nbIframes ; $i++) - { - $balisesIframe = $dom->getElementsByTagName('iframe'); - $iframe = $balisesIframe->item(0); - $parent = $iframe->parentNode; - - $lien = $iframe->getAttribute('src') . ' '; - $span = $dom->createElement('span', $lien); - $class = $dom->createAttribute('class'); - $class->value = 'media_embed'; - $span->appendChild($class); - $parent->replaceChild($span, $$iframe); - } - } - - // - // pour tout les sites multimedia, remplacer les balises par les liens nus - // en effet, en revenant dans l'éditeur les liens ne déclenchent pas automatiquement "l'embarquement" - // cliquer à la fin d'un lien et passer à la ligne a pour effet de créer des balises - // ça pose un problème parce que la bibliothèque "embera" ne gère pas les liens dans des balises - if(preg_match("#.*#", $chaine)) - { - $nbA = $dom->getElementsByTagName('a')->length; - - $j = 0; - for($i = 0; $i < $nbA ; $i++) - { - $balisesA = $dom->getElementsByTagName('a'); - $a = $balisesA->item($j); - $parent = $a->parentNode; - - $lien = $a->getAttribute('href') . ' '; - - // seules les liens pointant vers une des adresses de la liste sont concernés - if(preg_match("#" . $listeMedia . "#", $lien)) - { - $span = $dom->createElement('span', $lien); - $class = $dom->createAttribute('class'); - $class->value = 'media_embed'; - $span->appendChild($class); - $parent->replaceChild($span, $a); - } - else - { - $j++; // la balise n'est pas supprimée, on incrémente l'indice du tableau pour ne pas boucler à l'infini - } - } - } - - // pour nettoyer tous les warnings qu'on vient de produire et purifier nos fichiers log - libxml_clear_errors(); - // pour au contraire pouvoir les regarder, remplacer la ligne au dessus par celles en dessous - //~ $errors = libxml_get_errors(); - //~ var_dump($errors); - - // mettre à jour le DOM et enlever le DOCTYPE et les balises et - $chaine = $dom->saveHTML($dom); - $pattern = array ('##', '##', '##'); - $remplacement = array ('', '', ''); - $chaine = preg_replace($pattern, $remplacement, $chaine); - } - else - { - echo "Impossible de charger le HTML"; - } - - - - // détecter soundcloud - // normallement il n'y a rien à faire ici mais on sait jamais (une mise à jour du ckeditor?) - if(preg_match("#soundcloud.com#", $chaine)) - {} - - // détecter facebook (éventuellement fb.watch) - // je crois que ça va être compliqué ici - if(preg_match("#facebook.com|fb.watch#", $chaine)) - {} - - return($chaine); // = $contenu -} - - -// embarquement !! -function mediaEmbed($chaine) -{ - //require("../Embera/src/Autoloader.php"); - //$embera = new Embera\Embera(); - //$chaine = $embera->autoEmbed($chaine); - - $pattern = '#(.+)#'; - $remplacement = ''; - $chaine = preg_replace($pattern, $remplacement, $chaine); - - // requête oembed - //~ echo('

'); - //~ print_r($embera->getUrlData([ - //~ 'https://vimeo.com/374131624', - //~ 'https://www.flickr.com/photos/bees/8597283706/in/photostream', - //~ ])); - - return($chaine); // = $contenu -} + + + // on vire les balises
et ce qu'il y a entre les deux pour ne garder que les liens + // on pourra ensuite traiter ensemble les url avec et sans balises + // (?:) permet de matcher sans capturer, donc de retirer des choses + // pour la variable $tolere, on met: + // \w = [a-zA-Z0-9_], espace ' ', tabulation \t, CR et LF \r\n, + // /, @, %, deux-points ';', virgule ',' et point-virgule ; + // et devant être échappés: . # ( ) ] { } ? ! | et le tiret qui est placé au début ou la fin + $tolere = '\w\s\t\n\r/,;:éèàçù€\#@%\.\(\)[\]\{\}\?\!\|-'; + $pattern = '#(?:[' . $tolere . ']+)#'; + $chaine = preg_replace($pattern, '$2', $chaine); + //echo($chaine . "\n"); + + // on met les balises + $pattern = '#' . $regexListeNonGeree . '#'; + $remplacement = '$0'; + $chaine = preg_replace($pattern, $remplacement, $chaine); + + // c'est quoi cette merde? + // l'API DOM de PHP bien sur!! pour manipuler le "document object model" + // c'est du complètement calqué sur le javascript, avec une syntaxe PHP + // et en plus elle est super vieille, elle ne connaît pas les balises HTML5 ! + // mais une balise c'est une balise donc ça marche quand même + // la doc de PHP est comme toujours plutôt bonne, elle indique les types de données manipulées + // https://www.php.net/manual/fr/book.dom.php + $dom = new DOMDocument; + + // pour ne pas s'arrêter sur les erreurs à cause des "nouvelles" balises + libxml_use_internal_errors(true); + + if($dom->loadHTML($chaine)) // et non load() tout court qui sert à charger du XML + { + //
+ // détecter
+ // puis extraire le lien et supprimer les balises autour + if(preg_match("#
#", $chaine)) + { + $nbFigures = $dom->getElementsByTagName('figure')->length; + + $j = 0; + for($i = 0; $i < $nbFigures ; $i++) // boucle foreach impossible, une
disparaît à chaque tour + { + $balisesFigure = $dom->getElementsByTagName('figure'); + $figure = $balisesFigure->item($j); // l'item 1 devient 0 au deuxième passage, etc + $parent = $figure->parentNode; + + if($figure->getAttribute("class") == 'media') + { + $oembed = $figure->getElementsByTagName('oembed'); // tableau d'une seule case + $div = $figure->getElementsByTagName('div'); // tableau d'une seule case + if($oembed->length > 0) // si taille = 0, c'est que la balise figure contient autre chose qu'une oembed + { + $lien = $oembed->item(0)->getAttribute('url') . ' '; // un espace pour ne pas coller deux adresses + $span = $dom->createElement('span', $lien); + $class = $dom->createAttribute('class'); + $class->value = 'media_embed'; + $span->appendChild($class); + $parent->replaceChild($span, $figure); + } + elseif($div->length > 0) // cette div créée par le ckeditor contient notre lien, plus bas se trouve une iframe + { + $lien = $div->item(0)->getAttribute('data-oembed-url') . ' '; + $span = $dom->createElement('span', $lien); + $class = $dom->createAttribute('class'); + $class->value = 'media_embed'; + $span->appendChild($class); + $parent->replaceChild($span, $figure); + } + else + { + $j++; // la balise
n'est pas supprimée, on incrémente l'indice du tableau pour ne pas boucler à l'infini + } + } + } + } + + // #", $chaine)) + { + $nbIframes = $dom->getElementsByTagName('iframe')->length; + + for($i = 0; $i < $nbIframes ; $i++) + { + $balisesIframe = $dom->getElementsByTagName('iframe'); + $iframe = $balisesIframe->item(0); + $parent = $iframe->parentNode; + + $lien = $iframe->getAttribute('src') . ' '; + $span = $dom->createElement('span', $lien); + $class = $dom->createAttribute('class'); + $class->value = 'media_embed'; + $span->appendChild($class); + $parent->replaceChild($span, $$iframe); + } + } + + // + // pour tout les sites multimedia, remplacer les balises par les liens nus + // en effet, en revenant dans l'éditeur les liens ne déclenchent pas automatiquement "l'embarquement" + // cliquer à la fin d'un lien et passer à la ligne a pour effet de créer des balises + // ça pose un problème parce que la bibliothèque "embera" ne gère pas les liens dans des balises + if(preg_match("#.*#", $chaine)) + { + $nbA = $dom->getElementsByTagName('a')->length; + + $j = 0; + for($i = 0; $i < $nbA ; $i++) + { + $balisesA = $dom->getElementsByTagName('a'); + $a = $balisesA->item($j); + $parent = $a->parentNode; + + $lien = $a->getAttribute('href') . ' '; + + // seules les liens pointant vers une des adresses de la liste sont concernés + if(preg_match("#" . $listeMedia . "#", $lien)) + { + $span = $dom->createElement('span', $lien); + $class = $dom->createAttribute('class'); + $class->value = 'media_embed'; + $span->appendChild($class); + $parent->replaceChild($span, $a); + } + else + { + $j++; // la balise n'est pas supprimée, on incrémente l'indice du tableau pour ne pas boucler à l'infini + } + } + } + + // pour nettoyer tous les warnings qu'on vient de produire et purifier nos fichiers log + libxml_clear_errors(); + // pour au contraire pouvoir les regarder, remplacer la ligne au dessus par celles en dessous + //~ $errors = libxml_get_errors(); + //~ var_dump($errors); + + // mettre à jour le DOM et enlever le DOCTYPE et les balises et + $chaine = $dom->saveHTML($dom); + $pattern = array ('##', '##', '##'); + $remplacement = array ('', '', ''); + $chaine = preg_replace($pattern, $remplacement, $chaine); + } + else + { + echo "Impossible de charger le HTML"; + } + + + + // détecter soundcloud + // normallement il n'y a rien à faire ici mais on sait jamais (une mise à jour du ckeditor?) + if(preg_match("#soundcloud.com#", $chaine)) + {} + + // détecter facebook (éventuellement fb.watch) + // je crois que ça va être compliqué ici + if(preg_match("#facebook.com|fb.watch#", $chaine)) + {} + + return($chaine); // = $contenu +} + + +// embarquement !! +function mediaEmbed($chaine) +{ + //require("../Embera/src/Autoloader.php"); + //$embera = new Embera\Embera(); + //$chaine = $embera->autoEmbed($chaine); + + $pattern = '#(.+)#'; + $remplacement = ''; + $chaine = preg_replace($pattern, $remplacement, $chaine); + + // requête oembed + //~ echo('

'); + //~ print_r($embera->getUrlData([ + //~ 'https://vimeo.com/374131624', + //~ 'https://www.flickr.com/photos/bees/8597283706/in/photostream', + //~ ])); + + return($chaine); // = $contenu +} diff --git a/controller/visitor.php b/controller/visitor.php index 6c6146b..6044676 100644 --- a/controller/visitor.php +++ b/controller/visitor.php @@ -1,131 +1,146 @@ -readAll()); // lourd - - // variables $css, $js, $header et $content - require('view/melaine.php'); - // HTML - require('view/template.php'); -} - -function discoVisitor() -{ - $page_actuelle = "discographie"; - $title = "Discographie"; - - // modèle - $Albums = new Album($page_actuelle); - $Albums->makeFileList(); - - // contenu de tous les fichiers JSON (= tableau de chaines) - $albumsJSON = $Albums->readAll(); - // noms des fichiers JSON - $albumNamesJSON = $Albums->fileList; - - $annees = []; - for($i = 0; $i < count($albumsJSON); $i++) - { - // une case supplémentaire contient le "albumCode" - $albumsJSON[$i][3] = pathinfo($albumNamesJSON[$i])['filename']; - - $annees[$i] = $albumsJSON[$i][1]; - } - - // tri d'un tableau multidimensionnel - array_multisort($annees, $albumsJSON); - - // lien vers le HTML ou ancre? - $lienAlbum = []; - $avecLien = []; - $linkDiscoChrono = []; - for($i = 0; $i < count($albumsJSON); $i++) - { - // cas particulier où le dossier "html" est vide - if(!empty($Albums->fileList)) - { - if(file_exists('data/discographie/html/' . $albumsJSON[$i][3] . '.html')) - { - $lienAlbum[$i] = 'page=album&album_name=' . $albumsJSON[$i][0] . '&album_code=' . $albumsJSON[$i][3]; - $avecLien[$i] = true; - $linkDiscoChrono[$i] = 'linkChrono'; // css - } - else - { - $lienAlbum[$i] = 'page=discographie#' . $albumsJSON[$i][0]; - $avecLien[$i] = false; - $linkDiscoChrono[$i] = 'noLinkChrono'; // css - } - $lienBoutonModif[$i] = 'page=discographie&action=edition&album_code=' . $albumsJSON[$i][3]; - } - } - - // variables $css, $js et $content - require('view/discographie.php'); - // fin de l'assemblage - require('view/template.php'); -} - -// page d'un album -function album($albumCode, $albumName) -{ - $page_actuelle = 'discographie'; - $title = $albumName; - - $album = Album::readOneHTML($albumCode); // simple, efficace - - // variables $css, $header et $content - require('view/album.php'); - // HTML - require('view/template.php'); -} - -function presse() -{} - -function ateliers() -{} - -function liens() -{} - -function peinture() -{} - -function archives() +fileList[$i]['date']); + //getdate($Albums->fileList[$i]['fileCode']); + + // ajout des données dans $Articles->fileList['content'] + if(!empty($Articles->fileList)) + { + $Articles->readAll(); + $Articles->fileList = array_reverse($Articles->fileList); + } + + // variables $css, $js, $header et $content + require('view/melaine.php'); + // HTML + require('view/template.php'); +} + +function discoVisitor() +{ + $page_actuelle = "discographie"; + $title = "Discographie"; + + // modèle + $Albums = new Album($page_actuelle); + $Albums->makeFileList(); + + // on récupère tout: JSON, HTML, noms et chemins des fichiers + $Albums->readAll(); + $Albums->fileList = array_reverse($Albums->fileList); + //var_dump($Albums->fileList); die(); + + + + // tri des albums avec l'année dans le .json + + // noms des fichiers JSON + //$albumNamesJSON = $Albums->fileList; + + //$annees = []; + for($i = 0; $i < $Albums->fileListCount; $i++) + { + // une case supplémentaire contient le "fileCode" + //$albumsJSON[$i][3] = pathinfo($albumNamesJSON[$i])['filename']; + //$albumsJSON[$i][3] = pathinfo($Albums->fileList[$i]['fileCode'])['filename']; + + //$annees[$i] = $albumsJSON[$i][1]; + } + //var_dump($annee); die(); + + // tri d'un tableau multidimensionnel + //array_multisort($annees, $albumsJSON); + + + + // lien vers le HTML ou ancre? + /*$lienAlbum = []; + $avecLien = []; + $linkDiscoChrono = [];*/ + for($i = 0; $i < $Albums->fileListCount; $i++) + { + if(file_exists('data/discographie/html/' . $Albums->fileList[$i]['fileCode'] . '.html')) + { + $lienAlbum[$i] = 'page=album&album_name=' . $Albums->fileList[$i]['titre'] . '&file_code=' . $Albums->fileList[$i]['fileCode']; + $avecLien[$i] = true; + $linkDiscoChrono[$i] = 'linkChrono'; // css + } + else + { + $lienAlbum[$i] = 'page=discographie#' . $Albums->fileList[$i]['titre']; + $avecLien[$i] = false; + $linkDiscoChrono[$i] = 'noLinkChrono'; // css + } + $lienBoutonModif[$i] = 'page=discographie&action=edition&file_code=' . $Albums->fileList[$i]['fileCode']; + } + + // variables $css, $js et $content + require('view/discographie.php'); + // fin de l'assemblage + require('view/template.php'); +} + +// page d'un album +function album($fileCode, $albumName) +{ + $page_actuelle = 'discographie'; + $title = $albumName; + + $album = Album::readOneHTML($fileCode); // simple, efficace + + // variables $css, $header et $content + require('view/album.php'); + // HTML + require('view/template.php'); +} + +function presse() +{} + +function ateliers() +{} + +function liens() +{} + +function peinture() +{} + +function archives() {} \ No newline at end of file diff --git a/erreur404.php b/erreur404.php index e7254c7..3059e70 100644 --- a/erreur404.php +++ b/erreur404.php @@ -1,24 +1,24 @@ -javascript:window.close()'; - -// Le fichier .htaccess est situé dans le répertoire racine du site (et non du serveur), il comporte cette ligne: -// ErrorDocument 404 /siteweb/erreur404.php -// en cas d'erreur 404, le serveur apache renverra le visiteur vers ce fichier (erreur404.php) -// -// ATTENTION depuis apache en version 2.4 le .htaccess est ignoré par défaut, il y a deux méthodes pour y remedier: -// - méthode facile et moins sécurisée: trouver la ligne "AllowOverride None" dans le fichier de conf d'apache et changer "None" par "All" -// - méthode sécurisée consistant à autoriser uniquement la directive ErrorDocument pour notre site, -// ne rien modifier et ajouter une nouvelle section comme ci-dessous: -// -// AllowOverrideList ErrorDocument -//javascript:window.close()'; + +// Le fichier .htaccess est situé dans le répertoire racine du site (et non du serveur), il comporte cette ligne: +// ErrorDocument 404 /siteweb/erreur404.php +// en cas d'erreur 404, le serveur apache renverra le visiteur vers ce fichier (erreur404.php) +// +// ATTENTION depuis apache en version 2.4 le .htaccess est ignoré par défaut, il y a deux méthodes pour y remedier: +// - méthode facile et moins sécurisée: trouver la ligne "AllowOverride None" dans le fichier de conf d'apache et changer "None" par "All" +// - méthode sécurisée consistant à autoriser uniquement la directive ErrorDocument pour notre site, +// ne rien modifier et ajouter une nouvelle section comme ci-dessous: +// +// AllowOverrideList ErrorDocument +// contrôleur -// | -// v -// vue ---> envoi de la page au client -// -// pour retrouver quelque chose dans le code, il suffit de suivre un chemin linéaire dont le départ est ici -// pas de croisement, pas de marche arrière, impossible de se perdre! -// autre avantage: les chemins sont tous relatifs et sont toujours bons, parce que tous les fichiers sont "inclus" dans index.php -// -// Les dossiers: -// -> controller: traitements en PHP pur -// -> model: interface entre PHP et données, on y trouve le SQL ou la manipulation de fichiers textes, le PHP y est orienté objet -// -> view: le PHP y produit le HTML, il construit les pages, -// le mélange PHP/HTML étant très vite assez dégueulasse, le HTML figé est placé à part dans le fichier view/template.php -// -> public: contient CSS, JS, images, polices, medias, etc, il est utilisé par template.php -// -> lib: les bibliothèques, on y trouve en particulier le ckeditor qui occupe une place centrale dans le projet, et aussi htmLawed qui nettoie le HTML produit par l'éditeur (failles XSS) -// -> data: comme son nom l'indique -// -// Quelques fichiers spéciaux (indépendants de index.php): -// - imageAJAX.php traite les requêtes AJAX envoyées par l'éditeur -// - .htaccess pour la sécurité (configurer le httpd.conf d'apache peut être nécessaire) -// - erreur404.php (fonctionne avec .htaccess) -// -// le dossier data et son contenu sont "normalement" autorisés en écriture pour deux raisons: -// - placer le contenu pré-existant avec son client FTP -// - autoriser PHP (sinon, c'est comme si on avait un site statique) -// -> pour pouvoir installer le site en n'ayant qu'à modifier les droits du dossier data uniquement et éviter les situations pénibles ou l'utilisateur est bloqué sans rien comprendre, on pourra créer ou utiliser des sauvegardes au format ZIP depuis une page spéciale accessible avec le compte admin - -// sessions, penser aux attaques CSRF (cross-site request forgery): -// ça consite à faire qu'un utilisateur connecté avec une session envoie malgré lui une requête GET ou POST qu'un hacker aura cachée par exemple dans une fausse image clicable -// - solution: faire qu'un GET seul dans une session ne suffise pas à effectuer une action (les GET ne doivent servir qu'à afficher la bonne page), une attaque sur un POST est possible aussi mais plus difficile et nécessite d'injecter du javascript -// - on peut demander à l'utilisateur une vérification supplémentaire avant chaque action, mais c'est plutôt chiant -// - il y a la méthode des jetons, "nonces" et horodatage -// - vérifier le "référent", c'est à dire l'URL de la page d'où vient normallement la requête -// infos: https://fr.wikipedia.org/wiki/Cross-site_request_forgery -session_start(); - -if(!empty($_SESSION['erreur'])) -{ - echo(''); - unset($_SESSION['erreur']); -} - -// au premier démarrage du site -// l'explication des éventuels problèmes de droits en lecture/écriture est à chercher ici: -require('controller/installation.php'); -require('controller/password.php'); -installation(); - - -// traitement des requêtes AJAX -if(isset($_GET['action']) && isset($_GET['page']) && $_GET['action'] == 'upload_image') -{ - // et une backdoor de fermée! - if(!isset($_SESSION['admin']) || $_SESSION['admin'] != 1 - || !isset($_FILES['upload']) || empty($_FILES['upload'])) - { - // sans effet? - header('Location: index.php?erreur=image_ajax'); - } - else - { - require('model/Image.php'); - // paramètre "true" parce qu'on reçoit une requête AJAX - $Image = new Image(true); - $Image->upload(); - } - exit; // arrêt ici !! -} - - -// traitement des POST du ckeditor -// la fonction submitCKeditor est "autonome", elle n'affiche rien puis redirige sans GET -if(isset($_SESSION['admin']) && $_SESSION['admin'] == 1 - && isset($_GET['action']) && $_GET['action'] == 'submit' - // trois possibilités: on a un contenu HTML ou JSON ou les deux - && ((isset($_POST['contenu']) && $_POST['contenu'] != '') - || (isset($_POST['titre']) && isset($_POST['annee'])))) -{ - require('controller/ckeditor.php'); // traitement du POST - require('controller/Security.php'); // sécurité des chaines - require('model/Page.php'); // modèle - if($_GET['page'] == 'discographie') - { - require('model/Album.php'); - } - - // modification - if(isset($_SESSION['target'])) - { - submitCKeditor($_SESSION['target']); - } - // nouvel article - else - { - submitCKeditor(''); - } - - // nettoyage - unset($_SESSION['nomFichier']); - unset($_GET['action']); - unset($_POST['contenu']); - header('Location: index.php?page=' . $_GET['page']); - exit(); -} - -// déconnexion: nettoyer et recharger la page -if(isset($_GET['action']) && isset($_GET['page'])) -{ - if($_GET['action'] == "deconnexion") - { - $_SESSION['admin'] = 0; - header('Location: index.php?page=' . $_GET['page']); - } -} - -// contrôleur des pages en mode visiteur (= lecture uniquement) -// appelé tout le temps parce que certaines pages (accueil, menu) n'ont pas de version "admin" => à améliorer -require('controller/visitor.php'); - -// utile pour presque toutes les pages -if(isset($_GET['page']) && $_GET['page'] != 'menu') -{ - require('model/Page.php'); - if($_GET['page'] == 'discographie' || $_GET['page'] == 'album') - { - require('model/Album.php'); - } -} - -// contrôleur des pages en mode admin -if(isset($_SESSION['admin']) && $_SESSION['admin'] == 1) -{ - // contrôleur en mode admin (= lecture/écriture) - require('controller/admin.php'); -} -else -{ - // initialisation - $_SESSION['admin'] = 0; -} - - -// page du site demandée -if(isset($_GET['page'])) -{ - // page d'accueil - if($_GET['page'] == 'accueil') - { - accueil(); - } - // page menu - elseif($_GET['page'] == 'menu') - { - menu(); - } - // page melaine - elseif($_GET['page'] == 'melaine') - { - // Si ouverture de l'éditeur pour modification: - // le lien utilisé pour ouvrir l'éditeur contient un GET avec le numéro de l'article, mais pas le nom du fichier que le visiteur ne doit pas voir - // GET, POST, cookies => navigation (utiles au visiteur) - // sessions => action - // c'est ici qu'on passe des GET aux sessions - // on associe maintenant le numéro de l'article et le nom du fichier, l'article déjà existant inséré dans l'éditeur DOIT être celui qui sera modifié - - if($_SESSION['admin'] == 1 && isset($_GET['action']) && $_GET['action'] == 'editor') - { - // modification - if(isset($_GET['article']) && is_numeric($_GET['article']) && $_GET['article'] > 0) - { - melaineEdit($_GET['article'], 0); - } - // nouvel article - else - { - // par sécurité - unset($_SESSION['nomFichier']); - - melaineEdit(0, 0); - } - } - // suppression - else if($_SESSION['admin'] == 1 && isset($_GET['action']) && $_GET['action'] == 'suppression') - { - melaineEdit($_GET['article'], 1); - } - else - { - melaineVisitor(); - } - } - // page discographie - elseif($_GET['page'] == 'discographie') - { - if($_SESSION['admin'] == 1 && isset($_GET['action']) && $_GET['action'] == 'edition') - { - // modification - if(isset($_GET['album_code']) || !empty($_GET['album_code'])) - { - discoEdit($_GET['album_code'], 0); - } - // nouvel article - else - { - // par sécurité - $_GET['album_code'] = ''; - unset($_SESSION['nomFichier']); - - discoEdit('', 0); - } - } - // suppression - else if($_SESSION['admin'] == 1 && isset($_GET['action']) && $_GET['action'] == 'suppression') - { - discoEdit($_GET['album_code'], 1); - } - else - { - discoVisitor(); - } - } - // page d'un album de la discographie - elseif($_GET['page'] == 'album') - { - album($_GET['album_code'], $_GET['album_name']); - // page visiteur uniquement - } - // page connexion - elseif($_GET['page'] == 'connexion') - { - connect(); - } - // $_GET['page'] = n'importe quoi! - else - { - menu(); - } -} - - -// actions en mode admin, recharger une des pages principales -elseif($_SESSION['admin'] == 1 && isset($_GET['action'])) -{ - if($_GET['action'] == 'modif_mdp') - { - //changePassword($secret); - changePassword(); - } - // extraction du contenu du dossier data - else if($_GET['action'] == 'extraction') - { - require('controller/backup.php'); - extraction($_GET['from']); - } - // l'inverse, insertion des données d'une sauvegarde - else if($_GET['action'] == 'insertion') - { - require('controller/backup.php'); - insertion($_GET['from']); - } - else - { - accueil(); - } -} - -// renvoi ici par le .htaccess si lien mort ou sans http:// au début -elseif(isset($_GET['erreur'])) -{ - //if($_GET['erreur'] == 404) - //{ - // echo('

ERREUR 404
Le lien sur lequel vous avez cliqué pointe vers un emplacement introuvable.
- // Ce n\'est pas votre faute. Vous pouvez éventuellement rechercher la page demandée avec votre moteur de recherche ou prévenir le responsable du site.

'); - // echo('

Retour au site

'); - //} - //else - //{ - accueil(); - //} -} - -// page d'accueil (adresse sans GET valable) -else -{ - accueil(); -} +alert(\'' . $_SESSION['erreur'] . '\');'); + unset($_SESSION['erreur']); +} + +// au premier démarrage du site +// l'explication des éventuels problèmes de droits en lecture/écriture est à chercher ici: +require('controller/installation.php'); +require('controller/password.php'); +installation(); + + +// traitement des requêtes AJAX +if(isset($_GET['action']) && isset($_GET['page']) && $_GET['action'] == 'upload_image') +{ + // sécurité !! + if(!isset($_SESSION['admin']) || $_SESSION['admin'] != 1 + || !isset($_FILES['upload']) || empty($_FILES['upload'])) + { + // sans effet? + header('Location: index.php?erreur=image_ajax'); + } + else + { + require('model/Image.php'); + // paramètre "true" parce qu'on reçoit une requête AJAX + $Image = new Image(true); + $Image->upload(); + } + exit; // stop !! +} + + +// traitement des POST du ckeditor +// la fonction submitCKeditor est "autonome", elle n'affiche rien puis redirige sans GET +if(isset($_SESSION['admin']) && $_SESSION['admin'] == 1 + && isset($_GET['action']) && $_GET['action'] == 'submit' + // trois possibilités: on a un contenu HTML ou JSON ou les deux + && ((isset($_POST['contenu']) && $_POST['contenu'] != '') + || (isset($_POST['titre']) && isset($_POST['annee'])))) +{ + require('controller/ckeditor.php'); // traitement du POST + require('controller/Security.php'); // sécurité des chaines + require('model/Article.php'); + + if($_GET['page'] == 'discographie') + { + require('model/Album.php'); + } + + submitCKeditor(); + // modification + /*if(isset($_SESSION['target'])) + { + submitCKeditor($_SESSION['target']); + } + // nouvel article + else + { + submitCKeditor(''); // $target = '' + }*/ +} +else +{ + unset($_SESSION['target']); +} + + +// déconnexion +if(isset($_GET['action']) && isset($_GET['page'])) +{ + if($_GET['action'] == "deconnexion") + { + // on nettoie et on recharge + $_SESSION['admin'] = 0; + header('Location: index.php?page=' . $_GET['page']); + } +} + + + +// construction des pages + +// mode visiteur (sans l'éditeur) +require('controller/visitor.php'); + +// modèle +if(isset($_GET['page']) && $_GET['page'] != 'menu') +{ + require('model/Article.php'); + if($_GET['page'] == 'discographie' || $_GET['page'] == 'album') + { + require('model/Album.php'); + } +} + + +// contrôleur des pages en mode admin +if(isset($_SESSION['admin']) && $_SESSION['admin'] == 1) +{ + // contrôleur en mode admin (= lecture/écriture) + require('controller/admin.php'); +} +else +{ + // initialisation + $_SESSION['admin'] = 0; +} + + +// page du site demandée +if(isset($_GET['page'])) +{ + // page d'accueil + if($_GET['page'] == 'accueil') + { + accueil(); + } + // page menu + elseif($_GET['page'] == 'menu') + { + menu(); + } + // page melaine + elseif($_GET['page'] == 'melaine') + { + // cas de l'ouverture de l'éditeur pour modification: + // on récupère le GET qui sera placé dans une session + // GET, POST, cookies => navigation (utiles au visiteur) + // sessions => action + // un seul utilisateur peut modifier les données donc ça va, + // avec plusieurs utilisateurs, il faut plus de sécurités + + // rédaction + if($_SESSION['admin'] == 1 && isset($_GET['action']) && $_GET['action'] == 'editor') + { + //echo($_GET['file_code']); die(); + + // modification + if(isset($_GET['file_code']) || !empty($_GET['file_code'])) + { + melaineEdit($_GET['file_code'], 0); + } + // nouvel article + else + { + melaineEdit('', 0); + } + } + // suppression + else if($_SESSION['admin'] == 1 && isset($_GET['action']) && $_GET['action'] == 'suppression') + { + melaineEdit($_GET['file_code'], 1); + } + else + { + melaineVisitor(); + } + } + // page discographie + elseif($_GET['page'] == 'discographie') + { + // rédaction + if($_SESSION['admin'] == 1 && isset($_GET['action']) && $_GET['action'] == 'edition') + { + // modification + if(isset($_GET['file_code']) || !empty($_GET['file_code'])) + { + discoEdit($_GET['file_code'], 0); + } + // nouvel article + else + { + // par sécurité + unset($_GET['file_code']); + + discoEdit('', 0); + } + } + // suppression + else if($_SESSION['admin'] == 1 && isset($_GET['action']) && $_GET['action'] == 'suppression') + { + discoEdit($_GET['file_code'], 1); + } + else + { + discoVisitor(); + } + } + // page d'un album de la discographie + // page visiteur uniquement + elseif($_GET['page'] == 'album') + { + album($_GET['file_code'], $_GET['album_name']); + } + // page connexion + elseif($_GET['page'] == 'connexion') + { + connect(); + } + // $_GET['page'] = n'importe quoi! + else + { + menu(); + } +} + + +// actions en mode admin, recharger une des pages principales +elseif($_SESSION['admin'] == 1 && isset($_GET['action'])) +{ + if($_GET['action'] == 'modif_mdp') + { + //changePassword($secret); + changePassword(); + } + // extraction du contenu du dossier data + else if($_GET['action'] == 'extraction') + { + require('controller/backup.php'); + extraction($_GET['from']); + } + // l'inverse, insertion des données d'une sauvegarde + else if($_GET['action'] == 'insertion') + { + require('controller/backup.php'); + insertion($_GET['from']); + } + else + { + accueil(); + } +} + +// renvoi ici par le .htaccess si lien mort ou sans http:// au début +elseif(isset($_GET['erreur'])) +{ + //if($_GET['erreur'] == 404) + //{ + // echo('

ERREUR 404
Le lien sur lequel vous avez cliqué pointe vers un emplacement introuvable.
+ // Ce n\'est pas votre faute. Vous pouvez éventuellement rechercher la page demandée avec votre moteur de recherche ou prévenir le responsable du site.

'); + // echo('

Retour au site

'); + //} + //else + //{ + accueil(); + //} +} + +// page d'accueil (adresse sans GET valable) +else +{ + accueil(); +} diff --git a/lib/HtmlFormatter.php b/lib/HtmlFormatter.php index 89605ac..0101f1b 100644 --- a/lib/HtmlFormatter.php +++ b/lib/HtmlFormatter.php @@ -1,126 +1,126 @@ -)/U', $html, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE); - $dom = self::parseDom($elements); - - $indent = 0; - $output = array(); - foreach ($dom as $index => $element) - { - if ($element['opening']) - { - $output[] = "\n".str_repeat($indentWith, $indent).trim($element['content']); - - // make sure that only the elements who have not been blacklisted are being indented - if ( ! in_array($element['type'], explode(',', $tagsWithoutIndentation))) - { - ++$indent; - } - } - else if ($element['standalone']) - { - $output[] = "\n".str_repeat($indentWith, $indent).trim($element['content']); - } - else if ($element['closing']) - { - --$indent; - $lf = "\n".str_repeat($indentWith, abs($indent)); - if (isset($dom[$index - 1]) && $dom[$index - 1]['opening']) - { - $lf = ''; - } - $output[] = $lf.trim($element['content']); - } - else if ($element['text']) - { - // $output[] = "\n".str_repeat($indentWith, $indent).trim($element['content']); - $output[] = "\n".str_repeat($indentWith, $indent).preg_replace('/ [ \t]*/', ' ', $element['content']); - } - else if ($element['comment']) - { - $output[] = "\n".str_repeat($indentWith, $indent).trim($element['content']); - } - } - - return trim(implode('', $output)); - } - - /** - * Parses an array of HTML tokens and adds basic information about about the type of - * tag the token represents. - * - * @param Array $elements Array of HTML tokens (tags and text tokens). - * @return Array HTML elements with extra information. - */ - public static function parseDom(Array $elements) - { - $dom = array(); - foreach ($elements as $element) - { - $isText = false; - $isComment = false; - $isClosing = false; - $isOpening = false; - $isStandalone = false; - - $currentElement = trim($element); - - // comment - if (strpos($currentElement, '$/', $currentElement)) - { - $isStandalone = true; - } - // normal opening tag - else if (strpos($currentElement, '<') === 0) - { - $isOpening = true; - } - // text - else - { - $isText = true; - } - - $dom[] = array( - 'text' => $isText, - 'comment' => $isComment, - 'closing' => $isClosing, - 'opening' => $isOpening, - 'standalone' => $isStandalone, - 'content' => $element, - 'type' => preg_replace('/^<\/?(\w+)[ >].*$/U', '$1', $element) - ); - } - return $dom; - } -} +)/U', $html, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE); + $dom = self::parseDom($elements); + + $indent = 0; + $output = array(); + foreach ($dom as $index => $element) + { + if ($element['opening']) + { + $output[] = "\n".str_repeat($indentWith, $indent).trim($element['content']); + + // make sure that only the elements who have not been blacklisted are being indented + if ( ! in_array($element['type'], explode(',', $tagsWithoutIndentation))) + { + ++$indent; + } + } + else if ($element['standalone']) + { + $output[] = "\n".str_repeat($indentWith, $indent).trim($element['content']); + } + else if ($element['closing']) + { + --$indent; + $lf = "\n".str_repeat($indentWith, abs($indent)); + if (isset($dom[$index - 1]) && $dom[$index - 1]['opening']) + { + $lf = ''; + } + $output[] = $lf.trim($element['content']); + } + else if ($element['text']) + { + // $output[] = "\n".str_repeat($indentWith, $indent).trim($element['content']); + $output[] = "\n".str_repeat($indentWith, $indent).preg_replace('/ [ \t]*/', ' ', $element['content']); + } + else if ($element['comment']) + { + $output[] = "\n".str_repeat($indentWith, $indent).trim($element['content']); + } + } + + return trim(implode('', $output)); + } + + /** + * Parses an array of HTML tokens and adds basic information about about the type of + * tag the token represents. + * + * @param Array $elements Array of HTML tokens (tags and text tokens). + * @return Array HTML elements with extra information. + */ + public static function parseDom(Array $elements) + { + $dom = array(); + foreach ($elements as $element) + { + $isText = false; + $isComment = false; + $isClosing = false; + $isOpening = false; + $isStandalone = false; + + $currentElement = trim($element); + + // comment + if (strpos($currentElement, '$/', $currentElement)) + { + $isStandalone = true; + } + // normal opening tag + else if (strpos($currentElement, '<') === 0) + { + $isOpening = true; + } + // text + else + { + $isText = true; + } + + $dom[] = array( + 'text' => $isText, + 'comment' => $isComment, + 'closing' => $isClosing, + 'opening' => $isOpening, + 'standalone' => $isStandalone, + 'content' => $element, + 'type' => preg_replace('/^<\/?(\w+)[ >].*$/U', '$1', $element) + ); + } + return $dom; + } +} diff --git a/lib/ckeditor5/LICENSE.md b/lib/ckeditor5/LICENSE.md index 95eabee..868e64d 100644 --- a/lib/ckeditor5/LICENSE.md +++ b/lib/ckeditor5/LICENSE.md @@ -1,49 +1,49 @@ -Software License Agreement -========================== - -Copyright (c) 2014-2021, CKSource - Frederico Knabben. All rights reserved. - -Online builder code samples are licensed under the terms of the MIT License (see Appendix A): - - http://en.wikipedia.org/wiki/MIT_License - -CKEditor 5 collaboration features are only available under a commercial license. [Contact us](https://ckeditor.com/contact/) for more details. - -Free 30-days trials of CKEditor 5 collaboration features are available: - * https://ckeditor.com/collaboration/ - Real-time collaboration (with all features). - * https://ckeditor.com/collaboration/comments/ - Inline comments feature (without real-time collaborative editing). - * https://ckeditor.com/collaboration/track-changes/ - Track changes feature (without real-time collaborative editing). - -Trademarks ----------- - -CKEditor is a trademark of CKSource - Frederico Knabben. All other brand -and product names are trademarks, registered trademarks or service -marks of their respective holders. - ---- - -Appendix A: The MIT License ---------------------------- - -The MIT License (MIT) - -Copyright (c) 2014-2021, CKSource - Frederico Knabben - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. +Software License Agreement +========================== + +Copyright (c) 2014-2021, CKSource - Frederico Knabben. All rights reserved. + +Online builder code samples are licensed under the terms of the MIT License (see Appendix A): + + http://en.wikipedia.org/wiki/MIT_License + +CKEditor 5 collaboration features are only available under a commercial license. [Contact us](https://ckeditor.com/contact/) for more details. + +Free 30-days trials of CKEditor 5 collaboration features are available: + * https://ckeditor.com/collaboration/ - Real-time collaboration (with all features). + * https://ckeditor.com/collaboration/comments/ - Inline comments feature (without real-time collaborative editing). + * https://ckeditor.com/collaboration/track-changes/ - Track changes feature (without real-time collaborative editing). + +Trademarks +---------- + +CKEditor is a trademark of CKSource - Frederico Knabben. All other brand +and product names are trademarks, registered trademarks or service +marks of their respective holders. + +--- + +Appendix A: The MIT License +--------------------------- + +The MIT License (MIT) + +Copyright (c) 2014-2021, CKSource - Frederico Knabben + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/lib/ckeditor5/README.md b/lib/ckeditor5/README.md index fc116c4..c5438a7 100644 --- a/lib/ckeditor5/README.md +++ b/lib/ckeditor5/README.md @@ -1,68 +1,68 @@ -# CKEditor 5 editor generated with the online builder - -This repository presents a CKEditor 5 editor build generated by the [Online builder tool](https://ckeditor.com/ckeditor-5/online-builder) - -## Quick start - -1. Open the `sample/index.html` page in the browser. - -If you picked the real-time collaboration plugins: - -2. Fill the dialog with correct token, websocket and upload URL endpoints. If you do not have these yet or do not know their meaning, [contact us](https://ckeditor.com/contact/). - -3. Copy the URL and share it or paste in another tab to enjoy real-time collaborative editing. - -If you picked the non-real-time collaboration plugins: - -2. Fill the prompt with the license key. If you do not have the license key yet [contact us](https://ckeditor.com/contact/). - -## Configuring build - -Changes like changing toolbar items, changing order of icons or customizing plugin configurations should be relatively easy to make. Open the `sample/index.html` file and edit the script that initialized the CKEditor 5. Save the file and refresh the browser. That's all. - -*Note:* If you have any problems with browser caching use the `Ctrl + R` or `Cmd + R` shortcut depending on your system. - -However if you want to remove or add a plugin to the build you need to follow the next step of this guide. - -Note that it is also possible to go back to the [Online builder tool](https://ckeditor.com/ckeditor-5/online-builder) and pick other set of plugins. But we encourage you to try the harder way and to learn the principles of Node.js and CKEditor 5 ecosystems that will allow you to do more cool things in the future! - -### Installation - -In order to rebuild the application you need to install all dependencies first. To do it, open the terminal in the project directory and type: - -``` -npm install -``` - -Make sure that you have the `node` and `npm` installed first. If not, then follow the instructions on the [Node.js documentation page](https://nodejs.org/en/). - -### Adding or removing plugins - -Now you can install additional plugin in the build. Just follow the [Adding a plugin to an editor tutorial](https://ckeditor.com/docs/ckeditor5/latest/builds/guides/integration/installing-plugins.html#adding-a-plugin-to-an-editor) - -### Rebuilding editor - -If you have already done the [Installation](#installation) and [Adding or removing plugins](#adding-or-removing-plugins) steps, you're ready to rebuild the editor by running the following command: - -``` -npm run build -``` - -This will build the CKEditor 5 to the `build` directory. You can open your browser and you should be able to see the changes you've made in the code. If not, then try to refresh also the browser cache by typing `Ctrl + R` or `Cmd + R` depending on your system. - -## What's next? - -Follow the guides available on https://ckeditor.com/docs/ckeditor5/latest/framework/index.html and enjoy the document editing. - -## FAQ -| Where is the place to report bugs and feature requests? - -You can create an issue on https://github.com/ckeditor/ckeditor5/issues including the build id - `aovpauzd25hi-dqoovaepm42n`. Make sure that the question / problem is unique, please look for a possibly asked questions in the search box. Duplicates will be closed. - -| Where can I learn more about the CKEditor 5 framework? - -Here: https://ckeditor.com/docs/ckeditor5/latest/framework/ - -| Is it possible to use online builder with common frameworks like React, Vue or Angular? - -Not yet, but it these integrations will be available at some point in the future. +# CKEditor 5 editor generated with the online builder + +This repository presents a CKEditor 5 editor build generated by the [Online builder tool](https://ckeditor.com/ckeditor-5/online-builder) + +## Quick start + +1. Open the `sample/index.html` page in the browser. + +If you picked the real-time collaboration plugins: + +2. Fill the dialog with correct token, websocket and upload URL endpoints. If you do not have these yet or do not know their meaning, [contact us](https://ckeditor.com/contact/). + +3. Copy the URL and share it or paste in another tab to enjoy real-time collaborative editing. + +If you picked the non-real-time collaboration plugins: + +2. Fill the prompt with the license key. If you do not have the license key yet [contact us](https://ckeditor.com/contact/). + +## Configuring build + +Changes like changing toolbar items, changing order of icons or customizing plugin configurations should be relatively easy to make. Open the `sample/index.html` file and edit the script that initialized the CKEditor 5. Save the file and refresh the browser. That's all. + +*Note:* If you have any problems with browser caching use the `Ctrl + R` or `Cmd + R` shortcut depending on your system. + +However if you want to remove or add a plugin to the build you need to follow the next step of this guide. + +Note that it is also possible to go back to the [Online builder tool](https://ckeditor.com/ckeditor-5/online-builder) and pick other set of plugins. But we encourage you to try the harder way and to learn the principles of Node.js and CKEditor 5 ecosystems that will allow you to do more cool things in the future! + +### Installation + +In order to rebuild the application you need to install all dependencies first. To do it, open the terminal in the project directory and type: + +``` +npm install +``` + +Make sure that you have the `node` and `npm` installed first. If not, then follow the instructions on the [Node.js documentation page](https://nodejs.org/en/). + +### Adding or removing plugins + +Now you can install additional plugin in the build. Just follow the [Adding a plugin to an editor tutorial](https://ckeditor.com/docs/ckeditor5/latest/builds/guides/integration/installing-plugins.html#adding-a-plugin-to-an-editor) + +### Rebuilding editor + +If you have already done the [Installation](#installation) and [Adding or removing plugins](#adding-or-removing-plugins) steps, you're ready to rebuild the editor by running the following command: + +``` +npm run build +``` + +This will build the CKEditor 5 to the `build` directory. You can open your browser and you should be able to see the changes you've made in the code. If not, then try to refresh also the browser cache by typing `Ctrl + R` or `Cmd + R` depending on your system. + +## What's next? + +Follow the guides available on https://ckeditor.com/docs/ckeditor5/latest/framework/index.html and enjoy the document editing. + +## FAQ +| Where is the place to report bugs and feature requests? + +You can create an issue on https://github.com/ckeditor/ckeditor5/issues including the build id - `aovpauzd25hi-dqoovaepm42n`. Make sure that the question / problem is unique, please look for a possibly asked questions in the search box. Duplicates will be closed. + +| Where can I learn more about the CKEditor 5 framework? + +Here: https://ckeditor.com/docs/ckeditor5/latest/framework/ + +| Is it possible to use online builder with common frameworks like React, Vue or Angular? + +Not yet, but it these integrations will be available at some point in the future. diff --git a/lib/ckeditor5/build/ckeditor.js b/lib/ckeditor5/build/ckeditor.js index 6de3d4e..3b01c40 100644 --- a/lib/ckeditor5/build/ckeditor.js +++ b/lib/ckeditor5/build/ckeditor.js @@ -1,6 +1,6 @@ -/*! - * @license Copyright (c) 2003-2021, CKSource - Frederico Knabben. All rights reserved. - * For licensing, see LICENSE.md. - */ -(function(t){const e=t["fr"]=t["fr"]||{};e.dictionary=Object.assign(e.dictionary||{},{"%0 of %1":"%0 sur %1","Align center":"Centrer","Align left":"Aligner à gauche","Align right":"Aligner à droite","Align table to the left":"Aligner le tableau à gauche","Align table to the right":"Aligner le tableau à droite",Alignment:"Alignement",Aquamarine:"Bleu vert",Background:"Fond",Big:"Grand",Black:"Noir","Block quote":"Citation",Blue:"Bleu","Blue marker":"Marqueur bleu",Bold:"Gras",Border:"Bordure","Bulleted List":"Liste à puces",Cancel:"Annuler","Center table":"Centrer le tableau ","Centered image":"Image centrée","Change image text alternative":"Changer le texte alternatif à l’image","Choose heading":"Choisir l'en-tête",Color:"Couleur","Color picker":"Pipette à couleurs",Column:"Colonne",Dashed:"Tirets",Default:"Par défaut","Delete column":"Supprimer la colonne","Delete row":"Supprimer la ligne","Dim grey":"Gris pâle",Dimensions:"Dimensions","Document colors":"Couleurs du document",Dotted:"Pointillés",Double:"Double",Downloadable:"Fichier téléchargeable","Dropdown toolbar":"Barre d'outils dans un menu déroulant","Edit block":"Modifier le bloc","Edit link":"Modifier le lien","Edit source":"Modifier la source","Editor toolbar":"Barre d'outils de l'éditeur","Empty snippet content":"Aucun contenu pour ce fragment de code","Enter image caption":"Saisir la légende de l’image","Font Color":"Couleur de police","Font Family":"Police","Font Size":"Taille de police","Full size image":"Image taille réelle",Green:"Vert","Green marker":"Marqueur vert","Green pen":"Crayon vert",Grey:"Gris",Groove:"Rainuré","Header column":"Colonne d'entête","Header row":"Ligne d'entête",Heading:"En-tête","Heading 1":"Titre 1","Heading 2":"Titre 2","Heading 3":"Titre 3","Heading 4":"Titre 4","Heading 5":"Titre 5","Heading 6":"Titre 6",Height:"Hauteur",Highlight:"Surlignage","Horizontal line":"Ligne horizontale","HTML snippet":"Code HTML",Huge:"Enorme","Image toolbar":"Barre d'outils des images","image widget":"Objet image",Insert:"Insérer","Insert column left":"Insérer une colonne à gauche","Insert column right":"Insérer une colonne à droite","Insert HTML":"Insérer du code HTML","Insert image":"Insérer une image","Insert image via URL":"Insérer une image à partir d'une URL","Insert paragraph after block":"Insérer du texte après ce bloc","Insert paragraph before block":"Insérer du texte avant ce bloc","Insert row above":"Insérer une ligne au-dessus","Insert row below":"Insérer une ligne en-dessous","Insert table":"Insérer un tableau",Inset:"Relief intérieur",Italic:"Italique",Justify:"Justifier","Left aligned image":"Image alignée à gauche","Light blue":"Bleu clair","Light green":"Vert clair","Light grey":"Gris clair",Link:"Lien","Link image":"Lien d'image","Link URL":"URL du lien","Merge cell down":"Fusionner la cellule en-dessous","Merge cell left":"Fusionner la cellule à gauche","Merge cell right":"Fusionner la cellule à droite","Merge cell up":"Fusionner la cellule au-dessus","Merge cells":"Fusionner les cellules",Next:"Suivant","No preview available":"Aucun aperçu disponible",None:"Aucun","Numbered List":"Liste numérotée","Open in a new tab":"Ouvrir dans un nouvel onglet","Open link in new tab":"Ouvrir le lien dans un nouvel onglet",Orange:"Orange",Outset:"Relief extérieur",Paragraph:"Paragraphe","Paste raw HTML here...":"Collez le code HTML brut ici...","Pink marker":"Marqueur rose",Previous:"Précedent",Purple:"Violet",Red:"Rouge","Red pen":"Crayon rouge",Redo:"Restaurer","Remove color":"Enlever la couleur","Remove highlight":"Enlever le surlignage","Rich Text Editor":"Éditeur de texte enrichi","Rich Text Editor, %0":"Éditeur de texte enrichi, %0",Ridge:"Relief","Right aligned image":"Image alignée à droite",Row:"Ligne",Save:"Enregistrer","Save changes":"Enregistrer les changements","Saving changes":"Enregistrement des modifications","Select all":"Sélectionner tout","Select column":"Sélectionner la colonne","Select row":"Sélectionner la ligne","Show more items":"Montrer plus d'éléments","Side image":"Image latérale",Small:"Petit",Solid:"Continu","Split cell horizontally":"Scinder la cellule horizontalement","Split cell vertically":"Scinder la cellule verticalement",Style:"Style","Table alignment toolbar":"Barre d'outils pour modifier l'alignement du tableau","Table properties":"Propriétés du tableau","Table toolbar":"Barre d'outils des tableaux","Text alignment":"Alignement du texte","Text alignment toolbar":"Barre d'outils d'alignement du texte","Text alternative":"Texte alternatif","Text highlight toolbar":"Barre d'outils du surlignage",'The color is invalid. Try "#FF0000" or "rgb(255,0,0)" or "red".':'La couleur est invalide. Essayez "#FF0000" ou "rgb(255,0,0)" ou "red".','The value is invalid. Try "10px" or "2em" or simply "2".':'La valeur est invalide. Essayez "10px" ou "2em" ou simplement "2".',"This link has no URL":"Ce lien n'a pas d'URL",Tiny:"Minuscule","To-do List":"Liste de tâches",Turquoise:"Turquoise",Underline:"Souligné",Undo:"Annuler",Unlink:"Supprimer le lien",Update:"Modifier","Update image URL":"Modifier l'URL de l'image","Upload failed":"Échec de l'envoi","Upload in progress":"Téléchargement en cours",White:"Blanc","Widget toolbar":"Barre d'outils du widget",Width:"Largeur",Yellow:"Jaune","Yellow marker":"Marqueur jaune"});e.getPluralForm=function(t){return t>1}})(window.CKEDITOR_TRANSLATIONS||(window.CKEDITOR_TRANSLATIONS={}));(function t(e,n){if(typeof exports==="object"&&typeof module==="object")module.exports=n();else if(typeof define==="function"&&define.amd)define([],n);else if(typeof exports==="object")exports["ClassicEditor"]=n();else e["ClassicEditor"]=n()})(window,(function(){return function(t){var e={};function n(o){if(e[o]){return e[o].exports}var i=e[o]={i:o,l:false,exports:{}};t[o].call(i.exports,i,i.exports,n);i.l=true;return i.exports}n.m=t;n.c=e;n.d=function(t,e,o){if(!n.o(t,e)){Object.defineProperty(t,e,{enumerable:true,get:o})}};n.r=function(t){if(typeof Symbol!=="undefined"&&Symbol.toStringTag){Object.defineProperty(t,Symbol.toStringTag,{value:"Module"})}Object.defineProperty(t,"__esModule",{value:true})};n.t=function(t,e){if(e&1)t=n(t);if(e&8)return t;if(e&4&&typeof t==="object"&&t&&t.__esModule)return t;var o=Object.create(null);n.r(o);Object.defineProperty(o,"default",{enumerable:true,value:t});if(e&2&&typeof t!="string")for(var i in t)n.d(o,i,function(e){return t[e]}.bind(null,i));return o};n.n=function(t){var e=t&&t.__esModule?function e(){return t["default"]}:function e(){return t};n.d(e,"a",e);return e};n.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)};n.p="";return n(n.s=74)}([function(t,e,n){"use strict";n.d(e,"a",(function(){return i}));n.d(e,"b",(function(){return r}));const o="https://ckeditor.com/docs/ckeditor5/latest/framework/guides/support/error-codes.html";class i extends Error{constructor(t,e,n){const o=`${t}${n?` ${JSON.stringify(n)}`:""}${a(t)}`;super(o);this.name="CKEditorError";this.context=e;this.data=n}is(t){return t==="CKEditorError"}static rethrowUnexpectedError(t,e){if(t.is&&t.is("CKEditorError")){throw t}const n=new i(t.message,e);n.stack=t.stack;throw n}}function r(t,e){console.warn(...c(t,e))}function s(t,e){console.error(...c(t,e))}function a(t){return`\nRead more: ${o}#error-${t}`}function c(t,e){const n=a(t);return e?[t,e,n]:[t,n]}},function(t,e,n){"use strict";var o=function t(){var e;return function t(){if(typeof e==="undefined"){e=Boolean(window&&document&&document.all&&!window.atob)}return e}}();var i=function t(){var e={};return function t(n){if(typeof e[n]==="undefined"){var o=document.querySelector(n);if(window.HTMLIFrameElement&&o instanceof window.HTMLIFrameElement){try{o=o.contentDocument.head}catch(t){o=null}}e[n]=o}return e[n]}}();var r=[];function s(t){var e=-1;for(var n=0;nt.length)e=t.length;for(var n=0,o=new Array(e);n