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

// Ces méthodes ne concernent qu’une base STRUCTURE.
// Ces méthodes ne concernent (quasiment) que la gestion des référentiels par les personnels habilités.

class DB_STRUCTURE_REFERENTIEL
{

/**
 * DB_OPT_lister_elements_referentiels_prof
 *
 * @param int      $prof_id
 * @param string   $granulosite   'referentiel' | 'domaine' | 'theme'
 * @param int      $listing_id_matieres_autorisees
 * @return array
 */
public static function DB_OPT_lister_elements_referentiels_prof( $prof_id , $granulosite , $listing_id_matieres_autorisees )
{
  switch($granulosite)
  {
    case 'referentiel' :
      $select_valeur = 'CONCAT(matiere_id,"_","1","_",niveau_id,"_",niveau_ordre)';
      $select_texte = 'CONCAT(matiere_nom," - ",niveau_nom)';
      $left_join = '';
      $order_by  = '';
      $where     = '';
      break;
    case 'domaine' :
      $select_valeur = 'CONCAT(matiere_id,"_",niveau_id,"_",domaine_id,"_",domaine_ordre)';
      $select_texte = 'CONCAT(matiere_nom," - ",niveau_nom," - ",domaine_nom)';
      $left_join = 'LEFT JOIN sacoche_referentiel_domaine USING (matiere_id,niveau_id) ';
      $order_by  = ', domaine_ordre ASC';
      $where     = 'AND domaine_id IS NOT NULL ';
      break;
    case 'theme' :
      $select_valeur = 'CONCAT(matiere_id,"_",domaine_id,"_",theme_id,"_",theme_ordre)';
      $select_texte = 'CONCAT(matiere_nom," - ",niveau_nom," - ",domaine_nom," - ",theme_nom)';
      $left_join = 'LEFT JOIN sacoche_referentiel_domaine USING (matiere_id,niveau_id) LEFT JOIN sacoche_referentiel_theme USING (domaine_id) ';
      $order_by  = ', domaine_ordre ASC, theme_ordre ASC';
      $where     = 'AND theme_id IS NOT NULL ';
      break;
  }
  $DB_SQL = 'SELECT '.$select_valeur.' AS valeur, '.$select_texte.' AS texte '
          . 'FROM sacoche_referentiel '
          . 'LEFT JOIN sacoche_jointure_user_matiere USING (matiere_id) '
          . 'LEFT JOIN sacoche_niveau USING (niveau_id) '
          . 'LEFT JOIN sacoche_matiere USING (matiere_id) '
          . $left_join
          . 'WHERE matiere_active=1 AND user_id=:user_id ' // Test matiere car un prof peut être encore relié à des matières décochées par l’admin.
          . 'AND matiere_id IN ('.$listing_id_matieres_autorisees.') '
          . $where
          . 'ORDER BY matiere_nom ASC, niveau_ordre ASC'.$order_by;
  $DB_VAR = array( ':user_id' => $prof_id );
  $DB_TAB = DB::queryTab(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL , $DB_VAR);
  return !empty($DB_TAB) ? $DB_TAB : 'Aucun élément de référentiel trouvé.' ;
}

/**
 * recuperer_socle2016_for_referentiels_matiere
 *
 * @param int $matiere_id
 * @return array
 */
public static function DB_recuperer_socle2016_for_referentiels_matiere($matiere_id)
{
  $DB_SQL = 'SELECT item_id, socle_cycle_id, socle_composante_id, socle_cycle_nom, socle_domaine_ordre , socle_composante_nom_simple '
          . 'FROM sacoche_referentiel_domaine '
          . 'LEFT JOIN sacoche_referentiel_theme USING (domaine_id) '
          . 'LEFT JOIN sacoche_referentiel_item USING (theme_id) '
          . 'LEFT JOIN sacoche_jointure_referentiel_socle USING (item_id) '
          . 'LEFT JOIN sacoche_socle_cycle USING (socle_cycle_id) '
          . 'LEFT JOIN sacoche_socle_composante USING (socle_composante_id) '
          . 'LEFT JOIN sacoche_socle_domaine USING (socle_domaine_id) '
          . 'WHERE matiere_id=:matiere_id AND socle_cycle_id IS NOT NULL ' // on peut aussi utiliser INNER JOIN
          . 'ORDER BY socle_cycle_ordre ASC, socle_domaine_ordre ASC, socle_composante_ordre ASC';
  $DB_VAR = array( ':matiere_id' => $matiere_id );
  $DB_TAB = DB::queryTab(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL , $DB_VAR);
  $DB_TAB_socle2016 = array();
  // On retourne [item_id] => array( 'id' => array(cycle_id.composante_id) , 'nom' => array(cycle_nom.' - Domaine '.domaine_ordre.' - '.composante_nom) )
  if(!empty($DB_TAB))
  {
    foreach($DB_TAB as $DB_ROW)
    {
      $DB_TAB_socle2016[$DB_ROW['item_id']]['id' ][] = $DB_ROW['socle_cycle_id'].$DB_ROW['socle_composante_id'];
      $DB_TAB_socle2016[$DB_ROW['item_id']]['nom'][] = html($DB_ROW['socle_cycle_nom'].' - Domaine '.$DB_ROW['socle_domaine_ordre'].' - '.$DB_ROW['socle_composante_nom_simple']);
    }
  }
  return $DB_TAB_socle2016;
}

/**
 * recuperer_socle2016_for_referentiel_matiere_niveau
 *
 * @param int  $matiere_id
 * @param int  $niveau_id   0 pour tous les niveaux
 * @param bool $format    'texte' | 'ids'
 * @return array
 */
public static function DB_recuperer_socle2016_for_referentiel_matiere_niveau( $matiere_id , $niveau_id , $format )
{
  $select_noms = ($format=='texte') ? ', socle_cycle_nom, socle_domaine_ordre, socle_composante_nom_simple ' : '' ;
  $where_niveau = ($niveau_id)  ? 'AND niveau_id=:niveau_id ' : 'AND niveau_actif=1 ' ;
  $DB_SQL = 'SELECT item_id, socle_cycle_id, socle_domaine_id, socle_composante_id '.$select_noms
          . 'FROM sacoche_referentiel '
          . 'LEFT JOIN sacoche_niveau USING (niveau_id) '
          . 'LEFT JOIN sacoche_referentiel_domaine USING (matiere_id,niveau_id) '
          . 'LEFT JOIN sacoche_referentiel_theme USING (domaine_id) '
          . 'LEFT JOIN sacoche_referentiel_item USING (theme_id) '
          . 'LEFT JOIN sacoche_jointure_referentiel_socle USING (item_id) '
          . 'LEFT JOIN sacoche_socle_cycle USING (socle_cycle_id) '
          . 'LEFT JOIN sacoche_socle_composante USING (socle_composante_id) '
          . 'LEFT JOIN sacoche_socle_domaine USING (socle_domaine_id) '
          . 'WHERE matiere_id=:matiere_id AND socle_cycle_id IS NOT NULL '.$where_niveau // on peut aussi utiliser INNER JOIN
          . 'ORDER BY socle_cycle_ordre ASC, socle_domaine_ordre ASC, socle_composante_ordre ASC ';
  $DB_VAR = array(
    ':matiere_id' => $matiere_id,
    ':niveau_id'  => $niveau_id,
  );
  $DB_TAB = DB::queryTab(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL , $DB_VAR);
  $DB_TAB_socle2016 = array();
  // On retourne [item_id][] => array( 'cycle'=>cycle_id , 'domaine'=>domaine_id , 'composante'=>composante_id )
  if( !empty($DB_TAB) && ($format=='ids') )
  {
    foreach($DB_TAB as $DB_ROW)
    {
      $DB_TAB_socle2016[$DB_ROW['item_id']][] = array(
        'cycle'      => $DB_ROW['socle_cycle_id'],
        'domaine'    => $DB_ROW['socle_domaine_id'],
        'composante' => $DB_ROW['socle_composante_id'],
      );
    }
  }
  // On retourne [item_id] => array( 'id' => array(cycle_id.composante_id) , 'nom' => array(cycle_nom.' - Domaine '.domaine_ordre.' - '.composante_nom) )
  if( !empty($DB_TAB) && ($format=='texte') )
  {
    foreach($DB_TAB as $DB_ROW)
    {
      $DB_TAB_socle2016[$DB_ROW['item_id']]['id' ][] = $DB_ROW['socle_cycle_id'].$DB_ROW['socle_composante_id'];
      $DB_TAB_socle2016[$DB_ROW['item_id']]['nom'][] = html($DB_ROW['socle_cycle_nom'].' - Domaine '.$DB_ROW['socle_domaine_ordre'].' - '.$DB_ROW['socle_composante_nom_simple']);
    }
  }
  return $DB_TAB_socle2016;
}

/**
 * recuperer_socle2016_for_items
 *
 * @param string   $listing_items_id
 * @return array
 */
public static function DB_recuperer_socle2016_for_items( $listing_items_id )
{
  $DB_SQL = 'SELECT item_id, socle_cycle_id AS cycle, socle_composante_id AS composante '
          . 'FROM sacoche_jointure_referentiel_socle '
          . 'LEFT JOIN sacoche_socle_cycle USING (socle_cycle_id) '
          . 'LEFT JOIN sacoche_socle_composante USING (socle_composante_id) '
          . 'LEFT JOIN sacoche_socle_domaine USING (socle_domaine_id) '
          . 'WHERE item_id IN('.$listing_items_id.') '
          . 'ORDER BY socle_cycle_ordre ASC, socle_domaine_ordre ASC, socle_composante_ordre ASC';
  // Retourne [item_id][] => array( 'cycle'=>cycle_id , 'composante'=>composante_id )
  return DB::queryTab(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL , array() , TRUE);
}

/**
 * lister_referentiels
 *
 * @param string   $listing_matiere_id   rien pour toutes les matières
 * @param bool     $without_expe         TRUE pour éviter les matières expérimentales
 * @return array
 */
public static function DB_recuperer_referentiels( $listing_matiere_id , $without_expe )
{
  $where_matiere = ($listing_matiere_id) ? 'AND matiere_id IN('.$listing_matiere_id.') ' : '' ;
  $where_expe    = ($without_expe)       ? 'AND matiere_experimentale = 0 '              : '' ;
  $DB_SQL = 'SELECT matiere_id, niveau_id, matiere_nom, niveau_nom, referentiel_mode_synthese, referentiel_mode_livret, matiere_experimentale '
          . 'FROM sacoche_referentiel '
          . 'LEFT JOIN sacoche_matiere USING (matiere_id) '
          . 'LEFT JOIN sacoche_niveau USING (niveau_id) '
          . 'WHERE matiere_active=1 AND niveau_actif=1 '.$where_matiere.$where_expe
          . 'ORDER BY matiere_nom ASC, niveau_ordre ASC ';
  return DB::queryTab(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL);
}

/**
 * recuperer_referentiels_domaines
 *
 * @return array
 */
public static function DB_recuperer_referentiels_domaines()
{
  $DB_SQL = 'SELECT matiere_id, niveau_id, domaine_nom '
          . 'FROM sacoche_referentiel_domaine '
          . 'ORDER BY domaine_ordre ASC';
  return DB::queryTab(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL);
}

/**
 * recuperer_referentiels_domaines_cibles
 *
 * @param string $listing_matiere_id
 * @param string $listing_niveau_id
 * @return array
 */
public static function DB_recuperer_referentiels_domaines_cibles( $listing_matiere_id , $listing_niveau_id )
{
  $DB_SQL = 'SELECT matiere_id, niveau_id, domaine_id, domaine_nom '
          . 'FROM sacoche_referentiel_domaine '
          . 'WHERE matiere_id IN('.$listing_matiere_id.') AND niveau_id IN('.$listing_niveau_id.') ';
  return DB::queryTab(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL);
}

/**
 * recuperer_referentiels_themes
 *
 * @return array
 */
public static function DB_recuperer_referentiels_themes()
{
  $DB_SQL = 'SELECT matiere_id, niveau_id, theme_nom '
          . 'FROM sacoche_referentiel_theme '
          . 'LEFT JOIN sacoche_referentiel_domaine USING (domaine_id) '
          . 'ORDER BY domaine_ordre ASC, theme_ordre ASC';
  return DB::queryTab(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL);
}

/**
 * recuperer_referentiels_themes_cibles
 *
 * @param string $listing_domaine_id
 * @return array
 */
public static function DB_recuperer_referentiels_themes_cibles($listing_domaine_id)
{
  $DB_SQL = 'SELECT domaine_id, theme_id, theme_nom '
          . 'FROM sacoche_referentiel_theme '
          . 'WHERE domaine_id IN('.$listing_domaine_id.') ';
  return DB::queryTab(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL);
}

/**
 * recuperer_referentiels_items
 *
 * @return array
 */
public static function DB_recuperer_referentiels_items()
{
  $DB_SQL = 'SELECT matiere_id, niveau_id, item_nom '
          . 'FROM sacoche_referentiel_item '
          . 'LEFT JOIN sacoche_referentiel_theme USING (theme_id) '
          . 'LEFT JOIN sacoche_referentiel_domaine USING (domaine_id) '
          . 'ORDER BY domaine_ordre ASC, theme_ordre ASC, item_ordre ASC';
  return DB::queryTab(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL);
}

/**
 * recuperer_referentiels_items_cibles
 *
 * @param string $listing_theme_id
 * @return array
 */
public static function DB_recuperer_referentiels_items_cibles($listing_theme_id)
{
  $DB_SQL = 'SELECT theme_id, item_id, item_nom '
          . 'FROM sacoche_referentiel_item '
          . 'WHERE theme_id IN('.$listing_theme_id.') ';
  return DB::queryTab(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL);
}

/**
 * DB_recuperer_referentiel_partage_etat
 *
 * @param int    $matiere_id
 * @param int    $niveau_id
 * @return string
 */
public static function DB_recuperer_referentiel_partage_etat($matiere_id,$niveau_id)
{
  $DB_SQL = 'SELECT referentiel_partage_etat '
          . 'FROM sacoche_referentiel '
          . 'WHERE matiere_id=:matiere_id AND niveau_id=:niveau_id ';
  $DB_VAR = array(
    ':matiere_id' => $matiere_id,
    ':niveau_id'  => $niveau_id,
  );
  return DB::queryOne(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL , $DB_VAR);
}

/**
 * DB_recuperer_autres_professeurs_matiere
 *
 * @param int    $matiere_id
 * @param int    $user_id
 * @return string
 */
public static function DB_recuperer_autres_professeurs_matiere( $matiere_id , $user_id )
{
  // Lever si besoin une limitation de GROUP_CONCAT (group_concat_max_len est par défaut limité à une chaîne de 1024 caractères) ; éviter plus de 8096 (http://www.glpi-project.org/forum/viewtopic.php?id=23767).
  DB::query(SACOCHE_STRUCTURE_BD_NAME , 'SET group_concat_max_len = 8096');
  // Go
  $DB_SQL = 'SELECT CONVERT( GROUP_CONCAT(user_id SEPARATOR ",") , CHAR) AS identifiants '
          . 'FROM sacoche_jointure_user_matiere '
          . 'WHERE matiere_id=:matiere_id AND user_id!=:user_id';
  $DB_VAR = array(
    ':matiere_id' => $matiere_id,
    ':user_id'    => $user_id,
  );
  return DB::queryOne(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL , $DB_VAR);
}

/**
 * DB_recuperer_domaine_ordre_max
 *
 * @param int    $matiere_id
 * @param int    $niveau_id
 * @return int
 */
public static function DB_recuperer_domaine_ordre_max( $matiere_id , $niveau_id )
{
  $DB_SQL = 'SELECT MAX(domaine_ordre) '
          . 'FROM sacoche_referentiel_domaine '
          . 'WHERE matiere_id=:matiere_id AND niveau_id=:niveau_id ';
  $DB_VAR = array(
    ':matiere_id' => $matiere_id,
    ':niveau_id'  => $niveau_id,
  );
  return (int)DB::queryOne(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL , $DB_VAR);
}

/**
 * DB_recuperer_theme_ordre_max
 *
 * @param int    $domaine_id
 * @return int
 */
public static function DB_recuperer_theme_ordre_max($domaine_id)
{
  $DB_SQL = 'SELECT MAX(theme_ordre) '
          . 'FROM sacoche_referentiel_theme '
          . 'WHERE domaine_id=:domaine_id ';
  $DB_VAR = array(':domaine_id'=>$domaine_id);
  return (int)DB::queryOne(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL , $DB_VAR);
}

/**
 * DB_recuperer_liens_items_fusionnes
 *
 * @param int    $item_id_degageant
 * @param int    $item_id_absorbant
 * @return array
 */
public static function DB_recuperer_liens_items_fusionnes( $item_id_degageant , $item_id_absorbant )
{
  $DB_SQL = 'SELECT item_lien '
          . 'FROM sacoche_referentiel_item '
          . 'WHERE item_id = :item_id ';
  $DB_VAR = array(':item_id'=>$item_id_degageant);
  $lien_item_degageant = DB::queryOne(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL , $DB_VAR);
  $DB_VAR = array(':item_id'=>$item_id_absorbant);
  $lien_item_absorbant = DB::queryOne(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL , $DB_VAR);
  return array($lien_item_degageant,$lien_item_absorbant);
}

/**
 * Compter le nombre de saisies dépendant d’un référentiel
 *
 * @param int    $matiere_id
 * @param int    $niveau_id
 * @return void
 */
public static function DB_compter_saisies_for_referentiel_matiere_niveau( $matiere_id , $niveau_id )
{
  $DB_SQL = 'SELECT COUNT(saisie_note) '
          . 'FROM sacoche_referentiel '
          . 'LEFT JOIN sacoche_referentiel_domaine USING (matiere_id,niveau_id) '
          . 'LEFT JOIN sacoche_referentiel_theme USING (domaine_id) '
          . 'LEFT JOIN sacoche_referentiel_item USING (theme_id) '
          . 'LEFT JOIN sacoche_saisie USING (item_id) '
          . 'WHERE sacoche_referentiel.matiere_id=:matiere_id AND sacoche_referentiel.niveau_id=:niveau_id ';
  $DB_VAR = array(
    ':matiere_id' => $matiere_id,
    ':niveau_id'  => $niveau_id,
  );
  return (int)DB::queryOne(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL , $DB_VAR);
}

/**
 * Compter le nombre de saisies dépendant d’un élément de référentiel
 *
 * @param string   $granulosite   'domaine' | 'theme' | 'item'
 * @param int    $element_id
 * @return void
 */
public static function DB_compter_saisies_for_referentiel_element( $granulosite , $element_id )
{
  $DB_SQL = 'SELECT COUNT(saisie_note) ';
  switch($granulosite)
  {
    case 'domaine' :
      $DB_SQL.= 'FROM sacoche_referentiel_domaine '
              . 'LEFT JOIN sacoche_referentiel_theme USING (domaine_id) '
              . 'LEFT JOIN sacoche_referentiel_item USING (theme_id) ';
      break;
    case 'theme' :
      $DB_SQL.= 'FROM sacoche_referentiel_theme '
              . 'LEFT JOIN sacoche_referentiel_item USING (theme_id) ';
      break;
    case 'item' :
      $DB_SQL.= 'FROM sacoche_referentiel_item ';
      break;
  }
  $DB_SQL.= 'LEFT JOIN sacoche_saisie USING (item_id) '
          . 'WHERE '.$granulosite.'_id = :element_id ';
  $DB_VAR = array(
    ':element_id' => $element_id,
  );
  return (int)DB::queryOne(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL , $DB_VAR);
}

/**
 * Tester la présence d’un référentiel
 *
 * @param int    $matiere_id
 * @param int    $niveau_id
 * @return int
 */
public static function DB_tester_referentiel( $matiere_id , $niveau_id )
{
  $DB_SQL = 'SELECT matiere_id '
          . 'FROM sacoche_referentiel '
          . 'WHERE matiere_id=:matiere_id AND niveau_id=:niveau_id ';
  $DB_VAR = array(
    ':matiere_id' => $matiere_id,
    ':niveau_id'  => $niveau_id,
  );
  return (int)DB::queryOne(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL , $DB_VAR);
}

/**
 * Ajouter un référentiel (juste la coquille, sans son contenu)
 *
 * @param int    $matiere_id
 * @param int    $niveau_id
 * @param string $partage_etat
 * @return void
 */
public static function DB_ajouter_referentiel( $matiere_id , $niveau_id , $partage_etat )
{
  $DB_SQL = 'INSERT INTO sacoche_referentiel( matiere_id, niveau_id,referentiel_partage_etat,referentiel_partage_date,referentiel_calcul_methode,referentiel_calcul_limite,referentiel_calcul_retroactif,referentiel_mode_synthese,referentiel_information) '
          . 'VALUES                         (:matiere_id,:niveau_id,           :partage_etat,           :partage_date,           :calcul_methode,           :calcul_limite,           :calcul_retroactif,           :mode_synthese,           :information)';
  $DB_VAR = array(
    ':matiere_id'        => $matiere_id,
    ':niveau_id'         => $niveau_id,
    ':partage_etat'      => $partage_etat,
    ':partage_date'      => TODAY_SQL,
    ':calcul_methode'    => $_SESSION['CALCUL_METHODE'],
    ':calcul_limite'     => $_SESSION['CALCUL_LIMITE'],
    ':calcul_retroactif' => $_SESSION['CALCUL_RETROACTIF'],
    ':mode_synthese'     => 'inconnu',
    ':information'       => '',
  );
  DB::query(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL , $DB_VAR);
}

/**
 * Ajouter un domaine dans un référentiel (le numéro d’ordre des autres domaines impactés est modifié ailleurs)
 *
 * @param int    $matiere_id
 * @param int    $niveau_id
 * @param int    $domaine_ordre
 * @param string $domaine_code
 * @param string $domaine_ref
 * @param string $domaine_nom
 * @return int
 */
public static function DB_ajouter_referentiel_domaine( $matiere_id , $niveau_id , $domaine_ordre , $domaine_code , $domaine_ref , $domaine_nom )
{
  $DB_SQL = 'INSERT INTO sacoche_referentiel_domaine( matiere_id, niveau_id, domaine_ordre, domaine_code, domaine_ref, domaine_nom) '
          . 'VALUES                                 (:matiere_id,:niveau_id,:domaine_ordre,:domaine_code,:domaine_ref,:domaine_nom)';
  $DB_VAR = array(
    ':matiere_id'    => $matiere_id,
    ':niveau_id'     => $niveau_id,
    ':domaine_ordre' => $domaine_ordre,
    ':domaine_code'  => $domaine_code,
    ':domaine_ref'   => $domaine_ref,
    ':domaine_nom'   => $domaine_nom,
  );
  DB::query(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL , $DB_VAR);
  return DB::getLastOid(SACOCHE_STRUCTURE_BD_NAME);
}

/**
 * Ajouter un thème dans un référentiel (le numéro d’ordre des autres thèmes impactés est modifié ailleurs)
 *
 * @param int    $domaine_id
 * @param int    $theme_ordre
 * @param string $theme_ref
 * @param string $theme_nom
 * @return int
 */
public static function DB_ajouter_referentiel_theme( $domaine_id , $theme_ordre , $theme_ref , $theme_nom )
{
  $DB_SQL = 'INSERT INTO sacoche_referentiel_theme( domaine_id, theme_ordre, theme_ref, theme_nom) '
          . 'VALUES                               (:domaine_id,:theme_ordre,:theme_ref,:theme_nom)';
  $DB_VAR = array(
    ':domaine_id'  => $domaine_id,
    ':theme_ordre' => $theme_ordre,
    ':theme_ref'   => $theme_ref,
    ':theme_nom'   => $theme_nom,
  );
  DB::query(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL , $DB_VAR);
  return DB::getLastOid(SACOCHE_STRUCTURE_BD_NAME);
}

/**
 * Ajouter un item dans un référentiel (les liaisons au socle 2016 sont gérées ici ; le lien de ressources est géré ultérieurement ; le numéro d’ordre des autres items impactés est modifié ailleurs)
 *
 * @param int    $theme_id
 * @param int    $item_ordre
 * @param string $item_ref
 * @param string $item_nom
 * @param string $item_abrev
 * @param int    $item_coef
 * @param int    $item_cart
 * @param string $item_module
 * @param string $item_comm
 * @param array  $tab_socle2016
 * @return int
 */
public static function DB_ajouter_referentiel_item( $theme_id , $item_ordre , $item_ref , $item_nom , $item_abrev , $item_coef , $item_cart , $item_module , $item_comm , $tab_socle2016 )
{
  $DB_SQL = 'INSERT INTO sacoche_referentiel_item( theme_id, item_ordre, item_ref, item_nom, item_abrev, item_coef, item_cart, item_lien, item_module, item_comm) '
          . 'VALUES                              (:theme_id,:item_ordre,:item_ref,:item_nom,:item_abrev,:item_coef,:item_cart,:item_lien,:item_module,:item_comm) ';
  $DB_VAR = array(
    ':theme_id'    => $theme_id,
    ':item_ordre'  => $item_ordre,
    ':item_ref'    => $item_ref,
    ':item_nom'    => $item_nom,
    ':item_abrev'  => $item_abrev,
    ':item_coef'   => $item_coef,
    ':item_cart'   => $item_cart,
    ':item_lien'   => '',
    ':item_module' => $item_module,
    ':item_comm'   => $item_comm,
  );
  DB::query(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL , $DB_VAR);
  $item_id = DB::getLastOid(SACOCHE_STRUCTURE_BD_NAME);
  // Liaisons au socle
  DB_STRUCTURE_REFERENTIEL::DB_modifier_liaison_item_socle2016( $item_id , $tab_socle2016 , 'ajouter' );
  // Retour
  return $item_id;
}

/**
 * Déplacer un domaine d’un référentiel (le numéro d’ordre des autres domaines impactés est modifié ailleurs)
 *
 * @param int    $domaine_id
 * @param int    $niveau_id
 * @param int    $domaine_ordre
 * @param int    $matiere_id   uniquement pour un déplacement vers une autre matière
 * @return int   test si déplacement effectué (0|1)
 */
public static function DB_deplacer_referentiel_domaine( $domaine_id , $niveau_id , $domaine_ordre , $matiere_id=0 )
{
  $set_matiere = ($matiere_id) ? ', matiere_id=:matiere_id ' : '' ;
  $DB_SQL = 'UPDATE sacoche_referentiel_domaine '
          . 'SET niveau_id=:niveau_id, domaine_ordre=:domaine_ordre '.$set_matiere
          . 'WHERE domaine_id=:domaine_id ';
  $DB_VAR = array(
    ':domaine_id'    => $domaine_id,
    ':niveau_id'     => $niveau_id,
    ':domaine_ordre' => $domaine_ordre,
    ':matiere_id'    => $matiere_id,
  );
  DB::query(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL , $DB_VAR);
  return DB::rowCount(SACOCHE_STRUCTURE_BD_NAME);
}

/**
 * Déplacer un thème d’un référentiel (le numéro d’ordre des autres thèmes impactés est modifié ailleurs)
 *
 * @param int    $theme_id
 * @param int    $domaine_id
 * @param int    $theme_ordre
 * @return int   test si déplacement effectué (0|1)
 */
public static function DB_deplacer_referentiel_theme( $theme_id , $domaine_id , $theme_ordre )
{
  $DB_SQL = 'UPDATE sacoche_referentiel_theme '
          . 'SET domaine_id=:domaine_id, theme_ordre=:theme_ordre '
          . 'WHERE theme_id=:theme_id ';
  $DB_VAR = array(
    ':theme_id'    => $theme_id,
    ':domaine_id'  => $domaine_id,
    ':theme_ordre' => $theme_ordre,
  );
  DB::query(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL , $DB_VAR);
  return DB::rowCount(SACOCHE_STRUCTURE_BD_NAME);
}

/**
 * Déplacer un item d’un référentiel (le numéro d’ordre des autres items impactés est modifié ailleurs)
 *
 * @param int    $item_id
 * @param int    $theme_id
 * @param int    $item_ordre
 * @return int   test si déplacement effectué (0|1)
 */
public static function DB_deplacer_referentiel_item( $item_id , $theme_id , $item_ordre )
{
  $DB_SQL = 'UPDATE sacoche_referentiel_item '
          . 'SET theme_id=:theme_id, item_ordre=:item_ordre '
          . 'WHERE item_id=:item_id ';
  $DB_VAR = array(
    ':item_id'    => $item_id,
    ':theme_id'   => $theme_id,
    ':item_ordre' => $item_ordre,
    );
  DB::query(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL , $DB_VAR);
  return DB::rowCount(SACOCHE_STRUCTURE_BD_NAME);
}

/**
 * Importer dans la base l’arborescence d’un référentiel à partir d’un XML récupéré sur le serveur communautaire
 *
 * Remarque : les ordres des domaines / thèmes / items ne sont pas dans le XML car il sont générés par leur position dans l’arborescence.
 *
 * @param string $arbreXML
 * @param int    $matiere_id
 * @param int    $niveau_id
 * @return void
 */
public static function DB_importer_arborescence_from_XML( $arbreXML , $matiere_id , $niveau_id )
{
  // décortiquer l’arbre XML
  $xml = new DOMDocument;
  $xml -> loadXML($arbreXML);
  // On passe en revue les domaines...
  $domaine_liste = $xml -> getElementsByTagName('domaine');
  $domaine_nb = $domaine_liste -> length;
  for($domaine_ordre=0; $domaine_ordre<$domaine_nb; $domaine_ordre++)
  {
    $domaine_xml  = $domaine_liste -> item($domaine_ordre);
    $domaine_code = $domaine_xml -> getAttribute('code');
    $domaine_ref  = $domaine_xml -> getAttribute('ref');
    $domaine_nom  = $domaine_xml -> getAttribute('nom');
    $DB_SQL = 'INSERT INTO sacoche_referentiel_domaine( matiere_id, niveau_id, domaine_ordre, domaine_code, domaine_ref, domaine_nom) '
            . 'VALUES                                 (:matiere_id,:niveau_id,:domaine_ordre,:domaine_code,:domaine_ref,:domaine_nom) ';
    $DB_VAR = array(
      ':matiere_id'    => $matiere_id,
      ':niveau_id'     => $niveau_id,
      ':domaine_ordre' => $domaine_ordre+1,
      ':domaine_code'  => $domaine_code,
      ':domaine_ref'   => $domaine_ref,
      ':domaine_nom'   => $domaine_nom,
    );
    DB::query(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL , $DB_VAR);
    $domaine_id = DB::getLastOid(SACOCHE_STRUCTURE_BD_NAME);
    // On passe en revue les thèmes du domaine...
    $theme_liste = $domaine_xml -> getElementsByTagName('theme');
    $theme_nb = $theme_liste -> length;
    for($theme_ordre=0; $theme_ordre<$theme_nb; $theme_ordre++)
    {
      $theme_xml = $theme_liste -> item($theme_ordre);
      $theme_ref = $theme_xml -> getAttribute('ref');
      $theme_nom = $theme_xml -> getAttribute('nom');
      $DB_SQL = 'INSERT INTO sacoche_referentiel_theme( domaine_id, theme_ordre, theme_ref, theme_nom) '
              . 'VALUES                               (:domaine_id,:theme_ordre,:theme_ref,:theme_nom) ';
      $DB_VAR = array(
        ':domaine_id'  => $domaine_id,
        ':theme_ordre' => $theme_ordre+1,
        ':theme_ref'   => $theme_ref,
        ':theme_nom'   => $theme_nom,
      );
      DB::query(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL , $DB_VAR);
      $theme_id = DB::getLastOid(SACOCHE_STRUCTURE_BD_NAME);
      // On passe en revue les items du thème...
      $item_liste = $theme_xml -> getElementsByTagName('item');
      $item_nb = $item_liste -> length;
      for($item_ordre=0; $item_ordre<$item_nb; $item_ordre++)
      {
        $item_xml    = $item_liste -> item($item_ordre);
        $item_ref    = $item_xml -> getAttribute('ref');
        $item_nom    = $item_xml -> getAttribute('nom');
        $item_abrev  = $item_xml -> getAttribute('abrev');
        $item_coef   = $item_xml -> getAttribute('coef');
        $item_cart   = $item_xml -> getAttribute('cart');
        $item_lien   = $item_xml -> getAttribute('lien');
        $item_module = $item_xml -> getAttribute('module');
        $item_comm   = $item_xml -> getAttribute('comm');
        $DB_SQL = 'INSERT INTO sacoche_referentiel_item( theme_id, item_ordre, item_ref, item_nom, item_abrev, item_coef, item_cart, item_lien, item_module, item_comm) '
                . 'VALUES                              (:theme_id,:item_ordre,:item_ref,:item_nom,:item_abrev,:item_coef,:item_cart,:item_lien,:item_module,:item_comm) ';
        $DB_VAR = array(
          ':theme_id'    => $theme_id,
          ':item_ordre'  => $item_ordre,
          ':item_ref'    => $item_ref,
          ':item_nom'    => $item_nom,
          ':item_abrev'  => $item_abrev,
          ':item_coef'   => $item_coef,
          ':item_cart'   => $item_cart,
          ':item_lien'   => $item_lien,
          ':item_module' => $item_module,
          ':item_comm'   => $item_comm,
        );
        DB::query(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL , $DB_VAR);
        $item_id = DB::getLastOid(SACOCHE_STRUCTURE_BD_NAME);
        // On passe en revue les liaisons au socle 2016 des items...
        $socle_liste = $item_xml -> getElementsByTagName('socle');
        $socle_nb = $socle_liste -> length;
        for($socle_ordre=0; $socle_ordre<$socle_nb; $socle_ordre++)
        {
          $socle_xml           = $socle_liste -> item($socle_ordre);
          $socle_cycle_id      = $socle_xml -> getAttribute('cycle');
          $socle_composante_id = $socle_xml -> getAttribute('composante');
          $DB_SQL = 'INSERT INTO sacoche_jointure_referentiel_socle( item_id, socle_cycle_id, socle_composante_id) '
                  . 'VALUES                                        (:item_id,:socle_cycle_id,:socle_composante_id) ';
          $DB_VAR = array(
            ':item_id'             => $item_id,
            ':socle_cycle_id'      => $socle_cycle_id,
            ':socle_composante_id' => $socle_composante_id,
          );
          DB::query(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL , $DB_VAR);
        }
      }
    }
  }
}

/**
 * Modifier les caractéristiques d’un référentiel (juste ses paramètres, pas le contenu de son arborescence)
 *
 * @param int    $matiere_id
 * @param int    $niveau_id
 * @param array  array(':partage_etat'=>$val, ':partage_date'=>$val , ':calcul_methode'=>$val , ':calcul_limite'=>$val , ':calcul_retroactif'=>$val , ':mode_synthese'=>$val , ':information'=>$information );
 * @return int   nb de lignes modifiées (0|1)
 */
public static function DB_modifier_referentiel( $matiere_id , $niveau_id , $DB_VAR )
{
  $tab_set = array();
  foreach($DB_VAR as $key => $val)
  {
    $tab_set[] = 'referentiel_'.substr($key,1).'='.$key; // Par exemple : referentiel_partage_etat=:partage_etat
  }
  $DB_SQL = 'UPDATE sacoche_referentiel '
          . 'SET '.implode(', ',$tab_set).' '
          . 'WHERE matiere_id=:matiere_id AND niveau_id=:niveau_id ';
  $DB_VAR[':matiere_id'] = $matiere_id;
  $DB_VAR[':niveau_id'] = $niveau_id;
  DB::query(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL , $DB_VAR);
  return DB::rowCount(SACOCHE_STRUCTURE_BD_NAME);
}

/**
 * Modifier les caractéristiques d’un domaine d’un référentiel
 *
 * @param int    $domaine_id
 * @param string $domaine_code
 * @param string $domaine_ref
 * @param string $domaine_nom
 * @return bool  si ligne modifiée
 */
public static function DB_modifier_referentiel_domaine( $domaine_id , $domaine_code , $domaine_ref , $domaine_nom )
{
  $DB_SQL = 'UPDATE sacoche_referentiel_domaine '
          . 'SET domaine_code=:domaine_code, domaine_ref=:domaine_ref, domaine_nom=:domaine_nom '
          . 'WHERE domaine_id=:domaine_id ';
  $DB_VAR = array(
    ':domaine_id'   => $domaine_id,
    ':domaine_code' => $domaine_code,
    ':domaine_ref'  => $domaine_ref,
    ':domaine_nom'  => $domaine_nom,
  );
  DB::query(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL , $DB_VAR);
  return (bool)DB::rowCount(SACOCHE_STRUCTURE_BD_NAME);
}

/**
 * Modifier les caractéristiques d’un thème d’un référentiel
 *
 * @param int    $theme_id
 * @param string $theme_ref
 * @param string $theme_ref
 * @param string $theme_nom
 * @return bool  si ligne modifiée
 */
public static function DB_modifier_referentiel_theme( $theme_id , $theme_ref , $theme_nom )
{
  $DB_SQL = 'UPDATE sacoche_referentiel_theme '
          . 'SET theme_ref=:theme_ref, theme_nom=:theme_nom '
          . 'WHERE theme_id=:theme_id ';
  $DB_VAR = array(
    ':theme_id'  => $theme_id,
    ':theme_ref' => $theme_ref,
    ':theme_nom' => $theme_nom,
  );
  DB::query(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL , $DB_VAR);
  return (bool)DB::rowCount(SACOCHE_STRUCTURE_BD_NAME);
}

/**
 * Modifier les caractéristiques d’un item d’un référentiel (hors déplacement ; le lien de ressources est modifié ailleurs)
 *
 * @param int    $item_id
 * @param string $item_ref
 * @param string $item_nom
 * @param string $item_abrev
 * @param int    $item_coef
 * @param int    $item_cart
 * @param string $item_module
 * @param string $item_comm
 * @param array  $tab_socle2016
 * @return bool  si ligne(s) modifiée(s)
 */
public static function DB_modifier_referentiel_item( $item_id , $item_ref , $item_nom , $item_abrev , $item_coef , $item_cart , $item_module , $item_comm , $tab_socle2016 )
{
  // On récupère les liaisons au socle pour comparer
  $DB_SQL = 'UPDATE sacoche_referentiel_item '
          . 'SET item_ref=:item_ref, item_nom=:item_nom, item_abrev=:item_abrev, item_coef=:item_coef, item_cart=:item_cart, item_module=:item_module, item_comm=:item_comm '
          . 'WHERE item_id=:item_id ';
  $DB_VAR = array(
    ':item_id'     => $item_id,
    ':item_ref'    => $item_ref,
    ':item_nom'    => $item_nom,
    ':item_abrev'  => $item_abrev,
    ':item_coef'   => $item_coef,
    ':item_cart'   => $item_cart,
    ':item_module' => $item_module,
    ':item_comm'   => $item_comm,
  );
  DB::query(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL , $DB_VAR);
  $nb_modifs_item = DB::rowCount(SACOCHE_STRUCTURE_BD_NAME);
  $nb_modifs_socle = DB_STRUCTURE_REFERENTIEL::DB_modifier_liaison_item_socle2016( $item_id , $tab_socle2016 , 'modifier' );
  return $nb_modifs_item || $nb_modifs_socle ;
}

/**
 * modifier_liaison_item_socle2016
 *
 * @param int    $item_id
 * @param array  $tab_liaisons   tableau [socle_id]
 * @param string $mode        'ajouter' | 'modifier'
 * @return bool  si ligne(s) modifiée(s)
 */
public static function DB_modifier_liaison_item_socle2016( $item_id , $tab_liaisons , $mode )
{
  // Ajout
  if($mode=='ajouter')
  {
    if(empty($tab_liaisons))
    {
      return FALSE;
    }
    $DB_SQL = 'INSERT INTO sacoche_jointure_referentiel_socle( item_id, socle_cycle_id, socle_composante_id) '
            . 'VALUES                                        (:item_id,:socle_cycle_id,:socle_composante_id)';
    foreach($tab_liaisons as $socle_id)
    {
      $cycle_id      = floor($socle_id/100);
      $composante_id = $socle_id - $cycle_id*100;
      $DB_VAR = array(
        ':item_id'             => $item_id,
        ':socle_cycle_id'      => $cycle_id,
        ':socle_composante_id' => $composante_id,
      );
      DB::query(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL , $DB_VAR);
    }
    return TRUE;
  }
  // Modif
  elseif($mode=='modifier')
  {
    // On récupère la liste des liaisons déjà présentes
    $DB_SQL = 'SELECT CONCAT(socle_cycle_id,socle_composante_id) AS socle_id '
            . 'FROM sacoche_jointure_referentiel_socle '
            . 'WHERE item_id=:item_id ';
    $DB_VAR = array(':item_id'=>$item_id);
    $tab_old_liaisons = DB::queryCol(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL , $DB_VAR);
    // On compare
    $tab_liaisons_a_ajouter = array_diff( $tab_liaisons , $tab_old_liaisons );
    $tab_liaisons_a_retirer = array_diff( $tab_old_liaisons , $tab_liaisons );
    // On ajoute si besoin
    $nb_ajouts = count($tab_liaisons_a_ajouter);
    if($nb_ajouts)
    {
      $DB_SQL = 'INSERT INTO sacoche_jointure_referentiel_socle( item_id, socle_cycle_id, socle_composante_id) '
              . 'VALUES                                        (:item_id,:socle_cycle_id,:socle_composante_id)';
      foreach($tab_liaisons_a_ajouter as $socle_id)
      {
        $cycle_id      = floor($socle_id/100);
        $composante_id = $socle_id - $cycle_id*100;
        $DB_VAR = array(
          ':item_id'             => $item_id,
          ':socle_cycle_id'      => $cycle_id,
          ':socle_composante_id' => $composante_id,
        );
        DB::query(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL , $DB_VAR);
      }
    }
    // On retire si besoin
    $nb_retraits = count($tab_liaisons_a_retirer);
    if($nb_retraits)
    {
      $DB_SQL = 'DELETE FROM sacoche_jointure_referentiel_socle '
              . 'WHERE item_id=:item_id AND socle_cycle_id=:socle_cycle_id AND socle_composante_id=:socle_composante_id ';
      foreach($tab_liaisons_a_retirer as $socle_id)
      {
        $cycle_id      = floor($socle_id/100);
        $composante_id = $socle_id - $cycle_id*100;
        $DB_VAR = array(
          ':item_id'             => $item_id,
          ':socle_cycle_id'      => $cycle_id,
          ':socle_composante_id' => $composante_id,
        );
        DB::query(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL , $DB_VAR);
      }
    }
    return $nb_ajouts || $nb_retraits;
  }
}

/**
 * Modifier une propriété d’un ensemble d’items de référentiel
 *
 * @param string $granulosite   'referentiel' | 'domaine' | 'theme' | 'item'
 * @param int    $matiere_id    inutile si granulosité au niveau de l’item
 * @param int    $objet_id       id niveau | domaine | thème | item
 * @param string $element        'coef' | 'cart' | 'socle2016'
 * @param float  $valeur         0~100  | 0~1    | 211~454
 * @param int    $mode           NULL    | NULL   | 0~1
 * @return int   nb de lignes modifiées
 */
public static function DB_modifier_referentiel_items( $granulosite , $matiere_id , $objet_id , $element , $valeur , $mode=NULL )
{
  // Lever si besoin une limitation de GROUP_CONCAT (group_concat_max_len est par défaut limité à une chaîne de 1024 caractères) ; éviter plus de 8096 (http://www.glpi-project.org/forum/viewtopic.php?id=23767).
  DB::query(SACOCHE_STRUCTURE_BD_NAME , 'SET group_concat_max_len = 8096');
  // Lister les items concernés
  if($granulosite=='item')
  {
    $listing_item_id = $objet_id;
  }
  else
  {
    $DB_SQL = 'SELECT GROUP_CONCAT(item_id SEPARATOR ",") AS listing_item_id ';
    if($granulosite=='referentiel')
    {
      $DB_SQL.= 'FROM sacoche_referentiel_domaine '
              . 'LEFT JOIN sacoche_referentiel_theme USING (domaine_id) '
              . 'LEFT JOIN sacoche_referentiel_item USING (theme_id) '
              . 'WHERE matiere_id=:matiere_id AND niveau_id=:objet_id';
    }
    elseif($granulosite=='domaine')
    {
      $DB_SQL.= 'FROM sacoche_referentiel_theme '
              . 'LEFT JOIN sacoche_referentiel_item USING (theme_id) '
              . 'WHERE domaine_id=:objet_id';
    }
    elseif($granulosite=='theme')
    {
      $DB_SQL.= 'FROM sacoche_referentiel_item '
              . 'WHERE theme_id=:objet_id';
    }
    $DB_VAR = array(
      ':matiere_id' => $matiere_id,
      ':objet_id'   => $objet_id,
    );
    $listing_item_id = DB::queryOne(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL , $DB_VAR);
    // Mettre à jour les items concernés
    if(!$listing_item_id)
    {
      return 0;
    }
  }
  if( ($element=='coef') || ($element=='cart') )
  {
    $DB_SQL = 'UPDATE sacoche_referentiel_item '
            . 'SET item_'.$element.'=:valeur '
            . 'WHERE item_id IN('.$listing_item_id.') ';
    $DB_VAR = array(':valeur'=>$valeur);
    DB::query(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL , $DB_VAR);
  }
  elseif($element=='socle2016')
  {
    $cycle_id      = floor($valeur/100);
    $composante_id = $valeur - $cycle_id*100;
    $DB_VAR = array(
      ':socle_cycle_id'      => $cycle_id,
      ':socle_composante_id' => $composante_id,
    );
    // retirer des liaisons
    if($mode==0)
    {
      $DB_SQL = 'DELETE FROM sacoche_jointure_referentiel_socle '
              . 'WHERE item_id IN('.$listing_item_id.') AND socle_cycle_id=:socle_cycle_id AND socle_composante_id=:socle_composante_id ';
    }
    // ajouter des liaisons
    else if($mode==1)
    {
      $tab_values = array();
      $tab_items = explode( ',' , $listing_item_id );
      foreach($tab_items as $item_id)
      {
        $tab_values[] = '('.$item_id.',:socle_cycle_id,:socle_composante_id)';
      }
      // INSERT IGNORE est moins bien pour la gestion d’erreurs, mais mieux ici qu’un REPLACE qui effectue DELETE + INSERT donc renvoie toujours des modifs même si au final il n’y en a pas eu...
      $DB_SQL = 'INSERT IGNORE INTO sacoche_jointure_referentiel_socle( item_id, socle_cycle_id, socle_composante_id) '
              . 'VALUES '.implode( ',' , $tab_values );
    }
    DB::query(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL , $DB_VAR);
  }
  return DB::rowCount(SACOCHE_STRUCTURE_BD_NAME);
}

/**
 * Modifier le lien de vers des ressources d’un item d’un référentiel
 *
 * @param int    $item_id
 * @param string $item_lien
 * @return void
 */
public static function DB_modifier_referentiel_lien_ressources( $item_id , $item_lien )
{
  $DB_SQL = 'UPDATE sacoche_referentiel_item '
          . 'SET item_lien=:item_lien '
          . 'WHERE item_id=:item_id ';
  $DB_VAR = array(
    ':item_id'   => $item_id,
    ':item_lien' => $item_lien,
  );
  DB::query(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL , $DB_VAR);
}

/**
 * Modifier le nb de demandes d’évaluations autorisées pour les référentiels d’une matière donnée
 *
 * @param int    $matiere_id
 * @param int    $matiere_nb_demandes
 * @return void
 */
public static function DB_modifier_matiere_nb_demandes( $matiere_id , $matiere_nb_demandes )
{
  $DB_SQL = 'UPDATE sacoche_matiere '
          . 'SET matiere_nb_demandes=:matiere_nb_demandes '
          . 'WHERE matiere_id=:matiere_id ';
  $DB_VAR = array(
    ':matiere_id'          => $matiere_id,
    ':matiere_nb_demandes' => $matiere_nb_demandes,
  );
  DB::query(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL , $DB_VAR);
}

/**
 * Supprimer un référentiel (avec son contenu et ce qui en dépend)
 *
 * Le ménage dans sacoche_livret_jointure_referentiel est un peu pénible ici, il est effectué ailleurs.
 *
 * @param int    $matiere_id
 * @param int    $niveau_id
 * @return void
 */
public static function DB_supprimer_referentiel_matiere_niveau( $matiere_id , $niveau_id )
{
  $DB_SQL = 'DELETE sacoche_referentiel, sacoche_referentiel_domaine, sacoche_referentiel_theme, sacoche_referentiel_item, sacoche_jointure_referentiel_socle, sacoche_jointure_devoir_item, sacoche_jointure_selection_item, sacoche_saisie, sacoche_demande '
          . 'FROM sacoche_referentiel '
          . 'LEFT JOIN sacoche_referentiel_domaine USING (matiere_id,niveau_id) '
          . 'LEFT JOIN sacoche_referentiel_theme USING (domaine_id) '
          . 'LEFT JOIN sacoche_referentiel_item USING (theme_id) '
          . 'LEFT JOIN sacoche_jointure_referentiel_socle USING (item_id) '
          . 'LEFT JOIN sacoche_jointure_devoir_item USING (item_id) '
          . 'LEFT JOIN sacoche_jointure_selection_item USING (item_id) '
          . 'LEFT JOIN sacoche_saisie USING (item_id) '
          . 'LEFT JOIN sacoche_demande USING (matiere_id,item_id) '
          . 'WHERE sacoche_referentiel.matiere_id=:matiere_id AND sacoche_referentiel.niveau_id=:niveau_id ';
  $DB_VAR = array(
    ':matiere_id' => $matiere_id,
    ':niveau_id'  => $niveau_id,
  );
  DB::query(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL , $DB_VAR);
}

/**
 * Supprimer un domaine d’un référentiel (avec son contenu et ce qui en dépend)
 *
 * Le ménage dans sacoche_livret_jointure_referentiel est un peu pénible ici, il est effectué ailleurs.
 *
 * @param int    $domaine_id
 * @return int
 */
public static function DB_supprimer_referentiel_domaine($domaine_id)
{
  $DB_SQL = 'DELETE sacoche_referentiel_domaine, sacoche_referentiel_theme, sacoche_referentiel_item, sacoche_jointure_referentiel_socle, sacoche_jointure_devoir_item, sacoche_jointure_selection_item, sacoche_saisie, sacoche_demande '
          . 'FROM sacoche_referentiel_domaine '
          . 'LEFT JOIN sacoche_referentiel_theme USING (domaine_id) '
          . 'LEFT JOIN sacoche_referentiel_item USING (theme_id) '
          . 'LEFT JOIN sacoche_jointure_referentiel_socle USING (item_id) '
          . 'LEFT JOIN sacoche_jointure_devoir_item USING (item_id) '
          . 'LEFT JOIN sacoche_jointure_selection_item USING (item_id) '
          . 'LEFT JOIN sacoche_saisie USING (item_id) '
          . 'LEFT JOIN sacoche_demande USING (item_id) '
          . 'WHERE sacoche_referentiel_domaine.domaine_id=:domaine_id';
  $DB_VAR = array(':domaine_id'=>$domaine_id);
  DB::query(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL , $DB_VAR);
  return DB::rowCount(SACOCHE_STRUCTURE_BD_NAME);  // Est censé renvoyer le nb de lignes supprimées ; à cause du multi-tables curieusement ça renvoie 2, même pour un élément non lié
}

/**
 * Supprimer un thème d’un référentiel (avec son contenu et ce qui en dépend)
 *
 * Le ménage dans sacoche_livret_jointure_referentiel est un peu pénible ici, il est effectué ailleurs.
 *
 * @param int    $theme_id
 * @return int
 */
public static function DB_supprimer_referentiel_theme($theme_id)
{
  $DB_SQL = 'DELETE sacoche_referentiel_theme, sacoche_referentiel_item, sacoche_jointure_referentiel_socle, sacoche_jointure_devoir_item, sacoche_jointure_selection_item, sacoche_saisie, sacoche_demande '
          . 'FROM sacoche_referentiel_theme '
          . 'LEFT JOIN sacoche_referentiel_item USING (theme_id) '
          . 'LEFT JOIN sacoche_jointure_referentiel_socle USING (item_id) '
          . 'LEFT JOIN sacoche_jointure_devoir_item USING (item_id) '
          . 'LEFT JOIN sacoche_jointure_selection_item USING (item_id) '
          . 'LEFT JOIN sacoche_saisie USING (item_id) '
          . 'LEFT JOIN sacoche_demande USING (item_id) '
          . 'WHERE sacoche_referentiel_theme.theme_id=:theme_id';
  $DB_VAR = array(':theme_id'=>$theme_id);
  DB::query(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL , $DB_VAR);
  return DB::rowCount(SACOCHE_STRUCTURE_BD_NAME);  // Est censé renvoyer le nb de lignes supprimées ; à cause du multi-tables curieusement ça renvoie 2, même pour un élément non lié
}

/**
 * Supprimer un item et les demandes d’évaluations associées
 *
 * On ne supprime aussi les jointures aux devoirs ni les saisies que s’il ne s’agit pas d’une fusion.
 *
 * Le ménage dans sacoche_livret_jointure_referentiel est un peu pénible ici, il est effectué ailleurs.
 *
 * @param int    $item_id
 * @param bool   $with_notes   TRUE par défaut, FALSE dans le cas d’une fusion d’items (étudié ensuite par une autre fonction)
 * @return int
 */
public static function DB_supprimer_referentiel_item( $item_id , $with_notes=TRUE )
{
  // Dans le cas d’une fusion, PAS ENCORE les jointures aux devoirs ni les saisies
  $delete_notes = ($with_notes) ? ', sacoche_jointure_devoir_item, sacoche_saisie ' : '' ;
  $join_item    = ($with_notes) ? 'LEFT JOIN sacoche_jointure_devoir_item USING (item_id) ' : '' ;
  $join_saisie  = ($with_notes) ? 'LEFT JOIN sacoche_saisie USING (item_id) ' : ''  ;
  // Supprimer l’item et les demandes d’évaluations associées et les liaisons au socle associées
  $DB_SQL = 'DELETE sacoche_referentiel_item, sacoche_jointure_referentiel_socle, sacoche_demande, sacoche_jointure_selection_item '
          . $delete_notes
          . 'FROM sacoche_referentiel_item '
          . 'LEFT JOIN sacoche_jointure_referentiel_socle USING (item_id) '
          . 'LEFT JOIN sacoche_jointure_selection_item USING (item_id) '
          . $join_item
          . $join_saisie
          . 'LEFT JOIN sacoche_demande USING (item_id) '
          . 'WHERE item_id=:item_id';
  $DB_VAR = array(':item_id'=>$item_id);
  DB::query(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL , $DB_VAR);
  // Est censé renvoyer le nb de lignes supprimées ; à cause du multi-tables curieusement ça renvoie 2, même pour un élément non lié
  return DB::rowCount(SACOCHE_STRUCTURE_BD_NAME);
}

/**
 * Fusionner deux items
 *
 * L’item à fusionner a déjà été supprimé, et la partie concernée du référentiel a déjà été renumérotée.
 * Il reste à étudier les jointures aux devoirs et les saisies.
 *
 * @param int    $item_id_degageant
 * @param int    $item_id_absorbant
 * @return void
 */
public static function DB_fusionner_referentiel_items( $item_id_degageant , $item_id_absorbant )
{
  $DB_VAR = array(
    ':item_id_degageant' => $item_id_degageant,
    ':item_id_absorbant' => $item_id_absorbant,
  );
  // Dans le cas où les deux items ont été évalués dans une même évaluation, on est obligé de supprimer l’un des scores
  // On doit donc commencer par chercher les conflits possibles de clefs multiples pour éviter un erreur lors de l’UPDATE
  //
  // TABLE sacoche_jointure_devoir_item
  //
  $DB_SQL = 'SELECT devoir_id '
          . 'FROM sacoche_jointure_devoir_item '
          . 'WHERE item_id=:item_id_degageant';
  $COL1 = DB::queryCol(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL , $DB_VAR);
  $DB_SQL = 'SELECT devoir_id '
          . 'FROM sacoche_jointure_devoir_item '
          . 'WHERE item_id=:item_id_absorbant';
  $COL2 = DB::queryCol(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL , $DB_VAR);
  $tab_conflit = array_intersect($COL1,$COL2);
  if(count($tab_conflit))
  {
    $DB_SQL = 'DELETE FROM sacoche_jointure_devoir_item '
            . 'WHERE devoir_id=:devoir_id AND item_id=:item_id_degageant ';
    foreach($tab_conflit as $devoir_id)
    {
      $DB_VAR[':devoir_id'] = $devoir_id;
      DB::query(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL , $DB_VAR);
    }
  }
  $DB_SQL = 'UPDATE sacoche_jointure_devoir_item '
          . 'SET item_id=:item_id_absorbant '
          . 'WHERE item_id=:item_id_degageant';
  DB::query(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL , $DB_VAR);
  //
  // TABLE sacoche_saisie
  //
  $DB_SQL = 'SELECT CONCAT(eleve_id,"x",devoir_id) AS clefs '
          . 'FROM sacoche_saisie '
          . 'WHERE item_id=:item_id_degageant';
  $COL1 = DB::queryCol(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL , $DB_VAR);
  $DB_SQL = 'SELECT CONCAT(eleve_id,"x",devoir_id) AS clefs '
          . 'FROM sacoche_saisie '
          . 'WHERE item_id=:item_id_absorbant';
  $COL2 = DB::queryCol(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL , $DB_VAR);
  $tab_conflit = array_intersect($COL1,$COL2);
  if(count($tab_conflit))
  {
    $DB_SQL_select_note = 'SELECT saisie_note ';
    $DB_SQL_select_note.= 'FROM sacoche_saisie ';
    $DB_SQL_select_note.= 'WHERE eleve_id=:eleve_id AND devoir_id=:devoir_id AND item_id IN(:item_id_absorbant,:item_id_degageant) ';
    $DB_SQL_update_note = 'UPDATE sacoche_saisie ';
    $DB_SQL_update_note.= 'SET saisie_note=:saisie_note ';
    $DB_SQL_update_note.= 'WHERE eleve_id=:eleve_id AND devoir_id=:devoir_id AND item_id=:item_id_absorbant ';
    $DB_SQL = 'DELETE FROM sacoche_saisie '
            . 'WHERE eleve_id=:eleve_id AND devoir_id=:devoir_id AND item_id=:item_id_degageant ';
    foreach($tab_conflit as $ids)
    {
      list($eleve_id,$devoir_id) = explode('x',$ids);
      $DB_VAR[':eleve_id']  = $eleve_id;
      $DB_VAR[':devoir_id'] = $devoir_id;
      // On applique une note "moyenne" ou favorable
      $DB_COL = DB::queryCol(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL_select_note , $DB_VAR);
      if( is_numeric($DB_COL[0]) && is_numeric($DB_COL[1]) && ($DB_COL[0]!=$DB_COL[1]) )
      {
        $DB_VAR[':saisie_note'] = ceil( ($DB_COL[0]+$DB_COL[1])/2 );
        DB::query(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL_update_note , $DB_VAR);
      }
      else if( is_numeric($DB_COL[0]) && !is_numeric($DB_COL[1]) )
      {
        $DB_VAR[':saisie_note'] = (int)$DB_COL[0];
        DB::query(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL_update_note , $DB_VAR);
      }
      else if( is_numeric($DB_COL[1]) && !is_numeric($DB_COL[0]) )
      {
        $DB_VAR[':saisie_note'] = (int)$DB_COL[1];
        DB::query(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL_update_note , $DB_VAR);
      }
      DB::query(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL , $DB_VAR);
    }
  }
  $DB_SQL = 'UPDATE sacoche_saisie '
          . 'SET item_id=:item_id_absorbant '
          . 'WHERE item_id=:item_id_degageant';
  DB::query(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL , $DB_VAR);
}

/**
 * Incrémenter ou décrémenter dans un référentiel le numéro d’ordre d’une liste d’éléments précis
 *
 * Problème : si item_ordre = 0 ça plante quand même ("Numeric value out of range: 1690 BIGINT UNSIGNED value is out of range")
 * comme si SQL affectait item_ordre-1 (sans juste le calculer) pour en connaitre le résultat afin de le comparer à l’autre valeur.
 *
 * @param string   $element_champ 'domaine' | 'theme' | 'item'
 * @param array    $tab_elements_id
 * @param string   $operation     '+1' | '-1' 
 * @return void
 */
public static function DB_renumeroter_referentiel_liste_elements( $element_champ , $tab_elements_id , $operation )
{
  $min_value = ($element_champ=='item') ? 0 : 1 ;
  $listing_elements_id = implode(',',$tab_elements_id);
  $DB_SQL = 'UPDATE sacoche_referentiel_'.$element_champ.' '
          . 'SET '.$element_champ.'_ordre = GREATEST( '.$element_champ.'_ordre'.$operation.' , '.$min_value.' ) ' // GREATEST() car il arrive qu’il y ait des pb de numérotation et affecter -1 plante
          . 'WHERE '.$element_champ.'_id IN('.$listing_elements_id.') ';
  DB::query(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL);
}

/**
 * Décrémenter les domaines suivants le n° indiqué
 *
 * @param int   matiere_id
 * @param int   niveau_id
 * @param int   ordre_id
 * @return void
 */
public static function DB_renumeroter_referentiel_domaines_suivants( $matiere_id , $niveau_id , $ordre_id )
{
  $DB_SQL = 'UPDATE sacoche_referentiel_domaine '
          . 'SET domaine_ordre = GREATEST( domaine_ordre-1 , 1 ) ' // GREATEST() car il arrive qu’il y ait des pb de numérotation et affecter -1 plante
          . 'WHERE matiere_id=:matiere_id AND niveau_id=:niveau_id AND domaine_ordre>:ordre_id ';
  $DB_VAR = array(
    ':matiere_id' => $matiere_id,
    ':niveau_id'  => $niveau_id,
    ':ordre_id'   => $ordre_id,
  );
  DB::query(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL , $DB_VAR);
}

/**
 * Décrémenter les thèmes suivants le n° indiqué
 *
 * @param int   domaine_id
 * @param int   ordre_id
 * @return void
 */
public static function DB_renumeroter_referentiel_themes_suivants( $domaine_id , $ordre_id )
{
  $DB_SQL = 'UPDATE sacoche_referentiel_theme '
          . 'SET theme_ordre = GREATEST( theme_ordre-1 , 1 ) ' // GREATEST() car il arrive qu’il y ait des pb de numérotation et affecter -1 plante
          . 'WHERE domaine_id=:domaine_id AND theme_ordre>:ordre_id ';
  $DB_VAR = array(
    ':domaine_id' => $domaine_id,
    ':ordre_id'   => $ordre_id,
  );
  DB::query(SACOCHE_STRUCTURE_BD_NAME , $DB_SQL , $DB_VAR);
}

}
?>