<?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/>.
 * 
 */

if(!defined('SACoche')) {exit('Ce fichier ne peut être appelé directement !');}
if(($_SESSION['SESAMATH_ID']==ID_DEMO)&&($_POST['f_action']!='initialiser')){Json::end( FALSE , 'Action désactivée pour la démo.' );}

// ////////////////////////////////////////////////////////////////////////////////////////////////////
// Récupération des valeurs transmises
// ////////////////////////////////////////////////////////////////////////////////////////////////////

$groupe_id      = ($is_sous_groupe) ? $groupe_id : $classe_id ; // Le groupe = le groupe transmis ou sinon la classe (cas le plus fréquent).

// Autres chaines spécifiques...
$tab_matiere_id = Clean::post('f_listing_matieres', array('array',','));
$tab_matiere_id = array_filter( Clean::map('entier',$tab_matiere_id) , 'positif' );
$liste_matiere_id = implode(',',$tab_matiere_id);

$tab_action = array('initialiser','modifier_appr','ajouter_appr','supprimer_appr','modifier_note','ajouter_note','supprimer_note','modifier_disp','ajouter_disp','supprimer_disp');

// On vérifie les paramètres principaux

if( ($OBJET!='saisir_multiple') || !in_array($ACTION,$tab_action) || !$classe_id )
{
  Json::end( FALSE , 'Erreur avec les données transmises !<br>'.pathinfo(__FILE__,PATHINFO_FILENAME).':'.__LINE__ );
}

// On vérifie que le bilan est bien accessible en modification et on récupère les infos associées

$DB_ROW = DB_STRUCTURE_LIVRET::DB_recuperer_page_groupe_info( $classe_id , $PAGE_REF , $PAGE_PERIODICITE , $JOINTURE_PERIODE );

if(empty($DB_ROW))
{
  Json::end( FALSE , 'Association classe / livret introuvable !' );
}

$BILAN_TYPE          = 'livret';
$BILAN_ETAT          = $DB_ROW['jointure_etat'];
$PAGE_MOMENT         = $DB_ROW['livret_page_moment'];
$PAGE_TITRE_CLASSE   = $DB_ROW['livret_page_titre_classe'];
$PAGE_RESUME         = $DB_ROW['livret_page_resume'];
$PAGE_RUBRIQUE_TYPE  = $DB_ROW['livret_page_rubrique_type'];
$PAGE_RUBRIQUE_JOIN  = $DB_ROW['livret_page_rubrique_join'];
$PAGE_COLONNE        = $DB_ROW['livret_page_colonne'];
$PAGE_MOYENNE_CLASSE = $DB_ROW['livret_page_moyenne_classe'];
$PAGE_EPI            = $DB_ROW['livret_page_epi'];
$PAGE_AP             = $DB_ROW['livret_page_ap'];
$PAGE_PARCOURS       = $DB_ROW['livret_page_parcours'];
$PAGE_CRCN           = ( $DB_ROW['livret_page_crcn'] && ( $PAGE_PERIODICITE == 'periode' ) && ( $JOINTURE_PERIODE % 11 == 0 ) ); // seulement le bilan de la dernière période
$PAGE_VIE_SCOLAIRE   = $DB_ROW['livret_page_vie_scolaire'];
$classe_nom          = $DB_ROW['groupe_nom'];
$DATE_VERROU         = is_null($DB_ROW['jointure_date_verrou']) ? TODAY_FR : To::datetime_sql_to_french( $DB_ROW['jointure_date_verrou'] , FALSE /*return_time*/ ) ;
$BILAN_TYPE_ETABL    = in_array($PAGE_RUBRIQUE_TYPE,array('c3_matiere','c4_matiere','c3_socle','c4_socle')) ? 'college' : 'ecole' ;
$CONFIGURATION_REF   = $DB_ROW['configuration_ref'];

if( !in_array( $BILAN_ETAT , array('2rubrique','3mixte') ) || (substr($PAGE_RUBRIQUE_TYPE,3)!='matiere') )
{
  Json::end( FALSE , 'Bilan interdit d’accès pour cette action !' );
}

// Forcer la récupération des paramètres du bilan, au cas où un changement de paramétrage viendrait d’être effectué.
// La mémorisation se fait quand même en session pour des raisons historiques (les premiers bilans archivés utilisent cette variable) et un peu pratique (variable globale accessible partout).
$tab_configuration = DB_STRUCTURE_OFFICIEL_CONFIG::DB_recuperer_configuration( $BILAN_TYPE , $CONFIGURATION_REF );
if(empty($tab_configuration))
{
  Json::end( FALSE , 'Configuration '.$BILAN_TYPE.' / '.$CONFIGURATION_REF.' non récupérée !' );
}
foreach($tab_configuration as $key => $val)
{
  Session::_set('OFFICIEL',Clean::upper($BILAN_TYPE.'_'.$key) , $val);
}
Session::_set('OFFICIEL',Clean::upper($BILAN_TYPE).'_CONFIG_REF' , $CONFIGURATION_REF);

// On récupère et vérifie d’autres paramètres communs à certaines actions

if($ACTION!='initialiser')
{
  // Récup
  $rubrique_type     = Clean::post('f_rubrique_type'    , 'texte', '');
  $rubrique_id       = Clean::post('f_rubrique_id'      , 'entier', 0);
  $rubrique_dispense = Clean::post('f_rubrique_dispense', 'entier');
  $saisie_objet      = Clean::post('f_saisie_objet'     , 'texte', '');
  $page_colonne      = Clean::post('f_page_colonne'     , 'texte', '');
  $appreciation      = Clean::post('f_appreciation'     , 'appreciation');
  $position          = Clean::post('f_position'         , 'decimal', -1);
  // Vérif globale
  $test_pb_rubrique  = ($rubrique_type!='eval') ? TRUE : FALSE ;
  $test_pb_saisie_id = ( in_array($ACTION,array('modifier_appr','supprimer_appr','modifier_note','supprimer_note')) && !$saisie_id ) ? TRUE : FALSE ;
  if( $test_pb_rubrique || $test_pb_saisie_id )
  {
    Json::end( FALSE , 'Erreur avec les données transmises !<br>'.pathinfo(__FILE__,PATHINFO_FILENAME).':'.__LINE__ );
  }
}

// ////////////////////////////////////////////////////////////////////////////////////////////////////
// Récupérer et mettre en session les infos sur les seuils enregistrés
// ////////////////////////////////////////////////////////////////////////////////////////////////////

if( ($PAGE_PERIODICITE=='periode') && !in_array($PAGE_COLONNE,array('moyenne','pourcentage')) )
{
  Outil::recuperer_seuils_livret( $PAGE_REF , $PAGE_COLONNE );
}
elseif($PAGE_PERIODICITE=='cycle')
{
  $cycle_id = substr($PAGE_REF,-1);
  Outil::recuperer_seuils_livret( $PAGE_REF );
}

// ////////////////////////////////////////////////////////////////////////////////////////////////////
// Fermeture de session (mais pas destruction, juste écriture et libération des données pour éviter un verrouillage en écriture)
// ////////////////////////////////////////////////////////////////////////////////////////////////////

Session::write_close();

// ////////////////////////////////////////////////////////////////////////////////////////////////////
// Cas 1 : enregistrement d’une appréciation ou d’une note
// ////////////////////////////////////////////////////////////////////////////////////////////////////

if( ($ACTION=='ajouter_appr') || ($ACTION=='modifier_appr') )
{
  if( ( ($ACTION=='modifier_appr') && !$saisie_id ) || !$appreciation )
  {
    Json::end( FALSE , 'Erreur avec les données transmises !<br>'.pathinfo(__FILE__,PATHINFO_FILENAME).':'.__LINE__ );
  }
  // Enregistrer la saisie
  $saisie_valeur = $appreciation;
  if($ACTION=='modifier_appr')
  {
    DB_STRUCTURE_LIVRET::DB_modifier_saisie( $saisie_id , $saisie_objet , $saisie_valeur , 'saisie' , $_SESSION['USER_ID'] );
  }
  else
  {
    $cible_nature = ($eleve_id) ? 'eleve'   : 'classe' ;
    $cible_id     = ($eleve_id) ? $eleve_id : $classe_id ;
    $saisie_id = DB_STRUCTURE_LIVRET::DB_ajouter_saisie( $PAGE_REF , $PAGE_PERIODICITE , $JOINTURE_PERIODE , $rubrique_type , $rubrique_id , $cible_nature , $cible_id , $saisie_objet , $saisie_valeur , 'saisie' , $_SESSION['USER_ID'] );
  }
  Json::end( TRUE , $saisie_id );
}

if( ($ACTION=='ajouter_note') || ($ACTION=='modifier_note') || ($ACTION=='ajouter_disp') || ($ACTION=='modifier_disp') )
{
  $test_pb_saisie = (
      ( ($ACTION=='modifier_note') && !$saisie_id )
      || is_null($position) || !$rubrique_id
      || (in_array($PAGE_COLONNE,array('objectif','position'))&&!in_array($position,array(1,2,3,4)))
      || (($PAGE_COLONNE=='reussite')&&!in_array($position,array(1,2,3)))
      || (($PAGE_COLONNE=='pourcentage')&&($position>100))
      || (($PAGE_COLONNE=='moyenne')&&($position>20))
    ) ? TRUE : FALSE ;
  $test_pb_dispense = ( ( ($ACTION=='ajouter_disp') || ($ACTION=='modifier_disp') ) && ( !$rubrique_dispense || ($PAGE_REF!='3e') || ($rubrique_type!='eval') || ($saisie_objet!='position') || ($PAGE_COLONNE!='moyenne') ) ) ? TRUE : FALSE ;
  if( $test_pb_saisie || $test_pb_dispense )
  {
    Json::end( FALSE , 'Erreur avec les données transmises !<br>'.pathinfo(__FILE__,PATHINFO_FILENAME).':'.__LINE__ );
  }
  // Enregistrer la saisie
  if( ($ACTION=='ajouter_disp') || ($ACTION=='modifier_disp') )
  {
    $saisie_valeur = 'DI';
  }
  else if($PAGE_COLONNE=='pourcentage')
  {
    $saisie_valeur = round($position,1);
  }
  else if($PAGE_COLONNE=='moyenne')
  {
    $saisie_valeur = round($position*5,1);
  }
  else if(in_array($PAGE_COLONNE,array('objectif','position','maitrise','reussite')))
  {
    $saisie_valeur = ($_SESSION['LIVRET'][$position]['SEUIL_MIN']+$_SESSION['LIVRET'][$position]['SEUIL_MAX'])/2;
  }
  if($ACTION=='modifier_note')
  {
    DB_STRUCTURE_LIVRET::DB_modifier_saisie( $saisie_id , $saisie_objet , $saisie_valeur , 'saisie' , $_SESSION['USER_ID'] );
  }
  else
  {
    $cible_nature = 'eleve';
    $cible_id     = $eleve_id;
    $saisie_id = DB_STRUCTURE_LIVRET::DB_ajouter_saisie( $PAGE_REF , $PAGE_PERIODICITE , $JOINTURE_PERIODE , $rubrique_type , $rubrique_id , $cible_nature , $cible_id , $saisie_objet , $saisie_valeur , 'saisie' , $_SESSION['USER_ID'] );
  }
  Json::end( TRUE , $saisie_id );
}

// ////////////////////////////////////////////////////////////////////////////////////////////////////
// Cas 2 : suppression d’une appréciation ou d’une note
// ////////////////////////////////////////////////////////////////////////////////////////////////////

if($ACTION=='supprimer_appr')
{
  if(!$saisie_id)
  {
    Json::end( FALSE , 'Erreur avec les données transmises !<br>'.pathinfo(__FILE__,PATHINFO_FILENAME).':'.__LINE__ );
  }
  DB_STRUCTURE_LIVRET::DB_modifier_saisie( $saisie_id , $saisie_objet , NULL /*saisie_valeur*/ , 'saisie' , $_SESSION['USER_ID'] );
  Json::end( TRUE , $saisie_id );
}

if( ($ACTION=='supprimer_note') || ($ACTION=='supprimer_disp') )
{
  $test_pb_dispense = ( ($ACTION=='supprimer_disp') && ( !$rubrique_dispense || ($rubrique_type!='eval') || ($saisie_objet!='position') || ($PAGE_COLONNE!='moyenne') ) ) ? TRUE : FALSE ;
  if( !$saisie_id || $test_pb_dispense )
  {
    Json::end( FALSE , 'Erreur avec les données transmises !<br>'.pathinfo(__FILE__,PATHINFO_FILENAME).':'.__LINE__ );
  }
  DB_STRUCTURE_LIVRET::DB_modifier_saisie( $saisie_id , $saisie_objet , NULL /*saisie_valeur*/ , 'saisie' , $_SESSION['USER_ID'] );
  Json::end( TRUE , $saisie_id );
}

// ////////////////////////////////////////////////////////////////////////////////////////////////////
// Cas 3 : affichage des données du groupe classe et des élèves
// ////////////////////////////////////////////////////////////////////////////////////////////////////

// Récupérer la liste des élèves concernés : soit d’une classe (en général) soit d’une classe ET d’un sous-groupe pour un prof affecté à un groupe d’élèves

$groupe_nom = (!$is_sous_groupe) ? $classe_nom : $classe_nom.' - '.DB_STRUCTURE_COMMUN::DB_recuperer_groupe_nom($groupe_id) ;

$DB_TAB = (!$is_sous_groupe) ? DB_STRUCTURE_COMMUN::DB_lister_users_regroupement( 'eleve' /*profil_type*/ , 2 /*actuels_et_anciens*/ , 'classe' , $classe_id , 'nom' /*eleves_ordre*/ , 'user_id,user_nom,user_prenom' /*champs*/ , $periode_id )
                             : DB_STRUCTURE_COMMUN::DB_lister_eleves_classe_et_groupe( $classe_id , $groupe_id , 2 /*actuels_et_anciens*/ , $periode_id ) ;
if(empty($DB_TAB))
{
  Json::end( FALSE , 'Aucun élève évalué trouvé du regroupement '.$groupe_nom.' sur la période '.$date_debut.' ~ '.$date_fin.'.' );
}
$tab_eleve_id = array( 0 => array( 'eleve_nom' => $groupe_nom , 'eleve_prenom' => '' , 'eleve_dispositif' => array() ) );
foreach($DB_TAB as $DB_ROW)
{
  $tab_eleve_id[$DB_ROW['user_id']] = array(
    'eleve_nom'        => $DB_ROW['user_nom'] ,
    'eleve_prenom'     => $DB_ROW['user_prenom'] ,
    'eleve_dispositif' => array() ,
  );
}
$liste_eleve_id = implode(',',array_keys($tab_eleve_id));

// Lister les dispositifs
$DB_TAB_DISPOSITIF = DB_STRUCTURE_COMMUN::DB_lister_eleves_dispositifs( $liste_eleve_id , 'periode' , $periode_id );
if(!empty($DB_TAB_DISPOSITIF))
{
  foreach($DB_TAB_DISPOSITIF as $DB_ROW)
  {
    if(!is_null($DB_ROW['livret_modaccomp_id']))
    {
      $tab_eleve_id[$DB_ROW['user_id']]['eleve_dispositif'][$DB_ROW['livret_modaccomp_code']] = $DB_ROW['info_complement'];
    }
    if(!is_null($DB_ROW['livret_devoirsfaits_id']))
    {
      $tab_eleve_id[$DB_ROW['user_id']]['eleve_dispositif']['DF'] = NULL;
    }
  }
}

// sous-titre
$sous_titre = 'Saisie express des positionnements et appréciations (acquisitions, progrès et difficultés éventuelles)';

// (re)calculer les données du livret
// Attention ! On doit calculer des moyennes de classe, pas de groupe !
if(!$is_sous_groupe)
{
  $liste_eleve_id_tmp = $liste_eleve_id;
}
else
{
  $tab_eleve_id_tmp = array();
  $DB_TAB = DB_STRUCTURE_COMMUN::DB_lister_users_regroupement( 'eleve' /*profil_type*/ , 2 /*actuels_et_anciens*/ , 'classe' , $classe_id , 'nom' /*eleves_ordre*/ , 'user_id' /*champs*/ , $periode_id );
  foreach($DB_TAB as $DB_ROW)
  {
    $tab_eleve_id_tmp[] = $DB_ROW['user_id'];
  }
  $liste_eleve_id_tmp = implode(',',$tab_eleve_id_tmp);
}
$recalculer_positionnements = ( $BILAN_ETAT <= $_SESSION['OFFICIEL']['LIVRET_ETAPE_MAX_MAJ_POSITIONNEMENTS'] ) ? TRUE : FALSE ;
calculer_et_enregistrer_donnees_eleves( $PAGE_REF , $PAGE_PERIODICITE , $JOINTURE_PERIODE , $PAGE_RUBRIQUE_TYPE , $PAGE_RUBRIQUE_JOIN , $PAGE_COLONNE , $periode_id , $date_sql_debut , $date_sql_fin , $classe_id , $liste_eleve_id , $_SESSION['OFFICIEL']['LIVRET_IMPORT_BULLETIN_NOTES'] , $_SESSION['OFFICIEL']['LIVRET_ONLY_SOCLE'] , $_SESSION['OFFICIEL']['LIVRET_RETROACTIF'] , $recalculer_positionnements );

// Récupérer les saisies déjà effectuées ou enregistrées pour la période en cours

$tab_saisie_initialisation = array( 'saisie_id'=>0 , 'saisie_valeur'=>NULL );

$tab_saisie       = array(); // [eleve_id][rubrique_type][rubrique_id][saisie_objet] => array(saisie_id,saisie_valeur); avec eleve_id=0 pour position ou appréciation sur la classe
$tab_matiere_code = array(); // tableau avec les codes matières du bilan périodique, pour vérification des élèves de 3e
$DB_TAB = array_merge
(
  // pour prof_id on ne passe pas $_SESSION['USER_ID'] car il y a aussi les appréciations communes à plusieurs collègues présaisies par un autre.
  DB_STRUCTURE_LIVRET::DB_recuperer_donnees_classe( $PAGE_REF , $PAGE_PERIODICITE , $JOINTURE_PERIODE , '' /*liste_rubrique_type*/ , $classe_id      , 0 /*prof_id*/ , FALSE /*with_periodes_avant*/ ) ,
  DB_STRUCTURE_LIVRET::DB_recuperer_donnees_eleves( $PAGE_REF , $PAGE_PERIODICITE , $JOINTURE_PERIODE , '' /*liste_rubrique_type*/ , $liste_eleve_id , 0 /*prof_id*/ , FALSE /*with_periodes_avant*/ )
);
// La requête renvoie les appréciations et les positionnements de toutes les rubriques.
// Il ne faut prendre que les données qui vont avec les rubriques du prof.
// On effectue un premier passage afin de déterminer quelles sont ces rubriques
$tab_acces_prof = array();
foreach($DB_TAB as $DB_ROW)
{
  if( ($DB_ROW['rubrique_type']=='eval') && !empty($DB_ROW['listing_profs']) && in_array($_SESSION['USER_ID'],explode(',',$DB_ROW['listing_profs'])) )
  {
    $eleve_id = isset($DB_ROW['eleve_id']) ? $DB_ROW['eleve_id'] : 0 ;
    $tab_acces_prof[$DB_ROW['rubrique_id']][$eleve_id] = TRUE;
    if($eleve_id)
    {
      $tab_acces_prof[$DB_ROW['rubrique_id']][0] = TRUE;
    }
  }
}
foreach($DB_TAB as $DB_ROW)
{
  $eleve_id = isset($DB_ROW['eleve_id']) ? $DB_ROW['eleve_id'] : 0 ;
  if( ($DB_ROW['rubrique_type']=='eval') && isset($tab_acces_prof[$DB_ROW['rubrique_id']][$eleve_id]) )
  {
    if($eleve_id)
    {
      $tab_matiere_code[$DB_ROW['eleve_id']][$DB_ROW['rubrique_id']] = $DB_ROW['matiere_code'];
    }
    $tab_saisie[$eleve_id][$DB_ROW['rubrique_type']][$DB_ROW['rubrique_id']][$DB_ROW['saisie_objet']] = array(
      'saisie_id'     => $DB_ROW['livret_saisie_id'] ,
      'saisie_valeur' => $DB_ROW['saisie_valeur'] ,
    );
  }
}

if(empty($tab_saisie))
{
  Json::end( FALSE , 'Aucune saisie de note trouvée sur la période '.$date_debut.' ~ '.$date_fin.'.' );
}

// Récupérer la liste des rubriques

$tab_rubrique = array();
$DB_TAB = DB_STRUCTURE_LIVRET::DB_lister_rubriques( $PAGE_RUBRIQUE_TYPE , TRUE /*for_edition*/ );
if(empty($DB_TAB))
{
  Json::end( FALSE , 'Aucune matiere du livret n’est associée aux référentiels ! Il faut configurer un minimum le livret avant son édition...' );
}
foreach($DB_TAB as $DB_ROW)
{
  foreach($tab_eleve_id as $eleve_id => $tab_eleve)
  {
    if(isset($tab_saisie[$eleve_id]['eval'][$DB_ROW['livret_rubrique_id']]))
    {
      $tab_rubrique[$DB_ROW['livret_rubrique_id']] = $DB_ROW['rubrique'];
    }
  }
}

if(empty($tab_rubrique))
{
  Json::end( FALSE , 'Aucune rubrique avec un item évalué sur la période '.$date_debut.' ~ '.$date_fin.'.' );
}

// Récupérer les professeurs principaux

$affichage_prof_principal = ($BILAN_TYPE_ETABL=='college') ? TRUE : FALSE ;
$texte_prof_principal = '';

if( $affichage_prof_principal )
{
  $tab_pp = array();
  $DB_TAB = DB_STRUCTURE_OFFICIEL::DB_lister_profs_principaux($classe_id);
  if(empty($DB_TAB))
  {
    $texte_prof_principal = 'Professeur principal : sans objet.';
  }
  else if(count($DB_TAB)==1)
  {
    $DB_ROW = $DB_TAB[0];
    $tab_pp[$DB_ROW['user_id']] = To::texte_genre_identite($DB_ROW['user_nom'],FALSE,$DB_ROW['user_prenom'],TRUE,$DB_ROW['user_genre']);
    $texte_prof_principal = 'Professeur principal : '.$tab_pp[$DB_ROW['user_id']];
  }
  else
  {
    foreach($DB_TAB as $DB_ROW)
    {
      $tab_pp[$DB_ROW['user_id']] = To::texte_genre_identite($DB_ROW['user_nom'],FALSE,$DB_ROW['user_prenom'],TRUE,$DB_ROW['user_genre']);
    }
    $texte_prof_principal = 'Professeurs principaux : '.implode(' ; ',$tab_pp);
  }
}

// DNB 2025 : en 3e, retenir si la matière peut être notée "Dispensé(e)" pour l’élève.

$tab_matieres_code_dispense = array();
$tab_rubriques_id_dispense = array();
if($PAGE_REF=='3e')
{
  $DB_TAB = DB_STRUCTURE_LIVRET::DB_lister_matieres_mef_3e($liste_eleve_id);
  foreach($DB_TAB as $DB_ROW)
  {
    if($DB_ROW['livret_dnb_matiere_dispense_possible'])
    {
      $tab_matieres_code_dispense[$DB_ROW['user_id']][$DB_ROW['livret_dnb_matiere_code']] = TRUE;
    }
  }
  foreach($tab_matiere_code as $eleve_id => $tab_eleve_code)
  {
    foreach($tab_eleve_code as $rubrique_id => $matiere_code)
    {
      if(isset($tab_matieres_code_dispense[$eleve_id]))
      {
        foreach($tab_matieres_code_dispense[$eleve_id] as $masque_recherche => $bool)
        {
          if(preg_match('/^'.$masque_recherche.'$/',$matiere_code))
          {
            $tab_rubriques_id_dispense[$eleve_id][$rubrique_id] = TRUE;
            break;
          }
        }
      }
      else
      {
        $tab_rubriques_id_dispense[$eleve_id] = array();
      }
    }
  }
}

// Affichage du résultat

$nb_cols = max( 40 , floor( 120 / count($tab_rubrique) ) );
$effectif = count($tab_eleve_id) - 1;
Json::add_row( 'html' , '<h2>'.$sous_titre.'</h2>' );
Json::add_row( 'html' , '<div><b>'.html($periode_nom.' | '.$groupe_nom).' (&times;'.$effectif.')'.' :</b> <button type="button" class="valider" disabled="disabled">Enregistrement automatique !</button> <button id="fermer_zone_action_eleve" type="button" class="retourner">Retour</button></div>' );
Json::add_row( 'html' , '<div class="i">'.$texte_prof_principal.'</div><hr>' );
Json::add_row( 'html' , '<table><tbody><tr><td class="nu"></td><th colspan="2">'.implode('</th><th colspan="2">',$tab_rubrique).'</th></tr>' );
$pourcent = in_array($PAGE_COLONNE,array('objectif','position')) ? '/4' : ( ($PAGE_COLONNE=='moyenne') ? '' : '&nbsp;%' ) ;
foreach($tab_eleve_id as $eleve_id => $tab_eleve)
{
  extract($tab_eleve); // $eleve_prenom $eleve_nom $eleve_dispositif
  // Si cet élève a été évalué...
  if( !$eleve_id || isset($tab_saisie[$eleve_id]['eval']) )
  {
    $indication_dispositifs = empty($eleve_dispositif) ? '' : '<div class="dispositif">'.implode(' ',array_keys($eleve_dispositif)).'</div>' ;
    Json::add_row( 'html' , '<tr data-user_id="'.$eleve_id.'"><th>'.html($eleve_nom).'<br>'.html($eleve_prenom).$indication_dispositifs.'</th>' );
    // On passe en revue les rubriques...
    foreach($tab_rubrique as $livret_rubrique_id => $tab)
    {
      $position_info     = isset($tab_saisie[$eleve_id]['eval'][$livret_rubrique_id]['position'])     ? $tab_saisie[$eleve_id]['eval'][$livret_rubrique_id]['position']     : $tab_saisie_initialisation ;
      $appreciation_info = isset($tab_saisie[$eleve_id]['eval'][$livret_rubrique_id]['appreciation']) ? $tab_saisie[$eleve_id]['eval'][$livret_rubrique_id]['appreciation'] : $tab_saisie_initialisation ;
      // Positionnement
      if($eleve_id)
      {
        $dispense_possible = !empty($tab_rubriques_id_dispense[$eleve_id][$livret_rubrique_id]) ? 1 : 0 ;
        $bouton_dispense = ( $dispense_possible && ($position_info['saisie_valeur']!='DI') ) ? ' <button type="button" class="dispenser"'.infobulle('Déclarer l’élève dispensé(e).').'>&nbsp;</button>' : '' ;
        $pourcentage = !is_null($position_info['saisie_valeur']) ? $position_info['saisie_valeur'] :  FALSE ;
        if( in_array($PAGE_COLONNE,array('objectif','position')) )
        {
          $indice = OutilBilan::determiner_degre_maitrise($pourcentage);
          $note = ($indice) ? $indice : '' ;
        }
        else if( in_array($PAGE_COLONNE,array('moyenne','pourcentage')) )
        {
          if($position_info['saisie_valeur']==='DI') // sur les vieilles versions de PHP le test ( 0 == 'blablabla' ) renvoie TRUE !
          {
            $note = 'DI';
          }
          elseif($position_info['saisie_valeur']!==NULL)
          {
            $note = ($PAGE_COLONNE=='moyenne') ? round(($pourcentage/5),1) : $pourcentage ;
          }
          else
          {
            $note = '';
          }
        }
        // type="number" non retenu pour pouvoir intercepter la sortie du champ de saisie et ne pas soumettre un enregistrement à chaque incrément/décrément
        if( $BILAN_ETAT <= $_SESSION['OFFICIEL']['LIVRET_ETAPE_MAX_MAJ_POSITIONNEMENTS'] )
        {
          if(!$dispense_possible)
          {
            $champ = '<input required="required" id="note_'.$eleve_id.'_'.$rubrique_id.'" name="f_saisie_express" type="text" value="'.$note.'" size="3">'.$pourcent;
          }
          else
          {
            // En théorie il pourrait y avoir un souci avec le symbole "%" non masqué.
            // Mais s’il y a dispense c’est qu’on est en 3e avec note sur 20, donc normalement ce cas ne se produit pas.
            $value   = ($note == 'DI') ? '' : $note ;
            $class   = ($note != 'DI') ? '' : ' class="hide"' ;
            $checked = ($note != 'DI') ? '' : ' checked="checked"' ;
            $champ = '<input required="required" id="note_'.$eleve_id.'_'.$rubrique_id.'" name="f_saisie_express" type="text" value="'.$value.'" size="3"'.$class.'>'.$pourcent.' <label for="dispense_'.$eleve_id.'_'.$rubrique_id.'"><input id="dispense_'.$eleve_id.'_'.$rubrique_id.'" name="f_dispense_express" type="checkbox" value="1" size="3"'.$checked.'> DI</label>';
          }
        }
        else
        {
          $note_affichage = ($note != 'DI') ? $note.$pourcent : 'Dispensé' ;
          $champ = $note_affichage.' '.infobulle('Modification des positionnements interdite par la configuration de ce livret.',TRUE) ;
        }
        Json::add_row( 'html' , '<td data-id="'.$position_info['saisie_id'].'" data-rubrique_id="'.$livret_rubrique_id.'" data-page_colonne='.$PAGE_COLONNE.' data-dispense="'.$dispense_possible.'" class="now">'.$champ.'</td>' );
      }
      else if($PAGE_MOYENNE_CLASSE)
      {
        $pourcentage = !is_null($position_info['saisie_valeur']) ? $position_info['saisie_valeur'] :  FALSE ;
        if( in_array($PAGE_COLONNE,array('objectif','position')) )
        {
          $indice = OutilBilan::determiner_degre_maitrise($pourcentage);
          $note = ($indice) ? $indice.$pourcent : '-' ;
        }
        else if( in_array($PAGE_COLONNE,array('moyenne','pourcentage')) )
        {
          $note = ($position_info['saisie_valeur']!==NULL) ? ( ($PAGE_COLONNE=='moyenne') ? round(($pourcentage/5),1) : $pourcentage.$pourcent ) : '-' ;
        }
        Json::add_row( 'html' , '<td class="now">'.$note.'</td>' );
      }
      else
      {
        Json::add_row( 'html' , '<td></td>' );
      }
      // Acquisitions, progrès et difficultés éventuelles
      $appreciation = ($appreciation_info['saisie_valeur']) ? html($appreciation_info['saisie_valeur']) : '' ;
      $nb_rows = ($appreciation) ? max( 2 , floor( mb_strlen($appreciation)/($nb_cols*2/3) ) ) : 2 ;
      // attribut required sur le textarea pour pouvoir le styler en css si vide avec la propriété :invalid (@see https://stackoverflow.com/questions/7072576/can-i-select-empty-textareas-with-css)
      Json::add_row( 'html' , '<td data-id="'.$appreciation_info['saisie_id'].'" data-rubrique_id="'.$livret_rubrique_id.'" class="now"><textarea required="required" id="appr_'.$eleve_id.'x'.$livret_rubrique_id.'" name="f_saisie_express" rows="'.$nb_rows.'" cols="'.$nb_cols.'">'.html($appreciation).'</textarea></td>' );
    }
    Json::add_row( 'html' , '</tr>' );
  }
}
Json::add_row( 'html' , '</tbody></table>' );
Json::end( TRUE );

?>
