<?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) && (!in_array($_POST['f_action'],array('lister_evaluations','ordonner','indiquer_eleves_deja','saisir','voir','voir_repart','archiver_repart','imprimer_cartouche','generer_tableau_scores_vierge_csv','generer_tableau_scores_rempli_csv','generer_tableau_scores_vierge_pdf','generer_tableau_scores_rempli_pdf'))) ) {Json::end( FALSE , 'Action désactivée pour la démo.' );}

$action                   = Clean::post('f_action'                  , 'texte');
$type                     = Clean::post('f_type'                    , 'texte');
$aff_classe_txt           = Clean::post('f_aff_classe'              , 'texte' , '');
$aff_classe_id            = Clean::entier(substr($aff_classe_txt,1));
$aff_periode              = Clean::post('f_aff_periode'             , 'entier');
$eval_restriction_fini    = Clean::post('f_eval_restriction_fini'   , 'bool');
$eval_restriction_proprio = Clean::post('f_eval_restriction_proprio', 'bool');
$date_debut               = Clean::post('f_date_debut'              , 'date_fr');
$date_fin                 = Clean::post('f_date_fin'                , 'date_fr');
$ref                      = Clean::post('f_ref'                     , 'texte');
$date_devoir              = Clean::post('f_date_devoir'             , 'date_fr');
$choix_devoir_visible     = Clean::post('f_choix_devoir_visible'    , 'texte');
$date_devoir_visible      = Clean::post('f_date_devoir_visible'     , 'date_fr'); // JJ/MM/AAAA ou rien de transmis
$choix_saisie_visible     = Clean::post('f_choix_saisie_visible'    , 'texte');
$date_saisie_visible      = Clean::post('f_date_saisie_visible'     , 'date_fr'); // JJ/MM/AAAA ou rien de transmis
$date_autoeval            = Clean::post('f_date_autoeval'           , 'date_fr'); // JJ/MM/AAAA mais peut valoir 00/00/0000
$heure_autoeval           = Clean::post('f_heure_autoeval'          , 'entier'); // HH -> H
$minute_autoeval          = Clean::post('f_minute_autoeval'         , 'entier'); // MM -> M
$description              = Clean::post('f_description'             , 'texte');
$voir_repartition         = Clean::post('f_voir_repartition'        , 'bool');
$diagnostic               = Clean::post('f_diagnostic'              , 'bool');
$pluriannuel              = Clean::post('f_pluriannuel'             , 'bool');
$mode_discret             = Clean::post('f_mode_discret'            , 'bool');
$doc_sujet                = Clean::post('f_doc_sujet'               , 'url');
$doc_corrige              = Clean::post('f_doc_corrige'             , 'url');
$sujets_nombre            = Clean::post('f_sujets_nombre'           , 'entier');
$corriges_nombre          = Clean::post('f_corriges_nombre'         , 'entier');
$copies_nombre            = Clean::post('f_copies_nombre'           , 'entier');
$groupe                   = Clean::post('f_groupe'                  , 'lettres_chiffres');
$groupe_nom               = Clean::post('f_groupe_nom'              , 'texte');
$eleves_ordre             = Clean::post('f_eleves_ordre'            , 'eleves_ordre', 'nom'); // Non transmis par Safari si dans le <span> avec la classe "hide".
$equipe                   = Clean::post('f_equipe'                  , 'bool'); // Non transmis par Safari si dans le <span> avec la classe "hide".
$eleve_id                 = Clean::post('f_eleve_id'                , 'entier');
$msg_objet                = Clean::post('f_msg_objet'               , 'texte');
$msg_data                 = Clean::post('f_msg_data'                , 'texte');
$msg_url                  = Clean::post('f_msg_url'                 , 'url');
$msg_autre                = Clean::post('f_msg_autre'               , 'texte');
$mime_type                = Clean::post('f_mime_type'               , 'texte');
$repartition_type         = Clean::post('f_repartition_type'        , 'texte');
$cart_all_eleves          = Clean::post('box_all_eleves'            , 'bool');
$cart_autoeval            = Clean::post('f_autoeval'                , 'bool');
$cart_detail              = Clean::post('f_detail'                  , 'texte');
$cart_cases_nb            = Clean::post('f_cases_nb'                , 'entier');
$cart_ordre               = Clean::post('f_ordre'                   , 'entier');
$cart_contenu             = Clean::post('f_contenu'                 , 'texte');
$cart_restriction_item    = Clean::post('f_restriction_item'        , 'texte');
$cart_restriction_eleve   = Clean::post('f_restriction_eleve'       , 'texte');
$orientation              = Clean::post('f_orientation'             , 'texte');
$marge_min                = Clean::post('f_marge_min'               , 'texte');
$couleur                  = Clean::post('f_couleur'                 , 'texte');
$fond                     = Clean::post('f_fond'                    , 'texte');
$legende                  = Clean::post('f_legende'                 , 'texte');
$aff_reference            = Clean::post('f_reference'               , 'bool');
$aff_coef                 = Clean::post('f_coef'                    , 'bool');
$aff_socle                = Clean::post('f_socle'                   , 'bool');
$aff_domaine              = Clean::post('f_domaine'                 , 'bool');
$aff_theme                = Clean::post('f_theme'                   , 'bool');
$is_pdf4teachers          = Clean::post('f_pdf4teachers'            , 'bool');
$repart_categorie_autre   = Clean::post('f_categorie_autre'         , 'bool');
$repart_ref_pourcentage   = Clean::post('f_ref_pourcentage'         , 'texte');
$cart_hauteur             = Clean::post('f_hauteur'                 , 'texte');
$doc_objet                = Clean::post('f_doc_objet'               , 'texte');
$doc_url                  = Clean::post('f_doc_url'                 , 'url');
$fini                     = Clean::post('f_fini'                    , 'texte');
$masque                   = Clean::post('f_masque'                  , 'texte');
$fichier_import           = Clean::post('f_fichier'                 , 'fichier');

$chemin_devoir      = CHEMIN_DOSSIER_DEVOIR.$_SESSION['BASE'].DS;
$url_dossier_devoir = URL_DIR_DEVOIR.$_SESSION['BASE'].'/';
$fnom_export = $_SESSION['BASE'].'_'.Clean::fichier($groupe_nom).'_'.Clean::fichier($description).'_'.FileSystem::generer_fin_nom_fichier__date_et_alea();
$dossier_mult = CHEMIN_DOSSIER_IMPORT.$_SESSION['BASE'].'_'.$_SESSION['USER_ID'].DS;

$time_autoeval = sprintf("%02u",$heure_autoeval).':'.sprintf("%02u",$minute_autoeval);

// Si "ref" est renseigné (pour Éditer ou Retirer ou Saisir ou ...), il contient l’id de l’évaluation + '_' + l’initiale du type de groupe + l’id du groupe
// Dans le cas d’une duplication, "ref" sert à retrouver l’évaluation d’origine pour évenuellement récupérer l’ordre des items
if( !is_null($ref) && mb_strpos($ref,'_') )
{
  list($devoir_id,$groupe_temp) = explode('_',$ref,2);
  $devoir_id = Clean::entier($devoir_id);
  // Si "groupe" est transmis en POST (pour Ajouter ou Éditer), il faut le prendre comme référence nouvelle ; sinon, on prend le groupe extrait de "ref"
  $groupe = ($groupe) ? $groupe : Clean::lettres_chiffres($groupe_temp) ;
}
else
{
  $devoir_id = 0;
}

// Si "groupe" est transmis via "ref", il contient l’initiale du type de groupe + l’id du groupe
if($groupe)
{
  $groupe_type_initiale = $groupe[0];
  $tab_groupe  = array('classe'=>'C','groupe'=>'G','besoin'=>'B','eval'=>'E');
  $groupe_type = array_search($groupe_type_initiale,$tab_groupe);
  $groupe_id   = Clean::entier(mb_substr($groupe,1));
}
else
{
  $groupe_type = 'eval';
  $groupe_id   = 0;
}

// Contrôler la liste des élèves transmis (cartouche d’une évaluation)
$tab_cart_eleve = Clean::post('f_eleve', array('array',','));
$tab_cart_eleve = array_filter( Clean::map('entier',$tab_cart_eleve) , 'positif' );
// Contrôler la liste des items transmis (ordre dans l’évaluation)
$tab_id      = Clean::post('tab_id', array('array',','));
$tab_id      = array_filter( Clean::map('entier',$tab_id) , 'positif' );
// Contrôler la liste des items transmis
$tab_items   = Clean::post('f_compet_liste', array('array','_'));
$tab_items   = array_filter( Clean::map('entier',$tab_items) , 'positif' );
// Contrôler la liste des élèves transmis (sur des élèves sélectionnés uniquement)
$tab_eleves  = Clean::post('f_eleve_liste', array('array','_'));
$tab_eleves  = array_filter( Clean::map('entier',$tab_eleves) , 'positif' );
// Contrôler la liste des profs transmis
$tab_profs   = array();
$tab_droits  = array( 'v'=>'voir' , 's'=>'saisir' , 'm'=>'modifier' );
$tab_temp    = Clean::post('f_prof_liste', array('array','_'));
$profs_liste = implode('_',$tab_temp);
foreach($tab_temp as $valeur)
{
  $droit   = $valeur[0];
  $id_prof = (int)substr($valeur,1);
  if( isset($tab_droits[$droit]) && ($id_prof>0) && ( ($action!='dupliquer') || ($id_prof!=$_SESSION['USER_ID']) ) )
  {
    $tab_profs[$id_prof] = $tab_droits[$droit];
  }
  else
  {
    $profs_liste = str_replace( array( '_'.$valeur , $valeur.'_' , $valeur ) , '' , $profs_liste );
  }
}
$nb_items  = count($tab_items);
$nb_eleves = count($tab_eleves);
$nb_profs  = count($tab_profs);
// Liste des notes transmises
$tab_notes = Clean::post('f_notes', array('array',','));

$abonnement_ref_edition = 'devoir_edition';
$abonnement_ref_partage = 'devoir_prof_partage';
$abonnement_ref_saisie  = 'devoir_saisie';

$tab_repartition = array( 0=>'non' , 1=>'&#10003;' );
$tab_diagnostic  = array( 0=>'non' , 1=>'&#10003;' );
$tab_pluriannuel = array( 0=>'non' , 1=>'&#10003;' );

$tab_texte_devoir_visible = array(
  'toujours'  => 'sans délai',
  'veille'    => 'la veille',
  'devoir'    => 'jour du devoir',
  'lendemain' => 'le lendemain',
  'perso'     => 'date transmise'
);
$tab_texte_saisie_visible = array(
  'toujours'     => 'sans délai',
  'devoir'       => 'jour du devoir',
  'lendemain'    => 'le lendemain',
  'perso'        => 'date transmise'
);
if( $choix_devoir_visible && !is_null($date_devoir) )
{
  if($choix_devoir_visible == 'toujours')
  {
    $date_devoir_visible = '00/00/0000'; // sera transformé en NULL pour SQL ; évite un test de transmission négatif
  }
  else if($choix_devoir_visible == 'devoir')
  {
    $date_devoir_visible = $date_devoir;
  }
  else if($choix_devoir_visible == 'veille')
  {
    $date_devoir_visible = To::jour_decale( $date_devoir , '-1' , 'fr' );
  }
  else if($choix_devoir_visible == 'lendemain')
  {
    $date_devoir_visible = To::jour_decale( $date_devoir , '+1' , 'fr' );
  }
  else if( ($choix_devoir_visible == 'perso') && ( $date_devoir_visible == $date_devoir ) )
  {
    $choix_devoir_visible = 'devoir';
  }
  else if( ($choix_devoir_visible == 'perso') && ( $date_devoir_visible == To::jour_decale( $date_devoir , '-1' , 'fr' ) ) )
  {
    $choix_devoir_visible = 'veille';
  }
  else if( ($choix_devoir_visible == 'perso') && ( $date_devoir_visible == To::jour_decale( $date_devoir , '+1' , 'fr' ) ) )
  {
    $choix_devoir_visible = 'lendemain';
  }
}
if( $choix_saisie_visible && !is_null($date_devoir) )
{
  if($choix_saisie_visible == 'toujours')
  {
    $date_saisie_visible = '00/00/0000'; // sera transformé en NULL pour SQL ; évite un test de transmission négatif
  }
  else if($choix_saisie_visible == 'devoir')
  {
    $date_saisie_visible = $date_devoir;
  }
  else if($choix_saisie_visible == 'lendemain')
  {
    $date_saisie_visible = To::jour_decale( $date_devoir , '+1' , 'fr' );
  }
  else if( ($choix_saisie_visible == 'perso') && ( $date_saisie_visible == $date_devoir ) )
  {
    $choix_saisie_visible = 'devoir';
  }
  else if( ($choix_saisie_visible == 'perso') && ( $date_saisie_visible == To::jour_decale( $date_devoir , '+1' , 'fr' ) ) )
  {
    $choix_saisie_visible = 'lendemain';
  }
}

// ////////////////////////////////////////////////////////////////////////////////////////////////////
// Afficher une liste d’évaluations
// ////////////////////////////////////////////////////////////////////////////////////////////////////

if( ($action=='lister_evaluations') && $type && ( ($type=='selection') || ($aff_classe_txt && $aff_classe_id) ) && ( !is_null($aff_periode) || ($type=='selection') ) && ( $aff_periode || ($date_debut && $date_fin) ) )
{
  $tab_js_items = array();
  $tab_js_profs = array();
  $tab_js_sujet = array();
  $tab_js_corrige = array();
  $tab_js_eleves = array();
    // Restreindre la recherche à une période donnée, cas d’une date personnalisée (toujours le cas pour une sélection d’élèves)
  if(!$aff_periode)
  {
    // Formater les dates
    $date_debut_sql = To::date_french_to_sql($date_debut);
    $date_fin_sql   = To::date_french_to_sql($date_fin);
    // Vérifier que la date de début est antérieure à la date de fin
    if($date_debut_sql>$date_fin_sql)
    {
      Json::end( FALSE , 'Date de début postérieure à la date de fin !' );
    }
  }
  // Restreindre la recherche à une période donnée, cas d’une période associée à une classe ou à un groupe
  else
  {
    $DB_ROW = DB_STRUCTURE_COMMUN::DB_recuperer_dates_periode( $aff_classe_id , $aff_periode );
    if(empty($DB_ROW))
    {
      Json::end( FALSE , 'Cette classe et cette période ne sont pas reliées !' );
    }
    // Formater les dates
    $date_debut_sql = $DB_ROW['jointure_date_debut'];
    $date_fin_sql   = $DB_ROW['jointure_date_fin'];
  }
  Form::save_choix('evaluation_affichage');
  // Lister les évaluations
  $classe_id = ($aff_classe_txt!='d2') ? $aff_classe_id : -1 ; // 'd2' est transmis si on veut toutes les classes / tous les groupes ; classe_id vaut 0 si selection d’élèves
  $DB_TAB = DB_STRUCTURE_PROFESSEUR::DB_lister_devoirs_prof( $_SESSION['USER_ID'] , $classe_id , $date_debut_sql , $date_fin_sql , $eval_restriction_fini , $eval_restriction_proprio );
  if(!empty($DB_TAB))
  {
    // Récupérer le nb de saisies déjà effectuées par évaluation (ça posait trop de problème dans la requête précédente : saisies comptées plusieurs fois, évaluations sans saisies non retournées...)
    $tab_devoir_id = array();
    foreach($DB_TAB as $DB_ROW)
    {
      $tab_devoir_id[$DB_ROW['devoir_id']] = $DB_ROW['devoir_id'];
    }
    $tab_nb_saisies_effectuees = array_fill_keys($tab_devoir_id,0);
    $DB_TAB2 = DB_STRUCTURE_PROFESSEUR::DB_lister_nb_saisies_par_evaluation( implode(',',$tab_devoir_id) );
    foreach($DB_TAB2 as $DB_ROW)
    {
      $tab_nb_saisies_effectuees[$DB_ROW['devoir_id']] = $DB_ROW['saisies_nombre'];
    }
    // Récupérer les effectifs des classes et groupes
    $tab_effectifs = array();
    if($type=='groupe')
    {
      $DB_TAB2 = DB_STRUCTURE_PROFESSEUR::DB_lister_effectifs_groupes();
      foreach($DB_TAB2 as $DB_ROW)
      {
        $tab_effectifs[$DB_ROW['groupe_id']] = $DB_ROW['eleves_nombre'];
      }
    }
    foreach($DB_TAB as $DB_ROW)
    {
      // Profs avec qui on partage des droits
      if(!$DB_ROW['partage_listing'])
      {
        $profs_liste = '';
        $profs_nombre = 'non';
        $profs_class  = ' class="hc"';
      }
      else
      {
        $profs_liste  = $DB_ROW['partage_listing'];
        $nb_profs = mb_substr_count($profs_liste,'_')+1;
        $profs_nombre = ($nb_profs+1).' profs';
        $profs_class  = classbulle('profs');
      }
      // Droit sur cette évaluation
      if($DB_ROW['proprio_id']==$_SESSION['USER_ID'])
      {
        $niveau_droit = 4; // propriétaire
      }
      elseif($profs_liste) // forcément
      {
        $search_liste = '_'.$profs_liste.'_';
        if( strpos( $search_liste, '_m'.$_SESSION['USER_ID'].'_' ) !== FALSE )
        {
          $niveau_droit = 3; // modifier
        }
        elseif( strpos( $search_liste, '_s'.$_SESSION['USER_ID'].'_' ) !== FALSE )
        {
          $niveau_droit = 2; // saisir
        }
        elseif( strpos( $search_liste, '_v'.$_SESSION['USER_ID'].'_' ) !== FALSE )
        {
          $niveau_droit = 1; // voir
        }
        else
        {
          Json::end( FALSE , 'Droit attribué sur le devoir n°'.$DB_ROW['devoir_id'].' non trouvé !' );
        }
      }
      else
      {
        Json::end( FALSE , 'Vous n’êtes ni propriétaire ni bénéficiaire de droits sur le devoir n°'.$DB_ROW['devoir_id'].' !' );
      }
      $date_affich   = To::date_sql_to_french($DB_ROW['devoir_date']);
      if(is_null($DB_ROW['devoir_visible_date']))
      {
        $choix_devoir_visible = 'toujours';
      }
      else if( $DB_ROW['devoir_date'] == $DB_ROW['devoir_visible_date'] )
      {
        $choix_devoir_visible = 'devoir';
      }
      else if( $DB_ROW['devoir_date'] == To::jour_decale( $DB_ROW['devoir_visible_date'] , '-1' , 'sql' ) )
      {
        $choix_devoir_visible = 'lendemain';
      }
      else if( $DB_ROW['devoir_date'] == To::jour_decale( $DB_ROW['devoir_visible_date'] , '+1' , 'sql' ) )
      {
        $choix_devoir_visible = 'veille';
      }
      else
      {
        $choix_devoir_visible = 'perso';
      }
      if(is_null($DB_ROW['devoir_saisie_visible_date']))
      {
        $choix_saisie_visible = 'toujours';
      }
      else if( $DB_ROW['devoir_date'] == $DB_ROW['devoir_saisie_visible_date'] )
      {
        $choix_saisie_visible = 'devoir';
      }
      else if( $DB_ROW['devoir_date'] == To::jour_decale( $DB_ROW['devoir_saisie_visible_date'] , '-1' , 'sql' ) )
      {
        $choix_saisie_visible = 'lendemain';
      }
      else
      {
        $choix_saisie_visible = 'perso';
      }
      $texte_devoir_visible  = ($choix_devoir_visible != 'perso') ? $tab_texte_devoir_visible[$choix_devoir_visible] : To::date_sql_to_french($DB_ROW['devoir_visible_date']) ;
      $texte_saisie_visible  = ($choix_saisie_visible != 'perso') ? $tab_texte_saisie_visible[$choix_saisie_visible] : To::date_sql_to_french($DB_ROW['devoir_saisie_visible_date']) ;
      $date_autoeval  = ($DB_ROW['devoir_autoeval_date']===NULL) ? 'non' : To::datetime_sql_to_french($DB_ROW['devoir_autoeval_date'],FALSE) ;
      $bulle_autoeval = ($DB_ROW['devoir_autoeval_date']===NULL) ? '' : infobulle(To::datetime_sql_to_french($DB_ROW['devoir_autoeval_date'])) ;
      $class_autoeval = ($bulle_autoeval) ? 'hc bulle' : 'hc' ;
      $time_autoeval  = ($bulle_autoeval) ? substr($DB_ROW['devoir_autoeval_date'],11,5) : '23:59' ;
      $ref = $DB_ROW['devoir_id'].'_'.Clean::upper($DB_ROW['groupe_type'][0]).$DB_ROW['groupe_id'];
      $is = ($DB_ROW['items_nombre']>1) ? 's' : '';
      $us = ($type=='groupe') ? '' : ( ($DB_ROW['users_nombre']>1) ? 's' : '' );
      $ss = ($DB_ROW['sujets_nombre']>1) ? 's' : '' ;
      $cs = ($DB_ROW['corriges_nombre']>1) ? 's' : '' ;
      $ps = ($DB_ROW['copies_nombre']>1) ? 's' : '' ;
      $eleves_class = ($type=='selection') ? classbulle('eleves') : '' ;
      $items_class  = ($DB_ROW['items_nombre']) ? classbulle('items') : '' ;
      $image_sujet   = ($DB_ROW['devoir_doc_sujet'])   ? '<a href="'.FileSystem::verif_lien_safe($DB_ROW['devoir_doc_sujet']).'" target="_blank" rel="noopener noreferrer" class="no_puce"><img alt="sujet" src="./_img/document/sujet_oui.png"'.infobulle('Sujet commun.').'></a>' : '' ;
      $image_corrige = ($DB_ROW['devoir_doc_corrige']) ? '<a href="'.FileSystem::verif_lien_safe($DB_ROW['devoir_doc_corrige']).'" target="_blank" rel="noopener noreferrer" class="no_puce"><img alt="corrigé" src="./_img/document/corrige_oui.png"'.infobulle('Corrigé commun.').'></a>' : '' ;
      $image_sujets   = ($DB_ROW['sujets_nombre'])   ? '<img alt="sujet" src="./_img/document/sujet_multi.png"'.infobulle($DB_ROW['sujets_nombre'].' sujet'.$ss.' individuel'.$ss).'>' : '' ;
      $image_corriges = ($DB_ROW['corriges_nombre']) ? '<img alt="sujet" src="./_img/document/corrige_multi.png"'.infobulle($DB_ROW['corriges_nombre'].' corrigé'.$cs.' individuel'.$cs).'>' : '' ;
      $image_copies   = ($DB_ROW['copies_nombre'])   ? '<img alt="sujet" src="./_img/document/copie_multi.png"'.infobulle($DB_ROW['copies_nombre'].' copie'.$ps).'>' : '' ;
      $effectif_eleve = ($type=='groupe') ? ( isset($tab_effectifs[$DB_ROW['groupe_id']]) ? $tab_effectifs[$DB_ROW['groupe_id']] : 0 ) : $DB_ROW['users_nombre'] ;
      $nb_saisies_possibles = $DB_ROW['items_nombre']*$effectif_eleve;
      $remplissage_eleves   = ($type=='groupe') ? html($DB_ROW['groupe_nom']) : $DB_ROW['users_nombre'].' élève'.$us ;
      $remplissage_nombre   = $tab_nb_saisies_effectuees[$DB_ROW['devoir_id']].'/'.$nb_saisies_possibles ;
      $remplissage_class    = (!$tab_nb_saisies_effectuees[$DB_ROW['devoir_id']]) ? 'br' : ( ($tab_nb_saisies_effectuees[$DB_ROW['devoir_id']]<$nb_saisies_possibles) ? 'bj' : 'bv' ) ;
      $remplissage_class2   = ($DB_ROW['devoir_fini']) ? ' bf' : '' ;
      $remplissage_contenu  = ($DB_ROW['devoir_fini']) ? '<span>terminé</span><i>'.$remplissage_nombre.'</i>' : '<span>'.$remplissage_nombre.'</span><i>terminé</i>' ;
      $remplissage_lien1    = ($niveau_droit<4)  ? '' : '<a href="#fini" class="fini"'.infobulle('Cliquer pour indiquer (ou pas) qu’il n’y a plus de saisies à effectuer.').'>' ;
      $remplissage_lien2    = ($niveau_droit<4)  ? '' : '</a>' ;
      $remplissage_td_title = ($niveau_droit==4) ? '' : infobulle('Clôture restreinte au propriétaire de l’évaluation ('.$DB_ROW['proprietaire'].').') ;
      $q_uploader_doc       = ($niveau_droit==4)
                            ? '<q class="uploader_doc"'.infobulle('Ajouter / retirer un sujet ou une correction.').'></q>'
                            : '<q class="uploader_doc_non"'.infobulle('Upload restreint au propriétaire de l’évaluation ('.html($DB_ROW['proprietaire']).').').'></q>' ;
      $q_modifier           = ($niveau_droit>=3)
                            ? '<q class="modifier"'.infobulle('Modifier cette évaluation (date, description, ...).').'></q>'
                            : '<q class="modifier_non"'.infobulle('Action nécessitant le droit de modification (voir '.html($DB_ROW['proprietaire']).').').'></q>' ;
      $q_ordonner           = ($niveau_droit>=3)
                            ? '<q class="ordonner"'.infobulle('Réordonner les items de cette évaluation.').'></q>'
                            : '<q class="ordonner_non"'.infobulle('Action nécessitant le droit de modification (voir '.html($DB_ROW['proprietaire']).').').'></q>' ;
      $q_supprimer          = ($niveau_droit==4)
                            ? '<q class="supprimer"'.infobulle('Supprimer cette évaluation.').'></q>'
                            : '<q class="supprimer_non"'.infobulle('Suppression restreinte au propriétaire de l’évaluation ('.html($DB_ROW['proprietaire']).').').'></q>' ;
      $q_saisir             = ($niveau_droit>=2)
                            ? '<q class="saisir"'.infobulle('Saisir les acquisitions des élèves à cette évaluation.').'></q>'
                            : '<q class="saisir_non"'.infobulle('Action nécessitant le droit de saisie (voir '.html($DB_ROW['proprietaire']).').').'></q>' ;
      $q_module_envoyer     = !empty($_SESSION['MODULE']['GENERER_ENONCE']) ? '<q class="module_envoyer"'.infobulle('Générer des énoncés (module externe).').'></q>' : '' ;
      // Afficher une ligne du tableau
      Json::add_row( 'html' , '<tr>' );
      Json::add_row( 'html' ,   '<td>'.$date_affich.'</td>' );
      Json::add_row( 'html' ,   '<td data-option="'.$choix_devoir_visible.'">'.$texte_devoir_visible.'</td>' );
      Json::add_row( 'html' ,   '<td data-option="'.$choix_saisie_visible.'">'.$texte_saisie_visible.'</td>' );
      Json::add_row( 'html' ,   '<td data-time="'.$time_autoeval.'" class="'.$class_autoeval.'"'.$bulle_autoeval.'>'.$date_autoeval.'</td>' );
      Json::add_row( 'html' ,   '<td data-ordre="'.$DB_ROW['devoir_eleves_ordre'].'" data-equipe="'.$DB_ROW['devoir_equipe'].'"'.$eleves_class.'>'.$remplissage_eleves.'</td>' );
      Json::add_row( 'html' ,   '<td>'.html($DB_ROW['devoir_info']).'</td>' );
      Json::add_row( 'html' ,   '<td'.$items_class.'>'.$DB_ROW['items_nombre'].' item'.$is.'</td>' );
      Json::add_row( 'html' ,   '<td data-proprio="'.$DB_ROW['proprio_id'].'"'.$profs_class.'>'.$profs_nombre.'</td>' );
      Json::add_row( 'html' ,   '<td class="hc">'.$tab_diagnostic[$DB_ROW['devoir_diagnostic']].'</td>' );
      Json::add_row( 'html' ,   '<td class="hc">'.$tab_pluriannuel[$DB_ROW['devoir_pluriannuel']].'</td>' );
      Json::add_row( 'html' ,   '<td class="hc">'.$tab_repartition[$DB_ROW['devoir_voir_repartition']].'</td>' );
      Json::add_row( 'html' ,   '<td><span data-objet="sujet">'.$image_sujet.'</span><span data-objet="corrige">'.$image_corrige.'</span><span data-objet="sujets" data-nb="'.$DB_ROW['sujets_nombre'].'">'.$image_sujets.'</span><span data-objet="corriges" data-nb="'.$DB_ROW['corriges_nombre'].'">'.$image_corriges.'</span><span data-objet="copies" data-nb="'.$DB_ROW['copies_nombre'].'">'.$image_copies.'</span>'.$q_uploader_doc.'</td>' );
      Json::add_row( 'html' ,   '<td class="'.$remplissage_class.$remplissage_class2.'"'.$remplissage_td_title.'>'.$remplissage_lien1.$remplissage_contenu.$remplissage_lien2.'</td>' );
      Json::add_row( 'html' ,   '<td class="nu" id="devoir_'.$ref.'">' );
      Json::add_row( 'html' ,     $q_modifier );
      Json::add_row( 'html' ,     $q_ordonner );
      Json::add_row( 'html' ,     '<q class="dupliquer"'.infobulle('Dupliquer cette évaluation.').'></q>' );
      Json::add_row( 'html' ,     $q_supprimer );
      Json::add_row( 'html' ,     '<q class="imprimer"'.infobulle('Imprimer un cartouche pour cette évaluation.').'></q>' );
      Json::add_row( 'html' ,     $q_module_envoyer );
      Json::add_row( 'html' ,     $q_saisir );
      Json::add_row( 'html' ,     '<q class="voir"'.infobulle('Voir les acquisitions des élèves à cette évaluation.').'></q>' );
      Json::add_row( 'html' ,     '<q class="voir_repart"'.infobulle('Voir les répartitions des élèves à cette évaluation.').'></q>' );
      Json::add_row( 'html' ,   '</td>' );
      Json::add_row( 'html' , '</tr>' );
      $tab_js_items[$ref]   = $DB_ROW['items_listing'];
      $tab_js_profs[$ref]   = $profs_liste;
      $tab_js_sujet[$ref]   = FileSystem::verif_lien_safe($DB_ROW['devoir_doc_sujet']);
      $tab_js_corrige[$ref] = FileSystem::verif_lien_safe($DB_ROW['devoir_doc_corrige']);
      if($type=='selection')
      {
        $tab_js_eleves[$ref] = $DB_ROW['users_listing'];
      }
    }
  }
  else
  {
    Json::add_row( 'html' , '<tr class="vide"><td class="nu probleme" colspan="10">Cliquer sur l’icône ci-dessus (symbole "+" dans un rond vert) pour ajouter une évaluation.</td><td class="nu"></td></tr>' );
  }
  Json::add_row( 'tab_items'   , json_encode($tab_js_items) );
  Json::add_row( 'tab_profs'   , json_encode($tab_js_profs) );
  Json::add_row( 'tab_sujet'   , json_encode($tab_js_sujet) );
  Json::add_row( 'tab_corrige' , json_encode($tab_js_corrige) );
  Json::add_row( 'tab_eleves'  , json_encode($tab_js_eleves) );
  Json::end( TRUE );
}

// ////////////////////////////////////////////////////////////////////////////////////////////////////
// Importer une évaluation depuis un module externe
// ////////////////////////////////////////////////////////////////////////////////////////////////////

if($action=='importer_evaluation')
{
  // On récupère le fichier
  if(empty($fichier_import))
  {
    Json::end( FALSE , 'Nom du fichier non transmis !' );
  }
  if(empty($_SESSION['MODULE']['IMPORTER_EVALUATION']))
  {
    Json::end( FALSE , 'Pas de module externe enregistré pour traiter cette demande !' );
  }
  $fichier_url = $_SESSION['MODULE']['IMPORTER_EVALUATION'].$fichier_import;
  $fichier_contenu_json = cURL::get_contents($fichier_url);
  if(substr($fichier_contenu_json,0,6)=='Erreur')
  {
    Json::end( FALSE , $fichier_contenu_json );
  }
  // On vérifie le contenu principal
  $tab_fichier_contenu = json_decode($fichier_contenu_json,TRUE);
  if($tab_fichier_contenu===NULL)
  {
    Json::end( FALSE , 'Le contenu récupéré n’est pas un JSON valide !' );
  }
  $date_devoir         = isset($tab_fichier_contenu['date_devoir'])         ? Clean::date_fr($tab_fichier_contenu['date_devoir'])         : NULL ;
  $date_devoir_visible = isset($tab_fichier_contenu['date_devoir_visible']) ? Clean::date_fr($tab_fichier_contenu['date_devoir_visible']) : NULL ;
  $date_saisie_visible = isset($tab_fichier_contenu['date_saisie_visible']) ? Clean::date_fr($tab_fichier_contenu['date_saisie_visible']) : NULL ;
  $description         = isset($tab_fichier_contenu['intitule'])            ? Clean::texte($tab_fichier_contenu['intitule'],60)           : NULL ;
  $voir_repartition    = isset($tab_fichier_contenu['repartition'])         ? Clean::entier($tab_fichier_contenu['repartition'])          : NULL ;
  $diagnostic          = isset($tab_fichier_contenu['diagnostic'])          ? Clean::entier($tab_fichier_contenu['diagnostic'])           : NULL ;
  $pluriannuel         = isset($tab_fichier_contenu['pluriannuel'])         ? Clean::entier($tab_fichier_contenu['pluriannuel'])          : NULL ;
  $tab_saisie          = isset($tab_fichier_contenu['saisie'])              ? $tab_fichier_contenu['saisie']                              : NULL ;
  if( !$date_devoir || !$date_devoir_visible || !$date_saisie_visible || !$description || !isset($tab_repartition[$voir_repartition]) || !isset($tab_diagnostic[$diagnostic]) || !isset($tab_pluriannuel[$pluriannuel]) || is_null($diagnostic) || is_null($pluriannuel) || !is_array($tab_saisie) )
  {
    Json::end( FALSE , 'Le contenu récupéré n’est pas conforme à ce qui est attendu !' );
  }
  // On vérifie les saisies transmises, avec soin car c'est un import externe
  $tab_eleves = array();
  $tab_items  = array();
  $tab_notes  = array();
  $tab_codes_notes = array();
  foreach( $_SESSION['NOTE_ACTIF'] as $note_id )
  {
    $tab_codes_notes[$_SESSION['NOTE'][$note_id]['CLAVIER']] = $note_id;
  }
  $tab_codes_notes += array( 'A' => 'AB', 'D' => 'DI', 'E' => 'NE', 'F' => 'NF', 'N' => 'NN', 'R' => 'NR', 'P' => 'PA',
  );
  foreach( $tab_saisie as $tmp_eleve_id => $tab_eleve_saisie )
  {
    $eleve_id = intval($tmp_eleve_id);
    if(!$eleve_id)
    {
      Json::end( FALSE , 'Identifiant d’élève "'.html($tmp_eleve_id).'" incorrect !' );
    }
    $tab_eleves[$eleve_id] = $eleve_id;
    foreach( $tab_eleve_saisie as $tmp_item_id => $note )
    {
      $item_id = intval($tmp_item_id);
      if(!$item_id)
      {
        Json::end( FALSE , 'Identifiant d’item "'.html($tmp_item_id).'" incorrect !' );
      }
      $tab_items[$item_id] = $item_id;
      $note = strval($note);
      if(!isset($tab_codes_notes[$note]))
      {
        Json::end( FALSE , 'Note transmise "'.html($note).'" incorrecte !' );
      }
      $tab_notes[] = $item_id.'x'.$eleve_id.'_'.$tab_codes_notes[$note];
    }
  }
  if($_SESSION['USER_JOIN_GROUPES']=='config')
  {
    Outil::verif_eleves_prof($tab_eleves);
  }
  Outil::verif_items_base($tab_items);
  $nb_eleves = count($tab_eleves);
  $nb_items  = count($tab_items);
  // On complète avec des variables complémentaires utiles pour la suite
  $type = 'selection';
  if( $date_devoir == $date_devoir_visible )
  {
    $choix_devoir_visible = 'devoir';
  }
  else if( $date_devoir == To::jour_decale( $date_devoir_visible , '+1' , 'sql' ) )
  {
    $choix_devoir_visible = 'veille';
  }
  else if( $date_devoir == To::jour_decale( $date_devoir_visible , '-1' , 'sql' ) )
  {
    $choix_devoir_visible = 'lendemain';
  }
  else
  {
    $choix_devoir_visible = 'perso';
  }
  if( $date_devoir == $date_saisie_visible )
  {
    $choix_saisie_visible = 'devoir';
  }
  else if( $date_devoir == To::jour_decale( $date_saisie_visible , '-1' , 'sql' ) )
  {
    $choix_saisie_visible = 'lendemain';
  }
  else
  {
    $choix_saisie_visible = 'perso';
  }
  $date_autoeval = '00/00/0000';
  $time_autoeval = '23:59';
  $eleves_ordre = 'classe';
  // On est prêt pour la prise en compte des données.
  // La suite s'effectue aux sections [Ajouter une nouvelle évaluation] et [Mettre à jour les items acquis par les élèves à une évaluation].
}
// ////////////////////////////////////////////////////////////////////////////////////////////////////
// Ajouter une nouvelle évaluation
// ////////////////////////////////////////////////////////////////////////////////////////////////////

if( (($action=='ajouter')||(($action=='dupliquer')&&($devoir_id))||($action=='importer_evaluation')) && $type && $date_devoir && $choix_devoir_visible && $date_devoir_visible && $choix_saisie_visible && $date_saisie_visible && $date_autoeval && $time_autoeval && $description && isset($tab_repartition[$voir_repartition]) && isset($tab_diagnostic[$diagnostic]) && isset($tab_pluriannuel[$pluriannuel]) && ( ($groupe_type && $groupe_id) || $nb_eleves ) && $nb_items && $eleves_ordre && !is_null($equipe) )
{
  if( $diagnostic && $pluriannuel )
  {
    Json::end( FALSE , 'Une évaluation ne peut pas être diagnostique et pluriannuelle !' );
  }
  $date_devoir_sql         = To::date_french_to_sql($date_devoir);
  $date_devoir_visible_sql = To::date_french_to_sql($date_devoir_visible);
  $date_saisie_visible_sql = To::date_french_to_sql($date_saisie_visible);
  $date_autoeval_sql       = To::date_french_to_sql($date_autoeval);
  $datetime_autoeval_sql   = is_null($date_autoeval_sql) ? NULL : $date_autoeval_sql.' '.$time_autoeval.':00';
  // Tester les dates
  $jour_debut_annee_scolaire = To::jour_debut_annee_scolaire('sql');
  $jour_fin_annee_scolaire   = To::jour_fin_annee_scolaire('sql');
  $date_devoir_stamp         = strtotime($date_devoir_sql);
  $date_devoir_visible_stamp = strtotime( !is_null($date_devoir_visible_sql)  ? $date_devoir_visible_sql  : '' );
  $date_saisie_visible_stamp = strtotime( !is_null($date_saisie_visible_sql)  ? $date_saisie_visible_sql  : '' );
  $date_autoeval_stamp       = strtotime( !is_null($date_autoeval_sql) ? $date_autoeval_sql : '' );
  $mini_stamp         = strtotime("-5 month");
  $maxi_stamp         = strtotime("+5 month");
  $maxi_visible_stamp = strtotime("+10 month");
  if( ($date_devoir_sql<$jour_debut_annee_scolaire) || ($date_devoir_sql>$jour_fin_annee_scolaire) )
  {
    Json::end( FALSE , 'Date devoir hors année scolaire ('.To::jour_debut_annee_scolaire('fr').' - '.To::jour_fin_annee_scolaire('fr').') !' );
  }
  if( !is_null($date_saisie_visible_sql) && ( $date_saisie_visible_sql < $date_devoir_sql ) )
  {
    // On autorise une date de visibilité des saisies avant la date du devoir.
    // Par exemple un devoir volontairement daté en fin de période avec des saisies au fur et à mesure.
  }
  if( !is_null($date_saisie_visible_sql) && !is_null($date_devoir_visible_sql) && ( $date_saisie_visible_sql < $date_devoir_visible_sql ) )
  {
    Json::end( FALSE , 'Date visibilité saisies avant date visibilité devoir !' );
  }
  if( ($date_devoir_stamp<$mini_stamp) || ($date_devoir_stamp>$maxi_stamp) )
  {
    Json::end( FALSE , 'Date devoir trop éloignée !' );
  }
  if( !is_null($date_devoir_visible_sql) && ( ($date_devoir_visible_stamp<$mini_stamp) || ($date_devoir_visible_stamp>$maxi_visible_stamp) ) )
  {
    Json::end( FALSE , 'Date visibilité devoir trop éloignée !' );
  }
  if( !is_null($date_saisie_visible_sql) && ( ($date_saisie_visible_stamp<$mini_stamp) || ($date_saisie_visible_stamp>$maxi_visible_stamp) ) )
  {
    Json::end( FALSE , 'Date visibilité saisies trop éloignée !' );
  }
  if( !is_null($date_autoeval_sql) && ( ($date_autoeval_stamp<$mini_stamp) || ($date_autoeval_stamp>$maxi_stamp) ) )
  {
    Json::end( FALSE , 'Date fin auto-évaluation trop éloignée !' );
  }
  if( !is_null($date_autoeval_sql) && !is_null($date_devoir_visible_sql) && (TODAY_SQL<$date_devoir_visible_sql) )
  {
    Json::end( FALSE , 'Auto-évaluation impossible sans visibilité du devoir !' );
  }
  if( !is_null($date_autoeval_sql) && !is_null($date_saisie_visible_sql) && (TODAY_SQL<$date_saisie_visible_sql) )
  {
    Json::end( FALSE , 'Auto-évaluation impossible sans visibilité des saisies !' );
  }
  // Récupérer l’effectif de la classe ou du groupe
  $effectif_eleve = ($type=='groupe') ? DB_STRUCTURE_PROFESSEUR::DB_lister_effectifs_groupes($groupe_id) : $nb_eleves ;
  // Dans le cas d’une évaluation sur un regroupement, on vérifie qu’il n’est pas vide
  if(!$effectif_eleve)
  {
    Json::end( FALSE , 'Regroupement sans élève !' );
  }
  // Ordre des élèves
  $eleves_ordre = (($groupe_type=='classe')&&($eleves_ordre=='classe')) ? 'nom' : $eleves_ordre ;
  Form::save_choix('evaluation_gestion');
  // En cas de duplication d’une évaluation comportant un fichier d’énoncé ou de corrigé, il faut aussi en dupliquer ces documents sous un autre nom.
  // Sinon, lors de la suppression de l’un des devoirs, l’autre perd ses documents associés.
  // On ne peut pas intégrer dans le nouveau nom l’id du nouveau devoir car il n’est pas encore connu, mais on peut en modifier le timestamp.
  if($action=='dupliquer')
  {
    $tab_doc_objet = array( 'sujet' , 'corrige' );
    foreach($tab_doc_objet as $objet)
    {
      $masque_recherche = '#^'.str_replace('.','\.',$url_dossier_devoir).'devoir_([0-9]+)_('.$objet.')_([0-9]+)\.([a-z]{2,4})$#' ;
      $url_actuelle = ${'doc_'.$objet};
      if(preg_match( $masque_recherche , $url_actuelle ))
      {
        $masque_remplacement = $url_dossier_devoir.'devoir_$1_$2_'.time().'.$4';
        $url_nouvelle = preg_replace( $masque_recherche , $masque_remplacement , $url_actuelle );
        copy ( url_to_chemin($url_actuelle) , url_to_chemin($url_nouvelle) );
        ${'doc_'.$objet} = $url_nouvelle;
      }
    }
  }
  if($type=='selection')
  {
    // Commencer par 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
    $groupe_id = DB_STRUCTURE_REGROUPEMENT::DB_ajouter_groupe_par_prof( $_SESSION['USER_ID'] , $groupe_type , '' /*groupe_nom*/ , 0 /*niveau_id*/ );
  }
  // Insèrer l’enregistrement de l’évaluation
  $devoir_id2 = DB_STRUCTURE_PROFESSEUR::DB_ajouter_devoir( $_SESSION['USER_ID'] , $groupe_id , $date_devoir_sql , $description , $date_devoir_visible_sql ,$date_saisie_visible_sql , $datetime_autoeval_sql , $doc_sujet , $doc_corrige , $voir_repartition , $diagnostic , $pluriannuel , $eleves_ordre , $equipe );
  if($type=='selection')
  {
    // Affecter tous les élèves choisis
    DB_STRUCTURE_PROFESSEUR::DB_modifier_liaison_devoir_eleve( $_SESSION['USER_ID'] , $devoir_id2 , $groupe_id , $tab_eleves , 'creer' );
  }
  if($nb_profs)
  {
    // Affecter tous les profs choisis
    $tab_retour = DB_STRUCTURE_PROFESSEUR::DB_modifier_liaison_devoir_prof( $devoir_id2 , $tab_profs , 'creer' );
    // Notifications (rendues visibles ultérieurement) ; le mode discret ne d’applique volontairement pas ici car les modifications sont chirurgicales
    $listing_profs = implode(',',array_keys($tab_retour));
    $listing_abonnes = DB_STRUCTURE_NOTIFICATION::DB_lister_destinataires_listing_id( $abonnement_ref_partage , $listing_profs );
    if($listing_abonnes)
    {
      $notification_contenu = To::texte_genre_identite($_SESSION['USER_NOM'],FALSE,$_SESSION['USER_PRENOM'],TRUE,$_SESSION['USER_GENRE']).' vous partage son évaluation "'.$description.'" avec le droit ';
      $tab_texte_etat = array( 'voir'=>'de la visualiser / dupliquer.'."\r\n\r\n" , 'saisir'=>'d’en co-saisir les notes.'."\r\n\r\n" , 'modifier'=>'d’en modifier les paramètres.'."\r\n\r\n" );
      $notification_lien = "\r\n".'Pour y accéder :'."\r\n".Sesamail::adresse_lien_profond('page=evaluation&section=gestion_'.$type);
      $tab_abonnes = explode(',',$listing_abonnes);
      foreach($tab_abonnes as $abonne_id)
      {
        DB_STRUCTURE_NOTIFICATION::DB_ajouter_log_attente( $abonne_id , $abonnement_ref_partage , $devoir_id2 , NULL , $notification_contenu.$tab_texte_etat[$tab_profs[$abonne_id]].$notification_lien );
      }
    }
  }
  // Insérer les enregistrements des items de l’évaluation
  DB_STRUCTURE_PROFESSEUR::DB_modifier_liaison_devoir_item( $devoir_id2 , $tab_items , 'dupliquer' , $devoir_id );
  // Insérer les marqueurs d’évaluation 'PA' (cas d’une création d’évaluation depuis une synthèse, à partir d’items personnalisés par élève)
  $date_saisie_visible_sql = ( TODAY_SQL < $date_saisie_visible_sql ) ? $date_saisie_visible_sql : NULL ;
  if(!empty($_SESSION['tmp_req_user_item']))
  {
    $info = 'À saisir ('.To::texte_genre_identite($_SESSION['USER_NOM'],FALSE,$_SESSION['USER_PRENOM'],TRUE).')';
    foreach($_SESSION['tmp_req_user_item'] as $req)
    {
      list($eleve_id,$item_id) = explode('x',$req);
      DB_STRUCTURE_PROFESSEUR::DB_ajouter_saisie( $_SESSION['USER_ID'] , $eleve_id , $devoir_id2 , $item_id , $date_devoir_sql , 'PA' , $info , $date_saisie_visible_sql );
    }
    Session::_unset('tmp_req_user_item');
  }
  // Notifications (rendues visibles ultérieurement)
  if( !$mode_discret && DB_STRUCTURE_PROFESSEUR::DB_compter_devoir_matieres_non_experimentales($devoir_id) )
  {
    $listing_eleves = ($type=='selection') ? implode(',',$tab_eleves) : DB_STRUCTURE_PROFESSEUR::DB_recuperer_listing_eleves_id( $groupe_type , $groupe_id ) ;
    if($listing_eleves)
    {
      $listing_parents = DB_STRUCTURE_NOTIFICATION::DB_lister_parents_listing_id($listing_eleves);
      $listing_users = ($listing_parents) ? $listing_eleves.','.$listing_parents : $listing_eleves ;
      $listing_abonnes = DB_STRUCTURE_NOTIFICATION::DB_lister_destinataires_listing_id( $abonnement_ref_edition , $listing_users );
      if($listing_abonnes)
      {
        $adresse_lien_profond = Sesamail::adresse_lien_profond('page=evaluation&section=voir&devoir_id='.$devoir_id2.'&eleve_id=');
        $notification_date = ( TODAY_SQL < $date_devoir_visible_sql ) ? $date_devoir_visible_sql : NULL ;
        $notification_contenu = 'Évaluation "'.$description.'" du '.$date_devoir.' paramétrée par '.To::texte_genre_identite($_SESSION['USER_NOM'],FALSE,$_SESSION['USER_PRENOM'],TRUE,$_SESSION['USER_GENRE']).'.'."\r\n\r\n";
        $tab_abonnes = DB_STRUCTURE_NOTIFICATION::DB_lister_detail_abonnes_envois( $listing_abonnes , $listing_eleves , $listing_parents );
        foreach($tab_abonnes as $abonne_id => $tab_abonne)
        {
          foreach($tab_abonne as $eleve_id => $notification_intro_eleve)
          {
            $notification_lien = 'Voir le détail :'."\r\n".$adresse_lien_profond.$eleve_id;
            DB_STRUCTURE_NOTIFICATION::DB_ajouter_log_attente( $abonne_id , $abonnement_ref_edition , $devoir_id2 , $notification_date , $notification_intro_eleve.$notification_contenu.$notification_lien );
          }
        }
      }
    }
  }
  // Afficher le retour
  $texte_devoir_visible  = ($choix_devoir_visible != 'perso') ? $tab_texte_devoir_visible[$choix_devoir_visible] : To::date_sql_to_french($date_devoir_visible_sql) ;
  $texte_saisie_visible  = ($choix_saisie_visible != 'perso') ? $tab_texte_saisie_visible[$choix_saisie_visible] : To::date_sql_to_french($date_saisie_visible_sql) ;
  $date_autoeval  = ($date_autoeval=='00/00/0000') ? 'non' : $date_autoeval ;
  $bulle_autoeval = ($date_autoeval=='non') ? '' : infobulle(To::datetime_sql_to_french($datetime_autoeval_sql)) ;
  $class_autoeval = ($bulle_autoeval) ? 'hc bulle' : 'hc' ;
  $time_autoeval  = ($bulle_autoeval) ? $time_autoeval : '23:59' ;
  $ref = $devoir_id2.'_'.Clean::upper($groupe_type[0]).$groupe_id;
  $is = ($nb_items >1) ? 's' : '' ;
  $us = ($nb_eleves>1) ? 's' : '' ;
  $eleves_class = ($type=='selection') ? classbulle('eleves') : '' ;
  $profs_nombre = ($nb_profs) ? ($nb_profs+1).' profs' : 'non' ;
  $profs_class  = ($nb_profs) ? classbulle('profs') : ' class="hc"' ;
  $items_class  = ($nb_items) ? classbulle('items') : '' ;
  $image_sujet   = ($doc_sujet)   ? '<a href="'.$doc_sujet.'" target="_blank" rel="noopener noreferrer" class="no_puce"><img alt="sujet" src="./_img/document/sujet_oui.png"'.infobulle('Sujet commun.').'></a>' : '' ;
  $image_corrige = ($doc_corrige) ? '<a href="'.$doc_corrige.'" target="_blank" rel="noopener noreferrer" class="no_puce"><img alt="corrigé" src="./_img/document/corrige_oui.png"'.infobulle('Corrigé commun.').'></a>' : '' ;
  $nb_saisies_possibles = $nb_items*$effectif_eleve;
  $remplissage_eleves   = ($type=='groupe') ? '{{GROUPE_NOM}}' : $nb_eleves.' élève'.$us ;
  $remplissage_nombre   = '0/'.$nb_saisies_possibles ;
  $remplissage_class    = 'br';
  $remplissage_class2   = '' ;
  $remplissage_contenu  = '<span>'.$remplissage_nombre.'</span><i>terminé</i>';
  $remplissage_lien1    = '<a href="#fini" class="fini"'.infobulle('Cliquer pour indiquer (ou pas) qu’il n’y a plus de saisies à effectuer.').'>';
  $remplissage_lien2    = '</a>';
  $q_module_envoyer     = !empty($_SESSION['MODULE']['GENERER_ENONCE']) ? '<q class="module_envoyer"'.infobulle('Générer des énoncés (module externe).').'></q>' : '' ;
  Json::add_row( 'ref'  , $ref );
  Json::add_row( 'html' , '<td>'.$date_devoir.'</td>' );
  Json::add_row( 'html' , '<td data-option="'.$choix_devoir_visible.'">'.$texte_devoir_visible.'</td>' );
  Json::add_row( 'html' , '<td data-option="'.$choix_saisie_visible.'">'.$texte_saisie_visible.'</td>' );
  Json::add_row( 'html' , '<td data-time="'.$time_autoeval.'" class="'.$class_autoeval.'"'.$bulle_autoeval.'>'.$date_autoeval.'</td>' );
  Json::add_row( 'html' , '<td data-ordre="'.$eleves_ordre.'" data-equipe="'.$equipe.'"'.$eleves_class.'>'.$remplissage_eleves.'</td>' );
  Json::add_row( 'html' , '<td>'.html($description).'</td>' );
  Json::add_row( 'html' , '<td'.$items_class.'>'.$nb_items.' item'.$is.'</td>' );
  Json::add_row( 'html' , '<td data-proprio="'.$_SESSION['USER_ID'].'"'.$profs_class.'>'.$profs_nombre.'</td>' );
  Json::add_row( 'html' , '<td class="hc">'.$tab_diagnostic[$diagnostic].'</td>' );
  Json::add_row( 'html' , '<td class="hc">'.$tab_pluriannuel[$pluriannuel].'</td>' );
  Json::add_row( 'html' , '<td class="hc">'.$tab_repartition[$voir_repartition].'</td>' );
  Json::add_row( 'html' , '<td><span data-objet="sujet">'.$image_sujet.'</span><span data-objet="corrige">'.$image_corrige.'</span><span data-objet="sujets" data-nb="0"></span><span data-objet="corriges" data-nb="0"></span><span data-objet="copies" data-nb="0"></span>'.'<q class="uploader_doc"'.infobulle('Ajouter / retirer un sujet ou une correction.').'></q></td>' );
  Json::add_row( 'html' , '<td class="'.$remplissage_class.$remplissage_class2.'">'.$remplissage_lien1.$remplissage_contenu.$remplissage_lien2.'</td>' );
  Json::add_row( 'html' , '<td class="nu" id="devoir_'.$ref.'">' );
  Json::add_row( 'html' ,   '<q class="modifier"'.infobulle('Modifier cette évaluation (date, description, ...).').'></q>' );
  Json::add_row( 'html' ,   '<q class="ordonner"'.infobulle('Réordonner les items de cette évaluation.').'></q>' );
  Json::add_row( 'html' ,   '<q class="dupliquer"'.infobulle('Dupliquer cette évaluation.').'></q>' );
  Json::add_row( 'html' ,   '<q class="supprimer"'.infobulle('Supprimer cette évaluation.').'></q>' );
  Json::add_row( 'html' ,   '<q class="imprimer"'.infobulle('Imprimer un cartouche pour cette évaluation.').'></q>' );
  Json::add_row( 'html' ,   $q_module_envoyer );
  Json::add_row( 'html' ,   '<q class="saisir"'.infobulle('Saisir les acquisitions des élèves à cette évaluation.').'></q>' );
  Json::add_row( 'html' ,   '<q class="voir"'.infobulle('Voir les acquisitions des élèves à cette évaluation.').'></q>' );
  Json::add_row( 'html' ,   '<q class="voir_repart"'.infobulle('Voir les répartitions des élèves à cette évaluation.').'></q>' );
  Json::add_row( 'html' , '</td>' );
  Json::add_row( 'items'   , implode('_',$tab_items) );
  Json::add_row( 'profs'   , $profs_liste );
  Json::add_row( 'sujet'   , $doc_sujet );
  Json::add_row( 'corrige' , $doc_corrige );
  Json::add_row( 'eleves'  , implode('_',$tab_eleves) ); // transmis quelque soit $type : plus simple pour la récup js
  if($action!='importer_evaluation')
  {
    Json::end( TRUE );
  }
  else
  {
    // La suite s'effectue à la section [Mettre à jour les items acquis par les élèves à une évaluation].
    // Pour cela on a aussi besoin de cette nouvelle variable :
    $devoir_id = $devoir_id2;
  }
}

// ////////////////////////////////////////////////////////////////////////////////////////////////////
// Modifier une évaluation existante
// ////////////////////////////////////////////////////////////////////////////////////////////////////

if( ($action=='modifier') && $devoir_id && $groupe_type && $groupe_id && $date_devoir && $choix_devoir_visible && $date_devoir_visible && $choix_saisie_visible && $date_saisie_visible && $date_autoeval && $time_autoeval && $description && isset($tab_repartition[$voir_repartition]) && isset($tab_diagnostic[$diagnostic]) && isset($tab_pluriannuel[$pluriannuel]) && ( ($type=='groupe') || $nb_eleves ) && $nb_items && in_array($fini,array('oui','non')) && $eleves_ordre && !is_null($equipe) )
{
  if( $diagnostic && $pluriannuel )
  {
    Json::end( FALSE , 'Une évaluation ne peut pas être diagnostique et pluriannuelle !' );
  }
  $date_devoir_sql         = To::date_french_to_sql($date_devoir);
  $date_devoir_visible_sql = To::date_french_to_sql($date_devoir_visible);
  $date_saisie_visible_sql = To::date_french_to_sql($date_saisie_visible);
  $date_autoeval_sql       = To::date_french_to_sql($date_autoeval);
  $datetime_autoeval_sql   = is_null($date_autoeval_sql) ? NULL : $date_autoeval_sql.' '.$time_autoeval.':00';
  // Tester les dates
  $jour_debut_annee_scolaire = To::jour_debut_annee_scolaire('sql');
  $jour_fin_annee_scolaire   = To::jour_fin_annee_scolaire('sql');
  $date_devoir_stamp         = strtotime($date_devoir_sql);
  $date_devoir_visible_stamp = strtotime( !is_null($date_devoir_visible_sql)  ? $date_devoir_visible_sql  : '' );
  $date_saisie_visible_stamp = strtotime( !is_null($date_saisie_visible_sql)  ? $date_saisie_visible_sql  : '' );
  $date_autoeval_stamp       = strtotime( !is_null($date_autoeval_sql) ? $date_autoeval_sql : '' );
  $mini_stamp = strtotime("-10 month");
  $maxi_stamp = strtotime("+10 month");
  if( ($date_devoir_sql<$jour_debut_annee_scolaire) || ($date_devoir_sql>$jour_fin_annee_scolaire) )
  {
    Json::end( FALSE , 'Date devoir hors année scolaire ('.To::jour_debut_annee_scolaire('fr').' - '.To::jour_fin_annee_scolaire('fr').') !' );
  }
  if( !is_null($date_saisie_visible_sql) && ( $date_saisie_visible_sql < $date_devoir_sql ) )
  {
    // On autorise une date de visibilité des saisies avant la date du devoir.
    // Par exemple un devoir volontairement daté en fin de période avec des saisies au fur et à mesure.
  }
  if( !is_null($date_saisie_visible_sql) && !is_null($date_devoir_visible_sql) && ( $date_saisie_visible_sql < $date_devoir_visible_sql ) )
  {
    Json::end( FALSE , 'Date visibilité saisies avant date visibilité devoir !' );
  }
  if( ($date_devoir_stamp<$mini_stamp) || ($date_devoir_stamp>$maxi_stamp) )
  {
    Json::end( FALSE , 'Date devoir trop éloignée !' );
  }
  if( !is_null($date_devoir_visible_sql) && ( ($date_devoir_visible_stamp<$mini_stamp) || ($date_devoir_visible_stamp>$maxi_stamp) ) )
  {
    Json::end( FALSE , 'Date visibilité devoir trop éloignée !' );
  }
  if( !is_null($date_saisie_visible_sql) && ( ($date_saisie_visible_stamp<$mini_stamp) || ($date_saisie_visible_stamp>$maxi_stamp) ) )
  {
    Json::end( FALSE , 'Date visibilité saisies trop éloignée !' );
  }
  if( !is_null($date_autoeval_sql) && ( ($date_autoeval_stamp<$mini_stamp) || ($date_autoeval_stamp>$maxi_stamp) ) )
  {
    Json::end( FALSE , 'Date fin auto-évaluation trop éloignée !' );
  }
  if( !is_null($date_autoeval_sql) && !is_null($date_devoir_visible_sql) && (TODAY_SQL<$date_devoir_visible_sql) )
  {
    Json::end( FALSE , 'Auto-évaluation impossible sans visibilité du devoir !' );
  }
  if( !is_null($date_autoeval_sql) && !is_null($date_saisie_visible_sql) && (TODAY_SQL<$date_saisie_visible_sql) )
  {
    Json::end( FALSE , 'Auto-évaluation impossible sans visibilité des saisies !' );
  }
  // Récupérer l’effectif de la classe ou du groupe
  $effectif_eleve = ($type=='groupe') ? DB_STRUCTURE_PROFESSEUR::DB_lister_effectifs_groupes($groupe_id) : $nb_eleves ;
  // Dans le cas d’une évaluation sur un regroupement, on vérifie qu’il n’est pas vide
  if(!$effectif_eleve)
  {
    Json::end( FALSE , 'Regroupement sans élève !' );
  }
  // Tester les droits
  $proprio_id = DB_STRUCTURE_PROFESSEUR::DB_recuperer_devoir_proprietaire_id( $devoir_id );
  if($proprio_id==$_SESSION['USER_ID'])
  {
    $niveau_droit = 4; // propriétaire
    $proprietaire_genre  = $_SESSION['USER_GENRE'];
    $proprietaire_nom    = $_SESSION['USER_NOM'];
    $proprietaire_prenom = $_SESSION['USER_PRENOM'];
  }
  elseif($profs_liste) // forcément
  {
    $search_liste = '_'.$profs_liste.'_';
    if( strpos( $search_liste, '_m'.$_SESSION['USER_ID'].'_' ) !== FALSE )
    {
      $niveau_droit = 3; // modifier
    }
    elseif( strpos( $search_liste, '_s'.$_SESSION['USER_ID'].'_' ) !== FALSE )
    {
      Json::end( FALSE , 'Droit insuffisant attribué sur le devoir n°'.$devoir_id.' (niveau 2 au lieu de 3) !' ); // saisir
    }
    elseif( strpos( $search_liste, '_v'.$_SESSION['USER_ID'].'_' ) !== FALSE )
    {
      Json::end( FALSE , 'Droit insuffisant attribué sur le devoir n°'.$devoir_id.' (niveau 1 au lieu de 3) !' ); // voir
    }
    else
    {
      Json::end( FALSE , 'Droit attribué sur le devoir n°'.$devoir_id.' non trouvé !' );
    }
    $DB_ROW = DB_STRUCTURE_PROFESSEUR::DB_recuperer_devoir_proprietaire_identite( $devoir_id );
    $proprietaire_genre  = $DB_ROW['user_genre'];
    $proprietaire_nom    = $DB_ROW['user_nom'];
    $proprietaire_prenom = $DB_ROW['user_prenom'];
  }
  else
  {
    Json::end( FALSE , 'Vous n’êtes ni propriétaire ni bénéficiaire de droits sur le devoir n°'.$devoir_id.' !' );
  }
  $proprietaire_identite = $proprietaire_nom.' '.$proprietaire_prenom;
  $proprietaire_archive  = To::texte_genre_identite($proprietaire_nom,FALSE,$proprietaire_prenom,TRUE,$proprietaire_genre);
  // Ordre des élèves
  $eleves_ordre = (($groupe_type=='classe')&&($eleves_ordre=='classe')) ? 'nom' : $eleves_ordre ;
  Form::save_choix('evaluation_gestion');
  // sacoche_devoir (maj des paramètres date & info)
  DB_STRUCTURE_PROFESSEUR::DB_modifier_devoir( $devoir_id , $proprio_id , $date_devoir_sql , $description , $proprietaire_archive , $date_devoir_visible_sql ,$date_saisie_visible_sql , $datetime_autoeval_sql , $voir_repartition , $diagnostic , $pluriannuel , $eleves_ordre , $equipe );
  if($type=='selection')
  {
    // sacoche_jointure_user_groupe + sacoche_saisie pour les users supprimés
    DB_STRUCTURE_PROFESSEUR::DB_modifier_liaison_devoir_eleve( $_SESSION['USER_ID'] , $devoir_id , $groupe_id , $tab_eleves , 'substituer' );
  }
  elseif($type=='groupe')
  {
    // sacoche_devoir (maj groupe_id) + sacoche_saisie pour TOUS les users !
    DB_STRUCTURE_PROFESSEUR::DB_modifier_liaison_devoir_groupe( $devoir_id , $groupe_id );
  }
  // sacoche_jointure_devoir_prof ; à restreindre en cas de modification d’une évaluation dont on n’est pas le propriétaire
  if($proprio_id==$_SESSION['USER_ID'])
  {
    if($nb_profs)
    {
      // Mofifier les affectations des profs choisis
      $tab_retour = DB_STRUCTURE_PROFESSEUR::DB_modifier_liaison_devoir_prof( $devoir_id , $tab_profs , 'substituer' );
      if(!empty($tab_retour))
      {
        // Notifications (rendues visibles ultérieurement) ; le mode discret ne d’applique volontairement pas ici car les modifications sont chirurgicales
        $listing_profs = implode(',',array_keys($tab_retour));
        $listing_abonnes = DB_STRUCTURE_NOTIFICATION::DB_lister_destinataires_listing_id( $abonnement_ref_partage , $listing_profs );
        if($listing_abonnes)
        {
          $notification_contenu = To::texte_genre_identite($_SESSION['USER_NOM'],FALSE,$_SESSION['USER_PRENOM'],TRUE,$_SESSION['USER_GENRE']).' vous partage son évaluation "'.$description.'" avec le droit ';
          $tab_texte_etat = array( 'voir'=>'de la visualiser / dupliquer.'."\r\n\r\n" , 'saisir'=>'d’en co-saisir les notes.'."\r\n\r\n" , 'modifier'=>'d’en modifier les paramètres.'."\r\n\r\n" );
          $notification_lien = "\r\n".'Pour y accéder :'."\r\n".Sesamail::adresse_lien_profond('page=evaluation&section=gestion_'.$type);
          $tab_abonnes = explode(',',$listing_abonnes);
          foreach($tab_abonnes as $abonne_id)
          {
            if($tab_retour[$abonne_id]=='insert')
            {
              DB_STRUCTURE_NOTIFICATION::DB_ajouter_log_attente( $abonne_id , $abonnement_ref_partage , $devoir_id , NULL , $notification_contenu.$tab_texte_etat[$tab_profs[$abonne_id]].$notification_lien );
            }
            elseif($tab_retour[$abonne_id]=='update')
            {
              DB_STRUCTURE_NOTIFICATION::DB_modifier_log_attente( $abonne_id , $abonnement_ref_partage , $devoir_id , NULL , $notification_contenu.$tab_texte_etat[$tab_profs[$abonne_id]].$notification_lien , 'remplacer' );
            }
            elseif($tab_retour[$abonne_id]=='delete')
            {
              DB_STRUCTURE_NOTIFICATION::DB_supprimer_log_attente( $abonnement_ref_partage , $devoir_id , $abonne_id );
            }
          }
        }
      }
    }
    else
    {
      // Au cas où on aurait retiré les droits à tous
      DB_STRUCTURE_PROFESSEUR::DB_supprimer_liaison_devoir_prof($devoir_id);
    }
  }
  // sacoche_jointure_devoir_item + sacoche_saisie pour les items supprimés
  DB_STRUCTURE_PROFESSEUR::DB_modifier_liaison_devoir_item( $devoir_id , $tab_items , 'substituer' );
  // Récupérer le nb de saisies déjà effectuées pour l’évaluation
  $nb_saisies_effectuees = DB_STRUCTURE_PROFESSEUR::DB_lister_nb_saisies_par_evaluation($devoir_id);
  // Notifications : il peut falloir adapter les dates de toutes celles qui sont dépendantes de la date de visibilité du devoir.
  $notification_date = ( TODAY_SQL < $date_devoir_visible_sql ) ? $date_devoir_visible_sql : NULL ;
  DB_STRUCTURE_NOTIFICATION::DB_modifier_attente_date_devoir( $devoir_id , $notification_date );
  // Notifications (rendues visibles ultérieurement)
  if( !$mode_discret && DB_STRUCTURE_PROFESSEUR::DB_compter_devoir_matieres_non_experimentales($devoir_id) )
  {
    DB_STRUCTURE_NOTIFICATION::DB_supprimer_log_attente( $abonnement_ref_edition , $devoir_id );
    $listing_eleves = ($type=='selection') ? implode(',',$tab_eleves) : DB_STRUCTURE_PROFESSEUR::DB_recuperer_listing_eleves_id( $groupe_type , $groupe_id ) ;
    if($listing_eleves)
    {
      $listing_parents = DB_STRUCTURE_NOTIFICATION::DB_lister_parents_listing_id($listing_eleves);
      $listing_users = ($listing_parents) ? $listing_eleves.','.$listing_parents : $listing_eleves ;
      $listing_abonnes = DB_STRUCTURE_NOTIFICATION::DB_lister_destinataires_listing_id( $abonnement_ref_edition , $listing_users );
      if($listing_abonnes)
      {
        $adresse_lien_profond = Sesamail::adresse_lien_profond('page=evaluation&section=voir&devoir_id='.$devoir_id.'&eleve_id=');
        $notification_contenu = 'Évaluation "'.$description.'" du '.$date_devoir.' paramétrée par '.To::texte_genre_identite($_SESSION['USER_NOM'],FALSE,$_SESSION['USER_PRENOM'],TRUE,$_SESSION['USER_GENRE']).'.'."\r\n\r\n";
        $tab_abonnes = DB_STRUCTURE_NOTIFICATION::DB_lister_detail_abonnes_envois( $listing_abonnes , $listing_eleves , $listing_parents );
        foreach($tab_abonnes as $abonne_id => $tab_abonne)
        {
          foreach($tab_abonne as $eleve_id => $notification_intro_eleve)
          {
            $notification_lien = 'Voir le détail :'."\r\n".$adresse_lien_profond.$eleve_id;
            DB_STRUCTURE_NOTIFICATION::DB_ajouter_log_attente( $abonne_id , $abonnement_ref_edition , $devoir_id , $notification_date , $notification_intro_eleve.$notification_contenu.$notification_lien );
          }
        }
      }
    }
  }
  // Afficher le retour
  $texte_devoir_visible  = ($choix_devoir_visible != 'perso') ? $tab_texte_devoir_visible[$choix_devoir_visible] : To::date_sql_to_french($date_devoir_visible_sql) ;
  $texte_saisie_visible  = ($choix_saisie_visible != 'perso') ? $tab_texte_saisie_visible[$choix_saisie_visible] : To::date_sql_to_french($date_saisie_visible_sql) ;
  $date_autoeval  = ($date_autoeval=='00/00/0000') ? 'non' : $date_autoeval ;
  $bulle_autoeval = ($date_autoeval=='non') ? '' : infobulle(To::datetime_sql_to_french($datetime_autoeval_sql)) ;
  $class_autoeval = ($bulle_autoeval) ? 'hc bulle' : 'hc' ;
  $time_autoeval  = ($bulle_autoeval) ? $time_autoeval : '23:59' ;
  $ref = $devoir_id.'_'.Clean::upper($groupe_type[0]).$groupe_id;
  $is = ($nb_items>1)  ? 's' : '' ;
  $us = ($nb_eleves>1) ? 's' : '' ;
  $ss = ($sujets_nombre>1)   ? 's' : '' ;
  $cs = ($corriges_nombre>1) ? 's' : '' ;
  $ps = ($copies_nombre>1)   ? 's' : '' ;
  $eleves_class = ($type=='selection') ? classbulle('eleves') : '' ;
  $profs_nombre = ($nb_profs) ? ($nb_profs+1).' profs' : 'non' ;
  $profs_class  = ($nb_profs) ? classbulle('profs') : ' class="hc"' ;
  $items_class  = ($nb_items) ? classbulle('items') : '' ;
  $image_sujet   = ($doc_sujet)   ? '<a href="'.$doc_sujet.'" target="_blank" rel="noopener noreferrer" class="no_puce"><img alt="sujet" src="./_img/document/sujet_oui.png"'.infobulle('Sujet commun.').'></a>' : '' ;
  $image_corrige = ($doc_corrige) ? '<a href="'.$doc_corrige.'" target="_blank" rel="noopener noreferrer" class="no_puce"><img alt="corrigé" src="./_img/document/corrige_oui.png"'.infobulle('Corrigé commun.').'></a>' : '' ;
  $image_sujets   = ($sujets_nombre)   ? '<img alt="sujet" src="./_img/document/sujet_multi.png"'.infobulle($sujets_nombre.' sujet'.$ss.' individuel'.$ss).'>' : '' ;
  $image_corriges = ($corriges_nombre) ? '<img alt="sujet" src="./_img/document/corrige_multi.png"'.infobulle($corriges_nombre.' corrigé'.$cs.' individuel'.$cs).'>' : '' ;
  $image_copies   = ($copies_nombre)   ? '<img alt="sujet" src="./_img/document/copie_multi.png"'.infobulle($copies_nombre.' copie'.$ps).'>' : '' ;
  $nb_saisies_possibles = $nb_items*$effectif_eleve;
  $remplissage_eleves   = ($type=='groupe') ? '{{GROUPE_NOM}}' : $nb_eleves.' élève'.$us ;
  $remplissage_nombre   = $nb_saisies_effectuees.'/'.$nb_saisies_possibles ;
  $remplissage_class    = (!$nb_saisies_effectuees) ? 'br' : ( ($nb_saisies_effectuees<$nb_saisies_possibles) ? 'bj' : 'bv' ) ;
  $remplissage_class2   = ($fini=='oui') ? ' bf' : '' ;
  $remplissage_contenu  = ($fini=='oui') ? '<span>terminé</span><i>'.$remplissage_nombre.'</i>' : '<span>'.$remplissage_nombre.'</span><i>terminé</i>' ;
  $remplissage_lien1    = ($niveau_droit<4)  ? '' : '<a href="#fini" class="fini"'.infobulle('Cliquer pour indiquer (ou pas) qu’il n’y a plus de saisies à effectuer.').'>' ;
  $remplissage_lien2    = ($niveau_droit<4)  ? '' : '</a>' ;
  $remplissage_td_title = ($niveau_droit==4) ? '' : infobulle('Clôture restreinte au propriétaire de l’évaluation ('.html($proprietaire_identite).').') ;
  $q_uploader_doc       = ($niveau_droit==4)
                        ? '<q class="uploader_doc"'.infobulle('Ajouter / retirer un sujet ou une correction.').'></q>'
                        : '<q class="uploader_doc_non"'.infobulle('Upload restreint au propriétaire de l’évaluation ('.html($proprietaire_identite).').').'></q>' ;
  $q_modifier           = ($niveau_droit>=3)
                        ? '<q class="modifier" '.infobulle('Modifier cette évaluation (date, description, ...).').'></q>'
                        : '<q class="modifier_non"'.infobulle('Action nécessitant le droit de modification (voir '.html($proprietaire_identite).').').'></q>' ;
  $q_ordonner           = ($niveau_droit>=3)
                        ? '<q class="ordonner"'.infobulle('Réordonner les items de cette évaluation.').'></q>'
                        : '<q class="ordonner_non"'.infobulle('Action nécessitant le droit de modification (voir '.html($proprietaire_identite).').').'></q>' ;
  $q_supprimer          = ($niveau_droit==4)
                        ? '<q class="supprimer"'.infobulle('Supprimer cette évaluation.').'></q>'
                        : '<q class="supprimer_non"'.infobulle('Suppression restreinte au propriétaire de l’évaluation ('.html($proprietaire_identite).').').'></q>' ;
  $q_module_envoyer     = !empty($_SESSION['MODULE']['GENERER_ENONCE']) ? '<q class="module_envoyer"'.infobulle('Générer des énoncés (module externe).').'></q>' : '' ;
  Json::add_row( 'html' , '<td>'.$date_devoir.'</td>' );
  Json::add_row( 'html' , '<td data-option="'.$choix_devoir_visible.'">'.$texte_devoir_visible.'</td>' );
  Json::add_row( 'html' , '<td data-option="'.$choix_saisie_visible.'">'.$texte_saisie_visible.'</td>' );
  Json::add_row( 'html' , '<td data-time="'.$time_autoeval.'" class="'.$class_autoeval.'"'.$bulle_autoeval.'>'.$date_autoeval.'</td>' );
  Json::add_row( 'html' , '<td data-ordre="'.$eleves_ordre.'" data-equipe="'.$equipe.'"'.$eleves_class.'>'.$remplissage_eleves.'</td>' );
  Json::add_row( 'html' , '<td>'.html($description).'</td>' );
  Json::add_row( 'html' , '<td'.$items_class.'>'.$nb_items.' item'.$is.'</td>' );
  Json::add_row( 'html' , '<td data-proprio="'.$proprio_id.'"'.$profs_class.'>'.$profs_nombre.'</td>' );
  Json::add_row( 'html' , '<td class="hc">'.$tab_diagnostic[$diagnostic].'</td>' );
  Json::add_row( 'html' , '<td class="hc">'.$tab_pluriannuel[$pluriannuel].'</td>' );
  Json::add_row( 'html' , '<td class="hc">'.$tab_repartition[$voir_repartition].'</td>' );
  Json::add_row( 'html' , '<td><span data-objet="sujet">'.$image_sujet.'</span><span data-objet="corrige">'.$image_corrige.'</span><span data-objet="sujets" data-nb="'.$sujets_nombre.'">'.$image_sujets.'</span><span data-objet="corriges" data-nb="'.$corriges_nombre.'">'.$image_corriges.'</span><span data-objet="copies" data-nb="'.$copies_nombre.'">'.$image_copies.'</span>'.$q_uploader_doc.'</td>' );
  Json::add_row( 'html' , '<td class="'.$remplissage_class.$remplissage_class2.'"'.$remplissage_td_title.'>'.$remplissage_lien1.$remplissage_contenu.$remplissage_lien2.'</td>' );
  Json::add_row( 'html' , '<td class="nu" id="devoir_'.$ref.'">' );
  Json::add_row( 'html' ,   $q_modifier );
  Json::add_row( 'html' ,   $q_ordonner );
  Json::add_row( 'html' ,   '<q class="dupliquer"'.infobulle('Dupliquer cette évaluation.').'></q>' );
  Json::add_row( 'html' ,   $q_supprimer );
  Json::add_row( 'html' ,   '<q class="imprimer"'.infobulle('Imprimer un cartouche pour cette évaluation.').'></q>' );
  Json::add_row( 'html' ,   $q_module_envoyer );
  Json::add_row( 'html' ,   '<q class="saisir"'.infobulle('Saisir les acquisitions des élèves à cette évaluation.').'></q>' ); // niveau de droit à 3 ou 4 donc au moins à 2
  Json::add_row( 'html' ,   '<q class="voir"'.infobulle('Voir les acquisitions des élèves à cette évaluation.').'></q>' );
  Json::add_row( 'html' ,   '<q class="voir_repart"'.infobulle('Voir les répartitions des élèves à cette évaluation.').'></q>' );
  Json::add_row( 'html' , '</td>' );
  Json::add_row( 'ref'     , $ref );
  Json::add_row( 'items'   , implode('_',$tab_items) );
  Json::add_row( 'profs'   , $profs_liste );
  Json::add_row( 'sujet'   , $doc_sujet );
  Json::add_row( 'corrige' , $doc_corrige );
  Json::add_row( 'eleves'  , implode('_',$tab_eleves) ); // transmis quelque soit $type : plus simple pour la récup js
  Json::end( TRUE );
}

// ////////////////////////////////////////////////////////////////////////////////////////////////////
// Supprimer une évaluation existante
// ////////////////////////////////////////////////////////////////////////////////////////////////////

if( ($action=='supprimer') && $devoir_id && ( ($type=='groupe') || $groupe_id ) && $description )
{
  // Vérification des droits
  $proprio_id = DB_STRUCTURE_PROFESSEUR::DB_recuperer_devoir_proprietaire_id( $devoir_id );
  if($proprio_id!=$_SESSION['USER_ID'])
  {
    Json::end( FALSE , 'Vous n’êtes pas propriétaire du devoir n°'.$devoir_id.' !' );
  }
  // On y va
  if($type=='selection')
  {
    // supprimer le groupe spécialement associé (invisible à l’utilisateur) et les entrées dans sacoche_jointure_user_groupe pour une évaluation avec des élèves piochés en dehors de tout groupe prédéfini
    DB_STRUCTURE_REGROUPEMENT::DB_supprimer_groupe_par_prof( $groupe_id , $groupe_type , FALSE /*with_devoir*/ );
    SACocheLog::ajouter('Suppression d’un regroupement ('.$groupe_type.' '.$groupe_id.'), sans les devoirs associés.');
  }
  // on supprime l’évaluation avec ses saisies
  DB_STRUCTURE_PROFESSEUR::DB_supprimer_devoir_et_saisies( $devoir_id );
  SACocheLog::ajouter('Suppression du devoir "'.$description.'" (n°'.$devoir_id.'), et donc aussi des saisies associées.');
  // Notifications (rendues visibles ultérieurement)
  $notification_contenu = date('d-m-Y H:i:s').' '.$_SESSION['USER_PRENOM'].' '.$_SESSION['USER_NOM'].' a supprimé son devoir "'.$description.'" (n°'.$devoir_id.'), et donc aussi les saisies associées.'."\r\n";
  DB_STRUCTURE_NOTIFICATION::enregistrer_action_sensible($notification_contenu);
  DB_STRUCTURE_NOTIFICATION::DB_supprimer_log_attente( $abonnement_ref_edition , $devoir_id );
  // Afficher le retour
  Json::end( TRUE );
}

// ////////////////////////////////////////////////////////////////////////////////////////////////////
// Retrait et archivage d’une ancienne évaluation déclarée pluriannuelle par erreur
// ////////////////////////////////////////////////////////////////////////////////////////////////////

if( ($action=='supprimer_pluriannuel') && $devoir_id && ($type=='selection') && $groupe_id && ($groupe_type=='eval') && $description )
{
  // Vérification des droits
  $proprio_id = DB_STRUCTURE_PROFESSEUR::DB_recuperer_devoir_proprietaire_id( $devoir_id );
  if($proprio_id!=$_SESSION['USER_ID'])
  {
    Json::end( FALSE , 'Vous n’êtes pas propriétaire du devoir n°'.$devoir_id.' !' );
  }
  // On y va : on supprime l’évaluation sans ses saisies dont on modifie la date
  DB_STRUCTURE_PROFESSEUR::DB_supprimer_devoir_pluriannuel_et_archivage_saisies( $devoir_id );
  SACocheLog::ajouter('Retrait du devoir pluriannuel "'.$description.'" (n°'.$devoir_id.'), avec avancée des dates des saisies associées.');
  // Notifications (rendues visibles ultérieurement)
  $notification_contenu = date('d-m-Y H:i:s').' '.$_SESSION['USER_PRENOM'].' '.$_SESSION['USER_NOM'].' a retiré son ancien devoir pluriannuel "'.$description.'" (n°'.$devoir_id.'), avec avancée des dates des saisies associées.'."\r\n";
  DB_STRUCTURE_NOTIFICATION::enregistrer_action_sensible($notification_contenu);
  DB_STRUCTURE_NOTIFICATION::DB_supprimer_log_attente( $abonnement_ref_edition , $devoir_id );
  // Afficher le retour
  Json::end( TRUE );
}

// ////////////////////////////////////////////////////////////////////////////////////////////////////
// Afficher le formulaire pour réordonner les items d’une évaluation
// La vérification de droits suffisants s’effectuera lors de la soumission.
// ////////////////////////////////////////////////////////////////////////////////////////////////////

if( ($action=='ordonner') && $devoir_id )
{
  // liste des items
  $DB_TAB_COMP = DB_STRUCTURE_PROFESSEUR::DB_lister_devoir_items( $devoir_id , TRUE /*with_socle*/ , TRUE /*with_coef*/ , TRUE /*with_ref*/ , FALSE /*with_lien*/ , FALSE /*with_module*/ , FALSE /*with_comm*/ , FALSE /*with_domaine*/ , FALSE /*with_theme*/ );
  if(empty($DB_TAB_COMP))
  {
    Json::end( FALSE , 'Aucun item n’est associé à cette évaluation !' );
  }
  foreach($DB_TAB_COMP as $DB_ROW)
  {
    $item_ref = ($DB_ROW['ref_perso']) ? $DB_ROW['ref_perso'] : $DB_ROW['ref_auto'] ;
    $texte_s2016 = ($DB_ROW['s2016_nb'])  ? ' [S]' : ' [–]' ;
    $texte_coef  = ' [×'.$DB_ROW['item_coef'].']';
    Json::add_str('<li id="i'.$DB_ROW['item_id'].'"><b>'.html($DB_ROW['matiere_ref'].'.'.$item_ref.$texte_s2016.$texte_coef).'</b> - '.html($DB_ROW['item_nom']).'</li>');
  }
  Json::end( TRUE );
}

// ////////////////////////////////////////////////////////////////////////////////////////////////////
// Indiquer la liste des élèves associés à une évaluation de même nom (uniquement pour une sélection d’élèves)
// Reprise d’un développement initié par Alain Pottier <alain.pottier613@orange.fr> et publié le 08/02/2012
// ////////////////////////////////////////////////////////////////////////////////////////////////////

if( ($action=='indiquer_eleves_deja') && $description && $date_debut )
{
  $date_debut_sql = To::date_french_to_sql($date_debut);
  $DB_TAB = DB_STRUCTURE_PROFESSEUR::DB_lister_eleves_devoirs($_SESSION['USER_ID'],$description,$date_debut_sql);
  if(empty($DB_TAB))
  {
    Json::end( FALSE , 'Aucun élève trouvé pour "'.html($description).'".' );
  }
  foreach($DB_TAB as $DB_ROW)
  {
    Json::add_row( NULL , $DB_ROW['user_id'].'_'.To::date_sql_to_french($DB_ROW['devoir_date']) );
  }
  Json::end( TRUE );
}

// ////////////////////////////////////////////////////////////////////////////////////////////////////
// Concevoir un fichier d’informations à destination d’un module externe (conception de l’évaluation)
// ////////////////////////////////////////////////////////////////////////////////////////////////////

if( ($action=='generer_enonces') && $devoir_id && $groupe_id && $date_devoir && $eleves_ordre && $description && $groupe_nom )
{
  if(empty($_SESSION['MODULE']['GENERER_ENONCE']))
  {
    Json::end( FALSE , 'Pas de module externe enregistré pour traiter cette demande !' );
  }
  $structure_uai = ($_SESSION['WEBMESTRE_UAI']) ? $_SESSION['WEBMESTRE_UAI'] : $_SESSION['SESAMATH_UAI'] ;
  $structure_id  = ($_SESSION['SESAMATH_ID'])   ? $_SESSION['SESAMATH_ID']   : $_SESSION['BASE'] ;
  $structure_nom = ($_SESSION['ETABLISSEMENT']['DENOMINATION']) ? $_SESSION['ETABLISSEMENT']['DENOMINATION'] : ( ($_SESSION['SESAMATH_TYPE_NOM']) ? $_SESSION['SESAMATH_TYPE_NOM'] : $_SESSION['WEBMESTRE_DENOMINATION'] ) ;
  $tab_module = array(
    'structure' => array(
      'uai' => $structure_uai,
      'id'  => $structure_id,
      'nom' => $structure_nom,
    ),
    'devoir' => array(
      'id'       => $devoir_id,
      'groupe'   => $groupe_nom,
      'intitule' => $description,
      'date'     => $date_devoir,
    ),
    'prof' => array(
      'id'     => $_SESSION['USER_ID'],
      'nom'    => $_SESSION['USER_NOM'],
      'prenom' => $_SESSION['USER_PRENOM'],
    ),
    'item'   => array(),
    'eleve'  => array(),
    'panier' => array(),
  );
  // liste des items
  $DB_TAB_COMP = DB_STRUCTURE_PROFESSEUR::DB_lister_devoir_items( $devoir_id , FALSE /*with_socle*/ , FALSE /*with_coef*/ , TRUE /*with_ref*/ , FALSE /*with_lien*/ , TRUE /*with_module*/ , FALSE /*with_comm*/ , FALSE /*with_domaine*/ , FALSE /*with_theme*/ );
  // liste des élèves
  $DB_TAB_USER = DB_STRUCTURE_COMMUN::DB_lister_users_regroupement( 'eleve' /*profil_type*/ , 1 /*statut*/ , $groupe_type , $groupe_id , $eleves_ordre );
  // Let's go
  $item_nb = count($DB_TAB_COMP);
  if(!$item_nb)
  {
    Json::end( FALSE , 'Aucun item n’est associé à cette évaluation !' );
  }
  $eleve_nb = count($DB_TAB_USER);
  if(!$eleve_nb)
  {
    Json::end( FALSE , 'Aucun élève n’est associé à cette évaluation !' );
  }
  // items
  foreach($DB_TAB_COMP as $DB_ROW)
  {
    $item_ref = ($DB_ROW['item_module']) ? $DB_ROW['item_module'] : ( ($DB_ROW['ref_perso']) ? $DB_ROW['matiere_ref'].'.'.$DB_ROW['ref_perso'] : $DB_ROW['matiere_ref'].'.'.$DB_ROW['ref_auto'] ) ;
    $item_id = (int)$DB_ROW['item_id'];
    $tab_module['item'][$item_id] = array(
      'id'  => $item_id,
      'ref' => $item_ref,
      'nom' => $DB_ROW['item_nom'],
    );
  }
  // élèves
  foreach($DB_TAB_USER as $DB_ROW)
  {
    $user_id = (int)$DB_ROW['user_id'];
    $tab_module['eleve'][$user_id] = array(
      'id'     => $user_id,
      'nom'    => $DB_ROW['user_nom'],
      'prenom' => $DB_ROW['user_prenom'],
    );
  }
  // ajouter les demandes d’évaluation
  $DB_TAB = DB_STRUCTURE_PROFESSEUR::DB_lister_devoir_saisies( $devoir_id , TRUE /*with_marqueurs*/ );
  foreach($DB_TAB as $DB_ROW)
  {
    $item_id = (int)$DB_ROW['item_id'];
    $user_id = (int)$DB_ROW['eleve_id'];
    // Test pour éviter les pbs des élèves changés de groupes ou des items modifiés en cours de route
    if( isset($tab_module['item'][$item_id]) && isset($tab_module['eleve'][$user_id]) && ($DB_ROW['saisie_note']=='PA') )
    {
      $tab_module['panier'][$user_id][$item_id] = TRUE;
    }
  }
  // enregistrer le fichier
  $fichier_contenu = json_encode($tab_module);
  $fichier_nom = 'export_module_'.$fnom_export.'.json';
  FileSystem::ecrire_fichier( CHEMIN_DOSSIER_EXPORT.$fichier_nom , $fichier_contenu );
  // Retour du lien
  Json::end( TRUE , $_SESSION['MODULE']['GENERER_ENONCE'].'?json='.urlencode(URL_DIR_EXPORT.$fichier_nom) );
}

// ////////////////////////////////////////////////////////////////////////////////////////////////////
// Afficher le formulaire pour saisir les items acquis par les élèves à une évaluation
// Voir les items acquis par les élèves à une évaluation
// ////////////////////////////////////////////////////////////////////////////////////////////////////

if( in_array($action,array('saisir','voir')) && $devoir_id && $groupe_id && $eleves_ordre && $date_devoir && !is_null($equipe) ) // $description + $groupe_nom sont aussi transmis
{
  $with_lien = ($action=='voir') ? TRUE : FALSE ;
  // liste des items
  $DB_TAB_COMP = DB_STRUCTURE_PROFESSEUR::DB_lister_devoir_items( $devoir_id , TRUE /*with_socle*/ , TRUE /*with_coef*/ , TRUE /*with_ref*/ , $with_lien , FALSE /*with_module*/ , TRUE /*with_comm*/ , FALSE /*with_domaine*/ , FALSE /*with_theme*/ );
  // liste des élèves
  $DB_TAB_USER = DB_STRUCTURE_COMMUN::DB_lister_users_regroupement( 'eleve' /*profil_type*/ , 1 /*statut*/ , $groupe_type , $groupe_id , $eleves_ordre , 'user_id,user_nom,user_prenom' /*champs*/ );
  // liste des infos autoévaluation / audio / texte / copie
  $DB_TAB_JOIN = DB_STRUCTURE_DEVOIR_JOINTURE::DB_lister_elements($devoir_id);
  // Let’s go
  $item_nb = count($DB_TAB_COMP);
  if(!$item_nb)
  {
    Json::end( FALSE , 'Aucun item n’est associé à cette évaluation !' );
  }
  $eleve_nb = count($DB_TAB_USER);
  if(!$eleve_nb)
  {
    Json::end( FALSE , 'Aucun élève n’est associé à cette évaluation !' );
  }
  //
  // Présentation en tableau
  //
  $check_largeur = ($_COOKIE[COOKIE_ISTOUCH]) ? ' checked' : '' ;
  $tab_affich  = array(); // tableau bi-dimensionnel [n°ligne=id_item][n°colonne=id_user]
  $tab_plan    = array(); // tableau bi-dimensionnel [n°ligne=id_user][n°colonne=id_item]
  $tab_user_id = array(); // servira aussi pour l’affichage sur plan de classe et la récupération des dispositifs
  $tab_comp_id = array(); // sert à retenir la référence et le nom des items
  $tab_affich['head'][0] = '<td>';
  if($action=='saisir')
  {
    $tab_touches = array();
    foreach( $_SESSION['NOTE_ACTIF'] as $note_id )
    {
      $tab_touches[$note_id] = $_SESSION['NOTE'][$note_id]['CLAVIER'];
    }
    $tab_touches += array( 7 => 'A' , 'D' , 'E' , 'F' , 'N' , 'R' , 'P' , 'suppr' );
    $tab_affich['head'][0].= '<span class="manuel"><a class="pop_up" href="'.SERVEUR_DOCUMENTAIRE.'?fichier=support_professeur__evaluations_saisie_resultats">DOC : Saisie des résultats.</a></span>';
    $tab_affich['head'][0].= '<p>';
    $tab_affich['head'][0].= '<label for="radio_clavier"><input type="radio" id="radio_clavier" name="mode_saisie" value="clavier"> <span class="eval pilot_keyboard">Piloter au clavier</span></label> '.infobulle('Sélectionner un rectangle blanc'.BRJS.'au clavier (flèches) ou à la souris'.BRJS.'puis utiliser les touches suivantes :'.BRJS.implode(' ; ',$tab_touches).'.'.BRJS.'Pour un report multiple, presser avant'.BRJS.'C (Colonne), L (Ligne) ou T (Tableau).',TRUE).'<br>';
    $tab_affich['head'][0].= '<span id="arrow_continue"><label for="arrow_continue_down"><input type="radio" id="arrow_continue_down" name="arrow_continue" value="down"> <span class="eval arrow_continue_down">par élève</span></label>&nbsp;&nbsp;&nbsp;<label for="arrow_continue_right"><input type="radio" id="arrow_continue_right" name="arrow_continue" value="right"> <span class="eval arrow_continue_right">par item</span></label></span><br>';
    $tab_affich['head'][0].= '<label for="radio_souris"><input type="radio" id="radio_souris" name="mode_saisie" value="souris"> <span class="eval pilot_mouse">Piloter à la souris</span></label> '.infobulle('Survoler une case du tableau avec la souris'.BRJS.'puis cliquer sur une des images proposées.',TRUE);
    $tab_affich['head'][0].= '</p>';
  }
  $tab_affich['head'][0].= '<p>';
  $tab_affich['head'][0].= '<label for="check_largeur"><input type="checkbox" id="check_largeur" name="check_largeur" value="retrecir_largeur"'.$check_largeur.'> <span class="eval retrecir_largeur">Largeur optimale</span></label> '.infobulle('Diminuer la largeur des colonnes'.BRJS.'si les élèves sont nombreux.',TRUE).'<br>';
  $tab_affich['head'][0].= '<label for="check_hauteur"><input type="checkbox" id="check_hauteur" name="check_hauteur" value="retrecir_hauteur"> <span class="eval retrecir_hauteur">Hauteur optimale</span></label> '.infobulle('Diminuer la hauteur des lignes'.BRJS.'si les items sont nombreux.',TRUE);
  $tab_affich['head'][0].= '</p>';
  $tab_affich['head'][0].= '</td>';
  $tab_affich['foot_dispositif'][0] = '<th>Dispositif(s)</th>';
  $tab_affich['foot_autoeval'  ][0] = '<th>Auto-évaluation</th>';
  $tab_affich['foot_texte'     ][0] = '<th>Commentaire écrit</th>';
  $tab_affich['foot_audio'     ][0] = '<th>Commentaire audio</th>';
  $tab_affich['foot_doc_copie' ][0] = '<th>Copie de l’élève</th>';
  // première ligne (noms prénoms des élèves)
  $br_texte1 = ($_COOKIE[COOKIE_ISTOUCH]) ? ' '   : '<br>' ;
  $br_texte2 = ($_COOKIE[COOKIE_ISTOUCH]) ? ' - ' : '<br>' ;
  $br_class  = ($_COOKIE[COOKIE_ISTOUCH]) ? ''    : ' class="double"' ;
  $q_texte = ($action=='saisir')
           ? '<q id="texteCiL" class="texte_enregistrer"'.infobulle('Saisir un commentaire écrit.').'></q>'
           : '<q class="texte_consulter_non"'.infobulle('Pas de commentaire écrit.').'></q>' ;
  $q_audio = ($action=='saisir')
           ? '<q id="audioCiL" class="audio_enregistrer"'.infobulle('Enregistrer un commentaire audio.').'></q>'
           : '<q class="audio_ecouter_non"'.infobulle('Pas de commentaire audio.').'></q>' ;
  $num_colonne = 0;
  $tab_colonne_for_user = array();
  foreach($DB_TAB_USER as $DB_ROW)
  {
    $num_colonne++;
    $identite_first = ($eleves_ordre!='prenom') ? $DB_ROW['user_nom'] : $DB_ROW['user_prenom'] ;
    $identite_last  = ($eleves_ordre!='prenom') ? $DB_ROW['user_prenom'] : $DB_ROW['user_nom'] ;
    $tab_affich['head'][$DB_ROW['user_id']] = '<th><dfn id="dfn_'.$DB_ROW['user_id'].'" data-nom="'.html($DB_ROW['user_nom']).'" data-prenom="'.html($DB_ROW['user_prenom']).'"'.$br_class.'>'.html($identite_first).$br_texte1.html($identite_last).'</dfn></th>';
    $tab_user_id[$DB_ROW['user_id']] = array(
      'nom'         => $DB_ROW['user_nom'],
      'prenom'      => $DB_ROW['user_prenom'],
      'dispositifs' => array(),
    );
    // On initialise ces cellules, qui seront remplacées si besoin par une autre valeur dans la boucle suivante
    $tab_affich['foot_dispositif'][$DB_ROW['user_id']] = '<td id="dispositif_'.$DB_ROW['user_id'].'"></td>';
    $tab_affich['foot_autoeval'  ][$DB_ROW['user_id']] = '<td id="autoeval_'.$DB_ROW['user_id'].'"></td>';
    $tab_affich['foot_texte'     ][$DB_ROW['user_id']] = '<td id="texte_'.$DB_ROW['user_id'].'">'.str_replace('CiL','C'.$num_colonne.'L',$q_texte).'</td>';
    $tab_affich['foot_audio'     ][$DB_ROW['user_id']] = '<td id="audio_'.$DB_ROW['user_id'].'">'.str_replace('CiL','C'.$num_colonne.'L',$q_audio).'</td>';
    $tab_affich['foot_doc_copie' ][$DB_ROW['user_id']] = '<td><img alt="copie élève" src="./_img/document/copie_non.png"'.infobulle('Pas de copie transmise par l’élève.').'></td>';
    $tab_colonne_for_user[$DB_ROW['user_id']] = $num_colonne;
  }
  $find_copie = FALSE;
  if(!empty($DB_TAB_JOIN))
  {
    $tab_balise = array(
      'saisir' => array(
        'texte'     => '<q id="texteCiL" class="texte_enregistrer"'.infobulle('Modifier le commentaire écrit.').'></q>',
        'audio'     => '<q id="audioCiL" class="audio_enregistrer"'.infobulle('Modifier le commentaire audio.').'></q>',
        'doc_copie' => '<a href="#HREF#" target="_blank" rel="noopener noreferrer" class="no_puce"><img alt="copie élève" src="./_img/document/copie_oui.png"'.infobulle('Copie disponible.').'></a>',
      ),
      'voir' => array(
        'texte'     => '<q class="texte_consulter"'.infobulle('Commentaire écrit disponible.').'></q>',
        'audio'     => '<q class="audio_ecouter"'.infobulle('Commentaire audio disponible.').'></q>',
        'doc_copie' => '<a href="#HREF#" target="_blank" rel="noopener noreferrer" class="no_puce"><img alt="copie élève" src="./_img/document/copie_oui.png"'.infobulle('Copie disponible.').'></a>',
      )
    );
    foreach($DB_TAB_JOIN as $DB_ROW)
    {
      // Test pour éviter les pbs des élèves retirés depuis
      if(!empty($tab_user_id[$DB_ROW['eleve_id']]))
      {
        $num_colonne = $tab_colonne_for_user[$DB_ROW['eleve_id']];
        foreach($tab_balise[$action] as $msg_objet => $balise_html)
        {
          if($DB_ROW['jointure_'.$msg_objet])
          {
            if($msg_objet=='doc_copie')
            {
              $balise_html = str_replace( '#HREF#' , FileSystem::verif_lien_safe($DB_ROW['jointure_'.$msg_objet]) , $balise_html );
              $find_copie = TRUE;
            }
            else
            {
              $balise_html = str_replace( 'CiL' , 'C'.$num_colonne.'L' , $balise_html );
            }
            $tab_affich['foot_'.$msg_objet][$DB_ROW['eleve_id']] = '<td id="'.$msg_objet.'_'.$DB_ROW['eleve_id'].'" class="off">'.$balise_html.'</td>';
          }
        }
      }
    }
  }
  // première colonne (noms items)
  foreach($DB_TAB_COMP as $DB_ROW)
  {
    $item_ref = ($DB_ROW['ref_perso']) ? $DB_ROW['ref_perso'] : $DB_ROW['ref_auto'] ;
    $texte_s2016 = ($DB_ROW['s2016_nb'])  ? ' [S]' : ' [–]' ;
    $texte_comm  = ($DB_ROW['item_comm']) ? ' <img src="./_img/etat/comm_oui.png"'.infobulle($DB_ROW['item_comm']).'>' : '' ;
    $texte_coef  = ' [×'.$DB_ROW['item_coef'].']';
    $texte_lien_avant = ( ($action=='voir') && ($DB_ROW['item_lien']) ) ? '<a target="_blank" rel="noopener noreferrer" href="'.html($DB_ROW['item_lien']).'">' : '';
    $texte_lien_apres = ( ($action=='voir') && ($DB_ROW['item_lien']) ) ? '</a>' : '';
    $tab_affich[$DB_ROW['item_id']][0] = '<th data-ref="'.$DB_ROW['matiere_ref'].'.'.$item_ref.'"><b>'.$texte_lien_avant.html($DB_ROW['matiere_ref'].'.'.$item_ref.$texte_s2016.$texte_coef).$texte_lien_apres.'</b><div data-mode="complet">'.html($DB_ROW['item_nom']).$texte_comm.'</div></th>';
    $tab_comp_id[$DB_ROW['item_id']] = array(
      'ref' => html($DB_ROW['matiere_ref'].'.'.$item_ref),
      'nom' => html($DB_ROW['item_nom']),
    );
  }
  // Lister les dispositifs
  // On ne complète les dispositifs que maintenant car on a besoin de la liste des élèves
  $listing_user_id = implode(',',array_keys($tab_user_id));
  $date_devoir_sql = To::date_french_to_sql($date_devoir);
  $DB_TAB_DISPOSITIF = DB_STRUCTURE_COMMUN::DB_lister_eleves_dispositifs( $listing_user_id , 'date' , $date_devoir_sql );
  if(!empty($DB_TAB_DISPOSITIF))
  {
    foreach($DB_TAB_DISPOSITIF as $DB_ROW)
    {
      // Test pour éviter les pbs des élèves retirés depuis
      if(!empty($tab_user_id[$DB_ROW['user_id']]))
      {
        if(!is_null($DB_ROW['livret_modaccomp_id']))
        {
          $tab_user_id[$DB_ROW['user_id']]['dispositifs'][$DB_ROW['livret_modaccomp_code']] = $DB_ROW['info_complement'];
        }
        if(!is_null($DB_ROW['livret_devoirsfaits_id']))
        {
          $tab_user_id[$DB_ROW['user_id']]['dispositifs']['DF'] = NULL;
        }
      }
    }
    foreach($tab_user_id as $user_id => $val_user)
    {
      if(!empty($val_user['dispositifs']))
      {
        $tab_affich_dispositifs = array();
        foreach($val_user['dispositifs'] as $dispositif => $info)
        {
          $tab_affich_dispositifs[] = '<span'.infobulle($info).'>'.$dispositif.'</span>';
        }
        $tab_affich['foot_dispositif'][$user_id] = '<td><dfn data-dispositifs="'.implode(',',array_keys($val_user['dispositifs'])).'"'.$br_class.'>'.implode($br_texte2,$tab_affich_dispositifs).'</dfn></td>';
      }
    }
  }
  // On ne complète les autoévaluations que maintenant car on a besoin de l’ordre des items
  if(!empty($DB_TAB_JOIN))
  {
    foreach($DB_TAB_JOIN as $DB_ROW)
    {
      // Test pour éviter les pbs des élèves retirés depuis
      if( !empty($tab_user_id[$DB_ROW['eleve_id']]) && $DB_ROW['jointure_memo_autoeval'] )
      {
        $tab_autoeval = json_decode( $DB_ROW['jointure_memo_autoeval'] , TRUE );
        $tab_json = array();
        $title = '';
        foreach($tab_comp_id as $item_id => $val_comp)
        {
          $note = isset($tab_autoeval[$item_id]) ? $tab_autoeval[$item_id] : 'X' ;
          $tab_json[] = (int)$note ? 'N'.$note : $note ;
          $title .= '['.$note.'|'.Html::note_src($note).']'; // <img alt="'.$note.'" src="'.Html::note_src($note).'"><br>
        }
        $tab_affich['foot_autoeval'][$DB_ROW['eleve_id']] = '<td id="autoeval_'.$DB_ROW['eleve_id'].'" data-json="'.html(json_encode($tab_json)).'" class="off"'.infobulle($title).'>&nbsp;</td>';
      }
    }
  }
  // cases centrales...
  $img_note_X = Html::note_image('X','','',FALSE);
  $num_colonne = 0;
  $tab_user_colonne = array();
  foreach($tab_user_id as $user_id => $val_user)
  {
    $num_colonne++;
    $num_ligne=0;
    $tab_user_colonne[$user_id] = $num_colonne;
    foreach($tab_comp_id as $comp_id => $val_comp)
    {
      $num_ligne++;
      if($action=='saisir')
      {
        // ... avec un champ input de base
        $tab_affich[$comp_id][$user_id] = '<td class="td_clavier" id="td_C'.$num_colonne.'L'.$num_ligne.'"><input type="text" class="X" value="X" id="C'.$num_colonne.'L'.$num_ligne.'" name="'.$comp_id.'x'.$user_id.'" readonly></td>';
        $tab_plan[$user_id][$comp_id] = '<input'.infobulle($val_comp['ref'].BRJS.$val_comp['nom']).' id="plan_C'.$num_colonne.'L'.$num_ligne.'" type="text" class="X" value="X" name="'.$comp_id.'" readonly>';
      }
      elseif($action=='voir')
      {
        // ... vierges
        $tab_affich[$comp_id][$user_id] = '<td'.infobulle($val_user['nom'].' '.$val_user['prenom'].BRJS.$val_comp['ref']).'>-</td>';
        $tab_plan[$user_id][$comp_id] = '<span'.infobulle($val_comp['ref'].BRJS.$val_comp['nom']).'>'.$img_note_X.'</span>';
      }
    }
  }
  // ajouter le contenu
  $DB_TAB = DB_STRUCTURE_PROFESSEUR::DB_lister_devoir_saisies( $devoir_id , TRUE /*with_marqueurs*/ );
  $bad = 'class="X" value="X"';
  foreach($DB_TAB as $DB_ROW)
  {
    // Test pour éviter les pbs des élèves changés de groupes ou des items modifiés en cours de route
    if(isset($tab_affich[$DB_ROW['item_id']][$DB_ROW['eleve_id']]))
    {
      if($action=='saisir')
      {
        $class = (int)$DB_ROW['saisie_note'] ? 'N'.$DB_ROW['saisie_note'] : $DB_ROW['saisie_note'] ;
        $bon = 'class="'.$class.'" value="'.$DB_ROW['saisie_note'].'"';
        $tab_affich[$DB_ROW['item_id']][$DB_ROW['eleve_id']] = str_replace($bad,$bon,$tab_affich[$DB_ROW['item_id']][$DB_ROW['eleve_id']]);
        $tab_plan[  $DB_ROW['eleve_id']][$DB_ROW['item_id']] = str_replace($bad,$bon,$tab_plan[  $DB_ROW['eleve_id']][$DB_ROW['item_id']]);
      }
      elseif($action=='voir')
      {
        $tab_affich[$DB_ROW['item_id']][$DB_ROW['eleve_id']] = str_replace('>-<'      ,'>'.Html::note_image($DB_ROW['saisie_note'],'','',FALSE).'<',$tab_affich[$DB_ROW['item_id']][$DB_ROW['eleve_id']]);
        $tab_plan[  $DB_ROW['eleve_id']][$DB_ROW['item_id']] = str_replace($img_note_X,    Html::note_image($DB_ROW['saisie_note'],'','',FALSE)    ,$tab_plan[  $DB_ROW['eleve_id']][$DB_ROW['item_id']]);
      }
    }
  }
  //
  // Présentation sur plan de classe
  // Liste des équipes éventuelles
  //
  $tab_js_equipe = array();
  if( !is_int($eleves_ordre) || (!$eleves_ordre) )
  {
    Json::add_row( 'plan' , '<li>Sans objet</li>' );
  }
  else if($eleve_nb>99)
  {
    Json::add_row( 'plan' , '<li>Trop d’élèves présents dans ce regroupement !</li>' );
  }
  else
  {
    $plan_id = $eleves_ordre;
    // On ne vérifie pas que ce sont bien les élèves du professeur car une évaluation peut être partagée
    // On récupère les photos si elles existent
    $coef_reduction = 0.5;
    $img_height = PHOTO_DIMENSION_MAXI * $coef_reduction;
    $img_width  = PHOTO_DIMENSION_MAXI*2/3 * $coef_reduction;
    foreach($tab_user_id as $eleve_id => $tab)
    {
      $tab_user_id[$eleve_id] += array(
        'img_width'  => $img_width,
        'img_height' => $img_height,
        'img_src'    => '',
        'img_title'  => TRUE,
      );
    }
    $listing_user_id = implode(',',array_keys($tab_user_id));
    $DB_TAB = DB_STRUCTURE_IMAGE::DB_lister_images( $listing_user_id , 'photo' );
    if(!empty($DB_TAB))
    {
      foreach($DB_TAB as $DB_ROW)
      {
        $tab_user_id[$DB_ROW['user_id']]['img_width']  = $DB_ROW['image_largeur'] * $coef_reduction;
        $tab_user_id[$DB_ROW['user_id']]['img_height'] = $DB_ROW['image_hauteur'] * $coef_reduction;
        $tab_user_id[$DB_ROW['user_id']]['img_src']    = $DB_ROW['image_contenu'];
        $tab_user_id[$DB_ROW['user_id']]['img_title']  = FALSE;
      }
    }
    // On récupère le plan de classe
    // On ne vérifie pas que c’est le plan de classe du professeur car une évaluation peut être partagée
    $DB_ROW = DB_STRUCTURE_PROFESSEUR_PLAN::DB_recuperer_plan_prof( $plan_id );
    $nb_places = $DB_ROW['plan_nb_rangees'] * $DB_ROW['plan_nb_colonnes'];
    $nb_eleves = count($tab_user_id);
    if(empty($DB_ROW))
    {
      Json::add_row( 'plan' , '<li>Plan de classe introuvable !</li>' );
    }
    else if( $DB_ROW['groupe_id'] != $groupe_id )
    {
      Json::add_row( 'plan' , '<li>Plan de classe d’un autre regroupement !</li>' );
    }
    else if( $nb_eleves > $nb_places )
    {
      Json::add_row( 'plan' , '<li>Plan trop petit ('.$nb_places.' places pour '.$nb_eleves.' élèves) !</li>' );
    }
    else
    {
      // Récupèrer le placement des élèves, positionner au passage les élèves s’ils ne l’ont pas été, corriger d’éventuelles anomalies
      $tab_places_occupees = Outil::recuperer_ajuster_places_eleves( $plan_id , $DB_ROW['plan_nb_rangees'] , $DB_ROW['plan_nb_colonnes'] , $tab_user_id );
      $tab_equipe = array();
      // Retour
      Json::add_row( 'nb_rangees'  , $DB_ROW['plan_nb_rangees']  );
      Json::add_row( 'nb_colonnes' , $DB_ROW['plan_nb_colonnes'] );
      foreach($tab_places_occupees as $jointure_rangee => $tab_colonnes)
      {
        foreach($tab_colonnes as $jointure_colonne => $tab)
        {
          if(is_null($tab))
          {
            $div = '';
            $txt_equipe = '';
          }
          else
          {
            $img_src   = ($tab['img_src'])   ? ' src="data:'.image_type_to_mime_type(IMAGETYPE_JPEG).';base64,'.$tab['img_src'].'"' : ' src="./_img/trombinoscope_vide.png"' ;
            $img_title = ($tab['img_title']) ? infobulle('absence de photo') : '' ;
            $img_html  = '<img width="'.$tab['img_width'].'" height="'.$tab['img_height'].'" alt=""'.$img_src.$img_title.'>';
            $txt_equipe = ($tab['equipe']) ? ( ($tab['role']) ? '<br>'.$tab['equipe'].'&nbsp;'.html($tab['role']) : '<br>Équipe&nbsp;'.$tab['equipe'] ) : '';
            $div = '<div id="id'.$tab['id'].'">'.$img_html.'<span data="nom">'.html($tab['nom']).'</span><br><span data="prenom">'.html($tab['prenom']).'</span>'.$txt_equipe.'<div class="notes">'.implode('',$tab_plan[$tab['id']]).'</div></div>' ;
            if($equipe && $tab['equipe'])
            {
              // On affiche les couleurs des équipes dans le tableau principal que si c’est une évaluation par équipe.
              // D’une part car sinon ça gène la surbrillance de la colonne lors du déplacement dans les cases.
              // D’autre part car c’est plus intéressant à voir avec la présentation du plan de classe.
              $tab_affich['head'][$tab['id']] = str_replace('<th>','<th class="e'.$tab['equipe'].'">',$tab_affich['head'][$tab['id']]);
              $tab_equipe[$tab['equipe']][$tab_user_colonne[$tab['id']]] = $tab_user_colonne[$tab['id']];
            }
          }
          $class_equipe = ($txt_equipe) ? ' class="e'.$tab['equipe'].'"' : '' ;
          Json::add_row( 'plan' , '<li id="'.$jointure_rangee.'x'.$jointure_colonne.'"'.$class_equipe.'>'.$div.'</li>' );
        }
      }
      if( ($action=='saisir') && !empty($tab_equipe) )
      {
        foreach($tab_equipe as $equipe => $tab_eleves_equipe)
        {
          if(count($tab_eleves_equipe)>1)
          {
            foreach($tab_eleves_equipe as $colonne_id)
            {
              
              $tab_js_equipe[$colonne_id] = array_diff($tab_eleves_equipe,array($colonne_id));
            }
          }
        }
      }
    }
  }
  Json::add_row( 'tab_equipe' , json_encode($tab_js_equipe) );
  //
  // Affichage du retour de la présentation en tableau
  // On attendait la présentation en plan de classe pour l’ajout des équipes éventuelles
  //
  $tbody_class = ($_COOKIE[COOKIE_ISTOUCH]) ? 'v' : 'h' ;
  foreach($tab_affich as $comp_id => $tab_user)
  {
    if(!is_int($comp_id))
    {
      switch($comp_id)
      {
        case 'head'            : Json::add_row( 'table' , '<thead>' );break;
        case 'foot_dispositif' : Json::add_row( 'table' , '<tfoot>' );break;
        case 'foot_autoeval'   : break;
        case 'foot_texte'      : break;
        case 'foot_audio'      : break;
        case 'foot_doc_copie'  : break;
      }
    }
    $tr_open = (substr($comp_id,0,4)=='foot') ? '<tr class="no_margin">' : '<tr id="L'.$comp_id.'">' ;
    Json::add_row( 'table' , $tr_open );
    foreach($tab_user as $user_id => $val)
    {
      Json::add_row( 'table' , $val );
    }
    Json::add_row( 'table' , '</tr>' );
    if(!is_int($comp_id))
    {
      switch($comp_id)
      {
        case 'head'            : Json::add_row( 'table' , '</thead>' );break;
        case 'foot_dispositif' : break;
        case 'foot_autoeval'   : break;
        case 'foot_texte'      : break;
        case 'foot_audio'      : break;
        case 'foot_doc_copie'  : Json::add_row( 'table' , '</tfoot><tbody class="'.$tbody_class.'">' );break;
      }
    }
  }
  Json::add_row( 'table' , '</tbody>' );
  Json::add_row( 'is_copie' , $find_copie );
  Json::end( TRUE );
}

// ////////////////////////////////////////////////////////////////////////////////////////////////////
// Afficher le tableau avec les énoncés / corrigés individuels par élève
// ////////////////////////////////////////////////////////////////////////////////////////////////////

if( ($action=='voir_documents') && $devoir_id && $groupe_type && $groupe_id && $eleves_ordre )
{
  $tab_js_sujet = array();
  $tab_js_corrige = array();
  // liste des élèves
  $DB_TAB_USER = DB_STRUCTURE_COMMUN::DB_lister_users_regroupement( 'eleve' /*profil_type*/ , 1 /*statut*/ , $groupe_type , $groupe_id , $eleves_ordre );
  // liste des énoncés ou corrigés personnalisés
  $DB_TAB_DOC = DB_STRUCTURE_DEVOIR_JOINTURE::DB_lister_documents($devoir_id);
  // Let's go
  $eleve_nb = count($DB_TAB_USER);
  if(!$eleve_nb)
  {
    Json::end( FALSE , 'Aucun élève n’est associé à cette évaluation !' );
  }
  $tab_doc = array( 'sujet' => array() , 'corrige' => array() );
  if(!empty($DB_TAB_DOC))
  {
    $tab_balise = array(
      'sujet'   => '<a href="%HREF%" target="_blank" rel="noopener noreferrer" class="no_puce"><img alt="sujet" src="./_img/document/sujet_oui.png"'.infobulle('Sujet disponible.').'></a> <button id="bouton_supprimer_eleve_sujet_%USER%" type="button" class="supprimer">Retirer</button>',
      'corrige' => '<a href="%HREF%" target="_blank" rel="noopener noreferrer" class="no_puce"><img alt="corrigé" src="./_img/document/corrige_oui.png"'.infobulle('Corrigé disponible.').'></a> <button id="bouton_supprimer_eleve_corrige_%USER%" type="button" class="supprimer">Retirer</button>',
    );
    $tab_bad = array( '%HREF%' , '%USER%' );
    foreach($DB_TAB_DOC as $DB_ROW)
    {
      foreach($tab_balise as $doc_objet => $balise_html)
      {
        if($DB_ROW['jointure_doc_'.$doc_objet])
        {
          $url = FileSystem::verif_lien_safe($DB_ROW['jointure_doc_'.$doc_objet]);
          $tab_doc[$doc_objet][$DB_ROW['eleve_id']] = str_replace( $tab_bad , array($url,$DB_ROW['eleve_id']) , $balise_html );
          ${'tab_js_'.$doc_objet}[$ref.'_'.$DB_ROW['eleve_id']] = $url;
        }
      }
    }
  }
  $tab_affich = array();
  foreach($DB_TAB_USER as $DB_ROW)
  {
    $eleve_nom_prenom = To::texte_eleve_identite($DB_ROW['user_nom'],$DB_ROW['user_prenom'],$eleves_ordre);
    $image_sujet   = isset($tab_doc['sujet'][$DB_ROW['user_id']])   ? $tab_doc['sujet'][$DB_ROW['user_id']]   : '<img alt="sujet" src="./_img/document/sujet_non.png"> <button id="bouton_ajouter_eleve_sujet_'.$DB_ROW['user_id'].'" type="button" class="ajouter">Ajouter</button>' ;
    $image_corrige = isset($tab_doc['corrige'][$DB_ROW['user_id']]) ? $tab_doc['corrige'][$DB_ROW['user_id']] : '<img alt="corrigé" src="./_img/document/corrige_non.png"> <button id="bouton_ajouter_eleve_corrige_'.$DB_ROW['user_id'].'" type="button" class="ajouter">Ajouter</button>' ;
    $tab_affich[$DB_ROW['user_id']] = '<tr><td>'.html($eleve_nom_prenom).'</td><td id="sujet_'.$DB_ROW['user_id'].'">'.$image_sujet.'</td><td id="corrige_'.$DB_ROW['user_id'].'">'.$image_corrige.'</td></tr>';
  }
  // c’est fini ; affichage du retour
  $tbody_class = ($_COOKIE[COOKIE_ISTOUCH]) ? 'v' : 'h' ;
  Json::add_row( 'tab_sujet'   , json_encode($tab_js_sujet) );
  Json::add_row( 'tab_corrige' , json_encode($tab_js_corrige) );
  Json::add_row( 'html' , '<table class="fg"><thead><tr><th>Élève</th><th>Énoncé</th><th>Corrigé</th></tr></thead><tbody>'.implode('',$tab_affich).'</tbody></table>' );
  Json::end( TRUE );
}

// ////////////////////////////////////////////////////////////////////////////////////////////////////
// Mettre à jour les items acquis par les élèves à une évaluation
// ////////////////////////////////////////////////////////////////////////////////////////////////////

if( ( ($action=='enregistrer_saisie') || ($action=='importer_evaluation') ) && $devoir_id && $date_devoir && $choix_devoir_visible && $date_devoir_visible && $choix_saisie_visible && $date_saisie_visible && $description && count($tab_notes) && $groupe_type && $groupe_id )
{
  // Tester les droits
  $proprio_id = DB_STRUCTURE_PROFESSEUR::DB_recuperer_devoir_proprietaire_id( $devoir_id );
  if($proprio_id==$_SESSION['USER_ID'])
  {
    $niveau_droit = 4; // propriétaire
  }
  elseif($profs_liste) // forcément
  {
    $search_liste = '_'.$profs_liste.'_';
    if( strpos( $search_liste, '_m'.$_SESSION['USER_ID'].'_' ) !== FALSE )
    {
      $niveau_droit = 3; // modifier
    }
    elseif( strpos( $search_liste, '_s'.$_SESSION['USER_ID'].'_' ) !== FALSE )
    {
      $niveau_droit = 2; // saisir
    }
    elseif( strpos( $search_liste, '_v'.$_SESSION['USER_ID'].'_' ) !== FALSE )
    {
      Json::end( FALSE , 'Droit insuffisant attribué sur le devoir n°'.$devoir_id.' (niveau 1 au lieu de 2) !' ); // voir
    }
    else
    {
      Json::end( FALSE , 'Droit attribué sur le devoir n°'.$devoir_id.' non trouvé !' );
    }
  }
  else
  {
    Json::end( FALSE , 'Vous n’êtes ni propriétaire ni bénéficiaire de droits sur le devoir n°'.$devoir_id.' !' );
  }
  // Récupérer les élèves et les items du devoir afin d’éviter des valeurs truquées ou le problème d’élèves retirés entre l’affichage et la saisie (c'est arrivé).
  $tab_comp_id = array(); // liste des items
  $DB_TAB_COMP = DB_STRUCTURE_PROFESSEUR::DB_lister_devoir_items( $devoir_id , FALSE /*with_socle*/ , FALSE /*with_coef*/ , FALSE /*with_ref*/ , FALSE /*with_lien*/ , FALSE /*with_module*/ , FALSE /*with_comm*/ , FALSE /*with_domaine*/ , FALSE /*with_theme*/ );
  foreach($DB_TAB_COMP as $key => $DB_ROW)
  {
    $tab_comp_id[$DB_ROW['item_id']] = TRUE;
  }
  $tab_user_id = array(); // liste des élèves
  $DB_TAB_USER = DB_STRUCTURE_COMMUN::DB_lister_users_regroupement( 'eleve' /*profil_type*/ , 1 /*statut*/ , $groupe_type , $groupe_id , 'nom' /*eleves_ordre*/ );
  foreach($DB_TAB_USER as $DB_ROW)
  {
    $tab_user_id[$DB_ROW['user_id']] = TRUE;
  }
  // On y va
  $nb_saisies_possibles  = 0;
  $nb_saisies_effectuees = 0;
  // Tout est transmis : il faut comparer avec le contenu de la base pour ne mettre à jour que ce dont il y a besoin
  // On récupère les notes transmises dans $tab_post
  $tab_post = array();
  foreach($tab_notes as $key_note)
  {
    list( $key , $note ) = explode('_',$key_note);
    list( $item_id , $eleve_id ) = explode('x',$key);
    if( (int)$item_id && (int)$eleve_id && isset($tab_comp_id[$item_id]) && isset($tab_user_id[$eleve_id]) )
    {
      $tab_post[$item_id.'x'.$eleve_id] = $note;
      $nb_saisies_possibles++;
      $nb_saisies_effectuees += ( ($note!='X') && ($note!='PA') ) ? 1 : 0 ;
    }
  }
  // On recupère le contenu de la base déjà enregistré pour le comparer ; on remplit au fur et à mesure $tab_nouveau_modifier / $tab_nouveau_supprimer
  // $tab_demande_archiver sert à archiver des demandes d’élèves dont on met une note.
  $tab_nouveau_modifier  = array();
  $tab_nouveau_supprimer = array();
  $tab_demande_archiver  = array();
  $DB_TAB = DB_STRUCTURE_PROFESSEUR::DB_lister_devoir_saisies( $devoir_id , TRUE /*with_marqueurs*/ );
  foreach($DB_TAB as $DB_ROW)
  {
    $key = $DB_ROW['item_id'].'x'.$DB_ROW['eleve_id'];
    if( isset($tab_post[$key]) && isset($tab_comp_id[$DB_ROW['item_id']]) && isset($tab_user_id[$DB_ROW['eleve_id']]) ) // Test nécessaire si élève ou item évalués dans ce devoir, mais retiré depuis (donc non transmis dans la nouvelle saisie, mais à conserver).
    {
      if($tab_post[$key]!=$DB_ROW['saisie_note'])
      {
        if($tab_post[$key]=='X')
        {
          // valeur de la base à supprimer... sauf en cas d’évaluation partagée :
          // en effet, dans ce cas, plusieurs collègues peuvent co-saisir en même temps,
          // il est plus prudent de ne pas écraser des notes qui viendraient d’être enregistrées par des collègues,
          // quitte à se passer de la possibilité de retirer une note saisie par un collègue,
          // dans ce cas il faut d’abord la modifier (la modification restant possible) pour s’attribuer la paternité de la saisie, avant de la supprimer.
          // (mais bon, on touche là à une situation rarissime..)
          if( $DB_ROW['prof_id']==$_SESSION['USER_ID'])
          {
            $tab_nouveau_supprimer[$key] = $key;
          }
        }
        else
        {
          // valeur de la base à modifier
          $tab_nouveau_modifier[$key] = $tab_post[$key];
          if($DB_ROW['saisie_note']=='PA')
          {
            // demande d’évaluation à supprimer
            $tab_demande_archiver[$key] = $key;
          }
        }
      }
      unset($tab_post[$key]);
    }
  }
  // Il reste dans $tab_post les données à ajouter (mises dans $tab_nouveau_ajouter) et les données qui ne servent pas (non enregistrées et non saisies)
  $tab_nouveau_ajouter = array_filter($tab_post,'sans_rien');
  // Il n’y a plus qu’à mettre à jour la base
  if( !count($tab_nouveau_ajouter) && !count($tab_nouveau_modifier) && !count($tab_nouveau_supprimer) )
  {
    Json::end( FALSE , 'Aucune modification détectée !' );
  }
  // L’information associée à la note comporte le nom de l’évaluation + celui du professeur (c'est une information statique, conservée sur plusieurs années)
  $date_devoir_sql         = To::date_french_to_sql($date_devoir);
  $date_saisie_visible_sql = To::date_french_to_sql($date_saisie_visible);
  $date_saisie_visible_sql = ( TODAY_SQL < $date_saisie_visible_sql ) ? $date_saisie_visible_sql : NULL ;
  $info = $description.' ('.To::texte_genre_identite($_SESSION['USER_NOM'],FALSE,$_SESSION['USER_PRENOM'],TRUE,$_SESSION['USER_GENRE']).')';
  $tab_notif = array();
  foreach($tab_nouveau_ajouter as $key => $note)
  {
    list($item_id,$eleve_id) = explode('x',$key);
    DB_STRUCTURE_PROFESSEUR::DB_ajouter_saisie( $_SESSION['USER_ID'] , $eleve_id , $devoir_id , $item_id , $date_devoir_sql , $note , $info , $date_saisie_visible_sql );
    $tab_notif[$eleve_id] = $eleve_id;
  }
  foreach($tab_nouveau_modifier as $key => $note)
  {
    list($item_id,$eleve_id) = explode('x',$key);
    DB_STRUCTURE_PROFESSEUR::DB_modifier_saisie( $_SESSION['USER_ID'] , $eleve_id , $devoir_id , $item_id , $note , $info );
    $tab_notif[$eleve_id] = $eleve_id;
  }
  foreach($tab_nouveau_supprimer as $key => $key)
  {
    list($item_id,$eleve_id) = explode('x',$key);
    DB_STRUCTURE_PROFESSEUR::DB_supprimer_saisie( $eleve_id , $devoir_id , $item_id );
    $tab_notif[$eleve_id] = $eleve_id;
  }
  foreach($tab_demande_archiver as $key => $key)
  {
    list($item_id,$eleve_id) = explode('x',$key);
    DB_STRUCTURE_DEMANDE::DB_archiver_demande_precise_eleve_item( $eleve_id , $item_id );
  }
  // Notifications (rendues visibles ultérieurement) ; le mode discret ne d’applique volontairement pas ici car les modifications sont chirurgicales
  $listing_eleves = implode(',',$tab_notif);
  $listing_parents = DB_STRUCTURE_NOTIFICATION::DB_lister_parents_listing_id($listing_eleves);
  $listing_users = ($listing_parents) ? $listing_eleves.','.$listing_parents : $listing_eleves ;
  $listing_abonnes = DB_STRUCTURE_NOTIFICATION::DB_lister_destinataires_listing_id( $abonnement_ref_saisie , $listing_users );
  if($listing_abonnes)
  {
    $adresse_lien_profond = Sesamail::adresse_lien_profond('page=evaluation&section=voir&devoir_id='.$devoir_id.'&eleve_id=');
    $notification_contenu = 'Saisies pour l’évaluation "'.$description.'" du '.$date_devoir.' enregistrées par '.To::texte_genre_identite($_SESSION['USER_NOM'],FALSE,$_SESSION['USER_PRENOM'],TRUE,$_SESSION['USER_GENRE']).'.'."\r\n\r\n";
    $tab_abonnes = DB_STRUCTURE_NOTIFICATION::DB_lister_detail_abonnes_envois( $listing_abonnes , $listing_eleves , $listing_parents );
    foreach($tab_abonnes as $abonne_id => $tab_abonne)
    {
      foreach($tab_abonne as $eleve_id => $notification_intro_eleve)
      {
        $notification_lien = 'Voir le détail :'."\r\n".$adresse_lien_profond.$eleve_id;
        DB_STRUCTURE_NOTIFICATION::DB_modifier_log_attente( $abonne_id , $abonnement_ref_edition , $devoir_id , $date_saisie_visible_sql , $notification_intro_eleve.$notification_contenu.$notification_lien , 'remplacer' );
      }
    }
  }
  // Retour
  $remplissage_nombre   = $nb_saisies_effectuees.'/'.$nb_saisies_possibles ;
  $remplissage_class    = (!$nb_saisies_effectuees) ? 'br' : ( ($nb_saisies_effectuees<$nb_saisies_possibles) ? 'bj' : 'bv' ) ;
  $remplissage_class2   = ($fini=='oui') ? ' bf' : '' ;
  $remplissage_contenu  = ($fini=='oui') ? '<span>terminé</span><i>'.$remplissage_nombre.'</i>' : '<span>'.$remplissage_nombre.'</span><i>terminé</i>' ;
  $remplissage_lien1    = '<a href="#fini" class="fini"'.infobulle('Cliquer pour indiquer (ou pas) qu’il n’y a plus de saisies à effectuer.').'>';
  $remplissage_lien2    = '</a>';
  //Si ($action=='importer_evaluation') cela s'ajoute aux Json::add_row( ) déjà renseignés
  Json::add_row( 'td' , '<td class="'.$remplissage_class.$remplissage_class2.'">'.$remplissage_lien1.$remplissage_contenu.$remplissage_lien2.'</td>' );
  Json::end( TRUE );
}

// ////////////////////////////////////////////////////////////////////////////////////////////////////
// Générer un csv à récupérer pour une saisie déportée, vide ou plein.
// Générer un pdf contenant un tableau de saisie, vide ou plein.
// ////////////////////////////////////////////////////////////////////////////////////////////////////

if( in_array($action,array('generer_tableau_scores_vierge_csv','generer_tableau_scores_rempli_csv','generer_tableau_scores_vierge_pdf','generer_tableau_scores_rempli_pdf')) && $devoir_id && $groupe_id && $date_devoir && $description && $eleves_ordre && $couleur && $fond && $cart_detail ) // $groupe_nom est aussi transmis
{
  list( , , , $remplissage , $format ) = explode('_',$action);
  $tab_scores  = array(); // tableau bi-dimensionnel [id_item][id_user]
  $tab_user_id = array(); // [id_user] => dispositifs
  $tab_comp_id = array(); // pas indispensable, mais plus lisible
  $with_ref    = ($format=='csv') ? TRUE : ( ($cart_detail=='minimal') ? TRUE  : $aff_reference );
  $with_coef   = ($format=='csv') ? TRUE : ( ($cart_detail=='minimal') ? FALSE : $aff_coef );
  $with_socle  = ($format=='csv') ? TRUE : ( ($cart_detail=='minimal') ? FALSE : $aff_socle );
  // liste des items
  $DB_TAB_COMP = DB_STRUCTURE_PROFESSEUR::DB_lister_devoir_items( $devoir_id , $with_socle , $with_coef , $with_ref , FALSE /*with_lien*/ , FALSE /*with_module*/ , FALSE /*with_comm*/ , FALSE /*with_domaine*/ , FALSE /*with_theme*/ );
  // liste des élèves
  $DB_TAB_USER = DB_STRUCTURE_COMMUN::DB_lister_users_regroupement( 'eleve' /*profil_type*/ , 1 /*statut*/ , $groupe_type , $groupe_id , $eleves_ordre );
  // Let's go
  $item_nb = count($DB_TAB_COMP);
  if(!$item_nb)
  {
    Json::end( FALSE , 'Aucun item n’est associé à cette évaluation !' );
  }
  $eleve_nb = count($DB_TAB_USER);
  if(!$eleve_nb)
  {
    Json::end( FALSE , 'Aucun élève n’est associé à cette évaluation !' );
  }
  // liste items
  foreach($DB_TAB_COMP as $key => $DB_ROW)
  {
    if($with_ref)
    {
      $DB_TAB_COMP[$key]['item_ref'] = ($DB_ROW['ref_perso']) ? $DB_ROW['ref_perso'] : $DB_ROW['ref_auto'] ;
      $tab_comp_id[$DB_ROW['item_id']] = $DB_ROW['matiere_ref'].'.'.$DB_TAB_COMP[$key]['item_ref'];
      unset( $DB_TAB_COMP[$key]['ref_perso'] , $DB_TAB_COMP[$key]['ref_auto'] );
    }
    else
    {
      $tab_comp_id[$DB_ROW['item_id']] = TRUE;
    }
  }
  // liste élèves
  foreach($DB_TAB_USER as $key => $DB_ROW)
  {
    $tab_user_id[$DB_ROW['user_id']] = array();
    $DB_TAB_USER[$key]['user_identite'] = To::texte_eleve_identite($DB_ROW['user_nom'],$DB_ROW['user_prenom'],$eleves_ordre);
  }
  // Lister les dispositifs
  // On ne complète les dispositifs que maintenant car on a besoin de la liste des élèves
  $listing_user_id = implode(',',array_keys($tab_user_id));
  $date_devoir_sql = To::date_french_to_sql($date_devoir);
  $DB_TAB_DISPOSITIF = DB_STRUCTURE_COMMUN::DB_lister_eleves_dispositifs( $listing_user_id , 'date' , $date_devoir_sql );
  if(!empty($DB_TAB_DISPOSITIF))
  {
    foreach($DB_TAB_DISPOSITIF as $DB_ROW)
    {
      if(!is_null($DB_ROW['livret_modaccomp_id']))
      {
        $tab_user_id[$DB_ROW['user_id']][$DB_ROW['livret_modaccomp_code']] = $DB_ROW['info_complement'];
      }
      if(!is_null($DB_ROW['livret_devoirsfaits_id']))
      {
        $tab_user_id[$DB_ROW['user_id']]['DF'] = NULL;
      }
    }
  }
  // récupération des scores
  foreach($tab_user_id as $user_id=>$tab_dispositifs)
  {
    foreach($tab_comp_id as $comp_id=>$val_comp)
    {
      $tab_scores[$comp_id][$user_id] = '';
    }
  }
  if($remplissage=='rempli')
  {
    $DB_TAB = DB_STRUCTURE_PROFESSEUR::DB_lister_devoir_saisies( $devoir_id , TRUE /*with_marqueurs*/ );
    foreach($DB_TAB as $DB_ROW)
    {
      // Test pour éviter les pbs des élèves changés de groupes ou des items modifiés en cours de route
      if(isset($tab_scores[$DB_ROW['item_id']][$DB_ROW['eleve_id']]))
      {
        $tab_scores[$DB_ROW['item_id']][$DB_ROW['eleve_id']] = $DB_ROW['saisie_note'];
      }
    }
  }
  //
  // pdf contenant un tableau de saisie vide ou plein
  //
  if($format=='pdf')
  {
    Form::save_choix('evaluation_archivage');
    $tab_couleurs = array( 'oui'=>'couleur' , 'non'=>'monochrome' );
    $pdf = new PDF_evaluation_tableau( FALSE /*officiel*/ , 'A4' /*page_size*/ , 'landscape' /*orientation*/ , 10 /*marge_gauche*/ , 10 /*marge_droite*/ , 10 /*marge_haut*/ , 10 /*marge_bas*/ , $couleur , $fond );
    $pdf->saisie_initialiser( $eleve_nb , $item_nb , $cart_detail );
    // 1ère ligne : référence devoir, noms élèves
    $pdf->saisie_entete( $groupe_nom , $date_devoir , $description , $DB_TAB_USER );
    // ligne suivantes : référence item, cases vides ou pleines
    $tab_scores = ($remplissage=='rempli') ? $tab_scores : NULL ;
    $pdf->saisie_cases_eleves( $DB_TAB_COMP , $DB_TAB_USER , $eleve_nb , $tab_scores , $cart_detail , $with_ref , $with_coef , $with_socle );
    // dernière ligne : dispositifs
    $pdf->saisie_dispositifs( $tab_user_id );
    // On enregistre le PDF
    $fichier_nom = 'tableau_'.$remplissage.'_'.$tab_couleurs[$couleur].'_'.$fnom_export.'.pdf';
    FileSystem::ecrire_objet_pdf( CHEMIN_DOSSIER_EXPORT.$fichier_nom , $pdf );
    // Affichage du lien
    Json::end( TRUE , '<a target="_blank" rel="noopener noreferrer" href="'.URL_DIR_EXPORT.$fichier_nom.'"><span class="file file_pdf">Tableau '.$remplissage.' (format <em>pdf</em>).</span></a>' );
  }
  //
  // csv contenant un tableau de saisie vide ou plein
  //
  if($format=='csv')
  {
    $tab_conversion = array();
    foreach( $_SESSION['NOTE_ACTIF'] as $note_id )
    {
      $tab_conversion[$note_id] = $_SESSION['NOTE'][$note_id]['CLAVIER'];
    }
    $tab_conversion += array(
      'AB' => 'A' ,
      'DI' => 'D' ,
      'NE' => 'E' ,
      'NF' => 'F' ,
      'NN' => 'N' ,
      'NR' => 'R' ,
      'PA' => 'P' ,
      ''   => ' ' ,
    );
    $csv_colonne_texte = array();
    // première colonne (références items) pour le CSV + dernière colonne (noms items) pour le CSV
    foreach($DB_TAB_COMP as $DB_ROW)
    {
      $item_ref    = $tab_comp_id[$DB_ROW['item_id']];
      $texte_s2016 = ($DB_ROW['s2016_nb'])  ? ' [S]' : ' [–]' ;
      $texte_coef  = ' [×'.$DB_ROW['item_coef'].']';
      $tab_scores[$DB_ROW['item_id']][0] = $DB_ROW['item_id'];
      $csv_colonne_texte[$DB_ROW['item_id']] = $item_ref.$texte_s2016.$texte_coef.' '.$DB_ROW['item_nom'];
    }
    // première ligne (identifiants des élèves) + dernière ligne (noms prénoms des élèves)
    $csv_ligne_eleve_id   = array(' ');
    $csv_ligne_eleve_nom  = array(' ');
    $csv_ligne_dispositif = array(' ');
    $csv_nb_colonnes = 0;
    foreach($DB_TAB_USER as $DB_ROW)
    {
      $csv_ligne_eleve_nom[]  = To::texte_eleve_identite($DB_ROW['user_nom'],$DB_ROW['user_prenom'],$eleves_ordre);
      $csv_ligne_dispositif[] = implode(',',array_keys($tab_user_id[$DB_ROW['user_id']]));
      $csv_ligne_eleve_id[]   = $DB_ROW['user_id'];
      $csv_nb_colonnes++;
    }
    $csv = new CSV();
    $csv->add( $csv_ligne_eleve_id , 1 );
    // première colonne (identifiants items) + cases centrales vides ou pleines + dernière colonne (noms items)
    foreach($tab_comp_id as $comp_id=>$val_comp)
    {
      $csv->add( $tab_scores[$comp_id][0] );
      if($remplissage=='vierge')
      {
        $csv->add( array_fill( 0 , $csv_nb_colonnes, '' ) );
      }
      else
      {
        foreach($tab_user_id as $user_id=>$tab_dispositifs)
        {
          $csv->add( $tab_conversion[$tab_scores[$comp_id][$user_id]] );
        }
      }
      $csv->add( $csv_colonne_texte[$comp_id] , 1 );
    }
    // Fin du csv
    array_pop($tab_conversion);
    $csv->add( $csv_ligne_eleve_nom , 1 )->add( $csv_ligne_dispositif , 2 )
        ->add( $groupe_nom , 1 )->add( $date_devoir , 1 )->add( $description , 2 )
        ->add( 'CODAGES AUTORISÉS : '.implode(',',$tab_conversion) , 2 );
    // Ajout d’infos pour PDF4Teachers https://pdf4teachers.org
    if($is_pdf4teachers)
    {
      $csv->add( 'PDF4Teachers' , 1 )->add( array('CLAVIER','SIGLE','LEGENDE','IMAGE') , 1 );
      foreach( $_SESSION['NOTE_ACTIF'] as $note_id )
      {
        $tab_note = $_SESSION['NOTE'][$note_id];
        $csv->add( $tab_note['CLAVIER'] )->add( $tab_note['SIGLE'] )->add( $tab_note['LEGENDE'] )
            ->add( 'data:image/gif;base64,'.base64_encode(file_get_contents($tab_note['FICHIER'])) , 1 );
      }
    }
    // On enregistre le CSV
    $fichier_nom = 'saisie_deportee_'.$remplissage.'_'.$fnom_export.'.csv';
    FileSystem::ecrire_objet_csv( CHEMIN_DOSSIER_EXPORT.$fichier_nom , $csv );
    // Affichage du lien
    Json::end( TRUE , '<a target="_blank" rel="noopener noreferrer" href="'.URL_DIR_EXPORT.$fichier_nom.'"><span class="file file_txt">Fichier '.$remplissage.' (format <em>csv</em>).</span></a>' );
  }
}

// ////////////////////////////////////////////////////////////////////////////////////////////////////
// Voir ou Archiver la répartition, nominative ou quantitative, des élèves par item
// ////////////////////////////////////////////////////////////////////////////////////////////////////

if( in_array($action,array('voir_repart','archiver_repart')) && in_array($repart_ref_pourcentage,array('tous','only_eval')) && $devoir_id && $groupe_id && $date_devoir && $description && $eleves_ordre ) // $groupe_nom est aussi transmis
{
  // liste des items
  $DB_TAB_ITEM = DB_STRUCTURE_PROFESSEUR::DB_lister_devoir_items( $devoir_id , TRUE /*with_socle*/ , TRUE /*with_coef*/ , TRUE /*with_ref*/ , TRUE /*with_lien*/ , FALSE /*with_module*/ , FALSE /*with_comm*/ , FALSE /*with_domaine*/ , FALSE /*with_theme*/ );
  // liste des élèves
  $eleves_ordre = is_int($eleves_ordre) ? 'nom' : $eleves_ordre ;
  $DB_TAB_USER = DB_STRUCTURE_COMMUN::DB_lister_users_regroupement( 'eleve' /*profil_type*/ , 1 /*statut*/ , $groupe_type , $groupe_id , $eleves_ordre );
  // Let's go
  $item_nb = count($DB_TAB_ITEM);
  if(!$item_nb)
  {
    Json::end( FALSE , 'Aucun item n’est associé à cette évaluation !' );
  }
  $eleve_nb = count($DB_TAB_USER);
  if(!$eleve_nb)
  {
    Json::end( FALSE , 'Aucun élève n’est associé à cette évaluation !' );
  }
  Form::save_choix('evaluation_statistiques');
  $tab_user_id = array(); // pas indispensable, mais plus lisible
  $tab_item_id = array(); // pas indispensable, mais plus lisible
  $tab_user_order = array(); // pour trier les répartition nominatives
  // noms prénoms des élèves
  foreach($DB_TAB_USER as $key => $DB_ROW)
  {
    $tab_user_id[$DB_ROW['user_id']] = html( To::texte_eleve_identite($DB_ROW['user_nom'],$DB_ROW['user_prenom'],$eleves_ordre) );
    $tab_user_order[$DB_ROW['user_id']] = $key;
  }
  // noms des items
  foreach($DB_TAB_ITEM as $DB_ROW)
  {
    $item_ref = ($DB_ROW['ref_perso']) ? $DB_ROW['ref_perso'] : $DB_ROW['ref_auto'] ;
    $texte_s2016 = ($DB_ROW['s2016_nb'])  ? ' [S]' : ' [–]' ;
    $texte_coef  = ' [×'.$DB_ROW['item_coef'].']';
    $tab_item_id[$DB_ROW['item_id']] = array( $DB_ROW['matiere_ref'].'.'.$item_ref.$texte_s2016.$texte_coef , $DB_ROW['item_nom'] , $DB_ROW['item_lien'] );
  }
  // tableaux utiles ou pour conserver les infos
  $tab_init_nominatif = array();
  $tab_init_quantitatif = array();
  foreach( $_SESSION['NOTE_ACTIF'] as $note_id )
  {
    $tab_init_nominatif[  $note_id] = array();
    $tab_init_quantitatif[$note_id] = 0;
  }
  if($repart_categorie_autre)
  {
    $tab_init_nominatif[  'X'] = array();
    $tab_init_quantitatif['X'] = 0;
  }
  $tab_repartition_nominatif   = array();
  $tab_repartition_quantitatif = array();
  $tab_selection_nominatif     = array();
  $tab_nombre_eleves           = array();
  // initialisation
  foreach($tab_item_id as $item_id => $tab_infos_item)
  {
    $tab_repartition_nominatif[$item_id]   = $tab_init_nominatif;
    $tab_repartition_quantitatif[$item_id] = $tab_init_quantitatif;
    $tab_selection_nominatif[$item_id]     = $tab_init_nominatif;
    $tab_nombre_eleves[$item_id]           = 0;
  }
  // remplissage
  $DB_TAB = DB_STRUCTURE_PROFESSEUR::DB_lister_devoir_saisies( $devoir_id , FALSE /*with_marqueurs*/ );
  foreach($DB_TAB as $DB_ROW)
  {
    // Test pour éviter les pbs des élèves changés de groupes ou des items modifiés en cours de route
    if( isset($tab_user_id[$DB_ROW['eleve_id']]) && isset($tab_item_id[$DB_ROW['item_id']]) )
    {
      $note  = isset($tab_init_quantitatif[$DB_ROW['saisie_note']]) ? $DB_ROW['saisie_note'] : 'X' ; // Regrouper ce qui est hors des codes couleurs usuels
      $eleve = isset($tab_init_quantitatif[$DB_ROW['saisie_note']]) ? $tab_user_id[$DB_ROW['eleve_id']] : $tab_user_id[$DB_ROW['eleve_id']].' ('.$DB_ROW['saisie_note'].')' ; // Ajouter la note si hors des codes couleurs usuels
      $checkbox_user = '<input type="checkbox" name="id_user[]" value="'.$DB_ROW['eleve_id'].'">';
      $checkbox_req  = '<input type="checkbox" name="id_req[]" value="'.$DB_ROW['eleve_id'].'x'.$DB_ROW['item_id'].'">';
      if( $repart_categorie_autre || ($note!='X') )
      {
        $tab_repartition_nominatif[$DB_ROW['item_id']][$note][$DB_ROW['eleve_id']] = $eleve;
        $tab_repartition_quantitatif[$DB_ROW['item_id']][$note]++;
        $tab_selection_nominatif[$DB_ROW['item_id']][$note][$DB_ROW['eleve_id']] = $checkbox_user.$checkbox_req.' '.$eleve;
      }
    }
  }
  foreach($tab_repartition_quantitatif as $item_id => $tab)
  {
    $tab_nombre_eleves[$item_id] = ($repart_ref_pourcentage == 'tous') ? $eleve_nb : max( 1 , array_sum($tab) ) ;
  }
  // Tri des tableaux nominatifs par ordre alphabétique des élèves pour chaque catégorie
  // La fonction tri_clefs() utilisée avec uksort() trie les élèves par ordre alphabétique à partir de leur identifiant.
  function tri_clefs($key1, $key2)
  {
    global $tab_user_order;
    return $tab_user_order[$key1] - $tab_user_order[$key2];
  }
  foreach($tab_item_id as $item_id => $tab_infos_item)
  {
    foreach($tab_repartition_nominatif[$item_id] as $code => $tab_eleves)
    {
      uksort( $tab_repartition_nominatif[$item_id][$code] , 'tri_clefs' );
    }
    foreach($tab_selection_nominatif[$item_id] as $code => $tab_eleves)
    {
      uksort( $tab_selection_nominatif[$item_id][$code] , 'tri_clefs' );
    }
  }
  //
  // Sorties HTML (affichage direct + page avec cases à cocher)
  //
  if($action=='voir_repart')
  {
    // 1e ligne : référence des codes
    $affichage_repartition_head = '<th class="nu"></th>';
    foreach($tab_init_quantitatif as $note => $vide)
    {
      $affichage_repartition_head .= ($note!='X') ? '<th>'.Html::note_image($note,'','',FALSE).'</th>' : '<th>Autre</th>' ;
    }
    // PARTIE 1 : assemblage / affichage du tableau avec la répartition quantitative
    Json::add_row( 'quantitative' , '<thead><tr>'.$affichage_repartition_head.'</tr></thead><tbody>' );
    foreach($tab_item_id as $item_id => $tab_infos_item)
    {
      $texte_lien_avant = ($tab_infos_item[2]) ? '<a target="_blank" rel="noopener noreferrer" href="'.html($tab_infos_item[2]).'">' : '';
      $texte_lien_apres = ($tab_infos_item[2]) ? '</a>' : '';
      Json::add_row( 'quantitative' , '<tr>' );
      Json::add_row( 'quantitative' , '<th><b>'.$texte_lien_avant.html($tab_infos_item[0]).$texte_lien_apres.'</b><br>'.html($tab_infos_item[1]).'</th>' );
      foreach($tab_repartition_quantitatif[$item_id] as $code => $note_nb)
      {
        $valeur = round( 100 * $note_nb / $tab_nombre_eleves[$item_id] );
        Json::add_row( 'quantitative' , '<td style="font-size:'.(75+$valeur).'%">'.$valeur.'%</td>' );
      }
      Json::add_row( 'quantitative' , '</tr>' );
    }
    Json::add_row( 'quantitative' , '</tbody>' );
    // PARTIE 2 : assemblage / affichage du tableau avec la répartition nominative
    Json::add_row( 'nominative' , '<thead><tr>'.$affichage_repartition_head.'</tr></thead><tbody>' );
    foreach($tab_item_id as $item_id => $tab_infos_item)
    {
      $texte_lien_avant = ($tab_infos_item[2]) ? '<a target="_blank" rel="noopener noreferrer" href="'.html($tab_infos_item[2]).'">' : '';
      $texte_lien_apres = ($tab_infos_item[2]) ? '</a>' : '';
      Json::add_row( 'nominative' , '<tr>' );
      Json::add_row( 'nominative' , '<th><b>'.$texte_lien_avant.html($tab_infos_item[0]).$texte_lien_apres.'</b><br>'.html($tab_infos_item[1]).'</th>' );
      foreach($tab_repartition_nominatif[$item_id] as $code => $tab_eleves)
      {
        Json::add_row( 'nominative' , '<td>'.implode('<br>',$tab_eleves).'</td>' );
      }
      Json::add_row( 'nominative' , '</tr>' );
    }
    Json::add_row( 'nominative' , '</tbody>' );
    // PARTIE 3 : assemblage de la page HTML avec cases à cocher
    $html  = '<style>'.$_SESSION['CSS'].'</style>'.NL;
    $html .= '<h1>Exploitation d’une évaluation</h1>'.NL;
    $html .= '<h2>'.$groupe_nom.' | '.$date_devoir.' | '.$description.'</h2>'.NL;
    $html .= '<hr>'.NL;
    $html .= '<form id="form_synthese" action="#" method="post">'.NL;
    $html .= HtmlForm::afficher_synthese_exploitation('eleves + eleves-items').NL;
    $html .= '<table class="eval_exploitation">'.NL;
    $html .= '<thead><tr>'.$affichage_repartition_head.'</tr></thead>'.NL;
    $html .= '<tbody>';
    foreach($tab_item_id as $item_id => $tab_infos_item)
    {
      $html .= '<tr>';
      $html .= '<th><b>'.html($tab_infos_item[0]).'</b><br>'.html($tab_infos_item[1]).'</th>';
      foreach($tab_selection_nominatif[$item_id] as $code => $tab_eleves)
      {
        $html .= '<td>'.implode('<br>',$tab_eleves).'</td>';
      }
      $html .= '</tr>';
    }
    $html .= '</tbody>'.NL;
    $html .= '</table>'.NL;
    $html .= '</form>'.NL;
    // On enregistre la sortie HTML
    $fichier_nom = 'evaluation_'.$devoir_id.'_'.FileSystem::generer_fin_nom_fichier__date_et_alea();
    FileSystem::ecrire_fichier(CHEMIN_DOSSIER_EXPORT.$fichier_nom.'.html' , $html );
    // Affichage de l’adresse
    Json::add_row( 'href' , './releve_html.php?fichier='.$fichier_nom );
    // Terminé !
    Json::end( TRUE );
  }
  //
  // Sortie PDF
  //
  elseif( ($action=='archiver_repart') && $repartition_type && $couleur && $fond )
  {
    if($repartition_type=='quantitative')
    {
      $pdf = new PDF_evaluation_tableau( FALSE /*officiel*/ , 'A4' /*page_size*/ , 'portrait' /*orientation*/ , 10 /*marge_gauche*/ , 10 /*marge_droite*/ , 10 /*marge_haut*/ , 10 /*marge_bas*/ , $couleur , $fond );
      $pdf->repartition_quantitative_initialiser($item_nb);
      // 1ère ligne : référence du devoir et des codes
      $pdf->repartition_quantitative_entete( $groupe_nom , $date_devoir , $description , $tab_init_quantitatif );
      // ligne suivantes : référence item, cases répartition quantitative
      foreach($tab_item_id as $item_id => $tab_infos_item)
      {
        // ligne de répartition pour 1 item : référence item
        $pdf->saisie_reference_item( $tab_infos_item[0] , $tab_infos_item[1] , FALSE /*fusion_lignes*/ );
        // ligne de répartition pour 1 item : cases répartition quantitative
        $pdf->repartition_quantitative_cases_eleves( $tab_repartition_quantitatif[$item_id] , $tab_nombre_eleves[$item_id] );
      }
    }
    elseif($repartition_type=='nominative')
    {
      $pdf = new PDF_evaluation_tableau( FALSE /*officiel*/ , 'A4' /*page_size*/ , 'landscape' /*orientation*/ , 10 /*marge_gauche*/ , 10 /*marge_droite*/ , 10 /*marge_haut*/ , 10 /*marge_bas*/ , $couleur , $fond );
      // il faut additionner le nombre maxi d’élèves par case de chaque item (sans descendre en dessous de 4 pour avoir la place d’afficher l’intitulé de l’item) afin de prévoir le nb de lignes nécessaires
      $somme = 0;
      foreach($tab_repartition_quantitatif as $item_id => $tab_effectifs)
      {
        $somme += max(4,max($tab_effectifs));
      }
      $pdf->repartition_nominative_initialiser($somme);
      foreach($tab_item_id as $item_id => $tab_infos_item)
      {
        // 1ère ligne : nouvelle page si besoin + référence du devoir et des codes si besoin
        $pdf->repartition_nominative_entete( $groupe_nom , $date_devoir , $description , $tab_init_quantitatif , $tab_repartition_quantitatif[$item_id] );
        // ligne de répartition pour 1 item : référence item
        $pdf->saisie_reference_item( $tab_infos_item[0] , $tab_infos_item[1] , FALSE /*fusion_lignes*/ );
        // ligne de répartition pour 1 item : cases répartition nominative
        $pdf->repartition_nominative_cases_eleves( $tab_repartition_nominatif[$item_id] );
      }
    }
    // On enregistre le PDF
    $tab_couleurs = array( 'oui'=>'couleur' , 'non'=>'monochrome' );
    $fichier_nom = 'repartition_'.$repartition_type.'_'.$tab_couleurs[$couleur].'_'.$fnom_export.'.pdf';
    FileSystem::ecrire_objet_pdf( CHEMIN_DOSSIER_EXPORT.$fichier_nom , $pdf );
    Json::end( TRUE , '<a target="_blank" rel="noopener noreferrer" href="'.URL_DIR_EXPORT.$fichier_nom.'"><span class="file file_pdf">Répartition '.$repartition_type.' (format <em>pdf</em>).</span></a>' );
  }
}

// ////////////////////////////////////////////////////////////////////////////////////////////////////
// Mettre à jour l’ordre des items d’une évaluation
// ////////////////////////////////////////////////////////////////////////////////////////////////////

if( ($action=='enregistrer_ordre') && $devoir_id && count($tab_id) )
{
  // Tester les droits
  $proprio_id = DB_STRUCTURE_PROFESSEUR::DB_recuperer_devoir_proprietaire_id( $devoir_id );
  if($proprio_id==$_SESSION['USER_ID'])
  {
    $niveau_droit = 4; // propriétaire
  }
  elseif($profs_liste) // forcément
  {
    $search_liste = '_'.$profs_liste.'_';
    if( strpos( $search_liste, '_m'.$_SESSION['USER_ID'].'_' ) !== FALSE )
    {
      $niveau_droit = 3; // modifier
    }
    elseif( strpos( $search_liste, '_s'.$_SESSION['USER_ID'].'_' ) !== FALSE )
    {
      Json::end( FALSE , 'Droit insuffisant attribué sur le devoir n°'.$devoir_id.' (niveau 2 au lieu de 3) !' ); // saisir
    }
    elseif( strpos( $search_liste, '_v'.$_SESSION['USER_ID'].'_' ) !== FALSE )
    {
      Json::end( FALSE , 'Droit insuffisant attribué sur le devoir n°'.$devoir_id.' (niveau 1 au lieu de 3) !' ); // voir
    }
    else
    {
      Json::end( FALSE , 'Droit attribué sur le devoir n°'.$devoir_id.' non trouvé !' );
    }
  }
  else
  {
    Json::end( FALSE , 'Vous n’êtes ni propriétaire ni bénéficiaire de droits sur le devoir n°'.$devoir_id.' !' );
  }
  // Mise à jour dans la base
  DB_STRUCTURE_PROFESSEUR::DB_modifier_ordre_item( $devoir_id , $tab_id );
  Json::end( TRUE );
}

// ////////////////////////////////////////////////////////////////////////////////////////////////////
// Imprimer un cartouche d’une évaluation
// ////////////////////////////////////////////////////////////////////////////////////////////////////

if( ($action=='imprimer_cartouche') && ( $cart_all_eleves || count($tab_cart_eleve) ) && $devoir_id && $groupe_id && $date_devoir && $description && $cart_detail && in_array($cart_cases_nb,array(1,5)) && in_array($cart_ordre,array(-1,0,1)) && $cart_contenu && $cart_restriction_item && $cart_restriction_eleve && $cart_hauteur && $orientation && $marge_min && $couleur && $fond && $legende && $eleves_ordre )
{
  $with_nom     = (substr($cart_contenu,0,8)=='AVEC_nom')  ? TRUE  : FALSE ;
  $with_result  = (substr($cart_contenu,9)=='AVEC_result') ? TRUE  : FALSE ;
  $with_ref     = ($cart_detail=='minimal')                ? TRUE  : $aff_reference ;
  $with_coef    = ($cart_detail=='minimal')                ? FALSE : $aff_coef ;
  $with_socle   = ($cart_detail=='minimal')                ? FALSE : $aff_socle ;
  $with_domaine = ($cart_detail=='minimal')                ? FALSE : $aff_domaine ;
  $with_theme   = ($cart_detail=='minimal')                ? FALSE : $aff_theme ;
  // liste des items
  $DB_TAB_COMP = DB_STRUCTURE_PROFESSEUR::DB_lister_devoir_items( $devoir_id , $with_socle , $with_coef , $with_ref , FALSE /*with_lien*/ , FALSE /*with_module*/ , FALSE /*with_comm*/ , $with_domaine , $with_theme );
  // liste des élèves
  $DB_TAB_USER = DB_STRUCTURE_COMMUN::DB_lister_users_regroupement( 'eleve' /*profil_type*/ , 1 /*statut*/ , $groupe_type , $groupe_id , $eleves_ordre );
  if(!$cart_all_eleves)
  {
    // on ne conserve que ceux demandés
    foreach($DB_TAB_USER as $key => $DB_ROW)
    {
      if( !in_array($DB_ROW['user_id'],$tab_cart_eleve) )
      {
        unset($DB_TAB_USER[$key]);
      }
    }
  }
  // Let's go
  if(empty($DB_TAB_COMP))
  {
    Json::end( FALSE , 'Aucun item n’est associé à cette évaluation !' );
  }
  if(empty($DB_TAB_USER))
  {
    Json::end( FALSE , 'Aucun élève n’est associé à cette évaluation !' );
  }
  Form::save_choix('evaluation_cartouche');
  $cart_cases_nb = ($cart_cases_nb==1) ? $cart_cases_nb : $_SESSION['NOMBRE_CODES_NOTATION']+1 ; // 1 ou 5 dans le formulaire initial, mais à adapter en fonction du nombre de codes utilisés
  $tab_result  = array(); // tableau bi-dimensionnel [n°colonne=id_user][n°ligne=id_item]
  $tab_user_id = array(); // pas indispensable, mais plus lisible
  $tab_comp_id = array(); // pas indispensable, mais plus lisible
  $tab_user_nb_items      = array(); // pour retenir le nb d’items par utilisateur : utile si cartouche avec les seuls résultats ou demandes d’évaluations
  $tab_user_nb_notes      = array(); // pour retenir le nb de codes couleurs saisis par utilisateur : utile si cartouche avec les seuls élèves ayant un code couleur saisi
  $tab_user_nb_ligne_comm = array(); // pour retenir le nb de lignes de commentaires par utilisateur
  $tab_user_commentaire   = array(); // pour retenir les commentaires écrits pour par élève
  // enregistrer infos élèves
  foreach($DB_TAB_USER as $DB_ROW)
  {
    $tab_user_id[$DB_ROW['user_id']] = ($with_nom) ? html( To::texte_eleve_identite($DB_ROW['user_nom'],$DB_ROW['user_prenom'],$eleves_ordre).' ('.$groupe_nom.')' ) : '' ;
    $tab_user_nb_items[$DB_ROW['user_id']] = 0 ;
    $tab_user_nb_notes[$DB_ROW['user_id']] = 0 ;
    $tab_user_nb_ligne_comm[$DB_ROW['user_id']] = 0 ;
    $tab_user_commentaire[$DB_ROW['user_id']] = NULL;
  }
  // enregistrer infos items
  $longueur_ref_max = 0;
  $texte_ref     = '';
  $texte_s2016   = '';
  $texte_coef    = '';
  $texte_domaine = '';
  $texte_theme   = '';
  foreach($DB_TAB_COMP as $DB_ROW)
  {
    if($with_ref)
    {
      $item_ref = ($DB_ROW['ref_perso']) ? $DB_ROW['ref_perso'] : $DB_ROW['ref_auto'] ;
      $longueur_ref_max = max( $longueur_ref_max , strlen($item_ref) );
      $texte_ref = $DB_ROW['matiere_ref'].'.'.$item_ref;
    }
    if($with_socle)
    {
      $texte_s2016 = ($DB_ROW['s2016_nb'])  ? '[S] ' : '[–] ' ;
    }
    if($with_coef)
    {
      $texte_coef = '[×'.$DB_ROW['item_coef'].'] ';
    }
    if($with_domaine)
    {
      $texte_domaine = $DB_ROW['domaine_nom'].' | ';
    }
    if($with_theme)
    {
      $texte_theme = $DB_ROW['theme_nom'].' | ';
    }
    $tab_comp_id[$DB_ROW['item_id']] = array($texte_ref,$texte_s2016.$texte_coef.$texte_domaine.$texte_theme.$DB_ROW['item_nom']);
  }
  // résultats vierges
  foreach($tab_user_id as $user_id=>$val_user)
  {
    foreach($tab_comp_id as $comp_id=>$val_comp)
    {
      $tab_result[$user_id][$comp_id] = '';
    }
  }
  // compléter si demandé avec les résultats et/ou les demandes d’évaluations
  if( $with_result || ($cart_restriction_item!='non') || ($cart_restriction_eleve!='non') )
  {
    $with_marqueurs = ( ($cart_restriction_item!='only_valeur') && ($cart_restriction_eleve!='only_valeur') ) ? TRUE : FALSE ;
    $DB_TAB = DB_STRUCTURE_PROFESSEUR::DB_lister_devoir_saisies( $devoir_id , $with_marqueurs );
    foreach($DB_TAB as $DB_ROW)
    {
      // Test pour éviter les pbs des élèves changés de groupes ou des items modifiés en cours de route
      if(isset($tab_result[$DB_ROW['eleve_id']][$DB_ROW['item_id']]))
      {
        $valeur = ($with_result) ? $DB_ROW['saisie_note'] : ( ($DB_ROW['saisie_note']) ? 'PA' : '' ) ;
        $is_vraie_note = isset($_SESSION['NOTE'][$DB_ROW['saisie_note']]) ? TRUE : FALSE ; // Note {1;...;6}
        $is_item_valable = ( $is_vraie_note || ($cart_restriction_item !='only_valeur') ) ? TRUE : FALSE ;
        $is_note_valable = ( $is_vraie_note || ($cart_restriction_eleve!='only_valeur') ) ? TRUE : FALSE ;
        if( $is_item_valable )
        {
          $tab_result[$DB_ROW['eleve_id']][$DB_ROW['item_id']] = $valeur ;
        }
        if( $is_item_valable )
        {
          $tab_user_nb_items[$DB_ROW['eleve_id']]++;
        }
        if( $is_note_valable )
        {
          $tab_user_nb_notes[$DB_ROW['eleve_id']]++;
        }
      }
    }
  }
  // liste des infos autoévaluation / audio / texte / copie, si demandé avec les résultats
  if($with_result)
  {
    $DB_TAB_JOIN = DB_STRUCTURE_DEVOIR_JOINTURE::DB_lister_elements($devoir_id);
    if(!empty($DB_TAB_JOIN))
    {
      foreach($DB_TAB_JOIN as $DB_ROW)
      {
        $memo_autoeval = '';
        if($DB_ROW['jointure_memo_autoeval'])
        {
          $memo_autoeval = 'Auto-évaluation :';
          $tab_autoeval = json_decode( $DB_ROW['jointure_memo_autoeval'] , TRUE );
          foreach($tab_comp_id as $item_id=>$val_comp)
          {
            $note = isset($tab_autoeval[$item_id]) ? ( is_numeric($tab_autoeval[$item_id]) ? $_SESSION['NOTE'][$tab_autoeval[$item_id]]['SIGLE'] : $tab_autoeval[$item_id] ) : 'X' ;
            $memo_autoeval .= ' '.$note;
          }
        }
        $msg_data = '';
        if($DB_ROW['jointure_texte'])
        {
          // On récupère le contenu du fichier
          $msg_url = $DB_ROW['jointure_texte'];
          if(strpos($msg_url,URL_DIR_SACOCHE)===0)
          {
            $fichier_chemin = url_to_chemin($msg_url);
            $msg_data = is_file($fichier_chemin) ? file_get_contents($fichier_chemin) : 'Erreur : fichier avec le contenu du commentaire non trouvé.' ;
          }
          else
          {
            $msg_data = cURL::get_contents($msg_url);
          }
        }
        if( $memo_autoeval || $msg_data )
        {
          $user_comm = trim($memo_autoeval."\r\n".$msg_data) ;
          $tab_user_commentaire[$DB_ROW['eleve_id']] = $user_comm;
          $tab_user_nb_ligne_comm[$DB_ROW['eleve_id']] = max( 2 , ceil(mb_strlen($user_comm)/125) , substr_count($user_comm,"\n") + 1 );
        }
      }
    }
  }
  // Tri des items, si demandé (et si résultats)
  if($with_result && $cart_ordre)
  {
    foreach($tab_result as $user_id=>$tab_user_result)
    {
      if($cart_ordre<0)
      {
        arsort($tab_user_result,SORT_NUMERIC);
      }
      else
      {
        asort($tab_user_result,SORT_NUMERIC);
      }
      $tab_result[$user_id] = $tab_user_result;
    }
  }
  // On attaque l’élaboration des sorties HTML, CSV, TEX et PDF
  $htm = '<hr>';
  $htm.= '<a target="_blank" rel="noopener noreferrer" href="'.URL_DIR_EXPORT.'cartouche_'.$fnom_export.'.pdf"><span class="file file_pdf">Cartouches &rarr; Archiver / Imprimer (format <em>pdf</em>).</span></a><br>';
  $htm.= '<a target="_blank" rel="noopener noreferrer" href="./force_download.php?fichier=cartouche_'.$fnom_export.'.csv"><span class="file file_txt">Cartouches &rarr; Récupérer / Manipuler (fichier <em>csv</em> pour tableur).</span></a><br>';
  $htm.= '<a target="_blank" rel="noopener noreferrer" href="'.URL_DIR_EXPORT.'cartouche_'.$fnom_export.'.tex"><span class="file file_tex">Cartouches &rarr; Récupérer / Manipuler (fichier <em>LaTeX</em> pour connaisseurs).</span></a>';
  $csv = new CSV();
  $tex = new LaTeX();
  $tab_codes = array_fill_keys($_SESSION['NOTE_ACTIF'],TRUE) + array('X'=>FALSE);
  // Appel de la classe et définition de qqs variables supplémentaires pour la mise en page PDF
  $lignes_comm_max = max($tab_user_nb_ligne_comm);
  $item_nb = count($tab_comp_id);
  if($cart_restriction_item=='non')
  {
    $tab_user_nb_items = array_fill_keys( array_keys($tab_user_nb_items) , $item_nb );
  }
  if($cart_restriction_eleve=='non')
  {
    $tab_user_nb_notes = array_fill_keys( array_keys($tab_user_nb_notes) , $item_nb );
  }
  if( !array_sum($tab_user_nb_items) || !array_sum($tab_user_nb_notes) )
  {
    Json::end( FALSE , 'Rien à imprimer ! Vérifier vos restrictions dans les options.' );
  }
  $pdf = new PDF_evaluation_cartouche( FALSE /*officiel*/ , 'A4' /*page_size*/ , $orientation , $marge_min /*marge_gauche*/ , $marge_min /*marge_droite*/ , $marge_min /*marge_haut*/ , $marge_min /*marge_bas*/ , $couleur , $fond , $legende /*legende*/ );
  $pdf->initialiser( $cart_detail , $longueur_ref_max , $item_nb , $cart_cases_nb , $cart_autoeval );
  if($cart_detail=='minimal')
  {
    // dans le cas d’un cartouche minimal...
    foreach($tab_user_id as $user_id=>$val_user)
    {
      if( $tab_user_nb_items[$user_id] && $tab_user_nb_notes[$user_id] )
      {
        $colonnes_nb    = ( ($cart_autoeval==1) && ($cart_cases_nb!=1) ) ? 2*$tab_user_nb_items[$user_id] : $tab_user_nb_items[$user_id] ;
        $lignes_comm_nb = ($cart_hauteur=='variable') ? $tab_user_nb_ligne_comm[$user_id] : $lignes_comm_max ;
        $lignes_nb      = 1 + 2 + $cart_autoeval + $cart_cases_nb + $lignes_comm_nb; // marge + titre + contenu
        $texte_entete   = ($val_user) ? $date_devoir.' - '.$description.' - '.$val_user : $date_devoir.' - '.$description ;
        $htm_th_vide    = ( ($cart_cases_nb==1) && ($cart_autoeval==0) ) ? '' : '<th class="nu"></th>' ;
        $tex_cell_vide  = ( ($cart_cases_nb==1) && ($cart_autoeval==0) ) ? NULL : '\multicolumn{1}{c|}{}'.' & ' ;
        $tex_br_vide    = ( ($cart_cases_nb==1) && ($cart_autoeval==0) ) ? 0 : 1 ;
        $htm_ligne_auto = ( ($cart_cases_nb==1) || ($cart_autoeval==0) ) ? '' : '<tr><th class="nu"></th><th colspan="'.$tab_user_nb_items[$user_id].'" class="hc">auto-évaluation</th><th colspan="'.$tab_user_nb_items[$user_id].'" class="hc">évaluation prof</th></tr>' ;
        $htm .= '<table class="bilan"><thead><tr>'.$htm_th_vide.'<th colspan="'.$colonnes_nb.'">'.html($texte_entete).'</th></tr>'.$htm_ligne_auto.'</thead><tbody>';
        $colonnes_nb_tex = ($htm_th_vide) ? $colonnes_nb+1 : $colonnes_nb ;
        $tex_line = ($tex_cell_vide) ? '\cline{2-'.$colonnes_nb_tex.'}' : '\hline' ;
        $csv->add( $texte_entete , 1 );
        $tex->add( '\begin{center}' , 1 )
            ->add( '\begin{tabular}{|'.str_repeat('c|',$colonnes_nb_tex).'}' , 1 )
            ->add( $tex_line , 1 )
            ->add( $tex_cell_vide , $tex_br_vide )
            ->add( '\multicolumn{'.$colonnes_nb.'}{|l|}{'.LaTeX::escape($texte_entete).'}'.'\\\\' , 1 )
            ->add( $tex_line , 1 );
            ;
        if( ($cart_autoeval==1) && ($cart_cases_nb!=1) )
        {
          $tex->add( $tex_cell_vide , $tex_br_vide )
              ->add( '\multicolumn{'.$tab_user_nb_items[$user_id].'}{|c|}{'.LaTeX::escape('auto-évaluation').'}'.' & ' , 1 )
              ->add( '\multicolumn{'.$tab_user_nb_items[$user_id].'}{|c|}{'.LaTeX::escape('évaluation prof').'}'.'\\\\' , 1 )
              ->add( $tex_line , 1 );
          $csv->add( '' )
              ->add( 'auto-évaluation' )->add( array_fill( 0, $tab_user_nb_items[$user_id]-1 , '' ) )
              ->add( 'évaluation prof' )->add( array_fill( 0, $tab_user_nb_items[$user_id]-1 , '' ) , 1 );
        }
        $pdf->entete( $texte_entete , $lignes_nb , $cart_detail , $cart_autoeval );
        if($cart_cases_nb==1)
        {
          // ... avec une case à remplir
          if($cart_autoeval==0)
          {
            $rows_htm = array( 'item' => '' , 'note'=> '' );
            $rows_csv = array( 'item' => array() , 'note'=> array() );
            $rows_tex = array( 'item' => array() , 'note'=> array() );
          }
          else
          {
            $rows_htm = array( 'item' => '<td class="nu"></td>' , 'auto'=> '<th class="hc">auto-éval.</th>' , 'note'=> '<th class="hc">éval. prof</th>' );
            $rows_csv = array( 'item' => array('') , 'auto'=> array('auto-éval.') , 'note'=> array('éval. prof') );
            $rows_tex = array( 'item' => array('\multicolumn{1}{c|}{}') , 'auto'=> array('\begin{tabular}{c}'.LaTeX::escape('auto-éval.').'\end{tabular}') , 'note'=> array('\begin{tabular}{c}'.LaTeX::escape('éval. prof').'\end{tabular}') );
          }
        }
        else
        {
          // ... avec $cart_cases_nb dont une à cocher
          $rows_htm = array( 'item' => '<td class="nu"></td>' );
          $rows_csv = array( 'item' => array('') );
          $rows_tex = array( 'item' => array('\multicolumn{1}{c|}{}') );
          krsort($tab_codes,SORT_NUMERIC); // Pour avoir les notes de valeurs croissantes de bas en haut
          foreach($tab_codes as $note_code => $is_note )
          {
            $rows_htm[$note_code] = ($is_note) ? '<td class="hc">'.Html::note_image($note_code,'','',FALSE).'</td>'     : '<td class="hc">autre</td>';
            $rows_csv[$note_code] = ($is_note) ? array( To::note_sigle($note_code) )                                    : array('autre');
            $rows_tex[$note_code] = ($is_note) ? array('\begin{tabular}{c}'.To::note_sigle($note_code).'\end{tabular}') : array('\begin{tabular}{c}autre\end{tabular}');
          }
          if($cart_autoeval==1)
          {
            foreach($tab_result[$user_id] as $comp_id => $val)
            {
              if( ($cart_restriction_item=='non') || ($tab_result[$user_id][$comp_id]) )
              {
                $tab_val_comp = $tab_comp_id[$comp_id];
                list($ref_matiere,$ref_suite) = explode('.',$tab_val_comp[0],2);
                $rows_htm['item'] .= '<td class="hc">'.html($tab_val_comp[0]).'</td>';
                $rows_csv['item'][] = $tab_val_comp[0];
                $rows_tex['item'][] = '\begin{tabular}{c}'.LaTeX::escape($ref_matiere).'\\\\'.LaTeX::escape($ref_suite).'\end{tabular}';
                foreach($tab_codes as $note_code => $is_note )
                {
                  $rows_htm[$note_code] .= '<td></td>';
                  $rows_csv['note'][] = '';
                  $rows_tex[$note_code][] = '\begin{tabular}{c}\end{tabular}';
                }
              }
            }
          }
        }
        // On change le tableau de la boucle depuis qu’il est possible de trier selon les résultats de l’utilisateur
        // foreach($tab_comp_id as $comp_id => $tab_val_comp)
        foreach($tab_result[$user_id] as $comp_id => $val)
        {
          $tab_val_comp = $tab_comp_id[$comp_id];
          if( ($cart_restriction_item=='non') || ($tab_result[$user_id][$comp_id]) )
          {
            $note = ($tab_result[$user_id][$comp_id]!='PA') ? $tab_result[$user_id][$comp_id] : '' ; // Si on voulait récupérer les items ayant fait l’objet d’une demande d’évaluation, il n’y a pour autant pas lieu d’afficher les paniers sur les cartouches.
            list($ref_matiere,$ref_suite) = explode('.',$tab_val_comp[0],2);
            $rows_htm['item'] .= '<td class="hc">'.html($tab_val_comp[0]).'</td>';
            $rows_csv['item'][] = $tab_val_comp[0];
            $rows_tex['item'][] = '\begin{tabular}{c}'.LaTeX::escape($ref_matiere).'\\\\'.LaTeX::escape($ref_suite).'\end{tabular}';
            if($cart_cases_nb==1)
            {
              // ... avec une case à remplir
              if($cart_autoeval==1)
              {
                $rows_htm['auto'] .= '<td></td>';
                $rows_tex['auto'][] = '\begin{tabular}{c}\end{tabular}';
              }
              $rows_htm['note'] .= '<td class="hc">'.Html::note_image($note,$date_devoir,$description,FALSE).'</td>';
              $rows_csv['note'][] = To::note_sigle($note);
              $rows_tex['note'][] = '\begin{tabular}{c}'.To::note_sigle($note).'\end{tabular}';
            }
            else
            {
              // ... avec $cart_cases_nb dont une à cocher
              foreach($tab_codes as $note_code => $is_note )
              {
                if($is_note)
                {
                  $coche = ($note_code==$note) ? 'X' : NULL ;
                }
                else
                {
                  $coche = ( $note && !isset($tab_codes[$note]) ) ? $note : NULL ;
                }
                $rows_htm[$note_code]  .= ($coche) ? '<td class="hc">'.$coche.'</td>'            : '<td class="hc">&nbsp;</td>';
                $rows_csv[$note_code][] = ($coche) ? $coche                                      : '';
                $rows_tex[$note_code][] = ($coche) ? '\begin{tabular}{c}'.$coche.'\end{tabular}' : '';
              }
            }
            $pdf->competence_detail_minimal_colonne( $tab_val_comp[0] , $note , $cart_autoeval );
          }
        }
        $htm .= '<tr>'.implode('</tr><tr>',$rows_htm).'</tr>';
        foreach($rows_csv as $row_csv)
        {
          $csv->add( $row_csv , 1 );
        }
        foreach($rows_tex as $row_tex)
        {
          $tex->add( implode( ' & ' , $row_tex ).' \\\\' , 1 )->add( '\hline' , 1 );
        }
        // Commentaire écrit
        $colonnes_nb += ($cart_cases_nb!=1) ? 1 : 0 ;
        if($tab_user_nb_ligne_comm[$user_id])
        {
          $htm .= '<tr><td colspan="'.$colonnes_nb.'"><div class="appreciation">'.html($tab_user_commentaire[$user_id]).'</div></td></tr>';
          $csv->add( $tab_user_commentaire[$user_id] , 1 );
          $tex->add( '\multicolumn{'.$colonnes_nb.'}{|l|}{'.LaTeX::escape($tab_user_commentaire[$user_id]).'} \\\\' , 1 )->add( '\hline' , 1 );
        }
        $htm .= '</tbody></table>';
        $csv->add( NULL , 1 );
        $tex->add( '\end{tabular}' , 1 )->add( '\end{center}' , 2 );
        $lignes_vide_nb = ($cart_hauteur=='variable') ? 0 : $lignes_comm_max - $tab_user_nb_ligne_comm[$user_id] ;
        $pdf->commentaire_interligne( $cart_cases_nb+$cart_autoeval*max(0,2-$cart_cases_nb)+1 /*decalage_nb_lignes*/ , $tab_user_commentaire[$user_id] /*commentaire éventuel*/ , $tab_user_nb_ligne_comm[$user_id] , $lignes_vide_nb , $cart_hauteur );
        if($legende=='oui')
        {
          $htm .= Html::legende( array( 'codes_notation' => TRUE ) );
          // Pour le PDF c’est géré par commentaire_interligne(), inséré entre le commentaire et le trait de séparation
        }
      }
    }
  }
  elseif($cart_detail=='complet')
  {
    // dans le cas d’un cartouche complet...
    foreach($tab_user_id as $user_id=>$val_user)
    {
      if( $tab_user_nb_items[$user_id] && $tab_user_nb_notes[$user_id] )
      {
        $colonnes_nb    = ( ($cart_cases_nb==1) && ($cart_autoeval==0) ) ? 2+$aff_reference : 1+$aff_reference ;
        $lignes_comm_nb = ($cart_hauteur=='variable') ? $tab_user_nb_ligne_comm[$user_id] : $lignes_comm_max ;
        $lignes_item_nb = ($cart_hauteur=='variable') ? $tab_user_nb_items[$user_id]      : $item_nb ;
        $lignes_nb      = 1 + 1 + min(1,$cart_autoeval*($cart_cases_nb-1)) + $lignes_item_nb + $lignes_comm_nb ; // marge + titre + contenu
        $texte_entete   = ($val_user) ? $date_devoir.' - '.$description.' - '.$val_user : $date_devoir.' - '.$description ;
        $csv_col_ref    = ($aff_reference) ? '' : NULL ;
        $tex_col_ref    = ($aff_reference) ? '|c' : '' ;
        if($cart_cases_nb==1)
        {
          // ... avec une case à remplir
          if($cart_autoeval==0)
          {
            $htm .= '<table class="bilan"><thead><tr><th colspan="'.$colonnes_nb.'">'.html($texte_entete).'</th></tr></thead><tbody>';
            $csv->add( $texte_entete , 1 );
            $tex->add( '\begin{center}' , 1 )
                ->add( '\begin{tabular}{'.$tex_col_ref.'|l|p{2em}|}' , 1 )
                ->add( '\hline' , 1 )
                ->add( '\multicolumn{'.$colonnes_nb.'}{|l|}{'.LaTeX::escape($texte_entete).'}'.'\\\\' , 1 )
                ->add( '\hline' , 1 );
          }
          else
          {
            $htm .= '<table class="bilan"><thead><tr><th colspan="'.$colonnes_nb.'">'.html($texte_entete).'</th><th class="hc">auto-éval.</th><th class="hc">éval. prof</th></tr></thead><tbody>';
            $csv->add( $texte_entete )->add( $csv_col_ref )->add( 'auto-éval.' )->add( 'éval. prof' , 1 );
            $tex->add( '\begin{center}' , 1 )
                ->add( '\begin{tabular}{'.$tex_col_ref.'|l|c|c|}' , 1 )
                ->add( '\hline' , 1 )
                ->add( '\multicolumn{'.$colonnes_nb.'}{|l|}{'.LaTeX::escape($texte_entete).'}'.' & ' , 1 )
                ->add( LaTeX::escape('auto-éval.').' & ' , 1 )
                ->add( LaTeX::escape('éval. prof').'\\\\' , 1 )
                ->add( '\hline' , 1 );
          }
        }
        else
        {
          // ... avec $cart_cases_nb dont une à cocher
          $cols_htm = '';
          $cols_csv = array() ;
          $cols_tex = array() ;
          $tex->add( '\begin{center}' , 1 )
              ->add( '\begin{tabular}{'.$tex_col_ref.'|l'.str_repeat('|p{3em}',$cart_cases_nb*($cart_autoeval+1)).'|}' , 1 );
          $htm .= '<table class="bilan"><thead>';
          if($cart_autoeval==1)
          {
            $colonnes_nb_tex = $colonnes_nb + $cart_cases_nb + $cart_cases_nb;
            $htm .= '<tr><th colspan="'.$colonnes_nb.'" class="nu"></th><th class="hc" colspan="'.$cart_cases_nb.'">auto-évaluation</th><th class="hc" colspan="'.$cart_cases_nb.'">évaluation prof</th></tr>';
            $csv->add( $csv_col_ref )
                ->add( '' )
                ->add( $cols_csv )
                ->add( 'auto-évaluation' )
                ->add( array_fill( 0, $cart_cases_nb-1 , '' ) )
                ->add( 'évaluation prof' )
                ->add( array_fill( 0, $cart_cases_nb-1 , '' ) , 1 );
            $tex->add( '\cline{'.($colonnes_nb+1).'-'.$colonnes_nb_tex.'}' , 1 )
                ->add( '\multicolumn{'.($aff_reference+1).'}{c|}{}'.' & ' , 1 )
                ->add( '\multicolumn{'.$cart_cases_nb.'}{|c|}{'.LaTeX::escape('auto-évaluation').'}'.' & ' , 1 )
                ->add( '\multicolumn{'.$cart_cases_nb.'}{|c|}{'.LaTeX::escape('évaluation prof').'}'.'\\\\' , 1 );
          }
          // pour faire 2 passages en cas d'autoévaluation
          for( $i = 1-$cart_autoeval ; $i <= 1 ; $i++ )
          {
            foreach($tab_codes as $note_code => $is_note )
            {
              $cols_htm  .= ($is_note) ? '<td class="hc">'.Html::note_image($note_code,'','',FALSE).'</td>' : '<td class="hc">autre</td>';
              $cols_csv[] = ($is_note) ? To::note_sigle($note_code)                                         : 'autre';
              $cols_tex[] = ($is_note) ? '\begin{tabular}{c}'.To::note_sigle($note_code).'\end{tabular}'    : '\begin{tabular}{c}autre\end{tabular}';
            }
          }
          $htm .= '<tr><th colspan="'.$colonnes_nb.'">'.html($texte_entete).'</th>'.$cols_htm.'</tr></thead><tbody>';
          $csv->add( $texte_entete )
              ->add( $csv_col_ref )
              ->add( $cols_csv , 1 );
          $tex->add( '\hline' , 1 )
              ->add( '\multicolumn{'.$colonnes_nb.'}{|l|}{'.LaTeX::escape($texte_entete).'}'.' & ' , 1 )
              ->add( implode( ' & ' , $cols_tex ).' \\\\' , 1 )
              ->add( '\hline' , 1 );
        }
        $pdf->entete( $texte_entete , $lignes_nb , $cart_detail , $cart_autoeval );
        // On change le tableau de la boucle depuis qu’il est possible de trier selon les résultats de l’utilisateur
        // foreach($tab_comp_id as $comp_id=>$tab_val_comp)
        foreach($tab_result[$user_id] as $comp_id => $val)
        {
          $tab_val_comp = $tab_comp_id[$comp_id];
          if( ($cart_restriction_item=='non') || ($tab_result[$user_id][$comp_id]) )
          {
            $ref_html  = ($aff_reference) ? '<td>'.html($tab_val_comp[0]).'</td>' : '' ;
            $ref_csv   = ($aff_reference) ? $tab_val_comp[0]                      : NULL ;
            $ref_latex = ($aff_reference) ? LaTeX::escape($tab_val_comp[0]).' & '     : '' ;
            $note = ($tab_result[$user_id][$comp_id]!='PA') ? $tab_result[$user_id][$comp_id] : '' ; // Si on voulait récupérer les items ayant fait l’objet d’une demande d’évaluation, il n’y a pour autant pas lieu d’afficher les paniers sur les cartouches.
            if($cart_cases_nb==1)
            {
              // ... avec une case à remplir
              if($cart_autoeval==0)
              {
                $htm .= '<tr>'.$ref_html.'<td>'.html($tab_val_comp[1]).'</td><td>'.Html::note_image($note,$date_devoir,$description,FALSE).'</td></tr>';
                $csv->add( $ref_csv )->add( $tab_val_comp[1] )->add( To::note_sigle($note) , 1 );
                $tex->add( $ref_latex.LaTeX::escape($tab_val_comp[1]).' & '.To::note_sigle($note).' \\\\' , 1 )->add( '\hline' , 1 );
              }
              else
              {
                $htm .= '<tr>'.$ref_html.'<td>'.html($tab_val_comp[1]).'</td><td></td><td>'.Html::note_image($note,$date_devoir,$description,FALSE).'</td></tr>';
                $csv->add( $ref_csv )->add( $tab_val_comp[1] )->add( '' )->add( To::note_sigle($note) , 1 );
                $tex->add( $ref_latex.LaTeX::escape($tab_val_comp[1]).' & & '.To::note_sigle($note).' \\\\' , 1 )->add( '\hline' , 1 );
              }
            }
            else
            {
              // ... avec $cart_cases_nb dont une à cocher
              $htm .= '<tr>'.$ref_html.'<td>'.html($tab_val_comp[1]).'</td>';
              $csv->add( $ref_csv )->add( $tab_val_comp[1] );
              $tex->add( $ref_latex.LaTeX::escape($tab_val_comp[1]) );
              if($cart_autoeval==1)
              {
                $htm .= str_repeat('<td></td>',$cart_cases_nb);
                $csv->add( array_fill( 0, $cart_cases_nb , '' ) );
                $tex->add( str_repeat(' & ',$cart_cases_nb) );
              }
              foreach($tab_codes as $note_code => $is_note )
              {
                $colonnes_nb++;
                if($is_note)
                {
                  $coche = ($note_code==$note) ? 'X' : NULL ;
                }
                else
                {
                  $coche = ( $note && !isset($tab_codes[$note]) ) ? $note : NULL ;
                }
                $htm .= ($coche) ? '<td class="hc">'.$coche.'</td>' : '<td class="hc">&nbsp;</td>';
                $csv->add( ($coche) ? $coche : '' );
                $tex->add( ($coche) ? ' & \begin{tabular}{c}'.$coche.'\end{tabular}' : ' & ' );
              }
              $htm .= '</tr>';
              $csv->add( NULL , 1 );
              $tex->add(' \\\\' , 1 )->add( '\hline' , 1 );
            }
            $pdf->competence_detail_complet_ligne( $tab_val_comp[0] , $tab_val_comp[1] , $note , $cart_autoeval );
          }
        }
        // Commentaire écrit
        if($tab_user_nb_ligne_comm[$user_id])
        {
          $htm .= '<tr><td colspan="'.$colonnes_nb.'"><div class="appreciation">'.html($tab_user_commentaire[$user_id]).'</div></td></tr>';
          $csv->add( $tab_user_commentaire[$user_id] , 1 );
          $tex->add( '\multicolumn{'.$colonnes_nb.'}{|l|}{'.LaTeX::escape($tab_user_commentaire[$user_id]).'} \\\\' , 1 )->add( '\hline' , 1 );
        }
        $htm .= '</tbody></table>';
        $csv->add( NULL , 1 );
        $tex->add( '\end{tabular}' , 1 )->add( '\end{center}' , 2 );
        $lignes_vide_nb = ($cart_hauteur=='variable') ? 0 : ( $lignes_comm_max - $tab_user_nb_ligne_comm[$user_id]) + ( $item_nb - $tab_user_nb_items[$user_id]) ;
        $pdf->commentaire_interligne( 0 /*decalage_nb_lignes*/ , $tab_user_commentaire[$user_id] /*commentaire éventuel*/ , $tab_user_nb_ligne_comm[$user_id] , $lignes_vide_nb , $cart_hauteur );
        if($legende=='oui')
        {
          $htm .= Html::legende( array( 'codes_notation' => TRUE ) );
          // Pour le PDF c’est géré par commentaire_interligne(), inséré entre le commentaire et le trait de séparation
        }
      }
    }
  }
  // On archive le cartouche
  FileSystem::ecrire_objet_csv( CHEMIN_DOSSIER_EXPORT.'cartouche_'.$fnom_export.'.csv' , $csv );
  FileSystem::ecrire_objet_tex( CHEMIN_DOSSIER_EXPORT.'cartouche_'.$fnom_export.'.tex' , $tex );
  FileSystem::ecrire_objet_pdf( CHEMIN_DOSSIER_EXPORT.'cartouche_'.$fnom_export.'.pdf' , $pdf );
  // Affichage
  Json::end( TRUE , $htm );
}

// ////////////////////////////////////////////////////////////////////////////////////////////////////
// Traiter une demande d’importation d’une saisie déportée ; on n’enregistre rien, on ne fait que décrypter le contenu du fichier et renvoyer une chaine résultante au javascript
// ////////////////////////////////////////////////////////////////////////////////////////////////////

if($action=='importer_saisie_csv')
{
  // Récupération du fichier
  $fichier_nom = 'saisie_deportee_'.$_SESSION['BASE'].'_'.$_SESSION['USER_ID'].'_'.FileSystem::generer_fin_nom_fichier__date_et_alea().'.<EXT>';
  $result = FileSystem::recuperer_upload( CHEMIN_DOSSIER_IMPORT /*fichier_chemin*/ , $fichier_nom /*fichier_nom*/ , array('txt','csv') /*tab_extensions_autorisees*/ , NULL /*tab_extensions_interdites*/ , NULL /*taille_maxi*/ , NULL /*filename_in_zip*/ );
  if($result!==TRUE)
  {
    Json::end( FALSE , $result );
  }
  // On passe à son contenu
  // Extraire les lignes du fichier
  $tab_lignes = FileSystem::extraire_lignes_csv(CHEMIN_DOSSIER_IMPORT.FileSystem::$file_saved_name);
  // Pas de ligne d’en-tête à supprimer
  // Mémoriser les eleve_id de la 1ère ligne
  $tab_eleve = array();
  $tab_elements = $tab_lignes[0];
  unset($tab_elements[0]);
  foreach ($tab_elements as $num_colonne => $element_contenu)
  {
    $eleve_id = Clean::entier($element_contenu);
    if($eleve_id)
    {
      $tab_eleve[$num_colonne] = $eleve_id ;
    }
  }
  // Supprimer la 1ère ligne
  unset($tab_lignes[0]);

  $tab_codes = array();
  foreach( $_SESSION['NOTE_ACTIF'] as $note_id )
  {
    $tab_codes[$_SESSION['NOTE'][$note_id]['CLAVIER']] = $note_id;
  }
  $tab_codes += array(
    'A' => 'A' , 'a' => 'A' ,
    'D' => 'D' , 'd' => 'D' ,
    'E' => 'E' , 'e' => 'E' ,
    'F' => 'F' , 'f' => 'F' ,
    'N' => 'N' , 'n' => 'N' ,
    'R' => 'R' , 'r' => 'R' ,
    'P' => 'P' , 'p' => 'P' ,
  );

  $scores_autorises = '0123456789AaDdNnEeFfRrPp';
  // Parcourir les lignes suivantes et mémoriser les scores
  foreach ($tab_lignes as $tab_elements)
  {
    $item_id = Clean::entier($tab_elements[0]);
    if($item_id)
    {
      foreach ($tab_eleve as $num_colonne => $eleve_id)
      {
        if( (isset($tab_elements[$num_colonne])) && ($tab_elements[$num_colonne]!='') )
        {
          $score = $tab_elements[$num_colonne];
          if(isset($tab_codes[$score]))
          {
            Json::add_row( NULL , $eleve_id.'.'.$item_id.'.'.$tab_codes[$score] );
          }
        }
      }
    }
  }
  Json::end( TRUE );
}

// ////////////////////////////////////////////////////////////////////////////////////////////////////
// Référencer un sujet ou un corrigé d’évaluation
// ////////////////////////////////////////////////////////////////////////////////////////////////////

if( ($action=='referencer_document') && $devoir_id && in_array($doc_objet,array('sujet','corrige')) && $doc_url )
{
  // Vérification des droits
  $proprio_id = DB_STRUCTURE_PROFESSEUR::DB_recuperer_devoir_proprietaire_id( $devoir_id );
  if($proprio_id!=$_SESSION['USER_ID'])
  {
    Json::end( FALSE , 'Vous n’êtes pas propriétaire du devoir n°'.$devoir_id.' !' );
  }
  // Mise à jour dans la base
  DB_STRUCTURE_PROFESSEUR::DB_modifier_devoir_document( $devoir_id , $doc_objet , $doc_url );
  // Retour
  Json::end( TRUE );
}

// ////////////////////////////////////////////////////////////////////////////////////////////////////
// Uploader un sujet ou un corrigé d’évaluation pour le regroupement
// ////////////////////////////////////////////////////////////////////////////////////////////////////

if( ($action=='uploader_document_collectif') && $devoir_id && in_array($doc_objet,array('sujet','corrige')) )
{
  // Vérification des droits
  $proprio_id = DB_STRUCTURE_PROFESSEUR::DB_recuperer_devoir_proprietaire_id( $devoir_id );
  if($proprio_id!=$_SESSION['USER_ID'])
  {
    Json::end( FALSE , 'Vous n’êtes pas propriétaire du devoir n°'.$devoir_id.' !' );
  }
  // Récupération du fichier
  FileSystem::creer_sous_dossier_etabl_si_besoin( $chemin_devoir );
  $fichier_nom = 'devoir_'.$devoir_id.'_'.$doc_objet.'_'.$_SERVER['REQUEST_TIME'].'.<EXT>'; // pas besoin de le rendre inaccessible -> FileSystem::generer_fin_nom_fichier__date_et_alea() inutilement lourd
  $result = FileSystem::recuperer_upload( $chemin_devoir /*fichier_chemin*/ , $fichier_nom /*fichier_nom*/ , NULL /*tab_extensions_autorisees*/ , FileSystem::$tab_extensions_interdites , FICHIER_TAILLE_MAX /*taille_maxi*/ , NULL /*filename_in_zip*/ );
  if($result!==TRUE)
  {
    Json::end( FALSE , $result );
  }
  // Mise à jour dans la base
  DB_STRUCTURE_PROFESSEUR::DB_modifier_devoir_document( $devoir_id , $doc_objet , $url_dossier_devoir.FileSystem::$file_saved_name );
  // Retour
  Json::add_tab( array(
    'ref'   => $ref ,
    'objet' => $doc_objet ,
    'url'   => FileSystem::verif_lien_safe($url_dossier_devoir.FileSystem::$file_saved_name) ,
  ) );
  Json::end( TRUE );
}

// ////////////////////////////////////////////////////////////////////////////////////////////////////
// Uploader un sujet ou un corrigé individuel pour un élève donné
// ////////////////////////////////////////////////////////////////////////////////////////////////////

if( ($action=='uploader_document_individuel') && $eleve_id && $devoir_id && $groupe_type && $groupe_id && in_array($doc_objet,array('sujet','corrige')) )
{
  // Vérification des droits
  $proprio_id = DB_STRUCTURE_PROFESSEUR::DB_recuperer_devoir_proprietaire_id( $devoir_id );
  if($proprio_id!=$_SESSION['USER_ID'])
  {
    Json::end( FALSE , 'Vous n’êtes pas propriétaire du devoir n°'.$devoir_id.' !' );
  }
  // Récupération du fichier
  FileSystem::creer_sous_dossier_etabl_si_besoin( $chemin_devoir );
  $fichier_nom = 'devoir_'.$devoir_id.'_eleve_'.$eleve_id.'_'.$doc_objet.'_'.$_SERVER['REQUEST_TIME'].'.<EXT>'; // pas besoin de le rendre inaccessible -> FileSystem::generer_fin_nom_fichier__date_et_alea() inutilement lourd
  $result = FileSystem::recuperer_upload( $chemin_devoir /*fichier_chemin*/ , $fichier_nom /*fichier_nom*/ , NULL /*tab_extensions_autorisees*/ , FileSystem::$tab_extensions_interdites , FICHIER_TAILLE_MAX /*taille_maxi*/ , NULL /*filename_in_zip*/ );
  if($result!==TRUE)
  {
    Json::end( FALSE , $result );
  }
  $url = $url_dossier_devoir.FileSystem::$file_saved_name;
  // Mise à jour dans la base
  DB_STRUCTURE_DEVOIR_JOINTURE::DB_remplacer_objet( $devoir_id , $eleve_id , 'doc_'.$doc_objet , $url );
  // Retour
  $tab_balise = array(
    'sujet'   => '<a href="%HREF%" target="_blank" rel="noopener noreferrer" class="no_puce"><img alt="sujet" src="./_img/document/sujet_oui.png"'.infobulle('Sujet disponible.').'></a> <button id="bouton_supprimer_eleve_sujet_%USER%" type="button" class="supprimer">Retirer</button>',
    'corrige' => '<a href="%HREF%" target="_blank" rel="noopener noreferrer" class="no_puce"><img alt="corrigé" src="./_img/document/corrige_oui.png"'.infobulle('Corrigé disponible.').'></a> <button id="bouton_supprimer_eleve_corrige_%USER%" type="button" class="supprimer">Retirer</button>',
  );
  $tab_bad = array( '%HREF%' , '%USER%' );
  $url = FileSystem::verif_lien_safe($url);
  Json::add_tab( array(
    'td_id'   => $doc_objet.'_'.$eleve_id ,
    'cell'    => str_replace( $tab_bad , array($url,$eleve_id) , $tab_balise[$doc_objet] ) ,
    'doc_ref' => $ref.'_'.$eleve_id ,
    'doc_url' => $url ,
  ) );
  Json::end( TRUE );
}

// ////////////////////////////////////////////////////////////////////////////////////////////////////
// Uploader des fichiers individuels via un glisser-deposer multiple
// ////////////////////////////////////////////////////////////////////////////////////////////////////

if($action=='envoyer_fichier')
{
  // Création du dossier si besoin
  FileSystem::creer_dossier($dossier_mult);
  // Récupération du fichier
  $result = FileSystem::recuperer_upload( $dossier_mult /*fichier_chemin*/ , NULL /*fichier_nom*/ , NULL /*tab_extensions_autorisees*/ , FileSystem::$tab_extensions_interdites , FICHIER_TAILLE_MAX /*taille_maxi*/ , NULL /*filename_in_zip*/ );
  if($result!==TRUE)
  {
    Json::end( FALSE , $result );
  }
  // retour
  Json::end( TRUE );
}

// ////////////////////////////////////////////////////////////////////////////////////////////////////
// Traiter les fichiers individuels uploadées précédemment par glisser-deposer
// ////////////////////////////////////////////////////////////////////////////////////////////////////

if( ($action=='traiter_fichiers') && $devoir_id && $groupe_type && $groupe_id && in_array($doc_objet,array('sujet','corrige')) )
{
  $tab_js_cell  = array();
  $tab_js_sujet = array();
  $tab_js_corrige = array();
  // vérification du masque
  if(!$masque)
  {
    Json::end( FALSE , 'Masque des noms de fichiers non transmis !' );
  }
  $masque_filename  = '#\[(user_id|sconet_id|sconet_num|reference|nom|prenom|login|ent_id)\]#';
  $masque_extension = '#\.('.implode('|',FileSystem::$tab_extensions_interdites).')$#';
  if( !preg_match($masque_filename,$masque) || preg_match($masque_extension,$masque) )
  {
    Json::end( FALSE , 'Masque des noms de fichiers non conforme !' );
  }
  Form::save_choix('evaluation_fichiers');
  $extension = FileSystem::extension($masque);
  // Récupérer la liste des élèves et fabriquer le nom de fichier attendu correspondant à chacun
  $tab_bad = array( '[user_id]' , '[sconet_id]' , '[sconet_num]' , '[reference]' , '[nom]' , '[prenom]' , '[login]' , '[ent_id]' );
  $champs = 'user_id, user_id_ent, user_sconet_id, user_sconet_elenoet, user_reference, user_nom, user_prenom, user_login' ;
  $DB_TAB = DB_STRUCTURE_COMMUN::DB_lister_users_regroupement( 'eleve' /*profil_type*/ , 1 /*statut*/ , $groupe_type , $groupe_id , 'nom' /*eleves_ordre*/ , $champs );
  if(!empty($DB_TAB))
  {
    foreach($DB_TAB as $DB_ROW)
    {
      $tab_bon = array( $DB_ROW['user_id'] , $DB_ROW['user_sconet_id'] , $DB_ROW['user_sconet_elenoet'] , Clean::fichier($DB_ROW['user_reference']) , Clean::fichier($DB_ROW['user_nom']) , Clean::fichier($DB_ROW['user_prenom']) , Clean::fichier($DB_ROW['user_login']) , Clean::fichier($DB_ROW['user_id_ent']) );
      $tab_fichier_masque[$DB_ROW['user_id']] = Clean::fichier(str_replace( $tab_bad , $tab_bon , $masque ));
    }
  }
  // Pour l’affichage du retour
  $tab_retour = array( 'r' => '' , 'v' => '' , 'cell' => array() );
  $tab_balise = array(
    'sujet'   => '<a href="%HREF%" target="_blank" rel="noopener noreferrer" class="no_puce"><img alt="sujet" src="./_img/document/sujet_oui.png"'.infobulle('Sujet disponible.').'></a> <button id="bouton_supprimer_eleve_sujet_%USER%" type="button" class="supprimer">Retirer</button>',
    'corrige' => '<a href="%HREF%" target="_blank" rel="noopener noreferrer" class="no_puce"><img alt="corrigé" src="./_img/document/corrige_oui.png"'.infobulle('Corrigé disponible.').'></a> <button id="bouton_supprimer_eleve_corrige_%USER%" type="button" class="supprimer">Retirer</button>',
  );
  $tab_bad = array( '%HREF%' , '%USER%' );
  // Traiter les fichiers un à un
  $tab_fichier_menage = array();
  $tab_fichier = FileSystem::lister_contenu_dossier($dossier_mult);
  FileSystem::creer_sous_dossier_etabl_si_besoin( $chemin_devoir );
  foreach($tab_fichier as $fichier_nom_tmp)
  {
    $tab_user_id = array_keys( $tab_fichier_masque , $fichier_nom_tmp );
    $nb_user_find = count($tab_user_id);
    if($nb_user_find == 0)
    {
      $tab_retour['r'] .= '<li>'.html($fichier_nom_tmp).' <label class="danger">Pas de correspondance trouvée.</label></li>';
    }
    elseif($nb_user_find > 1)
    {
      $tab_retour['r'] .= '<li>'.html($fichier_nom_tmp).' <label class="danger">Plusieurs correspondances trouvées.</label></li>';
    }
    else
    {
      $eleve_id = current($tab_user_id);
      // Déplacer le fichier
      $fichier_nom_save = 'devoir_'.$devoir_id.'_eleve_'.$eleve_id.'_'.$doc_objet.'_'.$_SERVER['REQUEST_TIME'].'.'.$extension; // pas besoin de le rendre inaccessible -> FileSystem::generer_fin_nom_fichier__date_et_alea() inutilement lourd
      FileSystem::deplacer_fichier( $dossier_mult.$fichier_nom_tmp , $chemin_devoir.$fichier_nom_save );
      $tab_fichier_menage['devoir_'.$devoir_id.'_eleve_'.$eleve_id.'_'.$doc_objet] = '_'.$_SERVER['REQUEST_TIME'].'.'.$extension;
      $url = $url_dossier_devoir.$fichier_nom_save;
      // Mise à jour dans la base
      DB_STRUCTURE_DEVOIR_JOINTURE::DB_remplacer_objet( $devoir_id , $eleve_id , 'doc_'.$doc_objet , $url );
      // Retour
      $url = FileSystem::verif_lien_safe($url);
      $tab_retour['v'] .= '<li>'.html($fichier_nom_tmp).' <label class="valide">Document pris en compte.</label></li>';
      $tab_js_cell[$eleve_id] = str_replace( $tab_bad , array($url,$eleve_id) , $tab_balise[$doc_objet] );
      ${'tab_js_'.$doc_objet}[$ref.'_'.$eleve_id] = $url;
    }
  }
  // Nettoyer l’arborescence sinon à chaque upload massif différentes versions de fichiers se cumulent
  if(count($tab_fichier_menage))
  {
    $tab_fichier = FileSystem::lister_contenu_dossier($chemin_devoir);
    foreach($tab_fichier as $fichier_nom)
    {
      $pos_last_slash = strrpos($fichier_nom, '_');
      $fichier_debut = substr($fichier_nom,0,$pos_last_slash);
      if(isset($tab_fichier_menage[$fichier_debut]))
      {
        $fichier_fin = substr($fichier_nom,$pos_last_slash);
        if( $fichier_fin != $tab_fichier_menage[$fichier_debut] )
        {
          FileSystem::supprimer_fichier( $chemin_devoir.$fichier_nom , FALSE /*verif_exist*/ );
        }
      }
    }
  }
  // Supprimer le dossier temporaire
  FileSystem::supprimer_dossier($dossier_mult);
  // retour
  Json::add_row( 'html' , $tab_retour['r'].$tab_retour['v'] );
  Json::add_row( 'tab_sujet'   , json_encode($tab_js_sujet) );
  Json::add_row( 'tab_corrige' , json_encode($tab_js_corrige) );
  Json::add_row( 'tab_cell'    , json_encode($tab_js_cell) );
  Json::end( TRUE );
}

// ////////////////////////////////////////////////////////////////////////////////////////////////////
// Retirer un sujet ou un corrigé d’évaluation (regroupement ou éléve précis)
// ////////////////////////////////////////////////////////////////////////////////////////////////////

if( ($action=='retirer_document') && $devoir_id && in_array($doc_objet,array('sujet','corrige')) && $doc_url )
{
  // Vérification des droits
  $proprio_id = DB_STRUCTURE_PROFESSEUR::DB_recuperer_devoir_proprietaire_id( $devoir_id );
  if($proprio_id!=$_SESSION['USER_ID'])
  {
    Json::end( FALSE , 'Vous n’êtes pas propriétaire du devoir n°'.$devoir_id.' !' );
  }
  // Suppression du fichier, uniquement si ce n’est pas un lien externe ou vers un devoir d’un autre établissement
  if(mb_strpos($doc_url,$url_dossier_devoir)===0)
  {
    // Il peut ne pas être présent sur le serveur en cas de restauration de base ailleurs, etc.
    FileSystem::supprimer_fichier( url_to_chemin($doc_url) , TRUE /*verif_exist*/ );
  }
  // Mise à jour dans la base
  if(!$eleve_id)
  {
    DB_STRUCTURE_PROFESSEUR::DB_modifier_devoir_document( $devoir_id , $doc_objet , '' );
  }
  else
  {
    DB_STRUCTURE_DEVOIR_JOINTURE::DB_remplacer_objet( $devoir_id , $eleve_id , 'doc_'.$doc_objet , NULL );
  }
  
  // Retour
  Json::end( TRUE );
}

// ////////////////////////////////////////////////////////////////////////////////////////////////////
// Déclarer (ou pas) une évaluation complète en saisie
// ////////////////////////////////////////////////////////////////////////////////////////////////////

if( ($action=='maj_fini') && $devoir_id && in_array($fini,array('oui','non')) )
{
  // Vérification des droits
  $proprio_id = DB_STRUCTURE_PROFESSEUR::DB_recuperer_devoir_proprietaire_id( $devoir_id );
  if($proprio_id!=$_SESSION['USER_ID'])
  {
    Json::end( FALSE , 'Vous n’êtes pas propriétaire du devoir n°'.$devoir_id.' !' );
  }
  // Mise à jour dans la base
  DB_STRUCTURE_PROFESSEUR::DB_modifier_devoir_fini( $devoir_id , $fini );
  // Retour
  Json::end( TRUE );
}

// ////////////////////////////////////////////////////////////////////////////////////////////////////
// Récupérer un commentaire audio ou texte
// ////////////////////////////////////////////////////////////////////////////////////////////////////

if( ($action=='recuperer_message') && $devoir_id && $eleve_id && in_array($msg_objet,array('audio','texte')) )
{
  $msg_url = DB_STRUCTURE_DEVOIR_JOINTURE::DB_recuperer_commentaire($devoir_id,$eleve_id,$msg_objet);
  if(empty($msg_url))
  {
    Json::end( FALSE , 'Commentaire introuvable !' );
  }
  // [audio] => On renvoie le lien
  if($msg_objet=='audio')
  {
    if(strpos($msg_url,URL_DIR_SACOCHE)!==0)
    {
      // Violation des directives CSP si on essaye de le lire sur un serveur distant -> on le récupère et le copie localement temporairement
      $msg_data = cURL::get_contents($msg_url);
      $fichier_nom = 'devoir_'.$devoir_id.'_eleve_'.$eleve_id.'_audio_copie.mp3';
      FileSystem::ecrire_fichier( CHEMIN_DOSSIER_IMPORT.$fichier_nom , $msg_data );
      $msg_url = URL_DIR_IMPORT.$fichier_nom;
    }
    $msg_data = $msg_url;
  }
  // [texte] => On récupère le contenu du fichier ;  pas de html() sinon ce n’est pas décodé dans le textarea...
  if($msg_objet=='texte')
  {
    if(strpos($msg_url,URL_DIR_SACOCHE)===0)
    {
      $fichier_chemin = url_to_chemin($msg_url);
      $msg_data = is_file($fichier_chemin) ? file_get_contents($fichier_chemin) : 'Erreur : fichier avec le contenu du commentaire non trouvé.' ;
    }
    else
    {
      $msg_data = cURL::get_contents($msg_url);
      if(!perso_mb_detect_encoding_utf8($msg_data))
      {
        // Cas d’une redirection serveur vers une page d’erreur ou d’interdiction d’accès qui n’est pas en utf-8 (ce qui fait planter la conversion JSON pour le retour).
        $msg_data = To::utf8($msg_data);
      }
    }
  }
  // Retour
  Json::add_tab( array(
    'msg_url'  => $msg_url ,
    'msg_data' => $msg_data ,
  ) );
  Json::end( TRUE );
}

// ////////////////////////////////////////////////////////////////////////////////////////////////////
// Enregistrer un commentaire texte ou audio
// ////////////////////////////////////////////////////////////////////////////////////////////////////

if( ( ($action=='enregistrer_texte') || ($action=='enregistrer_audio') ) && $devoir_id && $eleve_id && in_array($msg_autre,array('oui','non')) && $date_devoir && $choix_saisie_visible && $date_saisie_visible && $description )
{
  $msg_objet = substr($action,-5);
  $date_saisie_visible_sql = To::date_french_to_sql($date_saisie_visible);
  // Tester les droits
  $proprio_id = DB_STRUCTURE_PROFESSEUR::DB_recuperer_devoir_proprietaire_id( $devoir_id );
  if($proprio_id==$_SESSION['USER_ID'])
  {
    $niveau_droit = 4; // propriétaire
  }
  elseif($profs_liste) // forcément
  {
    $search_liste = '_'.$profs_liste.'_';
    if( strpos( $search_liste, '_m'.$_SESSION['USER_ID'].'_' ) !== FALSE )
    {
      $niveau_droit = 3; // modifier
    }
    elseif( strpos( $search_liste, '_s'.$_SESSION['USER_ID'].'_' ) !== FALSE )
    {
      $niveau_droit = 2; // saisir
    }
    elseif( strpos( $search_liste, '_v'.$_SESSION['USER_ID'].'_' ) !== FALSE )
    {
      Json::end( FALSE , 'Droit insuffisant attribué sur le devoir n°'.$devoir_id.' (niveau 1 au lieu de 2) !' ); // voir
    }
    else
    {
      Json::end( FALSE , 'Droit attribué sur le devoir n°'.$devoir_id.' non trouvé !' );
    }
  }
  else
  {
    Json::end( FALSE , 'Vous n’êtes ni propriétaire ni bénéficiaire de droits sur le devoir n°'.$devoir_id.' !' );
  }
  // Supprimer un éventuel fichier précédent
  if( $msg_url && (mb_strpos($msg_url,$url_dossier_devoir)===0) )
  {
    // Il peut ne pas être présent sur le serveur en cas de restauration de base ailleurs, etc.
    FileSystem::supprimer_fichier( url_to_chemin($msg_url) , TRUE /*verif_exist*/ );
  }
  // Mise à jour dans la base
  if($msg_data)
  {
    if($action=='enregistrer_audio')
    {
      // extraire les données binaires brutes
      $msg_data = substr($msg_data, strpos($msg_data,',') + 1 );
      // les décoder
      $msg_data = base64_decode($msg_data);
      // Le type MIME dépend du navigateur ; fin 2024 :
      // - pour Firefox c’est "audio/ogg; codecs=opus" mais mediaRecorder.mimeType ne récupère pas l’info...
      // - pour Chrome / Edge / Opera c’est "audio/webm;codecs=opus"
      // - pour Safari c’est "audio/mp4"
      // /!\ Le format OGG n’est ensuite pas lu par Safari...
      if(strpos($mime_type,'ogg')!==FALSE)
      {
        $ext = 'ogg';
      }
      else if(strpos($mime_type,'webm')!==FALSE)
      {
        $ext = 'webm';
      }
      else if(strpos($mime_type,'mp4')!==FALSE)
      {
        $ext = 'mp4';
      }
      else if(strpos($msg_data,'Ogg')!==FALSE)
      {
        $ext = 'ogg';
      }
      else if(strpos($msg_data,'webm')!==FALSE)
      {
        $ext = 'webm';
      }
      else // par défaut pour mettre qq chose...
      {
        $ext = 'mp3';
      }
    }
    else
    {
      $ext = 'txt';
    }
    $fichier_nom = 'devoir_'.$devoir_id.'_eleve_'.$eleve_id.'_'.$msg_objet.'_'.$_SERVER['REQUEST_TIME'].'.'.$ext; // pas besoin de le rendre inaccessible -> FileSystem::generer_fin_nom_fichier__date_et_alea() inutilement lourd
    DB_STRUCTURE_DEVOIR_JOINTURE::DB_remplacer_objet( $devoir_id , $eleve_id , $msg_objet , $url_dossier_devoir.$fichier_nom );
    $presence = TRUE;
    // et enregistrement du fichier
    FileSystem::creer_sous_dossier_etabl_si_besoin( $chemin_devoir );
    FileSystem::ecrire_fichier( $chemin_devoir.$fichier_nom , $msg_data );
  }
  else
  {
    DB_STRUCTURE_DEVOIR_JOINTURE::DB_remplacer_objet( $devoir_id , $eleve_id , $msg_objet , NULL );
    $presence = ($msg_autre=='oui') ? TRUE : FALSE ;
  }
  // Notifications (rendues visibles ultérieurement) ; le mode discret ne d’applique volontairement pas ici car les modifications sont chirurgicales
  $listing_eleves = (string)$eleve_id;
  $listing_parents = DB_STRUCTURE_NOTIFICATION::DB_lister_parents_listing_id($listing_eleves);
  $listing_users = ($listing_parents) ? $listing_eleves.','.$listing_parents : $listing_eleves ;
  $listing_abonnes = DB_STRUCTURE_NOTIFICATION::DB_lister_destinataires_listing_id( $abonnement_ref_saisie , $listing_users );
  if($listing_abonnes)
  {
    $notification_date = ( TODAY_SQL < $date_saisie_visible_sql ) ? $date_saisie_visible_sql : NULL ;
    $notification_contenu = 'Saisies pour l’évaluation "'.$description.'" du '.$date_devoir.' enregistrées par '.To::texte_genre_identite($_SESSION['USER_NOM'],FALSE,$_SESSION['USER_PRENOM'],TRUE,$_SESSION['USER_GENRE']).'.'."\r\n\r\n";
    $notification_lien = 'Voir le détail :'."\r\n".Sesamail::adresse_lien_profond('page=evaluation&section=voir&devoir_id='.$devoir_id.'&eleve_id='.$eleve_id);
    $tab_abonnes = DB_STRUCTURE_NOTIFICATION::DB_lister_detail_abonnes_envois( $listing_abonnes , $listing_eleves , $listing_parents );
    foreach($tab_abonnes as $abonne_id => $tab_abonne)
    {
      foreach($tab_abonne as $eleve_id => $notification_intro_eleve)
      {
        if($presence)
        {
          DB_STRUCTURE_NOTIFICATION::DB_modifier_log_attente( $abonne_id , $abonnement_ref_saisie , $devoir_id , $notification_date , $notification_intro_eleve.$notification_contenu.$notification_lien , 'remplacer' );
        }
        else
        {
          DB_STRUCTURE_NOTIFICATION::DB_supprimer_log_attente( $abonnement_ref_saisie , $devoir_id , $abonne_id );
        }
      }
    }
  }
  // Retour
  $retour = ($msg_data) ? $url_dossier_devoir.$fichier_nom : 'supprimé' ;
  Json::end( TRUE , $retour );
}

// ////////////////////////////////////////////////////////////////////////////////////////////////////
// Il se peut que rien n’ait été récupéré à cause de l’upload d’un fichier trop lourd
// ////////////////////////////////////////////////////////////////////////////////////////////////////

if(empty($_POST))
{
  Json::end( FALSE , 'Aucune donnée reçue ! Fichier trop lourd ? '.InfoServeur::minimum_limitations_upload() );
}

// ////////////////////////////////////////////////////////////////////////////////////////////////////
// Zipper l’ensemble des copies des élèves à un devoir
// ////////////////////////////////////////////////////////////////////////////////////////////////////

if( ($action=='zipper_copies_eleves') && $devoir_id ) // $description + $groupe_nom sont aussi transmis
{
  Erreur500::prevention_et_gestion_erreurs_fatales( TRUE /*memory*/ , TRUE /*time*/ );
  // On récupère les adresses des copies et les identités des élèves
  $DB_TAB = DB_STRUCTURE_DEVOIR_JOINTURE::DB_lister_copies($devoir_id);
  $nb_copies = count($DB_TAB);
  if( !$nb_copies )
  {
    Json::end( FALSE , 'Aucune copie trouvée pour ce devoir !' );
  }
  // On copies les fichiers dans un dossier
  $sous_dossier_copies = 'copies_'.$_SESSION['BASE'].'_'.mt_rand();
  $chemin_copies = CHEMIN_DOSSIER_EXPORT.$sous_dossier_copies.DS;
  FileSystem::creer_ou_vider_dossier($chemin_copies);
  foreach($DB_TAB as $key => $DB_ROW)
  {
    $copie_url = $DB_ROW['jointure_doc_copie'];
    $extension = FileSystem::extension($copie_url);
    $fichier_nom  = Clean::fichier('copie_'.$devoir_id.'_'.$groupe_nom.'_'.$description.'_'.$DB_ROW['user_nom'].' '.$DB_ROW['user_prenom'].'.'.$extension);
    if(strpos($copie_url,URL_DIR_SACOCHE)===0)
    {
      $fichier_chemin = url_to_chemin($copie_url);
      if(is_file($fichier_chemin))
      {
        FileSystem::copier_fichier( $fichier_chemin , $chemin_copies.$fichier_nom );
      }
    }
    else
    {
      $copie_data = cURL::get_contents($copie_url);
      FileSystem::ecrire_fichier( $chemin_copies.$fichier_nom , $msg_data );
    }
  }
  // On zippe l’ensemble
  $fichier_zip_nom = Clean::fichier('copies_'.$devoir_id.'_'.$groupe_nom.'_'.$description.'.zip');
  $result = FileSystem::zip_fichiers( $chemin_copies , CHEMIN_DOSSIER_EXPORT , $fichier_zip_nom );
  if($result!==TRUE)
  {
    Json::end( FALSE , $result );
  }
  $href = URL_DIR_EXPORT.$fichier_zip_nom;
  $retour = '<ul class="puce"><li><a href="'.$href.'" target="_blank" rel="noopener noreferrer"><span class="file file_zip">Copies des élèves pour ce devoir (fichier <em>zip</em></span>)</a>.</li></ul>';
  Json::end( TRUE , $retour );

}

// ////////////////////////////////////////////////////////////////////////////////////////////////////
// On ne devrait pas en arriver là...
// ////////////////////////////////////////////////////////////////////////////////////////////////////

Json::end( FALSE , 'Erreur avec les données transmises !<br>'.pathinfo(__FILE__,PATHINFO_FILENAME).':'.__LINE__ );

?>
