<?php
/**
 * @version $Id$
 * @author Thomas Crespin <thomas.crespin@sesamath.net>
 * @copyright Thomas Crespin 2009-2022
 * 
 * ****************************************************************************************************
 * SACoche <https://sacoche.sesamath.net> - Suivi d’Acquisitions de Compétences
 * © Thomas Crespin pour Sésamath <https://www.sesamath.net> - Tous droits réservés.
 * Logiciel placé sous la licence libre Affero GPL 3 <https://www.gnu.org/licenses/agpl-3.0.html>.
 * ****************************************************************************************************
 * 
 * Ce fichier est une partie de SACoche.
 * 
 * SACoche est un logiciel libre ; vous pouvez le redistribuer ou le modifier suivant les termes 
 * de la “GNU Affero General Public License” telle que publiée par la Free Software Foundation :
 * soit la version 3 de cette licence, soit (à votre gré) toute version ultérieure.
 * 
 * SACoche est distribué dans l’espoir qu’il vous sera utile, mais SANS AUCUNE GARANTIE :
 * sans même la garantie implicite de COMMERCIALISABILITÉ ni d’ADÉQUATION À UN OBJECTIF PARTICULIER.
 * Consultez la Licence Publique Générale GNU Affero pour plus de détails.
 * 
 * Vous devriez avoir reçu une copie de la Licence Publique Générale GNU Affero avec SACoche ;
 * si ce n’est pas le cas, consultez : <http://www.gnu.org/licenses/>.
 * 
 */

class SessionUser
{

  public static $tab_dalton_css = array(
    2 => array( '#9B9B9B', '#E1E1E1' ),
    3 => array( '#909090', '#BEBEBE', '#EAEAEA' ),
    4 => array( '#828282', '#AAAAAA', '#D2D2D2', '#FAFAFA' ),
    5 => array( '#828282', '#A0A0A0', '#BEBEBE', '#DCDCDC', '#FAFAFA' ),
    6 => array( '#7D7D7D', '#969696', '#AFAFAF', '#C8C8C8', '#E1E1E1', '#FAFAFA' ),
  );

  /**
   * Tester si mdp du webmestre transmis convient.
   * 
   * @param string    $password
   * @return array(bool,string)   (TRUE,'') ou (FALSE,message_d_erreur)
   */
  public static function tester_authentification_webmestre($password)
  {
    global $PAGE;
    return Outil::verifier_mdp( $password , WEBMESTRE_PASSWORD ) ? array( TRUE , '' ) : array( FALSE , 'Mot de passe incorrect ! Nouvelle tentative autorisée dans '.$_SESSION['FORCEBRUTE'][$PAGE]['DELAI'].'s.' ) ;
  }

  /**
   * Tester si mdp d’un développeur SACoche transmis convient.
   * On réutilise des éléments de la connexion webmestre.
   * 
   * @param string    $password
   * @return array(bool,string)   (TRUE,'') ou (FALSE,message_d_erreur)
   */
  public static function tester_authentification_developpeur($password)
  {
    $result = ServeurCommunautaire::tester_auth_devel( Outil::clef_md5($password) );
    return ($result=='ok') ? array( TRUE , '' ) : array( FALSE , $result ) ;
  }

  /**
   * Tester si les données transmises permettent d’authentifier un partenaire (convention ENT serveur Sésamath).
   * 
   * @param int       $partenaire_id
   * @param string    $password
   * @return array(bool,array|string)   (TRUE,$DB_ROW) ou (FALSE,message_d_erreur)
   */
  public static function tester_authentification_partenaire($partenaire_id,$password)
  {
    // Récupérer les données associées à ce partenaire.
    $DB_ROW = DB_WEBMESTRE_PUBLIC::DB_recuperer_donnees_partenaire($partenaire_id);
    // Si id non trouvé...
    if(empty($DB_ROW))
    {
      return array( FALSE , 'Partenaire introuvable !' );
    }
    // Si mdp incorrect...
    if( !Outil::verifier_mdp( $password , $DB_ROW['partenaire_password'] ) )
    {
      global $PAGE;
      return array( FALSE , 'Mot de passe incorrect ! Nouvelle tentative autorisée dans '.$_SESSION['FORCEBRUTE'][$PAGE]['DELAI'].'s.' );
    }
    // Enregistrement d’un cookie sur le poste client servant à retenir le partenariat sélectionné si identification avec succès
    Cookie::definir( COOKIE_PARTENAIRE , $DB_ROW['partenaire_id'] , 31536000 /* 60*60*24*365 = 1 an */ );
    // Si on arrive ici c’est que l’identification s’est bien effectuée !
    return array( TRUE , $DB_ROW );
  }

  /**
   * Tester si les données transmises permettent d’authentifier un utilisateur (sauf webmestre & développeur).
   * 
   * En cas de connexion avec les identifiants SACoche, la reconnaissance s’effectue sur le couple login/password.
   * En cas de connexion depuis un service SSO extérieur type CAS, la reconnaissance s’effectue en comparant l’identifiant transmis (via $login) avec l’id ENT de jointure connu de SACoche.
   * 
   * @param int    $BASE
   * @param string $login
   * @param string $password
   * @param string $mode_connection 'normal_test' | 'normal' | 'cas:user' | 'cas:sconet' | 'cas:attributes' | 'shibboleth' | 'siecle' | 'vecteur_parent' | 'ldap' (?) | 'api'
   * @param string $nom              facultatif, seulement pour $mode_connection = 'cas:attributes' | 'vecteur_parent'
   * @param string $prenom           facultatif, seulement pour $mode_connection = 'cas:attributes' | 'vecteur_parent'
   * @param string $user_profil_type facultatif, seulement pour $mode_connection = 'cas:attributes'
   * @param string $birthdate        facultatif, seulement pour $mode_connection = 'cas:attributes'
   * @return array(bool,array|string)   (TRUE,$DB_ROW) ou (FALSE,message_d_erreur)
   */
  public static function tester_authentification_utilisateur( $BASE , $login , $password , $mode_connection , $nom='' , $prenom='' , $user_profil_type='' , $birthdate='' )
  {
    $is_connexion_base_interne = in_array( $mode_connection , array('normal','normal_test','api') ) ? TRUE : FALSE ;
    // En cas de multi-structures, il faut charger les paramètres de connexion à la base concernée
    // Sauf pour une connexion à un ENT, car alors il a déjà fallu les charger pour récupérer les paramètres de connexion à l’ENT
    if( $BASE && $is_connexion_base_interne )
    {
      DBextra::charger_parametres_sql_supplementaires($BASE);
    }
    // Récupérer les données associées à l’utilisateur.
    $DB_ROW = DB_STRUCTURE_PUBLIC::DB_recuperer_donnees_utilisateur( $mode_connection , $login , $nom , $prenom , $user_profil_type , $birthdate );
    // Si login (ou identifiant SSO) non trouvé...
    if(empty($DB_ROW))
    {
      switch($mode_connection)
      {
        case 'normal' :
          $message = 'Nom d’utilisateur inconnu !'; break;
        case 'normal_test' :
          $message = 'Nom d’utilisateur incorrect !'; break;
        case 'api' :
         $message = 'Jeton inconnu !'; break;
        case 'cas:user' :
        case 'cas:attributes' :
          $message = 'Identification réussie mais identifiant CAS "'       .$login.'" inconnu dans SACoche (base n°'.$BASE.') !<br>Un administrateur doit renseigner que l’identifiant ENT associé à votre compte SACoche est "'.$login.'"&hellip;<br>Il doit pour cela se connecter à SACoche, menu [Gestion&nbsp;des&nbsp;utilisateurs] [Élèves|Parents|Professeurs|Administrateurs], et indiquer pour votre compte dans le champ [Id.&nbsp;ENT] la valeur "'.$login.'".<br>Il a aussi la possibilité d’utiliser le menu [Gestion&nbsp;des&nbsp;utilisateurs] [Rechercher&nbsp;un&nbsp;utilisateur] pour effectuer la modification voulue.'; break;
        case 'shibboleth' :
          $message = 'Identification réussie mais identifiant Shibboleth "'.$login.'" inconnu dans SACoche (base n°'.$BASE.') !<br>Un administrateur doit renseigner que l’identifiant ENT associé à votre compte SACoche est "'.$login.'"&hellip;<br>Il doit pour cela se connecter à SACoche, menu [Gestion&nbsp;des&nbsp;utilisateurs] [Élèves|Parents|Professeurs|Administrateurs], et indiquer pour votre compte dans le champ [Id.&nbsp;ENT] la valeur "'.$login.'".<br>Il a aussi la possibilité d’utiliser le menu [Gestion&nbsp;des&nbsp;utilisateurs] [Rechercher&nbsp;un&nbsp;utilisateur] pour effectuer la modification voulue.'; break;
        case 'siecle' :
        case 'cas:sconet' :
          $message = 'Identification réussie mais identifiant Sconet "'    .$login.'" inconnu dans SACoche (base n°'.$BASE.') !<br>Un administrateur doit renseigner que l’identifiant Sconet associé à votre compte SACoche est "'.$login.'"&hellip;<br>Il doit pour cela se connecter à SACoche, menu [Gestion&nbsp;des&nbsp;utilisateurs] [Élèves|Parents|Professeurs|Administrateurs], et indiquer pour votre compte dans le champ [Id.&nbsp;Sconet] la valeur "'.$login.'".<br>Il a aussi la possibilité d’utiliser le menu [Gestion&nbsp;des&nbsp;utilisateurs] [Rechercher&nbsp;un&nbsp;utilisateur] pour effectuer la modification voulue.'; break;
        case 'vecteur_parent' :
          $message = 'Identification réussie mais compte parent'.                   ' inconnu dans SACoche (base n°'.$BASE.') !<br>Le compte SACoche d’un responsable légal dont le nom est "'.$nom.'", le prénom est "'.$prenom.'", et ayant la charge d’un enfant dont l’identifiant Sconet est "'.$login.'", n’a pas été trouvé.'; break;
      }
      return array( FALSE , $message );
    }
    // Blocage éventuel par le webmestre ou un administrateur ou l’automate
    LockAcces::stopper_si_blocage( $BASE , $DB_ROW['user_profil_sigle'] );
    // Si mdp incorrect...
    if( $is_connexion_base_interne && ($password!==FALSE) && !Outil::verifier_mdp( $password , $DB_ROW['user_password'] ) )
    {
      global $PAGE;
      return array( FALSE , 'Mot de passe incorrect ! Nouvelle tentative autorisée dans '.$_SESSION['FORCEBRUTE'][$PAGE]['DELAI'].'s.' );
    }
    // Si compte desactivé...
    if($DB_ROW['user_sortie_date']<=TODAY_SQL)
    {
      return array( FALSE , 'Identification réussie mais ce compte ('.$DB_ROW['user_login'].') est desactivé (sur la base n°'.$BASE.') !<br>Un administrateur doit ré-activer votre compte si cela est anormal&hellip;<br>Il doit pour cela se connecter à SACoche, menu [Gestion&nbsp;des&nbsp;utilisateurs] [Élèves|Parents|Professeurs], faire afficher les comptes anciens, cocher votre compte et cliquer sur "Réintégrer".<br>Il a aussi la possibilité d’utiliser le menu [Gestion&nbsp;des&nbsp;utilisateurs] [Rechercher&nbsp;un&nbsp;utilisateur] pour effectuer la modification voulue.' );
    }
    if($mode_connection!='normal_test')
    {
      // Mémoriser la date de la dernière connexion (pour les autres cas, sera enregistré lors de la confirmation de la prise en compte des infos CNIL-RGPD).
      if( ($DB_ROW['user_connexion_date']!==NULL) || in_array($DB_ROW['user_profil_type'],array('webmestre','administrateur')) )
      {
        DB_STRUCTURE_PUBLIC::DB_enregistrer_date_connexion($DB_ROW['user_id']);
      }
      // Mémoriser l’historique des accès
      DB_STRUCTURE_ACCES_HISTORIQUE::DB_ajouter( $DB_ROW['user_id'] , $mode_connection , $login );
      // Enregistrement d’un cookie sur le poste client servant à retenir le dernier établissement sélectionné si identification avec succès
      Cookie::definir( COOKIE_STRUCTURE , $BASE , 31536000 /* 60*60*24*365 = 1 an */ );
      // Enregistrement d’un cookie sur le poste client servant à retenir le dernier mode de connexion utilisé si identification avec succès
      Cookie::definir( COOKIE_AUTHMODE , $mode_connection );
    }
    // Si on arrive ici c’est que l’identification s’est bien effectuée !
    return array( TRUE , $DB_ROW );
  }

  /**
   * Enregistrer en session les informations authentifiant un utilisateur (sauf profils webmestre / développeur / partenaire).
   * 
   * @param int     $BASE
   * @param array   $DB_ROW   ligne issue de la table sacoche_user correspondant à l’utilisateur qui se connecte.
   * @return void
   */
  public static function initialiser_utilisateur($BASE,$DB_ROW)
  {
    // Récupérer et Enregistrer en session les données associées à l’établissement (indices du tableau de session en majuscules).
    $DB_TAB_PARAM = DB_STRUCTURE_PARAMETRE::DB_lister_parametres();
    $tab_type_tableau = array(
      'ETABLISSEMENT',
      'ENVELOPPE',
      'OFFICIEL',
      'CAS_SERVEUR',
    );
    foreach($DB_TAB_PARAM as $DB_ROW_PARAM)
    {
      $parametre_nom = Clean::upper($DB_ROW_PARAM['parametre_nom']);
      // Certains paramètres sont de type entier.
      $val = $DB_ROW_PARAM['parametre_valeur'];
      $parametre_valeur = ( ctype_digit((string)$val) && ( (mb_strlen($val)<=1) || ($val[0]!=='0') ) ) ? (int) $val : $val ; // éviter que "03" soit converti en entier
      // Certains paramètres sont à enregistrer sous forme de tableau.
      $find = FALSE;
      foreach($tab_type_tableau as $key1)
      {
        $longueur_key1 = strlen($key1);
        if(substr($parametre_nom,0,$longueur_key1)==$key1)
        {
          $key2 = substr($parametre_nom,$longueur_key1+1);
          Session::_set($key1,$key2,$parametre_valeur);
          $find = TRUE;
          break;
        }
      }
      // Les autres paramètres sont à enregistrer tels quels.
      if(!$find)
      {
        Session::_set($parametre_nom,$parametre_valeur);
      }
    }
    // Enregistrer en session le numéro de la base.
    Session::_set('BASE'                   , $BASE );
    // C’est un utilisateur d’un établissement.
    Session::_set('USER_ETABLISSEMENT'     , TRUE );
    // Enregistrer en session les données associées au profil de l’utilisateur.
    Session::_set('USER_PROFIL_SIGLE'      , $DB_ROW['user_profil_sigle'] );
    Session::_set('USER_PROFIL_TYPE'       , $DB_ROW['user_profil_type'] );
    Session::_set('USER_PROFIL_NOM_COURT'  , $DB_ROW['user_profil_nom_court_singulier'] );
    Session::_set('USER_PROFIL_NOM_LONG'   , $DB_ROW['user_profil_nom_long_singulier'] );
    Session::_set('USER_JOIN_GROUPES'      , $DB_ROW['user_profil_join_groupes'] );   // Seuls les enseignants sont rattachés à des classes et groupes définis ; les autres le sont à tout l’établissement.
    Session::_set('USER_JOIN_MATIERES'     , $DB_ROW['user_profil_join_matieres']  ); // Seuls les directeurs sont rattachés à toutes les matières ; les autres le sont à des matières définies.
    Session::_set('USER_MDP_LONGUEUR_MINI' , (int) $DB_ROW['user_profil_mdp_longueur_mini'] );
    Session::_set('USER_DUREE_INACTIVITE'  , (int) $DB_ROW['user_profil_duree_inactivite'] );
    // Enregistrer en session les données personnelles de l’utilisateur.
    $is_first_connexion = ($DB_ROW['user_connexion_date']===NULL) ? TRUE : FALSE ;
    $tab_user_form_options = !empty($DB_ROW['user_form_options']) ? @unserialize($DB_ROW['user_form_options']) : array() ;
    Session::_set('USER_ID'                , (int) $DB_ROW['user_id'] );
    Session::_set('USER_SWITCH_ID'         , $DB_ROW['user_switch_id'] );
    Session::_set('USER_GENRE'             , $DB_ROW['user_genre'] );
    Session::_set('USER_NOM'               , $DB_ROW['user_nom'] );
    Session::_set('USER_PRENOM'            , $DB_ROW['user_prenom'] );
    Session::_set('USER_NAISSANCE_DATE'    , $DB_ROW['user_naissance_date'] );
    Session::_set('USER_EMAIL'             , $DB_ROW['user_email'] );
    Session::_set('USER_EMAIL_ORIGINE'     , $DB_ROW['user_email_origine'] );
    Session::_set('USER_EMAIL_REFUS'       , $DB_ROW['user_email_refus'] );
    Session::_set('USER_LOGIN'             , $DB_ROW['user_login'] );
    Session::_set('USER_LANGUE'            , $DB_ROW['user_langue'] );
    Session::_set('USER_DALTONISME'        , $DB_ROW['user_daltonisme'] );
    Session::_set('USER_ID_ENT'            , $DB_ROW['user_id_ent'] );
    Session::_set('USER_ID_GEPI'           , $DB_ROW['user_id_gepi'] );
    Session::_set('USER_PARAM_ACCUEIL'     , $DB_ROW['user_param_accueil'] );
    Session::_set('USER_PARAM_MENU'        , $DB_ROW['user_param_menu'] );
    Session::_set('USER_PARAM_FAVORI'      , $DB_ROW['user_param_favori'] );
    Session::_set('USER_CSV_ENCODAGE'      , $DB_ROW['user_csv_encodage'] );
    Session::_set('TAB_USER_FORM_OPTIONS'  , $tab_user_form_options );
    Session::_set('ELEVE_CLASSE_ID'        , (int) $DB_ROW['eleve_classe_id'] );
    Session::_set('ELEVE_CLASSE_NOM'       , $DB_ROW['groupe_nom'] );
    Session::_set('DELAI_CONNEXION'        , (int) $DB_ROW['delai_connexion_secondes'] ); // Vaut (int)NULL = 0 à la 1e connexion, mais dans ce cas $_SESSION['FIRST_CONNEXION'] est testé avant.
    Session::_set('FIRST_CONNEXION'        , $is_first_connexion );
    if( ($DB_ROW['user_connexion_date']===NULL) && ($DB_ROW['user_profil_type']!='administrateur') )
    {
      Session::_set('STOP_CNIL' , TRUE );
    }
    // Récupérer et Enregistrer en session les données des élèves associées à un responsable légal.
    if($_SESSION['USER_PROFIL_TYPE']=='parent')
    {
      Session::_set('OPT_PARENT_ENFANTS' , DB_STRUCTURE_COMMUN::DB_OPT_enfants_parent($_SESSION['USER_ID']) );
      Session::_set('OPT_PARENT_CLASSES' , DB_STRUCTURE_COMMUN::DB_OPT_classes_parent($_SESSION['USER_ID']) );
      $nb_enfants = is_array($_SESSION['OPT_PARENT_ENFANTS']) ? count($_SESSION['OPT_PARENT_ENFANTS']) : 0 ;
      Session::_set('NB_ENFANTS' , $nb_enfants );
      if($_SESSION['NB_ENFANTS'])
      {
        $tab_enfant_num_resp = array();
        foreach($_SESSION['OPT_PARENT_ENFANTS'] as $key => $tab)
        {
          $tab_enfant_num_resp[$tab['valeur']] = $key+1 ;
        }
        Session::_set('ENFANT_NUM_RESP' , $tab_enfant_num_resp );
      }
      if( ($_SESSION['NB_ENFANTS']==1) && (is_array($_SESSION['OPT_PARENT_CLASSES'])) )
      {
        Session::_set('ELEVE_CLASSE_ID'  , (int) $_SESSION['OPT_PARENT_CLASSES'][0]['valeur'] );
        Session::_set('ELEVE_CLASSE_NOM' , $_SESSION['OPT_PARENT_CLASSES'][0]['texte'] );
      }
    }
    // Récupérer et Enregistrer en session la liste des modules externes associés à un professeur.
    if( ($_SESSION['USER_PROFIL_TYPE']=='professeur') || ($_SESSION['USER_PROFIL_TYPE']=='eleve') )
    {
      Session::_set('MODULE' , DB_STRUCTURE_PROFESSEUR::DB_lister_liaisons_user_module( $_SESSION['USER_ID'] ) );
    }
    // Récupérer et Enregistrer en session la liste des élèves associés à un professeur non rattaché à tous les élèves.
    if( ($_SESSION['USER_PROFIL_TYPE']=='professeur') && ($_SESSION['USER_JOIN_GROUPES']=='config') )
    {
      Session::_set('PROF_TAB_ELEVES' , DB_STRUCTURE_PROFESSEUR::DB_lister_ids_eleves_professeur( $_SESSION['USER_ID'] , $_SESSION['USER_JOIN_GROUPES'] , 'array' /*format_retour*/ ) );
    }
    // Récupérer et Enregistrer en session la liste des profs remplaçants ou remplacés associés à un professeur.
    if($_SESSION['USER_PROFIL_TYPE']=='professeur')
    {
      Session::_set('PROF_TAB_REMPLACEMENT' , DB_STRUCTURE_PROFESSEUR::DB_lister_ids_remplacement( $_SESSION['USER_ID'] ) );
    }
    // Récupérer et Enregistrer en session les données associées aux profils utilisateurs d’un établissement, activés ou non.
    if($_SESSION['USER_PROFIL_TYPE']=='administrateur')
    {
      Session::_set('TAB_PROFILS_ADMIN' , array(
        'TYPE'              => array() ,
        'LOGIN_MODELE'      => array() ,
        'MDP_LONGUEUR_MINI' => array() ,
        'DUREE_INACTIVITE'  => array() ,
      ) );
      $DB_TAB = DB_STRUCTURE_ADMINISTRATEUR::DB_lister_profils_parametres( 'user_profil_type,user_profil_login_modele,user_profil_mdp_longueur_mini,user_profil_mdp_date_naissance,user_profil_duree_inactivite' /*listing_champs*/ , FALSE /*only_actif*/ );
      foreach($DB_TAB as $DB_ROW)
      {
        Session::_set('TAB_PROFILS_ADMIN','TYPE'              ,$DB_ROW['user_profil_sigle'] , $DB_ROW['user_profil_type']);
        Session::_set('TAB_PROFILS_ADMIN','LOGIN_MODELE'      ,$DB_ROW['user_profil_sigle'] , $DB_ROW['user_profil_login_modele']);
        Session::_set('TAB_PROFILS_ADMIN','MDP_LONGUEUR_MINI' ,$DB_ROW['user_profil_sigle'] , (int) $DB_ROW['user_profil_mdp_longueur_mini']);
        Session::_set('TAB_PROFILS_ADMIN','MDP_DATE_NAISSANCE',$DB_ROW['user_profil_sigle'] , (int) $DB_ROW['user_profil_mdp_date_naissance']);
        Session::_set('TAB_PROFILS_ADMIN','DUREE_INACTIVITE'  ,$DB_ROW['user_profil_sigle'] , (int) $DB_ROW['user_profil_duree_inactivite']);
      }
    }
    // Récupérer et Enregistrer en session les noms des profils utilisateurs d’un établissement (activés) pour afficher les droits de certaines pages.
    else
    {
      Session::_set('TAB_PROFILS_DROIT' , array(
        'TYPE'             => array() ,
        'JOIN_GROUPES'     => array() ,
        'JOIN_MATIERES'    => array() ,
        'NOM_LONG_PLURIEL' => array() ,
      ) );
      $DB_TAB = DB_STRUCTURE_ADMINISTRATEUR::DB_lister_profils_parametres( 'user_profil_type,user_profil_join_groupes,user_profil_join_matieres,user_profil_nom_long_pluriel' /*listing_champs*/ , TRUE /*only_actif*/ );
      foreach($DB_TAB as $DB_ROW)
      {
        Session::_set('TAB_PROFILS_DROIT','TYPE'            ,$DB_ROW['user_profil_sigle'] , $DB_ROW['user_profil_type']);
        Session::_set('TAB_PROFILS_DROIT','JOIN_GROUPES'    ,$DB_ROW['user_profil_sigle'] , $DB_ROW['user_profil_join_groupes']);
        Session::_set('TAB_PROFILS_DROIT','JOIN_MATIERES'   ,$DB_ROW['user_profil_sigle'] , $DB_ROW['user_profil_join_matieres']);
        Session::_set('TAB_PROFILS_DROIT','NOM_LONG_PLURIEL',$DB_ROW['user_profil_sigle'] , $DB_ROW['user_profil_nom_long_pluriel']);
      }
    }
    // À partir de MySQL 5.7 on peut utiliser ANY_VALUE()
    // @see http://dev.mysql.com/doc/refman/5.7/en/sql-mode.html#sqlmode_only_full_group_by
    // @see http://dev.mysql.com/doc/refman/5.7/en/miscellaneous-functions.html#function_any-value
    // Le test s’est révélé incorrect sur un serveur utilisant MariaDB, d’où la détermination du moteur en plus si possible
    // @see http://stackoverflow.com/questions/37317869/determine-if-mysql-or-percona-or-mariadb
    // Enfin, MariaDB n’a pas implémenté la fonction ANY_VALUE...
    // @see https://mariadb.com/kb/en/mariadb/functions-and-modifiers-for-use-with-group-by/
    $sql_any_value = ( (InfoServeur::SQL_engine()=='MySQL') && version_compare(InfoServeur::SQL_version(),'5.7','>=') ) ? 'ANY_VALUE' : 'MAX' ;
    Session::_set('sql_any_value' , $sql_any_value);
    // Enregistrer en session les couleurs et paramètres pour les codes de notation, les états d’acquisition, les degrés de maîtrise
    SessionUser::memoriser_couleurs();
    // Fabriquer $_SESSION['NOTE'][i]['FICHIER'] en fonction de $_SESSION['USER_DALTONISME']
    // remarque : $_SESSION['USER_DALTONISME'] ne peut être utilisé que pour les profils élèves/parents/profs/directeurs, pas les admins ni le webmestre
    SessionUser::adapter_daltonisme();
    // Enregistrer en session le CSS personnalisé
    SessionUser::actualiser_style();
    // Enregistrer en session le menu personnalisé ; détection de la langue remis ici pour le cas de bascule entre comptes.
    Lang::setlocale( LC_MESSAGES, Lang::get_locale_used() );
    SessionUser::memoriser_menu();
    // Juste pour davantage de lisibilité si besoin de debug...
    ksort($_SESSION);
    // On écrit si besoin les fichiers des symboles personnalisées uploadés par l’établissement
    SessionUser::actualiser_fichiers_symboles_perso();
    // On profite de cet événement pour faire du ménage ou simuler une tâche planifiée
    SessionUser::cron();
  }

  /**
   * Enregistrer en session les informations authentifiant le webmestre.
   * 
   * @return void
   */
  public static function initialiser_webmestre()
  {
    // Numéro de la base
    Session::_set('BASE'                         , 0 );
    // Ce n’est pas un utilisateur d’un établissement.
    Session::_set('USER_ETABLISSEMENT'           , FALSE );
    // Données associées au profil de l’utilisateur.
    Session::_set('USER_PROFIL_SIGLE'            , 'WBM' );
    Session::_set('USER_PROFIL_TYPE'             , 'webmestre' );
    Session::_set('USER_PROFIL_NOM_COURT'        , 'webmestre' );
    Session::_set('USER_PROFIL_NOM_LONG'         , 'responsable du serveur (webmestre)' );
    Session::_set('USER_MDP_LONGUEUR_MINI'       , 6 );
    Session::_set('USER_DUREE_INACTIVITE'        , 15 );
    // Données personnelles de l’utilisateur.
    Session::_set('USER_ID'                      , 0 );
    Session::_set('USER_NOM'                     , WEBMESTRE_NOM );
    Session::_set('USER_PRENOM'                  , WEBMESTRE_PRENOM );
    Session::_set('USER_LANGUE'                  , LOCALE_DEFAULT );
    Session::_set('USER_PARAM_MENU'              , '' );
    // Données associées à l’établissement.
    Session::_set('SESAMATH_ID'                  , 0 );
    Session::_set('ETABLISSEMENT','DENOMINATION' , 'Gestion '.HEBERGEUR_INSTALLATION );
    Session::_set('CONNEXION_MODE'               , 'normal' );
    // Enregistrer en session le menu personnalisé
    SessionUser::memoriser_menu();
  }

  /**
   * Enregistrer en session les informations authentifiant un développeur.
   * 
   * @return void
   */
  public static function initialiser_developpeur()
  {
    // Numéro de la base
    Session::_set('BASE'                         , 0 );
    // Ce n’est pas un utilisateur d’un établissement.
    Session::_set('USER_ETABLISSEMENT'           , FALSE );
    // Données associées au profil de l’utilisateur.
    Session::_set('USER_PROFIL_SIGLE'            , 'DVL' );
    Session::_set('USER_PROFIL_TYPE'             , 'developpeur' );
    Session::_set('USER_PROFIL_NOM_COURT'        , 'devel' );
    Session::_set('USER_PROFIL_NOM_LONG'         , 'développeur SACoche' );
    Session::_set('USER_MDP_LONGUEUR_MINI'       , 6 );
    Session::_set('USER_DUREE_INACTIVITE'        , 15 );
    // Données personnelles de l’utilisateur.
    Session::_set('USER_ID'                      , 0 );
    Session::_set('USER_NOM'                     , 'SACoche' );
    Session::_set('USER_PRENOM'                  , 'développeur' );
    Session::_set('USER_LANGUE'                  , LOCALE_DEFAULT );
    Session::_set('USER_PARAM_MENU'              , '' );
    // Données associées à l’établissement.
    Session::_set('SESAMATH_ID'                  , 0 );
    Session::_set('ETABLISSEMENT','DENOMINATION' , HEBERGEUR_INSTALLATION );
    Session::_set('CONNEXION_MODE'               , 'normal' );
    // Enregistrer en session le menu personnalisé
    SessionUser::memoriser_menu();
  }

  /**
   * Enregistrer en session les informations authentifiant un partenaire.
   * 
   * @param array   $DB_ROW   ligne issue de la table sacoche_partenaire correspondant à l’utilisateur qui se connecte.
   * @return void
   */
  public static function initialiser_partenaire($DB_ROW)
  {
    // Numéro de la base
    Session::_set('BASE'                         , 0 );
    // Ce n’est pas un utilisateur d’un établissement.
    Session::_set('USER_ETABLISSEMENT'           , FALSE );
    // Données associées au profil de l’utilisateur.
    Session::_set('USER_PROFIL_SIGLE'            , 'ENT' );
    Session::_set('USER_PROFIL_TYPE'             , 'partenaire' );
    Session::_set('USER_PROFIL_NOM_COURT'        , 'partenaire' );
    Session::_set('USER_PROFIL_NOM_LONG'         , 'partenariat conventionné (ENT)' );
    Session::_set('USER_MDP_LONGUEUR_MINI'       , 6 );
    Session::_set('USER_DUREE_INACTIVITE'        , 15 );
    // Données personnelles de l’utilisateur.
    Session::_set('USER_ID'                      , (int) $DB_ROW['partenaire_id'] );
    Session::_set('USER_NOM'                     , $DB_ROW['partenaire_nom'] );
    Session::_set('USER_PRENOM'                  , $DB_ROW['partenaire_prenom'] );
    Session::_set('USER_LANGUE'                  , LOCALE_DEFAULT );
    Session::_set('USER_CONNECTEURS'             , $DB_ROW['partenaire_connecteurs'] );
    Session::_set('USER_PARAM_MENU'              , '' );
    // Données associées à l’établissement.
    Session::_set('SESAMATH_ID'                  , 0 );
    Session::_set('ETABLISSEMENT','DENOMINATION' , $DB_ROW['partenaire_denomination'] );
    Session::_set('CONNEXION_MODE'               , 'normal' );
    // Enregistrer en session le menu personnalisé
    SessionUser::memoriser_menu();
  }

  /**
   * Compléter la session avec les couleurs et paramètres pour les codes de notation, les états d’acquisition, les degrés de maîtrise
   * 
   * @return void
   */
  public static function memoriser_couleurs()
  {
    // Codes de notation - On récupère aussi les inactifs au cas où ils auraient été utilisés puis retirés.
    Session::_set('NOTE' , array() );
    $session_note_actif = array();
    $DB_TAB_NOTE = DB_STRUCTURE_PARAMETRE::DB_lister_parametres_note( TRUE /*priority_actifs*/ );
    foreach($DB_TAB_NOTE as $DB_ROW_NOTE)
    {
      Session::_set( 'NOTE' , (int)$DB_ROW_NOTE['note_id'] , array(
        'ACTIF'   => (int)$DB_ROW_NOTE['note_actif'],
        'VALEUR'  => (int)$DB_ROW_NOTE['note_valeur'],
        'IMAGE'   => $DB_ROW_NOTE['note_image'],
        'SIGLE'   => $DB_ROW_NOTE['note_sigle'],
        'LEGENDE' => $DB_ROW_NOTE['note_legende'],
        'CLAVIER' => $DB_ROW_NOTE['note_clavier'],
      ) );
      if($DB_ROW_NOTE['note_actif'])
      {
        $session_note_actif[] = (int)$DB_ROW_NOTE['note_id'];
      }
    }
    Session::_set('NOTE_ACTIF',$session_note_actif);
    // États d’acquisition - Les couleurs pour les daltoniens servent aussi pour les impressions PDF en niveau de gris
    Session::_set('ACQUIS' , array() );
    $DB_TAB_ACQUIS = DB_STRUCTURE_PARAMETRE::DB_lister_parametres_acquis( TRUE /*only_actifs*/ );
    foreach($DB_TAB_ACQUIS as $key => $DB_ROW_ACQUIS)
    {
      Session::_set( 'ACQUIS' , (int)$DB_ROW_ACQUIS['acquis_id'] , array(
        'ACTIF'     => (int)$DB_ROW_ACQUIS['acquis_actif'],
        'SEUIL_MIN' => (int)$DB_ROW_ACQUIS['acquis_seuil_min'],
        'SEUIL_MAX' => (int)$DB_ROW_ACQUIS['acquis_seuil_max'],
        'VALEUR'    => (int)$DB_ROW_ACQUIS['acquis_valeur'],
        'COULEUR'   => $DB_ROW_ACQUIS['acquis_couleur'],
        'GRIS'      => SessionUser::$tab_dalton_css[$_SESSION['NOMBRE_ETATS_ACQUISITION']][$key],
        'SIGLE'     => $DB_ROW_ACQUIS['acquis_sigle'],
        'LEGENDE'   => $DB_ROW_ACQUIS['acquis_legende'],
      ) );
    }
    // Degrés de maîtrise - Pour l’instant juste des couleurs (plus marquées que les originales en 4 colonnes) afin de permettre l’initialisation du style CSS
    Session::_set('LIVRET' , array(
      1 => array( 'COULEUR' => '#E5F4FF' , 'GRIS' => SessionUser::$tab_dalton_css[4][3] , 'POINTS' => 10 ), // TSV =(204; 10;100)
      2 => array( 'COULEUR' => '#91CBF2' , 'GRIS' => SessionUser::$tab_dalton_css[4][2] , 'POINTS' => 25 ), // TSV =(204; 40; 95)
      3 => array( 'COULEUR' => '#44A7E5' , 'GRIS' => SessionUser::$tab_dalton_css[4][1] , 'POINTS' => 40 ), // TSV =(204; 70; 90)
      4 => array( 'COULEUR' => '#0082D8' , 'GRIS' => SessionUser::$tab_dalton_css[4][0] , 'POINTS' => 50 ), // TSV =(204;100; 85)
    ) );
  }

  /**
   * Compléter la session avec les informations de style dépendant du daltonisme + des choix paramétrés au niveau de l’établissement (couleurs, codes de notation).
   * 
   * @return void
   */
  public static function adapter_daltonisme()
  {
    // pour les codes de notation
    $numero = 0;
    foreach( $_SESSION['NOTE'] as $note_id => $tab_note_info )
    {
      if( $_SESSION['USER_DALTONISME'] && $tab_note_info['ACTIF'] )
      {
        $numero++;
        Session::_set('NOTE',$note_id,'FICHIER' , Html::note_src_daltonisme($numero) );
      }
      else
      {
        Session::_set('NOTE',$note_id,'FICHIER' , Html::note_src_couleur($tab_note_info['IMAGE']) );
      }
    }
  }

  /**
   * Compléter la session avec les informations de style dépendant des choix paramétrés au niveau de l’établissement (couleurs, codes de notation).
   * 
   * @return void
   */
  public static function actualiser_style()
  {
    $key_couleur = $_SESSION['USER_DALTONISME'] ? 'GRIS' : 'COULEUR' ;
    $session_css  = '';
    // codes de notation
    foreach( $_SESSION['NOTE'] as $note_id => $tab_note_info )
    {
      $session_css .= '#zone_plan_classe input.N'.$note_id.' {background:#FFF url('.$tab_note_info['FICHIER'].') no-repeat center center;}'.NL;
      $session_css .= 'table.scor_eval tbody.h td input.N'.$note_id.' {background:#FFF url('.$tab_note_info['FICHIER'].') no-repeat center center;}'.NL;
      $session_css .= 'table.scor_eval tbody.v td input.N'.$note_id.' {background:#FFF url('.str_replace(array('/h/','/h_'),array('/v/','/v_'),$tab_note_info['FICHIER']).') no-repeat center center;}'.NL;
    }
    // couleurs des états d’acquisition
    foreach( $_SESSION['ACQUIS'] as $acquis_id => $tab_acquis_info )
    {
      $background_color = $tab_acquis_info[$key_couleur];
      $color = Html::luminance_couleur_texte($background_color);
      $session_css .= 'table th.A'.$acquis_id.' , table td.A'.$acquis_id.' , div.A'.$acquis_id.' ,span.A'.$acquis_id.' ,label.A'.$acquis_id.' {'.$color.'background-color:'.$background_color.'}'.NL;
    }
    // couleurs des degrés de maîtrise
    foreach( $_SESSION['LIVRET'] as $maitrise_id => $tab_maitrise_info )
    {
      $background_color = $tab_maitrise_info['COULEUR'];
      $color = Html::luminance_couleur_texte($background_color);
      $session_css .= 'table th.M'.$maitrise_id.' , table td.M'.$maitrise_id.' , div.M'.$maitrise_id.' ,span.M'.$maitrise_id.' ,label.M'.$maitrise_id.' {'.$color.'background-color:'.$background_color.'}'.NL;
    }
    Session::_set('CSS',$session_css);
  }

  /**
   * Enregistrer en session le menu selon le profil et éventuellement les droits de l’utilisateur ou les paramétrages de l’administrateur.
   * 
   * @return void
   */
  public static function memoriser_menu()
  {
    if( in_array($_SESSION['USER_PROFIL_TYPE'],array('eleve','parent','professeur','directeur')) )
    {
      if( is_null($_SESSION['USER_PARAM_MENU']) )
      {
        // L’utilisateur n’a pas enregistré de préférences : on surcharge par celles éventuelles de l’admin
        $DB_ROW = DB_STRUCTURE_PARAMETRE::DB_recuperer_parametres_profil($_SESSION['USER_PROFIL_TYPE']);
        $param_origine = is_null($DB_ROW['profil_param_menu']) ? 'aucun' : 'admin' ;
        Session::_set('USER_PARAM_MENU'    , $DB_ROW['profil_param_menu']);
        Session::_set('USER_PARAM_FAVORI'  , $DB_ROW['profil_param_favori']);
        Session::_set('USER_PARAM_ORIGINE' , $param_origine);
      }
      else
      {
        Session::_set('USER_PARAM_ORIGINE' , 'prof');
      }
    }
    $line_height = 43+0.5; // @see ./_css/style.css --> #menu li li a {line-height:43px}
    $numero_menu = 0;
    require(CHEMIN_DOSSIER_MENUS.'menu_'.$_SESSION['USER_PROFIL_TYPE'].'.php'); // récupère $tab_menu & $tab_sous_menu
    $session_menu = '<ul id="menu"><li><a class="boussole" href="#">'.html(Lang::_('MENU')).'</a><ul>'.NL;
    $nombre_menu = count($tab_menu);
    $tab_param_menu = !is_null($_SESSION['USER_PARAM_MENU']) ? explode(',',$_SESSION['USER_PARAM_MENU']) : array() ;
    foreach($tab_menu as $menu_id => $menu_titre)
    {
      $disabled_menu = in_array($menu_id,$tab_param_menu) ? ' disabled' : '' ;
      $session_menu .= '<li><a class="fleche'.$disabled_menu.'" href="#">'.html($menu_titre).'</a><ul>'.NL;
      $nombre_sous_menu = count($tab_sous_menu[$menu_id]);
      $premier_sous_menu = TRUE;
      foreach($tab_sous_menu[$menu_id] as $sous_menu_id => $tab)
      {
        $disabled_sousmenu = ( $disabled_menu || in_array($sous_menu_id,$tab_param_menu) || isset($tab['disabled']) ) ? ' disabled' : '' ;
        $nombre_cases_decalage = max( 0 , min( $numero_menu , $numero_menu-($nombre_menu-$nombre_sous_menu) ) );
        $style = ($premier_sous_menu && $nombre_cases_decalage) ? ' style="margin-top:-'.($nombre_cases_decalage*$line_height).'px"' : '' ;
        $session_menu .= '<li id="menu_li_'.$sous_menu_id.'"><a class="'.$tab['class'].$disabled_sousmenu.'"'.$style.' href="./index.php?'.$tab['href'].'">'.html($tab['texte']).'</a></li>'.NL;
        $premier_sous_menu = FALSE;
      }
      $session_menu .= '</ul></li>'.NL;
      $numero_menu++;
    }
    $session_menu .= '</ul></li></ul>'.NL;
    Session::_set('MENU',$session_menu);
    // On profite d’avoir le fichier du menu dispo pour s’occuper aussi des favoris en page d’accueil.
    $session_favori = '';
    if( in_array($_SESSION['USER_PROFIL_TYPE'],array('eleve','parent','professeur','directeur')) && $_SESSION['USER_PARAM_FAVORI'] )
    {
      $tab_param_favori = explode(',',$_SESSION['USER_PARAM_FAVORI']);
      $session_favori .= '<div class="sousmenu">';
      foreach($tab_menu as $menu_id => $menu_titre)
      {
        foreach($tab_sous_menu[$menu_id] as $sous_menu_id => $tab)
        {
          if(in_array($sous_menu_id,$tab_param_favori))
          {
            $session_favori .= '<a href="./index.php?'.$tab['href'].'" class="actif">'.html($tab['texte']).'</a>';
          }
        }
      }
      $session_favori .= '</div>'.NL;
    }
    Session::_set('FAVORI',$session_favori);
  }

  /**
   * Fichiers des symboles personnalisées uploadés par l’établissement à mettre en place si besoin
   * 
   * @return void
   */
  public static function actualiser_fichiers_symboles_perso()
  {
    $DB_TAB = DB_STRUCTURE_IMAGE::DB_lister_images_notes();
    if(!empty($DB_TAB))
    {
      FileSystem::creer_sous_dossier_etabl_si_besoin( CHEMIN_DOSSIER_SYMBOLE.$_SESSION['BASE'] );
      foreach($DB_TAB as $DB_ROW)
      {
        $image_nom = 'upload_'.$DB_ROW['image_note_id'];
        FileSystem::ecrire_fichier( FileSystem::chemin_fichier_symbole($image_nom,'h','perso') , base64_decode($DB_ROW['image_contenu_h']) );
        FileSystem::ecrire_fichier( FileSystem::chemin_fichier_symbole($image_nom,'v','perso') , base64_decode($DB_ROW['image_contenu_v']) );
      }
    }
  }

  /**
   * Tâches pseudo-planifiées exécutées lors de la connexion d’un utilisateur d’un établissement
   * 
   * @return void
   */
  public static function cron()
  {
    // On essaye de faire en sorte que plusieurs connexions ne lancent pas ces procédures simultanément
    $fichier_lock = CHEMIN_DOSSIER_TMP.'lock.txt';
    if(!file_exists($fichier_lock))
    {
      // On écrit un marqueur
      FileSystem::ecrire_fichier($fichier_lock,'');
      // On vérifie que des sous-dossiers ajoutés ultérieurement existent
      FileSystem::verifier_existence_dossiers();
      // On efface les fichiers temporaires obsolètes
      FileSystem::nettoyer_fichiers_temporaires_commun();
      FileSystem::nettoyer_fichiers_temporaires_etablissement($_SESSION['BASE']);
      // On allège le fichier de logs si nécessaire
      SACocheLog::alleger($_SESSION['BASE']);
      // On rend visibles les notifications en attente et on supprime les notifications plus anciennes
      Sesamail::envoyer_notifications();
      DB_STRUCTURE_NOTIFICATION::DB_supprimer_log_vus_anciens();
      // On efface le marqueur
      FileSystem::supprimer_fichier( $fichier_lock , TRUE /*verif_exist*/ );
    }
    // Si le fichier témoin du nettoyage existe, on vérifie que sa présence n’est pas anormale (cela s’est déjà produit...)
    else
    {
      if( $_SERVER['REQUEST_TIME'] - filemtime($fichier_lock) > 30 )
      {
        FileSystem::supprimer_fichier($fichier_lock);
      }
    }
  }

}
?>