<?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/>.
 *
 */
 
// Extension de classe qui étend DB (pour permettre l’autoload)

// Ces méthodes ne concernent qu’une base STRUCTURE.
// Ces méthodes ne concernent qu’un administrateur.

class DB_STRUCTURE_ADMINISTRATEUR
{

/**
 * rechercher_users
 *
 * @param string   champ_nom
 * @param string   champ_val
 * @return array
 */
public static function DB_rechercher_users($champ_nom,$champ_val)
{
  $DB_SQL = 'SELECT user_id, user_sconet_id, user_sconet_elenoet, user_reference, user_profil_sigle, user_genre, user_nom, user_prenom, user_email, user_login, user_sortie_date, user_id_ent, user_id_gepi, user_profil_nom_long_singulier '
          . 'FROM sacoche_user '
          . 'LEFT JOIN sacoche_user_profil USING (user_profil_sigle) '
          . 'WHERE user_'.$champ_nom.' LIKE :champ_val '
          . 'ORDER BY user_nom ASC, user_prenom ASC ';
  $DB_VAR = array(':champ_val'=>$champ_val);
  return DB::queryTab(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL , $DB_VAR);
}

/**
 * rechercher_eleves
 *
 * @param string   user_nom_like
 * @param string   user_profil_type
 * @param int      user_statut
 * @return array
 */
public static function DB_rechercher_user_for_fusion( $user_nom_like , $user_profil_type , $user_statut )
{
  $where_profil = ($user_profil_type=='eleve') ? 'user_profil_type="eleve"' : 'user_profil_type IN("professeur","directeur")' ;
  $where_statut = ($user_statut) ? 'user_sortie_date>NOW() ' : 'user_sortie_date<NOW() ' ;
  $DB_SQL = 'SELECT user_id, user_nom, user_prenom, user_login '
          . 'FROM sacoche_user '
          . 'LEFT JOIN sacoche_user_profil USING (user_profil_sigle) '
          . 'WHERE user_nom LIKE :user_nom_like AND '.$where_profil.' AND '.$where_statut
          . 'ORDER BY user_nom ASC, user_prenom ASC ';
  $DB_VAR = array( ':user_nom_like' => $user_nom_like );
  return DB::queryTab(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL , $DB_VAR);
}

/**
 * lister_users_cibles
 *
 * @param string   $listing_user_id   id des utilisateurs séparés par des virgules
 * @param string   $listing_champs    nom des champs séparés par des virgules
 * @param string   $avec_info         facultatif ; "classe" pour récupérer la classe des élèves | "enfant" pour récupérer une classe et un enfant associé à un parent
 * @return array
 */
public static function DB_lister_users_cibles( $listing_user_id , $listing_champs , $avec_info='' )
{
  if($avec_info=='classe')
  {
    $DB_SQL = 'SELECT '.$listing_champs.',groupe_nom AS info '
            . 'FROM sacoche_user '
            . 'LEFT JOIN sacoche_groupe ON sacoche_user.eleve_classe_id=sacoche_groupe.groupe_id '
            . 'LEFT JOIN sacoche_niveau USING (niveau_id) '
            . 'WHERE user_id IN('.$listing_user_id.') '
            . 'ORDER BY niveau_ordre ASC, groupe_ref ASC, user_nom ASC, user_prenom ASC';
  }
  elseif($avec_info=='enfant')
  {
    // Lever si besoin une limitation de GROUP_CONCAT (group_concat_max_len est par défaut limité à une chaîne de 1024 caractères) ; éviter plus de 8096 (http://www.glpi-project.org/forum/viewtopic.php?id=23767).
    DB::query(SACOCHE_STRUCTURE_BD_NAME , 'SET group_concat_max_len = 8096');
    $DB_SQL = 'SELECT '.$listing_champs.',GROUP_CONCAT( CONCAT(groupe_ref," ",enfant.user_nom) SEPARATOR " - ") AS info '
            . 'FROM sacoche_user AS parent '
            . 'LEFT JOIN sacoche_jointure_parent_eleve ON parent.user_id=sacoche_jointure_parent_eleve.parent_id '
            . 'LEFT JOIN sacoche_user AS enfant ON sacoche_jointure_parent_eleve.eleve_id=enfant.user_id '
            . 'LEFT JOIN sacoche_groupe ON enfant.eleve_classe_id=sacoche_groupe.groupe_id '
            . 'LEFT JOIN sacoche_niveau USING (niveau_id) '
            . 'WHERE parent.user_id IN('.$listing_user_id.') AND enfant.user_sortie_date>NOW() '
            . 'GROUP BY parent.user_id ' ;
    // ORDER BY niveau_ordre ASC, groupe_ref ASC, enfant.user_nom ASC, enfant.user_prenom ASC
    // retiré car "ORDER BY clause is not in GROUP BY clause and contains nonaggregated column which is not functionally dependent on columns in GROUP BY clause; this is incompatible with sql_mode=only_full_group_by"
  }
  else
  {
    $DB_SQL = 'SELECT '.$listing_champs.' '
            . 'FROM sacoche_user '
            . 'WHERE user_id IN('.$listing_user_id.') '
            . 'ORDER BY user_nom ASC, user_prenom ASC';
  }
  return DB::queryTab(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL);
}

/**
 * lister_info_enfants_par_parent
 *
 * @param string   $listing_parent_id   id des parents séparés par des virgules
 * @return array
 */
public static function DB_lister_info_enfants_par_parent($listing_parent_id)
{
  // Lever si besoin une limitation de GROUP_CONCAT (group_concat_max_len est par défaut limité à une chaîne de 1024 caractères) ; éviter plus de 8096 (http://www.glpi-project.org/forum/viewtopic.php?id=23767).
  DB::query(SACOCHE_STRUCTURE_BD_NAME , 'SET group_concat_max_len = 8096');
  $DB_SQL = 'SELECT parent.user_id as parent_id, GROUP_CONCAT( CONCAT(groupe_ref," ",enfant.user_nom) SEPARATOR " - ") AS info '
          . 'FROM sacoche_user AS parent '
          . 'LEFT JOIN sacoche_jointure_parent_eleve ON parent.user_id=sacoche_jointure_parent_eleve.parent_id '
          . 'LEFT JOIN sacoche_user AS enfant ON sacoche_jointure_parent_eleve.eleve_id=enfant.user_id '
          . 'LEFT JOIN sacoche_groupe ON enfant.eleve_classe_id=sacoche_groupe.groupe_id '
          . 'LEFT JOIN sacoche_niveau USING (niveau_id) '
          . 'WHERE parent.user_id IN('.$listing_parent_id.') AND enfant.user_sortie_date>NOW() '
          . 'GROUP BY parent.user_id ';
  return DB::queryTab(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL);
}

/**
 * lister_adresses_parents
 *
 * @param void
 * @return array
 */
public static function DB_lister_adresses_parents()
{
  $DB_SQL = 'SELECT * '
          . 'FROM sacoche_parent_adresse ';
  return DB::queryTab(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL);
}

/**
 * lister_parents_par_eleve
 *
 * @param void
 * @return array
 */
public static function DB_lister_parents_par_eleve()
{
  $DB_SQL = 'SELECT eleve.user_id AS eleve_id,   eleve.user_sconet_id AS eleve_sconet_id,   eleve.user_nom AS eleve_nom,   eleve.user_prenom AS eleve_prenom, '
          .       'parent.user_id AS parent_id, parent.user_sconet_id AS parent_sconet_id, parent.user_nom AS parent_nom, parent.user_prenom AS parent_prenom, '
          .   'sacoche_jointure_parent_eleve.resp_legal_num '
          . 'FROM sacoche_user AS eleve '
          . 'LEFT JOIN sacoche_user_profil AS eleve_profil ON eleve.user_profil_sigle=eleve_profil.user_profil_sigle '
          . 'LEFT JOIN sacoche_jointure_parent_eleve ON eleve.user_id=sacoche_jointure_parent_eleve.eleve_id '
          . 'LEFT JOIN sacoche_user AS parent ON sacoche_jointure_parent_eleve.parent_id=parent.user_id '
          . 'WHERE eleve_profil.user_profil_type="eleve" '
          . 'ORDER BY eleve_nom ASC, eleve_prenom ASC, resp_legal_num ASC ';
  return DB::queryTab(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL);
}

/**
 * lister_parents_actuels_avec_infos_for_eleve
 *
 * @param int   $eleve_id
 * @return array
 */
public static function DB_lister_parents_actuels_avec_infos_for_eleve($eleve_id)
{
  $DB_SQL = 'SELECT parent.user_id, parent.user_nom, parent.user_prenom, parent.user_login, sacoche_parent_adresse.*, resp_legal_num '
          . 'FROM sacoche_user AS eleve '
          . 'LEFT JOIN sacoche_jointure_parent_eleve ON eleve.user_id=sacoche_jointure_parent_eleve.eleve_id '
          . 'LEFT JOIN sacoche_user AS parent ON sacoche_jointure_parent_eleve.parent_id=parent.user_id '
          . 'LEFT JOIN sacoche_parent_adresse ON sacoche_jointure_parent_eleve.parent_id=sacoche_parent_adresse.parent_id '
          . 'WHERE eleve.user_id=:eleve_id AND parent.user_sortie_date>NOW() '
          . 'GROUP BY parent.user_id '
          . 'ORDER BY resp_legal_num ASC ';
  $DB_VAR = array(':eleve_id'=>$eleve_id);
  $DB_TAB_parents = DB::queryTab(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL , $DB_VAR, TRUE, TRUE);
  if(empty($DB_TAB_parents))
  {
    return array();
  }
  $listing_parent_id = implode(',',array_keys($DB_TAB_parents));
  // Lever si besoin une limitation de GROUP_CONCAT (group_concat_max_len est par défaut limité à une chaîne de 1024 caractères) ; éviter plus de 8096 (http://www.glpi-project.org/forum/viewtopic.php?id=23767).
  DB::query(SACOCHE_STRUCTURE_BD_NAME , 'SET group_concat_max_len = 8096');
  $DB_SQL = 'SELECT parent_id, GROUP_CONCAT( CONCAT(enfant.user_nom," ",enfant.user_prenom," (resp légal ",resp_legal_num,")") SEPARATOR " ; ") AS enfants_liste '
          . 'FROM sacoche_jointure_parent_eleve '
          . 'LEFT JOIN sacoche_user AS enfant ON sacoche_jointure_parent_eleve.eleve_id=enfant.user_id '
          . 'WHERE sacoche_jointure_parent_eleve.parent_id IN('.$listing_parent_id.') AND enfant.user_sortie_date>NOW() '
          . 'GROUP BY parent_id ';
  $DB_VAR = array(':eleve_id'=>$eleve_id);
  $DB_TAB_enfants = DB::queryTab(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL , $DB_VAR , TRUE , TRUE);
  $DB_TAB = array();
  foreach($DB_TAB_parents AS $id => $tab)
  {
    $DB_TAB[] = array_merge( $DB_TAB_parents[$id] , $DB_TAB_enfants[$id] , array('parent_id'=>$id) );
  }
  return $DB_TAB;
}

/**
 * lister_users
 *
 * @param string|array   $profil_type   'eleve' / 'parent' / 'professeur' / 'directeur' / 'administrateur' / ou par exemple array('eleve','professeur','directeur')
 * @param int            $statut        1 pour actuels, 0 pour anciens, 2 pour tout le monde
 * @param string         $liste_champs  liste des champs séparés par des virgules
 * @param bool           $with_classe   TRUE pour récupérer le nom de la classe de l’élève / FALSE sinon
 * @param bool           $tri_statut    TRUE pour trier par statut décroissant (les actifs en premier), FALSE par défaut
 * @return array
 */
public static function DB_lister_users( $profil_type , $statut , $liste_champs , $with_classe , $tri_statut=FALSE )
{
  $DB_VAR = array();
  $left_join = 'LEFT JOIN sacoche_user_profil USING (user_profil_sigle) ';
  $where     = '';
  $liste_champs .= ( $tri_statut || ($statut==2) ) ? ', (user_sortie_date>NOW()) AS statut ' : ' ' ;
  $order_by      = ($tri_statut) ? 'statut DESC, ' : '' ;
  if(is_string($profil_type))
  {
    $where .= 'user_profil_type=:profil_type ';
    $DB_VAR[':profil_type'] = $profil_type;
  }
  else
  {
    foreach($profil_type as $key => $val)
    {
      $or = ($key) ? 'OR ' : '( ' ;
      $where .= $or.'user_profil_type=:profil_type'.$key.' ';
      $DB_VAR[':profil_type'.$key] = $val;
    }
    $where .= ') ';
    $order_by .= 'user_profil_type ASC, ';
  }
  if($with_classe)
  {
    $liste_champs .= ', groupe_ref, groupe_nom ';
    $left_join .= 'LEFT JOIN sacoche_groupe ON sacoche_user.eleve_classe_id=sacoche_groupe.groupe_id ';
    $left_join .= 'LEFT JOIN sacoche_niveau USING (niveau_id) ';
    $order_by  .= 'niveau_ordre ASC, groupe_ref ASC, ';
  }
  $where .= ($statut==1) ? 'AND user_sortie_date>NOW() ' : ( ($statut==0) ? 'AND user_sortie_date<NOW() ' : '' ) ; // Pas besoin de tester l’égalité, NOW() renvoyant un datetime
  // On peut maintenant assembler les morceaux de la requête !
  $DB_SQL = 'SELECT '.$liste_champs
          . 'FROM sacoche_user '
          . $left_join
          . 'WHERE '.$where
          . 'ORDER BY '.$order_by.'user_nom ASC, user_prenom ASC ';
  return DB::queryTab(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL , $DB_VAR);
}

/**
 * lister_parents_avec_infos_enfants
 *
 * @param bool     $with_adresse
 * @param int      $statut            1 pour actuel, 0 pour ancien
 * @param string   $debut_nom         premières lettres du nom
 * @param string   $debut_prenom      premières lettres du prénom
 * @param string   $liste_parent_id   liste des id de parents
 * @return array
 */
public static function DB_lister_parents_avec_infos_enfants( $with_adresse , $statut , $debut_nom='' , $debut_prenom='' , $liste_parent_id='' )
{
  $select_parent = ($with_adresse) ? 'parent.user_id, parent.user_genre, parent.user_nom, parent.user_prenom, sacoche_parent_adresse.*, ' : 'parent.*, ' ;
  $join_adresse  = ($with_adresse) ? 'LEFT JOIN sacoche_parent_adresse ON parent.user_id=sacoche_parent_adresse.parent_id ' : '' ;
  $test_date_sortie_parent = ($statut) ? 'parent.user_sortie_date>NOW() ' : 'parent.user_sortie_date<NOW() ' ; // Pas besoin de tester l’égalité, NOW() renvoyant un datetime
  $test_date_sortie_enfant = ($statut) ? 'AND eleve.user_sortie_date>NOW() ' : '' ; // Pour un compte parent actif, on compte les enfants actifs, tout en récupérant les parents rattachés à aucun enfant à un des enfants sortis ; pour un compte parent inactif, aucun test afin de pouvoir aussi lister les enfants sortis
  if($liste_parent_id)
  {
    $where_parent = 'AND parent.user_id IN('.$liste_parent_id.') ';
  }
  else
  {
    $where_parent = ($debut_nom)    ? 'AND parent.user_nom LIKE :nom '       : '' ;
    $where_parent.= ($debut_prenom) ? 'AND parent.user_prenom LIKE :prenom ' : '' ;
  }
  // Lever si besoin une limitation de GROUP_CONCAT (group_concat_max_len est par défaut limité à une chaîne de 1024 caractères) ; éviter plus de 8096 (http://www.glpi-project.org/forum/viewtopic.php?id=23767).
  DB::query(SACOCHE_STRUCTURE_BD_NAME , 'SET group_concat_max_len = 8096');
  $DB_SQL = 'SELECT '.$select_parent
          . 'GROUP_CONCAT( CONCAT(eleve.user_nom," ",eleve.user_prenom," (resp légal ",resp_legal_num,")") SEPARATOR "'.BRJS.'") AS enfants_liste, '
          . 'COUNT(eleve.user_id) AS enfants_nombre '
          . 'FROM sacoche_user AS parent '
          . 'LEFT JOIN sacoche_user_profil AS parent_profil ON parent.user_profil_sigle=parent_profil.user_profil_sigle '
          . $join_adresse
          . 'LEFT JOIN sacoche_jointure_parent_eleve ON parent.user_id=sacoche_jointure_parent_eleve.parent_id '
          . 'LEFT JOIN sacoche_user AS eleve ON sacoche_jointure_parent_eleve.eleve_id=eleve.user_id '.$test_date_sortie_enfant
          . 'WHERE parent_profil.user_profil_type="parent" AND '.$test_date_sortie_parent.$where_parent
          . 'GROUP BY parent.user_id '
          . 'ORDER BY parent.user_nom ASC, parent.user_prenom ASC ';
  $DB_VAR = array(
    ':nom'    => $debut_nom   .'%',
    ':prenom' => $debut_prenom.'%',
  );
  return DB::queryTab(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL , $DB_VAR);
}

/**
 * lister_parents_adresses_par_enfant
 *
 * @return array
 */
public static function DB_lister_parents_adresses_par_enfant()
{
  $DB_SQL = 'SELECT eleve_id, parent_id, sacoche_parent_adresse.* '
          . 'FROM sacoche_user AS enfant '
          . 'LEFT JOIN sacoche_user_profil AS enfant_profil ON enfant.user_profil_sigle=enfant_profil.user_profil_sigle '
          . 'LEFT JOIN sacoche_jointure_parent_eleve ON enfant.user_id=sacoche_jointure_parent_eleve.eleve_id '
          . 'LEFT JOIN sacoche_user AS parent ON sacoche_jointure_parent_eleve.parent_id=parent.user_id '
          . 'LEFT JOIN sacoche_user_profil AS parent_profil ON parent.user_profil_sigle=parent_profil.user_profil_sigle '
          . 'LEFT JOIN sacoche_parent_adresse USING (parent_id) '
          . 'WHERE enfant_profil.user_profil_type="eleve" AND enfant.user_sortie_date>NOW() AND parent_profil.user_profil_type="parent" AND parent.user_sortie_date>NOW() ';
  return DB::queryTab(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL , array(), TRUE);
}

/**
 * lister_parents_homonymes
 *
 * @return array
 */
public static function DB_lister_parents_homonymes()
{
  $DB_SQL = 'SELECT user_nom, user_prenom, CONVERT( GROUP_CONCAT(user_id SEPARATOR ",") , CHAR) AS identifiants , COUNT(*) AS nombre '
          . 'FROM sacoche_user '
          . 'LEFT JOIN sacoche_user_profil USING(user_profil_sigle) '
          . 'WHERE user_profil_type="parent" AND user_sortie_date>NOW() '
          . 'GROUP BY user_nom,user_prenom HAVING nombre>1 ';
  return DB::queryTab(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL);
}

/**
 * lister_profs_remplacants
 *
 * @param void
 * @return array
 */
public static function DB_lister_profs_remplacants()
{
  $DB_SQL = 'SELECT prof_absent_id, prof_remplacant_id, '
          . 'absent.user_nom AS absent_nom, absent.user_prenom AS absent_prenom, '
          . 'remplacant.user_nom AS remplacant_nom, remplacant.user_prenom AS remplacant_prenom '
          . 'FROM sacoche_jointure_prof_remplacement '
          . 'LEFT JOIN sacoche_user AS absent ON sacoche_jointure_prof_remplacement.prof_absent_id=absent.user_id '
          . 'LEFT JOIN sacoche_user AS remplacant ON sacoche_jointure_prof_remplacement.prof_remplacant_id=remplacant.user_id '
          . 'ORDER BY absent.user_nom ASC, absent.user_prenom ASC, remplacant.user_nom ASC, remplacant.user_prenom ASC ';
  return DB::queryTab(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL);
}

/**
 * lister_users_avec_groupe
 *
 * @param string   $profil_type    'eleve' | 'professeur'
 * @param bool     $only_actuels   TRUE pour les actuels uniquement / FALSE pour tout le monde (actuel ou ancien)
 * @return array
 */
public static function DB_lister_users_avec_groupe($profil_type,$only_actuels)
{
  $where_sortie = ($only_actuels) ? 'AND user_sortie_date>NOW() ' : '' ;
  $DB_SQL = 'SELECT * '
          . 'FROM sacoche_user '
          . 'LEFT JOIN sacoche_user_profil USING (user_profil_sigle) '
          . 'LEFT JOIN sacoche_jointure_user_groupe USING (user_id) '
          . 'LEFT JOIN sacoche_groupe USING (groupe_id) '
          . 'WHERE user_profil_type=:profil_type AND groupe_type=:type '.$where_sortie
          . 'ORDER BY user_nom ASC, user_prenom ASC';
  $DB_VAR = array(
    ':profil_type' => $profil_type,
    ':type'        => 'groupe',
  );
  return DB::queryTab(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL , $DB_VAR);
}

/**
 * lister_users_desactives_obsoletes
 * Délai de 3 ans en général, 1 an pour les parents.
 * On compte 2 mois de moins pour être sur d'avoir tout le monde même si la procédure est lancée un peu tôt.
 *
 * @param void
 * @return array
 */
public static function DB_lister_users_desactives_obsoletes()
{
  $DB_SQL = 'SELECT user_id, user_profil_sigle '
          . 'FROM sacoche_user '
          . 'WHERE ( user_sortie_date < DATE_SUB(NOW(),INTERVAL 34 MONTH) ) OR ( ( user_profil_sigle IN("TUT","AVS") ) AND ( user_sortie_date < DATE_SUB(NOW(),INTERVAL 10 MONTH) ) ) ';
  return DB::queryTab(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL);
}

/**
 * lister_devoirs_id_disponibles
 *
 * @param void
 * @return array
 */
public static function DB_lister_devoirs_id_disponibles()
{
  $DB_SQL = 'SELECT DISTINCT(devoir_id) '
          . 'FROM sacoche_saisie ';
  $tab_devoirs_pris = DB::queryCol(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL);
  $DB_SQL = 'SELECT MAX(devoir_id) '
          . 'FROM sacoche_saisie ';
  $devoir_id_max = DB::queryOne(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL);
  $tab_devoirs_tous = range(1, $devoir_id_max);
  return array_diff( $tab_devoirs_tous , $tab_devoirs_pris);
}

/**
 * lister_profils_parametres
 *
 * @param string   $listing_champs (sans indiquer user_profil_sigle, par défaut dans la réponse)
 * @param bool     $only_actif
 * @param string   $only_profils_types
 * @return array
 */
public static function DB_lister_profils_parametres( $listing_champs , $only_actif , $only_profils_types=FALSE )
{
  $where_profil_actif = ($only_actif) ? 'AND ( user_profil_actif=1 OR user_profil_obligatoire=1 ) ' : '' ; // Sécurité au cas où un profil obligatoire aurait été déselectionné...
  $where_profil_type = ($only_profils_types) ? ( is_string($only_profils_types) ? 'AND user_profil_type="'.$only_profils_types.'" ' : 'AND user_profil_type IN("'.implode('","',$only_profils_types).'") ' ) : '' ;
  $DB_SQL = 'SELECT user_profil_sigle, '.$listing_champs.' '
          . 'FROM sacoche_user_profil '
          . 'WHERE user_profil_structure=1 AND user_profil_disponible=1 '.$where_profil_actif.$where_profil_type;
  return DB::queryTab(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL);
}

/**
 * lister_bilans_officiels_non_imprimes
 *
 * @param string $structure_uai
 * @param string $annee_scolaire
 * @param string $objet_type
 * @return int
 */
public static function DB_lister_bilans_officiels_non_imprimes( $structure_uai , $annee_scolaire , $objet_type )
{
  $DB_VAR = array(
    ':structure_uai'  => $structure_uai,
    ':annee_scolaire' => $annee_scolaire,
  );
  if($objet_type=='livret')
  {
    $DB_SQL = 'SELECT sacoche_periode.periode_nom, groupe_nom, COUNT(*) AS nombre '
            . 'FROM sacoche_livret_jointure_groupe '
            . 'LEFT JOIN sacoche_groupe USING(groupe_id) '
            . 'LEFT JOIN sacoche_user ON sacoche_livret_jointure_groupe.groupe_id = sacoche_user.eleve_classe_id '
            . 'LEFT JOIN sacoche_periode ON sacoche_livret_jointure_groupe.jointure_periode = sacoche_periode.periode_livret '
            . 'LEFT JOIN sacoche_officiel_archive ON sacoche_user.user_id = sacoche_officiel_archive.user_id '
            . ' AND structure_uai=:structure_uai AND annee_scolaire=:annee_scolaire AND archive_type="livret" '
            . ' AND sacoche_livret_jointure_groupe.livret_page_ref = sacoche_officiel_archive.archive_ref '
            . ' AND ( sacoche_periode.periode_id = sacoche_officiel_archive.periode_id OR ( jointure_periode=0 AND sacoche_officiel_archive.periode_id = 0 ) ) '
            . 'WHERE jointure_etat != "1vide" AND user_sortie_date>NOW() AND sacoche_officiel_archive.officiel_archive_id IS NULL '
            . 'GROUP BY sacoche_periode.periode_id, sacoche_groupe.groupe_id '
            . 'HAVING nombre > 5 ';
    return DB::queryTab(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL , $DB_VAR);
  }
  if($objet_type=='bulletin')
  {
    $DB_SQL = 'SELECT sacoche_periode.periode_nom, groupe_nom, COUNT(*) AS nombre '
            . 'FROM sacoche_jointure_groupe_periode '
            . 'LEFT JOIN sacoche_groupe USING(groupe_id) '
            . 'LEFT JOIN sacoche_user ON sacoche_jointure_groupe_periode.groupe_id = sacoche_user.eleve_classe_id '
            . 'LEFT JOIN sacoche_periode USING(periode_id) '
            . 'LEFT JOIN sacoche_officiel_archive ON sacoche_user.user_id = sacoche_officiel_archive.user_id '
            . ' AND structure_uai=:structure_uai AND annee_scolaire=:annee_scolaire AND archive_type="sacoche" AND archive_ref="bulletin" '
            . ' AND sacoche_periode.periode_id = sacoche_officiel_archive.periode_id '
            . 'WHERE officiel_bulletin != "" AND officiel_bulletin != "1vide" AND user_sortie_date>NOW() AND sacoche_officiel_archive.officiel_archive_id IS NULL '
            . 'GROUP BY sacoche_periode.periode_id, sacoche_groupe.groupe_id '
            . 'HAVING nombre > 5 ';
    return DB::queryTab(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL , $DB_VAR);
  }
  if($objet_type=='releve')
  {
    $DB_SQL = 'SELECT sacoche_periode.periode_nom, groupe_nom, COUNT(*) AS nombre '
            . 'FROM sacoche_jointure_groupe_periode '
            . 'LEFT JOIN sacoche_groupe USING(groupe_id) '
            . 'LEFT JOIN sacoche_user ON sacoche_jointure_groupe_periode.groupe_id = sacoche_user.eleve_classe_id '
            . 'LEFT JOIN sacoche_periode USING(periode_id) '
            . 'LEFT JOIN sacoche_officiel_archive ON sacoche_user.user_id = sacoche_officiel_archive.user_id '
            . ' AND structure_uai=:structure_uai AND annee_scolaire=:annee_scolaire AND archive_type="sacoche" AND archive_ref="releve" '
            . ' AND sacoche_periode.periode_id = sacoche_officiel_archive.periode_id '
            . 'WHERE officiel_releve != "" AND officiel_releve != "1vide" AND user_sortie_date>NOW() AND sacoche_officiel_archive.officiel_archive_id IS NULL '
            . 'GROUP BY sacoche_periode.periode_id, sacoche_groupe.groupe_id '
            . 'HAVING nombre > 5 ';
    return DB::queryTab(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL , $DB_VAR);
  }
}

/**
 * compter_matieres_etabl
 *
 * @param void
 * @return int
 */
public static function DB_compter_matieres_etabl()
{
  $DB_SQL = 'SELECT COUNT(*) AS nombre '
          . 'FROM sacoche_matiere '
          . 'WHERE matiere_active=1 ';
  return DB::queryOne(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL);
}

/**
 * compter_niveaux_etabl
 *
 * @param bool $with_specifiques
 * @return int
 */
public static function DB_compter_niveaux_etabl($with_specifiques)
{
  $join_niveau_famille  = ($with_specifiques) ? '' : 'LEFT JOIN sacoche_niveau_famille USING (niveau_famille_id) ' ;
  $where_niveau_famille = ($with_specifiques) ? '' : 'AND niveau_famille_categorie=3 ' ;
  $DB_SQL = 'SELECT COUNT(*) AS nombre '
          . 'FROM sacoche_niveau '
          . $join_niveau_famille
          . 'WHERE niveau_actif=1 '.$where_niveau_famille;
  return DB::queryOne(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL);
}

/**
 * compter_users_suivant_statut
 *
 * @param string|array   $profil_type   'eleve' / 'professeur' / 'directeur' / 'administrateur' / ou par exemple array('eleve','professeur','directeur')
 * @return array   [0]=>nb actuels , [1]=>nb anciens
 */
public static function DB_compter_users_suivant_statut($profil_type)
{
  $DB_VAR = array();
  if(is_string($profil_type))
  {
    $where = 'user_profil_type=:profil_type ';
    $DB_VAR[':profil_type'] = $profil_type;
  }
  else
  {
    foreach($profil_type as $key => $val)
    {
      $DB_VAR[':profil_type'.$key] = $val;
      $profil_type[$key] = ':profil_type'.$key;
    }
    $where = 'user_profil_type IN('.implode(',',$profil_type).') ';
  }
  $DB_SQL = 'SELECT (user_sortie_date>NOW()) AS statut, COUNT(*) AS nombre '
          . 'FROM sacoche_user '
          . 'LEFT JOIN sacoche_user_profil USING (user_profil_sigle) '
          . 'WHERE '.$where
          . 'GROUP BY statut';
  $DB_TAB = DB::queryTab(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL , $DB_VAR , TRUE , TRUE);
  $nb_actuels = ( (!empty($DB_TAB)) && (isset($DB_TAB[1])) ) ? $DB_TAB[1]['nombre'] : 0 ;
  $nb_anciens = ( (!empty($DB_TAB)) && (isset($DB_TAB[0])) ) ? $DB_TAB[0]['nombre'] : 0 ;
  return array($nb_actuels,$nb_anciens);
}

/**
 * Recherche si un identifiant d’utilisateur est déjà pris (sauf éventuellement l’utilisateur concerné)
 *
 * @param string $champ_nom      sans le préfixe 'user_' : login | sconet_id | sconet_elenoet | reference | id_ent | id_gepi
 * @param string $champ_valeur   la valeur testée
 * @param int    $user_id        inutile si recherche pour un ajout, mais id à éviter si recherche pour une modification
 * @param string $profil_type    si transmis alors recherche parmi les utilisateurs de même type de profil (sconet_id|sconet_elenoet|reference), sinon alors parmi tous les utilisateurs de l’établissement (login|id_ent|id_gepi)
 * @return null|bool             NULL si pas trouvé, FALSE si trouvé mais identique à $user_id transmis, TRUE si trouvé ($user_id non transmis ou différent), l’id trouvé dans le cas exceptionnel d’un développeur pour le compte administrateur "superviseur"
 */
public static function DB_tester_utilisateur_identifiant( $champ_nom , $champ_valeur , $user_id=NULL , $profil_type=NULL )
{
  $join_profil  = ($profil_type) ? 'LEFT JOIN sacoche_user_profil USING (user_profil_sigle) ' : '' ;
  $where_profil = ($profil_type) ? 'AND user_profil_type=:profil_type ' : '' ;
  $DB_SQL = 'SELECT user_id '
          . 'FROM sacoche_user '
          . $join_profil
          . 'WHERE user_'.$champ_nom.'=:champ_valeur '.$where_profil
          . 'LIMIT 1 '; // utile
  $DB_VAR = array(
    ':champ_valeur' => $champ_valeur,
    ':profil_type'  => $profil_type,
    ':user_id'      => $user_id,
  );
  $find_user_id = DB::queryOne(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL , $DB_VAR);
  if($find_user_id!==NULL)
  {
    if( $_SESSION['USER_PROFIL_TYPE'] != 'developpeur' )
    {
      $find_user_id = ( $find_user_id && ($user_id!=$find_user_id) ) ? TRUE : FALSE ;
    }
  }
  return $find_user_id;
}

/**
 * tester_prof_remplacement
 *
 * @param int $prof_absent_id
 * @param int $prof_remplacant_id
 * @return bool
 */
public static function DB_tester_prof_remplacement($prof_absent_id, $prof_remplacant_id)
{
  $DB_SQL = 'SELECT 1 '
          . 'FROM sacoche_jointure_prof_remplacement '
          . 'WHERE prof_absent_id=:prof_absent_id AND prof_remplacant_id=:prof_remplacant_id '
          . 'LIMIT 1 ';
  $DB_VAR = array(
    ':prof_absent_id'     => $prof_absent_id,
    ':prof_remplacant_id' => $prof_remplacant_id,
  );
  return (bool)DB::queryOne(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL , $DB_VAR);
}

/**
 * rechercher_login_disponible (parmi tout le personnel de l’établissement)
 *
 * @param string $login_pris
 * @return string
 */
public static function DB_rechercher_login_disponible($login_pris)
{
  $nb_chiffres = max(1 , LOGIN_LONGUEUR_MAX-mb_strlen($login_pris) );
  do
  {
    $login_tronque = mb_substr($login_pris,0,LOGIN_LONGUEUR_MAX-$nb_chiffres);
    $DB_SQL = 'SELECT user_login '
            . 'FROM sacoche_user '
            . 'WHERE user_login LIKE :user_login';
    $DB_VAR = array( ':user_login' => $login_tronque.'%' );
    $DB_COL = DB::queryCol(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL , $DB_VAR);
    $max_result = pow(10,$nb_chiffres);
    $nb_chiffres += 1;
  }
  while (count($DB_COL)>=$max_result);
  $login_nombre = 1;
  do
  {
    $login_disponible = $login_tronque.$login_nombre;
    $login_nombre++;
  }
  while (in_array($login_disponible,$DB_COL));
  return $login_disponible ;
}

/**
 * ajouter_saisies
 *
 * @param array   [ $eleve_id , $item_id , $saisie_note , $saisie_info , $saisie_date , $saisie_visible_date , $devoir_id ]
 * @param int     nombre d’enregistrements à effectuer
 * @return void
 */
public static function DB_ajouter_saisies( $tab_saisies , $nb_saisies )
{
  $TAB_SQL = array();
  $paquet = 1000;
  foreach($tab_saisies as $key => $tab)
  {
    $num = $key+1;
    list( $eleve_id , $item_id , $saisie_note , $saisie_info , $saisie_date , $saisie_visible_date , $devoir_id ) = $tab;
    $sql_visible_date = is_null($saisie_visible_date) ? 'NULL' : '"'.$saisie_visible_date.'"' ;
    $TAB_SQL[] = '(0,'.$eleve_id.','.$devoir_id.','.$item_id.',"'.$saisie_date.'","'.$saisie_note.'","'.str_replace('"','\"',$saisie_info).'",'.$sql_visible_date.')';
    if( ($num%$paquet==0) || ($num==$nb_saisies) )
    {
      DB::query(SACOCHE_STRUCTURE_BD_NAME , 'INSERT INTO sacoche_saisie(prof_id, eleve_id, devoir_id, item_id, saisie_date, saisie_note, saisie_info, saisie_visible_date) VALUES '.implode(',', $TAB_SQL));
      $TAB_SQL = array();
    }
  }
}

/**
 * ajouter_adresse_parent
 *
 * @param int    $parent_id
 * @param array  $tab_adresse
 * @return void
 */
public static function DB_ajouter_adresse_parent($parent_id,$tab_adresse)
{
  $DB_SQL = 'INSERT INTO sacoche_parent_adresse(parent_id,adresse_ligne1,adresse_ligne2,adresse_ligne3,adresse_ligne4,adresse_postal_code,adresse_postal_libelle,adresse_pays_nom) '
          . 'VALUES(                           :parent_id,       :ligne1,       :ligne2,       :ligne3,       :ligne4,       :postal_code,       :postal_libelle,       :pays_nom)';
  $DB_VAR = array(
    ':parent_id'      => $parent_id,
    ':ligne1'         => $tab_adresse[0],
    ':ligne2'         => $tab_adresse[1],
    ':ligne3'         => $tab_adresse[2],
    ':ligne4'         => $tab_adresse[3],
    ':postal_code'    => $tab_adresse[4],
    ':postal_libelle' => $tab_adresse[5],
    ':pays_nom'       => $tab_adresse[6],
  );
  DB::query(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL , $DB_VAR);
}

/**
 * ajouter_jointure_parent_eleve
 *
 * @param int    $parent_id
 * @param int    $eleve_id
 * @param int    $resp_legal_num
 * @return void
 */
public static function DB_ajouter_jointure_parent_eleve($parent_id,$eleve_id,$resp_legal_num)
{
  $DB_SQL = 'INSERT INTO sacoche_jointure_parent_eleve(parent_id, eleve_id, resp_legal_num) '
          . 'VALUES(                                  :parent_id,:eleve_id,:resp_legal_num)';
  $DB_VAR = array(
    ':parent_id'      => $parent_id,
    ':eleve_id'       => $eleve_id,
    ':resp_legal_num' => $resp_legal_num,
  );
  DB::query(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL , $DB_VAR);
}

/**
 * ajouter_jointure_prof_remplacement
 *
 * @param int $prof_absent_id
 * @param int $prof_remplacant_id
 * @return void
 */
public static function DB_ajouter_jointure_prof_remplacement($prof_absent_id, $prof_remplacant_id, $is_classes, $is_groupes, $is_matieres)
{
  $DB_SQL = 'INSERT INTO sacoche_jointure_prof_remplacement(prof_absent_id, prof_remplacant_id) '
          . 'VALUES(                                       :prof_absent_id,:prof_remplacant_id)';
  $DB_VAR = array(
    ':prof_absent_id'     => $prof_absent_id,
    ':prof_remplacant_id' => $prof_remplacant_id,
  );
  DB::query(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL , $DB_VAR);
  // On associe aussi le remplaçant aux devoirs déjà effectués par le prof absent
  $DB_SQL = 'SELECT devoir_id '
          . 'FROM sacoche_devoir '
          . 'WHERE proprio_id=:prof_absent_id ';
  $tab_devoir_prof_absent = DB::queryCol(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL , $DB_VAR);
  if(!empty($tab_devoir_prof_absent))
  {
    $DB_SQL = 'SELECT devoir_id '
            . 'FROM sacoche_jointure_devoir_prof '
            . 'WHERE prof_id=:prof_remplacant_id ';
    $tab_devoir_remplacant = DB::queryCol(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL , $DB_VAR);
    $tab_devoir_a_joindre = array_diff( $tab_devoir_prof_absent , $tab_devoir_remplacant);
   if(!empty($tab_devoir_a_joindre))
    {
      $DB_SQL = 'INSERT INTO sacoche_jointure_devoir_prof(devoir_id,prof_id,jointure_droit) '
              . 'VALUES(:devoir_id, :prof_remplacant_id, :droit) ';
      $DB_VAR[':droit'] = 'voir';
      foreach($tab_devoir_a_joindre as $devoir_id)
      {
        $DB_VAR[':devoir_id'] = $devoir_id;
        DB::query(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL , $DB_VAR);
      }
    }
  }
  // Association aux classes
  if($is_classes)
  {
    $DB_SQL = 'SELECT groupe_id '
            . 'FROM sacoche_user '
            . 'LEFT JOIN sacoche_jointure_user_groupe USING (user_id) '
            . 'LEFT JOIN sacoche_groupe USING (groupe_id) '
            . 'WHERE user_id=:prof_absent_id AND groupe_type="classe" ';
    $tab_classe_prof_absent = DB::queryCol(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL , $DB_VAR);
    if(!empty($tab_classe_prof_absent))
    {
      $DB_SQL = 'INSERT INTO sacoche_jointure_user_groupe(user_id, groupe_id, jointure_pp) '
              . 'VALUES(:prof_remplacant_id, :groupe_id, 0) '
              . 'ON DUPLICATE KEY UPDATE jointure_pp=jointure_pp ';
      foreach($tab_classe_prof_absent as $classe_id)
      {
        $DB_VAR[':groupe_id'] = $classe_id;
        DB::query(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL , $DB_VAR);
      }
    }
  }
  // Association aux groupes
  if($is_groupes)
  {
    $DB_SQL = 'SELECT groupe_id '
            . 'FROM sacoche_user '
            . 'LEFT JOIN sacoche_jointure_user_groupe USING (user_id) '
            . 'LEFT JOIN sacoche_groupe USING (groupe_id) '
            . 'WHERE user_id=:prof_absent_id AND groupe_type="groupe" ';
    $tab_groupe_prof_absent = DB::queryCol(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL , $DB_VAR);
    if(!empty($tab_groupe_prof_absent))
    {
      $DB_SQL = 'INSERT INTO sacoche_jointure_user_groupe(user_id, groupe_id, jointure_pp) '
              . 'VALUES(:prof_remplacant_id, :groupe_id, 0) '
              . 'ON DUPLICATE KEY UPDATE jointure_pp=jointure_pp ';
      foreach($tab_groupe_prof_absent as $groupe_id)
      {
        $DB_VAR[':groupe_id'] = $groupe_id;
        DB::query(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL , $DB_VAR);
      }
    }
  }
  // Association aux matières
  if($is_matieres)
  {
    $DB_SQL = 'SELECT matiere_id '
            . 'FROM sacoche_user '
            . 'LEFT JOIN sacoche_jointure_user_matiere USING (user_id) '
            . 'WHERE user_id=:prof_absent_id ';
    $tab_matiere_prof_absent = DB::queryCol(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL , $DB_VAR);
    if(!empty($tab_matiere_prof_absent))
    {
      $DB_SQL = 'INSERT INTO sacoche_jointure_user_matiere(user_id, matiere_id, jointure_coord) '
              . 'VALUES(:prof_remplacant_id, :matiere_id, 0) '
              . 'ON DUPLICATE KEY UPDATE jointure_coord=jointure_coord ';
      foreach($tab_matiere_prof_absent as $matiere_id)
      {
        $DB_VAR[':matiere_id'] = $matiere_id;
        DB::query(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL , $DB_VAR);
      }
    }
  }
}

/**
 * remplacer_structure_origine
 *
 * @param string $uai
 * @param string $denomination
 * @param string $localisation
 * @param string $courriel
 * @return void
 */
public static function DB_remplacer_structure_origine( $uai , $denomination , $localisation , $courriel )
{
  // INSERT ON DUPLICATE KEY UPDATE est plus performant que REPLACE et mieux par rapport aux id autoincrémentés ou aux contraintes sur les clefs étrangères
  // @see http://stackoverflow.com/questions/9168928/what-are-practical-differences-between-replace-and-insert-on-duplicate-ke
  $DB_SQL = 'INSERT INTO sacoche_structure_origine(structure_uai, structure_denomination, structure_localisation, structure_courriel) '
          . 'VALUES(                              :structure_uai,:structure_denomination,:structure_localisation,:structure_courriel) '
          . 'ON DUPLICATE KEY UPDATE '
          . 'structure_denomination=:structure_denomination, structure_localisation=:structure_localisation, structure_courriel=:structure_courriel ';
  $DB_VAR = array(
    ':structure_uai'          => $uai,
    ':structure_denomination' => $denomination,
    ':structure_localisation' => $localisation,
    ':structure_courriel'     => $courriel,
  );
  DB::query(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL , $DB_VAR);
}

/**
 * Dupliquer pour tous les utilisateurs une série d’identifiants vers un autre champ (exemples : id_gepi=id_ent | id_gepi=login | id_ent=id_gepi | id_ent=login )
 *
 * @param string $champ_depart
 * @param string $champ_arrive
 * @return void
 */
public static function DB_recopier_identifiants($champ_depart,$champ_arrive)
{
  $DB_SQL = 'UPDATE sacoche_user '
          . 'SET user_'.$champ_arrive.'=user_'.$champ_depart.' '
          . 'WHERE user_'.$champ_depart.'!="" ';
  DB::query(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL);
}

/**
 * modifier_adresse_parent
 *
 * @param string $parent_id
 * @param array  $tab_adresse
 * @return int
 */
public static function DB_modifier_adresse_parent($parent_id,$tab_adresse)
{
  $DB_SQL = 'UPDATE sacoche_parent_adresse '
          . 'SET adresse_ligne1=:ligne1, adresse_ligne2=:ligne2, adresse_ligne3=:ligne3, adresse_ligne4=:ligne4, adresse_postal_code=:postal_code, adresse_postal_libelle=:postal_libelle, adresse_pays_nom=:pays_nom '
          . 'WHERE parent_id=:parent_id ';
  $DB_VAR = array(
    ':parent_id'      => $parent_id,
    ':ligne1'         => $tab_adresse[0],
    ':ligne2'         => $tab_adresse[1],
    ':ligne3'         => $tab_adresse[2],
    ':ligne4'         => $tab_adresse[3],
    ':postal_code'    => $tab_adresse[4],
    ':postal_libelle' => $tab_adresse[5],
    ':pays_nom'       => $tab_adresse[6],
  );
  DB::query(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL , $DB_VAR);
}

/**
 * Modifier un ou plusieurs paramètres d’un utilisateur
 *
 * - Certains champ ("user_langue", "user_daltonisme", "user_connexion_date", "user_param_accueil", "user_param_menu", "user_param_favori", "user_form_options") ne sont ici forcés que via la fusion de comptes élèves.
 * - On peut envisager une modification de "profil_sigle" entre personnels.
 * - La mise à jour de la table [sacoche_user_switch] s’effectue lors de l’initialisation annuelle.
 *
 * @param int     $user_id
 * @param array   de la forme ':champ' => $val (voir ci-dessous)
 * @return void
 */
public static function DB_modifier_user($user_id,$DB_VAR)
{
  $tab_set = array();
  foreach($DB_VAR as $key => $val)
  {
    switch($key)
    {
      case ':sconet_id'     : $tab_set[] = 'user_sconet_id='     .$key; break;
      case ':sconet_num'    : $tab_set[] = 'user_sconet_elenoet='.$key; break;
      case ':reference'     : $tab_set[] = 'user_reference='     .$key; break;
      case ':profil_sigle'  : $tab_set[] = 'user_profil_sigle='  .$key; break;
      case ':genre'         : $tab_set[] = 'user_genre='         .$key; break;
      case ':nom'           : $tab_set[] = 'user_nom='           .$key; break;
      case ':prenom'        : $tab_set[] = 'user_prenom='        .$key; break;
      case ':birth_date'    : $tab_set[] = 'user_naissance_date='.$key; break;
      case ':courriel'      : $tab_set[] = 'user_email='         .$key; break;
      case ':email_origine' : $tab_set[] = 'user_email_origine=' .$key; break;
      case ':login'         : $tab_set[] = 'user_login='         .$key; break;
      case ':password'      : $tab_set[] = 'user_password='      .$key; break;
      case ':langue'        : $tab_set[] = 'user_langue='        .$key; break;
      case ':daltonisme'    : $tab_set[] = 'user_daltonisme='    .$key; break;
      case ':connexion_date': $tab_set[] = 'user_connexion_date='.$key; break;
      case ':entree_date'   : $tab_set[] = 'user_entree_date='   .$key; break;
      case ':sortie_date'   : $tab_set[] = 'user_sortie_date='   .$key; break;
      case ':classe'        : $tab_set[] = 'eleve_classe_id='    .$key; break;
      case ':elv_classe'    : $tab_set[] = 'eleve_classe_id='    .$key; break;
      case ':lv1'           : $tab_set[] = 'eleve_lv1='          .$key; break;
      case ':lv2'           : $tab_set[] = 'eleve_lv2='          .$key; break;
      case ':dnb_mef_id'    : $tab_set[] = 'eleve_dnb_mef_id='   .$key; break;
      case ':uai_origine'   : $tab_set[] = 'eleve_uai_origine='  .$key; break;
      case ':id_ent'        : $tab_set[] = 'user_id_ent='        .$key; break;
      case ':id_gepi'       : $tab_set[] = 'user_id_gepi='       .$key; break;
      case ':param_accueil' : $tab_set[] = 'user_param_accueil=' .$key; break;
      case ':param_menu'    : $tab_set[] = 'user_param_menu='    .$key; break;
      case ':param_favori'  : $tab_set[] = 'user_param_favori='  .$key; break;
      case ':form_options'  : $tab_set[] = 'user_form_options='  .$key; break;
    }
  }
  if(count($tab_set))
  {
    $DB_SQL = 'UPDATE sacoche_user '
            . 'SET '.implode(', ',$tab_set).' '
            . 'WHERE user_id=:user_id ';
    $DB_VAR[':user_id'] = $user_id;
    DB::query(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL , $DB_VAR);
  }
  else
  {
    Outil::ajouter_log_PHP( 'Erreur DB_modifier_user()' /*log_objet*/ , serialize($DB_VAR) /*log_contenu*/ , __FILE__ /*log_fichier*/ , __LINE__ /*log_ligne*/ , TRUE /*only_sesamath*/ );
  }
}

/**
 * Rendre une liste de comptes actifs ou inactifs en changeant la date de sortie
 *
 * La mise à jour de la table [sacoche_user_switch] s’effectue lors de l’initialisation annuelle.
 *
 * @param array   $tab_user_id
 * @param bool    $statut
 * @return void
 */
public static function DB_modifier_users_statut( $tab_user_id , $statut )
{
  $date = ($statut) ? 'DEFAULT' : 'NOW()' ;
  $DB_SQL = 'UPDATE sacoche_user '
          . 'SET user_sortie_date='.$date.' '
          . 'WHERE user_id IN('.implode(',',$tab_user_id).') ';
  DB::query(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL);
}

/**
 * Modifier une langue pour une liste d’élèves
 *
 * @param string $objet   lv1 | lv2
 * @param string $listing_user_id
 * @param int    $langue
 * @return void
 */
public static function DB_modifier_user_langue( $objet , $listing_user_id , $langue )
{
  $DB_SQL = 'UPDATE sacoche_user '
          . 'SET eleve_'.$objet.'=:langue '
          . 'WHERE user_id IN('.$listing_user_id.') ';
  $DB_VAR = array(':langue'=>$langue);
  DB::query(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL , $DB_VAR);
}

/**
 * Modifier une formation 3e pour une liste d’élèves
 *
 * @param string $listing_user_id
 * @param int    $dnb_mef_id
 * @return void
 */
public static function DB_modifier_user_dnb_mef( $listing_user_id , $dnb_mef_id )
{
  $DB_SQL = 'UPDATE sacoche_user '
          . 'SET eleve_dnb_mef_id=:dnb_mef_id '
          . 'WHERE user_id IN('.$listing_user_id.') ';
  $DB_VAR = array(':dnb_mef_id'=>$dnb_mef_id);
  DB::query(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL , $DB_VAR);
}

/**
 * modifier_bilan_officiel
 *
 * @param int      $groupe_id    id du groupe (en fait, obligatoirement une classe)
 * @param int      $periode_id   id de la période
 * @param string   $champ        officiel_releve | officiel_bulletin
 * @param string   $etat         nouvel état
 * @return int     0 ou 1 si modifié
 */
public static function DB_modifier_bilan_officiel( $groupe_id , $periode_id , $champ , $etat )
{
  $DB_SQL = 'UPDATE sacoche_jointure_groupe_periode '
          . 'SET '.$champ.'=:etat '
          . 'WHERE groupe_id=:groupe_id AND periode_id=:periode_id ';
  $DB_VAR = array(
    ':groupe_id'  => $groupe_id,
    ':periode_id' => $periode_id,
    ':etat'       => $etat,
  );
  DB::query(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL , $DB_VAR);
  return DB::rowCount(SACOCHE_STRUCTURE_BD_NAME);
}

/**
 * modifier_profil_parametre
 *
 * @param string   $profil_sigle
 * @param string   $champ
 * @param mixed    $valeur
 * @return void
 */
public static function DB_modifier_profil_parametre( $profil_sigle , $champ , $valeur )
{
  $where = ($profil_sigle!='ALL') ? 'user_profil_sigle=:profil_sigle ' : 'user_profil_structure=1 ' ;
  $DB_SQL = 'UPDATE sacoche_user_profil '
          . 'SET '.$champ.'=:valeur '
          . 'WHERE '.$where;
  $DB_VAR = array(
    ':profil_sigle' => $profil_sigle,
    ':valeur'       => $valeur,
  );
  DB::query(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL , $DB_VAR);
}

/**
 * Lister les évaluations pluriannuelles, en convertir le groupe pour une sélection d’élèves, en modifier les dates, retourner les devoirs et les groupes concernés (à ne pas supprimer)
 *
 * @param void
 * @return array(tab_devoir_id_to_keep,tab_groupe_id_to_keep)
 */
public static function DB_basculer_eval_pluriannuelles()
{
  $DB_SQL = 'SELECT devoir_id, proprio_id, devoir_eleves_ordre, groupe_id, groupe_type '
          . 'FROM sacoche_devoir '
          . 'LEFT JOIN sacoche_groupe USING (groupe_id) '
          . 'WHERE devoir_pluriannuel = 1 ';
  $DB_TAB = DB::queryTab(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL);
  if(empty($DB_TAB))
  {
    return array( array() , array() );
  }
  $tab_devoir_id = array();
  $tab_groupe_id = array();
  foreach($DB_TAB as $DB_ROW)
  {
    $devoir_id = (int)$DB_ROW['devoir_id'];
    $groupe_id = (int)$DB_ROW['groupe_id'];
    if( $DB_ROW['groupe_type'] != 'eval' )
    {
      // Convertir cette évaluation en une évaluation sur une sélection d’élèves
      $tab_eleves = array();
      $DB_TAB_USER = DB_STRUCTURE_COMMUN::DB_lister_users_regroupement( 'eleve' /*profil_type*/ , 1 /*statut*/ , $DB_ROW['groupe_type'] , $groupe_id , 'nom' /*eleves_ordre*/ );
      foreach($DB_TAB_USER as $DB_ROW_USER)
      {
        $tab_eleves[] = (int)$DB_ROW_USER['user_id'];
      }
      // Créer un nouveau groupe de type "eval", utilisé uniquement pour cette évaluation (c’est transparent pour le professeur) ; y associe automatiquement le prof, en responsable du groupe
      $prof_id = (int)$DB_ROW['proprio_id'];
      $groupe_id = DB_STRUCTURE_REGROUPEMENT::DB_ajouter_groupe_par_prof( $prof_id , 'eval' /*groupe_type*/ , '' /*groupe_nom*/ , 0 /*niveau_id*/ );
      // Y affecter tous les élèves
      DB_STRUCTURE_PROFESSEUR::DB_modifier_liaison_devoir_eleve( $prof_id , $devoir_id , $groupe_id , $tab_eleves , 'creer' );
    }
    $DB_VAR = array(
      ':devoir_id' => $devoir_id,
      ':groupe_id' => $groupe_id,
    );
    // Modifier le devoir
    $set_groupe   = ( $DB_ROW['groupe_type'] == 'eval' )     ? '' : ', groupe_id = :groupe_id ' ;
    $set_ordre    = ( !(int)$DB_ROW['devoir_eleves_ordre'] ) ? '' : ', devoir_eleves_ordre = "nom" ' ; // pour éviter une référence à un plan de classe supprimé
    $DB_SQL = 'UPDATE sacoche_devoir '
            . 'SET devoir_date = DATE_ADD(devoir_date, INTERVAL 1 YEAR), '
            .   'devoir_visible_date = DATE_ADD(devoir_visible_date, INTERVAL 1 YEAR), ' // si NULL alors reste NULL
            .   'devoir_saisie_visible_date = DATE_ADD(devoir_saisie_visible_date, INTERVAL 1 YEAR), ' // si NULL alors reste NULL
            .   'devoir_autoeval_date = DATE_ADD(devoir_autoeval_date, INTERVAL 1 YEAR) ' // si NULL alors reste NULL
            .   $set_groupe.$set_ordre
            . 'WHERE devoir_id = :devoir_id ';
    DB::query(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL , $DB_VAR);
    // Modifier aussi les dates des saisies
    $DB_SQL = 'UPDATE sacoche_saisie '
            . 'SET saisie_date = DATE_ADD(saisie_date, INTERVAL 1 YEAR), '
            .   'saisie_visible_date = DATE_ADD(saisie_visible_date, INTERVAL 1 YEAR) ' // si NULL alors reste NULL
            . 'WHERE devoir_id=:devoir_id ';
    DB::query(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL , $DB_VAR);
    // Retenir les identifiants concernés
    $tab_devoir_id[$devoir_id] = $devoir_id;
    $tab_groupe_id[$groupe_id] = $groupe_id;
  }
  return array( $tab_devoir_id , $tab_groupe_id );
}

/**
 * Supprimer les référentiels dépendant d’une matière ou d’un niveau
 *
 * Le ménage dans sacoche_livret_jointure_referentiel est un peu pénible ici, il est effectué ailleurs.
 *
 * @param string $champ_nom   'matiere_id' | 'niveau_id'
 * @param int    $champ_val   $matiere_id  | $niveau_id
 * @return void
 */
public static function DB_supprimer_referentiels( $champ_nom , $champ_val )
{
  $DB_SQL = 'DELETE sacoche_referentiel, sacoche_referentiel_domaine, sacoche_referentiel_theme, sacoche_referentiel_item, sacoche_jointure_devoir_item, sacoche_saisie, sacoche_demande '
          . 'FROM sacoche_referentiel '
          . 'LEFT JOIN sacoche_referentiel_domaine USING (matiere_id,niveau_id) '
          . 'LEFT JOIN sacoche_referentiel_theme USING (domaine_id) '
          . 'LEFT JOIN sacoche_referentiel_item USING (theme_id) '
          . 'LEFT JOIN sacoche_jointure_devoir_item USING (item_id) '
          . 'LEFT JOIN sacoche_saisie USING (item_id) '
          . 'LEFT JOIN sacoche_demande USING (matiere_id,item_id) '
          . 'WHERE sacoche_referentiel.'.$champ_nom.'=:champ_val ';
  $DB_VAR = array(':champ_val'=>$champ_val);
  DB::query(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL , $DB_VAR);
}

/**
 * supprimer_saisies_eval_diagnostiques
 *
 * @param void
 * @return void
 */
public static function DB_supprimer_saisies_eval_diagnostiques()
{
  $DB_SQL = 'DELETE sacoche_saisie '
          . 'FROM sacoche_saisie '
          . 'LEFT JOIN sacoche_devoir USING (devoir_id) '
          . 'WHERE devoir_diagnostic = 1 ';
  DB::query(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL);
}

/**
 * Supprimer les devoirs sans les saisies associées (utilisé uniquement dans le cadre d’un nettoyage annuel).
 * Les groupes de types 'besoin' et 'eval' sont supprimés dans un second temps.
 *
 * @param string   listing_devoir_id_to_keep
 * @return void
 */
public static function DB_supprimer_devoirs_sans_saisies($listing_devoir_id_to_keep)
{
  $commande = ($listing_devoir_id_to_keep) ? 'DELETE FROM ' : 'TRUNCATE ' ;
  $where    = ($listing_devoir_id_to_keep) ? ' WHERE devoir_id NOT IN ('.$listing_devoir_id_to_keep.')' : '' ;
  $tab_tables = array( 'sacoche_jointure_devoir_item' , 'sacoche_jointure_devoir_prof' , 'sacoche_jointure_devoir_eleve' );
  foreach( $tab_tables as $table )
  {
    $DB_SQL = $commande.$table.$where;
    DB::query(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL);
  }
  // Pas de TRUNCATE pour la table sacoche_devoir car il ne faut pas initialiser l’auto-incrément
  $DB_SQL = 'DELETE FROM sacoche_devoir'.$where;
  DB::query(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL);
}

/**
 * Supprimer les reliquats de marqueurs d’évaluations dans les devoirs (utilisé uniquement dans le cadre d’un nettoyage annuel)
 *
 * @param void
 * @return void
 */
public static function DB_supprimer_saisies_marqueurs()
{
  $DB_SQL = 'DELETE FROM sacoche_saisie '
          . 'WHERE saisie_note="PA" ';
  DB::query(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL);
}

/**
 * Supprimer les reliquats de notes "autres" saisies après la sortie d’un élève
 *
 * @param int    $user_id
 * @param string $sortie_date_sql
 * @return void
 */
public static function DB_supprimer_user_saisies_absences_apres_sortie( $user_id , $sortie_date_sql )
{
  $DB_SQL = 'DELETE FROM sacoche_saisie '
          . 'WHERE eleve_id=:eleve_id AND saisie_date>=:saisie_date AND saisie_note IN ("","AB","DI","NE","NF","NN","NR","PA") ';
  $DB_VAR = array(
    ':eleve_id'    =>$user_id,
    ':saisie_date' =>$sortie_date_sql,
  );
  DB::query(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL , $DB_VAR);
}

/**
 * supprimer_bilans_officiels
 *
 * @param void
 * @return void
 */
public static function DB_supprimer_bilans_officiels()
{
  DB::query(SACOCHE_STRUCTURE_BD_NAME , 'TRUNCATE sacoche_officiel_saisie');
  DB::query(SACOCHE_STRUCTURE_BD_NAME , 'TRUNCATE sacoche_officiel_assiduite');
  DB::query(SACOCHE_STRUCTURE_BD_NAME , 'TRUNCATE sacoche_officiel_jointure_decision');
}

/**
 * supprimer_officiel_archive_image
 *
 * @param void
 * @return void
 */
public static function DB_supprimer_officiel_archive_image()
{
  $DB_SQL = 'DELETE sacoche_officiel_archive_image '
          . 'FROM sacoche_officiel_archive_image '
          . 'LEFT JOIN sacoche_officiel_archive AS t1 ON sacoche_officiel_archive_image.archive_image_md5=t1.archive_md5_image1 '
          . 'LEFT JOIN sacoche_officiel_archive AS t2 ON sacoche_officiel_archive_image.archive_image_md5=t2.archive_md5_image2 '
          . 'LEFT JOIN sacoche_officiel_archive AS t3 ON sacoche_officiel_archive_image.archive_image_md5=t3.archive_md5_image3 '
          . 'LEFT JOIN sacoche_officiel_archive AS t4 ON sacoche_officiel_archive_image.archive_image_md5=t4.archive_md5_image4 '
          . 'WHERE t1.archive_md5_image1 IS NULL AND t2.archive_md5_image2 IS NULL AND t3.archive_md5_image3 IS NULL AND t4.archive_md5_image4 IS NULL ';
  DB::query(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL);
}

/**
 * supprimer_saisies
 *
 * @param void
 * @return void
 */
public static function DB_supprimer_saisies()
{
  DB::query(SACOCHE_STRUCTURE_BD_NAME , 'TRUNCATE sacoche_saisie');
}

/**
 * Supprimer toutes les demandes d’évaluations résiduelles dans l’établissement
 *
 * @param void
 * @return void
 */
public static function DB_supprimer_demandes_evaluation()
{
  DB::query(SACOCHE_STRUCTURE_BD_NAME , 'TRUNCATE sacoche_demande');
}

/**
 * Supprimer tous les plans de classes
 *
 * @param void
 * @return void
 */
public static function DB_supprimer_plans_classes()
{
  DB::query(SACOCHE_STRUCTURE_BD_NAME , 'TRUNCATE sacoche_plan_classe');
  DB::query(SACOCHE_STRUCTURE_BD_NAME , 'TRUNCATE sacoche_jointure_plan_eleve');
}

/**
 * supprimer_jointures_parents_for_eleves
 *
 * @param bool|string   $listing_eleve_id   id des élèves séparés par des virgules
 * @return void
 */
public static function DB_supprimer_jointures_parents_for_eleves($listing_eleve_id)
{
  $DB_SQL = 'DELETE FROM sacoche_jointure_parent_eleve '
          . 'WHERE eleve_id IN('.$listing_eleve_id.') ';
  DB::query(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL);
}

/**
 * supprimer_jointure_prof_remplacement
 *
 * @param int $prof_absent_id
 * @param int $prof_remplacant_id
 * @return void
 */
public static function DB_supprimer_jointure_prof_remplacement($prof_absent_id, $prof_remplacant_id)
{
  $DB_SQL = 'DELETE FROM sacoche_jointure_prof_remplacement '
          . 'WHERE prof_absent_id=:prof_absent_id AND prof_remplacant_id=:prof_remplacant_id';
  $DB_VAR = array(
    ':prof_absent_id'     => $prof_absent_id,
    ':prof_remplacant_id' => $prof_remplacant_id,
  );
  DB::query(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL , $DB_VAR);
}

/**
 * Supprimer un utilisateur avec tout ce qui en dépend
 *
 * On ne touche pas à [sacoche_acces_historique] pour garder des infos en cas de réclamation.
 * La mise à jour de la table [sacoche_user_switch] s’effectue lors de l’initialisation annuelle.
 * 
 * @param int    $user_id
 * @param string $user_profil_sigle
 * @return void
 */
public static function DB_supprimer_utilisateur( $user_id , $user_profil_sigle )
{
  $user_profil_type = isset($_SESSION['TAB_PROFILS_ADMIN']) ? $_SESSION['TAB_PROFILS_ADMIN']['TYPE'][$user_profil_sigle] : 'administrateur' ;
  $DB_VAR = array(':user_id'=>$user_id);
  $DB_SQL = 'DELETE FROM sacoche_user '
          . 'WHERE user_id=:user_id';
  DB::query(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL , $DB_VAR);
  $DB_SQL = 'DELETE FROM sacoche_jointure_user_abonnement '
          . 'WHERE user_id=:user_id';
  DB::query(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL , $DB_VAR);
  // Concernant sacoche_jointure_message_destinataire, pour les groupes cela est traité plus loin
  $DB_SQL = 'DELETE FROM sacoche_jointure_message_destinataire '
          . 'WHERE destinataire_type="user" AND destinataire_id=:user_id';
  if( $_SESSION['USER_PROFIL_TYPE'] == 'developpeur' )
  {
    // Cette fonction peut être appelée par un profil développeur pour le compte administrateur "superviseur" ; dans ce cas $_SESSION['TAB_PROFILS_ADMIN'] n’est alors pas défini.
    return;
  }
  DB::query(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL , $DB_VAR);
  if($user_profil_type=='eleve')
  {
    $DB_SQL = 'DELETE FROM sacoche_demande '
            . 'WHERE eleve_id=:user_id';
    DB::query(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL , $DB_VAR);
    $DB_SQL = 'DELETE FROM sacoche_jointure_parent_eleve '
            . 'WHERE eleve_id=:user_id';
    DB::query(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL , $DB_VAR);
    $DB_SQL = 'DELETE FROM sacoche_jointure_devoir_eleve '
            . 'WHERE eleve_id=:user_id';
    DB::query(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL , $DB_VAR);
    $DB_SQL = 'DELETE FROM sacoche_crcn_saisie '
            . 'WHERE eleve_id=:user_id';
    DB::query(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL , $DB_VAR);
    $DB_SQL = 'DELETE FROM sacoche_saisie '
            . 'WHERE eleve_id=:user_id';
    DB::query(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL , $DB_VAR);
    $DB_SQL = 'DELETE FROM sacoche_livret_jointure_devoirsfaits_eleve '
            . 'WHERE eleve_id=:user_id';
    DB::query(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL , $DB_VAR);
    $DB_SQL = 'DELETE FROM sacoche_livret_jointure_langcultregion_eleve '
            . 'WHERE eleve_id=:user_id';
    DB::query(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL , $DB_VAR);
    $DB_SQL = 'DELETE FROM sacoche_livret_jointure_modaccomp_eleve '
            . 'WHERE eleve_id=:user_id';
    DB::query(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL , $DB_VAR);
    $DB_SQL = 'DELETE FROM sacoche_livret_export '
            . 'WHERE user_id=:user_id';
    DB::query(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL , $DB_VAR);
    $DB_SQL = 'DELETE sacoche_livret_saisie, sacoche_livret_saisie_memo_detail, sacoche_livret_saisie_jointure_prof '
            . 'FROM sacoche_livret_saisie '
            . 'LEFT JOIN sacoche_livret_saisie_memo_detail USING (livret_saisie_id) '
            . 'LEFT JOIN sacoche_livret_saisie_jointure_prof USING (livret_saisie_id) '
            . 'WHERE cible_id=:user_id AND cible_nature="eleve" ';
    DB::query(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL , $DB_VAR);
    $DB_SQL = 'DELETE FROM sacoche_officiel_saisie '
            . 'WHERE eleve_ou_classe_id=:user_id AND saisie_type="eleve" ';
    DB::query(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL , $DB_VAR);
    $DB_SQL = 'DELETE FROM sacoche_officiel_archive '
            . 'WHERE user_id=:user_id';
    DB::query(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL , $DB_VAR);
    $DB_SQL = 'DELETE FROM sacoche_officiel_assiduite '
            . 'WHERE user_id=:user_id';
    DB::query(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL , $DB_VAR);
    $DB_SQL = 'DELETE FROM sacoche_officiel_jointure_decision '
            . 'WHERE user_id=:user_id';
    DB::query(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL , $DB_VAR);
  }
  if($user_profil_type=='parent')
  {
    $DB_SQL = 'DELETE FROM sacoche_jointure_parent_eleve '
            . 'WHERE parent_id=:user_id';
    DB::query(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL , $DB_VAR);
    $DB_SQL = 'DELETE FROM sacoche_parent_adresse '
            . 'WHERE parent_id=:user_id';
    DB::query(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL , $DB_VAR);
  }
  if($user_profil_type=='professeur')
  {
    $DB_SQL = 'DELETE FROM sacoche_jointure_prof_remplacement '
            . 'WHERE prof_absent_id=:user_id';
    DB::query(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL , $DB_VAR);
    $DB_SQL = 'DELETE FROM sacoche_jointure_prof_remplacement '
            . 'WHERE prof_remplacant_id=:user_id';
    DB::query(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL , $DB_VAR);
    $DB_SQL = 'DELETE FROM sacoche_jointure_user_matiere '
            . 'WHERE user_id=:user_id';
    DB::query(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL , $DB_VAR);
    $DB_SQL = 'DELETE FROM sacoche_livret_jointure_ap_prof '
            . 'WHERE prof_id=:user_id';
    DB::query(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL , $DB_VAR);
    $DB_SQL = 'DELETE FROM sacoche_livret_jointure_epi_prof '
            . 'WHERE prof_id=:user_id';
    DB::query(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL , $DB_VAR);
    $DB_SQL = 'DELETE FROM sacoche_livret_jointure_parcours_prof '
            . 'WHERE prof_id=:user_id';
    DB::query(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL , $DB_VAR);
    $DB_SQL = 'DELETE sacoche_livret_saisie, sacoche_livret_saisie_memo_detail, sacoche_livret_saisie_jointure_prof '
            . 'FROM sacoche_livret_saisie '
            . 'LEFT JOIN sacoche_livret_saisie_memo_detail USING (livret_saisie_id) '
            . 'LEFT JOIN sacoche_livret_saisie_jointure_prof USING (livret_saisie_id) '
            . 'WHERE sacoche_livret_saisie.prof_id=:user_id ';
    DB::query(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL , $DB_VAR);
    $DB_SQL = 'DELETE FROM sacoche_livret_saisie_jointure_prof '
            . 'WHERE prof_id=:user_id ';
    DB::query(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL , $DB_VAR);
    $DB_SQL = 'DELETE sacoche_jointure_devoir_item '
            . 'FROM sacoche_jointure_devoir_item '
            . 'LEFT JOIN sacoche_devoir USING (devoir_id) '
            . 'WHERE proprio_id=:user_id';
    DB::query(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL , $DB_VAR);
    $DB_SQL = 'DELETE sacoche_plan_classe , sacoche_jointure_plan_eleve '
            . 'FROM sacoche_plan_classe '
            . 'LEFT JOIN sacoche_jointure_plan_eleve USING (plan_id) '
            . 'WHERE prof_id=:user_id ';
    DB::query(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL , $DB_VAR);
    // Groupes type "eval" et "besoin", avec jointures devoirs + jointures users + jointures destinataires messages
    $DB_SQL = 'SELECT CONVERT( GROUP_CONCAT(groupe_id SEPARATOR ",") , CHAR) AS listing_groupe_id '
            . 'FROM sacoche_jointure_user_groupe '
            . 'LEFT JOIN sacoche_groupe USING (groupe_id) '
            . 'WHERE user_id=:user_id AND jointure_pp=1 AND groupe_type IN("besoin","eval") ';
    $listing_groupe_id = DB::queryOne(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL , $DB_VAR);
    if($listing_groupe_id)
    {
      $DB_SQL = 'DELETE sacoche_groupe, sacoche_devoir, sacoche_jointure_user_groupe, sacoche_jointure_message_destinataire '
              . 'FROM sacoche_groupe '
              . 'LEFT JOIN sacoche_devoir USING (groupe_id) '
              . 'LEFT JOIN sacoche_jointure_user_groupe USING (groupe_id) '
              . 'LEFT JOIN sacoche_jointure_message_destinataire ON sacoche_groupe.groupe_id=sacoche_jointure_message_destinataire.destinataire_id AND sacoche_groupe.groupe_type=sacoche_jointure_message_destinataire.destinataire_type '
              . 'WHERE proprio_id=:user_id AND groupe_type IN("besoin","eval") ';
      DB::query(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL , $DB_VAR);
    }
    // A priori les devoirs concernés ont été supprimés par la requête précédente, mais au cas où...
    $DB_SQL = 'DELETE FROM sacoche_devoir '
            . 'WHERE proprio_id=:user_id';
    DB::query(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL , $DB_VAR);
    $DB_SQL = 'DELETE FROM sacoche_jointure_devoir_prof '
            . 'WHERE prof_id=:user_id';
    DB::query(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL , $DB_VAR);
    $DB_SQL = 'UPDATE sacoche_saisie '
            . 'SET prof_id=0 '
            . 'WHERE prof_id=:user_id';
    DB::query(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL , $DB_VAR);
    $DB_SQL = 'DELETE FROM sacoche_selection_item '
            . 'WHERE proprio_id=:user_id';
    DB::query(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL , $DB_VAR);
    $DB_SQL = 'DELETE FROM sacoche_jointure_selection_prof '
            . 'WHERE prof_id=:user_id';
    DB::query(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL , $DB_VAR);
    $DB_SQL = 'UPDATE sacoche_demande '
            . 'SET prof_id=0 '
            . 'WHERE prof_id=:user_id';
    DB::query(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL , $DB_VAR);
  }
  if( ($user_profil_type=='eleve') || ($user_profil_type=='professeur') )
  {
    $DB_SQL = 'DELETE FROM sacoche_jointure_user_groupe '
            . 'WHERE user_id=:user_id';
    DB::query(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL , $DB_VAR);
  }
  if( ($user_profil_type=='professeur') || ($user_profil_type=='directeur') )
  {
    $DB_SQL = 'DELETE FROM sacoche_officiel_saisie '
            . 'WHERE prof_id=:user_id';
    DB::query(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL , $DB_VAR);
    $DB_SQL = 'DELETE FROM sacoche_catalogue_categorie '
            . 'WHERE user_id=:user_id';
    DB::query(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL , $DB_VAR);
    $DB_SQL = 'DELETE FROM sacoche_catalogue_appreciation '
            . 'WHERE user_id=:user_id';
    DB::query(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL , $DB_VAR);
  }
  if( ($user_profil_type=='eleve') || ($user_profil_type=='professeur') || ($user_profil_type=='directeur') )
  {
    // photo si élève ; signature si professeur ou directeur
    $DB_SQL = 'DELETE FROM sacoche_image '
            . 'WHERE user_id=:user_id';
    DB::query(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL , $DB_VAR);
  }
  if( ($user_profil_type!='eleve') && ($user_profil_type!='parent') )
  {
    $DB_SQL = 'DELETE sacoche_message, sacoche_jointure_message_destinataire '
            . 'FROM sacoche_message '
            . 'LEFT JOIN sacoche_jointure_message_destinataire USING (message_id) '
            . 'WHERE user_id=:user_id';
    DB::query(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL , $DB_VAR);
  }
}

/**
 * Optimiser les tables d’une base
 *
 * @param void
 * @return void
 */
public static function DB_optimiser_tables_structure()
{
  $DB_TAB = DB::queryTab(SACOCHE_STRUCTURE_BD_NAME , 'SHOW TABLE STATUS LIKE "sacoche_%"');
  if(!empty($DB_TAB))
  {
    foreach($DB_TAB as $DB_ROW)
    {
      DB::query(SACOCHE_STRUCTURE_BD_NAME , 'OPTIMIZE TABLE '.$DB_ROW['Name']);
    }
  }
}

/**
 * Fusionner les données associées à 2 comptes élèves (sauf tables sacoche_user, sacoche_jointure_user_groupe, sacoche_jointure_parent_eleve, sacoche_jointure_message_destinataire)
 *
 * @param int   $user_id_ancien
 * @param int   $user_id_actuel
 * @return bool
 */
public static function DB_fusionner_donnees_comptes_eleves( $user_id_ancien , $user_id_actuel )
{
  $tab_table_champ = array(
    'sacoche_demande'                              => 'eleve_id' ,
    'sacoche_jointure_devoir_eleve'                => 'eleve_id' ,
    'sacoche_livret_jointure_devoirsfaits_eleve'   => 'eleve_id' ,
    'sacoche_livret_jointure_langcultregion_eleve' => 'eleve_id' ,
    'sacoche_livret_jointure_modaccomp_eleve'      => 'eleve_id' ,
    'sacoche_saisie'                               => 'eleve_id' ,
    'sacoche_jointure_user_abonnement'             => 'user_id' ,
    'sacoche_livret_export'                        => 'user_id' ,
    'sacoche_notification'                         => 'user_id' ,
    'sacoche_officiel_archive'                     => 'user_id' ,
    'sacoche_officiel_assiduite'                   => 'user_id' ,
    'sacoche_officiel_jointure_decision'           => 'user_id' ,
    'sacoche_officiel_saisie'                      => 'eleve_ou_classe_id' ,
    'sacoche_livret_saisie'                        => 'cible_id' ,
  );
  foreach($tab_table_champ as $table_nom => $champ_nom)
  {
    switch($champ_nom)
    {
      case 'eleve_ou_classe_id' :
        $where_add = ' AND saisie_type="eleve"';
        break;
      case 'cible_id' :
        $where_add = ' AND cible_nature="eleve"';
        break;
      default :
        $where_add = '';
    }
    // UPDATE ... ON DUPLICATE KEY DELETE ...  n’existe pas, il faut s’y prendre en deux fois avec UPDATE IGNORE ... puis DELETE ...
    DB::query(SACOCHE_STRUCTURE_BD_NAME , 'UPDATE IGNORE '.$table_nom.' SET '.$champ_nom.'='.$user_id_actuel.' WHERE '.$champ_nom.'='.$user_id_ancien.$where_add );
    DB::query(SACOCHE_STRUCTURE_BD_NAME , 'DELETE FROM '.$table_nom.' WHERE '.$champ_nom.'='.$user_id_ancien.$where_add );
  }
}

/**
 * Fusionner les données associées à 2 comptes professeurs / personnels (sauf tables sacoche_user, sacoche_jointure_user_groupe, sacoche_jointure_message_destinataire)
 *
 * @param int   $user_id_ancien
 * @param int   $user_id_actuel
 * @return bool
 */
public static function DB_fusionner_donnees_comptes_personnels( $user_id_ancien , $user_id_actuel )
{
  $tab_table_champ = array(
    'sacoche_demande'                       => 'prof_id' ,
    'sacoche_jointure_devoir_prof'          => 'prof_id' ,
    'sacoche_jointure_selection_prof'       => 'prof_id' ,
    'sacoche_livret_jointure_ap_prof'       => 'prof_id' ,
    'sacoche_livret_jointure_epi_prof'      => 'prof_id' ,
    'sacoche_livret_jointure_parcours_prof' => 'prof_id' ,
    'sacoche_livret_saisie'                 => 'prof_id' ,
    'sacoche_livret_saisie_jointure_prof'   => 'prof_id' ,
    'sacoche_officiel_saisie'               => 'prof_id' ,
    'sacoche_saisie'                        => 'prof_id' ,
    'sacoche_image'                         => 'user_id' ,
    'sacoche_jointure_user_abonnement'      => 'user_id' ,
    'sacoche_jointure_user_matiere'         => 'user_id' ,
    'sacoche_jointure_user_module'          => 'user_id' ,
    'sacoche_message'                       => 'user_id' ,
    'sacoche_notification'                  => 'user_id' ,
    'sacoche_devoir'                        => 'proprio_id' ,
    'sacoche_selection_item'                => 'proprio_id' ,
  );
  foreach($tab_table_champ as $table_nom => $champ_nom)
  {
    // UPDATE ... ON DUPLICATE KEY DELETE ...  n’existe pas, il faut s’y prendre en deux fois avec UPDATE IGNORE ... puis DELETE ...
    DB::query(SACOCHE_STRUCTURE_BD_NAME , 'UPDATE IGNORE '.$table_nom.' SET '.$champ_nom.'='.$user_id_actuel.' WHERE '.$champ_nom.'='.$user_id_ancien );
    DB::query(SACOCHE_STRUCTURE_BD_NAME , 'DELETE FROM '.$table_nom.' WHERE '.$champ_nom.'='.$user_id_ancien );
  }
}

/**
 * Séparer les données d’un compte élève à partir d’une certaine date ; le compte supplémentaire a déjà été créé
 *
 * @param int    $user_id_ancien
 * @param int    $user_id_actuel
 * @param string $DB_separer_donnees_comptes_eleves
 * @return bool
 */
public static function DB_separer_donnees_comptes_eleves( $user_id_ancien , $user_id_actuel , $separation_date_sql )
{
  $tab_table_champ = array(
    'sacoche_notification'     => array( 'user_id' ,'notification_date') ,
    'sacoche_saisie'           => array( 'eleve_id','saisie_date') ,
    'sacoche_demande'          => array( 'eleve_id','demande_date') ,
    'sacoche_officiel_archive' => array( 'user_id' ,'archive_date_generation') ,
  );
  foreach($tab_table_champ as $table_nom => $table_champ)
  {
    list( $champ_user , $champ_date ) = $table_champ;
    DB::query(SACOCHE_STRUCTURE_BD_NAME , 'UPDATE '.$table_nom.' SET '.$champ_user.'='.$user_id_ancien.' WHERE '.$champ_user.'='.$user_id_actuel.' AND '.$champ_date.'<"'.$separation_date_sql.'"' );
  }
}

/**
 * Recherche et correction d’anomalies : numérotation des items d’un thème, ou des thèmes d’un domaine, ou des domaines d’un référentiel
 *
 * @param void
 * @return array   tableau avec label et commentaire pour chaque recherche
 */
public static function DB_corriger_numerotations()
{
  function make_where($champ,$valeur)
  {
    return $champ.'='.$valeur;
  }
  $tab_bilan = array();
  $tab_recherche = array();
  $tab_recherche[] = array( 'contenant_nom'=>'référentiel' , 'contenant_tab_champs'=>array('matiere_id','niveau_id') , 'element_nom'=>'domaine' , 'element_champ'=>'domaine' , 'debut'=>1 , 'decalage'=>0 );
  $tab_recherche[] = array( 'contenant_nom'=>'domaine'     , 'contenant_tab_champs'=>array('domaine_id')             , 'element_nom'=>'thème'   , 'element_champ'=>'theme'   , 'debut'=>1 , 'decalage'=>0 );
  $tab_recherche[] = array( 'contenant_nom'=>'thème'       , 'contenant_tab_champs'=>array('theme_id')               , 'element_nom'=>'item'    , 'element_champ'=>'item'    , 'debut'=>0 , 'decalage'=>1 );
  foreach($tab_recherche as $tab_donnees)
  {
    extract($tab_donnees,EXTR_OVERWRITE); // $contenant_nom $contenant_tab_champs $element_nom $element_champ $debut $decalage
    // numéros en double
    $DB_SQL = 'SELECT DISTINCT CONCAT('.implode(',",",',$contenant_tab_champs).') AS contenant_id , COUNT('.$element_champ.'_id) AS nombre '
            . 'FROM sacoche_referentiel_'.$element_champ.' '
            . 'GROUP BY '.implode(',',$contenant_tab_champs).','.$element_champ.'_ordre '
            . 'HAVING nombre>1 ';
    $DB_TAB1 = DB::queryTab(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL , array() , TRUE);
    // numéros manquants ou décalés
    $DB_SQL = 'SELECT DISTINCT CONCAT('.implode(',",",',$contenant_tab_champs).') AS contenant_id , MAX('.$element_champ.'_ordre) AS maximum , COUNT('.$element_champ.'_id) AS nombre '
            . 'FROM sacoche_referentiel_'.$element_champ.' '
            . 'GROUP BY '.implode(',',$contenant_tab_champs).' '
            . 'HAVING nombre!=maximum+'.$decalage.' ';
    $DB_TAB2 = DB::queryTab(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL , array() , TRUE);
    // en réunissant les 2 requêtes on a repéré tous les problèmes possibles
    $tab_bugs = array_unique( array_merge( array_keys($DB_TAB1) , array_keys($DB_TAB2) ) );
    $nb_bugs = count($tab_bugs);
    if($nb_bugs)
    {
      foreach($tab_bugs as $contenant_id)
      {
        $element_ordre = $debut;
        $contenant_tab_valeur = explode(',',$contenant_id);
        $tab_where = array_map('make_where', $contenant_tab_champs, $contenant_tab_valeur);
        $DB_SQL = 'SELECT '.$element_champ.'_id '
                . 'FROM sacoche_referentiel_'.$element_champ.' '
                . 'WHERE '.implode(' AND ',$tab_where).' '
                . 'ORDER BY '.$element_champ.'_ordre ASC ';
        $DB_COL = DB::queryCol(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL);
        foreach($DB_COL as $element_champ_id)
        {
          $DB_SQL = 'UPDATE sacoche_referentiel_'.$element_champ.' '
                  . 'SET '.$element_champ.'_ordre='.$element_ordre.' '
                  . 'WHERE '.$element_champ.'_id='.$element_champ_id.' ';
          DB::query(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL);
          $element_ordre++;
        }
      }
    }
    $message = (!$nb_bugs) ? 'rien à signaler' : ( ($nb_bugs>1) ? $nb_bugs.' '.$contenant_nom.'s dont le contenu a été renuméroté' : '1 '.$contenant_nom.' dont le contenu a été renuméroté' ) ;
    $classe  = (!$nb_bugs) ? 'valide' : 'alerte' ;
    $tab_bilan[] = '<label class="'.$classe.'">'.ucfirst($element_nom).'s des '.$contenant_nom.'s : '.$message.'.</label>';
  }
  return $tab_bilan;
}

/**
 * Recherche et suppression de correspondances anormales dans la base
 *
 * @param void
 * @return array   tableau avec label et commentaire pour chaque recherche
 */
public static function DB_corriger_anomalies()
{
  $tab_bilan = array();
  // un bout de code utilisé à chaque fois
  function compte_rendu( $nb_modifs , $sujet )
  {
    $message = (!$nb_modifs) ? 'rien à signaler' : ( ($nb_modifs>1) ? $nb_modifs.' anomalies supprimées' : '1 anomalie supprimée' ) ;
    $classe  = (!$nb_modifs) ? 'valide' : 'alerte' ;
    return '<label class="'.$classe.'">'.$sujet.' : '.$message.'.</label>';
  }
  // Référentiels associés à une matière supprimée
  $DB_SQL = 'DELETE sacoche_referentiel,sacoche_referentiel_domaine, sacoche_referentiel_theme, sacoche_referentiel_item, sacoche_jointure_referentiel_socle, sacoche_jointure_devoir_item, sacoche_saisie, sacoche_demande '
          . 'FROM sacoche_referentiel '
          . 'LEFT JOIN sacoche_matiere USING (matiere_id) '
          . 'LEFT JOIN sacoche_referentiel_domaine USING (matiere_id,niveau_id) '
          . 'LEFT JOIN sacoche_referentiel_theme USING (domaine_id) '
          . 'LEFT JOIN sacoche_referentiel_item USING (theme_id) '
          . 'LEFT JOIN sacoche_jointure_referentiel_socle USING (item_id) '
          . 'LEFT JOIN sacoche_jointure_devoir_item USING (item_id) '
          . 'LEFT JOIN sacoche_saisie USING (item_id) '
          . 'LEFT JOIN sacoche_demande USING (item_id) '
          . 'WHERE sacoche_matiere.matiere_id IS NULL ';
  DB::query(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL);
  $tab_bilan[] = compte_rendu( DB::rowCount(SACOCHE_STRUCTURE_BD_NAME) , 'Référentiels' );
  // Domaines associés à une matière supprimée...
  $DB_SQL = 'DELETE sacoche_referentiel_domaine, sacoche_referentiel_theme, sacoche_referentiel_item, sacoche_jointure_referentiel_socle, sacoche_jointure_devoir_item, sacoche_saisie, sacoche_demande '
          . 'FROM sacoche_referentiel_domaine '
          . 'LEFT JOIN sacoche_matiere USING (matiere_id) '
          . 'LEFT JOIN sacoche_referentiel_theme USING (domaine_id) '
          . 'LEFT JOIN sacoche_referentiel_item USING (theme_id) '
          . 'LEFT JOIN sacoche_jointure_referentiel_socle USING (item_id) '
          . 'LEFT JOIN sacoche_jointure_devoir_item USING (item_id) '
          . 'LEFT JOIN sacoche_saisie USING (item_id) '
          . 'LEFT JOIN sacoche_demande USING (item_id) '
          . 'WHERE sacoche_matiere.matiere_id IS NULL ';
  DB::query(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL);
  $tab_bilan[] = compte_rendu( DB::rowCount(SACOCHE_STRUCTURE_BD_NAME) , 'Domaines (arborescence)' );
  // Thèmes associés à un domaine supprimé...
  $DB_SQL = 'DELETE sacoche_referentiel_theme, sacoche_referentiel_item, sacoche_jointure_referentiel_socle, sacoche_jointure_devoir_item, sacoche_saisie, sacoche_demande '
          . 'FROM sacoche_referentiel_theme '
          . 'LEFT JOIN sacoche_referentiel_domaine USING (domaine_id) '
          . 'LEFT JOIN sacoche_referentiel_item USING (theme_id) '
          . 'LEFT JOIN sacoche_jointure_referentiel_socle USING (item_id) '
          . 'LEFT JOIN sacoche_jointure_devoir_item USING (item_id) '
          . 'LEFT JOIN sacoche_saisie USING (item_id) '
          . 'LEFT JOIN sacoche_demande USING (item_id) '
          . 'WHERE sacoche_referentiel_domaine.domaine_id IS NULL ';
  DB::query(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL);
  $tab_bilan[] = compte_rendu( DB::rowCount(SACOCHE_STRUCTURE_BD_NAME) , 'Thèmes (arborescence)' );
  // Items associés à un thème supprimé...
  $DB_SQL = 'DELETE sacoche_referentiel_item, sacoche_jointure_referentiel_socle, sacoche_jointure_devoir_item, sacoche_saisie, sacoche_demande '
          . 'FROM sacoche_referentiel_item '
          . 'LEFT JOIN sacoche_referentiel_theme USING (theme_id) '
          . 'LEFT JOIN sacoche_jointure_referentiel_socle USING (item_id) '
          . 'LEFT JOIN sacoche_jointure_devoir_item USING (item_id) '
          . 'LEFT JOIN sacoche_saisie USING (item_id) '
          . 'LEFT JOIN sacoche_demande USING (item_id) '
          . 'WHERE sacoche_referentiel_theme.theme_id IS NULL ';
  DB::query(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL);
  $tab_bilan[] = compte_rendu( DB::rowCount(SACOCHE_STRUCTURE_BD_NAME) , 'Items (arborescence)' );
  // Demandes d’évaluations associées à un user ou une matière ou un item supprimé...
  $DB_SQL = 'DELETE sacoche_demande '
          . 'FROM sacoche_demande '
          . 'LEFT JOIN sacoche_user ON sacoche_demande.eleve_id=sacoche_user.user_id '
          . 'LEFT JOIN sacoche_matiere USING (matiere_id) '
          . 'LEFT JOIN sacoche_referentiel_item USING (item_id) '
          . 'WHERE ( (sacoche_user.user_id IS NULL) OR (sacoche_matiere.matiere_id IS NULL) OR (sacoche_referentiel_item.item_id IS NULL) ) ';
  DB::query(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL);
  $nb_modifs = DB::rowCount(SACOCHE_STRUCTURE_BD_NAME);
  $DB_SQL = 'UPDATE sacoche_demande '
          . 'LEFT JOIN sacoche_user ON sacoche_demande.prof_id=sacoche_user.user_id '
          . 'SET prof_id=0 '
          . 'WHERE sacoche_user.user_id IS NULL ';
  DB::query(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL);
  $nb_modifs += DB::rowCount(SACOCHE_STRUCTURE_BD_NAME);
  $tab_bilan[] = compte_rendu( $nb_modifs , 'Demandes d’évaluations' );
  // Saisies de scores associées à un élève ou un item supprimé...
  // Attention, on ne teste pas le professeur ou le devoir, car les saisies sont conservées au delà
  $DB_SQL = 'DELETE sacoche_saisie '
          . 'FROM sacoche_saisie '
          . 'LEFT JOIN sacoche_user ON sacoche_saisie.eleve_id=sacoche_user.user_id '
          . 'LEFT JOIN sacoche_referentiel_item USING (item_id) '
          . 'WHERE ( (sacoche_user.user_id IS NULL) OR (sacoche_referentiel_item.item_id IS NULL) ) ';
  DB::query(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL);
  $tab_bilan[] = compte_rendu( DB::rowCount(SACOCHE_STRUCTURE_BD_NAME) , 'Scores' );
  // Devoirs associés à un prof ou un groupe supprimé...
  $DB_SQL = 'DELETE sacoche_devoir, sacoche_jointure_devoir_item , sacoche_jointure_devoir_prof , sacoche_jointure_devoir_eleve '
          . 'FROM sacoche_devoir '
          . 'LEFT JOIN sacoche_jointure_devoir_item  USING (devoir_id) '
          . 'LEFT JOIN sacoche_jointure_devoir_prof  USING (devoir_id) '
          . 'LEFT JOIN sacoche_jointure_devoir_eleve USING (devoir_id) '
          . 'LEFT JOIN sacoche_user ON sacoche_devoir.proprio_id=sacoche_user.user_id '
          . 'LEFT JOIN sacoche_groupe USING (groupe_id) '
          . 'WHERE ( (sacoche_user.user_id IS NULL) OR (sacoche_groupe.groupe_id IS NULL) ) ';
  DB::query(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL);
  $tab_bilan[] = compte_rendu( DB::rowCount(SACOCHE_STRUCTURE_BD_NAME) , 'Évaluations' );
  // Messages associés à un utilisateur supprimé...
  $DB_SQL = 'DELETE sacoche_message, sacoche_jointure_message_destinataire '
          . 'FROM sacoche_message '
          . 'LEFT JOIN sacoche_jointure_message_destinataire USING (message_id) '
          . 'LEFT JOIN sacoche_user USING (user_id) '
          . 'WHERE sacoche_user.user_id IS NULL ';
  DB::query(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL);
  $tab_bilan[] = compte_rendu( DB::rowCount(SACOCHE_STRUCTURE_BD_NAME) , 'Messages d’accueil' );
  // Destinataires de messages associés à un utilisateur ou un regroupement supprimé...
  $DB_SQL = 'DELETE sacoche_jointure_message_destinataire '
          . 'FROM sacoche_jointure_message_destinataire '
          . 'LEFT JOIN sacoche_groupe ON sacoche_jointure_message_destinataire.destinataire_id=sacoche_groupe.groupe_id AND sacoche_jointure_message_destinataire.destinataire_type=sacoche_groupe.groupe_type '
          . 'WHERE destinataire_type IN ("classe","groupe","besoin") AND (sacoche_groupe.groupe_id IS NULL) ';
  DB::query(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL);
  $nb_modifs = DB::rowCount(SACOCHE_STRUCTURE_BD_NAME);
  $DB_SQL = 'DELETE sacoche_jointure_message_destinataire '
          . 'FROM sacoche_jointure_message_destinataire '
          . 'LEFT JOIN sacoche_niveau ON sacoche_jointure_message_destinataire.destinataire_id=sacoche_niveau.niveau_id '
          . 'WHERE destinataire_type="niveau" AND (sacoche_niveau.niveau_id IS NULL) ';
  DB::query(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL);
  $nb_modifs += DB::rowCount(SACOCHE_STRUCTURE_BD_NAME);
  $DB_SQL = 'DELETE sacoche_jointure_message_destinataire '
          . 'FROM sacoche_jointure_message_destinataire '
          . 'LEFT JOIN sacoche_user ON sacoche_jointure_message_destinataire.destinataire_id=sacoche_user.user_id '
          . 'WHERE destinataire_type="user" AND (sacoche_user.user_id IS NULL) ';
  DB::query(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL);
  $nb_modifs += DB::rowCount(SACOCHE_STRUCTURE_BD_NAME);
  $tab_bilan[] = compte_rendu( $nb_modifs , 'Jointures message/destinataire' );
  // Bascules vers un compte désactivé ou supprimé...
  $tab_bilan[] = compte_rendu( DB_STRUCTURE_SWITCH::DB_supprimer_liaisons_obsoletes() , 'Bascules entre comptes' );
  // Sélections d’items associées à un professeur supprimé...
  $DB_SQL = 'DELETE sacoche_selection_item '
          . 'FROM sacoche_selection_item '
          . 'LEFT JOIN sacoche_user ON sacoche_selection_item.proprio_id=sacoche_user.user_id '
          . 'WHERE sacoche_user.user_id IS NULL ';
  DB::query(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL);
  $tab_bilan[] = compte_rendu( DB::rowCount(SACOCHE_STRUCTURE_BD_NAME) , 'Sélections d’items sans propriétaire' );
  // Plan de classe asociés à un groupe ou un professeur supprimé...
  $DB_SQL = 'DELETE sacoche_plan_classe , sacoche_jointure_plan_eleve '
          . 'FROM sacoche_plan_classe '
          . 'LEFT JOIN sacoche_jointure_plan_eleve USING (plan_id) '
          . 'LEFT JOIN sacoche_groupe USING (groupe_id) '
          . 'LEFT JOIN sacoche_user ON sacoche_plan_classe.prof_id=sacoche_user.user_id '
          . 'WHERE ( (sacoche_user.user_id IS NULL) OR (sacoche_groupe.groupe_id IS NULL) ) ';
  DB::query(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL);
  $tab_bilan[] = compte_rendu( DB::rowCount(SACOCHE_STRUCTURE_BD_NAME) , 'Pan de classe sans professeur ou groupe' );
  // Jointures sélection/item à un item supprimé...
  $tab_bilan[] = compte_rendu( DB_STRUCTURE_SELECTION_ITEM::DB_supprimer_jointures_items_obsoletes() , 'Jointures sélection/item' );
  // Sélections d’items associées à aucun item...
  $tab_bilan[] = compte_rendu( DB_STRUCTURE_SELECTION_ITEM::DB_supprimer_selections_items_obsoletes() , 'Sélections d’items sans item' );
  // Jointures période/groupe associées à une période ou un groupe supprimé...
  $DB_SQL = 'DELETE sacoche_jointure_groupe_periode '
          . 'FROM sacoche_jointure_groupe_periode '
          . 'LEFT JOIN sacoche_periode USING (periode_id) '
          . 'LEFT JOIN sacoche_groupe USING (groupe_id) '
          . 'WHERE ( (sacoche_periode.periode_id IS NULL) OR (sacoche_groupe.groupe_id IS NULL) ) ';
  DB::query(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL);
  $tab_bilan[] = compte_rendu( DB::rowCount(SACOCHE_STRUCTURE_BD_NAME) , 'Jointures période/groupe' );
  // Jointures période/saisie bilan officiel associées à une période supprimée... (on ne s’occupe volontairement pas de vérifier la jointure période/groupe) (on ne vérifie pas non plus les jointures élève / prof / rubrique ... de toutes façon cette table est vidée annuellement)
  $DB_SQL = 'DELETE sacoche_officiel_saisie '
          . 'FROM sacoche_officiel_saisie '
          . 'LEFT JOIN sacoche_periode USING (periode_id) '
          . 'WHERE sacoche_periode.periode_id IS NULL ';
  DB::query(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL);
  $tab_bilan[] = compte_rendu( DB::rowCount(SACOCHE_STRUCTURE_BD_NAME) , 'Jointures période/saisie bilan officiel' );
  // Jointures période/fichier bilan officiel associées à un user supprimé...
  $DB_SQL = 'DELETE sacoche_officiel_archive '
          . 'FROM sacoche_officiel_archive '
          . 'LEFT JOIN sacoche_user USING (user_id) '
          . 'WHERE sacoche_user.user_id IS NULL ';
  DB::query(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL);
  $tab_bilan[] = compte_rendu( DB::rowCount(SACOCHE_STRUCTURE_BD_NAME) , 'Jointures période/fichier bilan officiel' );
  // Jointures période/assiduité bilan officiel associées à un user ou une période supprimée...
  $DB_SQL = 'DELETE sacoche_officiel_assiduite '
          . 'FROM sacoche_officiel_assiduite '
          . 'LEFT JOIN sacoche_periode USING (periode_id) '
          . 'LEFT JOIN sacoche_user USING (user_id) '
          . 'WHERE ( (sacoche_user.user_id IS NULL) OR (sacoche_periode.periode_id IS NULL) ) ';
  DB::query(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL);
  $tab_bilan[] = compte_rendu( DB::rowCount(SACOCHE_STRUCTURE_BD_NAME) , 'Jointures période/assiduité bilan officiel' );
  // Jointures user/groupe associées à un user ou un groupe supprimé...
  $DB_SQL = 'DELETE sacoche_jointure_user_groupe '
          . 'FROM sacoche_jointure_user_groupe '
          . 'LEFT JOIN sacoche_user USING (user_id) '
          . 'LEFT JOIN sacoche_groupe USING (groupe_id) '
          . 'WHERE ( (sacoche_user.user_id IS NULL) OR (sacoche_groupe.groupe_id IS NULL) ) ';
  DB::query(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL);
  $tab_bilan[] = compte_rendu( DB::rowCount(SACOCHE_STRUCTURE_BD_NAME) , 'Jointures utilisateur/groupe' );
  // Jointures user/matière associées à un user ou une matière supprimée...
  $DB_SQL = 'DELETE sacoche_jointure_user_matiere '
          . 'FROM sacoche_jointure_user_matiere '
          . 'LEFT JOIN sacoche_user USING (user_id) '
          . 'LEFT JOIN sacoche_matiere USING (matiere_id) '
          . 'WHERE ( (sacoche_user.user_id IS NULL) OR (sacoche_matiere.matiere_id IS NULL) ) ';
  DB::query(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL);
  $tab_bilan[] = compte_rendu( DB::rowCount(SACOCHE_STRUCTURE_BD_NAME) , 'Jointures utilisateur/matière' );
  // Abonnement notifications associée à un user supprimé...
  $DB_SQL = 'DELETE sacoche_jointure_user_abonnement '
          . 'FROM sacoche_jointure_user_abonnement '
          . 'LEFT JOIN sacoche_user USING (user_id) '
          . 'WHERE user_id IS NULL ';
  DB::query(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL);
  $tab_bilan[] = compte_rendu( DB::rowCount(SACOCHE_STRUCTURE_BD_NAME) , 'Jointures utilisateur/abonnement notifications' );
  // Jointures item/socle associées à un item supprimé ou un élément de socle inexistant ...
  $DB_SQL = 'DELETE sacoche_jointure_referentiel_socle '
          . 'FROM sacoche_jointure_referentiel_socle '
          . 'LEFT JOIN sacoche_referentiel_item USING (item_id) '
          . 'LEFT JOIN sacoche_socle_cycle USING (socle_cycle_id) '
          . 'LEFT JOIN sacoche_socle_composante USING (socle_composante_id) '
          . 'WHERE ( (sacoche_referentiel_item.item_id IS NULL) OR (sacoche_socle_cycle.socle_cycle_id IS NULL) OR (sacoche_socle_composante.socle_composante_id IS NULL) ) ';
  DB::query(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL);
  $tab_bilan[] = compte_rendu( DB::rowCount(SACOCHE_STRUCTURE_BD_NAME) , 'Jointures item/socle' );
  // Jointures devoir/item associées à un devoir ou un item supprimé...
  $DB_SQL = 'DELETE sacoche_jointure_devoir_item '
          . 'FROM sacoche_jointure_devoir_item '
          . 'LEFT JOIN sacoche_devoir USING (devoir_id) '
          . 'LEFT JOIN sacoche_referentiel_item USING (item_id) '
          . 'WHERE ( (sacoche_devoir.devoir_id IS NULL) OR (sacoche_referentiel_item.item_id IS NULL) ) ';
  DB::query(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL);
  $tab_bilan[] = compte_rendu( DB::rowCount(SACOCHE_STRUCTURE_BD_NAME) , 'Jointures évaluation/item' );
  // Jointures devoir/droit associées à un devoir ou un user supprimé...
  $DB_SQL = 'DELETE sacoche_jointure_devoir_prof '
          . 'FROM sacoche_jointure_devoir_prof '
          . 'LEFT JOIN sacoche_devoir USING (devoir_id) '
          . 'LEFT JOIN sacoche_user ON sacoche_jointure_devoir_prof.prof_id=sacoche_user.user_id '
          . 'WHERE ( (sacoche_devoir.devoir_id IS NULL) OR (sacoche_user.user_id IS NULL) ) ';
  DB::query(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL);
  $tab_bilan[] = compte_rendu( DB::rowCount(SACOCHE_STRUCTURE_BD_NAME) , 'Jointures évaluation/prof' );
  // Jointures devoir/audio associées à un devoir ou un user supprimé...
  $DB_SQL = 'DELETE sacoche_jointure_devoir_eleve '
          . 'FROM sacoche_jointure_devoir_eleve '
          . 'LEFT JOIN sacoche_devoir USING (devoir_id) '
          . 'LEFT JOIN sacoche_user ON sacoche_jointure_devoir_eleve.eleve_id=sacoche_user.user_id '
          . 'WHERE ( (sacoche_devoir.devoir_id IS NULL) OR (sacoche_user.user_id IS NULL) ) ';
  DB::query(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL);
  $tab_bilan[] = compte_rendu( DB::rowCount(SACOCHE_STRUCTURE_BD_NAME) , 'Jointures évaluation/audio' );
  // Adresse associée à un parent supprimé...
  $DB_SQL = 'DELETE sacoche_parent_adresse '
          . 'FROM sacoche_parent_adresse '
          . 'LEFT JOIN sacoche_user ON sacoche_parent_adresse.parent_id=sacoche_user.user_id '
          . 'WHERE user_id IS NULL ';
  DB::query(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL);
  $tab_bilan[] = compte_rendu( DB::rowCount(SACOCHE_STRUCTURE_BD_NAME) , 'Jointures parent/adresse' );
  // Jointures parent/élève associées à un parent ou un élève supprimé...
  $DB_SQL = 'DELETE sacoche_jointure_parent_eleve '
          . 'FROM sacoche_jointure_parent_eleve '
          . 'LEFT JOIN sacoche_user AS parent ON sacoche_jointure_parent_eleve.parent_id=parent.user_id '
          . 'LEFT JOIN sacoche_user AS eleve ON sacoche_jointure_parent_eleve.eleve_id=eleve.user_id '
          . 'WHERE ( (parent.user_id IS NULL) OR (eleve.user_id IS NULL) ) ';
  DB::query(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL);
  $tab_bilan[] = compte_rendu( DB::rowCount(SACOCHE_STRUCTURE_BD_NAME) , 'Jointures parent/enfant' );
  // Élèves associés à une classe supprimée...
  // Attention, l’id de classe à 0 est normal pour un élève non affecté ou un autre statut
  $DB_SQL = 'UPDATE sacoche_user '
          . 'LEFT JOIN sacoche_groupe ON sacoche_user.eleve_classe_id=sacoche_groupe.groupe_id '
          . 'SET sacoche_user.eleve_classe_id=0 '
          . 'WHERE ( (sacoche_user.eleve_classe_id!=0) AND (sacoche_groupe.groupe_id IS NULL) ) ';
  DB::query(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL);
  $tab_bilan[] = compte_rendu( DB::rowCount(SACOCHE_STRUCTURE_BD_NAME) , 'Jointures élève/classe' );
  // Signature associée à un user supprimé...
  // Attention, l’id de user à 0 est normal pour le tampon et le logo de l’établissement
  $DB_SQL = 'DELETE sacoche_image '
          . 'FROM sacoche_image '
          . 'LEFT JOIN sacoche_user USING (user_id) '
          . 'WHERE sacoche_image.user_id!=0 AND sacoche_user.user_id IS NULL ';
  DB::query(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL);
  $tab_bilan[] = compte_rendu( DB::rowCount(SACOCHE_STRUCTURE_BD_NAME) , 'Jointures utilisateur/signature' );
  // Retour
  return $tab_bilan;
}

}
?>