From f27cca08ff786ceff1870147f6d6bad14d71117f Mon Sep 17 00:00:00 2001 From: polo Date: Fri, 23 Oct 2026 00:47:32 +0200 Subject: =?UTF-8?q?dossiers=20avec=20majuscule,=20namespace=20pour=20toute?= =?UTF-8?q?s=20les=20classes,=20autoload=20psr-4,=20UserDataService::creat?= =?UTF-8?q?eZip=20d=C3=A9plac=C3=A9=20dans=20Backup?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Model/DatabaseSettingUp.php | 157 +++++++++++++++++ src/Model/Entity/AppMetadata.php | 46 +++++ src/Model/Entity/Article.php | 94 +++++++++++ src/Model/Entity/Asset.php | 68 ++++++++ src/Model/Entity/AssetEmployment.php | 54 ++++++ src/Model/Entity/Email.php | 131 +++++++++++++++ src/Model/Entity/EmailForm.php | 57 +++++++ src/Model/Entity/Event.php | 129 ++++++++++++++ src/Model/Entity/Image.php | 89 ++++++++++ src/Model/Entity/Log.php | 38 +++++ src/Model/Entity/Node.php | 167 ++++++++++++++++++ src/Model/Entity/NodeData.php | 102 +++++++++++ src/Model/Entity/Page.php | 222 ++++++++++++++++++++++++ src/Model/Entity/Presentation.php | 135 +++++++++++++++ src/Model/Entity/User.php | 67 ++++++++ src/Model/EventDTO.php | 53 ++++++ src/Model/Menu.php | 44 +++++ src/Model/Model.php | 316 +++++++++++++++++++++++++++++++++++ src/Model/Path.php | 80 +++++++++ src/Model/Position.php | 52 ++++++ src/Model/doctrine-bootstrap.php | 34 ++++ 21 files changed, 2135 insertions(+) create mode 100644 src/Model/DatabaseSettingUp.php create mode 100644 src/Model/Entity/AppMetadata.php create mode 100644 src/Model/Entity/Article.php create mode 100644 src/Model/Entity/Asset.php create mode 100644 src/Model/Entity/AssetEmployment.php create mode 100644 src/Model/Entity/Email.php create mode 100644 src/Model/Entity/EmailForm.php create mode 100644 src/Model/Entity/Event.php create mode 100644 src/Model/Entity/Image.php create mode 100644 src/Model/Entity/Log.php create mode 100644 src/Model/Entity/Node.php create mode 100644 src/Model/Entity/NodeData.php create mode 100644 src/Model/Entity/Page.php create mode 100644 src/Model/Entity/Presentation.php create mode 100644 src/Model/Entity/User.php create mode 100644 src/Model/EventDTO.php create mode 100644 src/Model/Menu.php create mode 100644 src/Model/Model.php create mode 100644 src/Model/Path.php create mode 100644 src/Model/Position.php create mode 100644 src/Model/doctrine-bootstrap.php (limited to 'src/Model') diff --git a/src/Model/DatabaseSettingUp.php b/src/Model/DatabaseSettingUp.php new file mode 100644 index 0000000..725eaed --- /dev/null +++ b/src/Model/DatabaseSettingUp.php @@ -0,0 +1,157 @@ +getRepository(AppMetadata::class)->find('installed'); + return !$metadata || $metadata->getValue() !== '1'; + } + + // protection 2, qui vérifie vraiment que les tables concernées sont vides + static private function areTablesEmpty(EntityManager $entityManager): bool + { + $empty = true; + $entities = ['Page', 'Node', 'NodeData']; + foreach($entities as $entity){ + $entity = 'App\Entity\\' . $entity; // nécéssaire quand on insère le nom avec une variable + + if($entityManager + ->createQuery("SELECT e FROM $entity e") + ->setMaxResults(1) + ->getOneOrNullResult()){ + $empty = false; + } + } + + // cas anormal détecté, on remet en place la clé "installed" + if(!$empty){ + self::preventReinstallation($entityManager); + } + + return $empty; + } + + static private function fillStartingDatabase(EntityManager $entityManager): void + { + /* -- table page -- */ + // paramètres: name_page, end_of_path, reachable, in_menu, hidden, position, parent + $accueil = new Page('Accueil', 'accueil', "Page d'accueil", true, true, false, 1, NULL); + $article = new Page('Article', 'article', "", true, false, false, NULL, NULL); + $connection = new Page('Connexion', 'connection', "Connexion", true, false, false, NULL, NULL); + $my_account = new Page('Mon compte', 'user_edit', "Mon compte", true, false, false, NULL, NULL); + $menu_paths = new Page("Menu et chemins", 'menu_paths', "Menu et chemins", true, false, false, NULL, NULL); + $menu_paths->addCSS('menu'); + $menu_paths->addJS('menu'); + $new_page = new Page('Nouvelle page', 'new_page', "Nouvelle page", true, false, false, NULL, NULL); + $new_page->addCSS('new_page'); + $new_page->addJS('new_page'); + $emails = new Page("Courriels", 'emails', "Consulter les courriels en base de données", true, false, false, NULL, NULL); + $emails->addCSS('show_emails'); + $emails->addJS('form'); + $maintenance = new Page("Maintenance", 'maintenance', "Opérations de maintenance, installation, sauvegarde, restauration", true, false, false, NULL, NULL); + $maintenance->addCSS('maintenance'); + $maintenance->addJS('maintenance'); + + /* -- table node -- */ + // paramètres: name_node, article_timestamp, attributes, position, parent, page, article + $head = new Node('head', 1, NULL, NULL, NULL); + $header = new Node('header', 2, NULL, NULL, NULL); + $nav = new Node('nav', 1, $header, NULL, NULL); + $main = new Node('main', 3, NULL, NULL, NULL); + $footer = new Node('footer', 4, NULL, NULL, NULL); + $breadcrumb = new Node('breadcrumb', 2, $header, NULL, NULL); + $login = new Node('login', 1, $main, $connection, NULL); + $user_edit = new Node('user_edit', 1, $main, $my_account, NULL); + $bloc_edit_menu = new Node('menu', 1, $main, $menu_paths, NULL); + $bloc_new_page = new Node('new_page', 1, $main, $new_page, NULL); + $bloc_emails = new Node('show_emails', 1, $main, $emails, NULL); + $bloc_maintenance = new Node('maintenance', 1, $main, $maintenance, NULL); + + /* -- table node_data -- */ + // paramètres: data, node, images + $head_data = new NodeData([], $head); + $header_data = new NodeData([], $header); + $footer_data = new NodeData([], $footer); + $emails_data = new NodeData([], $bloc_emails); + + /* -- table page -- */ + $entityManager->persist($accueil); + $entityManager->persist($article); + $entityManager->persist($connection); + $entityManager->persist($my_account); + $entityManager->persist($menu_paths); + $entityManager->persist($new_page); + $entityManager->persist($emails); + $entityManager->persist($maintenance); + + /* -- table node -- */ + $entityManager->persist($head); + $entityManager->persist($header); + $entityManager->persist($nav); + $entityManager->persist($main); + $entityManager->persist($footer); + $entityManager->persist($breadcrumb); + $entityManager->persist($login); + $entityManager->persist($user_edit); + $entityManager->persist($bloc_edit_menu); + $entityManager->persist($bloc_new_page); + $entityManager->persist($bloc_emails); + $entityManager->persist($bloc_maintenance); + + /* -- table node_data -- */ + $entityManager->persist($head_data); + $entityManager->persist($header_data); + $entityManager->persist($footer_data); + $entityManager->persist($emails_data); + + $entityManager->flush(); + } + + // met en place la protection + static private function preventReinstallation(EntityManager $entityManager): void + { + $metadata = $entityManager->getRepository(AppMetadata::class)->find('installed'); + if($metadata){ + $metadata->setValue('1'); + } + else{ + $metadata = new AppMetadata('installed', '1'); + $entityManager->persist($metadata); + } + $entityManager->flush(); + } +} \ No newline at end of file diff --git a/src/Model/Entity/AppMetadata.php b/src/Model/Entity/AppMetadata.php new file mode 100644 index 0000000..b7d6923 --- /dev/null +++ b/src/Model/Entity/AppMetadata.php @@ -0,0 +1,46 @@ +key_name = $key; + $this->value = $value; + } + + public function getKey(): string + { + return $this->key_name; + } + public function getValue(): string + { + return $this->value; + } + + public function setKey(string $key): void + { + $this->key_name = $key; + } + public function setValue(string $value): void + { + $this->value = $value; + } +} \ No newline at end of file diff --git a/src/Model/Entity/Article.php b/src/Model/Entity/Article.php new file mode 100644 index 0000000..588ebd6 --- /dev/null +++ b/src/Model/Entity/Article.php @@ -0,0 +1,94 @@ + 'CURRENT_TIMESTAMP'], unique: true)] + private ?\DateTime $date_time; // le type datetime de doctrine convertit en type \DateTime de PHP + + #[ORM\Column(type: "text")] // "string" => varchar(255) => risque HTML non valide + private string $title; + + #[ORM\Column(type: "text")] + private string $preview; // une simple textarea + + #[ORM\Column(type: "text")] + private string $content; // de l'éditeur html + + #[ORM\ManyToMany(targetEntity: Image::class, inversedBy: "article")] // cascade: ['remove'] = très dangereux! + #[ORM\JoinTable( + name: TABLE_PREFIX . "article_image", + joinColumns: [new ORM\JoinColumn(name: "article_id", referencedColumnName: "id_article", onDelete: "CASCADE")], // onDelete: "CASCADE": très utile + inverseJoinColumns: [new ORM\JoinColumn(name: "image_id", referencedColumnName: "id_image", onDelete: "CASCADE")] + )] + private Collection $images; + + public function __construct(string $content, ?\DateTime $date_time = null, string $title = '', string $preview = '') + { + $this->date_time = $date_time; + $this->title = $title; + $this->preview = $preview; + $this->content = $content; + $this->images = new ArrayCollection(); // initialisation nécessaire + } + + public function getDateTime(): \DateTime + { + return $this->date_time; + } + public function setDateTime(\DateTime $date_time): void + { + $this->date_time = $date_time; + } + public function getTimestamp(): int + { + return $this->date_time->getTimestamp(); + } + public function getTitle(): string + { + return $this->title; + } + public function setTitle(string $title): void + { + $this->title = $title; + } + public function getPreview(): string + { + return $this->preview; + } + public function setPreview(string $preview): void + { + $this->preview = $preview; + } + public function getContent(): string + { + return $this->content; + } + public function setContent(string $data): void + { + $this->content = $data; + } + + public function getImages(): Collection + { + return $this->images; + } +} diff --git a/src/Model/Entity/Asset.php b/src/Model/Entity/Asset.php new file mode 100644 index 0000000..51e21fe --- /dev/null +++ b/src/Model/Entity/Asset.php @@ -0,0 +1,68 @@ +file_name = $name; + $this->mime_type = $mime_type; + $this->hash = $hash; + } + + public function getFileName(): string + { + return $this->file_name; + } + public function setFileName(string $name): void + { + $this->file_name = $name; + } + public function getMimeType(): string + { + return $this->mime_type; + } + public function setMimeType(string $mime_type): void + { + $this->mime_type = $mime_type; + } + public function getHash(): string + { + return $this->hash; + } + + public function getAssetEmployments(): Collection + { + return $this->asset_employment; + } +} diff --git a/src/Model/Entity/AssetEmployment.php b/src/Model/Entity/AssetEmployment.php new file mode 100644 index 0000000..e608af0 --- /dev/null +++ b/src/Model/Entity/AssetEmployment.php @@ -0,0 +1,54 @@ + ressource unique" (paires node_data/role uniques) +// doctrine gère mal les clés primaires triples, j'ai donc choisi une clé primaire double node_data_id/role + +declare(strict_types=1); + +namespace App\Entity; + +use Doctrine\ORM\Mapping as ORM; + +#[ORM\Entity] +#[ORM\Table(name: TABLE_PREFIX . 'asset_employment')] +class AssetEmployment +{ + // clé primaire double + #[ORM\Id] + #[ORM\ManyToOne(targetEntity: NodeData::class, inversedBy: 'asset_employment')] + #[ORM\JoinColumn(name: 'node_data_id', referencedColumnName: 'id_node_data', onDelete: 'CASCADE')] + private NodeData $node_data; + + #[ORM\Id] + #[ORM\Column(type: 'string', length: 50)] + private string $role; + + #[ORM\ManyToOne(targetEntity: Asset::class, inversedBy: 'asset_employment')] + #[ORM\JoinColumn(name: 'asset_id', referencedColumnName: 'id_asset', onDelete: 'CASCADE')] + private Asset $asset; + + public function __construct(NodeData $node_data, Asset $asset, string $role){ + $this->node_data = $node_data; + $this->asset = $asset; + $this->role = $role; + } + + /*public function getNodeData(): NodeData + { + return $this->node_data; + }*/ + public function getAsset(): Asset + { + return $this->asset; + } + public function setAsset(Asset $asset): self + { + $this->asset = $asset; + return $this; + } + public function getRole(): string + { + return $this->role; + } +} diff --git a/src/Model/Entity/Email.php b/src/Model/Entity/Email.php new file mode 100644 index 0000000..8ad66fd --- /dev/null +++ b/src/Model/Entity/Email.php @@ -0,0 +1,131 @@ + 'CURRENT_TIMESTAMP'])] + private \DateTime $date_time; + + #[ORM\Column(type: 'boolean')] + private bool $is_sensitive; // "sensitive" tout court est un mot réservé + + #[ORM\Column(type: 'datetime', options: ['default' => 'CURRENT_TIMESTAMP'])] + private \DateTime $last_contact_date; + + #[ORM\Column(type: 'datetime', nullable: true)] + private ?\DateTime $is_sensitive_since; + + #[ORM\ManyToOne(targetEntity: EmailForm::class)] + #[ORM\JoinColumn(name: "email_form_id", referencedColumnName: "id_email_form", nullable: true)] + private ?EmailForm $email_form; + + public function __construct(string $sender_name, string $sender_address, string $recipient, string $content, EmailForm $email_form, bool $sensitive = false){ + $this->sender_name = strtolower($sender_name); + $this->sender_address = strtolower($sender_address); + $this->recipient = strtolower($recipient); + $this->content = $content; + $this->date_time = new \DateTime; + $this->last_contact_date = new \DateTime; + $this->email_form = $email_form; + $this->makeSensitive($sensitive); + } + + public function getId(): int + { + return $this->id_email; + } + public function getSenderName(): string + { + return $this->sender_name; + } + public function getSenderAddress(): string + { + return $this->sender_address; + } + public function getRecipient(): string + { + return $this->recipient; + } + public function getContent(): string + { + return $this->content; + } + public function getDateTime(): \DateTime + { + return $this->date_time; + } + /*public function getLastContactDate(): \DateTime + { + return $this->last_contact_date; + }*/ + public function isSensitive(): bool + { + return $this->is_sensitive; + } + public function isSensitiveSince(): ?\DateTime + { + return $this->is_sensitive_since; + } + + public function makeSensitive(bool $sensitive = true): void + { + $this->is_sensitive = $sensitive; + if($sensitive && $this->is_sensitive_since === null){ + $this->is_sensitive_since = new \DateTime(); + } + } + + public function updateLastContactDate(): void + { + $this->last_contact_date = new \DateTime; + } + + public function getDeletionDate(): \DateTime // utilise une durée de conservation $period qui est propre au bloc formulaire (à son EmailForm) + { + // tests appliqués: + // => e-mail associé à un formulaire? + // => ce formulaire dispose d'une durée de stockage spécifique? + // => cette donnée est un entier > 0 + $key = $this->is_sensitive ? 'retention_period_sensible' : 'retention_period'; + $period = $this->email_form ? (int)($this->email_form->getData()[$key] ?? null) : null; + + $default = $this->is_sensitive ? self::DEFAULT_RETENTION_PERIOD_SENSITIVE : self::DEFAULT_RETENTION_PERIOD; + $period = ($period === null || $period <= 0) ? $default : $period; + + $date = clone ($this->is_sensitive ? $this->is_sensitive_since : $this->last_contact_date); // erreur si "sensible" mais sans date disponible (pas censé arriver) + return $date->modify('+ ' . (string)$period . ' month'); + } +} \ No newline at end of file diff --git a/src/Model/Entity/EmailForm.php b/src/Model/Entity/EmailForm.php new file mode 100644 index 0000000..5c54b86 --- /dev/null +++ b/src/Model/Entity/EmailForm.php @@ -0,0 +1,57 @@ +data = $data; + $this->node = $node; + } + + public function getId(): int + { + return $this->id_email_form; + } + + // getData et updateData sont indentiques au code dans NodeData + // plutôt qu'une interface, pourquoi pas une classe abstraite? ou peut-être un trait? + public function getData(): array + { + return $this->data; + } + public function updateData(string $key, string|int|bool|array $value = ''): void + { + if($value !== ''){ + $this->data[$key] = $value; + } + // si $value est vide, supprime la clé + elseif(isset($this->data[$key])){ + unset($this->data[$key]); + } + } + + public function setNode(?Node $node): void + { + $this->node = $node; + } +} \ No newline at end of file diff --git a/src/Model/Entity/Event.php b/src/Model/Entity/Event.php new file mode 100644 index 0000000..3b81da7 --- /dev/null +++ b/src/Model/Entity/Event.php @@ -0,0 +1,129 @@ + 255) + + #[ORM\Column(type: 'text')] + private string $description = ''; + // chatgpt: Dans un contexte API/Front comme FullCalendar, + // préférer une chaîne vide à une varaible "null" peut être plus pratique, + // car ça évite des contrôles côté JS. + + #[ORM\Column(type: 'datetime')] // chatgpt: Doctrine suppose UTC si pas de configuration spécifique + private \DateTimeInterface $start; // typage possible avec une interface, + //chatgpt: choix \DateTime par défaut, autorise \DateTimeImmutable + + #[ORM\Column(type: 'datetime')] + private \DateTimeInterface $end; + + #[ORM\Column(type: 'boolean')] + private bool $all_day; + + #[ORM\Column(type: 'string', length: 7, nullable: true)] + private ?string $color = null; + + public function __construct(array $json){ + $this->securedUpdateFromJSON($json); + } + + public function securedUpdateFromJSON(array $json): void + { + $this->title = htmlspecialchars($json['title']); + $this->description = htmlspecialchars($json['description']); + try{ + $this->start = new \Datetime($json['start']); + $this->end = new \Datetime($json['end']); + } + catch(\Exception $e){ + throw new \InvalidArgumentException('Bad date input'); + } + $all_day = filter_var($json['allDay'] ?? null, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE); + if(!is_bool($all_day)){ + throw new \InvalidArgumentException('Bad checkbox input'); + } + $this->all_day = $all_day; + $this->color = isset($json['color']) ? htmlspecialchars($json['color']) : null; + } + + public function getId(): int + { + return $this->id; + } + + public function getTitle(): string + { + return $this->title; + } + /*public function setTitle(string $title): self + { + $this->title = $title; + return $this; + }*/ + + public function getDescription(): string + { + return $this->description; + } + /*public function setDescription(string $description = ''): self + { + $this->description = $description; + return $this; + }*/ + + public function getStart(): \DateTimeInterface + { + return $this->start; + } + /*public function setStart(\DateTimeInterface $start): self + { + $this->start = $start; + return $this; + }*/ + + public function getEnd(): \DateTimeInterface + { + return $this->end; + } + /*public function setEnd(\DateTimeInterface $end): self + { + $this->end = $end; + return $this; + }*/ + + public function isAllDay(): bool + { + return $this->all_day; + } + /*public function setAllDay(bool $all_day): self + { + $this->all_day = $all_day; + return $this; + }*/ + + public function getColor(): ?string + { + return $this->color; + } + /*public function setColor(?string $color): self + { + $this->color = $color; + return $this; + }*/ +} diff --git a/src/Model/Entity/Image.php b/src/Model/Entity/Image.php new file mode 100644 index 0000000..06230ce --- /dev/null +++ b/src/Model/Entity/Image.php @@ -0,0 +1,89 @@ + Validation du type de fichier : On vérifie que le fichier est bien une image en utilisant le type MIME. On peut aussi vérifier la taille du fichier. + => Création d'un répertoire structuré : On génère un chemin dynamique basé sur la date (uploads/2024/12/24/) pour organiser les images. + => Génération d'un nom de fichier unique : On utilise uniqid() pour générer un nom unique et éviter les conflits de nom. + => Déplacement du fichier sur le serveur : Le fichier est déplacé depuis son emplacement temporaire vers le répertoire uploads/. + => Enregistrement dans la base de données : On enregistre les informations de l'image dans la base de données. */ + + #[ORM\ManyToMany(targetEntity: Article::class, mappedBy: "images")] + private Collection $article; + + public function __construct(string $name, ?string $path, ?string $path_mini, string $mime_type, string $alt) + { + $this->file_name = $name; + $this->file_path = $path; + $this->file_path_mini = $path_mini; + $this->mime_type = $mime_type; + $this->alt = $alt; + } + + public function getFileName(): string + { + return $this->file_name; + } + public function getFilePath(): string + { + return $this->file_path; + } + public function getFilePathMini(): string + { + return $this->file_path_mini; + } + public function getAlt(): string + { + return $this->alt; + } + + + // pour ViewBuilderController? + /*public function displayImage($imageId): void + { + //$imageId = 1; // Exemple d'ID d'image + $stmt = $pdo->prepare("SELECT file_path FROM images WHERE id = ?"); + $stmt->execute([$imageId]); + $image = $stmt->fetch(); + + if ($image) { + echo "Image"; + } else { + echo "Image non trouvée."; + } + }*/ +} diff --git a/src/Model/Entity/Log.php b/src/Model/Entity/Log.php new file mode 100644 index 0000000..b1cc168 --- /dev/null +++ b/src/Model/Entity/Log.php @@ -0,0 +1,38 @@ + 'CURRENT_TIMESTAMP'])] // CURRENT_TIMESTAMP "inutile", date générée par PHP + private \DateTime $date_time; // type datetime de doctrine <=> type \DateTime de PHP + + #[ORM\Column(type: "boolean")] + private bool $success; + + public function __construct(bool $success){ + $this->date_time = new \DateTime; + $this->success = $success; + } + + public function getFormatedDate(): string + { + return $this->date_time->format('d/m/Y à H\hi'); + } + public function getSuccess(): bool + { + return $this->success; + } +} diff --git a/src/Model/Entity/Node.php b/src/Model/Entity/Node.php new file mode 100644 index 0000000..07666c3 --- /dev/null +++ b/src/Model/Entity/Node.php @@ -0,0 +1,167 @@ +name_node = $name; + $this->position = $position; + $this->parent = $parent; + $this->page = $page; + $this->article = $article; + } + + // pfff... + public function getId(): ?int + { + return $this->id_node; + } + public function getName(): string + { + return $this->name_node; + } + /*public function setName(string $name): void + { + $this->name_node = $name; + }*/ + + public function getParent(): ?self + { + return $this->parent; + } + /*public function setParent(?self $parent): void + { + $this->parent = $parent; + }*/ + public function getPosition(): int + { + return $this->position; + } + public function setPosition(int $position): void + { + $this->position = $position; + } + public function getPage(): Page + { + return $this->page; + } + /*public function setPage(Page $page): void + { + $this->page = $page; + }*/ + + // utiliser l'interface de NodeData et EmailForm + public function getArticle(): Article + { + return $this->article; + } + /*public function setArticle(Article $article): void + { + $this->article = $article; + }*/ + + // une interface serait cool! + // OU possible un "héritage doctrine", faire en sorte que Node soit la mère de NodeData, EmailForm, Article... + // une seule instance est matérialisée par plusieurs tables en même temps + public function getNodeData(): NodeData|EmailForm|Presentation|null + { + // pour du polymorphisme (une unique variable $node_data): opter pour l'héritage + return match($this->name_node){ + 'form' => $this->email_form, + 'post_block', 'news_block' => $this->presentation, + default => $this->node_data, // inclut 'calendar' + }; + } + public function getChildren(): array + { + return $this->children; + } + public function getNodeByName(string $name): ?Node + { + foreach($this->children as $child){ + if($child->getName() === $name){ + return $child; + } + } + return null; + } + public function addChild(self $child): void + { + $this->children[] = $child; + + if(!Presentation::hasPresentation($this->getName())){ // post_block et news_block ont leurs enfants ordonnés avec ORDER BY + $this->sortChildren(false); + } + } + public function removeChild(self $child): void + { + foreach($this->children as $key => $object){ + if($object->getId() === $child->getId()){ + unset($this->children[$key]); + } + break; + } + $this->children = array_values($this->children); // réindexer pour supprimer la case vide + } + + public function getAdoptedChild(): ?self // peut renvoyer null + { + return $this->adopted; + } + public function setAdoptedChild(self $child): void + { + $this->adopted = $child; + } +} diff --git a/src/Model/Entity/NodeData.php b/src/Model/Entity/NodeData.php new file mode 100644 index 0000000..d81628d --- /dev/null +++ b/src/Model/Entity/NodeData.php @@ -0,0 +1,102 @@ + noeud "show_emails" + + public function __construct(array $data, Node $node, Collection $asset_employment = new ArrayCollection){ + $this->data = $data; + $this->node = $node; + $this->asset_employment = $asset_employment; + } + + public function getId(): int + { + return $this->id_node_data; + } + public function getData(): array + { + return $this->data; + } + /*public function setData(array $data): void // entrée = tableau associatif + { + $this->data = $data; + }*/ + public function updateData(string $key, string|int|bool|array $value = ''): void + { + if($value !== ''){ + $this->data[$key] = $value; + } + // si $value est vide, supprime la clé + elseif(isset($this->data[$key])){ + unset($this->data[$key]); + } + } + + public function setNode(?Node $node): void + { + $this->node = $node; + } + + public function getNodeDataAssets(): Collection + { + return $this->asset_employment; + } + public function getAssetEmploymentByRole(string $role): ?AssetEmployment + { + foreach($this->asset_employment as $nda){ + if($nda->getRole() === $role){ + return $nda; + } + } + return null; + } + public function getAssetByRole(string $role): ?Asset + { + $nda = $this->getAssetEmploymentByRole($role); + if($nda === null){ + return null; + } + return $nda->getAsset() ?? null; + } + + // pour affichage page Courriels + public function getEmails(): array // appelée dans ShowEmailsBuilder + { + return $this->emails; + } + public function setEmails(array $emails): void // appelée dans Model + { + $this->emails = $emails; + } +} \ No newline at end of file diff --git a/src/Model/Entity/Page.php b/src/Model/Entity/Page.php new file mode 100644 index 0000000..7cca3dc --- /dev/null +++ b/src/Model/Entity/Page.php @@ -0,0 +1,222 @@ +name_page = $name; + $this->end_of_path = $eop; + $this->description = $description; + $this->reachable = $reachable; + $this->in_menu = $in_menu; + $this->hidden = $hidden; + $this->position = $position; + $this->parent = $parent; + $this->children = new ArrayCollection(); + } + + // getters/setters + public function getId(): int + { + return $this->id_page; + } + public function getPageName(): string + { + return $this->name_page; + } + public function setPageName(string $name): void + { + $this->name_page = $name; + } + public function getPagePath(): string + { + return $this->page_path; + } + public function setPagePath(string $path):void + { + $this->page_path = $path; + } + public function getEndOfPath(): string + { + return $this->end_of_path; + } + public function setEndOfPath(string $end_of_path):void + { + $this->end_of_path = $end_of_path; + } + public function getDescription(): string + { + return $this->description; + } + public function setDescription(string $description): void + { + $this->description = $description; + } + + public function getCSS(): array + { + return array_merge(self::$default_css, $this->css ?? []); + } + public function addCSS(string $css): void + { + if(!in_array($css, $this->css ?? [])){ + $this->css[] = $css; + } + } + public function removeCSS(string $css): void + { + // array_diff renvoie une copie du 1er tableau alégée des élements existants aussi dans le 2è, array_values réindexe + $this->css = array_values(array_diff($this->css, [$css])); + } + public function getJS(): array + { + return array_merge(self::$default_js, $this->js ?? []); + //UPDATE `nb_page` SET `js` = NULL WHERE JSON_EQUALS(`js`, '["main"]'); + } + public function addJS(string $js): void + { + if(!in_array($js, $this->js ?? [])){ + $this->js[] = $js; + } + } + public function removeJS(string $js): void + { + $this->js = array_values(array_diff($this->js, [$js])); + } + + public function isReachable(): bool + { + return $this->reachable; + } + public function isInMenu(): bool + { + return $this->in_menu; + } + public function isHidden(): bool + { + return $this->hidden; + } + public function setHidden(bool $hidden): void + { + $this->hidden = $hidden; + } + public function getPosition(): ?int + { + return $this->position; + } + public function setPosition(int $position): void + { + $this->position = $position; + } + public function getParent(): ?Page + { + return $this->parent; + } + public function setParent(?Page $parent): void + { + $this->parent = $parent; + } + public function getChildren(): Collection + { + return $this->children; + } + + // utilisée par $menu_path + public function fillChildrenPagePath(string $parent_path = ''): void + { + $this->page_path = $parent_path != '' ? $parent_path . '/' . $this->end_of_path : $this->end_of_path; + foreach($this->getChildren() as $page){ + $page->fillChildrenPagePath($this->page_path); + } + } + + public function addChild(self $child): void + { + $this->children[] = $child; + $this->sortChildren(false); + } + public function removeChild(self $child): void + { + $this->children->removeElement($child); + $this->children = new ArrayCollection(array_values($this->children->toArray())); // réindexer en passant par un tableau + $this->sortChildren(false); + } + + public function findPageById(int $id): ?Page + { + $target = null; + foreach($this->children as $page){ + if($page->getId() === $id){ + return $page; + } + if(count($page->getChildren()) > 0){ + $target = $page->findPageById($id); + if($target !== null){ + return $target; + } + } + } + return $target; + } +} diff --git a/src/Model/Entity/Presentation.php b/src/Model/Entity/Presentation.php new file mode 100644 index 0000000..f5e3502 --- /dev/null +++ b/src/Model/Entity/Presentation.php @@ -0,0 +1,135 @@ +data = $data; + $this->node = $node; + /*if(!empty($presentation) && $presentation === 'grid'){ + $this->grid_cols_min_width = 250; + } + $this->chrono_order = $chrono_order ?? null;*/ + } + + /*public function getId(): int + { + return $this->id_presentation; + }*/ + + public function setNode(?Node $node): void + { + $this->node = $node; + } + + // un trait pour ça + public function getData(): array + { + return $this->data; + } + public function updateData(string $key, string|int|bool|array $value = ''): void + { + if($value !== ''){ + $this->data[$key] = $value; + } + // si $value est vide, supprime la clé + elseif(isset($this->data[$key])){ + unset($this->data[$key]); + } + } + + // spécifique aux blocs contenant des articles + public function getPresentation(): ?string + { + return $this->presentation; + } + public function setPresentation(string $presentation): void + { + $this->presentation = $presentation; + } + public function getColsMinWidth(): int + { + $default = 320; // pixels + return $this->grid_cols_min_width === null ? $default : $this->grid_cols_min_width; + } + public function setColsMinWidth(int $columns): void + { + $this->grid_cols_min_width = $columns; + } + public function getChronoOrder(): bool + { + return $this->chrono_order ?? false; + } + public function setChronoOrder(bool $reverse_order): void + { + $this->chrono_order = $reverse_order; + } + + public function getPaginationLimit(): ?int + { + $default = 12; // si 0 pas de pagination, 12 rend bien avec des grilles de 2, 3 ou 4 colonnes + return $this->pagination_limit === null ? $default : $this->pagination_limit; + } + public function setPaginationLimit(int $pagination_limit): void + { + $this->pagination_limit = $pagination_limit; + } + public function getNumberOfPages(): int + { + return $this->nb_pages; + } + public function setNumberOfPages(int $nb_pages): void + { + $this->nb_pages = $nb_pages; + } + + + /* -- blocs et présentations disponibles dans l'application -- */ + const BLOCKS = ['post_block' => 'Articles libres', 'news_block' => 'Actualités', + //'galery' => 'Galerie', + 'calendar' => 'Calendrier', 'form' => 'Formulaire' + ]; + const PRESENTATIONS = ['fullwidth' => 'Pleine largeur', 'grid' => 'Grille', 'mosaic' => 'Mosaïque' + //, 'carousel' => 'Carrousel' + ]; + static public function hasPresentation(string $block): bool + { + return in_array($block, ['post_block', 'news_block']) ? true : false; + } +} \ No newline at end of file diff --git a/src/Model/Entity/User.php b/src/Model/Entity/User.php new file mode 100644 index 0000000..461a5bc --- /dev/null +++ b/src/Model/Entity/User.php @@ -0,0 +1,67 @@ +login = $login; + $this->role = $role; + $this->password = $password; + } + + public function getId(): int + { + return $this->id_user; + } + + public function getLogin(): string + { + return $this->login; + } + public function setLogin(string $login): void + { + $this->login = $login; + } + public function getRole(): string + { + return $this->role; + } + public function setRole(string $role): void + { + $this->role = $role; + } + public function getPassword(): string + { + return $this->password; + } + public function setPassword(string $password): void + { + $this->password = $password; + } +} \ No newline at end of file diff --git a/src/Model/EventDTO.php b/src/Model/EventDTO.php new file mode 100644 index 0000000..b65176c --- /dev/null +++ b/src/Model/EventDTO.php @@ -0,0 +1,53 @@ +id = $event->getId(); + $this->title = $event->getTitle(); + $this->description = $event->getDescription() ?? ''; // renvoie $event->getDescription() si existe et ne vaut pas "null" + $this->allDay = $event->isAllDay(); + $this->color = $event->getColor(); + + if($this->allDay){ + $this->start = $event->getStart()->format('Y-m-d'); + $this->end = $event->getEnd()->format('Y-m-d'); + } + else{ + $this->start = $event->getStart()->format('Y-m-d\TH:i:s\Z'); + $this->end = $event->getEnd()->format('Y-m-d\TH:i:s\Z'); + } + } + + public function toArray(): array + { + return [ + 'id' => $this->id, + 'title' => $this->title, + 'description' => $this->description, + 'start' => $this->start, + 'end' => $this->end, + 'allDay' => $this->allDay, + 'color' => $this->color, + ]; + } +} diff --git a/src/Model/Menu.php b/src/Model/Menu.php new file mode 100644 index 0000000..6b08950 --- /dev/null +++ b/src/Model/Menu.php @@ -0,0 +1,44 @@ +children = new ArrayCollection(); + + $bulk_data = $entityManager + ->createQuery('SELECT n FROM App\Entity\Page n WHERE n.parent IS null') // :Doctrine\ORM\Query + ->getResult(); // :array de Page + + foreach($bulk_data as $first_level_entries){ + // dans le menu + if($first_level_entries->isInMenu()){ + $this->addChild($first_level_entries); + } + // autres pages + else{ + // attention, seul le premier élément du chemin est pris en compte + $this->other_pages[] = $first_level_entries; + } + } + + foreach($this->getChildren() as $page){ + $page->fillChildrenPagePath(); + } + } + + public function getOtherPages(): array + { + return $this->other_pages; + } +} \ No newline at end of file diff --git a/src/Model/Model.php b/src/Model/Model.php new file mode 100644 index 0000000..fa8f764 --- /dev/null +++ b/src/Model/Model.php @@ -0,0 +1,316 @@ +entityManager = $entityManager; + $this->node = new Node; // instance mère "vide" ne possédant rien d'autre que des enfants + } + + // à déplacer dans Path ou un truc comme ça? + // couper Model en deux classes NodeModel et PageModel? + public function makeMenuAndPaths(): void // lit la table "page" + { + self::$menu = new Menu($this->entityManager); + self::$page_path = new Path(); + $this->page = self::$page_path->getLast(); + } + + public function getNode(): Node + { + return $this->node; + } + public function getArticleNode(): Node + { + return $this->article; + } + + // affichage d'une page ordinaire + public function getWholePageData(Request $request): void // lit la table "node" + jointures + { + $id = CURRENT_PAGE === 'article' ? htmlspecialchars($request->query->get('id')) : ''; + + if($id === ''){ // page "normale" + // récupérer tous les noeuds sauf les articles + $dql = "SELECT n FROM App\Entity\Node n WHERE n.name_node != 'new' AND n.name_node != 'post' AND (n.page = :page OR n.page IS null)"; + $bulk_data = $this->entityManager + ->createQuery($dql) + ->setParameter('page', $this->page) + ->getResult(); + + foreach($bulk_data as $parent_block){ + // groupes d'articles triés par bloc, permet de paginer par bloc + if(Presentation::hasPresentation($parent_block->getName())){ // = post_block ou news_block + $bulk_data = array_merge($bulk_data, $this->getNextArticles($parent_block, $request)[0]); + } + + // emails + if($parent_block->getName() === 'show_emails'){ // show_emails = l'afficheur + $parent_block->getNodeData()->setEmails($this->getAllEmails()); + + // aller chercher les formulaires à la place (future MAJ?) + //$this->getFormsNodeData(); + } + } + } + else{ // page "article" + $dql = 'SELECT n FROM App\Entity\Node n WHERE n.page = :page OR n.page IS null OR n.id_node = :id'; + $bulk_data = $this->entityManager + ->createQuery($dql) + ->setParameter('page', $this->page) + ->setParameter('id', $id) + ->getResult(); + } + + $this->makeNodeTree($bulk_data); + } + + // récupération d'articles + public function getNextArticles(Node $parent_block, Request $request): array + { + $qb = $this->entityManager->createQueryBuilder(); + $qb->select('n') + ->from('App\Entity\Node', 'n') + ->where('n.parent = :parent') + ->setParameter('parent', $parent_block); + + if($parent_block->getName() === 'post_block'){ + $qb->orderBy('n.position'); + } + elseif($parent_block->getName() === 'news_block'){ + $qb->join('n.article', 'a'); + if($parent_block->getNodeData()->getChronoOrder() ?? false){ // ordre antichrono par défaut + $qb->orderBy('a.date_time', 'ASC'); + } + else{ + $qb->orderBy('a.date_time', 'DESC'); + } + } + + // pagination + $limit = $parent_block->getNodeData()->getPaginationLimit(); // = 12 par défaut si = null en BDD + $this->paginateWithCursor($qb, $parent_block, $request->query->get('last_article')); + $result = $qb->getQuery()->getResult(); + + // il reste des articles à récupérer SI on vient d'en récupérer trop + // ET on gère le cas particulier de $limit <= 0 + $truncated = false; + if(count($result) > $limit && $limit > 0){ // si nb résultat > limit > 0 + $truncated = true; + array_pop($result); // compenser le $limit + 1 dans paginateWithCursor + } + + return [$result, $truncated]; // besoin exceptionnel de retourner deux valeurs + } + + private function paginateWithCursor(QueryBuilder $qb, Node $parent_block, ?string $last_article): void + { + //var_dump($last_article); + $limit = $parent_block->getNodeData()->getPaginationLimit(); // = 12 par défaut si = null en BDD + + if($limit > 0){ // si 0 ou moins pas de pagination + // nombres de "pages" d'articles + $nb_pages = $this->getNumberOfPages($parent_block, $limit); + $parent_block->getNodeData()->setNumberOfPages($nb_pages > 1 ? $nb_pages : 1); + + // adaptation de la requête + if($parent_block->getName() === 'post_block'){ + $qb->andWhere('n.position > :last_position') + ->setParameter('last_position', empty($last_article) ? 0 : $last_article) + ->setMaxResults($limit + 1); + } + elseif($parent_block->getName() === 'news_block'){ + $cursor_start = $parent_block->getNodeData()->getChronoOrder() ? '1970-01-01' : '9999-12-31'; + $qb->andWhere($parent_block->getNodeData()->getChronoOrder() ? 'a.date_time > :date_time' : 'a.date_time < :date_time') + ->setParameter('date_time', empty($last_article) ? $cursor_start : $last_article) + ->setMaxResults($limit + 1); + } + } + } + + // le Paginator de doctrine le fait aussi si on décidait de s'en servir + private function getNumberOfPages(Node $parent_block, int $limit): int + { + $dql = 'SELECT COUNT(n.id_node) FROM App\Entity\Node n WHERE n.parent = :parent'; + $nb_articles = $this->entityManager + ->createQuery($dql) + ->setParameter('parent', $parent_block) + ->getSingleScalarResult(); + return (int)ceil($nb_articles / $limit); // que PHP fasse une division non euclidienne (pas comme en C) nous arrange ici + } + + private function makeNodeTree(array $bulk_data): void // $bulk_data = tableau de Node + { + // puis on les range + // (attention, risque de disfonctionnement si les noeuds de 1er niveau ne sont pas récupérés en 1er dans la BDD) + foreach($bulk_data as $node){ + // premier niveau + if($node->getParent() == null){ + $this->node->addChild($node); + + // spécifique page article + if($node->getName() === 'main' && $this->page->getEndOfPath() == 'article'){ + $main = $node; + } + } + // autres niveaux + else{ + $node->getParent()->addChild($node); + + // spécifique page article + if($this->page->getEndOfPath() == 'article'){ + if($node->getName() === 'new'){ + $new = $node; + } + } + } + } + if(isset($new)){ + $main->setAdoptedChild($new); + } + } + + // le basique + public function findNodeById(int $id): bool + { + $this->node = $this->entityManager->find('App\Entity\Node', $id); + return $this->node === null ? false : true; + } + public function findWhateverNode(string $field, string $value): bool + { + $queryBuilder = $this->entityManager->createQueryBuilder(); + $queryBuilder + ->select('n') + ->from('App\Entity\Node', 'n') + ->where("n.$field = :value") // avec le querybuilder, ce truc sale reste sécurisé + ->setParameter('value', $value); + $result = $queryBuilder->getQuery()->getOneOrNullResult(); + + if($result === null){ + return false; + } + else{ + $this->node = $result; + return true; + } + } + public function getWhatever(string $class, string $field, string $value): array + { + // penser au entityManager "repository" + $queryBuilder = $this->entityManager->createQueryBuilder(); + $queryBuilder + ->select('n') + ->from($class, 'n') + ->where("n.$field = :value") + ->setParameter('value', $value); + return $queryBuilder->getQuery()->getResult(); + } + + // récupération d'un article pour modification + public function makeArticleNode(string $id = '', bool $get_section = false): bool + { + if($get_section){ + $dql = 'SELECT n, p FROM App\Entity\Node n LEFT JOIN n.parent p WHERE n.id_node = :id'; + } + else{ + $dql = 'SELECT n FROM App\Entity\Node n WHERE n.id_node = :id'; + } + // n est l'article et p son $parent + $bulk_data = $this->entityManager + ->createQuery($dql) + ->setParameter('id', $id) + ->getResult(); + + if(count($bulk_data) === 0){ + return false; + } + + if($get_section){ + $this->article = $bulk_data[0]; + $this->findNodeById($bulk_data[0]->getParent()->getId()); + $this->makeSectionNode(); + } + else{ + $this->article = $bulk_data[0]; + } + + return true; + } + + // récupération des articles d'un bloc
à la création d'un article + public function makeSectionNode(): bool + { + $bulk_data = $this->entityManager + ->createQuery('SELECT n FROM App\Entity\Node n WHERE n.parent = :parent') + ->setParameter('parent', $this->node) + ->getResult(); + + foreach($bulk_data as $article){ + $this->node->addChild($article); // pas de flush, on ne va pas écrire dans la BDD à chaque nouvelle page + } + return true; + } + + public function findUniqueNodeByName(string $name): void // = unique en BDD, donc sans "page" associée + { + $bulk_data = $this->entityManager + ->createQuery('SELECT n FROM App\Entity\Node n WHERE n.name_node = :name') + ->setParameter('name', $name) + ->getResult(); + $this->node = $bulk_data[0]; + } + + public function findItsChildren(): void + { + $bulk_data = $this->entityManager + ->createQuery('SELECT n FROM App\Entity\Node n WHERE n.parent = :parent AND n.page = :page') + ->setParameter('parent', $this->node) + ->setParameter('page', $this->page) + ->getResult(); + foreach($bulk_data as $child){ + $this->node->addChild($child); + } + } + + private function getAllEmails(): array + { + $dql = 'SELECT e FROM App\Entity\Email e'; + return $this->entityManager + ->createQuery($dql) + ->getResult(); + } + /*private function getFormsNodeData(): array + { + $dql = 'SELECT fd FROM App\Entity\NodeData fd WHERE node.name_node = form'; + return $this->entityManager + ->createQuery($dql) + //->setParameter('page', $this->page) + ->getResult(); + }*/ + //private function getEmailsBySender(string $sender): array +} diff --git a/src/Model/Path.php b/src/Model/Path.php new file mode 100644 index 0000000..91eb2ff --- /dev/null +++ b/src/Model/Path.php @@ -0,0 +1,80 @@ +findPage(Model::$menu, $path_array); // remplit $this->current_page + } + catch(\Exception $e){} + } + + // produit un tableau de Page en comparant le chemin demandé avec les données dans Menu + // succès => une exception est lancée pour sortir des fonctions imbriquées + // echec => redirection vers la page erreur 404 + private function findPage(Page|Menu $menu, array $path_array): void + { + // recherche dans les autres pages + if($menu instanceof Menu){ + foreach($menu->getOtherPages() as $page){ + if($path_array[0] === $page->getEndOfPath()){ + $this->current_page[] = $page; + throw new \Exception(); + } + } + } + // recherche dans le menu + foreach($menu->getChildren() as $page){ + if($path_array[0] === $page->getEndOfPath()){ + $this->current_page[] = $page; + if(count($path_array) > 1){ + array_shift($path_array); // $this->path_array n'est pas modifié, un tableau PHP est passé à une fonction par copie + $this->findPage($page, $path_array); + } + else{ + throw new \Exception(); // sortir de tous les findPage() en même temps + } + } + } + + // rien trouvé + header('Location: '. new URL(['page' => Model::$menu->getChildren()[0]->getEndOfPath()])); + die; + } + + public function getString(): string + { + $path_string = ""; + foreach($this->current_page as $one_page){ + $path_string .= $one_page->getEndOfPath() . '/'; + } + return rtrim($path_string, '/'); + } + public function getArray(): array + { + return $this->current_page; + } + + // c'est là qu'on est quoi + public function getLast(): Page + { + return $this->current_page[count($this->current_page) - 1]; + } +} \ No newline at end of file diff --git a/src/Model/Position.php b/src/Model/Position.php new file mode 100644 index 0000000..7799cdf --- /dev/null +++ b/src/Model/Position.php @@ -0,0 +1,52 @@ +children); $i++) + { + $tmp = $this->children[$i]; + $j = $i - 1; + + // Déplacez les éléments du tableau qui sont plus grands que la clé à une position devant leur position actuelle + while($j >= 0 && $this->children[$j]->getPosition() > $tmp->getPosition()) { + $this->children[$j + 1] = $this->children[$j]; + $j--; + } + $this->children[$j + 1] = $tmp; + } + + // récursivité (utile dans Menu et chemin) + // appel dans Page et dans MenuAndPathController (un tri supplémentaire à faire?) + if(self::class === 'App\Entity\Page'){ + foreach($this->children as $child) { + if(count($child->children) > 0) { + $child->sortChildren($reindexation); + } + } + } + + if($reindexation){ + $this->reindexPositions(); + } + } + + // nouvelles positions (tableau $children => BDD) + public function reindexPositions(): void + { + $i = 1; + foreach($this->children as $child){ + $child->setPosition($i); + $i++; + } + } +} \ No newline at end of file diff --git a/src/Model/doctrine-bootstrap.php b/src/Model/doctrine-bootstrap.php new file mode 100644 index 0000000..13db87f --- /dev/null +++ b/src/Model/doctrine-bootstrap.php @@ -0,0 +1,34 @@ + Config::$db_driver, + 'user' => Config::$user, + 'password' => Config::$password, + 'host' => Config::$db_host, + 'dbname' => Config::$database, +], $config); + +// obtaining the entity manager +$entityManager = new EntityManager($connection, $config); + +foreach($entityManager->getMetadataFactory()->getAllMetadata() as $class){} \ No newline at end of file -- cgit v1.2.3