diff --git a/CHANGELOG b/CHANGELOG index 0ab629b..c92a2f6 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -373,12 +373,6 @@ Par contre, ok avec FPDF *) LDAP trou sécu (autres labos) : user se connecte avec bad mdp - *) - Rendre modifiable la config via page web "Gérer les champs obligatoires" - - OFF_nom_du_champ : 'libellé' - - Restaurer la config par défaut - - Réactiver une variable : OFF_nom_du_champ => nom_du_champ - *) => update matos lifecycle diag @@ -541,11 +535,19 @@ TODO : - add_edit generic -- fusionner groupe thematique et metier (et projet ?) +- fusionner groupe thematique et metier (et projet ?) : + => faire hériter les Controller et les Table d'une meme superclasse GroupController et GroupTable + => avoir un seul template + => c'est vraiment stupide d'avoir 2 classes qui font la meme chose... - Utiliser les vues "index" des entités associées pour la vue "view" de materiel (et suivi) : => éviter la redondance, le contenu est pratiquement le meme (???, sauf que les colonnes ne sont pas triables) +- Bien préciser quels sont les champs obligatoires avec une asterisque (et pour chaque LOT) + +- S/N à renseigner (recommended) ssi > 10000€ + Ajouter champ attributes.condition = "prix > 10000" + ======= NEXT ======= print etiquette pour les 2 serveurs + tester fin garantie orange @@ -563,9 +565,45 @@ Vues génériques (index et view) : - Suivis.statut => "en cours" ou "à terminer" => à calculer auto + *) + Rendre modifiable la config via page web "Gérer les champs obligatoires" + - OFF_nom_du_champ : 'libellé' + - Restaurer la config par défaut + - Réactiver une variable : OFF_nom_du_champ => nom_du_champ + + +saisir les personnes du gt2i et de tous les groupes... + +groupe.users associés : ajouter "(responsable)" when relevant + +comment faire un tri sur la dernière colonne des stats (connexDurAvg) ? + +erreur download depuis page documents/ (ou depuis vue du matériel) sur inventirap : erreur 404 (action impossible) + +TODO config fields : +- réadapter lecture config à new file format +- compléter le fichier config avec tous les champs possibles à chaque lot !!! +- fichier read-write par web server +- ./UPDATE cral et ip2i + new config file +- pub +- soigner la présentation du form (peu lisible now) + + + - (b) Bugfix fournisseur perdu (et champ vide qui n'est plus modifiable !) après validation du matos + (quand il manque un champ pour valider), et pourtant bien enregistré dans listes des fournisseurs + + ======= CHANGES ======= ------- +22/11/2021 v5.3.1-3.7.9 + - (e) BIG NEW FEATURE : Configuration des champs obligatoires et readonly se fait maintenant via page WEB !!! + => plus besoin de modifier manuellement le fichier de config texte yaml + - (e) ajout lien vers stats utilisateurs depuis page accueil superadmin + - (e) modif page Apropos + + +------- 15/11/2021 v5.3.0-3.7.9 - (e) Refactorisation : Vue détaillée users (view) générique avec contenu enrichi ("(responsable)") - (e) Refactorisation : Vue liste users (index) générique avec contenu enrichi ("(responsable)") diff --git a/README.md b/README.md index 4ed61ae..dbcc8ee 100644 --- a/README.md +++ b/README.md @@ -52,8 +52,8 @@ Logiciel testé et validé sur les configurations suivantes : -------------------------------------------------------------------------------------------- -Date: 15/11/2021 -Version: v5.3.0-3.7.9 +Date: 22/11/2021 +Version: v5.3.1-3.7.9 HISTORIQUE DES CHANGEMENTS DE VERSION : voir le fichier CHANGES.txt (ou la page web /pages/changes) diff --git a/config/app_labinvent_mandatory_fields.default.yml b/config/app_labinvent_mandatory_fields.default.yml deleted file mode 100644 index 49d91a2..0000000 --- a/config/app_labinvent_mandatory_fields.default.yml +++ /dev/null @@ -1,616 +0,0 @@ -# FICHIER DE CONFIGURATION PAR DÉFAUT POUR LES CHAMPS D'UN MATÉRIEL - - -# Activer la possibilité de commander un matériel (au service gestion) en cliquant sur un bouton "Commander" ? -# L'étape "A commander" (status TOBEORDERED) devient alors une étape intermédiaire OPTIONNELLE entre CREATED et VALIDATED -# Les transitions de statut d'un materiel sont alors : CREATED => [ TEBEORDERED (à commander) ] => VALIDATED => TOBEARCHIVED => ARCHIVED -HAS_ORDER_BUTTON: true -#HAS_ORDER_BUTTON: false - - - -# ************************************************************ -# ************** CHAMPS INITIALEMENT READONLY **************** -# ************************************************************ - -# Un champ peut etre readonly pour 3 raisons : -# - soit parce qu’il a une valeur par défaut non modifiable (ex: Acheteur = celui qui crée la fiche), -# - soit parce qu’il est réservé à l’Administration (champs administratifs, « Admin only »), -# - soit parce qu’il a servi à valider une étape et ne doit donc plus être changé (sous peine d’invalider l’étape) - -UNEDITABLE_FIELDS: - - # #### CHAMPS GÉNÉRAUX #### - - #- designation - - #- will_stay - - #- description - - #- hors_service - - #- sur_categorie_id - - #- categorie_id - - #- sous_categorie_id - - #- groupes_thematique_id - - #- groupes_metier_id - - #- projet_id - - #- materiel_technique - #- materiel_administratif - - #- metrologie - - #- etiquette - - #- site_id - - #- lieu_detail - - #- date_acquisition - - #- date_reception - - # Garantie - #- duree_garantie - #- unite_duree_garantie - #- date_fin_garantie - - # Super Administrateur only - #- status - - #- numero_serie - - # Utilisateur du materiel (destinataire du bien) - #- nom_user - - # - Acheteur (le Créateur de la fiche) - Un role "Utilisateur" ne peut pas changer ça, c'est par défaut lui-même - - nom_responsable (sauf Responsable, Administration) - - #- nom_ancien_responsable - - #- resp_credit - - #- gestionnaire_id - - #- fournisseur_id - - #- organisme_id - - #- prix_ht - - #- budgets - - - # #### CHAMPS ADMINISTRATIFS : #### - - # Entité(s) dépensière(s) (budget(s)) // ligne budgétaire (sur quel(s) budget(s)) ou entité(s) dépensière(s) ?) - - eotp (sauf Administration) - - numero_commande (sauf Administration) - - numero_inventaire_organisme (sauf Administration) - - numero_inventaire_old (sauf Administration) - - numero_laboratoire # READONLY toujours car généré automatiquement - - - - - - -# *************************************************************** -# ************** LOT 0 - CRÉATION FICHE MATÉRIEL **************** -# *************************************************************** - -# Champs OBLIGATOIRES pour CRÉER une fiche Matériel -# - Aucun champ -#MANDATORY_FIELDS_FOR_LOT0: [] -# - Au moins un champ -MANDATORY_FIELDS_FOR_LOT0: - - # Infos toujours obligatoires (cachées car calculées automatiquement) - #'status', - #'tobeordered', - - designation: 'Désignation' - - description: 'Description' - - sur_categorie_id: 'Domaine' - categorie_id: 'Catégorie' - - # - Acheteur (le Créateur de la fiche) - nom_responsable: "Nom de l'Acheteur" - #// (rempli automatiquement) - email_responsable: "Email de l'Acheteur" - - # - Utilisateur - nom_user: "Nom de l'utilisateur" - - # Calculé auto au moment du save() - #'numero_laboratoire', - - # ******* END OF MANDATORY_FIELDS_LOT0 ******** - - -# Liste des champs qui sont NON MODIFIABLES APRÈS la CRÉATION d'une fiche -# Attention, les champs UNEDITABLE_FIELDS (voir au début du fichier) sont aussi pris en compte (donc, inutile de les répéter) - -# - Aucun champ -UNEDITABLE_FIELDS_AFTER_LOT0: [] - -# - Au moins un champ -#UNEDITABLE_FIELDS_AFTER_LOT0: - - # #### CHAMPS GÉNÉRAUX #### - - #- designation - - #- will_stay - - #- description - - #- hors_service - - #- sur_categorie_id - - #- categorie_id - - #- sous_categorie_id - - #- groupes_thematique_id - - #- groupes_metier_id - - #- projet_id - - #- materiel_technique - #- materiel_administratif - - #- metrologie - - #- etiquette - - #- site_id - - #- lieu_detail - - #- date_acquisition - - #- date_reception - - # Garantie - #- duree_garantie - #- unite_duree_garantie - #- date_fin_garantie - - # Super Administrateur only - #- status - - #- numero_serie - - # Utilisateur du materiel (destinataire du bien) - #- nom_user - - # - Acheteur (le Créateur de la fiche) - Un role "Utilisateur" ne peut pas changer ça, c'est par défaut lui-même - #- nom_responsable (sauf Responsable, Administration) - - #- nom_ancien_responsable - - #- resp_credit - - #- gestionnaire_id - - #- fournisseur_id - - #- organisme_id - - #- prix_ht - - #- budgets - - # #### CHAMPS ADMINISTRATIFS : #### - #- eotp (sauf Administration) - #- numero_commande (sauf Administration) - #- numero_laboratoire # READONLY toujours car généré automatiquement - #- numero_inventaire_organisme (sauf Administration) - #- numero_inventaire_old (sauf Administration) - - -# Champs NON OBLIGATOIRES MAIS FORTEMENT RECOMMANDÉS APRÈS COMMANDE -# Ces champs ne seront pas demandés à la saisie, mais un simple rappel sera affiché sur la vue détaillée du matériel (tant que ces éléments sont absents) -# Attention, les champs RECOMMENDED_FIELDS_XXX définis plus haut sont aussi pris en compte (donc, inutile de les répéter) -# - Aucun champ -RECOMMENDED_FIELDS_AFTER_LOT0: [] - - - - - - - - -# ****************************************************************************** -# ************** LOT 1 - COMMANDE (DEMANDE D'ACHAT) (optionnel) **************** -# ****************************************************************************** - -# Champs OBLIGATOIRES POUR passer la COMMANDE (La commande est une action optionnelle) -# Attention, les champs MANDATORY_FIELDS_XXX définis plus haut sont aussi pris en compte (donc, inutile de les répéter) -# - Aucun champ -#MANDATORY_FIELDS_FOR_LOT1: [] -# - Au moins un champ -MANDATORY_FIELDS_FOR_LOT1: - - # Infos toujours obligatoires (cachées car calculées automatiquement) - #'status', - #'tobeordered', - - #'hors_service', // O/N - - ##designation: 'Désignation' - - ##description: 'Description' - - #'permanent', - #'will_stay', // O/N - - ##sur_categorie_id: 'Domaine' - ##categorie_id: 'Catégorie' - - # - Utilisateur - #nom_user: "Nom de l'utilisateur de ce matériel" - - # - Acheteur - #nom_responsable: 'Nom du responsable' - # (rempli automatiquement) - #email_responsable: 'Email du responsable' - - # Calculé auto au moment du save() - #'numero_laboratoire', - - organisme_id: 'Organisme' - - prix_ht: 'Prix HT' - - # (par défaut = acheteur) - resp_credit: 'Responsable du crédit' - - # Gestionnaire de référence (recevra la commande) - gestionnaire_id: 'Gestionnaire de référence' - - # Fournisseur - fournisseur_id: 'Fournisseur' - - # Utilisé par la Gestion pour remplir le champ eotp - budgets: 'Budgets' - - # INFOS ADMINISTRATIVES - # - EOTP : obligatoire seulement dans LOT2 - - # - Devis joint : c'est un champ virtuel, il n'existe pas physiquement (sauf dans la table Documents) - DOC_DEVIS: 'Devis' - #DOC_BC: "Bon de Commande" - #DOC_BL: "Bon de Livraison" - #DOC_FACTURE: "Facture" - - - # ******* END OF MANDATORY_FIELDS_LOT1 ******** - - -# Liste des champs NON MODIFIABLES APRÈS la demande d'ACHAT (c'est à dire APRÈS avoir fait la "COMMANDE") -# Attention, les champs UNEDITABLE_FIELDS_XXX définis plus haut sont aussi pris en compte (donc, inutile de les répéter) -# - Aucun champ -#UNEDITABLE_FIELDS_AFTER_LOT1: [] -# - Au moins un champ -UNEDITABLE_FIELDS_AFTER_LOT1: - - # #### CHAMPS GÉNÉRAUX #### - - #- designation - - #- will_stay - - #- description - - #- hors_service - - # On ne devrait pas pouvoir changer la NATURE du bien - # Domaine & Catégorie - - sur_categorie_id (sauf Administration) # Domaine - - categorie_id (sauf Administration) # Catégorie - #- sous_categorie_id - - #- groupes_thematique_id - - #- groupes_metier_id - - #- projet_id - - #- materiel_technique - #- materiel_administratif - - #- metrologie - - #- etiquette - - #- site_id - - #- lieu_detail - - #- date_acquisition - - #- date_reception - - # Garantie - #- duree_garantie - #- unite_duree_garantie - #- date_fin_garantie - - # Super Administrateur only - #- status - - #- numero_serie - - # Utilisateur du materiel (destinataire du bien) - #- nom_user - - # - Acheteur (le Créateur de la fiche) - Un role "Utilisateur" ne peut pas changer ça, c'est par défaut lui-même - #- nom_responsable (sauf Responsable, Administration) - - #- nom_ancien_responsable - - # (par défaut = acheteur) - - resp_credit (sauf Administration) - - - gestionnaire_id (sauf Administration) - - # Fournisseur - - fournisseur_id (sauf Administration) - - - organisme_id (sauf Administration) - - - prix_ht (sauf Administration) - - # Utilisé par la Gestion pour remplir le champ eotp - - budgets (sauf Administration) - - - # #### CHAMPS VIRTUELS : doc attachés #### - # Ces champs virtuels n'existent pas physiquement (sauf dans la table Documents) - # On indique ici les documents attachés à la fiche matériel, qui ne peuvent plus être ni modifiés ni supprimés - # - Devis - # Le devis attaché au matériel commandé n'est ni modifiable ni supprimable - - DOC_DEVIS - # - Bon de Commande (si un BC a été joint à la fiche, on ne peut plus le modifier ni le supprimer) - - DOC_BC (sauf Administration) - # - Bon de Livraison - #- DOC_BL - # - Facture - #- DOC_FACTURE - - - # #### CHAMPS ADMINISTRATIFS : #### - #- eotp (sauf Administration) - #- numero_commande (sauf Administration) - #- numero_laboratoire # READONLY toujours car généré automatiquement - #- numero_inventaire_organisme (sauf Administration) - #- numero_inventaire_old (sauf Administration) - - - -# Champs NON OBLIGATOIRES MAIS FORTEMENT RECOMMANDÉS APRÈS COMMANDE -# Ces champs ne seront pas demandés à la saisie, mais un simple rappel sera affiché sur la vue détaillée du matériel (tant que ces éléments sont absents) -# Attention, les champs RECOMMENDED_FIELDS_XXX définis plus haut sont aussi pris en compte (donc, inutile de les répéter) -# - Aucun champ -RECOMMENDED_FIELDS_AFTER_LOT1: [] -#RECOMMENDED_FIELDS_AFTER_LOT1: - # - Etiquette posée sur le matériel - #etiquette: "d'imprimer l'étiquette associée et de la coller sur le matériel" - - - - - - - - -# *************************************************************************** -# ************** LOT 2 - VALIDATION (matériel livré et payé) **************** -# *************************************************************************** - -# Champs OBLIGATOIRES POUR VALIDER la livraison -# Attention, les champs MANDATORY_FIELDS_XXX définis plus haut sont aussi pris en compte (donc, inutile de les répéter) -# - Aucun champ -#MANDATORY_FIELDS_FOR_LOT2: [] -# - Au moins un champ -MANDATORY_FIELDS_FOR_LOT2: - - #//'fournisseur_id' => 'Fournisseur', - #//'fournisseur' => 'Fournisseur', - - date_acquisition: "Date d'achat" - - date_reception: 'Date de livraison' - - site_id: 'Site' - - #TODO: seulement si prix > 10K€ - lieu_detail: 'Lieu de stockage' - #numero_serie: 'S/N' - - #// INFOS ADMINISTRATIVES : - - #// La Gestion doit remplir ce champ a partir des infos qui sont dans le champ "budget" (rempli par acheteur) - #// ligne budgétaire (sur quel(s) budget(s)) ou entité(s) dépensière(s) - eotp: 'Entité(s) dépensière(s) (budget(s))' - - numero_commande: 'Num. BC' - - # Docs attachés obligatoires pour valider - DOC_DEVIS: 'Devis' - #DOC_BC: "Bon de Commande" - #DOC_BL: "Bon de Livraison" - #DOC_FACTURE: "Facture" - - - # ******* END OF $MANDATORY_FIELDS_LOT2 ******** - - - - -# Champs NON OBLIGATOIRES MAIS FORTEMENT RECOMMANDÉS APRÈS VALIDATION de la livraison -# Ces champs ne seront pas demandés à la saisie, mais un simple rappel sera affiché sur la vue détaillée du matériel (tant que ces éléments sont absents) -# Attention, les champs RECOMMENDED_FIELDS_XXX définis plus haut sont aussi pris en compte (donc, inutile de les répéter) -# - Aucun champ -#RECOMMENDED_FIELDS_AFTER_LOT2: [] -# - Au moins un champ -RECOMMENDED_FIELDS_AFTER_LOT2: - - - # - Etiquette posée sur le matériel - etiquette: "d'imprimer l'étiquette associée et de la coller sur le matériel" - - # - Numéro inventaire tutelles - # On aurait pu rendre cet élément obligatoire mais on ne peut pas l'exiger au moment de la livraison - # car le gestionnaire n'a cette info que lorsque "Service fait CNRS" (il récupère alors le n° inventaire "Tutelle" sur GESLAB) - numero_inventaire_organisme: "de renseigner le champ 'N° inventaire comptable/tutelles (Organisme)'" - - - # DOCS ATTACHÉS recommandés après validation - #DOC_DEVIS: "d'ajouter un Devis" - DOC_BC: "d'ajouter le Bon de Commande" - DOC_BL: "d'ajouter le Bon de Livraison" - DOC_FACTURE: "d'ajouter la Facture" - - - - - -# Liste des champs qui sont NON MODIFIABLES APRÈS la VALIDATION (livraison) -# Attention, les champs UNEDITABLE_FIELDS_XXX définis plus haut sont aussi pris en compte -# - Aucun champ -#UNEDITABLE_FIELDS_AFTER_LOT2: [] -# - Au moins un champ -UNEDITABLE_FIELDS_AFTER_LOT2: - - # #### CHAMPS GÉNÉRAUX #### - - #- designation - - #- will_stay - - #- description - - #- hors_service - - - sur_categorie_id - - categorie_id - - #- sous_categorie_id - - #- groupes_thematique_id - - #- groupes_metier_id - - #- projet_id - - #- materiel_technique - #- materiel_administratif - - #- metrologie - - #- etiquette - - #- site_id - - #- lieu_detail - - - date_acquisition - - date_reception - - # Garantie - #- duree_garantie - #- unite_duree_garantie - #- date_fin_garantie - - # Super Administrateur only - #- status - - #- numero_serie - - # Utilisateur du materiel (destinataire du bien) - #- nom_user - - # - Acheteur (le Créateur de la fiche) - Un role "Utilisateur" ne peut pas changer ça, c'est par défaut lui-même - #- nom_responsable (sauf Responsable, Administration) - - #- nom_ancien_responsable - - #- resp_credit - - #- gestionnaire_id - - #- fournisseur_id - - #- organisme_id - - #- prix_ht - - #- budgets - - - # #### CHAMPS ADMINISTRATIFS #### - - - eotp - - numero_commande - # READONLY toujours car généré automatiquement - #- numero_laboratoire - #- numero_inventaire_organisme - #- numero_inventaire_old - - - # DOCS ATTACHÉS qui ne sont plus ni supprimables ni modifiables - # - Devis - #- DOC_DEVIS - # - Bon de Commande - - DOC_BC - # - Bon de Livraison - - DOC_BL - # - Facture - - DOC_FACTURE - - - - -# ****************************************************************************** -# ************** LOT 3 - ARCHIVAGE (matériel à sortir ou sorti) **************** -# ****************************************************************************** - -# (TODO) Ce 3e niveau n'est pas encore implémenté, il est pour l'instant codé en dur dans le code - -# - Aucun champ -MANDATORY_FIELDS_FOR_LOT3: [] - -# - Aucun champ -UNEDITABLE_FIELDS_AFTER_LOT3: [] -# En fait, TOUS LES CHAMPS SONT READONLY SAUF : description, ... - - - - - - - - - - -# Astuce utilisable : -# Pour faire : MANDATORY_FIELDS_LOT2 = MANDATORY_FIELDS_LOT1 -# On fait comme ceci : -#MANDATORY_FIELDS_LOT1: &lot1 -#MANDATORY_FIELDS_LOT2: *lot1 - diff --git a/config/bootstrap.php b/config/bootstrap.php index bee9fa5..dcd39f3 100755 --- a/config/bootstrap.php +++ b/config/bootstrap.php @@ -36,6 +36,10 @@ use Yaml\Configure\Engine\YamlConfig; */ require __DIR__ . '/paths.php'; +/* EP 2021-11 : mes propres constantes */ +define('CONFIG_MATERIEL_FIELDS_FILE_NAME', 'app_labinvent_mandatory_fields'); + + // Use composer to load the autoloader. require ROOT . DS . 'vendor' . DS . 'autoload.php'; @@ -106,17 +110,31 @@ try { } // (EP 2021 09 Ajout nouveaux fichiers config pour les champs obligatoires (et les autorisations)) -$config_mandatory_fields_file_name = 'app_labinvent_mandatory_fields'; +//$config_mandatory_fields_file_name = 'app_labinvent_mandatory_fields'; // Si le fichier de conf n'existe pas, on le crée en copiant le fichier par défaut -if ( !file_exists(CONFIG.DS.$config_mandatory_fields_file_name.'.yml') ) - copy(CONFIG.DS.$config_mandatory_fields_file_name.'.default.yml', CONFIG.DS.$config_mandatory_fields_file_name.'.yml'); +//if ( !file_exists(CONFIG.DS.$config_mandatory_fields_file_name.'.yml') ) +//$config_matos_full_file_name = $config_matos_full_file_name_default = CONFIG.DS.CONFIG_MATERIEL_FIELDS_FILE_NAME; +$config_matos_full_file_name = $config_matos_full_file_name_default = CONFIG_MATERIEL_FIELDS_FILE_NAME; +//$config_matos_full_file_name_default = $config_matos_full_file_name; +$config_matos_full_file_name .= '.yml'; +$config_matos_full_file_name_default .= '.default.yml'; +//if ( !file_exists($config_matos_full_file_name) ) copy($config_matos_full_file_name_default, $config_matos_full_file_name); try { - Configure::config('yaml', new YamlConfig()); - Configure::load($config_mandatory_fields_file_name, 'yaml'); + Configure::config('my_yaml_engine', new YamlConfig()); + //Configure::load($config_mandatory_fields_file_name, 'yaml', true); + Configure::load(CONFIG_MATERIEL_FIELDS_FILE_NAME, 'my_yaml_engine'); //Configure::load('app_labinvent_mandatory_fields_IP2I', 'yaml'); //Configure::load('app_labinvent_authorizations', 'yaml'); } catch (\Exception $e) { - die('config/bootstrap.php: Unable to load yaml config file'); + echo("
config/bootstrap.php: Impossible de charger le fichier de configuration des champs matériels (".CONFIG.DS.CONFIG_MATERIEL_FIELDS_FILE_NAME.".yml)"); + echo("
- soit ce fichier n'est pas accessible en lecture par le serveur web (attention, il faut aussi qu'il soit accessible en écriture)"); + echo("
- soit il n'existe pas => dans ce cas, créez le en faisant une copie du fichier de configuration par défaut :"); + echo("
cd ".CONFIG); + echo("
cp $config_matos_full_file_name_default $config_matos_full_file_name"); + echo("
chown webserver_user_name $config_matos_full_file_name"); + echo("
chmod 600 $config_matos_full_file_name"); + echo("
(si vous ne voulez pas faire le chown, faite plutot un chmod 666, moins propre, mais marche aussi)"); + die(); } // Load an environment local configuration file. diff --git a/src/Controller/ConfigurationFieldsController.php b/src/Controller/ConfigurationFieldsController.php new file mode 100644 index 0000000..f50c75e --- /dev/null +++ b/src/Controller/ConfigurationFieldsController.php @@ -0,0 +1,308 @@ +is_authorized_action[]) as $a) if ($a != 'display') unset($this->is_authorized_action[$a]); + $this->is_authorized_action = []; + + // admin (+) only + $this->setAuthorizationsForAction('index', -1, ['admin'=>0, 'super'=>0]); + $this->setAuthorizationsForAction('view', -1, ['admin'=>0, 'super'=>0]); + $this->setAuthorizationsForAction('edit', -1, ['admin'=>0, 'super'=>0]); + $this->setAuthorizationsForAction('resetToDefault', -1, ['admin'=>0, 'super'=>0]); + // tous + //$this->setAuthorizationsForAction('index', 0); + // Superadmin only : + //$this->setAuthorizationsForAction('index', -1, ['super'=>0]); + + } + + public function index() { + $this->view(); + } + + public function view() { + + $this->edit(true); + // Lecteurs configurés : 'default', 'yaml' + //debug(Configure::configured()); + + //$fields = Configure::readOrFail('MANDATORY_FIELDS_FOR_LOT'.$lot_num); + /* + $fields = Configure::readOrFail('MANDATORY_AND_READONLY_FIELDS'); + $this->set(compact('fields')); + */ + } + + public function resetToDefault() { + $config_mandatory_fields_file_name = CONFIG.DS.CONFIG_MATERIEL_FIELDS_FILE_NAME.'.yml'; + $config_mandatory_fields_file_name_default = CONFIG.DS.CONFIG_MATERIEL_FIELDS_FILE_NAME.'.default.yml'; + + $config_default = Yaml::parse(file_get_contents($config_mandatory_fields_file_name_default)); //parse le fichier + $config_default_as_yaml = Yaml::dump($config_default); //remet en yaml + if (! file_put_contents($config_mandatory_fields_file_name, $config_default_as_yaml) ) + throw new \ErrorException("Impossible de remettre la configuration des champs de matériel aux valeurs par défaut - le fichier de config $config_mandatory_fields_file_name n'est pas accessible en écriture"); + + $this->Flash->success("La configuration des champs de matériel a bien été réinitialisée aux valeurs par défaut"); + return $this->redirect(['action' => 'view']); + } + + + public function edit($READONLY=false) { + + $contact = new ConfigurationFieldsForm(); + //debug($contact); + + // - En mode POST + if ($this->request->is('post')) { + /* + * We use the execute() method to run our form’s _execute() method only when the data is valid, + * and set flash messages accordingly. + * We could have also used the validate() method to only validate the request data: + * $isValid = $form->validate($this->request->getData()); + */ + //debug($this->request->getData()); + + // - OK + if ($contact->execute($this->request->getData())) { + $fieldsets = $this->request->getData(); + //debug($data); + $contact->setData($fieldsets); + + // On sauvegarde le contenu de $fieldsets dans le fichier de config (array => yaml) + + //debug($contact); + // See : https://book.cakephp.org/3/en/development/configuration.html#writing-configuration-data + /* + Configure::write('Company.name','Pizza, Inc.'); + Configure::write('Company.slogan','Pizza for your body and soul'); + // idem : + Configure::write('Company', [ + 'name' => 'Pizza, Inc.', + 'slogan' => 'Pizza for your body and soul' + ]); + */ + //$data1 = Configure::read('MANDATORY_AND_READONLY_FIELDS'); + //debug($data1); + /* + Configure::write('MANDATORY_AND_READONLY_FIELDS',$data); + $data2 = Configure::read('MANDATORY_AND_READONLY_FIELDS'); + */ + //debug($data2); + //$name = $contact->getData('name'); + /* NE MARCHE PAS, WHY ??? + $config_mandatory_fields_file_name = 'app_labinvent_mandatory_fields'; + if ( Configure::dump($config_mandatory_fields_file_name, 'my_yaml', ['MANDATORY_AND_READONLY_FIELDS']) ) + $this->Flash->success("La configuration des champs matériels a bien été sauvegardée"); + */ + //$config_mandatory_fields_file_name = CONFIG.DS.'app_labinvent_mandatory_fields'.'.yml'; + $config_mandatory_fields_file_name = CONFIG.DS.CONFIG_MATERIEL_FIELDS_FILE_NAME.'.yml'; + // Symfony YAML component : https://symfony.com/doc/current/components/yaml.html + /* + $array = Yaml::parse(file_get_contents($config_mandatory_fields_file_name)); //parse le fichier + debug($array); + $res = Yaml::dump($array); //remet en yaml + */ + $fieldsets_new = []; + $fieldsets_new['MANDATORY_AND_READONLY_FIELDS']=$fieldsets; + //debug($fieldsets_new); + + $fieldsets_as_yaml = Yaml::dump($fieldsets_new); //remet en yaml + //debug($res); exit; + if (! file_put_contents($config_mandatory_fields_file_name, $fieldsets_as_yaml) ) + throw new \ErrorException("Impossible d'enregistrer la configuration des champs de matériel - le fichier de config $config_mandatory_fields_file_name n'est pas accessible en écriture"); + $this->Flash->success("La configuration des champs de matériel a bien été sauvegardée"); + return $this->redirect(['action' => 'view']); + } + + // - KO + else { + // Once a form has been validated you can retrieve the errors from it: + $errors = $form->getErrors(); // $form->errors(); // prior to 3.7.0 + debug($errors); + /* $errors contains + [ + 'email' => ['A valid email address is required'] + ] + */ + $this->Flash->error("La configuration n'a pas pu être enregistrée"); + } + } + + // - En mode GET (ou POST avec erreur) + + if ($this->request->is('get')) { + /* + $contact->setData([ + 'name' => 'John Doe', + 'email' => 'john.doe@example.com' + ]); + */ + } + + $this->set('contact', $contact); + + $fieldsets = Configure::readOrFail('MANDATORY_AND_READONLY_FIELDS'); + $this->set(compact('READONLY', 'fieldsets')); + + } // edit() + + + public function OLD_edit($READONLY=false) + { + $contact = new ConfigurationFieldsForm(); + //debug($contact); + + // - En mode POST + if ($this->request->is('post')) { + /* + * We use the execute() method to run our form’s _execute() method only when the data is valid, + * and set flash messages accordingly. + * We could have also used the validate() method to only validate the request data: + * $isValid = $form->validate($this->request->getData()); + */ + //debug($this->request->getData()); + + // - OK + if ($contact->execute($this->request->getData())) { + $fieldsets = $this->request->getData(); + //debug($data); + $contact->setData($fieldsets); + + /* + * 1) On retouche $fieldsets pour qu'il puisse être sauvegardé au format fichier texte (yaml) : + * + * Ex: + 'designation' => [ + 'selected' => '0', + 'labl' => 'commentaire', + 'roles' => [ + (int) 0 => 'Responsable', + (int) 1 => 'Administration' + ] + ], + * + * - '.selected' : si = 0 => on préfixe le nom du champ par "OFF_" + * => 'OFF_designation' + * - '.labl' : on l'ajoute entre parenthèse à la suite du nom du champ + * => 'OFF_designation (commentaire)' + * - '.roles' : on les ajoute entre parenthèse à la fin du nom du champ => + * => 'OFF_designation (commentaire) (sauf Responsable, Administration)' + */ + $fieldsets_new = []; + $fieldsets_new['MANDATORY_AND_READONLY_FIELDS']=[]; + //$fn = &$fieldsets_new['MANDATORY_AND_READONLY_FIELDS']; + foreach ($fieldsets as $fieldset_name=>$fields) { + $fieldsets_new['MANDATORY_AND_READONLY_FIELDS'][$fieldset_name] = []; + foreach ($fields as $field_name=>$attributes) { + $field_name_new = $field_name; + // - selected + if (! $attributes['selected'] ) + $field_name_new = 'OFF_'.$field_name_new; + // - labl + if ($attributes['labl']) + $field_name_new .= ' ('. $attributes['labl'] .')'; + // - roles + if (isset($attributes['except_roles']) && $attributes['except_roles']) + $field_name_new .= ' (sauf '. implode(',',$attributes['except_roles']) .')'; + //debug($field_name_new); + $fieldsets_new['MANDATORY_AND_READONLY_FIELDS'][$fieldset_name][] = $field_name_new; + } + } + //debug($fieldsets_new); exit; + + // 2) On sauvegarde le contenu de $fieldsets dans le fichier de config (array => yaml) + + //debug($contact); + // See : https://book.cakephp.org/3/en/development/configuration.html#writing-configuration-data + /* + Configure::write('Company.name','Pizza, Inc.'); + Configure::write('Company.slogan','Pizza for your body and soul'); + // idem : + Configure::write('Company', [ + 'name' => 'Pizza, Inc.', + 'slogan' => 'Pizza for your body and soul' + ]); + */ + //$data1 = Configure::read('MANDATORY_AND_READONLY_FIELDS'); + //debug($data1); + /* + Configure::write('MANDATORY_AND_READONLY_FIELDS',$data); + $data2 = Configure::read('MANDATORY_AND_READONLY_FIELDS'); + */ + //debug($data2); + //$name = $contact->getData('name'); + /* NE MARCHE PAS, WHY ??? + $config_mandatory_fields_file_name = 'app_labinvent_mandatory_fields'; + if ( Configure::dump($config_mandatory_fields_file_name, 'my_yaml', ['MANDATORY_AND_READONLY_FIELDS']) ) + $this->Flash->success("La configuration des champs matériels a bien été sauvegardée"); + */ + //$config_mandatory_fields_file_name = CONFIG.DS.'app_labinvent_mandatory_fields'.'.yml'; + $config_mandatory_fields_file_name = CONFIG.DS.CONFIG_MATERIEL_FIELDS_FILE_NAME.'.yml'; + // Symfony YAML component : https://symfony.com/doc/current/components/yaml.html + /* + $array = Yaml::parse(file_get_contents($config_mandatory_fields_file_name)); //parse le fichier + debug($array); + $res = Yaml::dump($array); //remet en yaml + */ + $fieldsets_new_as_yaml = Yaml::dump($fieldsets_new); //remet en yaml + //debug($res); exit; + if ( file_put_contents($config_mandatory_fields_file_name, $fieldsets_new_as_yaml) ) + $this->Flash->success("La configuration des champs matériels a bien été sauvegardée"); + return $this->redirect(['action' => 'view']); + } + + // - KO + else { + // Once a form has been validated you can retrieve the errors from it: + $errors = $form->getErrors(); // $form->errors(); // prior to 3.7.0 + debug($errors); + /* $errors contains + [ + 'email' => ['A valid email address is required'] + ] + */ + $this->Flash->error("La configuration n'a pas pu être enregistrée"); + } + } + + // - En mode GET (ou POST avec erreur) + + if ($this->request->is('get')) { + /* + $contact->setData([ + 'name' => 'John Doe', + 'email' => 'john.doe@example.com' + ]); + */ + } + + $this->set('contact', $contact); + + $fieldsets = Configure::readOrFail('MANDATORY_AND_READONLY_FIELDS'); + $this->set(compact('READONLY', 'fieldsets')); + + } // edit() + + + +} // end of class \ No newline at end of file diff --git a/src/Controller/MaterielsController.php b/src/Controller/MaterielsController.php index f9c026d..54b802d 100755 --- a/src/Controller/MaterielsController.php +++ b/src/Controller/MaterielsController.php @@ -1913,8 +1913,40 @@ class MaterielsController extends AppController { } */ $recommended_fields = $this->Materiels->getRecommendedFieldsForMaterielStatus($materiel->status); - foreach ($recommended_fields as $fname => $msg) { - + //debug($recommended_fields); + /* + * Ex : + [ + 'etiquette' => [ + 'selected' => '1', + 'comment' => 'd'imprimer l'étiquette associée et de la coller sur le matériel', + 'roles' => '' + ], + 'numero_inventaire_organisme' => [ + 'selected' => '1', + 'comment' => 'de renseigner le champ 'N° inventaire comptable/tutelles _Organisme_'', + 'roles' => '' + ], + 'DOC_BC' => [ + 'selected' => '1', + 'comment' => 'd'ajouter le Bon de Commande', + 'roles' => '' + ], + 'DOC_BL' => [ + 'selected' => '1', + 'comment' => 'd'ajouter le Bon de Livraison', + 'roles' => '' + ], + 'DOC_FACTURE' => [ + 'selected' => '1', + 'comment' => 'd'ajouter la Facture', + 'roles' => '' + ] + */ + //foreach ($recommended_fields as $fname => $msg) { + //foreach ($recommended_fields as $fname => $attributes) { + foreach ( array_keys($recommended_fields) as $fname ) { + //debug($fname); // - Documents attachés (champs virtuels) // DOC_DEVIS, DOC_BC, ... @@ -1937,8 +1969,8 @@ class MaterielsController extends AppController { } // Les champs restants sont les champs manquants => message flash de rappel - foreach ($recommended_fields as $fname => $reminder_msg) - $this->Flash->set("Ce matériel est '".$entity->getNiceStatus()."' mais n'oubliez pas $reminder_msg"); + foreach ($recommended_fields as $fname => $attributes) + $this->Flash->set("Ce matériel est '".$entity->getNiceStatus()."' mais n'oubliez pas ".$attributes['comment']); } // view @@ -1974,7 +2006,6 @@ class MaterielsController extends AppController { // uniquement à cause de parent::add_or_edit() : $entity_name=null, array $associated_entities=[], $with_parent=false) { - //debug("ici"); exit; $this->myDebug("step 3: MaterielsController.add_or_edit()"); $IS_EDIT = !$IS_ADD; @@ -2080,72 +2111,11 @@ class MaterielsController extends AppController { $materiel->numero_laboratoire = null; } } - //debug($materiel); exit; - /* - // ADD - if ($IS_ADD) { - // 1) on crée un materiel vide - $materiel = $this->Materiels->newEntity(); - // - add_by_copy : COPIE de materiel (on a cliqué sur "Copier ce materiel") => id passé en argument - if (isset($this->request->getAttribute('params')['pass'][0])) { - // On récupère le materiel à copier et on le copie dans $materiel - /S - $materiel_to_copy = $this->Materiels->get($this->request->getAttribute('params')['pass'][0]); - $materiel_to_copy = $materiel_to_copy->toArray(); - S/ - $materiel_to_copy = $this->e->toArray(); - //var_dump($materiel_to_copy); - ///foreach ($materiel_to_copy as $key=>$value) $materiel->$key = $value; - // IMPORTANT: validate=False car sinon, les données sont validées avant la copie, - // et le numero_laboratoire est vu comme invalide car déjà utilisé et doit etre unique !!! - // et on a pour résultat : "le matériel n'a pas pu être ajouté" (sans savoir pourquoi !!!) - $materiel = $this->Materiels->patchEntity($materiel, $materiel_to_copy, ['validate' => false]); - //TODO: On pourrait traiter les erreurs de validation déjà ici - if ($materiel->errors()) { - // traitement - } - // Du coup, on supprime le champ numero_laboratoire car il va être généré automatiquement - unset($materiel->numero_laboratoire); - // IMPORTANT: on ne doit pas laisser l'id égal à celui du matériel copié !!! il en faut un nouveau - $materiel->id = NULL; - //$materiel->id = False; - //unset($materiel->id); - - /S - $attribute="[original]"; - $materiel->$attribute = []; - S/ - //$materiel->id = FALSE; - //$materiel->'[new]' => true, - } - // - NOUVEAU materiel (on a cliqué sur "Nouveau materiel") - else { - //$materiel = $this->Materiels->newEntity(); - /S (EP 20200505 plus nécessaire car on utilise désormais NULL) - // Set default values : "N/A" - $materiel->groupes_thematique_id = 1; - $materiel->groupes_metier_id = 1; - $materiel->site_id = 9; - S/ - } - } - // EDIT - else { - /S - $this->log("Edit a doc1 !", 'debug'); - Log::write('debug',"Edit a doc2 !"); - $this->dlog("Edit a doc1 !"); - S/ - $materiel = $this->Materiels->get($id, [ - 'contain' => [] // load associated entities - ]); - //$this->myDebug($materiel); - //debug($materiel); - } - */ - /* SI POST... + /* + * SI POST... + * * Les données ont été saisies et postées * On va donc les sauvegarder (si ok) */ @@ -2154,123 +2124,32 @@ class MaterielsController extends AppController { $authorized_actions = $IS_ADD ? ['post'] : ['post','patch','put']; if ( $this->request->is($authorized_actions) ) { - //debug($this->request->getData()); - //exit; - - /* - // ADD - // Nouveau materiel saisi et posted - ($is_add && $this->request->is('post')) - - || - - // EDIT - // materiel modifié et posted - ( (!$is_add) && $this->request->is(['patch','post','put']) ) - */ + //debug($this->request->getData()); exit; // (1) On remplit $materiel avec les données de ce materiel $materiel = $this->Materiels->patchEntity($materiel, $this->request->getData()); - - /* - // AVIRER - // BUGFIX temporaire pour php5 !!! - if ($this->isLabinventDebugMode()) { - $d2 = $materiel->date_reception; - $d1 = $materiel->date_acquisition; - debug("d1 achat avant:"); debug($d1); - debug("d2 recep avant:"); debug($d2); - /S - $tz = new \DateTimeZone('Europe/Paris'); - $d1 = new \DateTime(strtr($d1,'/','-'),$tz); - $d2 = new \DateTime(strtr($d2,'/','-'),$tz); - debug("d1 achat après:"); debug($d1); - debug("d2 recep après:"); debug($d2); - S/ - if ($d2 < $d1) debug("d2 < d1 !!!"); - // $d2 > $d1 oui mais pas trop... - $diff = $d2->diff($d1); - //debug($diff->y); - debug("diff:"); debug($diff); - debug("diff->y:"); debug($diff->y); - debug("diff->days:"); debug($diff->days); - exit; - } - // FIN BUGFIX - */ - - // ADD : Set the user_id from the session. - //$materiel->user_id = $this->Auth->user('id'); - // EDIT : Added: Disable modification of user_id. - //'accessibleFields' => ['user_id' => false] - - /* (EP 7/12/20 : déjà fait dans la vue, donc inutile !!!) - // (2) Si l'utilisateur courant est un "administratif" => le mettre comme gestionnaire du materiel - // (tout ça pour ça !!! Faudra réduire ce bazar !) - $current_user_name = $_SESSION['Auth']['User']['sn'][0]; - if (in_array( - $current_user_name, - $usersTable - ->find('list', [ - 'keyField' => 'id', - 'valueField' => 'nom' - ]) - ->where([ - 'role =' => 'Administration' - ]) - ->toArray() - )) { - $materiel->gestionnaire_id = $usersTable - ->find() - ->where([ - 'nom' => $current_user_name - ]) - ->first()->id; - } - */ - - + //debug($materiel); + /* * \AV1 : Attributs OBLIGATOIRES * On définit les infos obligatoires et on vérifie qu'elles sont bien présentes dans le POST */ - /* - // Attributs obligatoires pour la phase COMMANDE - //$LOT1 = $this->Materiels->MANDATORY_FIELDS_FOR_LOT1; - $LOT1 = $this->Materiels->getMandatoryFieldsForLot(1); - - // Attributs obligatoires pour la phase VALIDATION (livré et payé) - //$LOT2 = $this->Materiels->MANDATORY_FIELDS_FOR_LOT2; - $LOT2 = $this->Materiels->getMandatoryFieldsForLot(2); - - //TODO 202109 - // Seulement si prix > 10K€ : exiger la facture jointe et le n° série - if ($materiel->prix_ht > 10000) { - //$LOT2[] = 'facture jointe'; - $LOT2['numero_serie'] = 'S/N'; - } - // LOT2 = LOT1 + LOT2; - ////$LOT2 = array_merge($LOT1, $LOT2); - //debug($LOT2);exit; - // Champs obligatoires = LOT1 si CREATED, LOT2 sinon (>CREATED, c'est à dire VALIDATED ou plus) - $mandatory_fields = ($materiel->status == 'CREATED') ? $LOT1 : $LOT2; - //debug($mandatory_fields); exit; - */ //$mandatory_fields = MaterielsTable::getMandatoryFieldsForMaterielStatus($materiel->status); $mandatory_fields = $this->Materiels->getMandatoryFieldsForMateriel($materiel); + //debug($mandatory_fields); //$mandatory_fields = $this->Materiels->getMandatoryFieldsForMaterielStatus($materiel->status); - //debug($mandatory_fields); exit; $verb = $IS_ADD ? "ajouté" : "modifié"; // On vérifie que les infos obligatoires sont présentes // Si au moins un champ obligatoire est nul ou vide => ERROR - $ALL_MANDATORY_FIELDS_FOR_GIVEN = true; + $ALL_MANDATORY_FIELDS_GIVEN = true; //foreach ($mandatory_fields as $fname => $fval) { //print_r($materiel); - foreach ($mandatory_fields as $fname=>$fname_nice) { - + //foreach ($mandatory_fields as $fname=>$fname_nice) { + foreach ($mandatory_fields as $fname=>$attributes) { + /* * Champs obligatoires à ignorer : * - champs virtuels (n'existent pas physiquement) => DOC_XXX (DOC_DEVIS, DOC_BC, ...) @@ -2282,7 +2161,7 @@ class MaterielsController extends AppController { //if ( in_array($fname, ['DOC_DEVIS', 'fournisseur_id', 'etiquette', 'numero_inventaire_organisme']) ) continue; if ($materiel->$fname === null || $materiel->$fname == '') { - $ALL_MANDATORY_FIELDS_FOR_GIVEN = false; + $ALL_MANDATORY_FIELDS_GIVEN = false; /* (EP 2020 03) * Ce genre de ligne ($this->Flash->...) affichant un message flash en haut de page, * ne fonctionnait plus à cause de bootstrap (css). @@ -2292,7 +2171,8 @@ class MaterielsController extends AppController { * de bootstrap, ou bootsrap-ui, ou cakephp..., à surveiller donc) */ //$msgError1 = "Le champ suivant est obligatoire : ".$fname_nice.' du matériel'; - $error_msg = "Le champ suivant est obligatoire : ".$fname_nice; + //$error_msg = "Le champ suivant est obligatoire : ".$fname_nice; + $error_msg = "Le champ suivant est obligatoire : ".$attributes['comment']; /////debug($msgError1); $this->Flash->error($error_msg); /* MARCHE PAS POURQUOI ? @@ -2307,10 +2187,11 @@ class MaterielsController extends AppController { ///////////////$this->Flash->error(__("Le matériel n'a pas pu être $verb")); //return false; break; - } - } + } // si champ obligatoire pas rempli + + } // foreach $mandatory_fields - if ($ALL_MANDATORY_FIELDS_FOR_GIVEN) { + if ($ALL_MANDATORY_FIELDS_GIVEN) { // (3) On l'ajoute en BD, on envoie un email, et on affiche ok sur page accueil //$verb = $IS_ADD ? "ajouté" : "modifié"; @@ -2342,7 +2223,8 @@ class MaterielsController extends AppController { foreach ($materiel->getErrors() as $f=>$e) foreach($e as $k=>$v) $this->Flash->error(__($v.' : '.$f)); - } + } // save KO + else { //debug($this->getCurrentEntity()); exit; //debug($this->Materiels->current_entity); exit; @@ -2380,7 +2262,7 @@ class MaterielsController extends AppController { 'action' => 'view', $id ]); - } + } // save KO } // $ALL_MANDATORY_FIELDS_FOR_GIVEN @@ -2388,11 +2270,12 @@ class MaterielsController extends AppController { - - /* SINON (PAS POST) + /* SINON (GET, PAS POST) * C'est la première fois qu'on vient sur cette vue, * donc on va préparer le formulaire de saisie) */ + //debug($materiel); exit; + /* @@ -2709,6 +2592,7 @@ class MaterielsController extends AppController { 'ARCHIVED' => 'ARCHIVED' ]; $entity = $materiel; + //debug($entity); $this->set(compact( 'IS_ADD', 'mail_responsable', @@ -2738,8 +2622,10 @@ class MaterielsController extends AppController { 'gestionnaires' )); - - $this->set('readonlyFields', $this->getUneditableFieldsForMaterielStatus($materiel->status)); + $readonlyFields = $this->getUneditableFieldsForMaterielStatus($materiel->status); + $this->set(compact('readonlyFields')); + //debug($materiel->status); + //debug($readonlyFields); //$this->set('readonlyFields', $IS_ADD ? [] : $this->getUneditableFieldsForMaterielStatus($materiel->status)); //debug($this->Materiels->getUneditableFieldsForMaterielStatus($status)); exit; //$this->set('readonlyFields', $IS_ADD ? [] : $this->Materiels->getUneditableFieldsForMaterielStatus($materiel->status)); @@ -2767,11 +2653,12 @@ class MaterielsController extends AppController { if ($this->USER_IS_SUPERADMIN()) return []; $uneditable_fields = $this->Materiels->getUneditableFieldsForMaterielStatus($materiel_status); - foreach ($uneditable_fields as $fname=>$except_list) { + //foreach ($uneditable_fields as $fname=>$except_list) { + foreach ($uneditable_fields as $fname=>$attributes) { // S'il y a une exception, voir si elle concerne le USER courant, sinon supprimer ce champ $fname - if ($except_list) { + if ($attributes['except_roles']) { //debug($except_list); - if ($this->currentUserRoleInList($except_list)) unset($uneditable_fields[$fname]); + if ($this->currentUserRoleInList($attributes['except_roles'])) unset($uneditable_fields[$fname]); //debug("remove $fname"); } } @@ -3051,10 +2938,11 @@ class MaterielsController extends AppController { // Attributs obligatoires pour la phase COMMANDE (LOT1) $mandatoryFields = $this->Materiels->getMandatoryFieldsForLot(1); + //debug($mandatoryFields); $ACTION = 'ordonner la commande de'; // Si au moins un champ obligatoire est nul ou vide => ERROR - foreach ($mandatoryFields as $fname => $fnicename) { + foreach ($mandatoryFields as $fname => $attributes) { // Doc attaché obligatoire ? (champ virtuel) //if (strtoupper($fname) == 'DOC_DEVIS') { @@ -3085,7 +2973,7 @@ class MaterielsController extends AppController { $fval = $materiel->$fname; if ($fval === null || $fval == '') { //$msgError1 = "Pour $ACTION ce matériel, le champ suivant ne doit pas être vide : ".$fnicename.' du matériel'; - $error_msg = sprintf($ERROR_MSG_EMPTY_FIELD, $ACTION, $fnicename); + $error_msg = sprintf($ERROR_MSG_EMPTY_FIELD, $ACTION, $attributes['comment']); $this->Flash->error($error_msg); // Utile ??? (plante les tests) ////$this->e->setError($fname, 'Ce champ ne doit pas être vide'); @@ -3108,6 +2996,7 @@ class MaterielsController extends AppController { if ($newStatus == 'VALIDATED') { // Attributs obligatoires pour la phase VALIDATION (livré et payé) (LOT2) $mandatoryFields = $this->Materiels->getMandatoryFieldsForLot(2); + //$mandatoryFields = $this->Materiels->getCategoryFieldsForLot('MANDATORY_FIELDS_FOR_LOT', 2) //$mandatoryFields = $this->Materiels->MANDATORY_FIELDS_FOR_LOT2; //debug($mandatoryFields);exit; /* @@ -3142,7 +3031,7 @@ class MaterielsController extends AppController { */ // Si au moins un champ obligatoire est nul ou vide => ERROR - foreach ($mandatoryFields as $fname => $fnicename) { + foreach ($mandatoryFields as $fname => $attributes) { $ACTION = 'valider'; @@ -3184,7 +3073,8 @@ class MaterielsController extends AppController { * de bootstrap, ou bootsrap-ui, ou cakephp..., à surveiller donc) */ //$msgError1 = "Pour $ACTION ce matériel, le champ suivant ne doit pas être vide : ".$fnicename.' du matériel'; - $error_msg = sprintf($ERROR_MSG_EMPTY_FIELD, $ACTION, $fnicename); + //debug($fname); + $error_msg = sprintf($ERROR_MSG_EMPTY_FIELD, $ACTION, $attributes['comment']); $this->Flash->error($error_msg); //debug($error_msg); diff --git a/src/Controller/PagesController.php b/src/Controller/PagesController.php index 7cc24eb..d4038d7 100755 --- a/src/Controller/PagesController.php +++ b/src/Controller/PagesController.php @@ -70,6 +70,7 @@ class PagesController extends AppController { $this->myDebug("step 0A (specific): PagesController.initialize()"); parent::initialize(); + $this->modelClass = false; // On autorise l'action add SANS authentification (unauthenticated) //$this->Auth->allow(['add']); //$this->LdapAuth->allow(['display']); diff --git a/src/Controller/StatsController.php b/src/Controller/StatsController.php index 8df56a9..0da3aa3 100644 --- a/src/Controller/StatsController.php +++ b/src/Controller/StatsController.php @@ -61,7 +61,7 @@ class StatsController extends AppController 'nice_name'=>"Temps connexion cumulé (h)", 'f'=>'getHourMnSecForDuration'], 'connex_nb'=>['nice_name'=>"Nb connexions"], 'connexDurAvg'=>[ - 'nice_name'=>"Durée connexion moyenne (sur année)", 'f'=>'getHourMnSecForDuration'] + 'nice_name'=>"Durée connexion moyenne (sur 1 an)", 'f'=>'getHourMnSecForDuration'] ], ['Users'], false, false, diff --git a/src/Controller/SurCategoriesController.php b/src/Controller/SurCategoriesController.php index a55cbf5..ca62d75 100755 --- a/src/Controller/SurCategoriesController.php +++ b/src/Controller/SurCategoriesController.php @@ -256,6 +256,8 @@ class SurCategoriesController extends AppController $surCategory = $this->SurCategories->get($id, [ 'contain' => [] ]); + + // En mode POST if ($this->request->is([ 'patch', 'post', @@ -274,10 +276,14 @@ class SurCategoriesController extends AppController $this->Flash->error(__('Le domaine n\'a pas pu être édité.')); } } + + // En mode GET (ou alors POST après une erreur) $this->set(compact('surCategory')); + /* (EP) inutile sauf si json $this->set('_serialize', [ 'surCategory' ]); + */ } /** diff --git a/src/Form/ConfigurationFieldsForm.php b/src/Form/ConfigurationFieldsForm.php new file mode 100644 index 0000000..2e23f0e --- /dev/null +++ b/src/Form/ConfigurationFieldsForm.php @@ -0,0 +1,47 @@ +$val) debug($fname); + + return $schema + ->addField('name', 'string') + ->addField('email', ['type' => 'string']) + ->addField('body', ['type' => 'text']); + } + */ + + /* + public function validationDefault(Validator $validator) + { + $validator->add('name', 'length', [ + 'rule' => ['minLength', 5], + 'message' => 'A name is required' + ])->add('email', 'format', [ + 'rule' => 'email', + 'message' => 'A valid email address is required', + ]); + + return $validator; + } + */ + + protected function _execute(array $data) + { + // Send an email. + return true; + } +} \ No newline at end of file diff --git a/src/Model/Table/MaterielsTable.php b/src/Model/Table/MaterielsTable.php index f8751d0..de359d2 100755 --- a/src/Model/Table/MaterielsTable.php +++ b/src/Model/Table/MaterielsTable.php @@ -252,60 +252,84 @@ class MaterielsTable extends AppTable * - LOT0 total = [] + LOT0 * - LOT1 total = LOT0 + LOT1 * - LOT2 total = LOT1 + LOT2 + * + * Algo RECURSIF */ + public static function getCategoryFieldsForLot($categ, $lot_num, $categ_initial=null) { + //debug($lot_num); + // ex: $categ = RECOMMENDED_FIELDS_AFTER_LOT + //if ($lot_num < 0) return $categ_initial ? Configure::readOrFail('UNEDITABLE_FIELDS') : []; + if ($lot_num < 0) return $categ_initial ? Configure::readOrFail("MANDATORY_AND_READONLY_FIELDS.$categ_initial") : []; + + // Recursive call + $base_fields = self::getCategoryFieldsForLot($categ, $lot_num-1, $categ_initial); + $base_fields = self::removeUnselectedFieldsFrom($base_fields); + //debug($base_fields); + + //$new_fields = Configure::readOrFail('MANDATORY_AND_READONLY_FIELDS.RECOMMENDED_FIELDS_AFTER_LOT'.$lot_num); + $new_fields = Configure::readOrFail("MANDATORY_AND_READONLY_FIELDS.$categ".$lot_num); + $new_fields = self::removeUnselectedFieldsFrom($new_fields); + //debug($new_fields); + + //debug($new_fields); + return array_merge($base_fields, $new_fields); + } + + + + public static function removeUnselectedFieldsFrom($fields) { + /* + * Exemple : + Before remove : + { + designation: { selected: '0', comment: commentaire, roles: '' }, + description: { selected: '0', comment: autre, roles: '' }, + nom_responsable: { selected: '1', comment: 'nom du responsable', + } + After remove : + { + nom_responsable: { selected: '1', comment: 'nom du responsable', + } + */ + //$fields_selected = []; + foreach ($fields as $field_name => $attributes) { + if ( $field_name=='fieldset_comment' || !$attributes['selected'] ) unset($fields[$field_name]); + } + return $fields; + } + public static function getMandatoryFieldsForLot($lot_num) { + return self::getCategoryFieldsForLot('MANDATORY_FIELDS_FOR_LOT', $lot_num); /* $specific_constant_name = "MANDATORY_FIELDS_FOR_LOT1_".self::getLabName(); $mandatory_fields_lot1 = defined($specific_constant_name) ? constant($specific_constant_name) : MANDATORY_FIELDS_FOR_LOT1; return $mandatory_fields_lot1; */ + /* if ($lot_num < 0) return []; $base_fields = self::getMandatoryFieldsForLot($lot_num-1); $new_fields = Configure::readOrFail('MANDATORY_FIELDS_FOR_LOT'.$lot_num); //debug($new_fields); return array_merge($base_fields, $new_fields); + */ } + // RECURSIF public static function getRecommendedFieldsForLot($lot_num) { + return self::getCategoryFieldsForLot('RECOMMENDED_FIELDS_AFTER_LOT', $lot_num); + /* if ($lot_num < 0) return []; $base_fields = self::getRecommendedFieldsForLot($lot_num-1); - $new_fields = Configure::readOrFail('RECOMMENDED_FIELDS_AFTER_LOT'.$lot_num); + $new_fields = Configure::readOrFail('MANDATORY_AND_READONLY_FIELDS.RECOMMENDED_FIELDS_AFTER_LOT'.$lot_num); //debug($new_fields); return array_merge($base_fields, $new_fields); + */ } - - // (EP 2021 10) - public static function list_to_dict($uneditable_fields) { - $dict = []; - foreach ($uneditable_fields as $f) { - $except_roles = []; - $except_str = '(sauf'; - $pos0 = strpos($f, '('); - $pos = strpos($f, $except_str); - // Si le nom du champ $f fini par "(sauf ...)", on récupère cette chaine except (sans le mot 'sauf' ni la parenthèse finale) dans $except - if ($pos0 !== false) { - $error_msg = "Erreur dans le fichier de configuration des champs non modifiables"; - // EXCEPTION si parenthèse début mais pas suivie du mot-clé 'sauf' - if ($pos === false) - throw new \Exception("$error_msg : une parenthèse doit toujours être suivie du mot-clé 'sauf', ce qui n'est pas le cas de la ligne '$f'"); - // EXCEPTION si pas de parenthèse finale, ou role mal orthographié - //$except_list = trim( substr($f, $pos + strlen($except_str), -1) ); - if ( substr($f, -1) != ')' ) - throw new \Exception("$error_msg : il manque une parenthèse à la fin de la ligne '$f'"); - $except_roles = str_replace(" ", "", substr($f, $pos + strlen($except_str), -1) ); - $except_roles = explode(',', $except_roles); - foreach ($except_roles as $role) if (! self::isValidRole($role)) - throw new \Exception("$error_msg : le role '$role' est mal orthographié dans la ligne '$f'"); - $f = trim( substr($f,0,$pos) ); - } - $dict[$f] = $except_roles; - } - return $dict; - } - // (EP 2021 09) fonction récursive : // => champs readonly pour le lot N = champs readonly pour le lot N-1 + champs readonly pour le lot N : public static function getUneditableFieldsForLot($lot_num) { - + return self::getCategoryFieldsForLot('UNEDITABLE_FIELDS_AFTER_LOT', $lot_num, 'UNEDITABLE_FIELDS'); + /* + // Si N = -1 => on retourne la liste initiale de champs readonly (fin de la récursivité) //if ($lot_num < 0) return []; if ($lot_num < 0) return self::list_to_dict( Configure::readOrFail('UNEDITABLE_FIELDS') ); @@ -323,10 +347,57 @@ class MaterielsTable extends AppTable // => Somme des 2 (champs readonly pour le lot N-1 + champs readonly pour le lot N) //return array_unique(array_merge($base_uneditable_fields, $new_uneditable_fields)); return array_merge($base_uneditable_fields, $new_uneditable_fields); + */ } + public static function separateFieldNameAndExceptedRoles($f) { + $except_roles = []; + $except_str = '(sauf'; + $pos0 = strpos($f, '('); + $pos = strpos($f, $except_str); + // Si le nom du champ $f fini par "(sauf ...)", on récupère cette chaine except (sans le mot 'sauf' ni la parenthèse finale) dans $except + if ($pos0 !== false) { + $error_msg = "Erreur dans le fichier de configuration des champs non modifiables"; + + // EXCEPTION si parenthèse début mais pas suivie du mot-clé 'sauf' + /* + if ($pos === false) + throw new \Exception("$error_msg : une parenthèse doit toujours être suivie du mot-clé 'sauf', ce qui n'est pas le cas de la ligne '$f'"); + */ + if ($pos !== false) { + + // EXCEPTION si pas de parenthèse finale, ou role mal orthographié + //$except_list = trim( substr($f, $pos + strlen($except_str), -1) ); + if ( substr($f, -1) != ')' ) + throw new \Exception("$error_msg : il manque une parenthèse à la fin de la ligne '$f'"); + + $except_roles = str_replace(" ", "", substr($f, $pos + strlen($except_str), -1) ); + $except_roles = explode(',', $except_roles); + foreach ($except_roles as $role) if (! self::isValidRole($role)) + throw new \Exception("$error_msg : le role '$role' est mal orthographié dans la ligne '$f'"); + + $f = trim( substr($f,0,$pos) ); + } + } + //return [$f=>$except_roles]; + return ['name'=>$f, 'roles'=>$except_roles]; + } + + // (EP 2021 10) + public static function list_to_dict($uneditable_fields) { + $dict = []; + foreach ($uneditable_fields as $f) { + //$dict[$f] = $except_roles; + $fieldNameAndExceptedRoles = self::separateFieldNameAndExceptedRoles($f); + $dict[$fieldNameAndExceptedRoles['name']] = $fieldNameAndExceptedRoles['except_roles']; + } + return $dict; + } + + + /* public static function getMandatoryFieldsLot1() { /S @@ -353,7 +424,13 @@ class MaterielsTable extends AppTable $fields = self::getMandatoryFieldsForMaterielStatus($status); // Seulement si prix > 10K€ : exiger la facture jointe et le n° série et lieu de stockage précis if ($status == 'VALIDATED' && $mat->prix_ht && $mat->prix_ht > 10000) { - $fields['numero_serie'] = 'S/N'; + //$fields['numero_serie'] = 'S/N'; + /* + $fields['numero_serie'] = [ + //'selected'=>'1', + 'comment'=>'S/N' + ]; + */ //TODO 202109 //$fields[] = 'lieu stockage'; //$fields[] = 'facture jointe'; @@ -361,7 +438,9 @@ class MaterielsTable extends AppTable return $fields; } public static function getMandatoryFieldsForMaterielStatus($status) { - + //return self::getCategoryFieldsForMaterielStatus('MANDATORY_FIELDS_FOR_LOT', $status); + return self::getCategoryFieldsForMaterielStatus('MANDATORY', $status); + /* if (is_null($status)) $status='CREATED'; // On recup le LOT qui convient au status courant du matos //$fields = ($status == 'CREATED') ? self::getMandatoryFieldsForLot(1) : self::getMandatoryFieldsForLot(2); @@ -375,9 +454,12 @@ class MaterielsTable extends AppTable //debug($fields); return $fields; + */ } public static function getUneditableFieldsForMaterielStatus($status) { - + //return self::getCategoryFieldsForMaterielStatus('UNEDITABLE_FIELDS_AFTER_LOT', $status); + return self::getCategoryFieldsForMaterielStatus('UNEDITABLE', $status); + /* if (is_null($status)) $status='CREATED'; // On recup le LOT qui convient au status courant du matos //$fields = ($status == 'CREATED') ? self::getMandatoryFieldsForLot(1) : self::getMandatoryFieldsForLot(2); @@ -390,12 +472,19 @@ class MaterielsTable extends AppTable $fields = self::getUneditableFieldsForLot($lot_num); return $fields; + */ } public static function getRecommendedFieldsForMaterielStatus($status) { - return self::getFieldsCategoryForMaterielStatus('RECOMMENDED', $status); + //return self::getCategoryFieldsForMaterielStatus('RECOMMENDED', $status); + //return self::getCategoryFieldsForMaterielStatus('RECOMMENDED_FIELDS_AFTER_LOT', $status); + return self::getCategoryFieldsForMaterielStatus('RECOMMENDED', $status); } - public static function getFieldsCategoryForMaterielStatus($fields_categ, $status) { - $func_name = "get".ucfirst($fields_categ)."FieldsForLot"; // getUneditableFieldsForLot() + + public static function getCategoryFieldsForMaterielStatus($categ, $status) { + //$func_name = "get".ucfirst($fields_categ)."FieldsForLot"; // getUneditableFieldsForLot() + //$func_name = "getCategoryFieldsForLot"; // getUneditableFieldsForLot() + $func_name = "get".ucfirst($categ)."FieldsForLot"; // getUneditableFieldsForLot() + if (is_null($status)) $status='CREATED'; // On recup le LOT qui convient au status courant du matos //$fields = ($status == 'CREATED') ? self::getMandatoryFieldsForLot(1) : self::getMandatoryFieldsForLot(2); @@ -1560,6 +1649,9 @@ class MaterielsTable extends AppTable $fournisseur_asis = $entity->fournisseur ? $entity->fournisseur['name'] : ''; // Enlever les espaces superflus $fournisseur = trim($fournisseur_asis); + //debug($fournisseur); + //debug($entity); + // Si champ fournisseur obligatoire, vérification qu'il est bien rempli (ou emission d'une erreur) //if (IP2I && $entity->fournisseur && $fournisseur == '') { @@ -1688,6 +1780,7 @@ class MaterielsTable extends AppTable } // ssi changement // Finalement, on supprime les champs 'fournisseur' car sinon erreur de sauvegarde, // (normal ces champs n'existent pas dans materiel) + //debug($entity); exit; unset($entity->fournisseur_orig); unset($entity->fournisseur); //debug($entity); exit; diff --git a/src/Template/ConfigurationFields/edit.ctp b/src/Template/ConfigurationFields/edit.ctp new file mode 100644 index 0000000..302ed42 --- /dev/null +++ b/src/Template/ConfigurationFields/edit.ctp @@ -0,0 +1,149 @@ + 'hiraigc02eb46spa06iiog6tvh' +] +*/ +//debug($_ENV); +//debug($_REQUEST); + +// Variables passées par le controleur +$CAN_EDIT = true; +$CAN_EDIT = $CAN_EDIT; +$READONLY = $READONLY; +$contact = $contact; +$fieldsets = $fieldsets; +//debug($fieldsets); + +$icon = $READONLY ? '' : ""; +//$icon = $READONLY ? '' : ""; +//$end = $READONLY ? "(Visualisation)" : "(Modification)"; +//$title = "Configuration des champs obligatoires ou non modifiables de la fiche Matériel $end"; +$title = "Configuration des champs obligatoires ou non modifiables de la fiche Matériel"; +echo "

$icon $title



"; +/* + Autres icones possibles : +echo "

$title



"; +echo "

$title



"; +echo "

$title



"; +echo "

$title



"; +echo "

$title



"; +*/ + +if ($READONLY && $CAN_EDIT) $this->MyHelper->echoEditButton(); +if ($READONLY && $CAN_EDIT) $this->MyHelper->echoButtonForAction('icon-trash', 'reset-to-default', 'Remettre les valeurs par défaut', 'Remettre les valeurs par défaut', true); + +//echo $this->Html->icon('pencil'); + +echo $this->Form->create($contact); + + if (!$READONLY) { + echo $this->Form->button('Enregistrer', ['class'=>'btn btn-outline-success', 'type'=>'submit']); + //echo $this->Form->button('Submit'); + echo '

'; + } + + //echo $this->Form->button($this->Html->icon('pencil'), ['escape' => false]); + // ...can be easily rewritten as: + //echo $this->Form->button('i:pencil'); + + foreach ($fieldsets as $fieldset_name => $fields) { + $controls = []; + + //foreach ($fields as $field) { + foreach ($fields as $field_name => $attributes) { + + if ($field_name=='fieldset_comment') continue; + + // champ activé ou désactivé + $checked = $attributes['selected']; + $comment = $attributes['comment']; + $except_roles = isset($attributes['except_roles']) ? $attributes['except_roles'] : []; + + $name = "$fieldset_name.$field_name"; + + // - CHAMP de la table materiel : Checkbox pour le dé/sélectionner + $label = $field_name; + if ($READONLY) { + if ($comment) $label .= ' ('.$comment.')'; + if ($except_roles) $label .= ' (sauf '.implode(', ',$except_roles).')'; + } + $controls["$name.selected"] = [ + 'type' => 'checkbox', + //'hiddenField' => true, + 'label' => $label, + 'checked' => $checked, + 'disabled' => $READONLY, + //'readonly' =>$READONLY, + //'readonly' => 'readonly', + ]; + + // (EDIT ONLY) + // - Label associé au champ (entre parenthèses) doit pouvoir être modifié + if (!$READONLY) $controls["$name.comment"] = [ + //'type' => 'input', + 'label' => false, + 'val' => $comment, + //'size' => 20, + //'disabled' => $READONLY, + //'readonly' => $READONLY, + ]; + + // (EDIT ONLY) + $fieldsets_with_roles = ['UNEDITABLE_FIELDS','UNEDITABLE_FIELDS_AFTER_LOT0', 'UNEDITABLE_FIELDS_AFTER_LOT1', 'UNEDITABLE_FIELDS_AFTER_LOT2']; + // - ROLES exceptés (select multiple list) + //if (!$READONLY && $field_name!='HAS_ORDER_BUTTON') + if ( !$READONLY && in_array($fieldset_name, $fieldsets_with_roles) ) $controls["$name.except_roles"] = [ + 'type' => 'select', + 'label' => '(excepté pour les rôles)', + //'options' => ['', 'user', 'admin', 'resp'], + //'options' => ['Utilisateur', 'Responsable', 'Administration'], + 'options' => [ + ['text' => 'Utilisateur', 'value' => 'Utilisateur'], + ['text' => 'Responsable', 'value' => 'Responsable'], + ['text' => 'Administration', 'value' => 'Administration'], + ], + //'val' => ['Responsable', 'Utilisateur'], + 'val' => $except_roles, + 'multiple' => 'checkbox', + //'size' => 20, + 'disabled' => $READONLY, + ]; + + } // foreach $fields + + // https://api.cakephp.org/3.5/class-Cake.View.Helper.FormHelper.html#_controls + $DEBUG && debug($controls); + $fieldset_name_with_comment = $fieldset_name; + if ( isset($fields['fieldset_comment']) ) $fieldset_name_with_comment .= ' ('. $fields['fieldset_comment'] .')'; + echo $this->Form->controls($controls, ['legend' => $fieldset_name_with_comment.' :']); + + } // foreach $fieldsets + + /* + echo $this->Form->control('name'); + echo $this->Form->control('email'); + echo $this->Form->control('body'); + */ + + if (!$READONLY) + echo $this->Form->button('Enregistrer', ['class'=>'btn btn-outline-success', 'type'=>'submit']); + //echo $this->Form->button('Submit'); + + +echo $this->Form->end(); + +if ($READONLY && $CAN_EDIT) $this->MyHelper->echoEditButton(); + diff --git a/src/Template/ConfigurationFields/index.ctp b/src/Template/ConfigurationFields/index.ctp new file mode 100644 index 0000000..a2d92bf --- /dev/null +++ b/src/Template/ConfigurationFields/index.ctp @@ -0,0 +1,2 @@ +extend('view'); \ No newline at end of file diff --git a/src/Template/ConfigurationFields/view.ctp b/src/Template/ConfigurationFields/view.ctp new file mode 100644 index 0000000..4274daa --- /dev/null +++ b/src/Template/ConfigurationFields/view.ctp @@ -0,0 +1,8 @@ +extend('edit'); + +/* +$fields = $fields; +debug($fields); +*/ \ No newline at end of file diff --git a/src/Template/Materiels/index.ctp b/src/Template/Materiels/index.ctp index 461c66e..efab34b 100644 --- a/src/Template/Materiels/index.ctp +++ b/src/Template/Materiels/index.ctp @@ -525,7 +525,7 @@ $displayLegend = function() { noir=livré (validé) ;" //rouge=à sortir ; ." bleu=archivé ; - (couleur date : rouge=fin garantie, orange=< 1 an) + (couleur date : rouge=fin garantie, orange=<1 an)

"; }; diff --git a/src/Template/Pages/about.ctp b/src/Template/Pages/about.ctp index 05398fd..131aa4b 100755 --- a/src/Template/Pages/about.ctp +++ b/src/Template/Pages/about.ctp @@ -1,9 +1,23 @@
+ '; echo '

A PROPOS DU LOGICIEL ' . $softwareName . '

'; +echo '
'; +echo 'Changements faits sur le logiciel (nouveautés et historique)'; + +echo '

'; +echo 'Documentation générale'; + +echo '

'; +echo 'Guide utilisateur'; +echo '

'; + + + /* echo <<<"EOD"
@@ -34,8 +48,12 @@ Licence GPL (http://www.gnu.org/copyleft/gpl.html)

EOD; */ +?> -echo '
'; + +
+ +
'; + echo $line.'
'; //if (mb_strpos($line, "$level: ") !== FALSE) echo $line.'

'; //if (mb_strpos($line, 'Info: ') !== FALSE) echo $line.'

'; //if (mb_strpos($line, '/materiels/edit/') !== FALSE) echo $line.'

'; @@ -84,18 +102,10 @@ foreach ($lines as $line) { // Close file $f=null; -echo '
'; - - -echo '
'; -echo 'Changements faits sur le logiciel (nouveautés et historique)'; +?> -echo '

'; -echo 'Documentation générale'; +
-echo '

'; -echo 'Guide utilisateur'; -?>