diff options
| author | polo <ordipolo@gmx.fr> | 2026-10-23 00:47:32 +0200 |
|---|---|---|
| committer | polo <ordipolo@gmx.fr> | 2026-10-23 00:47:32 +0200 |
| commit | f27cca08ff786ceff1870147f6d6bad14d71117f (patch) | |
| tree | d36597a72025313d6faf4ed228755fa267ebe2f6 /src/View | |
| parent | c7309f900ef05bd5cb313110ec159b761af394ec (diff) | |
| download | cms-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')
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 | |||
| 4 | declare(strict_types=1); | ||
| 5 | |||
| 6 | namespace App\View; | ||
| 7 | |||
| 8 | use App\Entity\Node; | ||
| 9 | use App\Model\Model; | ||
| 10 | |||
| 11 | abstract 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 | |||
| 4 | declare(strict_types=1); | ||
| 5 | |||
| 6 | namespace App\View; | ||
| 7 | |||
| 8 | //use App\Entity\Node; | ||
| 9 | use App\Model\Model; | ||
| 10 | use App\Service\URL; | ||
| 11 | |||
| 12 | class 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 | |||
| 4 | declare(strict_types=1); | ||
| 5 | |||
| 6 | namespace App\View; | ||
| 7 | |||
| 8 | use App\Entity\Node; | ||
| 9 | |||
| 10 | class 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 | |||
| 4 | declare(strict_types=1); | ||
| 5 | |||
| 6 | namespace App\View; | ||
| 7 | |||
| 8 | use App\Entity\Node; | ||
| 9 | use App\Entity\Asset; | ||
| 10 | use App\Controller\ViewController; | ||
| 11 | use App\Model\Model; | ||
| 12 | use App\Service\URL; | ||
| 13 | |||
| 14 | class 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 | |||
| 4 | declare(strict_types=1); | ||
| 5 | |||
| 6 | namespace App\View; | ||
| 7 | |||
| 8 | use App\Entity\Node; | ||
| 9 | use App\Entity\Email; | ||
| 10 | use App\Service\Captcha; | ||
| 11 | use App\Service\Config; | ||
| 12 | |||
| 13 | class 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 | |||
| 4 | declare(strict_types=1); | ||
| 5 | |||
| 6 | namespace App\View; | ||
| 7 | |||
| 8 | use App\Entity\Node; | ||
| 9 | |||
| 10 | class 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 | |||
| 4 | declare(strict_types=1); | ||
| 5 | |||
| 6 | namespace App\View; | ||
| 7 | |||
| 8 | use App\Entity\Asset; | ||
| 9 | use App\Entity\Node; | ||
| 10 | use App\Model\Model; | ||
| 11 | |||
| 12 | class 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 | |||
| 4 | declare(strict_types=1); | ||
| 5 | |||
| 6 | namespace App\View; | ||
| 7 | |||
| 8 | use App\Entity\Node; | ||
| 9 | use App\Entity\NodeData; | ||
| 10 | use App\Entity\Asset; | ||
| 11 | |||
| 12 | class 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 | |||
| 4 | declare(strict_types=1); | ||
| 5 | |||
| 6 | namespace App\View; | ||
| 7 | |||
| 8 | use App\Entity\Node; | ||
| 9 | use App\Service\URL; | ||
| 10 | use App\Service\Captcha; | ||
| 11 | |||
| 12 | class 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 | |||
| 4 | declare(strict_types=1); | ||
| 5 | |||
| 6 | namespace App\View; | ||
| 7 | |||
| 8 | use App\Entity\Article; | ||
| 9 | use App\Entity\Node; | ||
| 10 | use App\Entity\Presentation; | ||
| 11 | use App\Model\Model; | ||
| 12 | use App\Service\URL; | ||
| 13 | |||
| 14 | class 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 | |||
| 4 | declare(strict_types=1); | ||
| 5 | |||
| 6 | namespace App\View; | ||
| 7 | |||
| 8 | use App\Entity\Node; | ||
| 9 | use App\Service\Backup; | ||
| 10 | |||
| 11 | class 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 | |||
| 6 | declare(strict_types=1); | ||
| 7 | |||
| 8 | namespace App\View; | ||
| 9 | |||
| 10 | use App\Entity\Node; | ||
| 11 | use App\Entity\Page; | ||
| 12 | use App\Model\Model; | ||
| 13 | use App\Service\URL; | ||
| 14 | |||
| 15 | class 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 | |||
| 6 | declare(strict_types=1); | ||
| 7 | |||
| 8 | namespace App\View; | ||
| 9 | |||
| 10 | //use App\Entity\Node; | ||
| 11 | use App\Entity\Page; | ||
| 12 | use App\Model\Model; | ||
| 13 | use App\Service\URL; | ||
| 14 | |||
| 15 | class 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 | |||
| 6 | declare(strict_types=1); | ||
| 7 | |||
| 8 | namespace App\View; | ||
| 9 | |||
| 10 | use App\Entity\Node; | ||
| 11 | use App\Service\URL; | ||
| 12 | |||
| 13 | class 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 | |||
| 6 | declare(strict_types=1); | ||
| 7 | |||
| 8 | namespace App\View; | ||
| 9 | |||
| 10 | use App\Entity\Node; | ||
| 11 | use App\Entity\Page; | ||
| 12 | use App\Model\Model; | ||
| 13 | use App\Service\URL; | ||
| 14 | |||
| 15 | class 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 | |||
| 4 | declare(strict_types=1); | ||
| 5 | |||
| 6 | namespace App\View; | ||
| 7 | |||
| 8 | use App\Entity\Node; | ||
| 9 | use App\Service\URL; | ||
| 10 | |||
| 11 | class 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 | |||
| 4 | declare(strict_types=1); | ||
| 5 | |||
| 6 | namespace App\View; | ||
| 7 | |||
| 8 | use App\Entity\Node; | ||
| 9 | |||
| 10 | class 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 | |||
| 4 | declare(strict_types=1); | ||
| 5 | |||
| 6 | namespace App\View; | ||
| 7 | |||
| 8 | use App\Entity\Node; | ||
| 9 | use App\Service\URL; | ||
| 10 | |||
| 11 | class 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 | |||
| 4 | declare(strict_types=1); | ||
| 5 | |||
| 6 | namespace App\View; | ||
| 7 | |||
| 8 | use App\Entity\Node; | ||
| 9 | use App\Service\URL; | ||
| 10 | |||
| 11 | class 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 | |||
| 6 | declare(strict_types=1); | ||
| 7 | |||
| 8 | namespace App\View; | ||
| 9 | |||
| 10 | use App\Entity\Node; | ||
| 11 | use App\Service\URL; | ||
| 12 | use App\Service\Captcha; | ||
| 13 | |||
| 14 | class 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 | |||
| 6 | declare(strict_types=1); | ||
| 7 | |||
| 8 | namespace App\View; | ||
| 9 | |||
| 10 | use App\Entity\Node; | ||
| 11 | |||
| 12 | class 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 | ||
| 2 | declare(strict_types=1); | ||
| 3 | use 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 | ||
| 2 | declare(strict_types=1); | ||
| 3 | use 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 | ||
| 3 | declare(strict_types=1); | ||
| 4 | use 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 | ||
| 2 | declare(strict_types=1); | ||
| 3 | use 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 | ||
| 2 | declare(strict_types=1); | ||
| 3 | use 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 | ||
| 2 | declare(strict_types=1); | ||
| 3 | use 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 | ||
| 2 | declare(strict_types=1); | ||
| 3 | use 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 | ||
| 26 | if($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 | } | ||
| 37 | if(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 | ||
| 2 | declare(strict_types=1); | ||
| 3 | use App\Model\Model; | ||
| 4 | use 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 | ||
| 2 | declare(strict_types=1); | ||
| 3 | use 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 | ||
| 2 | declare(strict_types=1); | ||
| 3 | use 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 | ||
