aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorpolo <ordipolo@gmx.fr>2025-10-09 01:35:52 +0200
committerpolo <ordipolo@gmx.fr>2025-10-09 01:35:52 +0200
commit15cbf5d56b4644151d59fee512f5f8fbe496caa3 (patch)
tree0da411f4aff53a0249cf3d59ea13f3786897b740
parent9f88389d78755d7c69c29e9db8f114fd1dbef034 (diff)
downloadcms-15cbf5d56b4644151d59fee512f5f8fbe496caa3.zip
pagination partie 2, nouvelles fonctions + renommage dans Director, ArticleController::fetch, et en JS: fetchArticles, insertLocalDates, modifs dans changePaginationLimit, dans les vues
-rw-r--r--public/css/body.css8
-rw-r--r--public/js/main.js57
-rw-r--r--public/js/modif_page.js78
-rw-r--r--public/js/tinymce.js28
-rw-r--r--src/controller/ArticleController.php41
-rw-r--r--src/controller/Director.php132
-rw-r--r--src/controller/PageManagementController.php17
-rw-r--r--src/model/entities/NodeData.php17
-rw-r--r--src/router.php31
-rw-r--r--src/view/AbstractBuilder.php4
-rw-r--r--src/view/NewBuilder.php6
-rw-r--r--src/view/NewsBlockBuilder.php3
-rw-r--r--src/view/PostBlockBuilder.php1
-rw-r--r--src/view/templates/modify_block.php7
-rw-r--r--src/view/templates/news_block.php5
-rw-r--r--src/view/templates/post_block.php5
16 files changed, 332 insertions, 108 deletions
diff --git a/public/css/body.css b/public/css/body.css
index 140655b..b728d05 100644
--- a/public/css/body.css
+++ b/public/css/body.css
@@ -49,12 +49,20 @@ main
49{ 49{
50 display: none; 50 display: none;
51} 51}
52section
53{
54 margin: 10px 0;
55}
52section > h3 56section > h3
53{ 57{
54 padding: 15px; 58 padding: 15px;
55 margin: 0; 59 margin: 0;
56 text-align: center; 60 text-align: center;
57} 61}
62section .fetch_articles
63{
64 margin-left: 15px;
65}
58.login_form 66.login_form
59{ 67{
60 background-color: white; 68 background-color: white;
diff --git a/public/js/main.js b/public/js/main.js
index d2f8876..59a9331 100644
--- a/public/js/main.js
+++ b/public/js/main.js
@@ -36,15 +36,10 @@ function toastNotify(message){
36 setTimeout(function(){ toast.className = toast.className.replace('show', ''); }, 5000); 36 setTimeout(function(){ toast.className = toast.className.replace('show', ''); }, 5000);
37} 37}
38 38
39
40// exécuté à la fin du chargement de la page 39// exécuté à la fin du chargement de la page
41document.addEventListener('DOMContentLoaded', () => { 40document.addEventListener('DOMContentLoaded', () => {
42 41
43 // détection des dates et conversion à l'heure locale 42 insertLocalDates();
44 document.querySelectorAll('.local_date').forEach(function(element){
45 const utc_date = element.getAttribute('date-utc'); // forme: 2025-10-10T12:17:00+00:00
46 element.innerText = toFormatedLocalDate(utc_date);
47 });
48 43
49 // ouvrir/fermer les sous-menus avec écran tactile 44 // ouvrir/fermer les sous-menus avec écran tactile
50 document.querySelectorAll('.sub-menu-toggle').forEach(button => { 45 document.querySelectorAll('.sub-menu-toggle').forEach(button => {
@@ -85,6 +80,48 @@ document.addEventListener('DOMContentLoaded', () => {
85}); 80});
86 81
87 82
83function fetchArticles(bloc_id){
84 const parent = document.getElementById(bloc_id);
85
86 const block_type = parent.getAttribute('block-type');
87 let last_article = '';
88 if(block_type === 'post_block'){
89 // pas parfait, suppose que les positions sont correctes
90 last_article = parent.querySelectorAll('article').length - 1;
91 }
92 else if(block_type === 'news_block'){
93 // date_time du dernier article affiché (heure UTC), date vide si bloc vide
94 const news_elements = parent.querySelector('.section_child').querySelectorAll('article');
95 last_article = news_elements.length !== 0 ? news_elements[news_elements.length - 1].querySelector('.local_date').getAttribute('date-utc') : '';
96 }
97 else{
98 console.log("Erreur, le type de bloc n'est pas reconnu");
99 return;
100 }
101
102 fetch('index.php?fetch=next_articles&id=' + bloc_id + '&last_article=' + last_article) // méthode GET par défaut
103 .then(response => response.json())
104 .then(data => {
105 if(data.success){
106 // insérer les articles
107 parent.querySelector('.section_child').innerHTML += data.html;
108 insertLocalDates();
109
110 // cacher le bouton
111 parent.querySelector('.fetch_articles').querySelector('button').className = data.truncated ? '' : 'hidden';
112
113 console.log("Articles insérés dans le bloc");
114 }
115 else{
116 console.log("Erreur côté serveur à la récupération d'articles");
117 }
118 })
119 .catch(error => {
120 console.error('Erreur:', error);
121 });
122}
123
124
88// complète les fonctions dans tinymce.js 125// complète les fonctions dans tinymce.js
89function switchPositions(article_id, direction) 126function switchPositions(article_id, direction)
90{ 127{
@@ -239,6 +276,14 @@ function submitDate(id_date)
239 } 276 }
240} 277}
241 278
279function insertLocalDates(){
280 // détection des dates et conversion à l'heure locale
281 document.querySelectorAll('.local_date').forEach(function(element){
282 const utc_date = element.getAttribute('date-utc'); // forme: 2025-10-10T12:17:00+00:00
283 element.innerText = toFormatedLocalDate(utc_date);
284 });
285}
286
242function toFormatedLocalDate(utc_string_date){ // forme: 2025-07-17T13:54:00.000Z ou 2025-02-04T00:24 287function toFormatedLocalDate(utc_string_date){ // forme: 2025-07-17T13:54:00.000Z ou 2025-02-04T00:24
243 const date = new Date(utc_string_date); 288 const date = new Date(utc_string_date);
244 289
diff --git a/public/js/modif_page.js b/public/js/modif_page.js
index bf269ee..15f3598 100644
--- a/public/js/modif_page.js
+++ b/public/js/modif_page.js
@@ -1,5 +1,8 @@
1/* -- mode modification d'une page -- */ 1/* -- mode modification d'une page -- */
2 2
3// beaucoup de fonctions similaires
4// à factoriser avec le pattern stratégie?
5
3// même fonction que dans new_page.js 6// même fonction que dans new_page.js
4function makePageNamePath(){ 7function makePageNamePath(){
5 document.getElementById("page_name_path").value = document.getElementById("page_name").value 8 document.getElementById("page_name_path").value = document.getElementById("page_name").value
@@ -92,7 +95,7 @@ function changeDescription(node_data_id){
92 toastNotify("la nouvelle description de la page est: " + data.description); 95 toastNotify("la nouvelle description de la page est: " + data.description);
93 } 96 }
94 else{ 97 else{
95 console.error('Erreur à la modification de la description de la page.'); 98 console.error('Erreur côté serveur à la modification de la description de la page.');
96 } 99 }
97 }) 100 })
98 .catch(error => { 101 .catch(error => {
@@ -119,7 +122,7 @@ function renamePageBloc(bloc_id){
119 toastNotify('Le bloc a été renommé: ' + data.title); 122 toastNotify('Le bloc a été renommé: ' + data.title);
120 } 123 }
121 else{ 124 else{
122 console.error('Erreur au renommage du titre.'); 125 console.error('Erreur côté serveur au renommage du titre.');
123 } 126 }
124 }) 127 })
125 .catch(error => { 128 .catch(error => {
@@ -168,7 +171,7 @@ function switchBlocsPositions(bloc_id, direction) {
168 } 171 }
169 else { 172 else {
170 173
171 console.error('Échec de l\'inversion'); 174 console.error("Échec de l'inversion côté serveur");
172 } 175 }
173 }) 176 })
174 .catch(error => { 177 .catch(error => {
@@ -187,17 +190,21 @@ function articlesOrderSelect(bloc_id){
187 .then(response => response.json()) 190 .then(response => response.json())
188 .then(data => { 191 .then(data => {
189 if(data.success){ 192 if(data.success){
190 // inverser l'ordre des articles!! 193 // inversion des articles
191 const parent = document.getElementById(bloc_id).querySelector(".section_child"); 194 /*const parent = document.getElementById(bloc_id).querySelector(".section_child");
192 const articles = Array.from(parent.querySelectorAll("article")); 195 const articles = Array.from(parent.querySelectorAll("article"));
193 articles.reverse().forEach(article => { 196 articles.reverse().forEach(article => {
194 parent.appendChild(article); // déplace dans le DOM, ne copie pas 197 parent.appendChild(article); // déplace dans le DOM, ne copie pas
195 }); 198 });*/
199
200 // à cause de la pagination, au lieu d'inverser, on remplace les articles par les 1er dans le nouveau sens
201 document.getElementById(bloc_id).querySelector('.section_child').innerHTML = '';
202 fetchArticles(bloc_id);
196 203
197 console.log('ordre ' + articles_order_select); 204 console.log('ordre ' + articles_order_select);
198 } 205 }
199 else{ 206 else{
200 console.log("Erreur au changement de l'ordre d'affichage côté serveur"); 207 console.log("Erreur côté serveur au changement de l'ordre d'affichage");
201 } 208 }
202 }) 209 })
203 .catch(error => { 210 .catch(error => {
@@ -219,10 +226,10 @@ function changePresentation(bloc_id){
219 document.getElementById(bloc_id).className = presentation; 226 document.getElementById(bloc_id).className = presentation;
220 document.getElementById(bloc_id).querySelector(".section_child").style.gridTemplateColumns = presentation === 'grid' ? 'repeat(auto-fit, minmax(' + data.cols_min_width + 'px, 1fr))' : ''; 227 document.getElementById(bloc_id).querySelector(".section_child").style.gridTemplateColumns = presentation === 'grid' ? 'repeat(auto-fit, minmax(' + data.cols_min_width + 'px, 1fr))' : '';
221 document.getElementById('cols_min_width_edit_' + bloc_id).className = presentation === 'grid' ? '' : 'hidden'; 228 document.getElementById('cols_min_width_edit_' + bloc_id).className = presentation === 'grid' ? '' : 'hidden';
222 console.log('changement de présentation'); 229 console.log('Changement de présentation');
223 } 230 }
224 else{ 231 else{
225 console.log('Erreur au changement de présentation côté serveur'); 232 console.log('Erreur côté serveur au changement de présentation');
226 } 233 }
227 }) 234 })
228 .catch(error => { 235 .catch(error => {
@@ -230,6 +237,7 @@ function changePresentation(bloc_id){
230 }); 237 });
231} 238}
232 239
240// ressemble à changePaginationLimit
233function changeColsMinWidth(bloc_id){ 241function changeColsMinWidth(bloc_id){
234 const cols_min_width_input = document.getElementById('cols_min_width_select_' + bloc_id); 242 const cols_min_width_input = document.getElementById('cols_min_width_select_' + bloc_id);
235 243
@@ -250,10 +258,58 @@ function changeColsMinWidth(bloc_id){
250 if(data.success){ 258 if(data.success){
251 document.getElementById(bloc_id).className = 'grid'; 259 document.getElementById(bloc_id).className = 'grid';
252 document.getElementById(bloc_id).querySelector(".section_child").style.gridTemplateColumns = 'repeat(auto-fit, minmax(' + data.cols_min_width + 'px, 1fr))'; 260 document.getElementById(bloc_id).querySelector(".section_child").style.gridTemplateColumns = 'repeat(auto-fit, minmax(' + data.cols_min_width + 'px, 1fr))';
253 console.log('changement de la largeur minimum en mode grille'); 261 console.log('Changement de la largeur minimum en mode grille');
262 }
263 else{
264 console.log('Erreur côté serveur au changement du nb de colonnes en mode grille');
265 }
266 })
267 .catch(error => {
268 console.error('Erreur:', error);
269 });
270}
271
272// ressemble à changeColsMinWidth
273function changePaginationLimit(bloc_id){
274 const pagination_limit_input = document.getElementById('pagination_limit_' + bloc_id);
275
276 if(pagination_limit_input.value > 30){
277 pagination_limit_input.value = 30;
278 }
279 else if(pagination_limit_input.value < 0){
280 pagination_limit_input.value = 0; // fait joli dans la BDD, les valeurs négatives ont le même effet que 0
281 }
282
283 fetch('index.php?bloc_edit=change_pagination_limit', {
284 method: 'POST',
285 headers: { 'Content-Type': 'application/json' },
286 body: JSON.stringify({ id: bloc_id, pagination_limit: pagination_limit_input.value })
287 })
288 .then(response => response.json())
289 .then(data => {
290 if(data.success){
291 const parent = document.getElementById(bloc_id).querySelector('.section_child');
292 const articles_list = parent.querySelectorAll('article');
293
294 if(data.new_limit > data.old_limit || data.new_limit <= 0){ // si 0, fetchArticles va TOUT chercher!
295 parent.innerHTML = ''; // pas opti, mais améliorer ça serait très compliqué
296 fetchArticles(bloc_id);
297 }
298 else if(data.new_limit < articles_list.length){
299 // retirer les articles
300 const articles_array = Array.from(articles_list).slice(0, data.new_limit);
301 parent.innerHTML = '';
302 for(let i = 0; i < articles_array.length; i++){
303 parent.appendChild(articles_array[i]);
304 }
305 // remettre le bouton "Articles suivants"
306 document.getElementById(bloc_id).querySelector('.fetch_articles').querySelector('button').className = '';
307 }
308
309 console.log("Changement du nombre d'articles affichés simultanément dans ce bloc");
254 } 310 }
255 else{ 311 else{
256 console.log('Erreur au changement du nb de colonnes en mode grille ´té serveur'); 312 console.log("Erreur côté serveur au changement du nb d'éléments affichés par la pagination");
257 } 313 }
258 }) 314 })
259 .catch(error => { 315 .catch(error => {
diff --git a/public/js/tinymce.js b/public/js/tinymce.js
index 97ecad8..d2f9c46 100644
--- a/public/js/tinymce.js
+++ b/public/js/tinymce.js
@@ -1,5 +1,5 @@
1// code à réorganiser 1// code à réorganiser
2// seule certaines fonctions ont leur place dans Editor, d'autres servent à manipuler les articles d'une autre manière (déplacer, supprimer...) 2// seules certaines fonctions ont leur place dans Editor, d'autres servent à manipuler les articles d'une autre manière (déplacer, supprimer...)
3// => encapsuler Editor dans une classe Article (comme la balise) qui existe même quand l'éditeur est fermé 3// => encapsuler Editor dans une classe Article (comme la balise) qui existe même quand l'éditeur est fermé
4 4
5 5
@@ -292,7 +292,7 @@ class Editor
292 } 292 }
293 293
294 submit(clone = null){ 294 submit(clone = null){
295 var content; 295 let content;
296 const params = new URL(document.location).searchParams; // "search" = ? et paramètres, searchParams = objet avec des getters 296 const params = new URL(document.location).searchParams; // "search" = ? et paramètres, searchParams = objet avec des getters
297 // à comparer avec: new URLSearchParams(window.location.search); 297 // à comparer avec: new URLSearchParams(window.location.search);
298 // c'est pareil ou pas? 298 // c'est pareil ou pas?
@@ -302,7 +302,7 @@ class Editor
302 const prefixes = ['t', 'p', 'i', 'd']; 302 const prefixes = ['t', 'p', 'i', 'd'];
303 const allElemsWithId = document.querySelectorAll('.data'); 303 const allElemsWithId = document.querySelectorAll('.data');
304 content = {}; 304 content = {};
305 var id_from_builder; 305 let id_from_builder;
306 306
307 allElemsWithId.forEach(element => { 307 allElemsWithId.forEach(element => {
308 const first_letter = element.id.charAt(0).toLowerCase(); 308 const first_letter = element.id.charAt(0).toLowerCase();
@@ -394,14 +394,14 @@ class Editor
394// restera ici jusqu'à ce que la gestion des balises soient faite ailleurs 394// restera ici jusqu'à ce que la gestion des balises soient faite ailleurs
395function makeNewArticleButtons(id, article_id, clone, placement = 'last') 395function makeNewArticleButtons(id, article_id, clone, placement = 'last')
396{ 396{
397 var share_btn = document.querySelector(`.share.hidden`); // combinaison de deux classes 397 let share_btn = document.querySelector(`.share.hidden`); // combinaison de deux classes
398 var new_btn = document.getElementById(`new-${id}`); 398 let new_btn = document.getElementById(`new-${id}`);
399 var edit_btn = document.getElementById(`edit-${id}`); 399 let edit_btn = document.getElementById(`edit-${id}`);
400 var pos_up_btn = document.getElementById(`position_up-${id}`); 400 let pos_up_btn = document.getElementById(`position_up-${id}`);
401 var pos_down_btn = document.getElementById(`position_down-${id}`); 401 let pos_down_btn = document.getElementById(`position_down-${id}`);
402 var delete_btn = document.getElementById(`delete-${id}`); 402 let delete_btn = document.getElementById(`delete-${id}`);
403 var cancel_btn = document.getElementById(`cancel-${id}`); 403 let cancel_btn = document.getElementById(`cancel-${id}`);
404 var submit_btn = document.getElementById(`submit-${id}`); 404 let submit_btn = document.getElementById(`submit-${id}`);
405 405
406 share_btn.classList.remove('hidden'); 406 share_btn.classList.remove('hidden');
407 new_btn.classList.add('hidden'); 407 new_btn.classList.add('hidden');
@@ -412,8 +412,8 @@ function makeNewArticleButtons(id, article_id, clone, placement = 'last')
412 //cancel_btn.classList.add('hidden'); 412 //cancel_btn.classList.add('hidden');
413 //submit_btn.classList.add('hidden'); 413 //submit_btn.classList.add('hidden');
414 414
415 var article = document.getElementById(id); 415 let article = document.getElementById(id);
416 var article_elem_parent = findParentByTagName(article, 'article'); 416 let article_elem_parent = findParentByTagName(article, 'article');
417 417
418 share_btn.setAttribute('onclick', "copyInClipBoard('" + window.location.href + article_id + "')"); // # de l'ancre ajouté au clic sur le lien ouvrant l'éditeur 418 share_btn.setAttribute('onclick', "copyInClipBoard('" + window.location.href + article_id + "')"); // # de l'ancre ajouté au clic sur le lien ouvrant l'éditeur
419 article.id = article_id; 419 article.id = article_id;
@@ -430,7 +430,7 @@ function makeNewArticleButtons(id, article_id, clone, placement = 'last')
430 submit_btn.id = 'submit-' + article_id; 430 submit_btn.id = 'submit-' + article_id;
431 submit_btn.querySelector('button').setAttribute('onclick', "submitArticle('" + article_id + "')"); 431 submit_btn.querySelector('button').setAttribute('onclick', "submitArticle('" + article_id + "')");
432 432
433 var section_child = article_elem_parent.parentNode.querySelector('.section_child'); // renommer section_child 433 let section_child = article_elem_parent.parentNode.querySelector('.section_child'); // renommer section_child
434 434
435 // parentNode vise la balise section 435 // parentNode vise la balise section
436 article_elem_parent.parentNode.replaceChild(clone.cloneNode(true), article_elem_parent); // clone du squelette pour le garder intact 436 article_elem_parent.parentNode.replaceChild(clone.cloneNode(true), article_elem_parent); // clone du squelette pour le garder intact
diff --git a/src/controller/ArticleController.php b/src/controller/ArticleController.php
index 411c1dc..8bbef19 100644
--- a/src/controller/ArticleController.php
+++ b/src/controller/ArticleController.php
@@ -6,10 +6,51 @@ declare(strict_types=1);
6use App\Entity\Node; 6use App\Entity\Node;
7use App\Entity\Article; 7use App\Entity\Article;
8use Doctrine\ORM\EntityManager; 8use Doctrine\ORM\EntityManager;
9use Symfony\Component\HttpFoundation\Request;
9use Symfony\Component\HttpFoundation\Response; 10use Symfony\Component\HttpFoundation\Response;
10 11
11class ArticleController 12class ArticleController
12{ 13{
14 static public function fetch(EntityManager $entityManager, Request $request): void
15 {
16 if($request->query->has('id') && !empty($request->query->get('id')) && $request->query->has('last_article')){
17 //var_dump($request->query->get('last_article'));
18 $id = (int)$request->get('id'); // type et nettoie
19 $director = new Director($entityManager);
20 $director->findNodeById($id);
21 $parent_block = $director->getNode();
22
23 if(Blocks::hasPresentation($parent_block->getName())){
24 $get_articles_return = $director->getNextArticles($parent_block, $request);
25 $bulk_data = $get_articles_return[0];
26
27 if($parent_block->getName() === 'post_block'){
28 $builder_name = 'PostBuilder';
29 }
30 elseif($parent_block->getName() === 'news_block'){
31 $builder_name = 'NewBuilder';
32 }
33
34 $html = '';
35 foreach($bulk_data as $article){
36 $builder = new $builder_name($article);
37 $html .= $builder->render();
38 }
39
40 echo json_encode(['success' => true, 'html' => $html, 'truncated' => $get_articles_return[1]]);
41 die;
42 }
43 else{
44 echo json_encode(['success' => false, 'error' => 'mauvais type de bloc']);
45 die;
46 }
47 }
48 else{
49 echo json_encode(['success' => false, 'error' => 'la requête ne comporte pas les paramètres attendus']);
50 die;
51 }
52 }
53
13 static public function editorSubmit(EntityManager $entityManager, array $json): void 54 static public function editorSubmit(EntityManager $entityManager, array $json): void
14 { 55 {
15 if(json_last_error() === JSON_ERROR_NONE) 56 if(json_last_error() === JSON_ERROR_NONE)
diff --git a/src/controller/Director.php b/src/controller/Director.php
index 8f038fa..9c1c6e3 100644
--- a/src/controller/Director.php
+++ b/src/controller/Director.php
@@ -1,12 +1,14 @@
1<?php 1<?php
2// src/controller/Director.php 2// src/controller/Director.php
3 3
4// => src/model/Model.php serait mieux
5
4declare(strict_types=1); 6declare(strict_types=1);
5 7
6use Doctrine\ORM\EntityManager; 8use Doctrine\ORM\EntityManager;
7use App\Entity\Page; 9use App\Entity\Page;
8use App\Entity\Node; 10use App\Entity\Node;
9//use Doctrine\ORM\QueryBuilder; 11use Doctrine\ORM\QueryBuilder;
10use Symfony\Component\HttpFoundation\Request; 12use Symfony\Component\HttpFoundation\Request;
11 13
12class Director 14class Director
@@ -24,7 +26,9 @@ class Director
24 $this->node = new Node; // instance mère "vide" ne possédant rien d'autre que des enfants 26 $this->node = new Node; // instance mère "vide" ne possédant rien d'autre que des enfants
25 } 27 }
26 28
27 public function makeMenuAndPaths(): void 29 // à déplacer dans Path ou un truc comme ça?
30 // couper Director en deux classes NodeModel et PageModel?
31 public function makeMenuAndPaths(): void // lit la table "page"
28 { 32 {
29 self::$menu_data = new Menu($this->entityManager); 33 self::$menu_data = new Menu($this->entityManager);
30 self::$page_path = new Path(); 34 self::$page_path = new Path();
@@ -41,13 +45,12 @@ class Director
41 } 45 }
42 46
43 // affichage d'une page ordinaire 47 // affichage d'une page ordinaire
44 public function getWholePageData(Request $request): void 48 public function getWholePageData(Request $request): void // lit la table "node" + jointures
45 { 49 {
46 $id = CURRENT_PAGE === 'article' ? htmlspecialchars($request->query->get('id')) : ''; 50 $id = CURRENT_PAGE === 'article' ? htmlspecialchars($request->query->get('id')) : '';
47 51
48 if($id === '') // page "normale" 52 if($id === ''){ // page "normale"
49 { 53 // récupérer tous les noeuds sauf les articles
50 // tous les noeuds sauf les articles, tri par page
51 $dql = "SELECT n FROM App\Entity\Node n WHERE n.name_node != 'new' AND n.name_node != 'post' AND (n.page = :page OR n.page IS null)"; 54 $dql = "SELECT n FROM App\Entity\Node n WHERE n.name_node != 'new' AND n.name_node != 'post' AND (n.page = :page OR n.page IS null)";
52 $bulk_data = $this->entityManager 55 $bulk_data = $this->entityManager
53 ->createQuery($dql) 56 ->createQuery($dql)
@@ -57,45 +60,11 @@ class Director
57 // groupes d'articles triés par bloc, permet de paginer par bloc 60 // groupes d'articles triés par bloc, permet de paginer par bloc
58 foreach($bulk_data as $parent_block){ 61 foreach($bulk_data as $parent_block){
59 if(Blocks::hasPresentation($parent_block->getName())){ // = post_block ou news_block 62 if(Blocks::hasPresentation($parent_block->getName())){ // = post_block ou news_block
60 $qb = $this->entityManager->createQueryBuilder(); 63 $bulk_data = array_merge($bulk_data, $this->getNextArticles($parent_block, $request)[0]);
61 $qb->select('n')
62 ->from('App\Entity\Node', 'n')
63 ->where('n.parent = :parent')
64 ->setParameter('parent', $parent_block);
65
66 if($parent_block->getName() === 'post_block'){
67 $qb->orderBy('n.position');
68 }
69 elseif($parent_block->getName() === 'news_block'){
70 $qb->join('n.article', 'a');
71 if($parent_block->getNodeData()->getChronoOrder() ?? false){ // ordre antichrono par défaut
72 $qb->orderBy('a.date_time', 'ASC');
73 }
74 else{
75 $qb->orderBy('a.date_time', 'DESC');
76 }
77 }
78
79 // pagination
80 $limit = $parent_block->getNodeData()->getPaginationLimit() ?? 0; // 0 par défaut = pas de pagination, sinon 12 rend bien avec des grilles de 2, 3 ou 4 colonnes
81 if($limit > 0){
82 //$this->paginateWithCursor($qb, $request->query->get('last_position') ?? 0, $limit);
83 $qb->andWhere('n.position > :last_position')
84 ->setParameter('last_position', $request->query->get('last_position') ?? 0)
85 ->setMaxResults($limit);
86
87 $nb_pages = $this->getNumberOfPages($parent_block, $limit); // nombres de "pages" d'articles
88 if($nb_pages > 1){
89 //$parent_block->setNumberOfPages($nb_pages); // => navigation en HTML
90 }
91 }
92
93 $bulk_data = array_merge($bulk_data, $qb->getQuery()->getResult());
94 } 64 }
95 } 65 }
96 } 66 }
97 else // page "article" 67 else{ // page "article"
98 {
99 $dql = 'SELECT n FROM App\Entity\Node n WHERE n.page = :page OR n.page IS null OR n.id_node = :id'; 68 $dql = 'SELECT n FROM App\Entity\Node n WHERE n.page = :page OR n.page IS null OR n.id_node = :id';
100 $bulk_data = $this->entityManager 69 $bulk_data = $this->entityManager
101 ->createQuery($dql) 70 ->createQuery($dql)
@@ -103,18 +72,74 @@ class Director
103 ->setParameter('id', $id) 72 ->setParameter('id', $id)
104 ->getResult(); 73 ->getResult();
105 } 74 }
75
106 $this->makeNodeTree($bulk_data); 76 $this->makeNodeTree($bulk_data);
107 } 77 }
108 78
109 /*private function paginateWithCursor(QueryBuilder $qb, int $last_position = 0, int $limit = 0): void 79 // récupération d'articles
80 public function getNextArticles(Node $parent_block, Request $request): array
81 {
82 $qb = $this->entityManager->createQueryBuilder();
83 $qb->select('n')
84 ->from('App\Entity\Node', 'n')
85 ->where('n.parent = :parent')
86 ->setParameter('parent', $parent_block);
87
88 if($parent_block->getName() === 'post_block'){
89 $qb->orderBy('n.position');
90 }
91 elseif($parent_block->getName() === 'news_block'){
92 $qb->join('n.article', 'a');
93 if($parent_block->getNodeData()->getChronoOrder() ?? false){ // ordre antichrono par défaut
94 $qb->orderBy('a.date_time', 'ASC');
95 }
96 else{
97 $qb->orderBy('a.date_time', 'DESC');
98 }
99 }
100
101 // pagination
102 $limit = $parent_block->getNodeData()->getPaginationLimit(); // = 12 par défaut si = null en BDD
103 $this->paginateWithCursor($qb, $parent_block, $request->query->get('last_article'));
104 $result = $qb->getQuery()->getResult();
105
106 // il reste des articles à récupérer SI on vient d'en récupérer trop
107 // ET on gère le cas particulier de $limit <= 0
108 $truncated = false;
109 if(count($result) > $limit && $limit > 0){ // si nb résultat > limit > 0
110 $truncated = true;
111 array_pop($result); // compenser le $limit + 1 dans paginateWithCursor
112 }
113
114 return [$result, $truncated]; // besoin exceptionnel de retourner deux valeurs
115 }
116
117 private function paginateWithCursor(QueryBuilder $qb, Node $parent_block, ?string $last_article): void
110 { 118 {
111 $qb->andWhere('n.position > :last_position') 119 //var_dump($last_article);
112 ->setParameter('last_position', $last_position) 120 $limit = $parent_block->getNodeData()->getPaginationLimit(); // = 12 par défaut si = null en BDD
113 ->setMaxResults($limit); 121
114 }*/ 122 if($limit > 0){ // si 0 ou moins pas de pagination
123 // nombres de "pages" d'articles
124 $nb_pages = $this->getNumberOfPages($parent_block, $limit);
125 $parent_block->getNodeData()->setNumberOfPages($nb_pages > 1 ? $nb_pages : 1);
126
127 // adaptation de la requête
128 if($parent_block->getName() === 'post_block'){
129 $qb->andWhere('n.position > :last_position')
130 ->setParameter('last_position', empty($last_article) ? 0 : $last_article)
131 ->setMaxResults($limit + 1);
132 }
133 elseif($parent_block->getName() === 'news_block'){
134 $cursor_start = $parent_block->getNodeData()->getChronoOrder() ? '1970-01-01' : '9999-12-31';
135 $qb->andWhere($parent_block->getNodeData()->getChronoOrder() ? 'a.date_time > :date_time' : 'a.date_time < :date_time')
136 ->setParameter('date_time', empty($last_article) ? $cursor_start : $last_article)
137 ->setMaxResults($limit + 1);
138 }
139 }
140 }
115 141
116 // requête à part n'alimentant pas $bulk_data 142 // le Paginator de doctrine le fait aussi si on décidait de s'en servir
117 // fonctionnalité offerte par le Paginator de doctrine si on décidait de s'en servir
118 private function getNumberOfPages(Node $parent_block, int $limit): int 143 private function getNumberOfPages(Node $parent_block, int $limit): int
119 { 144 {
120 $dql = 'SELECT COUNT(n.id_node) FROM App\Entity\Node n WHERE n.parent = :parent'; 145 $dql = 'SELECT COUNT(n.id_node) FROM App\Entity\Node n WHERE n.parent = :parent';
@@ -129,11 +154,9 @@ class Director
129 { 154 {
130 // puis on les range 155 // puis on les range
131 // (attention, risque de disfonctionnement si les noeuds de 1er niveau ne sont pas récupérés en 1er dans la BDD) 156 // (attention, risque de disfonctionnement si les noeuds de 1er niveau ne sont pas récupérés en 1er dans la BDD)
132 foreach($bulk_data as $node) 157 foreach($bulk_data as $node){
133 {
134 // premier niveau 158 // premier niveau
135 if($node->getParent() == null) 159 if($node->getParent() == null){
136 {
137 $this->node->addChild($node); 160 $this->node->addChild($node);
138 161
139 // spécifique page article 162 // spécifique page article
@@ -142,8 +165,7 @@ class Director
142 } 165 }
143 } 166 }
144 // autres niveaux 167 // autres niveaux
145 else 168 else{
146 {
147 $node->getParent()->addChild($node); 169 $node->getParent()->addChild($node);
148 170
149 // spécifique page article 171 // spécifique page article
diff --git a/src/controller/PageManagementController.php b/src/controller/PageManagementController.php
index 2cc88a5..8efcb79 100644
--- a/src/controller/PageManagementController.php
+++ b/src/controller/PageManagementController.php
@@ -334,4 +334,21 @@ class PageManagementController
334 } 334 }
335 die; 335 die;
336 } 336 }
337 static public function changePaginationLimit(EntityManager $entityManager, array $json): void
338 {
339 if(isset($json['id']) && isset($json['pagination_limit'])){
340 $director = new Director($entityManager);
341 $director->findNodeById($json['id']);
342 $old_limit = $director->getNode()->getNodeData()->getPaginationLimit() ?? 12;
343 $director->getNode()->getNodeData()->setPaginationLimit((int)$json['pagination_limit']); // attention conversion?
344
345 $entityManager->flush();
346
347 echo json_encode(['success' => true, 'old_limit' => $old_limit, 'new_limit' => $json['pagination_limit']]);
348 }
349 else{
350 echo json_encode(['success' => false]);
351 }
352 die;
353 }
337} \ No newline at end of file 354} \ No newline at end of file
diff --git a/src/model/entities/NodeData.php b/src/model/entities/NodeData.php
index 1d7db4c..d8281c0 100644
--- a/src/model/entities/NodeData.php
+++ b/src/model/entities/NodeData.php
@@ -48,6 +48,8 @@ class NodeData
48 )] 48 )]
49 private Collection $images; 49 private Collection $images;
50 50
51 private int $nb_pages = 1;
52
51 public function __construct(array $data, Node $node, Collection $images = new ArrayCollection, ?string $presentation = null, ?bool $chrono_order = null) 53 public function __construct(array $data, Node $node, Collection $images = new ArrayCollection, ?string $presentation = null, ?bool $chrono_order = null)
52 { 54 {
53 $this->data = $data; 55 $this->data = $data;
@@ -111,7 +113,20 @@ class NodeData
111 113
112 public function getPaginationLimit(): ?int 114 public function getPaginationLimit(): ?int
113 { 115 {
114 return $this->pagination_limit ?? null; 116 $default = 12; // si 0 pas de pagination, 12 rend bien avec des grilles de 2, 3 ou 4 colonnes
117 return $this->pagination_limit === null ? $default : $this->pagination_limit;
118 }
119 public function setPaginationLimit(int $pagination_limit): void
120 {
121 $this->pagination_limit = $pagination_limit;
122 }
123 public function getNumberOfPages(): int
124 {
125 return $this->nb_pages;
126 }
127 public function setNumberOfPages(int $nb_pages): void
128 {
129 $this->nb_pages = $nb_pages;
115 } 130 }
116 131
117 /*public function setNode(Node $node): void 132 /*public function setNode(Node $node): void
diff --git a/src/router.php b/src/router.php
index 773a25c..8d19e49 100644
--- a/src/router.php
+++ b/src/router.php
@@ -1,7 +1,7 @@
1<?php 1<?php
2// src/router.php 2// src/router.php
3// 3//
4/* fonctionnement du routeur 4/* fonctionnement:
5=> 1er test, méthode http: GET, POST ou autre chose 5=> 1er test, méthode http: GET, POST ou autre chose
6=> 2ème test, type de contenu (méthode POST uniquement): 6=> 2ème test, type de contenu (méthode POST uniquement):
7"application/x-www-form-urlencoded" = formulaire 7"application/x-www-form-urlencoded" = formulaire
@@ -13,22 +13,27 @@ $_SERVER['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest' requête AJAX xhs, non uti
13 13
14declare(strict_types=1); 14declare(strict_types=1);
15 15
16if($_SERVER['REQUEST_METHOD'] === 'GET'){ 16if($request->getMethod() === 'GET'){
17 // table "user" vide 17 // table "user" vide
18 if(!UserController::existUsers($entityManager)){ 18 if(!UserController::existUsers($entityManager)){
19 require '../src/view/templates/user_create.php'; 19 require AbstractBuilder::VIEWS_PATH . 'user_create.php';
20 die; 20 die;
21 } 21 }
22 22
23 // bouton déconnexion 23 // bouton déconnexion (méthode GET parce que l'utilisateur ne modifie plus de données à partir de là)
24 if($request->query->has('action') && $request->query->get('action') === 'deconnection'){ 24 if($request->query->has('action') && $request->query->get('action') === 'deconnection'){
25 UserController::disconnect($entityManager); 25 UserController::disconnect($entityManager);
26 } 26 }
27 27
28 // articles suivants
29 if($request->query->has('fetch') && $request->query->get('fetch') === 'next_articles'){
30 ArticleController::fetch($entityManager, $request);
31 }
32
28 // données du calendrier 33 // données du calendrier
29 // création du calendrier et changement de dates affichées (boutons flèches mais pas changement de vue) 34 // création du calendrier et changement de dates affichées (boutons flèches mais pas changement de vue)
30 if($_SERVER['REQUEST_METHOD'] === 'GET' && $request->query->has('action') && $request->query->get('action') === 'get_events' 35 if($request->query->has('action') && $request->query->get('action') === 'get_events'
31 && $request->query->has('start') && $request->query->has('end') && empty($_POST)) 36 && $request->query->has('start') && $request->query->has('end') && empty($request->getPayload()->all())) // getPayload ne récupère pas que des POST
32 { 37 {
33 CalendarController::getData($entityManager); 38 CalendarController::getData($entityManager);
34 } 39 }
@@ -43,7 +48,7 @@ if($_SERVER['REQUEST_METHOD'] === 'GET'){
43} 48}
44 49
45 50
46elseif($_SERVER['REQUEST_METHOD'] === 'POST'){ 51elseif($request->getMethod() === 'POST'){
47 /* -- contrôleurs appellables par tout le monde -- */ 52 /* -- contrôleurs appellables par tout le monde -- */
48 53
49 // table "user" vide 54 // table "user" vide
@@ -54,8 +59,7 @@ elseif($_SERVER['REQUEST_METHOD'] === 'POST'){
54 // requêtes JSON avec fetch() 59 // requêtes JSON avec fetch()
55 if($_SERVER['CONTENT_TYPE'] === 'application/json') 60 if($_SERVER['CONTENT_TYPE'] === 'application/json')
56 { 61 {
57 $data = file_get_contents('php://input'); 62 $json = json_decode($request->getContent(), true); // = json_decode(file_get_contents('php://input'), true);
58 $json = json_decode($data, true);
59 63
60 if(isset($_GET['action'])) 64 if(isset($_GET['action']))
61 { 65 {
@@ -195,6 +199,9 @@ elseif($_SERVER['REQUEST_METHOD'] === 'POST'){
195 elseif($request->query->get('bloc_edit') === 'change_cols_min_width'){ 199 elseif($request->query->get('bloc_edit') === 'change_cols_min_width'){
196 PageManagementController::changeColsMinWidth($entityManager, $json); 200 PageManagementController::changeColsMinWidth($entityManager, $json);
197 } 201 }
202 elseif($request->query->get('bloc_edit') === 'change_pagination_limit'){
203 PageManagementController::changePaginationLimit($entityManager, $json);
204 }
198 } 205 }
199 } 206 }
200 207
@@ -316,7 +323,7 @@ if(isset($response)){
316 header('Location: ' . new URL(['page' => !empty($_GET['from']) ? $_GET['from'] : 'accueil'])); 323 header('Location: ' . new URL(['page' => !empty($_GET['from']) ? $_GET['from'] : 'accueil']));
317 } 324 }
318 // redirection après traitement de formulaires HTTP 325 // redirection après traitement de formulaires HTTP
319 elseif($_SERVER['REQUEST_METHOD'] === 'POST' && $_SERVER['CONTENT_TYPE'] === 'application/x-www-form-urlencoded'){ 326 elseif($request->getMethod() === 'POST' && $_SERVER['CONTENT_TYPE'] === 'application/x-www-form-urlencoded'){
320 $response_data = json_decode(($response)->getContent(), true); 327 $response_data = json_decode(($response)->getContent(), true);
321 $url = new URL(['page' => !empty($_GET['from']) ? $_GET['from'] : 'accueil']); 328 $url = new URL(['page' => !empty($_GET['from']) ? $_GET['from'] : 'accueil']);
322 $url->addParams(['success' => $response_data['success'], 'message' => $response_data['message']]); 329 $url->addParams(['success' => $response_data['success'], 'message' => $response_data['message']]);
@@ -329,7 +336,7 @@ if(isset($response)){
329} 336}
330// pas utilisation de RESPONSE (cas destiné à disparaître) 337// pas utilisation de RESPONSE (cas destiné à disparaître)
331else{ 338else{
332 if($_SERVER['REQUEST_METHOD'] === 'POST' && $_SERVER['CONTENT_TYPE'] === 'application/x-www-form-urlencoded'){ 339 if($request->getMethod() === 'POST' && $_SERVER['CONTENT_TYPE'] === 'application/x-www-form-urlencoded'){
333 header("Location: " . new URL(['error' => 'erreur côté serveur'])); 340 header("Location: " . new URL(['error' => 'erreur côté serveur']));
334 } 341 }
335 else{ 342 else{
@@ -337,4 +344,4 @@ else{
337 echo "erreur côté serveur"; 344 echo "erreur côté serveur";
338 } 345 }
339} 346}
340//die; // inutile normalement \ No newline at end of file 347//die; // inutile \ No newline at end of file
diff --git a/src/view/AbstractBuilder.php b/src/view/AbstractBuilder.php
index ab5e389..0748df0 100644
--- a/src/view/AbstractBuilder.php
+++ b/src/view/AbstractBuilder.php
@@ -7,7 +7,7 @@ use App\Entity\Node;
7 7
8abstract class AbstractBuilder 8abstract class AbstractBuilder
9{ 9{
10 protected const string VIEWS_PATH = '../src/view/templates/'; 10 public const string VIEWS_PATH = '../src/view/templates/';
11 protected string $html = ''; 11 protected string $html = '';
12 protected int $id_node; 12 protected int $id_node;
13 13
@@ -44,7 +44,7 @@ abstract class AbstractBuilder
44 } 44 }
45 } 45 }
46 46
47 protected function snakeToPascalCase(string $input): string 47 private function snakeToPascalCase(string $input): string
48 { 48 {
49 return str_replace('_', '', ucwords($input, '_')); 49 return str_replace('_', '', ucwords($input, '_'));
50 } 50 }
diff --git a/src/view/NewBuilder.php b/src/view/NewBuilder.php
index 83dd728..ec48541 100644
--- a/src/view/NewBuilder.php
+++ b/src/view/NewBuilder.php
@@ -51,9 +51,7 @@ class NewBuilder extends AbstractBuilder
51 } 51 }
52 52
53 $content = ''; 53 $content = '';
54 54 if(CURRENT_PAGE === 'article'){
55 // page article unique
56 if(Director::$page_path->getLast()->getEndOfPath() === 'article'){
57 $content = $node->getArticle()->getContent(); 55 $content = $node->getArticle()->getContent();
58 $from_to_button = '<p><a class="link_to_article" href="' . new URL(isset($_GET['from']) ? ['page' => $_GET['from']] : []) . '"><button>Retour</button></a></p>'; 56 $from_to_button = '<p><a class="link_to_article" href="' . new URL(isset($_GET['from']) ? ['page' => $_GET['from']] : []) . '"><button>Retour</button></a></p>';
59 $overflow = ''; 57 $overflow = '';
@@ -84,7 +82,7 @@ class NewBuilder extends AbstractBuilder
84 $date_buttons = ''; 82 $date_buttons = '';
85 $admin_buttons = ''; 83 $admin_buttons = '';
86 if($_SESSION['admin']){ 84 if($_SESSION['admin']){
87 if(Director::$page_path->getLast()->getEndOfPath() === 'article'){ 85 if(CURRENT_PAGE === 'article'){
88 $title_js = 'onclick="openEditor(\'' . $id_title . '\')"'; 86 $title_js = 'onclick="openEditor(\'' . $id_title . '\')"';
89 $modify_title = '<p id="edit-' . $id_title . '"><button ' . $title_js . '><img class="action_icon" src="assets/edit.svg">Titre</button></p>' . "\n"; 87 $modify_title = '<p id="edit-' . $id_title . '"><button ' . $title_js . '><img class="action_icon" src="assets/edit.svg">Titre</button></p>' . "\n";
90 $close_js_title = 'onclick="closeEditor(\'' . $id_title . '\')"'; 88 $close_js_title = 'onclick="closeEditor(\'' . $id_title . '\')"';
diff --git a/src/view/NewsBlockBuilder.php b/src/view/NewsBlockBuilder.php
index 963afe6..4c7c4ec 100644
--- a/src/view/NewsBlockBuilder.php
+++ b/src/view/NewsBlockBuilder.php
@@ -28,6 +28,7 @@ class NewsBlockBuilder extends AbstractBuilder
28 $min_width = $node->getNodeData()->getColsMinWidth(); 28 $min_width = $node->getNodeData()->getColsMinWidth();
29 $cols_min_width = 'grid-template-columns: repeat(auto-fit, minmax(' . (string)$min_width . 'px, 1fr));'; 29 $cols_min_width = 'grid-template-columns: repeat(auto-fit, minmax(' . (string)$min_width . 'px, 1fr));';
30 } 30 }
31 $fetch_button_hidden = $node->getNodeData()->getNumberOfPages() > 1 ? '' : ' hidden';
31 32
32 // ajouter un article 33 // ajouter un article
33 $new_article = ''; 34 $new_article = '';
@@ -54,7 +55,7 @@ class NewsBlockBuilder extends AbstractBuilder
54 $html = ''; 55 $html = '';
55 $admin_buttons = $new_button . $modify_article . $delete_article . $close_editor . $submit_article; 56 $admin_buttons = $new_button . $modify_article . $delete_article . $close_editor . $submit_article;
56 57
57 // post vide mis là pour le bouton "Nouvel article" => déplace vers page "article" 58 // insérer post.php pour le bouton "Nouvel article", new.php pour les vrais articles
58 ob_start(); 59 ob_start();
59 require self::VIEWS_PATH . 'post.php'; // nécéssite $admin_buttons et $html 60 require self::VIEWS_PATH . 'post.php'; // nécéssite $admin_buttons et $html
60 $new_article = ob_get_clean(); 61 $new_article = ob_get_clean();
diff --git a/src/view/PostBlockBuilder.php b/src/view/PostBlockBuilder.php
index ba54901..ba4de12 100644
--- a/src/view/PostBlockBuilder.php
+++ b/src/view/PostBlockBuilder.php
@@ -28,6 +28,7 @@ class PostBlockBuilder extends AbstractBuilder
28 $min_width = $node->getNodeData()->getColsMinWidth(); 28 $min_width = $node->getNodeData()->getColsMinWidth();
29 $cols_min_width = 'grid-template-columns: repeat(auto-fit, minmax(' . (string)$min_width . 'px, 1fr));'; 29 $cols_min_width = 'grid-template-columns: repeat(auto-fit, minmax(' . (string)$min_width . 'px, 1fr));';
30 } 30 }
31 $fetch_button_hidden = $node->getNodeData()->getNumberOfPages() > 1 ? '' : ' hidden';
31 32
32 // ajouter un article 33 // ajouter un article
33 // => fait un peu double emploi avec PostBuilder 34 // => fait un peu double emploi avec PostBuilder
diff --git a/src/view/templates/modify_block.php b/src/view/templates/modify_block.php
index 1d097e0..3620100 100644
--- a/src/view/templates/modify_block.php
+++ b/src/view/templates/modify_block.php
@@ -47,4 +47,11 @@ if(Blocks::hasPresentation($child_node->getName())){
47<?php 47<?php
48} 48}
49?> 49?>
50 <div class="pagination_limit">
51 <label for="pagination_limit_<?= $child_node->getId() ?>">
52 Nombre max d'articles affichés
53 <input type="number" id="pagination_limit_<?= $child_node->getId() ?>" name="pagination_limit" onchange="changePaginationLimit(<?= $child_node->getId() ?>)" min="0" max="30" value="<?= $child_node->getNodeData()->getPaginationLimit() ?>">
54 <i>(mettre 0 désactive la pagination)</i>
55 </label>
56 </div>
50</div> \ No newline at end of file 57</div> \ No newline at end of file
diff --git a/src/view/templates/news_block.php b/src/view/templates/news_block.php
index 9d01513..43dbe01 100644
--- a/src/view/templates/news_block.php
+++ b/src/view/templates/news_block.php
@@ -1,5 +1,5 @@
1<?php declare(strict_types=1); ?> 1<?php declare(strict_types=1); ?>
2<section class="<?= $section_class ?>" id="<?= $this->id_node ?>"> 2<section class="<?= $section_class ?>" block-type="<?= $node->getName() ?>" id="<?= $this->id_node ?>">
3 <h3><?= $title ?></h3> 3 <h3><?= $title ?></h3>
4<?= $new_article ?> 4<?= $new_article ?>
5 <script> 5 <script>
@@ -8,4 +8,7 @@
8 <div class="section_child" style="<?= $cols_min_width ?>"> 8 <div class="section_child" style="<?= $cols_min_width ?>">
9<?= $content ?> 9<?= $content ?>
10 </div> 10 </div>
11 <div class="fetch_articles">
12 <button class="<?= $fetch_button_hidden ?>" onclick="fetchArticles(<?= $this->id_node ?>)">Articles suivants</button>
13 </div>
11</section> \ No newline at end of file 14</section> \ No newline at end of file
diff --git a/src/view/templates/post_block.php b/src/view/templates/post_block.php
index 9d01513..43dbe01 100644
--- a/src/view/templates/post_block.php
+++ b/src/view/templates/post_block.php
@@ -1,5 +1,5 @@
1<?php declare(strict_types=1); ?> 1<?php declare(strict_types=1); ?>
2<section class="<?= $section_class ?>" id="<?= $this->id_node ?>"> 2<section class="<?= $section_class ?>" block-type="<?= $node->getName() ?>" id="<?= $this->id_node ?>">
3 <h3><?= $title ?></h3> 3 <h3><?= $title ?></h3>
4<?= $new_article ?> 4<?= $new_article ?>
5 <script> 5 <script>
@@ -8,4 +8,7 @@
8 <div class="section_child" style="<?= $cols_min_width ?>"> 8 <div class="section_child" style="<?= $cols_min_width ?>">
9<?= $content ?> 9<?= $content ?>
10 </div> 10 </div>
11 <div class="fetch_articles">
12 <button class="<?= $fetch_button_hidden ?>" onclick="fetchArticles(<?= $this->id_node ?>)">Articles suivants</button>
13 </div>
11</section> \ No newline at end of file 14</section> \ No newline at end of file