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/service/Backup.php | 233 ------------------------- src/service/Captcha.php | 55 ------ src/service/Config.php | 88 ---------- src/service/EmailService.php | 104 ----------- src/service/FormValidation.php | 214 ----------------------- src/service/Installation.php | 107 ------------ src/service/Router.php | 376 ---------------------------------------- src/service/Security.php | 110 ------------ src/service/URL.php | 88 ---------- src/service/User.php | 23 --- src/service/UserDataService.php | 46 ----- src/service/session.php | 90 ---------- 12 files changed, 1534 deletions(-) delete mode 100644 src/service/Backup.php delete mode 100644 src/service/Captcha.php delete mode 100644 src/service/Config.php delete mode 100644 src/service/EmailService.php delete mode 100644 src/service/FormValidation.php delete mode 100644 src/service/Installation.php delete mode 100644 src/service/Router.php delete mode 100644 src/service/Security.php delete mode 100644 src/service/URL.php delete mode 100644 src/service/User.php delete mode 100644 src/service/UserDataService.php delete mode 100644 src/service/session.php (limited to 'src/service') diff --git a/src/service/Backup.php b/src/service/Backup.php deleted file mode 100644 index 2ee69c1..0000000 --- a/src/service/Backup.php +++ /dev/null @@ -1,233 +0,0 @@ -format('Y-m-d') . '_' . $type . '.sql'; - - // les versions de mysql sont comme ci: 8.0.36 - // celles de mariadb sont comme ça: 10.11.6-MariaDB - $version = $entityManager->getConnection()->fetchOne('SELECT VERSION()'); - $engine = stripos($version, 'mariadb') !== false ? 'mariadb-dump' : 'mysqldump'; - - $tmp = tempnam('../var', 'tmp_db_codes_'); // crée un fichier avec un nom aléatoire et des droits 600 (concurrence) - // attention, si le dossier de destination n'est pas disponible, le fichier est placé avec les fichiers temporaires - file_put_contents($tmp, - "[client]\n - user=" . Config::$user . "\n - password=" . Config::$password . "\n - host=" . Config::$db_host . "\n"); - - $command = new Process([ - $engine, - '--defaults-extra-file=' . $tmp, // pour ne pas enregistrer les codes dans l'historique de la console ou dans les processus de l'OS - '--single-transaction', - '--quick', // évite d'exploser la RAM si beaucoup de données - '--result-file=' . $file_path, - Config::$database - ]); - - try{ - // unlink et chmod permettent que le serveur et l'utilisateur appelant bin/mysqldump.php réussissent - if(file_exists($file_path)){ - unlink($file_path); - } - $command->mustRun(); // comme run() mais lance une ProcessFailedException - - //$file_path = self::gzipCompress($file_path); // '.gz' ajouté à la fin - - chmod($file_path, 0666); - return $file_path; - } - finally{ - // exécuté même quand situé après "return" - unlink($tmp); - self::cleanBackups(); - } - } - - // compression gzip (gros gain de place sur le serveur), nécessite l'extension zlib - static public function gzipCompress(string $file_path): string - { - try{ - file_put_contents( - $file_path . '.gz', - gzencode(file_get_contents($file_path), 5), // plus rapide que 9 et taille identique d'après mes essais - ); - unlink($file_path); - $file_path .= '.gz'; - } - finally{ - return $file_path; - } - } - - static public function getBackupList(): array - { - $files = scandir(Backup::$backup_dir); // affiche un warning si échoue (à cacher en prod) - if(!$files){ - throw new RuntimeException("Le serveur a rencontré une erreur:
Accès aux backups impossible faute de permissions."); - } - - $backup_array = []; - foreach($files as $file){ - if($file[0] === '.'){ - continue; - } - $backup_array[] = $file; - } - return $backup_array; - } - - static public function cleanBackups(): void - { - $files = glob(self::$backup_dir . '/*.sql'); - //usort($files, fn($a, $b) => filemtime($b) <=> filemtime($a)); // filemtime = date de dernière modification - arsort($files); - - // tri par nom de BDD puis par date - $sorted_files = []; - $list_by_database = []; // pour le nettoyage 2 - foreach($files as $file){ - $exploded = explode('_', basename($file)); - $sorted_files[$exploded[0]][$exploded[1]][] = $file; - $list_by_database[$exploded[0]][] = $file; - } - - $date = new DateTime; - foreach($sorted_files as $db_name => $from_one_database){ - // on garde une "quantité à garder" par BDD - if(count($from_one_database) > self::$amount_to_keep){ - // nettoyage 1 - foreach($from_one_database as $date_key => $with_same_date){ - // pas touche à aujourd'hui - if($date_key != $date->format('Y-m-d')){ - self::cleanBackupsByPriority($with_same_date); - } - } - // nettoyage 2 - $files_to_delete = array_slice($list_by_database[$db_name], self::$amount_to_keep); - foreach($files_to_delete as $file){ - unlink($file); - } - } - } - } - - // conserver un seul backup par jour choisi dans cet ordre de préférence: console => before-restore => download => auto - // cet ordre correspond à: volonté de l'utilisateur => état du jour avant changement => volonté de changement => automatique sans contrôle - static private function cleanBackupsByPriority(array $files): void - { - $priorities = [ - 'console' => 1, - 'before-restore' => 2, - 'uploaded' => 3, - 'auto' => 4, - ]; - $best_priority = PHP_INT_MAX; - - // recherche du fichier à conserver - $to_keep = null; - foreach($files as $file){ - foreach($priorities as $keyword => $priority){ - if(str_contains(basename($file), $keyword) && $priority < $best_priority){ - $best_priority = $priority; - $to_keep = $file; - break; - } - } - } - // suppression des autres - foreach($files as $file){ - if($file !== $to_keep){ - unlink($file); - } - } - } - - static public function restoreDatabase(EntityManager $entityManager, string $file_name, array $exclusion_list = []): void - { - // création d'un backup de sécurité non écrasable - $date = new DateTime; - if(!file_exists(self::$backup_dir . '/' . Config::$database . '_' . $date->format('Y-m-d') . '_before-restore.sql')){ - Backup::mySQLdump($entityManager, 'before-restore'); - } - - $version = $entityManager->getConnection()->fetchOne('SELECT VERSION()'); - $engine = stripos($version, 'mariadb') !== false ? 'mariadb' : 'mysql'; - - $tables = self::getDBListOfTables($entityManager, $exclusion_list); // choisir les tables à effacer et restaurer, exclure "user" - - $tmp_db_codes = tempnam('../var', 'tmp_db_codes_'); // crée un fichier avec un nom aléatoire et des droits 600 (concurrence) - file_put_contents($tmp_db_codes, - "[client]\n - user=" . Config::$user . "\n - password=" . Config::$password . "\n - host=" . Config::$db_host . "\n"); - - //$file_name = self::gzipExtract($file_name); // '.gz' ajouté à la fin - - $command = new Process([ - $engine, // mariadb ou mysql - '--defaults-extra-file=' . $tmp_db_codes, // pour ne pas enregistrer les codes dans l'historique de la console ou dans les processus de l'OS - Config::$database - ]); - $command->setInput(file_get_contents(Backup::$backup_dir . '/' . $file_name)); // l'entrée < - - // utiliser une liste de tables à exclure dynamique - try{ - // tout effacer - $tables_with_backquotes = array_map(fn($t) => '`' . $t . '`', $tables); - if(!empty($tables_with_backquotes)){ - $sql = "SET FOREIGN_KEY_CHECKS=0; DROP TABLE " . implode(', ', $tables_with_backquotes) . "; SET FOREIGN_KEY_CHECKS=1;"; - $entityManager->getConnection()->executeStatement($sql); - } - - // copie des tables à ne pas restaurer - foreach($exclusion_list as $excluded){ - $entityManager->getConnection()->executeStatement('RENAME TABLE `' . TABLE_PREFIX . $excluded . '` TO `' . TABLE_PREFIX . $excluded . '_dont_touch`;'); - } - - // restaurer - $command->mustRun(); // comme run() mais lance une ProcessFailedException - - // remettre table user comme avant - foreach($exclusion_list as $excluded){ - $entityManager->getConnection()->executeStatement('DROP TABLE `' . TABLE_PREFIX . $excluded . '`;'); - $entityManager->getConnection()->executeStatement('RENAME TABLE `' . TABLE_PREFIX . $excluded . '_dont_touch` TO `' . TABLE_PREFIX . $excluded . '`;'); - } - } - finally{ - // exécuté même quand situé après "return" - unlink($tmp_db_codes); - } - } - - static private function getDBListOfTables(EntityManager $entityManager, array $exclusion_list = []): array - { - $tables = $entityManager->getConnection()->createSchemaManager()->listTableNames(); - foreach($tables as $key => $elem){ - foreach($exclusion_list as $excluded){ - if(!is_string($excluded)){ // cas pas du tout censé arriver! - throw new LogicException("Un nom de table doit etre une chaîne de caractères."); - } - if($elem === TABLE_PREFIX . $excluded){ - unset($tables[$key]); - break; // petite optimisation - } - } - } - return $tables; - } -} \ No newline at end of file diff --git a/src/service/Captcha.php b/src/service/Captcha.php deleted file mode 100644 index d57f912..0000000 --- a/src/service/Captcha.php +++ /dev/null @@ -1,55 +0,0 @@ - on pourrait appliquer le pattern "singleton" (justification = le captcha devient une sorte de ressource partagée) - -declare(strict_types=1); - -class Captcha -{ - private int $a; - private int $b; - - public function __construct(){ - $this->a = rand(2, 9); - $this->b = rand(2, 9); - } - - public function getA(): string - { - return $this->toLettersFrench($this->a); - } - public function getB(): string - { - return $this->toLettersFrench($this->b); - } - public function getSolution(): int - { - return ($this->a * $this->b); - } - - private function toLettersFrench(int $number): string - { - return match($number){ - 2 => 'deux', - 3 => 'trois', - 4 => 'quatre', - 5 => 'cinq', - 6 => 'six', - 7 => 'sept', - 8 => 'huit', - 9 => 'neuf', - default => '', // erreur - }; - } - - // (à déplacer dans FormValidation?) - static public function controlInput(string $input = '0'): int - { - // un POST est une chaîne qu'on doit convertir en nombre dans deux conditions: - // test de format: $input est un nombre - // test d'intégrité: supprimer les décimales avec (int) ne change pas la valeur du nombre - return is_numeric($input) && $input == (int)$input ? (int)$input : 0; - } -} \ No newline at end of file diff --git a/src/service/Config.php b/src/service/Config.php deleted file mode 100644 index 7077e01..0000000 --- a/src/service/Config.php +++ /dev/null @@ -1,88 +0,0 @@ -Le fichier config/config.ini n'existe pas ou n'est pas lisible.

"; - } - define('TABLE_PREFIX', self::$table_prefix); - } - - // renseigner les variables internes de Config - static private function hydrate(array $raw_data): void - { - foreach($raw_data as $field => $value) - { - if($value != '') // valeur par défaut - { - if(isset(self::$$field)) // le champ existe dans Config - { - // problème du slash à la fin du nom d'un dossier - $value = self::slashAtEndOfPath($field, $value); - self::$$field = $value; - } - else - { - echo "debug: le fichier config.ini comporte une erreur, le champ: " . $field . " est incorrect,\nl'information contenue sur cette ligne ne sera pas utilisée\n"; - } - } - /*else - { - echo "debug: le champ " . $field . " est vide, la valeur par défaut " . self::$$field . " sera utilisée.\n"; - }*/ - } - } - - - // pour que les chemins finissent toujours par un / - static private function slashAtEndOfPath(string $field, string $value): string - { - foreach(self::$path_vars as $item) - { - if($field === $item){ - return !str_ends_with($value, '/') ? $value . '/' : $value; - } - } - return $value; - } -} diff --git a/src/service/EmailService.php b/src/service/EmailService.php deleted file mode 100644 index 1579bdb..0000000 --- a/src/service/EmailService.php +++ /dev/null @@ -1,104 +0,0 @@ - exceptions - $mail->CharSet = 'UTF-8'; - - $smtp_host = $form_data->getData()['smtp_host'] ?? Config::$smtp_host; - $smtp_secure = $form_data->getData()['smtp_secure'] ?? Config::$smtp_secure; - $smtp_username = $form_data->getData()['smtp_username'] ?? Config::$smtp_username; - $smtp_password = $form_data->getData()['smtp_password'] ?? Config::$smtp_password; - - $email_from = $form_data->getData()['email_from'] ?? Config::$email_from; // adresse de l'expéditeur affichée - $email_from = empty($email_from) ? $smtp_username : $email_from; // si vide - $email_from_name = $form_data->getData()['email_from_name'] ?? Config::$email_from_name; // = nom de l'expéditeur affiché - $email_dest = $form_data->getData()['email_dest'] ?? Config::$email_dest; - $email_dest_name = $form_data->getData()['email_dest_name'] ?? Config::$email_dest_name; // = destinataire formulaire - - try{ - // Paramètres du serveur - $mail->isSMTP(); - $mail->Host = $smtp_host; - $mail->SMTPAuth = true; - $mail->Port = 25; - - if($mail->SMTPAuth){ - $mail->Username = $smtp_username; // e-mail - $mail->Password = $smtp_password; - $mail->SMTPSecure = $smtp_secure; // tls (starttls) ou ssl (smtps) - if($mail->SMTPSecure === 'tls'){ - $mail->Port = 587; - } - elseif($mail->SMTPSecure === 'ssl'){ - $mail->Port = 465; - } - } - //var_dump($mail->smtpConnect());die; // test de connexion - - // Expéditeur et destinataire - // $email_from, $email_from_name et $email_dest_name sont modifiables uniquement dans le config.ini pour l'instant - $mail->setFrom(strtolower($email_from), $email_from_name); - $mail->addAddress(strtolower($email_dest), $email_dest_name); - - // Contenu - $mail->isHTML(true); - if($test_email){ - $mail->Subject = "TEST d'un envoi d'e-mail depuis le site web"; - } - else{ - $mail->Subject = 'Message envoyé par: ' . $name . ' (' . $email . ') depuis le site web'; - } - $mail->Body = $message; - $mail->AltBody = $message; - - $mail->send(); - - // copie en BDD - if(!$test_email && ($form_data->getData()['keep_emails'] ?? self::KEEP_EMAILS_DEFAULT)){ - $db_email = new Email($name, $email, $email_dest, $message, $form_data); - $entityManager->persist($db_email); - self::updateLastContactDate($entityManager, $email); - $entityManager->flush(); - } - - return true; - } - catch(Exception $e){ - echo "Le message n'a pas pu être envoyé. Erreur : {$e}
{$mail->ErrorInfo}"; - return false; - } - } - - static public function updateLastContactDate(EntityManager $entityManager, string $sender): void - { - foreach($entityManager->getRepository('App\Entity\Email')->findAll() as $email){ - $email->getSenderAddress() === $sender ? $email->updateLastContactDate() : null; - } - } - - // peut être appelée par bin/clean_emails_cron.php - static public function cleanEmails(EntityManager $entityManager): void - { - $emails = $entityManager->getRepository('App\Entity\Email')->findAll(); - foreach($emails as $email){ - if($email->getDeletionDate() < new \DateTime()){ - $entityManager->remove($email); - } - } - $entityManager->flush(); - } -} \ No newline at end of file diff --git a/src/service/FormValidation.php b/src/service/FormValidation.php deleted file mode 100644 index 9d0b838..0000000 --- a/src/service/FormValidation.php +++ /dev/null @@ -1,214 +0,0 @@ - 'emailSendStrategy', - self::EmailParam => 'emailParamStrategy', - self::CreateUser => 'createUserStrategy', - self::Connection => 'connectionStrategy', - self::UsernameUpdate => 'usernameUpdateStrategy', - self::PasswordUpdate => 'passwordUpdateStrategy' - }; - } -} - -class FormValidation -{ - private array $data; // tableau associatif (probablement $_POST) - private Closure $callStrategy; - private ?string $error; - - public function __construct(array $data, ValidationStrategy $strategy){ - $this->data = $data; - $method = $strategy->method(); - $this->callStrategy = fn() => $this->$method(); - } - - public function validate(): bool - { - $this->error = ($this->callStrategy)(); // appel de la callback - return empty($this->errors); - } - - public function getError(): string - { - return $this->error; - } - - public function getField(string $field): string - { - return $this->data[$field] ?? ''; - } - - // méthodes de validation - private function captchaValidate(bool $clean_session = true): ?string - { - $captcha_solution = (isset($_SESSION['captcha']) && is_int($_SESSION['captcha'])) ? $_SESSION['captcha'] : 0; - $captcha_try = isset($this->data['captcha']) ? Captcha::controlInput($this->data['captcha']) : 0; - if($clean_session){ - unset($_SESSION['captcha']); - } - - if($captcha_try == 0){ - return 'error_non_valid_captcha'; - } - elseif($captcha_solution == 0){ // ne peut pas arriver, si? - return 'captcha_server_error'; - } - elseif($captcha_try !== $captcha_solution){ - return 'bad_solution_captcha'; - } - return null; - } - - // erreurs à la création des mots de passe - static private function removeSpacesTabsCRLF(string $chaine): string - { - $cibles = [' ', "\t", "\n", "\r"]; // doubles quotes !! - return(str_replace($cibles, '', $chaine)); - } - - - // stratégies - private function emailSendStrategy(): ?string - { - if($error = $this->captchaValidate(false)){ - return $error; - } - - if(empty($this->data['name']) || empty($this->data['email']) || empty($this->data['message']) - || !isset($this->data['hidden']) || !empty($this->data['hidden'])){ - return 'missing_fields'; - } - - elseif(!filter_var(trim($this->data['email']), FILTER_VALIDATE_EMAIL)){ - return 'bad_email_address'; - } - - $this->data['name'] = htmlspecialchars(trim($this->data['name'])); - $this->data['email'] = htmlspecialchars(trim($this->data['email'])); - $this->data['message'] = htmlspecialchars($this->data['message']); - - return null; - } - private function emailParamStrategy(): ?string - { - if(empty($this->data['id']) || empty($this->data['what_param']) || empty($this->data['value']) - || !isset($this->data['hidden']) || !empty($this->data['hidden'])){ - return 'missing_fields'; - } - - if(!in_array($this->data['what_param'], ['smtp_host', 'smtp_secure', 'smtp_username', 'smtp_password', 'email_dest'])){ - return 'unknown_parameter'; - } - elseif($this->data['what_param'] === 'smtp_username' || $this->data['what_param'] === 'email_dest'){ - if(!filter_var($this->data['value'], FILTER_VALIDATE_EMAIL)){ - return 'invalide_email_address'; - } - } - return null; - } - private function createUserStrategy(): ?string - { - if($error = $this->captchaValidate()){ - return $error; - } - - // test mauvais paramètres - if(empty($this->data['login']) || empty($this->data['password']) || empty($this->data['password_confirmation']) - || !isset($this->data['create_user_hidden']) || !empty($this->data['create_user_hidden'])) - { - return 'bad_login_or_password'; - } - - if($this->data['password'] !== $this->data['password_confirmation']){ - return 'different_passwords'; - } - - if($this->data['login'] !== self::removeSpacesTabsCRLF(htmlspecialchars($this->data['login'])) - || $this->data['password'] !== self::removeSpacesTabsCRLF(htmlspecialchars($this->data['password']))){ - return 'forbidden_characters'; - } - return null; - } - private function connectionStrategy(): ?string - { - if($error = $this->captchaValidate()){ - return $error; - } - - if(empty($this->data['login']) || empty($this->data['password']) - || !isset($this->data['connection_hidden']) || !empty($this->data['connection_hidden'])) - { - return 'bad_login_or_password'; - } - return null; - } - private function usernameUpdateStrategy(): ?string - { - if($error = $this->captchaValidate()){ - return $error; - } - - if(empty($this->data['login']) || empty($this->data['password']) || empty($this->data['new_login']) - || !isset($this->data['modify_username_hidden']) || !empty($this->data['modify_username_hidden'])) - { - return 'bad_login_or_password'; - } - - $new_login = self::removeSpacesTabsCRLF(htmlspecialchars($this->data['new_login'])); - if($new_login !== $this->data['new_login']){ - return 'forbidden_characters'; - } - - if($this->data['login'] !== $_SESSION['user']['username']){ - return 'bad_login_or_password'; - } - if($this->data['login'] === $new_login){ - return 'same_username_as_before'; - } - return null; - } - private function passwordUpdateStrategy(): ?string - { - if($error = $this->captchaValidate()){ - return $error; - } - - if(empty($this->data['login']) || empty($this->data['password']) || empty($this->data['new_password']) - || !isset($this->data['modify_password_hidden']) || !empty($this->data['modify_password_hidden'])) - { - return 'bad_login_or_password'; - } - - $new_password = self::removeSpacesTabsCRLF(htmlspecialchars($this->data['new_password'])); - if($new_password !== $this->data['new_password']){ - return 'forbidden_characters'; - } - - if($this->data['login'] !== $_SESSION['user']['username']){ - return 'bad_login_or_password'; - } - if($this->data['password'] === $new_password){ - return 'same_password_as_before'; - } - return null; - } -} \ No newline at end of file diff --git a/src/service/Installation.php b/src/service/Installation.php deleted file mode 100644 index f7de2cf..0000000 --- a/src/service/Installation.php +++ /dev/null @@ -1,107 +0,0 @@ -L'extension " . $extension . " est manquante.

"); - $flag = true; - } - } - if(!class_exists(DOMDocument::class)){ // théoriquement plus fiable que extension_loaded() - echo("

L'extension dom est manquante.

"); - $flag = true; - } - - /*if(!extension_loaded('imagick') && !extension_loaded('gd')){ - echo("

il manque une de ces extensions au choix pour le traitement des images: imagick (de préférence) ou gd.

"); - $flag = true; - }*/ - // si imagick n'est pas disponible, essayer gd (reste encore à coder) - - if($flag){ - echo '

Réalisez les actions nécéssaires sur le serveur ou contactez l\'administrateur du site.
- Quand le problème sera résolu, il vous suffira de recharger la page.

'; - die; - } - } - - static public function checkFilesAndFoldersRights(): void - { - // -- droits des fichiers et dossiers -- - $droits_dossiers = 0777; - - $flag = false; - if(!file_exists('user_data')){ - try{ - mkdir('user_data/'); - chmod('user_data/', $droits_dossiers); - } - catch(Exception $e){ - echo '

Le dossier public/user_data introuvable et le serveur n\'a pas la permission de le créer.
- Pour faire ça bien:
sudo -u "serveur web" mkdir /chemin/du/site/public/user_data

'; - echo $e; - $flag = true; - } - } - if(!file_exists('../var')){ - try{ - mkdir('../var'); - chmod('../var', $droits_dossiers); - } - catch(Exception $e){ - echo $e; - $flag = true; - } - } - if(!file_exists('../var/backups')){ - try{ - mkdir('../var/backups'); - chmod('../var/backups', $droits_dossiers); // autoriser à la fois le serveur et les scripts dans bin/ - } - catch(Exception $e){ - echo $e; - $flag = true; - } - } - - // droits 600 pour celui-ci - if(!file_exists('../config/config.ini')){ - // aide à la création du config.ini - echo '

Le fichier config/config.ini est introuvable.

'; - echo '

Il doit obligatoirement contenir les codes de la base de données, le protocole http ou https (et éventuellement le port) utilisé pour créer les liens internes.
- Un modèle est disponible, il s\'agit du fichier config/config-template.ini

-

Ce fichier a une importance critique. Si vous le pouvez faites en sorte que le serveur en soit le propriétaire et donner lui des droits 600.

'; - $flag = true; - } - /*else{ - // propriétaire du fichier - if(fileowner('../config/config.ini') != posix_geteuid()){ - echo "

le fichier config/config.ini n'appartient pas au serveur.

"; - } - else{ - // droits du config.ini - if(substr(sprintf('%o', fileperms('../config/config.ini')), -4) != 600){ - echo '

Attention, le

'; - //chmod('../config/config.ini', $droits_fichiers); - } - } - }*/ - if($flag){ - die; - } - - // tester les liens internes - // - - // le test de connexion à la BDD est dans le doctrine bootstrap - } -} \ No newline at end of file diff --git a/src/service/Router.php b/src/service/Router.php deleted file mode 100644 index 28a6c39..0000000 --- a/src/service/Router.php +++ /dev/null @@ -1,376 +0,0 @@ - 1er test, méthode http GET? POST? -=> 2ème test, type de contenu: -"application/x-www-form-urlencoded" = formulaire -"application/json" = requête AJAX avec fetch() -"multipart/form-data" = upload d'image par tinymce -$_SERVER['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest' requête AJAX xhs, non utilisée -=> 3ème test, comme le 2ème test mais uniquement si IS_ADMIN est vrai */ - -/* classes de réponses pour les contrôleurs -Response($html, Response::HTTP_OK) page html -JsonResponse(['success' => true, 'data' => $data]) ajax -RedirectResponse('index.php?page=login') redirection -BinaryFileResponse($filePath) téléchargement -StreamedResponse(function () {echo "ligne 1\n";echo "ligne 2\n";}) gros fichier */ - -// relire ça à l'occaz: -// https://symfony.com/doc/current/introduction/from_flat_php_to_symfony.html - -declare(strict_types=1); - -use Symfony\Component\HttpFoundation\Request; -use Doctrine\ORM\EntityManager; -use Symfony\Component\HttpFoundation\JsonResponse; -use Symfony\Component\HttpFoundation\RedirectResponse; -use Symfony\Component\HttpFoundation\Response; - -class Router{ - private Request $request; - private EntityManager $entityManager; - private string $route = ''; // défaut page d'accueil - - public function __construct(Request $request, EntityManager $entityManager){ - $this->request = $request; - $this->entityManager = $entityManager; - - if(!User::existUsers($entityManager)){ // table "user" vide - $this->route = 'no_user'; - } - } - - public function dispatch(): Response - { - if($this->request->getMethod() === 'GET'){ - // table "user" vide - if($this->route === 'no_user'){ - ob_start(); - require AbstractBuilder::VIEWS_PATH . 'user_create.php'; - return new Response(ob_get_clean()); - } - - // bouton déconnexion (méthode GET parce que l'utilisateur ne modifie plus de données à partir de là) - if($this->request->query->get('action') === 'deconnection'){ - return UserController::disconnect(); // retourne un RedirectResponse - } - - // articles suivants - if($this->request->query->get('fetch') === 'next_articles'){ - return ArticleController::fetch($this->entityManager, $this->request); // retourne un JsonResponse - } - - // données du calendrier - // création du calendrier ou changement de dates affichées (boutons flèches mais pas changement de vue) - if($this->request->query->get('action') === 'get_events' - && $this->request->query->has('start') && $this->request->query->has('end') && empty($this->request->getPayload()->all())){ // getPayload ne récupère pas que des POST - return CalendarController::getData($this->entityManager); - } - - // pages interdites - if(!IS_ADMIN && in_array(CURRENT_PAGE, ['menu_paths', 'new_page', 'user_edit', 'emails', 'maintenance'])){ - return new RedirectResponse((string)new URL); - } - - if(IS_ADMIN){ - if($this->request->query->get('action') === 'get_mysqldump'){ - return MaintenanceController::getLastDump($this->entityManager); - } - if($this->request->query->get('action') === 'get_all_media'){ - return MaintenanceController::getAllMedia(); - } - } - - // affichage d'une page - return ViewController::getWebPage($this->entityManager, $this->request); - } - - - elseif($this->request->getMethod() === 'POST'){ - /* -- contrôleurs appelables par tout le monde -- */ - - // table "user" vide - if($this->route === 'no_user'){ - return UserController::createAdminUser($this->entityManager); - } - - // requête JSON avec fetch() - if($this->request->headers->get('Content-Type') === 'application/json') - { - $json = json_decode($this->request->getContent(), true); // = json_decode(file_get_contents('php://input'), true); - - // formulaire de contact - if($this->request->query->get('action') === 'send_email'){ - return ContactFormController::sendVisitorEmail($this->entityManager, $json); - } - /*else{ - return new JsonResponse(['success' => false, 'error' => 'tu fais quoi là mec?'], JsonResponse::HTTP_BAD_REQUEST); - }*/ - } - - // envoi formulaire HTML - elseif($this->request->headers->get('Content-Type') === 'application/x-www-form-urlencoded'){ - // tentative de connexion - if($this->request->query->get('action') === 'connection'){ - return UserController::connect($this->entityManager); - } - /*else{ - return new RedirectResponse((string)new URL(['error' => 'tu fais quoi là mec?'])); - }*/ - } - - if(IS_ADMIN){ - /* -- requêtes AJAX -- */ - - // requêtes JSON avec fetch() - if($this->request->headers->get('Content-Type') === 'application/json'){ - $json = json_decode($this->request->getContent(), true); // = json_decode(file_get_contents('php://input'), true); - - if($this->request->query->has('action')){ - /* -- manipulation des articles -- */ - if($this->request->query->get('action') === 'editor_submit' && isset($json['id']) && isset($json['content'])){ - return ArticleController::editorSubmit($this->entityManager, $json); - } - elseif($this->request->query->get('action') === 'delete_article' && isset($json['id'])){ - return ArticleController::deleteArticle($this->entityManager, $this->request); // version AJAX - } - elseif($this->request->query->get('action') === 'switch_positions' && isset($json['id1']) && isset($json['id2'])){ - return ArticleController::switchPositions($this->entityManager, $json); - } - elseif($this->request->query->get('action') === 'date_submit' && isset($json['id']) && isset($json['date'])){ - return ArticleController::dateSubmit($this->entityManager, $json); - } - - switch($this->request->query->get('action')){ - /* -- bloc Formulaire -- */ - case 'keep_emails': - return ContactFormController::keepEmails($this->entityManager, $json); - case 'set_retention_period': - return ContactFormController::setEmailsRetentionPeriod($this->entityManager, $json); - case 'set_email_param': - return ContactFormController::setEmailParam($this->entityManager, $json); - case 'test_email': - return ContactFormController::sendTestEmail($this->entityManager, $json); - - /* -- page emails -- */ - case 'delete_email': - return ContactFormController::deleteEmail($this->entityManager, $json); - case 'toggle_sensitive_email': - return ContactFormController::toggleSensitiveEmail($this->entityManager, $json); - - /* -- upload d'image dans tinymce par copier-coller -- */ - // collage de HTML contenant une ou plusieurs balises - case 'upload_image_url': - return ImageUploadController::uploadImageHtml(); - // collage d'une image (code base64 dans le presse-papier) non encapsulée dans du HTML - case 'upload_image_base64': - return ImageUploadController::uploadImageBase64(); - - /* -- requêtes spécifiques au calendrier -- */ - case 'new_event': - return CalendarController::newEvent($json, $this->entityManager); - case 'update_event': - return CalendarController::updateEvent($json, $this->entityManager); - case 'remove_event': - return CalendarController::removeEvent($json, $this->entityManager); - - /* -- mode maintenance -- */ - case 'get_logs': - return MaintenanceController::getLogs($this->entityManager); - case 'erase_logs': - return MaintenanceController::eraseLogs($this->entityManager); - - default: - return new JsonResponse(['success' => false, 'error' => 'bad parameters'], JsonResponse::HTTP_BAD_REQUEST); - } - } - - /* -- site entier (header, footer, favicon) -- */ - elseif($this->request->query->has('head_foot_text')){ - return HeadFootController::setTextData($this->entityManager, $this->request->query->get('head_foot_text'), $json); - } - elseif($this->request->query->has('head_foot_social_check')){ - return HeadFootController::displaySocialNetwork($this->entityManager, $this->request->query->get('head_foot_social_check'), $json); - } - - /* -- page Menu et chemins -- */ - elseif($this->request->query->has('menu_edit')){ - // ne suit pas la règle, faire ça dans un contrôleur? - Model::$menu = new Menu($this->entityManager); // récupération des données - - // flèche gauche <=: position = position du parent + 1, parent = grand-parent, recalculer les positions - if($this->request->query->get('menu_edit') === 'move_one_level_up' && isset($json['id'])){ - return MenuAndPathsController::MoveOneLevelUp($this->entityManager, $json); - } - // flèche droite =>: position (léments de a fraterie + 1, l'élément précédent devient le parent - elseif($this->request->query->get('menu_edit') === 'move_one_level_down' && isset($json['id'])){ - return MenuAndPathsController::MoveOneLevelDown($this->entityManager, $json); - } - elseif($this->request->query->get('menu_edit') === 'switch_positions' && isset($json['id1']) && isset($json['id2'])){ - return MenuAndPathsController::switchPositions($this->entityManager, $json); - } - elseif($this->request->query->get('menu_edit') === 'display_in_menu' && isset($json['id']) && isset($json['checked'])){ - return MenuAndPathsController::displayInMenu($this->entityManager, $json); - } - elseif($this->request->query->get('menu_edit') === 'url_edit' && isset($json['id']) && isset($json['field']) && isset($json['input_data'])){ - return MenuAndPathsController::editUrl($this->entityManager, $json); - } - else{ - return new JsonResponse(['success' => false, 'error' => 'bad parameters'], JsonResponse::HTTP_BAD_REQUEST); // code 400 - } - } - - /* -- mode Modification d'une page -- */ - // partie "page" - elseif($this->request->query->has('page_edit')){ - switch($this->request->query->get('page_edit')){ - case 'page_title': - return PageManagementController::setPageTitle($this->entityManager, $json); - case 'page_description': - return PageManagementController::setPageDescription($this->entityManager, $json); - default: - return new JsonResponse(['success' => false, 'error' => 'bad parameters'], JsonResponse::HTTP_BAD_REQUEST); // code 400 - } - } - - // partie "blocs" - elseif($this->request->query->has('bloc_edit')){ - switch($this->request->query->get('bloc_edit')){ - case 'rename_page_bloc': - return PageManagementController::renameBloc($this->entityManager, $json); - case 'switch_blocs_positions': - return PageManagementController::SwitchBlocsPositions($this->entityManager, $json, $this->request->query->get('page')); - case 'change_articles_order': - return PageManagementController::changeArticlesOrder($this->entityManager, $json); - case 'change_presentation': - return PageManagementController::changePresentation($this->entityManager, $json); - case 'change_cols_min_width': - return PageManagementController::changeColsMinWidth($this->entityManager, $json); - case 'change_pagination_limit': - return PageManagementController::changePaginationLimit($this->entityManager, $json); - default: - return new JsonResponse(['success' => false, 'error' => 'bad parameters'], JsonResponse::HTTP_BAD_REQUEST); // code 400 - } - } - - else{ - return new JsonResponse(['success' => false, 'error' => 'bad parameters'], JsonResponse::HTTP_BAD_REQUEST); // code 400 - } - } - - /* -- upload avec FormData OU formulaire HTML avec fichier -- */ - elseif(str_starts_with($this->request->headers->get('Content-Type'), 'multipart/form-data')){ // = $_SERVER['CONTENT_TYPE'] - // dans tinymce avec le plugin (bouton "insérer une image" de l'éditeur ou glisser-déposer) - if($this->request->query->get('action') === 'upload_image_tinymce'){ - return ImageUploadController::imageUploadTinyMce(); - } - // dans tinymce, des quatre méthodes: bouton "link", drag & drop, html, base64 - elseif($this->request->query->get('action') === 'upload_file_tinymce'){ - return FileUploadController::fileUploadTinyMce(); - } - elseif($this->request->query->has('head_foot_image')){ - return HeadFootController::uploadAsset($this->entityManager, $this->request->query->get('head_foot_image')); - } - - /* -- page Maintenance -- */ - elseif($this->request->query->get('action') === 'restore_database' && $this->request->request->get('hidden') === '' - && $this->request->files->has('uploaded_sql')){ - return MaintenanceController::downloadSQL($entityManager, $request); - } - else{ - // choix ici entre répondre en JSON ou par une redirection, choix du JSON pour pouvoir passer un message - return new JsonResponse(['success' => false, 'error' => 'bad parameters'], JsonResponse::HTTP_BAD_REQUEST); // code 400 - } - } - - /* -- formulaire HTML sans fichier -- */ - elseif($this->request->headers->get('Content-Type') === 'application/x-www-form-urlencoded'){ - if($this->request->query->get('action') === 'delete_article' && $this->request->query->has('id')){ - return ArticleController::deleteArticle($this->entityManager, $this->request); // version formulaire - } - - /* -- nouvelle page -- */ - elseif($this->request->request->get('page_name') !== null - && $this->request->request->get('page_name_path') !== null - && $this->request->request->get('page_location') !== null - && $this->request->request->get('page_description') !== null - && $this->request->request->get('new_page_hidden') === ''){ - return PageManagementController::newPage($this->entityManager, $this->request->request); - } - - /* -- suppression d'une page -- */ - elseif($this->request->request->get('page_id') !== null - && $this->request->request->get('submit_hidden') === ''){ - return PageManagementController::deletePage($this->entityManager, $this->request->request->get('page_id')); - } - - - /* -- mode Modification d'une page -- */ - // modification du chemins en snake_case - elseif($this->request->request->get('page_menu_path') !== null - && $this->request->request->get('page_id') !== null - && $this->request->request->get('page_name_path_hidden') === ''){ - return PageManagementController::updatePageMenuPath($this->entityManager, $this->request->request->get('page_menu_path')); - } - // ajout d'un bloc dans une page - elseif($this->request->request->get('bloc_title') !== null - && $this->request->request->get('bloc_select') !== null - && $this->request->request->get('bloc_title_hidden') === ''){ // contrôle anti-robot avec input hidden - return PageManagementController::addBloc($this->entityManager, $this->request); - } - // suppression d'un bloc de page - elseif($this->request->request->get('delete_bloc_id') !== null - && $this->request->request->get('delete_bloc_hidden') === ''){ // contrôle anti-robot avec input hidden - return PageManagementController::deleteBloc($this->entityManager, $this->request); - } - - - /* -- page Menu et chemins -- */ - // création d'une entrée de menu avec une URL - elseif($this->request->request->has("label_input") && $this->request->request->has("url_input") && $this->request->request->has("location")){ - return MenuAndPathsController::newUrlMenuEntry($this->entityManager); - } - // suppression d'une entrée de menu avec une URL - elseif($this->request->request->has('delete') && $this->request->request->has('x') && $this->request->request->has('y')){ // 2 params x et y sont là parce qu'on a cliqué sur une image - return MenuAndPathsController::deleteUrlMenuEntry($this->entityManager); - } - - - /* -- page Mon compte -- */ - elseif($this->request->query->get('action') === 'update_username'){ - return UserController::updateUsername($this->entityManager); - } - elseif($this->request->query->get('action') === 'update_password'){ - return UserController::updatePassword($this->entityManager); - } - - /* -- page Maintenance -- */ - elseif($this->request->query->get('action') === 'restore_database' && $this->request->get('hidden') === '' && $this->request->request->has('selected_sql')){ - return MaintenanceController::handleBackupSelection($this->entityManager, $this->request); - } - - // redirection page d'accueil - return new RedirectResponse((string)new URL(['error' => 'paramètres inconnus'])); - } - - // requêtes XMLHttpRequest - elseif($this->request->isXmlHttpRequest()){ - return new JsonResponse(['success' => false]); // noyer le poisson en laissant penser que le site gère les requêtes XHR - } - - // POST admin ne matchant pas - return new Response('bad parameters', Response::HTTP_BAD_REQUEST); // code 400 - } - - // POST non-admin ne matchant pas - return new Response('bad parameters', Response::HTTP_BAD_REQUEST); // code 400 - } - - // méthode HTTP inconnue - else{ - return new RedirectResponse((string)new URL(['error' => 'tu fais quoi là mec?'])); - } - } -} \ No newline at end of file diff --git a/src/service/Security.php b/src/service/Security.php deleted file mode 100644 index 356f4f4..0000000 --- a/src/service/Security.php +++ /dev/null @@ -1,110 +0,0 @@ -1, // protection contre les élements et attributs dangereux - - // liste blanche d'éléments HTML - 'elements'=> 'h1, h2, h3, h4, h5, h6, p, br, s, em, span, strong, a, ul, ol, li, sup, sub, code, blockquote, div, pre, table, caption, colgroup, col, tbody, tr, th, td, figure, img, figcaption, iframe, small', - - // liste noire d'attributs HTML - 'deny_attribute'=> 'id, class' // on garde 'style' - ); - // faire qu'un certain élément puisse n'avoir que certains attributs, regarder la doc - private static $specHtmLawed = ''; - - // obtenir du HTML non dangereur sans appliquer htmlspecialchars - public static function secureHTML(string $chaine): string - { - return trim(htmLawed($chaine, self::$configHtmLawed, self::$specHtmLawed)); - } - - public static function secureFileName(string $chaine): string - { - // sécuriser un nom avec chemin avec basename? - //$chaine = basename($chaine); - - /* - - caractères interdits sous windows / \ : * ? " < > | - - mac autorise les / - - mac interdit : - - linux autorise tout sauf les / - - imagemagick ne supporte pas les : - - - 'espace' fonctionne - - / remplacé par firefox en : - - \ retire ce qui est devant le \ - - * fonctionne - - ? permet le téléchargement mais pas l'affichage - - " ne fonctionne pas, remplacé par %22, filtrer %22 - - < > fonctionnent - - | fonctionne - - = fonctionne, mais je filtre parce qu'on en trouve dans une URL - - ' ` fonctionnent - - % fonctionne - - (){}[] fonctionnent - - ^ fonctionne - - # ne fonctionne pas - - ~ fonctionne - - & fonctionne - - ^ pas encore testé - */ - - // => on remplace tout par des _ - // filtrer / et \ semble inutile - - /*$cibles = [' ', '/', '\\', ':', '*', '?', '<', '>', '|', '=', "'", '`', '"', '%22', '#']; - $chaine = str_replace($cibles, '_', $chaine); // nécéssite l'extension mbstring - $chaine = mb_strtolower($chaine); - return($chaine);*/ - - $chaine = preg_replace('/[^a-zA-Z0-9_-]/', '_', $chaine); // ne garder que les lettres, chiffres, tirets et underscores - $chaine = preg_replace('/_+/', '_', $chaine); // doublons d'underscores - return trim($chaine, '_'); - - // les problèmes avec \ persistent !! - // => javascript - // malheureusement document.getElementById('upload').files[0].name = chaine; ne marche pas! interdit! - // javascript ne doit pas pouvoir accéder au système de fichiers - // solutions: - // - au lieu de fournir une chaine (le chemin du fichier), donner un objet à files[0].name - // - créer une copie du fichier et l'envoyer à la place - // - envoyer le fichier en AJAX - // - envoyer le nom du fichier à part puis renommer en PHP - } -} - -// lien sans http:// -function fixLinks($data) -{ - // 1/ - // si une adresse est de type "domaine.fr" sans le http:// devant, le comportement des navigateurs est de rechercher un fichier comme si mon adresse commençait par file:// - // tomber ainsi sur une page d'erreur est parfaitement déroutant - - // regex pour détecter les balises
et ajouter http:// au début des liens si nécessaire - $pattern = '#(]+href=")((?!https?://)[^>]+>)#'; - //$data = preg_replace($pattern, '$1http://$2', $data); - - // 2/ - // cas où la regex fait mal son boulot: - // l'erreur 404 est gérée par le .htaccess - // et le visiteur est redirigé à la page "menu" - // (ça ne règle pas le problème mais c'est mieux) - - // 3/ - // quand l'éditeur est ouvert (avant de valider l'article), - // le lien qu'on vient de créer apparaît dans l'infobulle, - // cliquer dessus ouvre un onglet sur une erreur 404 - // solution partielle avec le .htaccess - // - // solution? fermer ce nouvel onglet avec echo ''; - // comment déclencher le JS? en faisant qu'une erreur 404 causée pour cette raison soit particulière? - - return($data); -} diff --git a/src/service/URL.php b/src/service/URL.php deleted file mode 100644 index 5bd2594..0000000 --- a/src/service/URL.php +++ /dev/null @@ -1,88 +0,0 @@ -params = $gets; - if($anchor != ''){ - $this->setAnchor($anchor); - } - } - - // setters statiques - static public function setProtocol(string $protocol = 'http'): void - { - self::$protocol = $protocol === 'https' ? 'https://' : 'http://'; - } - static public function setPort(int|string $port = 80): void - { - if((int)$port === 443){ - self::$protocol = 'https://'; - self::$port = ''; - } - elseif((int)$port === 80){ - self::$protocol = 'http://'; - self::$port = ''; - } - else{ - self::$port = ':' . (string)$port; - } - } - static public function setHost(string $host): void - { - self::$host = $host; - } - static public function setPath(string $path): void - { - self::$path = '/' . ltrim($path, '/'); - } - - //setters normaux - public function addParams(array $gets): void - { - // array_merge est préféré à l'opérateur d'union +, si une clé existe déjà la valeur est écrasée - $this->params = array_merge($this->params, $gets); - } - public function setAnchor(string $anchor = ''): void - { - if($anchor != ''){ - $this->anchor = '#' . ltrim($anchor, '#'); - } - else{ - $this->anchor = ''; - } - } - - private function makeParams(): string - { - $output = ''; - $first = true; - - foreach($this->params as $key => $value) { - if($first){ - $output .= '?'; - $first = false; - } - else{ - $output .= '&'; - } - $output .= $key . '=' . $value; - } - return $output; - } - - public function __toString(): string - { - return self::$protocol . self::$host . self::$port . self::$path . $this->makeParams() . $this->anchor; - } -} \ No newline at end of file diff --git a/src/service/User.php b/src/service/User.php deleted file mode 100644 index bd17dce..0000000 --- a/src/service/User.php +++ /dev/null @@ -1,23 +0,0 @@ -createQuery("SELECT u FROM App\Entity\User u") - ->setMaxResults(1) - ->getOneOrNullResult()) - { - unset($_SESSION['user']); - return false; - } - else{ - return true; - } - } -} \ No newline at end of file diff --git a/src/service/UserDataService.php b/src/service/UserDataService.php deleted file mode 100644 index e0156e2..0000000 --- a/src/service/UserDataService.php +++ /dev/null @@ -1,46 +0,0 @@ -open($file_path, ZipArchive::CREATE | ZipArchive::OVERWRITE) !== TRUE){ // ou ZipArchive::FL_OVERWRITE, à comparer à ZipArchive::OVERWRITE - throw new RuntimeException("Création ou ouverture du fichier demandé impossible à cause d'une erreur de permissions sur le serveur."); - } - - // recherche récursive dans les dossiers dans $source_directories, c'est comme le paramètre "-r" dans la console - $counter = 0; - foreach($source_directories as $path){ - $directory = new RecursiveDirectoryIterator($path); - $iterator = new RecursiveIteratorIterator($directory); - - foreach($iterator as $info){ - if($info->getFilename() != "." && $info->getFilename() != ".." && $info->getFilename() != '.htaccess'){ // chemins inutiles . et .. et le .htaccess - $Zip->addGlob($info->getPathname(), 0, array('')); - $counter++; - } - } - } - // recherche à la racine avec des pattern de noms de fichiers (optionnel) - foreach($pattern_to_target_in_user_data as $one_pattern){ - $Zip->addGlob($one_pattern, 0, array('')); - } - - $Zip->close(); - if($counter > 0){ - //chmod($file_path, 0666); - return $zip_name; - } - else{ - throw new RuntimeException("Téléchargement des fichiers impossible, aucun fichier n'a été trouvé sur le serveur."); - } - } -} \ No newline at end of file diff --git a/src/service/session.php b/src/service/session.php deleted file mode 100644 index 24023e9..0000000 --- a/src/service/session.php +++ /dev/null @@ -1,90 +0,0 @@ - 'session_invalide'])); - die; - } - - // MAJ de la session avec CERTAINES données - $_SESSION['user']['username'] = $user->getLogin(); - $_SESSION['user']['role'] = $user->getRole(); - - $is_admin = $user->getRole() === 'admin'; - } - - define('IS_ADMIN', $is_admin); - - // si on a un jour besoin d'une variable globale au lieu d'une constante - //$GLOBALS['is_admin'] = $is_admin; // version modifiable 1 - /*function isAdmin(): bool { // version modifiable 2 - return $_SESSION['user']['role'] ?? null === 'admin'; - }*/ - - - // => système de cache à ajouter pour ne pas lire la BDD à chaque fois - //remplacer ce qui est en haut - /*$user = $_SESSION['user'] ?? null; - if (!$user) { - // visiteur - } - // Vérification périodique (ex: toutes les 5 minutes) - if (time() - $user['last_check'] > 300) { - $user = UserController::getUserById($user['id'], $entityManager); - if (!$user) { - session_destroy(); - header('Location: /login.php'); - exit; - } - // cache pour ne pas avoir à lire la BDD à chaque page - $_SESSION['user'] = [ - 'id' => $user['id'], - 'role' => $user['role'], - 'username' => $user['username'], - 'last_check' => time() - ]; - $user = $_SESSION['user']; - } - $is_admin = ($user['role'] === 'admin');*/ - - - // améliorations possibles: ajouter expiration automatique + protection contre vol de session (IP / user-agent) sans casser ton app. -} - -// nettoyage complet -/*function cleanSession(){ - unset($_SESSION['user']); // mémoire vive - session_destroy(); // fichier côté serveur - setcookie('PHPSESSID', '', time() - 86400, '/'); // cookie de session -}*/ \ No newline at end of file -- cgit v1.2.3