aboutsummaryrefslogtreecommitdiff
path: root/src/View
diff options
context:
space:
mode:
authorpolo <ordipolo@gmx.fr>2026-10-23 00:47:32 +0200
committerpolo <ordipolo@gmx.fr>2026-10-23 00:47:32 +0200
commitf27cca08ff786ceff1870147f6d6bad14d71117f (patch)
treed36597a72025313d6faf4ed228755fa267ebe2f6 /src/View
parentc7309f900ef05bd5cb313110ec159b761af394ec (diff)
downloadcms-f27cca08ff786ceff1870147f6d6bad14d71117f.tar.gz
cms-f27cca08ff786ceff1870147f6d6bad14d71117f.tar.bz2
cms-f27cca08ff786ceff1870147f6d6bad14d71117f.zip
dossiers avec majuscule, namespace pour toutes les classes, autoload psr-4, UserDataService::createZip déplacé dans Backup
Diffstat (limited to 'src/View')
-rw-r--r--src/View/AbstractBuilder.php81
-rw-r--r--src/View/BreadcrumbBuilder.php53
-rw-r--r--src/View/CalendarBuilder.php31
-rw-r--r--src/View/FooterBuilder.php122
-rw-r--r--src/View/FormBuilder.php55
-rw-r--r--src/View/GaleryBuilder.php72
-rw-r--r--src/View/HeadBuilder.php84
-rw-r--r--src/View/HeaderBuilder.php131
-rw-r--r--src/View/LoginBuilder.php56
-rw-r--r--src/View/MainBuilder.php99
-rw-r--r--src/View/MaintenanceBuilder.php36
-rw-r--r--src/View/MenuBuilder.php101
-rw-r--r--src/View/NavBuilder.php77
-rw-r--r--src/View/NewBuilder.php157
-rw-r--r--src/View/NewPageBuilder.php53
-rw-r--r--src/View/NewsBlockBuilder.php76
-rw-r--r--src/View/PostBlockBuilder.php90
-rw-r--r--src/View/PostBuilder.php60
-rw-r--r--src/View/ShowEmailsBuilder.php71
-rw-r--r--src/View/UserEditBuilder.php64
-rw-r--r--src/View/ViewDirector.php30
-rw-r--r--src/View/templates/calendar.php14
-rw-r--r--src/View/templates/footer.php43
-rw-r--r--src/View/templates/form.php36
-rw-r--r--src/View/templates/form_admin.php73
-rw-r--r--src/View/templates/galery.php12
-rw-r--r--src/View/templates/head.php13
-rw-r--r--src/View/templates/header.php58
-rw-r--r--src/View/templates/login.php31
-rw-r--r--src/View/templates/maintenance.php65
-rw-r--r--src/View/templates/menu.php51
-rw-r--r--src/View/templates/modify_block.php61
-rw-r--r--src/View/templates/modify_page.php62
-rw-r--r--src/View/templates/new.php27
-rw-r--r--src/View/templates/new_page.php38
-rw-r--r--src/View/templates/news_block.php14
-rw-r--r--src/View/templates/post.php14
-rw-r--r--src/View/templates/post_block.php14
-rw-r--r--src/View/templates/show_emails.php30
-rw-r--r--src/View/templates/user_create.php54
-rw-r--r--src/View/templates/user_edit.php63
41 files changed, 2372 insertions, 0 deletions
diff --git a/src/View/AbstractBuilder.php b/src/View/AbstractBuilder.php
new file mode 100644
index 0000000..170e871
--- /dev/null
+++ b/src/View/AbstractBuilder.php
@@ -0,0 +1,81 @@
1<?php
2// src/View/AbstractBuilder.php
3
4declare(strict_types=1);
5
6namespace App\View;
7
8use App\Entity\Node;
9use App\Model\Model;
10
11abstract class AbstractBuilder
12{
13 const VIEWS_PATH = '../src/View/templates/';
14 protected string $html = '';
15 protected int $id_node;
16
17 protected function __construct(Node $node)
18 {
19 $this->id_node = $node->getId();
20 }
21
22 protected function useChildrenBuilder(Node $node): void
23 {
24 foreach($node->getChildren() as $child_node)
25 {
26 // le double \\ permet de ne pas échapper la quote
27 $builder_name = 'App\View\\' . $this->snakeToPascalCase($child_node->getName()) . 'Builder';
28 $builder = new $builder_name($child_node);
29 $this->html .= $builder->render();
30
31 // pages spéciales où on n'assemble pas tout
32 if($builder_name === 'HeadBuilder' && in_array(Model::$page_path->getString(), ['connection', 'user_edit']))
33 {
34 foreach($node->getChildren() as $target_node){
35 if($target_node->getName() === 'main'){
36 $main_node = $target_node;
37 break;
38 }
39 }
40 // on construit <main> et on s'arrête! les autres noeuds sont ignorés
41 $builder_name = $this->snakeToPascalCase($main_node->getName()) . 'Builder';
42 $builder = new $builder_name($main_node);
43 $this->html .= "<body>\n";
44 $this->html .= $builder->render() . "\n";
45 $this->html .= "</body>\n</html>";
46 break;
47 }
48 }
49 }
50
51 private function snakeToPascalCase(string $input): string
52 {
53 return str_replace('_', '', ucwords($input, '_'));
54 }
55
56 public function render(): string // = getHTML(), nécéssite d'être public
57 {
58 return $this->html;
59 }
60 protected function addHTML(string $html): void
61 {
62 $this->html .= $html;
63 }
64
65 protected function insertSVG(string $path, array $attributes = []): string
66 {
67 $svg = file_get_contents($path);
68
69 // modification des attributs
70 if(!empty($attributes)){
71 $dom = new \DOMDocument();
72 $dom->loadXML($svg);
73 $svg_elem = $dom->documentElement;
74 foreach($attributes as $key => $value){
75 $svg_elem->setAttribute($key, $value);
76 }
77 $svg = $dom->saveXML($svg_elem);
78 }
79 return $svg;
80 }
81} \ No newline at end of file
diff --git a/src/View/BreadcrumbBuilder.php b/src/View/BreadcrumbBuilder.php
new file mode 100644
index 0000000..81df0eb
--- /dev/null
+++ b/src/View/BreadcrumbBuilder.php
@@ -0,0 +1,53 @@
1<?php
2// src/View/BreadcrumbBuilder.php
3
4declare(strict_types=1);
5
6namespace App\View;
7
8//use App\Entity\Node;
9use App\Model\Model;
10use App\Service\URL;
11
12class BreadcrumbBuilder extends AbstractBuilder
13{
14 public function __construct(/*Node $node*/){
15 $this->html = $this->breadcrumbHTML();
16 }
17
18 private function breadcrumbHTML(): string
19 {
20 $asset = 'assets/home.svg'; // => BDD?
21 $breadcrumb_array = Model::$page_path->getArray(); // tableau de Page
22 $html = '';
23 $nb_of_entries = count($breadcrumb_array);
24
25 if($nb_of_entries > 1)
26 {
27 // petite maison et flèche
28 $html .= '<nav class="breadcrumb" aria-label="Breadcrumb">' . "\n";
29 $html .= '<a href="' . new URL . '"><img src="' . $asset . '"></a><span class="arrow"> →</span>' . "\n";
30
31 // partie intermédiaire (pas de lien sur le dernier élément)
32 for($i = 0; $i < ($nb_of_entries - 1); $i++)
33 {
34 // liens optionnels
35 if($breadcrumb_array[$i]->isReachable())
36 {
37 $html .= '<a href="' . new URL(['page' => $breadcrumb_array[$i]->getPagePath()]) . '">';
38 }
39 $html .= '<span>' . $breadcrumb_array[$i]->getPageName() . '</span>';
40 if($breadcrumb_array[$i]->isReachable())
41 {
42 $html .= '</a>';
43 }
44 $html .= '<span class="arrow"> →</span>' . "\n";
45 }
46
47 // fin du chemin (=> Thésée)
48 $html .= '<span id="thesee" aria-current="page">' . $breadcrumb_array[$nb_of_entries - 1]->getPageName() . "</span>\n";
49 $html .= "</nav>\n";
50 }
51 return $html;
52 }
53} \ No newline at end of file
diff --git a/src/View/CalendarBuilder.php b/src/View/CalendarBuilder.php
new file mode 100644
index 0000000..34b1952
--- /dev/null
+++ b/src/View/CalendarBuilder.php
@@ -0,0 +1,31 @@
1<?php
2// src/View/CalendarBuilder.php
3
4declare(strict_types=1);
5
6namespace App\View;
7
8use App\Entity\Node;
9
10class CalendarBuilder extends AbstractBuilder
11{
12 public function __construct(Node $node)
13 {
14 parent::__construct($node);
15 $viewFile = self::VIEWS_PATH . $node->getName() . '.php';
16
17 $calendar_js_file = IS_ADMIN ? 'calendar_admin' : 'calendar';
18
19 if(file_exists($viewFile))
20 {
21 if(!empty($node->getNodeData()->getData()))
22 {
23 extract($node->getNodeData()->getData());
24 }
25
26 ob_start();
27 require $viewFile;
28 $this->html = ob_get_clean(); // pas de concaténation ici, on écrase
29 }
30 }
31} \ No newline at end of file
diff --git a/src/View/FooterBuilder.php b/src/View/FooterBuilder.php
new file mode 100644
index 0000000..72a571d
--- /dev/null
+++ b/src/View/FooterBuilder.php
@@ -0,0 +1,122 @@
1<?php
2// src/View/FooterBuilder.php
3
4declare(strict_types=1);
5
6namespace App\View;
7
8use App\Entity\Node;
9use App\Entity\Asset;
10use App\Controller\ViewController;
11use App\Model\Model;
12use App\Service\URL;
13
14class FooterBuilder extends AbstractBuilder
15{
16 public function __construct(Node $node)
17 {
18 $viewFile = self::VIEWS_PATH . $node->getName() . '.php';
19
20 if(file_exists($viewFile))
21 {
22 $node_data = $node->getNodeData();
23 // nom du contact, adresse et e-mail
24 if(!empty($node_data->getData()))
25 {
26 extract($node_data->getData());
27 }
28
29 $footer_logo = Asset::USER_PATH . $node_data->getAssetByRole('footer_logo')?->getFileName() ?? '';
30
31 $this->useChildrenBuilder($node);
32 $breadcrumb = $this->html;
33
34 $empty_admin_zone = '';
35 if(IS_ADMIN)
36 {
37 // données du footer
38 $admin_footer_name = '<input type="text" id="footer_name_input" class="hidden" value="' . htmlspecialchars($name ?? '') . '" placeholder="raison sociale" size="30">
39 <img id="footer_name_open" class="action_icon" src="assets/edit.svg" onclick="footer_name.open()">
40 <img id="footer_name_submit" class="action_icon hidden" src="assets/save.svg" onclick="footer_name.submit()">
41 <img id="footer_name_cancel" class="action_icon hidden" src="assets/close.svg" onclick="footer_name.cancel()">';
42 $admin_footer_address = '<input type="text" id="footer_address_input" class="hidden" value="' . htmlspecialchars($address ?? '') . '" placeholder="adresse" size="30">
43 <img id="footer_address_open" class="action_icon" src="assets/edit.svg" onclick="footer_address.open()">
44 <img id="footer_address_submit" class="action_icon hidden" src="assets/save.svg" onclick="footer_address.submit()">
45 <img id="footer_address_cancel" class="action_icon hidden" src="assets/close.svg" onclick="footer_address.cancel()">';
46 $admin_footer_email = '<input type="text" id="footer_email_input" class="hidden" value="' . htmlspecialchars($email ?? '') . '" placeholder="e-mail" size="30">
47 <img id="footer_email_open" class="action_icon" src="assets/edit.svg" onclick="footer_email.open()">
48 <img id="footer_email_submit" class="action_icon hidden" src="assets/save.svg" onclick="footer_email.submit()">
49 <img id="footer_email_cancel" class="action_icon hidden" src="assets/close.svg" onclick="footer_email.cancel()">';
50
51 $admin_footer_logo = '<input type="file" id="footer_logo_input" class="hidden" accept="image/png, image/jpeg, image/gif, image/webp, image/tiff">
52 <img id="footer_logo_open" class="action_icon" src="assets/edit.svg" onclick="footer_logo.open()">
53 <img id="footer_logo_submit" class="action_icon hidden" src="assets/save.svg" onclick="footer_logo.submit()">
54 <img id="footer_logo_cancel" class="action_icon hidden" src="assets/close.svg" onclick="footer_logo.cancel()">';
55
56 // zone admin
57 $empty_admin_zone = 'empty_admin_zone';
58 if(MainBuilder::$modif_mode){
59 $mode = 'modification de page';
60 $div_admin = 'logged_in modif_mode';
61 }
62 else{
63 $mode = 'administrateur';
64 $div_admin = 'logged_in';
65 }
66
67 $link_change_password = new URL(['page' => 'user_edit', 'from' => CURRENT_PAGE]);
68 isset($_GET['id']) ? $link_change_password->addParams(['id' => $_GET['id']]) : '';
69
70 $link_logout = new URL(['action' => 'deconnection', 'from' => CURRENT_PAGE]);
71 isset($_GET['id']) ? $link_logout->addParams(['id' => $_GET['id']]) : '';
72
73 $zone_admin = '<div class="admin_buttons_zone">
74 <p>Vous êtes en mode ' . $mode . ".</p>\n" .
75 '<div><a href="' . new URL(['page' => 'new_page']) . '"><button>Nouvelle page</button></a></div>' . "\n" .
76 $this->makePageModifModeButton() . "\n" .
77 '<div><a href="' . new URL(['page' => 'menu_paths']) . '"><button>Menu et chemins</button></a></div>' . "\n" .
78 '<div><a href="' . $link_change_password . '"><button>Mon compte</button></a></div>' . "\n" .
79 '<div><a href="' . new URL(['page' => 'maintenance']) . '"><button>Maintenance</button></a></div>' . "\n" .
80 '<div><a href="' . $link_logout . '"><button>Déconnexion</button></a></div>' . "\n" .
81 '</div>' . "\n";
82 }
83 else
84 {
85 $div_admin = 'logged_out';
86 $url = new URL(['page' => 'connection', 'from' => CURRENT_PAGE]);
87 if(Model::$page_path->getLast()->getEndOfPath() === 'article' && isset($_GET['id'])){
88 $url->addParams(['id' => $_GET['id']]);
89 }
90 $zone_admin = '<button><a href="' . $url . '">Mode admin</a></button>';
91
92 $admin_footer_name = '';
93 $admin_footer_address = '';
94 $admin_footer_email = '';
95 $admin_footer_logo = '';
96 }
97
98 ob_start();
99 require $viewFile;
100 $this->html = ob_get_clean();
101 }
102 }
103
104 private function makePageModifModeButton(): string
105 {
106 $link_edit_page = new URL(['page' => CURRENT_PAGE]);
107 if(!in_array(CURRENT_PAGE, ViewController::SPECIAL_PAGES)) // cas impossibles: 'user_edit' et 'connection' qui n'ont pas de footer
108 {
109 if(MainBuilder::$modif_mode){
110 $link_edit_label = 'Sortir du mode modification';
111 }
112 else{
113 $link_edit_page->addParams(['mode' => 'page_modif']);
114 $link_edit_label = 'Modifier la page';
115 }
116 return '<div><a href="' . $link_edit_page . '"><button>' . $link_edit_label . '</button></a></div>';
117 }
118 else{
119 return '';
120 }
121 }
122} \ No newline at end of file
diff --git a/src/View/FormBuilder.php b/src/View/FormBuilder.php
new file mode 100644
index 0000000..df5687d
--- /dev/null
+++ b/src/View/FormBuilder.php
@@ -0,0 +1,55 @@
1<?php
2// src/View/FormBuilder.php
3
4declare(strict_types=1);
5
6namespace App\View;
7
8use App\Entity\Node;
9use App\Entity\Email;
10use App\Service\Captcha;
11use App\Service\Config;
12
13class FormBuilder extends AbstractBuilder
14{
15 static private ?Captcha $captcha = null;
16
17 public function __construct(Node $node){
18 parent::__construct($node);
19
20 if(!empty($node->getNodeData()->getData())){
21 extract($node->getNodeData()->getData());
22 }
23
24 // un seul captcha à la fois!
25 if(!self::$captcha){
26 self::$captcha = new Captcha;
27 $_SESSION['captcha'] = self::$captcha->getSolution();
28 }
29
30 // données stockées en vrac dans du JSON et récupérées avec extract => changer ça un jour
31 $smtp_host = $smtp_host ?? Config::$smtp_host;
32 $smtp_secure = $smtp_secure ?? Config::$smtp_secure;
33 $smtp_username = $smtp_username ?? Config::$smtp_username;
34 $email_dest = $email_dest ?? Config::$email_dest;
35 $keep_emails = (bool)($keep_emails ?? false); // (bool) est inutile mais plus clair
36 $retention_period = $this->getRetentionPeriod($retention_period ?? null, Email::DEFAULT_RETENTION_PERIOD);
37 $retention_period_sensible = $this->getRetentionPeriod($retention_period_sensible ?? null, Email::DEFAULT_RETENTION_PERIOD_SENSITIVE);
38
39 $admin_content = '';
40 if(IS_ADMIN){
41 ob_start();
42 require self::VIEWS_PATH . 'form_admin.php';
43 $admin_content = ob_get_clean();
44 }
45
46 ob_start();
47 require self::VIEWS_PATH . $node->getName() . '.php';
48 $this->html = ob_get_clean(); // pas de concaténation ici, on écrase
49 }
50
51 private function getRetentionPeriod(mixed $period, int $default_period): int
52 {
53 return ($period === null || (int)$period <= 0) ? $default_period : (int)$period; // (int) est nécessaire à cause du stockage JSON
54 }
55} \ No newline at end of file
diff --git a/src/View/GaleryBuilder.php b/src/View/GaleryBuilder.php
new file mode 100644
index 0000000..436cc36
--- /dev/null
+++ b/src/View/GaleryBuilder.php
@@ -0,0 +1,72 @@
1<?php
2// src/View/GaleryBuilder.php
3
4declare(strict_types=1);
5
6namespace App\View;
7
8use App\Entity\Node;
9
10class GaleryBuilder extends AbstractBuilder
11{
12 public function __construct(Node $node)
13 {
14 parent::__construct($node);
15 $viewFile = self::VIEWS_PATH . $node->getName() . '.php';
16
17 if(file_exists($viewFile))
18 {
19 if(!empty($node->getNodeData()->getData()))
20 {
21 extract($node->getNodeData()->getData());
22 }
23
24 // ajouter un article
25 $new_article = '';
26 if(IS_ADMIN)
27 {
28 $id = 'n' . $this->id_node;
29 $js = 'onclick="openEditor(\'' . $id . '\')"';
30
31 $share_button = '<p class="share hidden"><img class="action_icon" src="assets/share.svg"></p>';
32 $html = '';
33
34 $new_button = '<p id="new-' . $id . '">' . "\n" .
35 '<button ' . $js . '><img class="action_icon" src="assets/edit.svg">Nouvel article</button></p>';
36
37 $modify_js = 'onclick="openEditor(\'' . $id . '\')"';
38 $modify_article = '<p id="edit-' . $id . '" class="hidden"><img class="action_icon" src="assets/edit.svg" ' . $modify_js . '></p>' . "\n";
39
40 $up_js = 'onclick="switchPositions(\'' . $id . '\', \'up\')"';
41 $up_button = '<p id="position_up-' . $id . '" class="hidden"><img class="action_icon" src="assets/arrow-up.svg" ' . $up_js . '></p>' . "\n";
42
43 $down_js = 'onclick="switchPositions(\'' . $id . '\', \'down\')"';
44 $down_button = '<p id="position_down-' . $id . '" class="hidden"><img class="action_icon" src="assets/arrow-down.svg" ' . $down_js . '></p>' . "\n";
45
46 $delete_js = 'onclick="deleteArticle(\'' . $id . '\')"';
47 $delete_article = '<p id="delete-' . $id . '" class="hidden"><img class="action_icon" src="assets/delete-bin.svg" ' . $delete_js . '></p>' . "\n";
48
49 $close_js = 'onclick="closeEditor(\'' . $id . '\')"';
50 $close_editor = '<p id="cancel-' . $id . '" class="hidden"><button ' . $close_js . '>Annuler</button></p>';
51
52 $submit_js = 'onclick="submitArticle(\'' . $id . '\', \'\', clone' . $this->id_node . ')"';
53 $submit_article = '<p id="submit-' . $id . '" class="hidden"><button ' . $submit_js . '>Valider</button></p>';
54
55 $admin_buttons = $new_button . $modify_article . $up_button . $down_button . $delete_article . $close_editor . $submit_article;
56
57 // squelette d'un nouvel article
58 ob_start();
59 require self::VIEWS_PATH . 'post.php';
60 $new_article = ob_get_clean();
61 }
62
63 // articles existants
64 $this->useChildrenBuilder($node);
65 $content = $this->html;
66
67 ob_start();
68 require $viewFile;
69 $this->html = ob_get_clean(); // pas de concaténation ici, on écrase
70 }
71 }
72} \ No newline at end of file
diff --git a/src/View/HeadBuilder.php b/src/View/HeadBuilder.php
new file mode 100644
index 0000000..4d63457
--- /dev/null
+++ b/src/View/HeadBuilder.php
@@ -0,0 +1,84 @@
1<?php
2// src/View/HeadBuilder.php
3
4declare(strict_types=1);
5
6namespace App\View;
7
8use App\Entity\Asset;
9use App\Entity\Node;
10use App\Model\Model;
11
12class HeadBuilder extends AbstractBuilder
13{
14 public function __construct(Node $node)
15 {
16 $viewFile = self::VIEWS_PATH . $node->getName() . '.php';
17
18 if(file_exists($viewFile)){
19 // css et js
20 $page = Model::$page_path->getLast();
21
22 $css = '';
23 foreach($page->getCSS() as $name){
24 $css .= self::insertCSS($name);
25 }
26
27 $js = '';
28 foreach($page->getJS() as $name){
29 $js .= self::insertJS($name);
30 }
31
32 if(MainBuilder::$modif_mode){
33 $css .= self::insertCSS('modif_page');
34 $js .= self::insertJS('modif_page');
35 }
36
37 if(IS_ADMIN){
38 // édition éléments sur toutes les pages (header, footer et favicon)
39 $js .= self::insertJS('Input');
40
41 // sert partout?
42 $js .= self::insertJS('Fetcher');
43
44 // tinymce, nécéssite un script post-install et post-update dans composer.json
45 $css .= self::insertCSS('tinymce');
46 $js .= self::insertJS('tinymce/tinymce.min');
47 $js .= self::insertJS('tinymce');
48 }
49
50 $title = Model::$page_path->getLast()->getPageName();
51 $description = Model::$page_path->getLast()->getDescription();
52
53 // favicon
54 // ?-> est l'opérateur de navigation sécurisée => LOVE!
55 $favicon_name = ($favicon_object = $node->getNodeData()->getAssetByRole('head_favicon'))?->getFileName();
56 $favicon = $favicon_name ? Asset::USER_PATH . $favicon_name : '';
57 $favicon_type = $favicon_object?->getMimeType() ?? '';
58
59 ob_start();
60 require $viewFile;
61 $this->html .= ob_get_clean();
62 }
63 }
64
65 static function insertCSS(string $name): string
66 {
67 return '<link rel="stylesheet" href="' . self::versionedFileURL('css', $name) . '">' . "\n";
68 }
69 static function insertJS(string $name, bool $module = false): string
70 {
71 return '<script ' . ($module ? 'type="module"' : '') . ' src="' . self::versionedFileURL('js', $name) . '"></script>' . "\n";
72 }
73
74 static function versionedFileURL(string $type, string $filename): string
75 {
76 $path = $type . '/' . $filename . '.' . $type;
77
78 if(file_exists($path)){
79 $version = substr(md5_file($path), 0, 8);
80 return $path . '?v=' . $version;
81 }
82 return $path; // sécurité fichier absent
83 }
84}
diff --git a/src/View/HeaderBuilder.php b/src/View/HeaderBuilder.php
new file mode 100644
index 0000000..14f5ffa
--- /dev/null
+++ b/src/View/HeaderBuilder.php
@@ -0,0 +1,131 @@
1<?php
2// src/View/HeaderBuilder.php
3
4declare(strict_types=1);
5
6namespace App\View;
7
8use App\Entity\Node;
9use App\Entity\NodeData;
10use App\Entity\Asset;
11
12class HeaderBuilder extends AbstractBuilder
13{
14 //private ?Node $nav = null;
15 //private ?Node $breadcrumb = null;
16 const ICON_PATH = 'icons/';
17
18 public function __construct(Node $node)
19 {
20 // pas de useChildrenBuilder, il faudrait peut-être
21 $children = $node->getChildren();
22 foreach($children as $child)
23 {
24 if($child->getName() === 'nav'){
25 //$this->nav = $child;
26 // actuellement le noeud nav ne contient aucune info utile et l'envoyer à NavBuilder est inutile
27 $nav_builder = new NavBuilder(/*$this->nav*/);
28
29 $nav = $nav_builder->render();
30 }
31 elseif($child->getName() === 'breadcrumb'){
32 //$this->breadcrumb = $child;
33 $breadcrumb_builder = new BreadcrumbBuilder(/*$this->breadcrumb*/);
34 $breadcrumb = $breadcrumb_builder->render();
35 }
36 }
37
38 $viewFile = self::VIEWS_PATH . $node->getName() . '.php';
39
40 if(file_exists($viewFile))
41 {
42 $node_data = $node->getNodeData();
43 // titre et description
44 if(!empty($node_data->getData()))
45 {
46 extract($node_data->getData());
47 }
48
49 // réseaux sociaux + logo dans l'entête
50 // ?-> est l'opérateur de chainage optionnel
51 $header_logo = Asset::USER_PATH . $node_data->getAssetByRole('header_logo')?->getFileName() ?? '';
52 $header_background_name = $node_data->getAssetByRole('header_background')?->getFileName();
53 $header_background = $header_background_name ? Asset::USER_PATH . $header_background_name : '';
54 $social_networks = '';
55
56 // boutons mode admin
57 if(IS_ADMIN){
58 // assets dans classe header_additional_inputs
59 $admin_head_favicon = '<input type="file" id="head_favicon_input" class="hidden" accept="image/png, image/jpeg, image/gif, image/webp, image/tiff, image/x-icon, image/bmp">
60 <button id="head_favicon_open" onclick="head_favicon.open()"><img id="head_favicon_content" class="action_icon"> Favicon</button>
61 <img id="head_favicon_submit" class="action_icon hidden" src="assets/save.svg" onclick="head_favicon.submit()">
62 <img id="head_favicon_cancel" class="action_icon hidden" src="assets/close.svg" onclick="head_favicon.cancel()">';
63 $admin_header_background = '<input type="file" id="header_background_input" class="hidden" accept="image/png, image/jpeg, image/gif, image/webp, image/tiff">
64 <button id="header_background_open" onclick="header_background.open()"><img id="header_background_content" class="background_button" src="' . $header_background . '"> Image de fond</button>
65 <img id="header_background_submit" class="action_icon hidden" src="assets/save.svg" onclick="header_background.submit()">
66 <img id="header_background_cancel" class="action_icon hidden" src="assets/close.svg" onclick="header_background.cancel()">';
67
68 // asset dans classe header_content
69 $admin_header_logo = '<input type="file" id="header_logo_input" class="hidden" accept="image/png, image/jpeg, image/gif, image/webp, image/tiff">
70 <img id="header_logo_open" class="action_icon" src="assets/edit.svg" onclick="header_logo.open()">
71 <img id="header_logo_submit" class="action_icon hidden" src="assets/save.svg" onclick="header_logo.submit()">
72 <img id="header_logo_cancel" class="action_icon hidden" src="assets/close.svg" onclick="header_logo.cancel()">';
73
74 // texte dans classe header_content
75 $admin_header_title = '<input type="text" id="header_title_input" class="hidden" value="' . htmlspecialchars($title ?? '') . '" placeholder="nom du site web" size="30">
76 <img id="header_title_open" class="action_icon" src="assets/edit.svg" onclick="header_title.open()">
77 <img id="header_title_submit" class="action_icon hidden" src="assets/save.svg" onclick="header_title.submit()">
78 <img id="header_title_cancel" class="action_icon hidden" src="assets/close.svg" onclick="header_title.cancel()">';
79 $admin_header_description = '<input type="text" id="header_description_input" class="hidden" value="' . htmlspecialchars($description ?? '') . '" placeholder="sous-titre ou description" size="30">
80 <img id="header_description_open" class="action_icon" src="assets/edit.svg" onclick="header_description.open()">
81 <img id="header_description_submit" class="action_icon hidden" src="assets/save.svg" onclick="header_description.submit()">
82 <img id="header_description_cancel" class="action_icon hidden" src="assets/close.svg" onclick="header_description.cancel()">';
83
84 // icônes réseaux sociaux
85 // boucle sur la liste complète de réseaux sociaux
86 foreach(NodeData::$social_networks as $network){
87 $checked = (isset($social_show[$network]) && $social_show[$network]) ? 'checked' : '';
88 $href = (isset($social[$network]) && $social[$network] !== '') ? 'href="' . $social[$network] . '"' : '';
89
90 $social_networks .= '<div id="header_' . $network . '">
91 <input type="checkbox" onclick="checkSocialNetwork(\'header_' . $network . '\')" ' . $checked . '>
92 <a ' . $href . ' target="_blank" rel="noopener noreferrer">'
93 . $this->insertSVG(self::ICON_PATH . $network . '.svg', ['id' => 'header_' . $network . '_content', 'class' => ($checked ? 'svg_fill_red' : '')])
94 . '</a>
95 <input type="url" id="header_' . $network . '_input" class="hidden" value="' . ($social[$network] ?? "") . '" placeholder="https://..." size="30" onchange="controlURL(this)">
96 <img id="header_' . $network . '_open" class="action_icon" src="assets/edit.svg" onclick="header_' . $network . '.open()">
97 <img id="header_' . $network . '_submit" class="action_icon hidden" src="assets/save.svg" onclick="header_' . $network . '.submit()">
98 <img id="header_' . $network . '_cancel" class="action_icon hidden" src="assets/close.svg" onclick="header_' . $network . '.cancel()">
99 <script>let header_' . $network . ' = new InputTextSocialNetwork(\'header_' . $network . '\', {\'has_content\': false});</script>
100 </div>';
101 // {'has_content': false} => InputToggle ne gèrera pas cette balise
102 }
103 }
104 else{
105 $admin_head_favicon = '';
106 $admin_header_background = '';
107 $admin_header_logo = '';
108 $admin_header_title = '';
109 $admin_header_description = '';
110
111 if(isset($social_show)){
112 // boucle sur les réseaux sociaux "activés"
113 foreach(array_keys($social_show) as $network){
114 if($social_show[$network]){
115 $href = (isset($social[$network]) && $social[$network] !== '') ? 'href="' . $social[$network] . '"' : '';
116 $social_networks .= '<div id="header_' . $network . '">
117 <a ' . $href . ' target="_blank" rel="noopener noreferrer">'
118 . $this->insertSVG(self::ICON_PATH . $network . '.svg', ['id' => 'header_' . $network . '_content','class' => 'svg_fill_red'])
119 . '</a>
120 </div>';
121 }
122 }
123 }
124 }
125
126 ob_start();
127 require $viewFile;
128 $this->html .= ob_get_clean();
129 }
130 }
131} \ No newline at end of file
diff --git a/src/View/LoginBuilder.php b/src/View/LoginBuilder.php
new file mode 100644
index 0000000..3a303b0
--- /dev/null
+++ b/src/View/LoginBuilder.php
@@ -0,0 +1,56 @@
1<?php
2// src/View/LoginBuilder.php
3
4declare(strict_types=1);
5
6namespace App\View;
7
8use App\Entity\Node;
9use App\Service\URL;
10use App\Service\Captcha;
11
12class LoginBuilder extends AbstractBuilder
13{
14 public function __construct(Node $node)
15 {
16 // déjà connecté?
17 if(IS_ADMIN)
18 {
19 header('Location: ' . new URL);
20 die;
21 }
22
23 $viewFile = self::VIEWS_PATH . $node->getName() . '.php';
24
25 $captcha = new Captcha;
26 $_SESSION['captcha'] = $captcha->getSolution(); // enregistrement de la réponse du captcha pour vérification
27
28 //$this->html .= $header;
29
30 $error_messages = [
31 'error_non_valid_captcha' => 'Erreur au test anti-robot, veuillez saisir un nombre entier.',
32 'captcha_server_error' => 'captcha_server_error',
33 'bad_solution_captcha' => 'Erreur au test anti-robot, veuillez réessayer.',
34 'bad_login_or_password' => 'Mauvais identifiant ou mot de passe, veuillez réessayer.', // ne pas indiquer où est l'erreur
35 'forbidden_characters' => 'Caractères interdits: espaces, tabulations, sauts CR/LF.'
36 ];
37
38 $link_form = new URL(['action' => 'connection']);
39 isset($_GET['from']) ? $link_form->addParams(['from' => $_GET['from']]) : '';
40 isset($_GET['id']) ? $link_form->addParams(['id' => $_GET['id']]) : '';
41
42 $link_exit = new URL;
43 isset($_GET['from']) ? $link_exit->addParams(['page' => $_GET['from'] ]) : '';
44 isset($_GET['id']) ? $link_exit->addParams(['id' => $_GET['id']]) : '';
45
46 $error = isset($_GET['error']) ? $error_messages[$_GET['error']] : '';
47
48 ob_start();
49 require $viewFile;
50 $this->html = ob_get_clean(); // nouveau contenu
51
52 //$this->html .= <p style='color: red;'>Ce site utilise un cookie « obligatoire » lorsque vous êtes connecté ainsi que sur cette page.<br>Il sera supprimé à votre déconnexion ou dès que vous aurez quitté le site.</p>;
53
54 //$this->html .= $footer;
55 }
56}
diff --git a/src/View/MainBuilder.php b/src/View/MainBuilder.php
new file mode 100644
index 0000000..b5b9b00
--- /dev/null
+++ b/src/View/MainBuilder.php
@@ -0,0 +1,99 @@
1<?php
2// src/View/MainBuilder.php
3
4declare(strict_types=1);
5
6namespace App\View;
7
8use App\Entity\Article;
9use App\Entity\Node;
10use App\Entity\Presentation;
11use App\Model\Model;
12use App\Service\URL;
13
14class MainBuilder extends AbstractBuilder
15{
16 static public bool $modif_mode = false;
17
18 public function __construct(Node $node)
19 {
20 $this->html .= "<main>\n";
21
22 // page article: cas particulier où l'article est greffé sur main
23 if(Model::$page_path->getLast()->getEndOfPath() === 'article'){
24 // pas censé arriver
25 if(!isset($_GET['id'])){
26 header('Location: ' . new URL);
27 die;
28 }
29
30 // nouvel article
31 if($node->getAdoptedChild() == null){
32 $date = new \DateTime;
33 $article = new Article('', $date);
34 $new = new Node('new', 0, null, null, $article);
35 }
36 // modification
37 else{
38 $new = $node->getAdoptedChild();
39 }
40 //$builder_name = 'App\View\\' . $this->snakeToPascalCase($new->getName()) . 'Builder';
41 $builder_name = 'App\View\NewBuilder';
42 $builder = new $builder_name($new);
43 $this->html .= $builder->render();
44 }
45 else{
46 // si action = "modif_page", affiche des commandes supplémentaires
47 if(IS_ADMIN && self::$modif_mode){
48 // ajouter un contrôle du champ in_menu
49 $this->viewEditBlocks($node);
50 }
51
52 // dans tous les cas
53 $this->useChildrenBuilder($node);
54 }
55
56 $this->html .= "</main>\n";
57 }
58
59 // mode modification de page uniquement
60 private function viewEditBlocks(Node $node): void
61 {
62 $options = '';
63 foreach(Presentation::BLOCKS as $key => $value){
64 $options .= '<option value= "' . $key . '">' . $value . "</option>\n";
65 }
66
67 // ceci pourrait être déplacé au début des blocs
68 $bloc_edit = '';
69 foreach($node->getChildren() as $child_node){
70 // ordre des articles 'news'
71 if($child_node->getName() === 'news_block'){
72 $order = $child_node->getNodeData()->getChronoOrder() ? 'chrono' : 'antichrono';
73 }
74
75 // présentation par défaut
76 if(Presentation::hasPresentation($child_node->getName()) && $child_node->getNodeData()->getPresentation() === null){
77 $child_node->getNodeData()->setPresentation('full_width'); // pas de persistence ici
78 }
79
80 ob_start();
81 require self::VIEWS_PATH . 'modify_block.php';
82 $bloc_edit .= ob_get_clean();
83 }
84
85 ob_start();
86 require self::VIEWS_PATH . 'modify_page.php';
87 $this->html .= ob_get_clean();
88 }
89
90 // utilisée dans modify_block.php
91 private function makePresentationOptions(string $presentation): string
92 {
93 $options = '';
94 foreach(Presentation::PRESENTATIONS as $key => $value){
95 $options .= '<option value="' . $key . '" ' . ($presentation === $key ? 'selected' : '') . '>' . $value . '</option>';
96 }
97 return $options;
98 }
99} \ No newline at end of file
diff --git a/src/View/MaintenanceBuilder.php b/src/View/MaintenanceBuilder.php
new file mode 100644
index 0000000..13d391c
--- /dev/null
+++ b/src/View/MaintenanceBuilder.php
@@ -0,0 +1,36 @@
1<?php
2// src/View/MaintenanceBuilder.php
3
4declare(strict_types=1);
5
6namespace App\View;
7
8use App\Entity\Node;
9use App\Service\Backup;
10
11class MaintenanceBuilder extends AbstractBuilder
12{
13 public function __construct(Node $node){
14 $viewFile = self::VIEWS_PATH . $node->getName() . '.php';
15
16 // noter qu'un backup a été créé dans UserController::connect()
17 try{
18 $backup_array = Backup::getBackupList();
19 }
20 catch(\RuntimeException $e){
21 $backup_array = [];
22 $_SESSION['flash_message'] = $e->getMessage(); // peut écraser une flash error déjà dans $_SESSION['flash_message']
23 }
24
25 $backup_options = '';
26 for($i = count($backup_array) - 1; $i >= 0; $i--){
27 $backup_options .= '<option value="' . $backup_array[$i] . '">' . $backup_array[$i] . '</option>';
28 }
29
30 if(file_exists($viewFile)){
31 ob_start();
32 require $viewFile;
33 $this->html = ob_get_clean();
34 }
35 }
36} \ No newline at end of file
diff --git a/src/View/MenuBuilder.php b/src/View/MenuBuilder.php
new file mode 100644
index 0000000..f14cea7
--- /dev/null
+++ b/src/View/MenuBuilder.php
@@ -0,0 +1,101 @@
1<?php
2// src/View/MenuBuilder.php
3//
4// page Menu et chemins en mode admin, fonctionne avec menu.js
5
6declare(strict_types=1);
7
8namespace App\View;
9
10use App\Entity\Node;
11use App\Entity\Page;
12use App\Model\Model;
13use App\Service\URL;
14
15class MenuBuilder extends AbstractBuilder
16{
17 //private int $margin_left_multiplier = 29;
18 private string $options = '';
19
20 public function __construct(?Node $node, bool $template = true)
21 {
22 // $node peut $etre null parce qu'on ne construit pas de page dans MenuAndPathsController, on ne lit pas la table "node", etc
23 $viewFile = $node === null ? self::VIEWS_PATH . 'menu.php' : self::VIEWS_PATH . $node->getName() . '.php';
24
25 if(file_exists($viewFile))
26 {
27 if(IS_ADMIN){
28 $this->unfoldMenu(Model::$menu);
29
30 if($template){
31 $this->unfoldOptions(Model::$menu);
32 }
33 }
34 else{
35 header('Location: ' . new URL);
36 die;
37 }
38
39 // si faux, n'utilise pas le template
40 if($template){
41 ob_start();
42 require $viewFile; // insertion de $this->html généré par unfoldMenu
43 $this->html = ob_get_clean(); // pas de concaténation .= cette fois on écrase
44 }
45 }
46 }
47
48 private function unfoldMenu(Page $page): void
49 {
50 $this->html .= '<div class="level">' . "\n";
51
52 foreach($page->getChildren() as $entry)
53 {
54 $checked = $entry->isHidden() ? '' : 'checked';
55 $this->html .= '<div id="' . $entry->getId() . '" class="menu_edit_entry">
56 <img class="move_entry_icon" onclick="moveOneLevelUp(' . $entry->getId() . ')" src="assets/arrow-left.svg">
57 <img class="move_entry_icon" onclick="moveOneLevelDown(' . $entry->getId() . ')" src="assets/arrow-right.svg">
58 <img class="move_entry_icon" onclick="switchMenuPositions(' . $entry->getId() . ', \'up\')" src="assets/arrow-up.svg">
59 <img class="move_entry_icon" onclick="switchMenuPositions(' . $entry->getId() . ', \'down\')" src="assets/arrow-down.svg">
60 <span class="menu_entry_checkbox">
61 <input type="checkbox" ' . $checked . ' onclick="checkMenuEntry(' . $entry->getId() . ')">
62 </span>';
63
64 // entrées URL: bouton poubelle + nom du lien + adresse
65 if(str_starts_with($entry->getEndOfPath(), 'http')){
66 $this->html .= '<form style="display: inline;" id="delete-i' . $entry->getId() . '" method="post" action="' . new URL(['from' => 'menu_paths']) . '">
67 <input type="hidden" name="delete" value="' . $entry->getId() . '">
68 <input type="image" class="move_entry_icon" src="assets/delete-bin.svg" alt="delete link button" onclick="return confirm(\'Voulez-vous vraiment supprimer cette entrée?\');">
69 </form>
70 <span class="url_name">
71 <button class="url_name"><input type="text" value="' . htmlspecialchars($entry->getPageName()) . '"></button>
72 <img class="move_entry_icon" src="assets/save.svg" onclick="editUrl(' . $entry->getId() . ', \'url_name\')">
73 </span>
74 <span class="url_content">
75 <input type="url" value="' . htmlspecialchars($entry->getEndOfPath()) . '" placeholder="https://..." onchange="controlURL(this)">
76 <img class="move_entry_icon" src="assets/save.svg" onclick="editUrl(' . $entry->getId() . ', \'url_content\')">
77 </span>';
78 }
79 else{
80 $this->html .= '<button>' . $entry->getPageName() . '</button>
81 <i class="path">' . $entry->getPagePath() . '</i>';
82 }
83
84 if(count($entry->getChildren()) > 0){
85 $this->unfoldMenu($entry);
86 }
87 $this->html .= '</div>' . "\n";
88 }
89 $this->html .= "</div>\n";
90 }
91
92 private function unfoldOptions(Page $page): void
93 {
94 foreach($page->getChildren() as $entry){
95 $this->options .= '<option value="' . $entry->getId() . '">' . $entry->getPageName() . "</option>\n";
96 if(count($entry->getChildren()) > 0){
97 $this->unfoldOptions($entry);
98 }
99 }
100 }
101} \ No newline at end of file
diff --git a/src/View/NavBuilder.php b/src/View/NavBuilder.php
new file mode 100644
index 0000000..2e3cdbd
--- /dev/null
+++ b/src/View/NavBuilder.php
@@ -0,0 +1,77 @@
1<?php
2// src/View/NavBuilder.php
3//
4// menu principal
5
6declare(strict_types=1);
7
8namespace App\View;
9
10//use App\Entity\Node;
11use App\Entity\Page;
12use App\Model\Model;
13use App\Service\URL;
14
15class NavBuilder extends AbstractBuilder
16{
17 public function __construct(/*?Node $node = null*/)
18 {
19 $this->html .= '<nav class="nav_main"><ul>';
20 if(count(Model::$menu->getChildren()) > 1 || !Model::$menu->getChildren()[0]->getChildren()->isEmpty()){
21 $this->html .= $this->navMainHTML(
22 Model::$menu, // Menu étend Page
23 Model::$page_path != null ? Model::$page_path->getArray() : []); // param nullable, ça retire une dépendance stricte entre NavBuilder et Model
24 }
25 $this->html .= '</ul></nav>';
26 }
27
28 private function navMainHTML(Page $nav_data, array $current): string
29 {
30 $nav_html = '';
31 static $level = 0;
32
33 foreach($nav_data->getChildren() as $data)
34 {
35 if(!$data->isHidden()){
36 $li_class = '';
37 if(isset($current[$level]) && $data->getEndOfPath() === $current[$level]->getEndOfPath()){
38 $li_class = 'current ';
39 }
40
41 $link = '';
42 if($data->isReachable()) // titre de catégorie du menu non clicable
43 {
44 if(str_starts_with($data->getEndOfPath(), 'http')) // lien vers autre site
45 {
46 $link .= '<a href="' . htmlspecialchars($data->getEndOfPath()) . '" target="_blank">';
47 }
48 elseif($data->getEndOfPath() != '') // lien relatif
49 {
50 $link .= '<a href="' . new URL(['page' => $data->getPagePath()]) . '">';
51 }
52 }
53 else{
54 $link .= '<a>';
55 }
56
57 if(count($data->getChildren()) > 0) // titre de catégorie
58 {
59 $li_class .= $data->getParent() == null ? 'drop-down' : 'drop-right';
60
61 $nav_html .= '<li class="'. $li_class . '">' . $link . '<p id="m_' . $data->getId() . '">' . $data->getPageName() . '</p></a>
62 <button class="sub-menu-toggle" aria-label="Ouvrir le sous-menu">▼</button>
63 <ul class="sub-menu">' . "\n";
64 $level++;
65 $nav_html .= $this->navMainHTML($data, $current);
66 $level--;
67 $nav_html .= '</ul></li>' . "\n";
68 }
69 else
70 {
71 $nav_html .= '<li class="'. $li_class . '">' . $link . '<p id="m_' . $data->getId() . '">' . $data->getPageName() . '</p></a></li>' . "\n";
72 }
73 }
74 }
75 return $nav_html;
76 }
77} \ No newline at end of file
diff --git a/src/View/NewBuilder.php b/src/View/NewBuilder.php
new file mode 100644
index 0000000..598bb1c
--- /dev/null
+++ b/src/View/NewBuilder.php
@@ -0,0 +1,157 @@
1<?php
2// src/View/NewBuilder.php
3//
4// new = nouvelle (du journal)
5
6declare(strict_types=1);
7
8namespace App\View;
9
10use App\Entity\Node;
11use App\Service\URL;
12
13class NewBuilder extends AbstractBuilder
14{
15 static public bool $new_article_mode = false;
16
17 public function __construct(Node $node)
18 {
19 $viewFile = self::VIEWS_PATH . $node->getName() . '.php';
20
21 if(file_exists($viewFile))
22 {
23 if(self::$new_article_mode){
24 $id = $_GET['id']; // ici l'id est le nom du block news_block parent
25 $title = '';
26 $preview = '';
27
28 // lettre au début de l'id: t = title, p = preview, i = article, d = date
29 $id_title = $id;
30 $id_title[0] = 't';
31 $id_preview = $id;
32 $id_preview[0] = 'p';
33 $id_content = 'i' . $id;
34 $id_content[0] = 'i';
35 $id_date = $id;
36 $id_date[0] = 'd';
37 }
38 else{
39 $id = (string)$node->getId();
40
41 // html, date
42 $title = $node->getArticle()->getTitle();
43 $preview = $node->getArticle()->getPreview();
44
45 // lettre au début de l'id: t = title, p = preview, i = article, d = date
46 $id_title = 't' . $id;
47 $id_preview = 'p' . $id;
48 $id_content = 'i' . $id;
49 $id_date = 'd' . $id;
50 }
51
52 $content = '';
53 if(CURRENT_PAGE === 'article'){
54 $content = $node->getArticle()->getContent();
55 $from_to_button = '<p><a class="link_to_article" href="' . new URL(isset($_GET['from']) ? ['page' => $_GET['from']] : []) . '"><button>Retour</button></a></p>';
56 $overflow = '';
57 }
58 else{
59 $from_to_button = '<p><a class="link_to_article" href="' . new URL(['page' => 'article', 'id' => $id, 'from' => CURRENT_PAGE]) . '"><button><img class="action_icon" src="assets/book-open.svg">Lire la suite</button></a></p>';
60 $overflow = ' overflow_hidden';
61 }
62
63 $date = $node->getArticle()->getDateTime()->format('Y-m-d\TH:i:s.v\Z'); // format: 2025-07-17T13:54:00.000Z
64 // format(\DateTime::ATOM) produit le format: 2025-10-10T12:17:00+00:00, c'est aussi de la norme ISO, mais à éviter pour être compatible avec date.toISOString en JS
65
66 // partage
67 $share_link = new URL(['page' => 'article', 'id' => $id]);
68 $share_js = 'onclick="copyInClipBoard(\'' . $share_link . '\')"';
69 if(isset($_GET['id']) && $_GET['id'][0] === 'n'){
70 $class = 'class="share hidden"';
71 }
72 else{
73 $class = 'class="share"';
74 }
75 $share_button = '<p ' . $class . ' ' . $share_js . '><img class="action_icon" src="assets/share.svg"></p>' . "\n";
76
77 // modifier un article
78 $title_buttons = '';
79 $preview_buttons = '';
80 $article_buttons = '';
81 $date_buttons = '';
82 $admin_buttons = '';
83 if(IS_ADMIN){
84 if(CURRENT_PAGE === 'article'){
85 $title_js = 'onclick="openEditor(\'' . $id_title . '\')"';
86 $modify_title = '<p id="edit-' . $id_title . '"><button ' . $title_js . '><img class="action_icon" src="assets/edit.svg">Titre</button></p>' . "\n";
87 $close_js_title = 'onclick="closeEditor(\'' . $id_title . '\')"';
88 $close_editor_title = '<p id="cancel-' . $id_title . '" class="hidden"><button ' . $close_js_title . '>Annuler</button></p>';
89 $submit_js_title = 'onclick="submitArticle(\'' . $id_title . '\')"';
90 $submit_title = '<p id="submit-' . $id_title . '" class="hidden"><button ' . $submit_js_title . '>Valider</button></p>';
91 $title_buttons = '<div class="button_zone">' . $modify_title . $close_editor_title . $submit_title . '</div>';
92
93 $preview_js = 'onclick="openEditor(\'' . $id_preview . '\')"';
94 $modify_preview = '<p id="edit-' . $id_preview . '"><button ' . $preview_js . '><img class="action_icon" src="assets/edit.svg">Aperçu</button></a></p>' . "\n";
95 $close_js_preview = 'onclick="closeEditor(\'' . $id_preview . '\')"';
96 $close_editor_preview = '<p id="cancel-' . $id_preview . '" class="hidden"><button ' . $close_js_preview . '>Annuler</button></p>';
97 $submit_js_preview = 'onclick="submitArticle(\'' . $id_preview . '\')"';
98 $submit_preview = '<p id="submit-' . $id_preview . '" class="hidden"><button ' . $submit_js_preview . '>Valider</button></p>';
99 $preview_buttons = '<div class="button_zone">' . $modify_preview . $close_editor_preview . $submit_preview . '</div>';
100
101 $article_js = 'onclick="openEditor(\'' . $id_content . '\')"';
102 $modify_article = '<p id="edit-' . $id_content . '"><button ' . $article_js . '><img class="action_icon" src="assets/edit.svg">Article</button></p>' . "\n";
103 $close_js_article = 'onclick="closeEditor(\'' . $id_content . '\')"';
104 $close_editor_article = '<p id="cancel-' . $id_content . '" class="hidden"><button ' . $close_js_article . '>Annuler</button></p>';
105 $submit_js_article = 'onclick="submitArticle(\'' . $id_content . '\')"';
106 $submit_article = '<p id="submit-' . $id_content . '" class="hidden"><button ' . $submit_js_article . '>Valider</button></p>';
107 $article_buttons = '<div class="button_zone">' . $modify_article . $close_editor_article . $submit_article . '</div>';
108
109 $date_js = 'onclick="openDatetimeLocalInput(\'' . $id_date . '\', \'article\');';
110 $modify_date = '<p id="edit-' . $id_date . '"><button ' . $date_js . '"><img class="action_icon" src="assets/edit.svg">Date</button></p>' . "\n";
111 $close_js_date = 'onclick="closeInput(\'' . $id_date . '\')"';
112 $close_editor_date = '<p id="cancel-' . $id_date . '" class="hidden"><button ' . $close_js_date . '>Annuler</button></p>';
113 $submit_js_date = 'onclick="submitDate(\'' . $id_date . '\')"';
114 $submit_date = '<p id="submit-' . $id_date . '" class="hidden"><button ' . $submit_js_date . '>Valider</button></p>';
115 $date_buttons = '<div class="button_zone">' . $modify_date . $close_editor_date . $submit_date . '</div>';
116
117 // mode nouvel article
118 if(self::$new_article_mode){
119 $delete_article = '';
120 // valider la création d'un nouvel article
121 $submit_js = 'onclick="submitArticle(\'' . $_GET['id'] . '\')"';
122 $submit_article = '<p id="save-' . $id . '"><img class="action_icon delete_button" src="assets/save.svg" ' . $submit_js . '></p>' . "\n";
123 }
124 // mode article existant
125 else{
126 $url = new URL(['action' => 'delete_article', 'id' => $_GET['id'], 'from' => $_GET['from'] ?? '']);
127 $delete_article = '<form id="delete-' . $id . '" method="post" action="' . $url . '">
128 <p>
129 <img src="assets/delete-bin.svg" alt="Supprimer l\'article" class="action_icon" style="cursor: pointer;" onclick="if(confirm(\'Voulez-vous vraiment supprimer cet article ?\')) { this.closest(\'form\').submit(); }"
130 </p>
131 </form>' . "\n"; // this.closest('form').submit() = submit du formulaire avec javascript
132 $submit_article = '';
133 }
134
135 $admin_buttons = $share_button . $delete_article . $submit_article . $from_to_button;
136 }
137 // autre page
138 else{
139 $delete_js = 'onclick="deleteArticle(\'' . $id . '\')"';
140 $delete_article = '<p id="delete-' . $id . '"><img class="action_icon" src="assets/delete-bin.svg" ' . $delete_js . '></p>' . "\n";
141
142 $close_editor = '<p id="cancel-' . $id . '" class="hidden"></p>';
143 $submit_article = '<p id="submit-' . $id . '" class="hidden"></p>';
144
145 $admin_buttons = $from_to_button . $share_button . $delete_article . $close_editor . $submit_article;
146 }
147 }
148 else{
149 $admin_buttons = $share_button . $from_to_button;
150 }
151
152 ob_start();
153 require($viewFile);
154 $this->html .= ob_get_clean();
155 }
156 }
157}
diff --git a/src/View/NewPageBuilder.php b/src/View/NewPageBuilder.php
new file mode 100644
index 0000000..578a098
--- /dev/null
+++ b/src/View/NewPageBuilder.php
@@ -0,0 +1,53 @@
1<?php
2// src/View/NewPageBuilder.php
3//
4// page Nouvelle page en mode admin, fonctionne avec new_page.js
5
6declare(strict_types=1);
7
8namespace App\View;
9
10use App\Entity\Node;
11use App\Entity\Page;
12use App\Model\Model;
13use App\Service\URL;
14
15class NewPageBuilder extends AbstractBuilder
16{
17 //private int $margin_left_multiplier = 29;
18 private string $options = '';
19
20 public function __construct(Node $node)
21 {
22 //parent::__construct($node);
23 $viewFile = self::VIEWS_PATH . $node->getName() . '.php';
24
25 if(IS_ADMIN && file_exists($viewFile))
26 {
27 /*if(!empty($node->getNodeData()->getData()))
28 {
29 extract($node->getNodeData()->getData());
30 }*/
31
32 $this->unfoldOptions(Model::$menu);
33
34 ob_start();
35 require $viewFile; // insertion de $this->html généré par unfoldMenu
36 $this->html = ob_get_clean(); // pas de concaténation .= cette fois on écrase
37 }
38 else{
39 header('Location: ' . new URL);
40 die;
41 }
42 }
43
44 private function unfoldOptions(Page $page): void
45 {
46 foreach($page->getChildren() as $entry){
47 $this->options .= '<option value="' . $entry->getId() . '">' . $entry->getPageName() . "</option>\n";
48 if(count($entry->getChildren()) > 0){
49 $this->unfoldOptions($entry);
50 }
51 }
52 }
53} \ No newline at end of file
diff --git a/src/View/NewsBlockBuilder.php b/src/View/NewsBlockBuilder.php
new file mode 100644
index 0000000..6b3aba5
--- /dev/null
+++ b/src/View/NewsBlockBuilder.php
@@ -0,0 +1,76 @@
1<?php
2// src/View/NewsBlockBuilder.php
3
4declare(strict_types=1);
5
6namespace App\View;
7
8use App\Entity\Node;
9use App\Service\URL;
10
11class NewsBlockBuilder extends AbstractBuilder
12{
13 public function __construct(Node $node)
14 {
15 parent::__construct($node);
16
17 // à remplacer par list.php/grid.php (une vue par stratégie) le jour ou ou a besoin de les différencier
18 $viewFile = self::VIEWS_PATH . $node->getName() . '.php'; // = news_block.php, actuellement identique à post_block.php
19
20 if(file_exists($viewFile))
21 {
22 if(!empty($node->getNodeData()->getData()))
23 {
24 extract($node->getNodeData()->getData());
25 }
26
27 // stratégie d'affichage du contenu (utilisation de méthodes ou de classe List, GridPresentation, etc)
28 $section_class = $node->getNodeData()->getPresentation(); // = list, grid , mosaic ou carousel
29 $cols_min_width = '';
30 if($section_class === 'grid'){
31 $min_width = $node->getNodeData()->getColsMinWidth();
32 $cols_min_width = 'grid-template-columns: repeat(auto-fit, minmax(' . (string)$min_width . 'px, 1fr));';
33 }
34 $fetch_button_hidden = $node->getNodeData()->getNumberOfPages() > 1 ? '' : ' hidden';
35
36 // ajouter un article
37 $new_article = '';
38 if(IS_ADMIN)
39 {
40 $id = 'n' . $this->id_node;
41
42 $share_button = '<p class="share hidden"><img class="action_icon" src="assets/share.svg"></p>';
43
44 $new_button = '<p><a class="link_to_article" href="' . new URL(['page' => 'article', 'id' => $id, 'from' => CURRENT_PAGE]) . '"><button><img class="action_icon" src="assets/edit.svg">Nouvel article</button></a></p>';
45
46 $modify_js = 'onclick="openEditor(\'' . $id . '\')"';
47 $modify_article = '<p id="edit-' . $id . '" class="hidden"><img class="action_icon" src="assets/edit.svg" ' . $modify_js . '></p>' . "\n";
48
49 $delete_js = 'onclick="deleteArticle(\'' . $id . '\')"';
50 $delete_article = '<p id="delete-' . $id . '" class="hidden"><img class="action_icon" src="assets/delete-bin.svg" ' . $delete_js . '></p>' . "\n";
51
52 $close_js = 'onclick="closeEditor(\'' . $id . '\')"';
53 $close_editor = '<p id="cancel-' . $id . '" class="hidden"><button ' . $close_js . '>Annuler</button></p>';
54
55 $submit_js = 'onclick="submitArticle(\'' . $id . '\', clone' . $this->id_node . ')"';
56 $submit_article = '<p id="submit-' . $id . '" class="hidden"><button ' . $submit_js . '>Valider</button></p>';
57
58 $html = '';
59 $admin_buttons = $new_button . $modify_article . $delete_article . $close_editor . $submit_article;
60
61 // insérer post.php pour le bouton "Nouvel article", new.php pour les vrais articles
62 ob_start();
63 require self::VIEWS_PATH . 'post.php'; // nécéssite $admin_buttons et $html
64 $new_article = ob_get_clean();
65 }
66
67 // articles existants
68 $this->useChildrenBuilder($node);
69 $content = $this->html;
70
71 ob_start();
72 require $viewFile; // utilise $content
73 $this->html = ob_get_clean(); // nouveau contenu
74 }
75 }
76} \ No newline at end of file
diff --git a/src/View/PostBlockBuilder.php b/src/View/PostBlockBuilder.php
new file mode 100644
index 0000000..850364a
--- /dev/null
+++ b/src/View/PostBlockBuilder.php
@@ -0,0 +1,90 @@
1<?php
2// src/View/PostBlockBuilder.php
3
4declare(strict_types=1);
5
6namespace App\View;
7
8use App\Entity\Node;
9
10class PostBlockBuilder extends AbstractBuilder
11{
12 public function __construct(Node $node)
13 {
14 parent::__construct($node);
15
16 // à remplacer par list.php/grid.php (une vue par stratégie) le jour ou ou a besoin de les différencier
17 $viewFile = self::VIEWS_PATH . $node->getName() . '.php'; // = post_block.php, actuellement identique à news_block.php
18
19 if(file_exists($viewFile))
20 {
21 if(!empty($node->getNodeData()->getData()))
22 {
23 extract($node->getNodeData()->getData());
24 }
25
26 // stratégie d'affichage du contenu (utilisation de méthodes ou de classe List, GridPresentation, etc)
27 $section_class = $node->getNodeData()->getPresentation(); // = list, grid , mosaic ou carousel
28 $cols_min_width = '';
29 if($section_class === 'grid'){
30 $min_width = $node->getNodeData()->getColsMinWidth();
31 $cols_min_width = 'grid-template-columns: repeat(auto-fit, minmax(' . (string)$min_width . 'px, 1fr));';
32 }
33 $fetch_button_hidden = $node->getNodeData()->getNumberOfPages() > 1 ? '' : ' hidden';
34
35 // ajouter un article
36 // => fait un peu double emploi avec PostBuilder
37 $new_article = '';
38 if(IS_ADMIN)
39 {
40 $id = 'n' . $this->id_node;
41
42 $share_button = '<p class="share hidden"><img class="action_icon" src="assets/share.svg"></p>';
43
44 $new_button = '<p id="new-' . $id . '">' . "\n" . '<button onclick="openEditor(\'' . $id . '\')"><img class="action_icon" src="assets/edit.svg">Nouvel article</button></p>';
45
46 $modify_js = 'onclick="openEditor(\'' . $id . '\')"';
47 $modify_article = '<p id="edit-' . $id . '" class="hidden"><img class="action_icon" src="assets/edit.svg" ' . $modify_js . '></p>' . "\n";
48
49 $up_js = 'onclick="switchPositions(\'' . $id . '\', \'up\')"';
50 $up_button = '<p id="position_up-' . $id . '" class="hidden"><img class="action_icon" src="assets/arrow-up.svg" ' . $up_js . '></p>' . "\n";
51
52 $down_js = 'onclick="switchPositions(\'' . $id . '\', \'down\')"';
53 $down_button = '<p id="position_down-' . $id . '" class="hidden"><img class="action_icon" src="assets/arrow-down.svg" ' . $down_js . '></p>' . "\n";
54
55 $delete_js = 'onclick="deleteArticle(\'' . $id . '\')"';
56 $delete_article = '<p id="delete-' . $id . '" class="hidden"><img class="action_icon" src="assets/delete-bin.svg" ' . $delete_js . '></p>' . "\n";
57
58 $close_js = 'onclick="closeEditor(\'' . $id . '\')"';
59 $close_editor = '<p id="cancel-' . $id . '" class="hidden"><button ' . $close_js . '>Annuler</button></p>';
60
61 $position = '<div id="radio-' . $id . '" class="hidden" style="margin: 5px; font-size: 90%;">
62 Placement:<br>
63 <input type="radio" id="radio_first-' . $id . '" name="article_placement-' . $id . '" value="first" onchange="setArticlePlacement(\'' . $id . '\')">
64 <label for="radio_start">en premier</label><br>
65 <input type="radio" id="radio_last-' . $id . '" name="article_placement-' . $id . '" value="last" onchange="setArticlePlacement(\'' . $id . '\')">
66 <label for="radio_end">en dernier</label>
67 </div>';
68
69 $submit_js = 'onclick="submitArticle(\'' . $id . '\', clone' . $this->id_node . ')"';
70 $submit_article = '<p id="submit-' . $id . '" class="hidden"><button ' . $submit_js . '>Valider</button></p>';
71
72 $html = '';
73 $admin_buttons = $new_button . $modify_article . $up_button . $down_button . $delete_article . $close_editor . $submit_article . $position;
74
75 // squelette d'un nouvel article
76 ob_start();
77 require self::VIEWS_PATH . 'post.php';
78 $new_article = ob_get_clean();
79 }
80
81 // articles existants
82 $this->useChildrenBuilder($node);
83 $content = $this->html;
84
85 ob_start();
86 require $viewFile;
87 $this->html = ob_get_clean(); // pas de concaténation ici, on écrase
88 }
89 }
90} \ No newline at end of file
diff --git a/src/View/PostBuilder.php b/src/View/PostBuilder.php
new file mode 100644
index 0000000..ade7304
--- /dev/null
+++ b/src/View/PostBuilder.php
@@ -0,0 +1,60 @@
1<?php
2// src/View/PostBuilder.php
3
4declare(strict_types=1);
5
6namespace App\View;
7
8use App\Entity\Node;
9use App\Service\URL;
10
11class PostBuilder extends AbstractBuilder
12{
13 public function __construct(Node $node)
14 {
15 $viewFile = self::VIEWS_PATH . $node->getName() . '.php';
16
17 if(file_exists($viewFile))
18 {
19 // html
20 $title = $node->getArticle()->getTitle();
21 $html = $node->getArticle()->getContent();
22 $id = (string)$node->getId();
23
24 // partage
25 $share_link = new URL(['page' => CURRENT_PAGE], $id);
26 $share_js = 'onclick="copyInClipBoard(\'' . $share_link . '\')"';
27 $share_button = '<p class="share" ' . $share_js . '><img class="action_icon" src="assets/share.svg"></p>' . "\n";
28
29 // modifier un article
30 // => fait un peu double emploi avec PostBlockBuilder
31 $admin_buttons = '';
32 if(IS_ADMIN)
33 {
34 $modify_js = 'onclick="openEditor(\'' . $id . '\')"';
35 $modify_article = '<p id="edit-' . $id . '"><img class="action_icon" src="assets/edit.svg" ' . $modify_js . '></p>' . "\n";
36
37 $up_js = 'onclick="switchPositions(\'' . $id . '\', \'up\')"';
38 $up_button = '<p id="position_up-' . $id . '"><img class="action_icon" src="assets/arrow-up.svg" ' . $up_js . '></p>' . "\n";
39
40 $down_js = 'onclick="switchPositions(\'' . $id . '\', \'down\')"';
41 $down_button = '<p id="position_down-' . $id . '"><img class="action_icon" src="assets/arrow-down.svg" ' . $down_js . '></p>' . "\n";
42
43 $delete_js = 'onclick="deleteArticle(\'' . $id . '\')"';
44 $delete_article = '<p id="delete-' . $id . '"><img class="action_icon" src="assets/delete-bin.svg" ' . $delete_js . '></p>' . "\n";
45
46 $close_js = 'onclick="closeEditor(\'' . $id . '\')"';
47 $close_editor = '<p id="cancel-' . $id . '" class="hidden"><button ' . $close_js . '>Annuler</button></p>';
48
49 $submit_js = 'onclick="submitArticle(\'' . $id . '\')"';
50 $submit_article = '<p id="submit-' . $id . '" class="hidden"><button ' . $submit_js . '>Valider</button></p>';
51
52 $admin_buttons = $modify_article . $up_button . $down_button . $delete_article . $close_editor . $submit_article;
53 }
54
55 ob_start();
56 require($viewFile);
57 $this->html .= ob_get_clean();
58 }
59 }
60}
diff --git a/src/View/ShowEmailsBuilder.php b/src/View/ShowEmailsBuilder.php
new file mode 100644
index 0000000..b02174d
--- /dev/null
+++ b/src/View/ShowEmailsBuilder.php
@@ -0,0 +1,71 @@
1<?php
2// src/View/ShowEmailsBuilder.php
3
4declare(strict_types=1);
5
6namespace App\View;
7
8use App\Entity\Node;
9use App\Service\URL;
10
11class ShowEmailsBuilder extends AbstractBuilder
12{
13 public function __construct(?Node $node)
14 {
15 //parent::__construct($node);
16 $viewFile = self::VIEWS_PATH . $node->getName() . '.php';
17 if(file_exists($viewFile))
18 {
19 // objets Email groupés par destinataire
20 $emails_by_recipient = [];
21 foreach($node->getNodeData()->getEmails() as $email){
22 $recipient = $email->getRecipient();
23 $emails_by_recipient[$recipient][] = $email;
24 }
25
26 // affiche une table par destinataire
27 $emails = '';
28 foreach($emails_by_recipient as $recipient => $emails_list){
29 $html = '<h4>Destinataire: ' . $recipient . '</h4>
30 <table>
31 <thead>
32 <tr>
33 <th>Expéditeur</th>
34 <th>Adresse</th>
35 <th>Contenu</th>
36 <th>Date</th>
37 <th>Effacement prévu le</th>
38 <th>Sensible</th>
39 <th class="email_delete_button"></th>
40 </tr>
41 </thead>
42 <tbody>';
43
44 // insère les données
45 foreach($emails_list as $email){
46 $html .= '<tr id="' . $email->getId() . '">
47 <td>' . htmlspecialchars($email->getSenderName()) . '</td>
48 <td>' . htmlspecialchars($email->getSenderAddress()) . '</td>
49 <td>' . htmlspecialchars($email->getContent()) . '</td>
50 <td>' . $email->getDateTime()->format('d/m/Y') . '</td>
51 <td class="deletion_date">' . $email->getDeletionDate()->format('d/m/Y') . '</td>
52 <td><input class="make_checkbox_sensitive" type="checkbox" ' . ($email->isSensitive() ? 'checked' : '') . ' onclick="toggleSensitiveEmail(' . $email->getId() . ')"></td>
53 <td class="email_delete_button"><img class="action_icon" src="assets/delete-bin.svg" onclick="deleteEmail(' . $email->getId() . ')"></td>
54 </tr>';
55 }
56
57 $html .= '</tbody>
58 </table>';
59 $emails .= $html;
60 }
61
62 ob_start();
63 require $viewFile; // insertion de $this->html généré par unfoldMenu
64 $this->html = ob_get_clean(); // pas de concaténation .= cette fois on écrase
65 }
66 else{
67 header('Location: ' . new URL(['error' => 'show_emails_view_not_found']));
68 die;
69 }
70 }
71} \ No newline at end of file
diff --git a/src/View/UserEditBuilder.php b/src/View/UserEditBuilder.php
new file mode 100644
index 0000000..4a9fd90
--- /dev/null
+++ b/src/View/UserEditBuilder.php
@@ -0,0 +1,64 @@
1<?php
2// src/View/UserEditBuilder.php
3//
4// fonctionne avec UserController
5
6declare(strict_types=1);
7
8namespace App\View;
9
10use App\Entity\Node;
11use App\Service\URL;
12use App\Service\Captcha;
13
14class UserEditBuilder extends AbstractBuilder
15{
16 public function __construct(Node $node){
17 // pour éviter les arnaques
18 if(!IS_ADMIN){
19 header('Location: ' . new URL);
20 die;
21 }
22
23 $viewFile = self::VIEWS_PATH . $node->getName() . '.php';
24
25 $error_messages = [
26 'error_non_valid_captcha' => 'Erreur au test anti-robot, veuillez saisir un nombre entier.',
27 'captcha_server_error' => 'captcha_server_error',
28
29 'bad_login_or_password' => 'Mauvais identifiant ou mot de passe, veuillez réessayer.', // ne pas indiquer où est l'erreur
30 'bad_solution_captcha' => 'Erreur au test anti-robot, veuillez réessayer.',
31 'forbidden_characters' => 'Caractères interdits: espaces, tabulations, sauts CR/LF.',
32 'same_username_as_before' => 'Nouveau nom identique au précédent.',
33 'same_password_as_before' => 'Nouveau mot de passe identique au précédent.'
34 ];
35
36 $error_username = isset($_GET['error_username']) ? $error_messages[$_GET['error_username']] : '';
37 $success_username = (isset($_GET['success_username']) && $_GET['success_username']) ? 'Identifiant modifié avec succès.' : '';
38 $error_password = isset($_GET['error_password']) ? $error_messages[$_GET['error_password']] : '';
39 $success_password = (isset($_GET['success_password']) && $_GET['success_password']) ? 'Mot de passe modifié avec succès.' : '';
40
41 $captcha = new Captcha;
42 $_SESSION['captcha'] = $captcha->getSolution(); // enregistrement de la réponse du captcha pour vérification
43
44 $link_user_form = new URL(['action' => 'update_username']);
45 isset($_GET['from']) ? $link_user_form->addParams(['from' => $_GET['from']]) : '';
46 isset($_GET['id']) ? $link_user_form->addParams(['id' => $_GET['id']]) : '';
47
48 $link_password_form = new URL(['action' => 'update_password']);
49 isset($_GET['from']) ? $link_password_form->addParams(['from' => $_GET['from']]) : '';
50 isset($_GET['id']) ? $link_password_form->addParams(['id' => $_GET['id']]) : '';
51
52 $link_exit = new URL;
53 isset($_GET['from']) ? $link_exit->addParams(['page' => $_GET['from'] ]) : '';
54 isset($_GET['id']) ? $link_exit->addParams(['id' => $_GET['id']]) : '';
55
56 $link_logout = new URL(['action' => 'deconnection']);
57 isset($_GET['from']) ? $link_logout->addParams(['from' => $_GET['from'] ]) : '';
58 isset($_GET['id']) ? $link_logout->addParams(['id' => $_GET['id']]) : '';
59
60 ob_start();
61 require $viewFile;
62 $this->html = ob_get_clean(); // nouveau contenu
63 }
64} \ No newline at end of file
diff --git a/src/View/ViewDirector.php b/src/View/ViewDirector.php
new file mode 100644
index 0000000..af35179
--- /dev/null
+++ b/src/View/ViewDirector.php
@@ -0,0 +1,30 @@
1<?php
2// src/View/ViewDirector.php
3//
4// génère le HTML avec des Builder
5
6declare(strict_types=1);
7
8namespace App\View;
9
10use App\Entity\Node;
11
12class ViewDirector extends AbstractBuilder // ViewDirector est le premier Builder
13{
14 public Node $root_node;
15
16 public function __construct(){} // surcharge celui de AbstractBuilder
17
18 public function buildHTML(Node $root_node): string
19 {
20 $this->root_node = $root_node;
21 $this->useChildrenBuilder($this->root_node);
22
23 if(isset($_SESSION['flash_message'])){
24 $this->html .= '<script>window.flash_message = "' . $_SESSION['flash_message'] . '";</script>';
25 unset($_SESSION['flash_message']);
26 }
27
28 return $this->html;
29 }
30} \ No newline at end of file
diff --git a/src/View/templates/calendar.php b/src/View/templates/calendar.php
new file mode 100644
index 0000000..fd920fd
--- /dev/null
+++ b/src/View/templates/calendar.php
@@ -0,0 +1,14 @@
1<?php
2declare(strict_types=1);
3use App\View\HeadBuilder;
4?>
5<section class="calendar" id="<?= $this->id_node ?>">
6 <?= HeadBuilder::insertJS('fullcalendar/index.global.min') ?>
7 <?= HeadBuilder::insertJS('fullcalendar/fr.global.min') ?>
8 <?= HeadBuilder::insertJS($calendar_js_file) ?>
9 <h3><?= $title ?></h3>
10 <div id="calendar_zone">
11 <div id="calendar"></div>
12 <aside id="event_modal" class="modal"></aside>
13 </div>
14</section> \ No newline at end of file
diff --git a/src/View/templates/footer.php b/src/View/templates/footer.php
new file mode 100644
index 0000000..cd8bd0a
--- /dev/null
+++ b/src/View/templates/footer.php
@@ -0,0 +1,43 @@
1<?php
2declare(strict_types=1);
3use App\Service\URL;
4?>
5 <footer>
6<?= $breadcrumb ?>
7 <div class="data">
8 <div class="contact">
9 <div id="footer_name">
10 <span id="footer_name_content"><?= htmlspecialchars($name ?? '') ?></span>
11 <?= $admin_footer_name ?>
12 </div>
13 <div id="footer_address">
14 <span id="footer_address_content"><?= htmlspecialchars($address ?? '') ?></span>
15 <?= $admin_footer_address ?>
16 </div>
17 <div id="footer_email">
18 <a id="footer_email_content" href="mailto:<?= htmlspecialchars($email ?? '') ?>"><?= htmlspecialchars($email ?? '') ?></a>
19 <?= $admin_footer_email ?>
20 </div>
21 </div>
22 <div id="footer_logo">
23 <a href="<?= new URL ?>"><img id="footer_logo_content" src="<?= $footer_logo ?? '' ?>" alt=""></a>
24 <?= $admin_footer_logo ?>
25 </div>
26<?php if(IS_ADMIN){ ?>
27 <script>
28 let footer_name = new InputText('footer_name');
29 let footer_address = new InputText('footer_address');
30 let footer_email = new InputText('footer_email');
31 let footer_logo = new InputFile('footer_logo');
32 </script>
33<?php } ?>
34 </div>
35 <div class="<?= $empty_admin_zone ?>"></div>
36 <div class="<?= $div_admin ?>">
37<?= $zone_admin ?>
38 </div>
39 <div id="toast"></div>
40 </footer>
41 </div>
42 </body>
43</html> \ No newline at end of file
diff --git a/src/View/templates/form.php b/src/View/templates/form.php
new file mode 100644
index 0000000..384bde9
--- /dev/null
+++ b/src/View/templates/form.php
@@ -0,0 +1,36 @@
1<?php declare(strict_types=1); ?>
2<section class="form" id="<?= $this->id_node ?>">
3 <h3><?= $title ?></h3>
4 <?= $admin_content ?>
5 <div class="form_inputs">
6 <label for="email_name_<?= $node->getNodeData()->getId() ?>">Votre nom</label>
7 <input id="email_name_<?= $node->getNodeData()->getId() ?>" type="text" name="email_name" value="">
8
9 <label for="email_address_<?= $node->getNodeData()->getId() ?>">Votre e-mail</label>
10 <input id="email_address_<?= $node->getNodeData()->getId() ?>" type="email" name="email_address" placeholder="mon-adresse@email.fr" value="" onchange="checkCase(<?= $node->getNodeData()->getId() ?>)">
11
12 <label for="email_message_<?= $node->getNodeData()->getId() ?>">Votre message</label>
13 <textarea id="email_message_<?= $node->getNodeData()->getId() ?>" type="text" name="email_message" rows="4"></textarea>
14
15 <div class="full_width_column">
16 <label for="email_captcha_<?= $node->getNodeData()->getId() ?>" >Montrez que vous n'êtes pas un robot</label>
17 </div>
18
19 <label for="email_captcha_<?= $node->getNodeData()->getId() ?>" >Combien font <?= self::$captcha->getA() ?> fois <?= self::$captcha->getB() ?>?</label>
20 <div>
21 <input id="email_captcha_<?= $node->getNodeData()->getId() ?>" type="text" name="email_captcha" size="1" autocomplete="off">
22 </div>
23
24 <input id="form_id_hidden" type="hidden" name="form_id_hidden" value="">
25 <input id="email_hidden_<?= $node->getNodeData()->getId() ?>" type="hidden" name="email_hidden">
26
27 <div class="full_width_column">
28 <input type="submit" value="Envoyez votre message" onclick="sendVisitorEmail(<?= $node->getNodeData()->getId() ?>)">
29 </div>
30
31 <p class="send_email_success_<?= $node->getNodeData()->getId() ?> full_width_column"></p>
32 </div>
33 <p id="form_warning_<?= $node->getNodeData()->getId() ?>" class="form_warning <?= ($keep_emails ?? false) ? '' : 'hidden' ?>"><i>
34 Une copie de votre e-mail (nom, adresse et message) sera conservée dans notre base de données dans le but de pouvoir mieux répondre à votre demande et éventuellement dans d'autres buts (prospection). Ces données seront traitées automatiquement par notre serveur et conservées pendant au maximum <?= $retention_period ?> mois à compter de votre dernier message. Ce traitement repose sur votre consentement. Vous pouvez consulter, modifier ou supprimer vos données sur simple demande.
35 </i></p>
36</section> \ No newline at end of file
diff --git a/src/View/templates/form_admin.php b/src/View/templates/form_admin.php
new file mode 100644
index 0000000..47bca38
--- /dev/null
+++ b/src/View/templates/form_admin.php
@@ -0,0 +1,73 @@
1<?php
2// src/view/templates/form_params.php
3declare(strict_types=1);
4use App\Service\URL;
5// note: l'id ici n'est pas celui du noeud bloc mais celui de l'entrée dans node_data correspondante
6?>
7<div class="admin_form">
8 <p>
9 <label for="keep_emails_<?= $node->getNodeData()->getId() ?>">Conserver les e-mails en base de données</label>
10 <input type="checkbox" id="keep_emails_<?= $node->getNodeData()->getId() ?>" <?= $keep_emails ? 'checked' : '' ?> onclick="keepEmails(<?= $node->getNodeData()->getId() ?>)">
11 </p>
12 <p><i>Notez que ces enregistrements sont des données personnelles et sont concernés par le RGPD.</i></p>
13 <p><a href="<?= new URL(['page' => 'emails']) ?>"><button>Consulter les e-mails enregistrés</button></a></p>
14 <p>
15 <label for="retention_period_<?= $node->getNodeData()->getId() ?>">Durée de conservation (en mois)</label>
16 <input type="number" id="retention_period_<?= $node->getNodeData()->getId() ?>" min="0" value="<?= $retention_period ?>" size="2" onchange="setEmailsRetentionPeriod(<?= $node->getNodeData()->getId() ?>, 'retention_period')">
17 </p>
18 <p>
19 <label for="retention_period_sensible_<?= $node->getNodeData()->getId() ?>">Durée de conservation des emails sensibles(en mois)</label>
20 <input type="number" id="retention_period_sensible_<?= $node->getNodeData()->getId() ?>" min="0" value="<?= $retention_period_sensible ?>" size="2" onchange="setEmailsRetentionPeriod(<?= $node->getNodeData()->getId() ?>, 'retention_period_sensible')">
21 </p>
22
23</div>
24<div class="admin_form">
25 <h3>Paramètres d'envoi</h3>
26 <p>
27 <label for="smtp_host_<?= $node->getNodeData()->getId() ?>">Adresse serveur SMTP</label>
28 <input id="smtp_host_<?= $node->getNodeData()->getId() ?>" type="text" name="smtp_host" placeholder="smtp.messagerie.fr" value="<?= htmlspecialchars($smtp_host) ?>">
29 <input type="hidden" id="smtp_host_hidden_<?= $node->getNodeData()->getId() ?>" value="">
30 <button onclick="setEmailParam('smtp_host', <?= $node->getNodeData()->getId() ?>)">Valider</button>
31 </p>
32 <p>
33 <label for="smtp_secure_<?= $node->getNodeData()->getId() ?>">Chiffrement</label>
34 <select id="smtp_secure_<?= $node->getNodeData()->getId() ?>" name="smtp_secure" onchange="setEmailParam('smtp_secure', <?= $node->getNodeData()->getId() ?>)">
35 <option value="plain_text" >Aucun (port 25)</option>
36 <option value="tls" <?= htmlspecialchars($smtp_secure) === 'tls' ? 'selected' : '' ?>>StartTLS (port 587)</option>
37 <option value="ssl" <?= htmlspecialchars($smtp_secure) === 'ssl' ? 'selected' : '' ?>>SSL (port 465)</option>
38 </select>
39 <input type="hidden" id="smtp_secure_hidden_<?= $node->getNodeData()->getId() ?>" value="">
40 </p>
41 <p>
42 <label for="smtp_username_<?= $node->getNodeData()->getId() ?>">Identifiant (adresse e-mail)</label>
43 <input id="smtp_username_<?= $node->getNodeData()->getId() ?>" type="email" name="smtp_username" autocomplete="new-password" placeholder="mon-adresse@email.fr" value="<?= htmlspecialchars($smtp_username) ?>">
44 <input type="hidden" id="smtp_username_hidden_<?= $node->getNodeData()->getId() ?>" value="">
45 <button onclick="setEmailParam('smtp_username', <?= $node->getNodeData()->getId() ?>)">Valider</button>
46 </p>
47 <p>
48 <label for="smtp_password_<?= $node->getNodeData()->getId() ?>">Mot de passe</label>
49 <input id="smtp_password_<?= $node->getNodeData()->getId() ?>" type="password" name="smtp_password" autocomplete="new-password">
50 <input type="hidden" id="smtp_password_hidden_<?= $node->getNodeData()->getId() ?>" value="">
51 <button onclick="setEmailParam('smtp_password', <?= $node->getNodeData()->getId() ?>)">Valider</button>
52 </p>
53 <p><i>Il s'agit du service qui acheminera les messages envoyés par ce formulaire.<br>
54 Site web => serveur d'envoi => serveur de réception => votre application "mail"</i></p>
55 <p><i>
56 Ce formulaire utilise les paramètres dans le fichier config.ini sur le serveur à mois que vous n'en renseignez d'autres ci-dessus. Les adresses d'envoi et de réception peuvent être identiques.
57 </i></p>
58 <p><i>Le site web peut ne pas réussir à se connecter à certains fournisseurs.</i></p>
59</div>
60<div class="admin_form">
61 <h3>Paramètres de réception</h3>
62 <p>
63 <label for="email_dest_<?= $node->getNodeData()->getId() ?>">Adresse e-mail</label>
64 <input id="email_dest_<?= $node->getNodeData()->getId() ?>" type="email" name="email_dest" placeholder="mon-adresse@email.fr" value="<?= htmlspecialchars($email_dest) ?>">
65 <input type="hidden" id="email_dest_hidden_<?= $node->getNodeData()->getId() ?>" value="">
66 <button onclick="setEmailParam('email_dest', <?= $node->getNodeData()->getId() ?>)">Valider</button>
67 </p>
68</div>
69<div class="admin_form">
70 <p><button onclick="sendTestEmail(<?= $node->getNodeData()->getId() ?>)">Envoi d'un e-mail de test</button></p>
71 <p><i>Vérifie la connexion au serveur d'envoi. Pour tester la réception, consultez vos e-mails à l'adresse de réception.</i></p>
72 <p class="test_email_success_<?= $node->getNodeData()->getId() ?> full_width_column"></p>
73</div> \ No newline at end of file
diff --git a/src/View/templates/galery.php b/src/View/templates/galery.php
new file mode 100644
index 0000000..97f8cd8
--- /dev/null
+++ b/src/View/templates/galery.php
@@ -0,0 +1,12 @@
1<?php declare(strict_types=1); ?>
2<section class="galery" id="<?= $this->id_node ?>">
3 <h3><?= $title ?></h3>
4<?= $new_article ?>
5 <script>
6 var clone<?= $this->id_node ?> = document.currentScript.previousElementSibling.cloneNode(true);
7 </script>
8 <div class="galery_photos">
9<?= $content ?>
10 </div>
11 <script>enableGaleryScroller();</script>
12</section> \ No newline at end of file
diff --git a/src/View/templates/head.php b/src/View/templates/head.php
new file mode 100644
index 0000000..bce0a19
--- /dev/null
+++ b/src/View/templates/head.php
@@ -0,0 +1,13 @@
1<?php declare(strict_types=1); ?>
2<!DOCTYPE html>
3<html lang="fr">
4 <head>
5 <script>window.Config = {page: "<?= CURRENT_PAGE ?>", favicon: "<?= $favicon ?>"};</script>
6 <meta charset="utf-8">
7 <title><?= $title ?></title>
8 <link rel="icon" type="<?= $favicon_type ?>" href="<?= $favicon ?>">
9 <meta name="description" content="<?= $description ?>">
10 <meta name="viewport" content="width=device-width">
11 <?= $css ?>
12 <?= $js ?>
13 </head> \ No newline at end of file
diff --git a/src/View/templates/header.php b/src/View/templates/header.php
new file mode 100644
index 0000000..08bc478
--- /dev/null
+++ b/src/View/templates/header.php
@@ -0,0 +1,58 @@
1<?php
2declare(strict_types=1);
3use App\Service\URL;
4?>
5<body>
6 <div>
7 <header style="background-image: url('<?= $header_background ?? '' ?>');">
8 <div id="nav_zone">
9 <div class="nav_button">
10 <button onclick="toggleTouchMenu()">MENU</button>
11 </div>
12 <?= $nav ?>
13 </div>
14 <div class="header_above">
15 <div id="head_favicon">
16 <?= $admin_head_favicon ?>
17 </div>
18 <div id="header_background">
19 <?= $admin_header_background ?>
20 </div>
21 </div>
22 <div class="header_content">
23 <div class="header_left_col">
24 <div id="header_logo">
25 <a href="<?= new URL ?>"><img id="header_logo_content" src="<?= $header_logo ?? '' ?>" alt=""></a>
26 <?= $admin_header_logo ?>
27 </div>
28 </div>
29 <div class="header_center_col">
30 <h1 id="header_title">
31 <a id="header_title_content" href="<?= new URL ?>"><?= htmlspecialchars($title ?? '') ?></a>
32 <?= $admin_header_title ?>
33 </h1>
34 <h2 id="header_description">
35 <span id="header_description_content"><?= htmlspecialchars($description ?? '') ?></span>
36 <?= $admin_header_description ?>
37 </h2>
38 </div>
39 <div class="header_right_col">
40 <div id="header_social">
41 <div id="header_social_content">
42 <?= $social_networks ?>
43 </div>
44 </div>
45 <?= $breadcrumb ?? '' ?>
46 </div>
47 </div>
48<?php if(IS_ADMIN){ ?>
49 <script>
50 document.getElementById("head_favicon_content").src = window.Config.favicon;
51 let head_favicon = new InputFileFavicon('head_favicon');
52 let header_background = new InputFileHeaderBackground('header_background');
53 let header_logo = new InputFile('header_logo');
54 let header_title = new InputText('header_title');
55 let header_description = new InputText('header_description');
56 </script>
57<?php } ?>
58 </header> \ No newline at end of file
diff --git a/src/View/templates/login.php b/src/View/templates/login.php
new file mode 100644
index 0000000..3257690
--- /dev/null
+++ b/src/View/templates/login.php
@@ -0,0 +1,31 @@
1<?php declare(strict_types=1); ?>
2 <section>
3 <h3>Connexion à l'espace d'administration</h3>
4 <div class="basic_div">
5 <p>Veuillez saisir votre identifiant et votre mot de passe.</p>
6 <p style="color: red; font-style: italic;"><?= $error ?></p>
7 <form method="post" action="<?= $link_form ?>" >
8 <p><label for="login" >Identifiant:</label>
9 <input id="login" type="text" name="login" autofocus required></p>
10 <p><label for="password" >Mot de passe:</label>
11 <input id="password" type="password" name="password" required></p>
12 <input type="hidden" name="connection_hidden">
13
14 <p>Montrez que vous n'êtes pas un robot.<br>
15 <label for="captcha" >Combien font <?= $captcha->getA() ?> fois <?= $captcha->getB() ?>?</label>
16 <input required type="text" id="captcha" name="captcha" autocomplete="off" size="1">
17 </p>
18
19 <input type="submit" value="Valider">
20 </form>
21 <p>Au fait? Vous n'utilisez pas votre propre ordinateur ou téléphone?<br/>
22 Utilisez la navigation privée.</p>
23 </div>
24 </section>
25 <section>
26 <div class="basic_div">
27 <a href="<?= $link_exit ?>" >
28 <button>Retour au site</button>
29 </a>
30 </div>
31 </section> \ No newline at end of file
diff --git a/src/View/templates/maintenance.php b/src/View/templates/maintenance.php
new file mode 100644
index 0000000..5a8a46d
--- /dev/null
+++ b/src/View/templates/maintenance.php
@@ -0,0 +1,65 @@
1<?php
2declare(strict_types=1);
3use App\Service\URL;
4?>
5<section class="maintenance">
6 <h3>Maintenance du site</h3>
7 <div class="basic_div">
8 <p>
9 <button onclick="displayLogs()">Consulter les journaux de connexion</button><br>
10 <i>Qui a essayé de se connecter, quand et a-t'il réussi?</i>
11 </p>
12 <div id="log_table"></div>
13 <p><button onclick="cleanLogs()">Effacer les journaux de connexion</button></p>
14
15 </div>
16 <div class="basic_div">
17 <p>
18 <a href="<?= new URL(['page' => 'emails']) ?>"><button>Consulter les emails</button></a>
19 <i>reçus depuis tous les formulaires de contact</i>
20 </p>
21 </div>
22 <div class="basic_div">
23 <p>
24 <a href="<?= new URL(['action' => 'get_mysqldump']) ?>">
25 <button id="get_mysqldump" onclick="preventClickSpam(this)">Télécharger une sauvegarde de la base de données</button>
26 </a><br>
27 <i>Obtenir un fichier SQL à conserver. Une sauvegarde (désignée par "auto") est réalisée à chaque connexion au mode admin.</i>
28 </p>
29 </div>
30 <div class="basic_div">
31 <p>Restaurer la base de données à partir d'un fichier SQL.<br>
32 <i>Attention, l'actuelle BDD sera écrasée! (à l'exception de la table <?= TABLE_PREFIX ?>user)</i>
33 </p>
34 <form action="<?= new URL(['from' => 'maintenance', 'action' => 'restore_database']) ?>" method="post">
35 <label for="selected_sql">Utiliser une sauvegarde conservée sur le serveur:</label><br>
36 <select id="selected_sql" name="selected_sql">
37 <?= $backup_options ?>
38 </select>
39 <input type="hidden" name="hidden" value="">
40 <input type="submit" value="Valider" onclick="return confirm('Voulez-vous vraiment restaurer la base de données? Toutes les données seront supprimées et remplacées par les nouvelles.')">
41 </form>
42 <form enctype="multipart/form-data" action="<?= new URL(['from' => 'maintenance', 'action' => 'restore_database']) ?>" method="post">
43 <label for="uploaded_sql">Utiliser un fichier sur votre ordinateur:</label><br>
44 <input id="uploaded_sql" type="file" accept=".sql" name="uploaded_sql">
45 <input type="hidden" name="hidden" value="">
46 <input type="submit" value="Valider" onclick="return confirm('Voulez-vous vraiment restaurer la base de données? Toutes les données seront supprimées et remplacées par les nouvelles.')">
47 </form>
48 </div>
49 <div class="basic_div">
50 <p>
51 <a href="<?= new URL(['from' => 'maintenance', 'action' => 'get_all_media']) ?>">
52 <button id="get_all_media" onclick="preventClickSpam(this)">Récupérer l'ensemble des fichiers mutimedia</button>
53 </a><br>
54 <i>Toutes vos photos et vos documents dans un .zip</i>
55 </p>
56 <p>
57 <button onclick="openExplorer()" style="color: grey">Explorateur de fichiers</button><br>
58 <i>Gérer les fichiers multimedia, fonction actuellement indisponible</i>
59 </p>
60 </div>
61
62 <div class="basic_div">
63 <a href="http://nageurs.localhost/index.php"><button>Retour au site</button></a>
64 </div>
65</section> \ No newline at end of file
diff --git a/src/View/templates/menu.php b/src/View/templates/menu.php
new file mode 100644
index 0000000..5a15fdb
--- /dev/null
+++ b/src/View/templates/menu.php
@@ -0,0 +1,51 @@
1<?php
2declare(strict_types=1);
3use App\Service\URL;
4?>
5<section class="menu">
6 <h3>Menu et chemins</h3>
7 <div class="new_page_button">
8 <p>Créer une <a href="<?= new URL(['page' => 'new_page']) ?>"><button style="color: #ff1d04;">Nouvelle page</button></a>.</p>
9 </div>
10 <div class="url_form_zone">
11 <p>Créer une entrée dans le menu avec une adresse vers un site quelconque:</p>
12 <form method="post" action="<?= new URL(['from' => 'menu_paths']) ?>">
13 <p>
14 <label for="label_input">Nom dans le menu:</label>
15 <input id="label_input" type="text" name="label_input">
16 </p>
17 <p>
18 <label for="url_input">Adresse (collez votre lien):</label>
19 <input id="url_input" type="url" name="url_input" placeholder="https://..." onchange="controlURL(this)">
20 </p>
21 <p>
22 <label>Placer le lien juste après cette entrée:</label>
23 <select id="location" name="location">
24 <?= $this->options ?>
25 </select>
26 </p>
27 <input type="submit" onclick="createUrlEntry()" value="Valider">
28 </form>
29 </div>
30 <aside>
31 <p>Modifier le menu:</p>
32 <div class="controls_explanations">
33 <p><img src="assets/arrow-left.svg"> remonter dans l'arbre</p>
34 <p><img src="assets/arrow-right.svg"> devenir une branche de l'élément précédent</p>
35 <p><img src="assets/arrow-up.svg"><img src="assets/arrow-down.svg"> déplacer la branche parmi celles de même niveau</p>
36 <p><input type="checkbox" checked> afficher/cacher</p>
37 <p><img src="assets/save.svg"> enregistrer une modification</p>
38 <p><img src="assets/delete-bin.svg"> supprimer un lien</p>
39 </div>
40 </aside>
41 <div id="menu_edit_buttons">
42<?= $this->html ?>
43 </div>
44</section>
45<section>
46 <div class="basic_div">
47 <a href="<?= new URL ?>">
48 <button>Retour au site</button>
49 </a>
50 </div>
51</section> \ No newline at end of file
diff --git a/src/View/templates/modify_block.php b/src/View/templates/modify_block.php
new file mode 100644
index 0000000..bb5abd3
--- /dev/null
+++ b/src/View/templates/modify_block.php
@@ -0,0 +1,61 @@
1<?php
2declare(strict_types=1);
3use App\Service\URL;
4?>
5<div class="modify_one_block" id="bloc_edit_<?= $child_node->getId() ?>">
6 <div class="block_options">
7 <label for="bloc_rename_<?= $child_node->getId() ?>">Type <b><?= App\Entity\Presentation::BLOCKS[$child_node->getName()] ?? '<i>erreur base de données</i>' ?></b>
8 </label>
9 <p>
10 <input type="text" id="bloc_rename_<?= $child_node->getId() ?>" name="bloc_rename_title" value="<?= $child_node->getNodeData()->getdata()['title'] ?>" required>
11 <button onclick="renamePageBloc(<?= $child_node->getId() ?>)">Renommer</button>
12 </p>
13 <div>
14 <p>
15 <img class="action_icon" onclick="switchBlocsPositions(<?= $child_node->getId() ?>, 'up')" src="assets/arrow-up.svg">
16 <img class="action_icon" onclick="switchBlocsPositions(<?= $child_node->getId() ?>, 'down')" src="assets/arrow-down.svg">
17 </p>
18 <form method="post" action="<?= new URL(['page' => CURRENT_PAGE]) ?>">
19 <input type="hidden" name="delete_bloc_id" value="<?= $child_node->getId() ?>">
20 <input type="hidden" name="delete_bloc_hidden">
21 <input type="submit" value="Supprimer" onclick="return confirm('Voulez-vous vraiment supprimer ce bloc?');">
22 </form>
23 </div>
24 </div>
25<?php
26if($child_node->getName() === 'news_block'){
27?>
28 <div class="news_order">
29 <label>Ordre des articles</label>
30 <select id="articles_order_select_<?= $child_node->getId() ?>" onchange="articlesOrderSelect(<?= $child_node->getId() ?>)">
31 <option value="antichrono" <?= $order === 'antichrono' ? 'selected' : '' ?>>Antichronologique</option>
32 <option value="chrono" <?= $order === 'chrono' ? 'selected' : '' ?>>Chronologique</option>
33 </select>
34 </div>
35<?php
36}
37if(App\Entity\Presentation::hasPresentation($child_node->getName())){
38?>
39 <div class="grid_options">
40 <div>
41 <label for="presentation_select_<?= $child_node->getId() ?>">Présentation</label>
42 <select id="presentation_select_<?= $child_node->getId() ?>" onchange="changePresentation(<?= $child_node->getId() ?>)">
43 <?= $this->makePresentationOptions($child_node->getNodeData()->getPresentation()) ?>
44 </select>
45 </div>
46 <div id="cols_min_width_edit_<?= $child_node->getId() ?>" class="<?= ($child_node->getNodeData()->getPresentation() === 'grid' ? '' : 'hidden') ?>">
47 <label for="cols_min_width_select_<?= $child_node->getId() ?>">Largeur minimum </label>
48 <input type="number" id="cols_min_width_select_<?= $child_node->getId() ?>" onchange="changeColsMinWidth(<?= $child_node->getId() ?>)" min="150" max="400" value="<?= $child_node->getNodeData()->getColsMinWidth() ?>"> pixels
49 </div>
50 </div>
51 <div class="pagination_limit">
52 <label for="pagination_limit_<?= $child_node->getId() ?>">
53 Nombre max d'articles affichés
54 <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() ?>">
55 <i>(mettre 0 désactive la pagination)</i>
56 </label>
57 </div>
58<?php
59}
60?>
61</div> \ No newline at end of file
diff --git a/src/View/templates/modify_page.php b/src/View/templates/modify_page.php
new file mode 100644
index 0000000..773277b
--- /dev/null
+++ b/src/View/templates/modify_page.php
@@ -0,0 +1,62 @@
1<?php
2declare(strict_types=1);
3use App\Model\Model;
4use App\Service\URL;
5?>
6<section class="page_modification">
7 <h3>Modification de la page</h3>
8 <div class="edit_page_zone">
9 <div class="edit_page_title_zone">
10 <p id="edit_page_title">
11 <label for="page_name">Titre de la page</label>
12 <input type="text" id="page_name" name="edit_page_title" value="<?= Model::$page_path->getLast()->getPageName() ?>" onchange="makePageNamePath()" required>
13 <button onclick="changePageTitle('<?= Model::$page_path->getLast()->getId() ?>')">Renommer</button>
14 </p>
15 <form id="edit_page_menu_path" method="post" action="<?= new URL(['page' => CURRENT_PAGE]) ?>">
16 <label for="page_name_path">Chemin en "snake_case"</label>
17 <input type="text" id="page_name_path" name="page_menu_path" value="<?= Model::$page_path->getLast()->getEndOfPath() ?>" placeholder="ex: nouvelle_page" required>
18 <input type="hidden" name="page_id" value="<?= Model::$page_path->getLast()->getId() ?>"><input type="hidden" name="page_name_path_hidden">
19 <input type="submit" value="Modifier">
20 </form>
21 </div>
22 <div id="edit_description">
23 <label for="description_textarea">Description sous le titre dans les moteurs de recherche</label>
24 <div>
25 <textarea id="description_textarea" name="edit_description" cols="40" rows="3" placeholder="ex: nous faisons ceci et cela, etc" required><?= Model::$page_path->getLast()->getDescription(); ?></textarea>
26 <button onclick="changeDescription('<?= Model::$page_path->getLast()->getId() ?>')">Modifier</button>
27 </div>
28 </div>
29 </div>
30 <div class="delete_page_zone">
31 <form method="post" action="<?= new URL(['page' => CURRENT_PAGE]) ?>">
32 <label>Supprimer cette page</label>
33 <input type="hidden" name="page_id" value="<?= Model::$page_path->getLast()->getId() ?>">
34 <input type="hidden" name="submit_hidden">
35 <input type="submit" value="Supprimer" onclick="return confirm('Voulez-vous vraiment supprimer cette page?');">
36 </form>
37 </div>
38 <div class="edit_block_zone">
39 <div class="new_block">
40 <p>Ajouter un bloc de page</p>
41 <form method="post" action="<?= new URL(['page' => CURRENT_PAGE]) ?>">
42 <p><label for="bloc_title">Titre</label>
43 <input type="text" id="bloc_title" name="bloc_title" required></p>
44 <p><label for="bloc_select">Type</label>
45 <select id="bloc_select" name="bloc_select" required>
46 <?= $options ?>
47 </select>
48 <input type="hidden" name="bloc_title_hidden">
49 <input type="submit" value="Valider"></p>
50 </form>
51 <div class="explanations">
52 <p><b>Articles libres</b>: textes riches générés par l'éditeur et librement positionnables</p>
53 <p><b>Actualités</b>: contenus structurés avec titre, aperçu, date et possédant une page dédiée</p>
54 <!-- <p><b>Galerie</b>: photos parcourables en mode plein écran (cette fonctionalité n'est pas encore disponible)</p> -->
55 </div>
56 </div>
57 <div class="modify_block">
58 <p>Modifier un bloc</p>
59 <?= $bloc_edit ?>
60 </div>
61 </div>
62</section> \ No newline at end of file
diff --git a/src/View/templates/new.php b/src/View/templates/new.php
new file mode 100644
index 0000000..30b244f
--- /dev/null
+++ b/src/View/templates/new.php
@@ -0,0 +1,27 @@
1<?php declare(strict_types=1); ?>
2<article>
3 <div class="new_content <?= $overflow ?>" id="<?= $id ?>">
4 <div class="data" id="<?= $id_title ?>">
5 <?= $title ?>
6 </div>
7 <?= $title_buttons ?>
8 <div class="data new_content_text" id="<?= $id_preview ?>">
9 <?= $preview ?>
10 </div>
11 <?= $preview_buttons ?>
12 <div class="data article_content_text" id="<?= $id_content ?>">
13 <?= $content ?>
14 </div>
15 <?= $article_buttons ?>
16 <div class="under_an_article">
17 <p>
18 <img src="assets/calendar.svg">
19 <span class="data local_date" id="<?= $id_date ?>" date-utc="<?= $date ?>">Chargement...</span>
20 </p>
21 </div>
22 <?= $date_buttons ?>
23 <div class="article_admin_zone">
24 <?= $admin_buttons ?>
25 </div>
26 </div>
27</article> \ No newline at end of file
diff --git a/src/View/templates/new_page.php b/src/View/templates/new_page.php
new file mode 100644
index 0000000..535d88d
--- /dev/null
+++ b/src/View/templates/new_page.php
@@ -0,0 +1,38 @@
1<?php
2declare(strict_types=1);
3use App\Service\URL;
4?>
5<section class="new_page">
6 <h3>Création d'une nouvelle page</h3>
7 <div class="form_zone">
8 <form method="post" action="<?= new URL ?>">
9 <p>
10 <label for="page_name">Nom de la page</label>
11 <input id="page_name" type="text" name="page_name" onchange="makePageNamePath()" required>
12 </p>
13 <p>
14 <label for="page_name_path">Chemin de l'URL</label>
15 <input id="page_name_path" type="text" name="page_name_path" placeholder="ex: nouvelle_page" required>
16 </p>
17 <p>
18 <label for="page_location">Placer la page juste après cette entrée</label>
19 <select id="page_location" name="page_location">
20 <?= $this->options ?>
21 </select>
22 </p>
23 <input type="hidden" name="new_page_hidden">
24 <p>
25 <label class="label_textarea" for="page_description">Description qui apparaît sous le titre dans les moteurs de recherche</label>
26 <textarea id="page_description" name="page_description" cols="35" rows="3" placeholder="ex: nous faisons ceci et cela, etc"></textarea>
27 </p>
28 <input type="submit" value="Créer la page">
29 </form>
30 </div>
31</section>
32<section>
33 <div class="basic_div">
34 <a href="<?= new URL ?>">
35 <button>Retour au site</button>
36 </a>
37 </div>
38</section> \ No newline at end of file
diff --git a/src/View/templates/news_block.php b/src/View/templates/news_block.php
new file mode 100644
index 0000000..43dbe01
--- /dev/null
+++ b/src/View/templates/news_block.php
@@ -0,0 +1,14 @@
1<?php declare(strict_types=1); ?>
2<section class="<?= $section_class ?>" block-type="<?= $node->getName() ?>" id="<?= $this->id_node ?>">
3 <h3><?= $title ?></h3>
4<?= $new_article ?>
5 <script>
6 var clone<?= $this->id_node ?> = document.currentScript.previousElementSibling.cloneNode(true);
7 </script>
8 <div class="section_child" style="<?= $cols_min_width ?>">
9<?= $content ?>
10 </div>
11 <div class="fetch_articles">
12 <button class="<?= $fetch_button_hidden ?>" onclick="fetchArticles(<?= $this->id_node ?>)">Articles suivants</button>
13 </div>
14</section> \ No newline at end of file
diff --git a/src/View/templates/post.php b/src/View/templates/post.php
new file mode 100644
index 0000000..d0d01b2
--- /dev/null
+++ b/src/View/templates/post.php
@@ -0,0 +1,14 @@
1<?php declare(strict_types=1); ?>
2<article>
3 <div class="post_content">
4 <div class="article_title_zone">
5 <?= $share_button ?>
6 </div>
7 <div id="<?= $id ?>">
8 <?= $html ?>
9 </div>
10 <div class="article_admin_zone">
11 <?= $admin_buttons ?>
12 </div>
13 </div>
14</article> \ No newline at end of file
diff --git a/src/View/templates/post_block.php b/src/View/templates/post_block.php
new file mode 100644
index 0000000..43dbe01
--- /dev/null
+++ b/src/View/templates/post_block.php
@@ -0,0 +1,14 @@
1<?php declare(strict_types=1); ?>
2<section class="<?= $section_class ?>" block-type="<?= $node->getName() ?>" id="<?= $this->id_node ?>">
3 <h3><?= $title ?></h3>
4<?= $new_article ?>
5 <script>
6 var clone<?= $this->id_node ?> = document.currentScript.previousElementSibling.cloneNode(true);
7 </script>
8 <div class="section_child" style="<?= $cols_min_width ?>">
9<?= $content ?>
10 </div>
11 <div class="fetch_articles">
12 <button class="<?= $fetch_button_hidden ?>" onclick="fetchArticles(<?= $this->id_node ?>)">Articles suivants</button>
13 </div>
14</section> \ No newline at end of file
diff --git a/src/View/templates/show_emails.php b/src/View/templates/show_emails.php
new file mode 100644
index 0000000..f561dc7
--- /dev/null
+++ b/src/View/templates/show_emails.php
@@ -0,0 +1,30 @@
1<?php declare(strict_types=1); ?>
2<section class="show_emails">
3 <h3>Table "<?= TABLE_PREFIX ?>email" de la base de données</h3>
4 <p><i>
5 Les e-mails ci-dessous sont des copies de ceux arrivés dans votre boite de messagerie depuis tous les formulaires existant sur le site. Ils sont conservés dans un but pratique (historique, prospection) ou dans tout autre but justifiant leur conservation. Cette conservation est optionnelle et règlementée par la loi européenne RGPD.<br>
6 <b>Notez qu'utiliser cette application ne vous garantit pas d'être en conformité avec la loi, c'est un outil vous permettant de faire les choses comme vous l'entendez. Si tout ceci vous gêne, désactivez le stockage des e-mails et supprimez ceux déjà enregistrés (ci-dessous).</b>
7 </i></p>
8 <h4>Durées de conservation</h4>
9 <p><i>
10 Ce sont des durées maximales, les données peuvent être supprimées plus tôt ou même immédiatement. Le faire est d'ailleurs une obligation dans le cas où les personnes concernées le demandent.<br>
11 </i></p>
12 <p><i>
13 Les e-mails ordinaires d'un même expéditeur (même adresse e-mail) sont tous supprimés simultanément lorsque le plus récent d'entre eux atteint les 3 ans (utilisateur est considéré inactif).<br>
14 Les e-mails sensibles quand à eux sont supprimés 5 ans après être devenus sensibles (durée juridique d'une preuve).
15 </i></p>
16 <p><i>
17 Un nettoyeur supprimant les messages dépassant ces durées est exécuté au moment de votre connexion au mode administrateur.<br>
18 Si vos connexions sont rares, il est possible d'automatiser ce nettoyage à l'aide d'une tâche CRON. Pour cela, vous devez configurer le serveur pour qu'il exécute periodiquement la commande "php /chemin/du/site/bin/cron.php".
19 </i></p>
20 <h4>Données sensibles</h4>
21 <p><i>
22 Un e-mail peut-être considéré comme "sensible". Vous pouvez rendre un e-mail sensible lorsqu'il possède une valeur de preuve dans le cas d'un litige.<br>
23 Lorsqu'une personne demande la suppression de ses données personnelles du serveur, les e-mails sensibles peuvent être conservés, vous aurez noté que la durée de conservation est calculée différement.
24 </i></p>
25 <p><i>
26 Les spams ne sont pas sensibles, c'est juste de la pollution, supprimez-les!
27 </i></p>
28
29 <?= $emails ?>
30</section> \ No newline at end of file
diff --git a/src/View/templates/user_create.php b/src/View/templates/user_create.php
new file mode 100644
index 0000000..662d9ac
--- /dev/null
+++ b/src/View/templates/user_create.php
@@ -0,0 +1,54 @@
1<?php
2declare(strict_types=1);
3use App\Service\Captcha;
4
5$error_messages = [
6 'error_non_valid_captcha' => 'Erreur au test anti-robot, veuillez saisir un nombre entier.',
7 'captcha_server_error' => 'captcha_server_error',
8 'bad_solution_captcha' => 'Erreur au test anti-robot, veuillez réessayer.',
9 'bad_login_or_password' => 'Mauvais identifiant ou mot de passe, veuillez réessayer.',
10 'different_passwords' => 'Les deux mots de passe saisis sont différents',
11 'forbidden_characters' => 'Caractères interdits: espaces, tabulations, sauts CR/LF.'
12];
13$error = isset($_GET['error']) ? $error_messages[$_GET['error']] : '';
14
15$captcha = new Captcha;
16$_SESSION['captcha'] = $captcha->getSolution(); // enregistrement de la réponse du captcha pour vérification
17?>
18<!DOCTYPE html>
19<html lang="fr">
20 <head>
21 <meta charset="utf-8">
22 <title>Bienvenue</title>
23 <link rel="icon" type="image/png" href="assets/favicon48x48.png">
24 <link rel="stylesheet" href="css/body.css">
25 <meta name="viewport" content="width=device-width, initial-scale=1.0">
26 </head>
27 <body>
28 <main>
29 <section>
30 <h3>Bienvenue.</h3>
31 <p style="text-align: center;">Pour commencer, veuillez créer un compte administrateur.</p>
32 <div class="basic_div">
33 <p style="color: red; font-style: italic;"><?= $error ?></p>
34 <form method="post" action="index.php?action=create_user" >
35 <p><label for="login" >Identifiant:</label>
36 <input id="login" type="text" name="login" autofocus required></p>
37 <p><label for="password" >Mot de passe:</label>
38 <input id="password" type="password" name="password" required></p>
39 <p><label for="password_confirmation" >Confirmer le mot de passe:</label>
40 <input id="password_confirmation" type="password" name="password_confirmation" required></p>
41 <input type="hidden" name="create_user_hidden">
42
43 <p>Montrez que vous n'êtes pas un robot.<br>
44 <label for="captcha" >Combien font <?= $captcha->getA() ?> fois <?= $captcha->getB() ?>?</label>
45 <input required type="text" id="captcha" name="captcha" autocomplete="off" size="1">
46 </p>
47
48 <input type="submit" value="Valider">
49 </form>
50 </div>
51 </section>
52 </main>
53 </body>
54</html> \ No newline at end of file
diff --git a/src/View/templates/user_edit.php b/src/View/templates/user_edit.php
new file mode 100644
index 0000000..9191e7d
--- /dev/null
+++ b/src/View/templates/user_edit.php
@@ -0,0 +1,63 @@
1<?php declare(strict_types=1); ?>
2 <section>
3 <div class="user_edit_header">
4 <div class="empty_column"></div>
5 <h3>Mon compte</h3>
6 <div>
7 <img class="user_icon" src="assets/user_hollow.svg">
8 <div><?= $_SESSION['user']['username'] ?></div>
9 </div>
10 </div>
11 <div class="user_edit_flex">
12 <div class="basic_div">
13 <p>Modifier mon <b>nom d'utilisateur</b>.</p>
14 <p style="color: red; font-style: italic;"><?= $error_username ?></p>
15 <p style="color: green; font-style: italic;"><?= $success_username ?></p>
16 <form method="post" action="<?= $link_user_form ?>" >
17 <p><label for="login" >Ancien nom:</label>
18 <input id="login" type="text" name="login" required></p>
19 <p><label for="password" >Mot de passe:</label>
20 <input id="password" type="password" name="password" required ></p>
21 <p><label for="new_login" >Nouveau nom:</label>
22 <input id="new_login" type="text" name="new_login" required></p>
23 <input type="hidden" name="modify_username_hidden">
24
25 <p>Montrez que vous n'êtes pas un robot.<br>
26 <label for="captcha" >Combien font <?= $captcha->getA() ?> fois <?= $captcha->getB() ?>?</label>
27 <input required type="text" id="captcha" name="captcha" autocomplete="off" size="1">
28 </p>
29
30 <input type="submit" value="Valider">
31 </form>
32 </div>
33 <div class="basic_div">
34 <p>Modifier mon <b>mot de passe</b>.</p>
35 <p style="color: red; font-style: italic;"><?= $error_password ?></p>
36 <p style="color: green; font-style: italic;"><?= $success_password ?></p>
37 <form method="post" action="<?= $link_password_form ?>" >
38 <p><label for="login" >Nom:</label>
39 <input id="login" type="text" name="login" required></p>
40 <p><label for="password" >Ancien mot de passe:</label>
41 <input id="password" type="password" name="password" required ></p>
42 <p><label for="new_password" >Nouveau mot de passe:</label>
43 <input id="new_password" type="password" name="new_password" required autocomplete="off"></p>
44 <input type="hidden" name="modify_password_hidden">
45
46 <p>Montrez que vous n'êtes pas un robot.<br>
47 <label for="captcha" >Combien font <?= $captcha->getA() ?> fois <?= $captcha->getB() ?>?</label>
48 <input required type="text" id="captcha" name="captcha" autocomplete="off" size="1">
49 </p>
50
51 <input type="submit" value="Valider">
52 </form>
53 </div>
54 </div>
55 </section>
56 <section>
57 <div class="basic_div">
58 <a href="<?= $link_exit ?>">
59 <button>Retour au site</button></a>
60 <a href="<?= $link_logout ?>">
61 <button>Déconnexion</button></a>
62 </div>
63 </section> \ No newline at end of file