/**
 * @version $Id$
 * @author Thomas Crespin <thomas.crespin@sesamath.net>
 * @copyright Thomas Crespin 2009-2022
 * 
 * ****************************************************************************************************
 * SACoche <http://sacoche.sesamath.net> - Suivi d’Acquisitions de Compétences
 * © Thomas Crespin pour Sésamath <http://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/>.
 * 
 */

// Activer le mode strict afin de tendre vers un code rigoureux
'use strict';

/**
 * Ajout de la méthode trim() pour les navigateurs embarquant un javascript de version < 1.8.1 (année 2010 environ).
 *
 * @see https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux/String/Trim
 * @see http://www.w3schools.com/jsref/jsref_trim_string.asp
 * @see https://caniuse.com/#feat=mdn-javascript_builtins_string_trim
 *
 * @param string
 * @return string
 */
if(!String.prototype.trim)
{
  String.prototype.trim = function()
  {
    return this.replace(/^\s+|\s+$/gm,'');
  };
}

/**
 * Ajout de la méthode repeat() pour les vieux navigateurs (le support date de 2015 environ).
 *
 * @see https://caniuse.com/#feat=mdn-javascript_builtins_string_repeat
 * @see https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux/String/repeat
 * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/repeat
 *
 * @param count
 * @return string
 */
if(!String.prototype.repeat)
{
  String.prototype.repeat = function(count)
  {
    if (this == null) return '';
    var string = '' + this; // convertir en chaîne
    count = +count; // convertir en nombre
    if (count != count) return ''; // isNaN(count)
    if (count == Infinity) return '';
    count = Math.floor(count);
    if (count<=0) return '';
    while (count)
    {
      string += string;
      count--;
    }
    return string;
  }
}

/**
 * Ajout de la méthode replaceAll() pour remplacer plusieurs occurences dans une chaine.
 * Début d’implémentation dans les navigateurs en 2020.
 * Attention si search contient des caractères réservés aux expressions régulières ( $ * + ? . ^ \ {i} [xyz] ): il faut les échapper.
 *
 * @see http://stackoverflow.com/questions/1144783/replacing-all-occurrences-of-a-string-in-javascript
 * @see http://javascript.developpez.com/sources/?page=tips#replaceall
 * @see https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Global_Objects/String/replaceAll
 * @see https://caniuse.com/#search=replaceAll
 *
 * @param search
 * @param replacement
 * @return string
 */
if(!String.prototype.replaceAll)
{
  String.prototype.replaceAll = function(search, replacement)
  {
    return this.replace(new RegExp(search, 'g'), replacement);
  };
}

/**
 * Ajout de la méthode latinize() pour retirer les accents dans une chaine.
 * Utilisé pour les filtres de recherche
 *
 * @see https://stackoverflow.com/questions/990904/remove-accents-diacritics-in-a-string-in-javascript/5912746#5912746
 * @see https://www.npmjs.com/package/latinize?activeTab=code
 * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/normalize
 *
 * @param string
 * @return string
 */
// 
var Latinise={};Latinise.latin_map={"Á":"A","Ă":"A","Ắ":"A","Ặ":"A","Ằ":"A","Ẳ":"A","Ẵ":"A","Ǎ":"A","Â":"A","Ấ":"A","Ậ":"A","Ầ":"A","Ẩ":"A","Ẫ":"A","Ä":"A","Ǟ":"A","Ȧ":"A","Ǡ":"A","Ạ":"A","Ȁ":"A","À":"A","Ả":"A","Ȃ":"A","Ā":"A","Ą":"A","Å":"A","Ǻ":"A","Ḁ":"A","Ⱥ":"A","Ã":"A","Ꜳ":"AA","Æ":"AE","Ǽ":"AE","Ǣ":"AE","Ꜵ":"AO","Ꜷ":"AU","Ꜹ":"AV","Ꜻ":"AV","Ꜽ":"AY","Ḃ":"B","Ḅ":"B","Ɓ":"B","Ḇ":"B","Ƀ":"B","Ƃ":"B","Ć":"C","Č":"C","Ç":"C","Ḉ":"C","Ĉ":"C","Ċ":"C","Ƈ":"C","Ȼ":"C","Ď":"D","Ḑ":"D","Ḓ":"D","Ḋ":"D","Ḍ":"D","Ɗ":"D","Ḏ":"D","ǲ":"D","ǅ":"D","Đ":"D","Ƌ":"D","Ǳ":"DZ","Ǆ":"DZ","É":"E","Ĕ":"E","Ě":"E","Ȩ":"E","Ḝ":"E","Ê":"E","Ế":"E","Ệ":"E","Ề":"E","Ể":"E","Ễ":"E","Ḙ":"E","Ë":"E","Ė":"E","Ẹ":"E","Ȅ":"E","È":"E","Ẻ":"E","Ȇ":"E","Ē":"E","Ḗ":"E","Ḕ":"E","Ę":"E","Ɇ":"E","Ẽ":"E","Ḛ":"E","Ꝫ":"ET","Ḟ":"F","Ƒ":"F","Ǵ":"G","Ğ":"G","Ǧ":"G","Ģ":"G","Ĝ":"G","Ġ":"G","Ɠ":"G","Ḡ":"G","Ǥ":"G","Ḫ":"H","Ȟ":"H","Ḩ":"H","Ĥ":"H","Ⱨ":"H","Ḧ":"H","Ḣ":"H","Ḥ":"H","Ħ":"H","Í":"I","Ĭ":"I","Ǐ":"I","Î":"I","Ï":"I","Ḯ":"I","İ":"I","Ị":"I","Ȉ":"I","Ì":"I","Ỉ":"I","Ȋ":"I","Ī":"I","Į":"I","Ɨ":"I","Ĩ":"I","Ḭ":"I","Ꝺ":"D","Ꝼ":"F","Ᵹ":"G","Ꞃ":"R","Ꞅ":"S","Ꞇ":"T","Ꝭ":"IS","Ĵ":"J","Ɉ":"J","Ḱ":"K","Ǩ":"K","Ķ":"K","Ⱪ":"K","Ꝃ":"K","Ḳ":"K","Ƙ":"K","Ḵ":"K","Ꝁ":"K","Ꝅ":"K","Ĺ":"L","Ƚ":"L","Ľ":"L","Ļ":"L","Ḽ":"L","Ḷ":"L","Ḹ":"L","Ⱡ":"L","Ꝉ":"L","Ḻ":"L","Ŀ":"L","Ɫ":"L","ǈ":"L","Ł":"L","Ǉ":"LJ","Ḿ":"M","Ṁ":"M","Ṃ":"M","Ɱ":"M","Ń":"N","Ň":"N","Ņ":"N","Ṋ":"N","Ṅ":"N","Ṇ":"N","Ǹ":"N","Ɲ":"N","Ṉ":"N","Ƞ":"N","ǋ":"N","Ñ":"N","Ǌ":"NJ","Ó":"O","Ŏ":"O","Ǒ":"O","Ô":"O","Ố":"O","Ộ":"O","Ồ":"O","Ổ":"O","Ỗ":"O","Ö":"O","Ȫ":"O","Ȯ":"O","Ȱ":"O","Ọ":"O","Ő":"O","Ȍ":"O","Ò":"O","Ỏ":"O","Ơ":"O","Ớ":"O","Ợ":"O","Ờ":"O","Ở":"O","Ỡ":"O","Ȏ":"O","Ꝋ":"O","Ꝍ":"O","Ō":"O","Ṓ":"O","Ṑ":"O","Ɵ":"O","Ǫ":"O","Ǭ":"O","Ø":"O","Ǿ":"O","Õ":"O","Ṍ":"O","Ṏ":"O","Ȭ":"O","Ƣ":"OI","Ꝏ":"OO","Ɛ":"E","Ɔ":"O","Ȣ":"OU","Ṕ":"P","Ṗ":"P","Ꝓ":"P","Ƥ":"P","Ꝕ":"P","Ᵽ":"P","Ꝑ":"P","Ꝙ":"Q","Ꝗ":"Q","Ŕ":"R","Ř":"R","Ŗ":"R","Ṙ":"R","Ṛ":"R","Ṝ":"R","Ȑ":"R","Ȓ":"R","Ṟ":"R","Ɍ":"R","Ɽ":"R","Ꜿ":"C","Ǝ":"E","Ś":"S","Ṥ":"S","Š":"S","Ṧ":"S","Ş":"S","Ŝ":"S","Ș":"S","Ṡ":"S","Ṣ":"S","Ṩ":"S","Ť":"T","Ţ":"T","Ṱ":"T","Ț":"T","Ⱦ":"T","Ṫ":"T","Ṭ":"T","Ƭ":"T","Ṯ":"T","Ʈ":"T","Ŧ":"T","Ɐ":"A","Ꞁ":"L","Ɯ":"M","Ʌ":"V","Ꜩ":"TZ","Ú":"U","Ŭ":"U","Ǔ":"U","Û":"U","Ṷ":"U","Ü":"U","Ǘ":"U","Ǚ":"U","Ǜ":"U","Ǖ":"U","Ṳ":"U","Ụ":"U","Ű":"U","Ȕ":"U","Ù":"U","Ủ":"U","Ư":"U","Ứ":"U","Ự":"U","Ừ":"U","Ử":"U","Ữ":"U","Ȗ":"U","Ū":"U","Ṻ":"U","Ų":"U","Ů":"U","Ũ":"U","Ṹ":"U","Ṵ":"U","Ꝟ":"V","Ṿ":"V","Ʋ":"V","Ṽ":"V","Ꝡ":"VY","Ẃ":"W","Ŵ":"W","Ẅ":"W","Ẇ":"W","Ẉ":"W","Ẁ":"W","Ⱳ":"W","Ẍ":"X","Ẋ":"X","Ý":"Y","Ŷ":"Y","Ÿ":"Y","Ẏ":"Y","Ỵ":"Y","Ỳ":"Y","Ƴ":"Y","Ỷ":"Y","Ỿ":"Y","Ȳ":"Y","Ɏ":"Y","Ỹ":"Y","Ź":"Z","Ž":"Z","Ẑ":"Z","Ⱬ":"Z","Ż":"Z","Ẓ":"Z","Ȥ":"Z","Ẕ":"Z","Ƶ":"Z","Ĳ":"IJ","Œ":"OE","ᴀ":"A","ᴁ":"AE","ʙ":"B","ᴃ":"B","ᴄ":"C","ᴅ":"D","ᴇ":"E","ꜰ":"F","ɢ":"G","ʛ":"G","ʜ":"H","ɪ":"I","ʁ":"R","ᴊ":"J","ᴋ":"K","ʟ":"L","ᴌ":"L","ᴍ":"M","ɴ":"N","ᴏ":"O","ɶ":"OE","ᴐ":"O","ᴕ":"OU","ᴘ":"P","ʀ":"R","ᴎ":"N","ᴙ":"R","ꜱ":"S","ᴛ":"T","ⱻ":"E","ᴚ":"R","ᴜ":"U","ᴠ":"V","ᴡ":"W","ʏ":"Y","ᴢ":"Z","á":"a","ă":"a","ắ":"a","ặ":"a","ằ":"a","ẳ":"a","ẵ":"a","ǎ":"a","â":"a","ấ":"a","ậ":"a","ầ":"a","ẩ":"a","ẫ":"a","ä":"a","ǟ":"a","ȧ":"a","ǡ":"a","ạ":"a","ȁ":"a","à":"a","ả":"a","ȃ":"a","ā":"a","ą":"a","ᶏ":"a","ẚ":"a","å":"a","ǻ":"a","ḁ":"a","ⱥ":"a","ã":"a","ꜳ":"aa","æ":"ae","ǽ":"ae","ǣ":"ae","ꜵ":"ao","ꜷ":"au","ꜹ":"av","ꜻ":"av","ꜽ":"ay","ḃ":"b","ḅ":"b","ɓ":"b","ḇ":"b","ᵬ":"b","ᶀ":"b","ƀ":"b","ƃ":"b","ɵ":"o","ć":"c","č":"c","ç":"c","ḉ":"c","ĉ":"c","ɕ":"c","ċ":"c","ƈ":"c","ȼ":"c","ď":"d","ḑ":"d","ḓ":"d","ȡ":"d","ḋ":"d","ḍ":"d","ɗ":"d","ᶑ":"d","ḏ":"d","ᵭ":"d","ᶁ":"d","đ":"d","ɖ":"d","ƌ":"d","ı":"i","ȷ":"j","ɟ":"j","ʄ":"j","ǳ":"dz","ǆ":"dz","é":"e","ĕ":"e","ě":"e","ȩ":"e","ḝ":"e","ê":"e","ế":"e","ệ":"e","ề":"e","ể":"e","ễ":"e","ḙ":"e","ë":"e","ė":"e","ẹ":"e","ȅ":"e","è":"e","ẻ":"e","ȇ":"e","ē":"e","ḗ":"e","ḕ":"e","ⱸ":"e","ę":"e","ᶒ":"e","ɇ":"e","ẽ":"e","ḛ":"e","ꝫ":"et","ḟ":"f","ƒ":"f","ᵮ":"f","ᶂ":"f","ǵ":"g","ğ":"g","ǧ":"g","ģ":"g","ĝ":"g","ġ":"g","ɠ":"g","ḡ":"g","ᶃ":"g","ǥ":"g","ḫ":"h","ȟ":"h","ḩ":"h","ĥ":"h","ⱨ":"h","ḧ":"h","ḣ":"h","ḥ":"h","ɦ":"h","ẖ":"h","ħ":"h","ƕ":"hv","í":"i","ĭ":"i","ǐ":"i","î":"i","ï":"i","ḯ":"i","ị":"i","ȉ":"i","ì":"i","ỉ":"i","ȋ":"i","ī":"i","į":"i","ᶖ":"i","ɨ":"i","ĩ":"i","ḭ":"i","ꝺ":"d","ꝼ":"f","ᵹ":"g","ꞃ":"r","ꞅ":"s","ꞇ":"t","ꝭ":"is","ǰ":"j","ĵ":"j","ʝ":"j","ɉ":"j","ḱ":"k","ǩ":"k","ķ":"k","ⱪ":"k","ꝃ":"k","ḳ":"k","ƙ":"k","ḵ":"k","ᶄ":"k","ꝁ":"k","ꝅ":"k","ĺ":"l","ƚ":"l","ɬ":"l","ľ":"l","ļ":"l","ḽ":"l","ȴ":"l","ḷ":"l","ḹ":"l","ⱡ":"l","ꝉ":"l","ḻ":"l","ŀ":"l","ɫ":"l","ᶅ":"l","ɭ":"l","ł":"l","ǉ":"lj","ſ":"s","ẜ":"s","ẛ":"s","ẝ":"s","ḿ":"m","ṁ":"m","ṃ":"m","ɱ":"m","ᵯ":"m","ᶆ":"m","ń":"n","ň":"n","ņ":"n","ṋ":"n","ȵ":"n","ṅ":"n","ṇ":"n","ǹ":"n","ɲ":"n","ṉ":"n","ƞ":"n","ᵰ":"n","ᶇ":"n","ɳ":"n","ñ":"n","ǌ":"nj","ó":"o","ŏ":"o","ǒ":"o","ô":"o","ố":"o","ộ":"o","ồ":"o","ổ":"o","ỗ":"o","ö":"o","ȫ":"o","ȯ":"o","ȱ":"o","ọ":"o","ő":"o","ȍ":"o","ò":"o","ỏ":"o","ơ":"o","ớ":"o","ợ":"o","ờ":"o","ở":"o","ỡ":"o","ȏ":"o","ꝋ":"o","ꝍ":"o","ⱺ":"o","ō":"o","ṓ":"o","ṑ":"o","ǫ":"o","ǭ":"o","ø":"o","ǿ":"o","õ":"o","ṍ":"o","ṏ":"o","ȭ":"o","ƣ":"oi","ꝏ":"oo","ɛ":"e","ᶓ":"e","ɔ":"o","ᶗ":"o","ȣ":"ou","ṕ":"p","ṗ":"p","ꝓ":"p","ƥ":"p","ᵱ":"p","ᶈ":"p","ꝕ":"p","ᵽ":"p","ꝑ":"p","ꝙ":"q","ʠ":"q","ɋ":"q","ꝗ":"q","ŕ":"r","ř":"r","ŗ":"r","ṙ":"r","ṛ":"r","ṝ":"r","ȑ":"r","ɾ":"r","ᵳ":"r","ȓ":"r","ṟ":"r","ɼ":"r","ᵲ":"r","ᶉ":"r","ɍ":"r","ɽ":"r","ↄ":"c","ꜿ":"c","ɘ":"e","ɿ":"r","ś":"s","ṥ":"s","š":"s","ṧ":"s","ş":"s","ŝ":"s","ș":"s","ṡ":"s","ṣ":"s","ṩ":"s","ʂ":"s","ᵴ":"s","ᶊ":"s","ȿ":"s","ɡ":"g","ᴑ":"o","ᴓ":"o","ᴝ":"u","ť":"t","ţ":"t","ṱ":"t","ț":"t","ȶ":"t","ẗ":"t","ⱦ":"t","ṫ":"t","ṭ":"t","ƭ":"t","ṯ":"t","ᵵ":"t","ƫ":"t","ʈ":"t","ŧ":"t","ᵺ":"th","ɐ":"a","ᴂ":"ae","ǝ":"e","ᵷ":"g","ɥ":"h","ʮ":"h","ʯ":"h","ᴉ":"i","ʞ":"k","ꞁ":"l","ɯ":"m","ɰ":"m","ᴔ":"oe","ɹ":"r","ɻ":"r","ɺ":"r","ⱹ":"r","ʇ":"t","ʌ":"v","ʍ":"w","ʎ":"y","ꜩ":"tz","ú":"u","ŭ":"u","ǔ":"u","û":"u","ṷ":"u","ü":"u","ǘ":"u","ǚ":"u","ǜ":"u","ǖ":"u","ṳ":"u","ụ":"u","ű":"u","ȕ":"u","ù":"u","ủ":"u","ư":"u","ứ":"u","ự":"u","ừ":"u","ử":"u","ữ":"u","ȗ":"u","ū":"u","ṻ":"u","ų":"u","ᶙ":"u","ů":"u","ũ":"u","ṹ":"u","ṵ":"u","ᵫ":"ue","ꝸ":"um","ⱴ":"v","ꝟ":"v","ṿ":"v","ʋ":"v","ᶌ":"v","ⱱ":"v","ṽ":"v","ꝡ":"vy","ẃ":"w","ŵ":"w","ẅ":"w","ẇ":"w","ẉ":"w","ẁ":"w","ⱳ":"w","ẘ":"w","ẍ":"x","ẋ":"x","ᶍ":"x","ý":"y","ŷ":"y","ÿ":"y","ẏ":"y","ỵ":"y","ỳ":"y","ƴ":"y","ỷ":"y","ỿ":"y","ȳ":"y","ẙ":"y","ɏ":"y","ỹ":"y","ź":"z","ž":"z","ẑ":"z","ʑ":"z","ⱬ":"z","ż":"z","ẓ":"z","ȥ":"z","ẕ":"z","ᵶ":"z","ᶎ":"z","ʐ":"z","ƶ":"z","ɀ":"z","ﬀ":"ff","ﬃ":"ffi","ﬄ":"ffl","ﬁ":"fi","ﬂ":"fl","ĳ":"ij","œ":"oe","ﬆ":"st","ₐ":"a","ₑ":"e","ᵢ":"i","ⱼ":"j","ₒ":"o","ᵣ":"r","ᵤ":"u","ᵥ":"v","ₓ":"x"};
String.prototype.latinise=function(){return this.replace(/[^A-Za-z0-9\[\] ]/g,function(a){return Latinise.latin_map[a]||a})};
String.prototype.latinize=String.prototype.latinise;

// Variable globale utilisée par le catalogue d’appréciations
var catalogue_prenom = 'INCONNU';

// Pour éviter une soumission d’un formulaire en double :
// + lors de l’appui sur "entrée" (constaté avec Chrome, malgré l’usage de la biblio jquery.form.js, avant l’utilisation complémentaire de "disabled")
// + lors d’un clic sur une image "q", même si elles sont normalement masquées...
var please_wait = false;

/**
 * Fonction addBR() pour ajouter des balises HTML de retour à la ligne
 *
 * @param txt
 * @return string
 */
function addBR(txt)
{
  return txt
    .replace(/\r\n/g, '<br>')
    .replace(/\r/g  , '<br>')
    .replace(/\n/g  , '<br>');
}

/**
 * Fonction removeBR() pour retirer des balises HTML de retour à la ligne
 *
 * @param txt
 * @return string
 */
function removeBR(txt)
{
  return txt
    .replace(/<br ?\/?>/g, '\r')
    .replace(/#BR#/g     , '\r');
}

/**
 * Fonction htmlspecialchars() en javascript
 *
 * @param unsafe
 * @return string
 */
function escapeHtml(unsafe)
{
  return unsafe
    .replace(/&/g, '&amp;')
    .replace(/</g, '&lt;')
    .replace(/>/g, '&gt;')
    .replace(/"/g, '&quot;')
    .replace(/'/g, '&#039;');
}

/**
 * Fonction réciproque de htmlspecialchars() en javascript
 *
 * @param safe
 * @return string
 */
function unescapeHtml(safe)
{
  return safe
    .replace(/&amp;/g , '&')
    .replace(/&lt;/g  , '<')
    .replace(/&gt;/g  , '>')
    .replace(/&quot;/g, '"')
    .replace(/&#039;/g,'\'');
}

/**
 * Fonction htmlspecialchars() en javascript mais juste pour les apostrophes doubles.
 *
 * @param unsafe
 * @return string
 */
function escapeQuote(unsafe)
{
  return unsafe.replace(/"/g, '&quot;');
}

/**
 * Fonction strip_tags() en javascript pour retirer les balises HTML.
 *
 * @param txt
 * @return string
 */
function strip_tags(txt)
{
  return txt.replace(/<(?:.|\s)*?>/gm,'' );
}

/**
 * Fonction pour extraire le hash (sans le dièse) d’une URL
 * Mise en place car un substring() ne passe pas si 
 * session.use_trans_sid = ON et session.use_only_cookies = OFF
 * car alors PHP rajoute ?SACoche-session= dans les liens
 *
 * @param href
 * @return string
 */
function extract_hash(href)
{
  var pos_hash = href.lastIndexOf('#');
  return (pos_hash!==-1) ? href.substring(pos_hash+1) : '' ;
}

/**
 * Fonction pour retourner un nombre entier
 *
 * @param nombre
 * @return int
 */
function entier(nombre)
{
  if(typeof(nombre)=='string')
    return parseInt(nombre.replace(',','.'),10);
  if(typeof(nombre)=='number')
    return parseInt(nombre,10);
  return NaN;
}

/**
 * Fonction pour retourner un nombre décimal
 *
 * @param nombre
 * @return float
 */
function decimal(nombre,precision)
{
  // Un second parseFloat() pour éviter les zéros inutiles
  if(typeof(nombre)=='string')
    return parseFloat( parseFloat(nombre.replace(',','.'),10).toFixed(precision) ,10);
  if(typeof(nombre)=='number')
    return parseFloat( parseFloat(nombre,10).toFixed(precision) ,10);
  return NaN;
}

/**
 * Fonction pour envoyer un message vers la console javascript
 *
 * @param type  log | info | warn | error | table | time | timeEnd | group | dir | assert | trace
 * @param msg   le contenu du message
 * @return string
 */
function log(type,msg)
{
  try
  {
         if(type=='log')     { console.log(msg);     }
    else if(type=='info')    { console.info(msg);    }
    else if(type=='warn')    { console.warn(msg);    }
    else if(type=='error')   { console.error(msg);   }
    else if(type=='table')   { console.table(msg);   }
    else if(type=='time')    { console.time(msg);    }
    else if(type=='timeEnd') { console.timeEnd(msg); }
    else if(type=='group')   { console.group(msg);   }
    else if(type=='dir')     { console.dir(msg);     }
    else if(type=='assert')  { console.assert(msg);  }
    else if(type=='trace')   { console.trace(msg);   }
  }
  catch (e)
  {}
}

/**
 * Fonction pour interpréter une erreur d’extraction json
 *
 * @param jqXHR      l’objet retourné par ajax, contenant la réponse du serveur
 * @param textStatus le statut de l’analyse json
 * @return string
 */
function afficher_json_message_erreur(jqXHR, textStatus)
{
  // Une erreur de syntaxe lors de l’analyse du json : probablement une erreur ou un avertissement PHP, éventuellement suivi de la chaine json retournée
  if(textStatus=='parsererror')
  {
    if( jqXHR['responseText'] == '' )
    {
      return 'Absence de réponse du serveur : dépassement des capacités (mémoire ou durée d’exécution) ?';
    }
    else
    {
      var pos_debut_json = jqXHR['responseText'].indexOf('{"');
      var chaine_anormale = (pos_debut_json>0) ? jqXHR['responseText'].substring(0,pos_debut_json) : jqXHR['responseText'] ;
      return 'Anomalie rencontrée : ' + strip_tags(chaine_anormale);
    }
  }
  // textStatus parmi [ null | "timeout" | "error" | "abort" ]
  else
  {
    if(jqXHR['status'])
    {
      // 404 par exemple...
      return 'Erreur '+jqXHR['status']+' : '+jqXHR['statusText'];
    }
    else if( typeof(jqXHR['responseText']) !== 'undefined' )
    {
      // Je ne sais pas si on peut passer ici...
      return 'Erreur inattendue : ' + escapeHtml(jqXHR['responseText']);
    }
    else
    {
      // Si le serveur est KO alors textStatus="error" et jqXHR ne contient aucune info exploitable
      return 'Échec de la connexion au serveur !';
    }
  }
}

/**
 * Déterminer et retenir si on a affaire à un écran tactile.
 * 
 * Avant on utilisait la classe PHP Mobile_Detect (http://mobiledetect.net)
 * mais depuis 2009 les iPad sous IOS 13 se déclarent en mode bureau
 * et le HTTP_USER_AGENT ne permet plus de les différencier.
 * 
 * @see https://patrickhlauke.github.io/touch/touchscreen-detection/
 * Le code ci-dessous en est inspiré, avec un test || remplacé par && car
 * cela retournait un faux-positif pour Chrome ou Opéra sous Windows 10.
 * 
 * @see https://codeburst.io/the-only-way-to-detect-touch-with-javascript-7791a3346685
 * Intéressant aussi, mais non mis en oeuvre.
 * 
 * @param void
 * @return bool
 */
function IsTouchDeviceEnabled()
{
  if( window.PointerEvent && ('maxTouchPoints' in navigator) && (navigator.maxTouchPoints > 0) )
    return true;
  if( window.PointerEvent && ('msMaxTouchPoints' in navigator) && (navigator.msMaxTouchPoints > 0) )
    return true;
  if( window.matchMedia && window.matchMedia('(any-pointer:coarse)').matches )
    return true;
  if( window.TouchEvent && ('ontouchstart' in window) )
    return true;
  return false;
}

var IsTouch = GetCookie('SACoche-is-tactile');

if( IsTouch === null )
{
  // Un cookie est une chaîne ; y enregistrer true ou false récupère ensuite "true" ou "false" : éviter les booléens.
  IsTouch = IsTouchDeviceEnabled() ? 1 : 0 ;
  SetCookie('SACoche-is-tactile',IsTouch);
}
else
{
  IsTouch = entier(IsTouch);
}

/**
 * Infobulle : title renseigné ou non selon qu’un écran tactile est utilisé.
 * 
 * @param string texte
 * @param bool   with_image
 * @return string
 */
function infobulle( texte , with_image )
{
  // Pas de valeurs par défaut dans les params d'une fonction avant ECMAScript 2015 (https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Fonctions/Valeurs_par_d%C3%A9faut_des_arguments)
  var with_image = (typeof with_image !== 'undefined') ? with_image : false;
  if( IsTouch || !texte )
  {
    return '';
  }
  else
  {
    var title = 'title="'+escapeQuote(texte)+'"';
    return (with_image) ? '<img alt="" src="./_img/bulle_aide.png" width="16" height="16" '+title+'>' : ' '+title ;
  }
}

/**
 * Fonction pour appliquer une infobulle au survol de tous les éléments possédants un attribut "title"
 *
 * Remarque : attention, cela fait disparaitre le contenu de l’attribut "alt"...
 *
 * @param void
 * @return void
 */
function display_infobulle()
{
  if( !IsTouch )
  {
    $(document).tooltip
    (
      {
        track: true,
        position: { my: 'left+15 top+15', collision: 'flipfit' },
        content: function()
        {
          if( ($(this).hasClass('fancybox-nav')) || ($(this).hasClass('fancybox-item')) )
          {
            $(this).removeAttr('title');
            return false;
          }
          // Protection contre l’exécution en infobulle de code HTML ou de scripts malicieux (qui ne se déclenchent pas au premier chargement mais si l’infobulle est éditée).
          // On ne tolère donc comme mise en forme que les retours à la ligne, et le besoin d’afficher des images de notes dans le tableau de saisie des évaluations pour l’auto-évaluation.
          var titre = addBR( escapeHtml( removeBR( $(this).attr('title') ) ) );
          if( $(this).attr('id') && $(this).attr('id').substring(0,9) == 'autoeval_' )
          {
            titre = titre.replaceAll('[','<img alt="').replaceAll('|','" src="').replaceAll(']','"><br>');
          }
          // La ligne suivante permet aussi la prise en compte des <br>... pas vraiment compris pourquoi mais bon...
          return '<b>'+titre+'</b>';
        }
      }
    );
  }
}

/**
 * Fonction pour un tester la robustesse d’un mot de passe.
 *
 * @param mdp
 * @return void
 */
function analyse_mdp(mdp)
{
  mdp.replace(/^\s+/g,'').replace(/\s+$/g,'');  // équivalent de trim() en javascript
  mdp = mdp.substring(0,20);
  var nb_min = 0;
  var nb_maj = 0;
  var nb_num = 0;
  var nb_spe = 0;
  var longueur = mdp.length;
  for (var i=0 ; i<longueur ; i++)
  {
    var car = mdp.charAt(i);
         if((/[a-z]/).test(car)) {nb_min++;}  // 2 points maxi pour des minuscules
    else if((/[A-Z]/).test(car)) {nb_maj++;}  // 2 points maxi pour des majuscules
    else if((/[0-9]/).test(car)) {nb_num++;}  // 2 points maxi pour des chiffres
    else                         {nb_spe++;}  // 6 points maxi pour des caractères autres
  }
  var coef = Math.min(nb_min,2) + Math.min(nb_maj,2) + Math.min(nb_num,2) + Math.min(nb_spe*2,6) ;
  if(longueur>7)
  {
    coef += Math.floor( (longueur-5)/3 );  // 6 points maxi pour la longueur du mdp
  }
  coef = Math.min(coef,12);  // total 18 points maxi, plafonné à 12
  var rouge = 255 - 16*Math.max(0,coef-6) ; // 255 -> 255 -> 159
  var vert  = 159 + 16*Math.min(6,coef) ;   // 159 -> 255 -> 255
  var bleu  = 159 ;
  $('#robustesse').css('background-color','rgb('+rouge+','+vert+','+bleu+')').children('span').html(coef);
}

/**
 * Fonction pour imprimer un contenu
 *
 * En javascript, print() s’applique à l’objet window, et l’usage d’une feuille de style adaptée n’a pas permis d’obtenir un résultat satisfaisant.
 * D'où l’ouverture d’un pop-up.
 *
 * @param object contenu
 * @return void
 */
function imprimer(contenu)
{
  var wp = window.open('','SACochePrint','toolbar=no,location=no,menubar=no,directories=no,status=no,scrollbars=no,resizable=no,copyhistory=no,width=1,height=1,top=0,left=0');
  wp.document.write('<!DOCTYPE html><html><head><link rel="stylesheet" type="text/css" href="./_css/style.css"><title>SACoche - Impression</title></head><body onload="window.print();window.close()">'+document.getElementById('top_info').innerHTML+contenu+'</body></html>');
  wp.document.close();
}

var memo_eleve = 0;

/**
 * Fonction pour Afficher / Masquer la photo d’un élève
 *
 * Est utilisé sur les bilans officiels (bulletin, livret, mais aussi la saisie de résultats aux évaluations.
 *
 * @param int    eleve_id
 * @param mode   'init' | 'maj'
 * @return void
 */
function charger_photo_eleve(eleve_id,mode)
{
  if( (mode=='init') || ($('#voir_photo').length==0) )
  {
    $('#cadre_photo').html('<label id="ajax_photo" class="loader">En cours&hellip;</label>');
    $.ajax
    (
      {
        type : 'POST',
        url : 'ajax.php?page=calque_voir_photo',
        data : 'csrf='+window.CSRF+'&user_id='+eleve_id,
        dataType : 'json',
        error : function(jqXHR, textStatus, errorThrown)
        {
          $('#ajax_photo').attr('class','alerte').html(afficher_json_message_erreur(jqXHR,textStatus));
        },
        success : function(responseJSON)
        {
          if(responseJSON['statut']==true)
          {
            $('#cadre_photo').html('<div>'+responseJSON['value']+'</div><div style="margin-top:-20px"><button id="masquer_photo" type="button" class="annuler">Fermer</button></div>');
          }
          else
          {
            $('#ajax_photo').attr('class','alerte').html(responseJSON['value']);
          }
        }
      }
    );
  }
}

/**
 * Fonction pour afficher et cocher une liste d’items donnés
 *
 * @param string matieres_items_liste : ids séparés par des underscores
 * @return [liste,nombre] des éventuels items restants
 */
function cocher_matieres_items(matieres_items_liste)
{
  var tab_id_reliquat = [];
  var obj_zone_matieres_items = $('#zone_matieres_items');
  // Replier tout sauf le plus haut niveau
  obj_zone_matieres_items.find('ul').css('display','none');
  obj_zone_matieres_items.find('ul.ul_m1').css('display','block');
  // Décocher tout
  obj_zone_matieres_items.find('input[type=checkbox]').each
  (
    function()
    {
      this.checked = false;
    }
  );
  // Cocher ce qui doit l’être (initialisation)
  if(matieres_items_liste.length)
  {
    var tab_id = matieres_items_liste.split('_');
    for(var i in tab_id)
    {
      var obj_input = $('#item_'+tab_id[i]);
      if(obj_input.length)
      {
        obj_input.prop('checked',true);
        obj_input.closest('ul.ul_n3').css('display','block');  // les items
        obj_input.closest('ul.ul_n2').css('display','block');  // le thème
        obj_input.closest('ul.ul_n1').css('display','block');  // le domaine
        obj_input.closest('ul.ul_m2').css('display','block');  // le niveau
      }
      else
      {
        tab_id_reliquat.push(tab_id[i]);
      }
    }
  }
  // En cas d’évaluation partagée où tous les profs ne sont pas tous reliés aux référentiels concernés
  var reliquat_nombre = tab_id_reliquat.length;
  if(reliquat_nombre)
  {
    return { 'liste' : tab_id_reliquat.join('_') , 'nombre' : reliquat_nombre };
  }
  else
  {
    return { 'liste' : '' , 'nombre' : 0 };
  }
}

/**
 * Fonction pour mémoriser une liste d’items donnés
 *
 * @param string selection_items_nom
 * @return void
 */
function memoriser_selection_matieres_items(selection_items_nom)
{
  var obj_ajax_msg = $('#ajax_msg_memo');
  if(!selection_items_nom)
  {
    obj_ajax_msg.attr('class','erreur').html('nom manquant');
    $('#f_liste_items_nom').focus();
    return false;
  }
  var compet_liste = '';
  $('#zone_matieres_items').children('ul').find('input[type=checkbox]:checked').each
  (
    function()
    {
      compet_liste += $(this).val()+'_';
    }
  );
  if(!compet_liste)
  {
    obj_ajax_msg.attr('class','erreur').html('Aucun item coché !');
    return false;
  }
  var compet_liste  = compet_liste.substring(0,compet_liste.length-1);
  var f_action = $('#action_modifier').is(':checked') ? 'modifier' : 'ajouter' ;
  var selection_id = $('#selection_id').val();
  obj_ajax_msg.attr('class','loader').html('En cours&hellip;');
  $.ajax
  (
    {
      type : 'POST',
      url : 'ajax.php?page=compte_selection_items',
      data : 'f_action='+f_action+'&f_id='+selection_id+'&f_origine='+window.PAGE+'&f_compet_liste='+compet_liste+'&f_nom='+encodeURIComponent(selection_items_nom),
      dataType : 'json',
      error : function(jqXHR, textStatus, errorThrown)
      {
        obj_ajax_msg.attr('class','alerte').html(afficher_json_message_erreur(jqXHR,textStatus));
      },
      success : function(responseJSON)
      {
        initialiser_compteur();
        if(responseJSON['statut']==false)
        {
          obj_ajax_msg.attr('class','alerte').html(responseJSON['value']);
          if( responseJSON['value'].indexOf('vous-même') != -1 )
          {
            $('#selection_id').val(responseJSON['selection_id']);
            $('#action_modifier').prop('checked',true);
            $('#span_selection_modifier').show(0);
          }
          $('#f_liste_items_nom').focus();
        }
        else
        {
          obj_ajax_msg.attr('class','valide').html('Sélection mémorisée.');
          $('#f_selection_items option:disabled').remove();
          $('#f_selection_items').append(responseJSON['value']);
          $('#selection_id').val('');
          $('#action_modifier').prop('checked',false);
          $('#span_selection_modifier').hide(0);
        }
      }
    }
  );
}

/**
 * Fonction pour afficher et cocher un item du socle
 *
 * @param socle_item_id
 * @return void
 */
function cocher_socle_item(socle_item_id)
{
  var obj_zone_socle_item = $('#zone_socle_item');
  // Replier tout sauf le plus haut niveau la 1e fois ; ensuite on laisse aussi volontairement ouvert ce qui a pu l’être précédemment
  if(cocher_socle_item_first_appel)
  {
    obj_zone_socle_item.find('ul').css('display','none');
    obj_zone_socle_item.find('ul.ul_m1').css('display','block');
    cocher_socle_item_first_appel = false;
  }
  obj_zone_socle_item.find('ul.ul_n1').css('display','block'); // zone "Hors socle" éventuelle
  // Décocher tout
  obj_zone_socle_item.find('input[type=radio]').each
  (
    function()
    {
      this.checked = false;
    }
  );
  // Cocher ce qui doit l’être (initialisation)
  var obj_input = $('#socle_'+socle_item_id);
  if(socle_item_id!='0')
  {
    if(obj_input.length)
    {
      obj_input.prop('checked',true);
      obj_input.closest('ul.ul_n3').css('display','block');  // les items
      obj_input.closest('ul.ul_n2').css('display','block');  // la section
      obj_input.closest('ul.ul_n1').css('display','block');  // le pilier
    }
  }
  else
  {
    $('#socle_0').prop('checked',true);
  }
  obj_input.focus();
}

var cocher_socle_item_first_appel = true;

/**
 * Fonction pour afficher et cocher des items du socle 2016
 *
 * @param listing_socle_id
 * @return void
 */
function cocher_socle2016_composantes(listing_socle_id)
{
  var obj_zone_socle2016_composante = $('#zone_socle2016_composante');
  // Replier tout sauf le plus haut niveau la 1e fois ; ensuite on laisse aussi volontairement ouvert ce qui a pu l’être précédemment
  if(cocher_socle2016_composante_first_appel)
  {
    obj_zone_socle2016_composante.find('ul').css('display','none');
    obj_zone_socle2016_composante.find('ul.ul_m1').css('display','block');
    cocher_socle2016_composante_first_appel = false;
  }
  // Décocher tout
  obj_zone_socle2016_composante.find('input[type=checkbox]').each
  (
    function()
    {
      this.checked = false;
    }
  );
  // Cocher ce qui doit l’être (initialisation)
  if(listing_socle_id)
  {
    var tab_socle_id = listing_socle_id.toString().split(',');
    for(var i in tab_socle_id)
    {
      var obj_input = $('#socle2016_'+tab_socle_id[i]);
      obj_input.prop('checked',true);
      obj_input.closest('ul.ul_n2').css('display','block');  // le domaine
      obj_input.closest('ul.ul_n1').css('display','block');  // le cycle
    }
    obj_input.focus();
  }
}

var cocher_socle2016_composante_first_appel = true;

/**
 * Fonction pour afficher et cocher une liste d’élèves donnés
 *
 * @param prof_liste : ids séparés par des underscores
 * @return void
 */
function cocher_eleves(eleve_liste)
{
  var obj_zone_eleve = $('#zone_eleve');
  // Replier les classes
    obj_zone_eleve.find('ul').css('display','none');
    obj_zone_eleve.find('ul.ul_m1').css('display','block');
  // Décocher tout
  obj_zone_eleve.find('input[type=checkbox]').each
  (
    function()
    {
      this.checked = false;
      $(this).next('label').removeAttr('class').next('span').html(''); // retrait des indications éventuelles d’élèves associés à une évaluation de même nom
    }
  );
  // Cocher ce qui doit l’être (initialisation)
  if(eleve_liste.length)
  {
    var tab_id = eleve_liste.split('_');
    for(var i in tab_id)
    {
      var obj_input = $('input[id^=id_'+tab_id[i]+'_]');
      if(obj_input.length)
      {
        obj_input.prop('checked',true);
        obj_input.parent().parent().css('display','block');  // le regroupement
      }
    }
  }
}

/**
 * Fonction pour cocher une liste de matières données
 *
 * @param matiere_liste : ids séparés par des underscores
 * @return void
 */
function cocher_matieres(matiere_liste)
{
  // Décocher tout
  $('#zone_matieres').find('input[type=checkbox]').each
  (
    function()
    {
      this.checked = false;
    }
  );
  // Cocher des cases des matières
  if(matiere_liste.length)
  {
    var tab_id = matiere_liste.split('_');
    for(var i in tab_id)
    {
      var obj_input = $('#m_'+tab_id[i]);
      if(obj_input.length)
      {
        obj_input.prop('checked',true);
      }
    }
  }
}

/**
 * Fonction pour cocher une liste de profs donnés
 *
 * @param prof_liste : ids séparés par des underscores
 * @return void
 */
function cocher_profs(prof_liste)
{
  // Décocher tout
  $('#zone_profs').find('input[type=checkbox]').each
  (
    function()
    {
      if(this.disabled == false)
      {
        this.checked = false;
      }
    }
  );
  // Cocher des cases des profs
  if(prof_liste.length)
  {
    var tab_id = prof_liste.split('_');
    for(var i in tab_id)
    {
      var obj_input = $('#p_'+tab_id[i]);
      if(obj_input.length)
      {
        obj_input.prop('checked',true);
      }
    }
  }
}

/**
 * Fonction pour selectionner une option pour une liste de profs donnés
 *
 * @param prof_liste : { lettre de l’option concaténée avec l’id du prof } séparés par des underscores
 * @return void
 */
function selectionner_profs_option(prof_liste)
{
  $('#appliquer_droit_prof_groupe').prop('disabled',false);
  $('#load_profs_groupe').removeAttr('class').html('');
  // Sélectionner l’option par défaut pour tous les profs
  $('#zone_profs').find('select').find('option[value=x]').prop('selected',true);
  $('.prof_liste').find('span.select_img').attr('class','select_img droit_x');
  // Décocher les boutons pour reporter une valeur à tous
  $('#zone_profs').find('input[type=radio]').prop('checked',false);
  // Modifier les sélections des profs concernés
  if(prof_liste.length)
  {
    var tab_val = prof_liste.split('_');
    for(var i in tab_val)
    {
      var val_option = tab_val[i].substring(0,1);
      var id_prof    = tab_val[i].substring(1);
      var obj_select = $('#p_'+id_prof);
      if(obj_select.length)
      {
        obj_select.find('option[value='+val_option+']').prop('selected',true);
        obj_select.next('span').attr('class','select_img droit_'+val_option);
      }
    }
  }
}

/**
 * Fonction pour afficher le nombre de caractères restants autorisés dans un textarea.
 * À appeler avec les événements onkeyup et onchange.
 *
 * Inspiration : http://www.paperblog.fr/349086/limiter-le-nombre-de-caractere-d-un-textarea/
 * Plugin jQuery possible : http://www.devzone.fr/plugin-jquery-maxlength-nombre-de-caracteres-restants
 *
 * @param textarea_obj
 * @param textarea_maxi_length
 * @return void
 */
function afficher_textarea_reste(textarea_obj,textarea_maxi_length)
{
  var textarea_contenu = textarea_obj.val();
  var textarea_longueur = textarea_contenu.length;
  if(textarea_longueur > textarea_maxi_length)
  {
    textarea_obj.val( textarea_contenu.substring(0,textarea_maxi_length) );
    textarea_longueur = textarea_maxi_length;
  }
  var reste_nb    = textarea_maxi_length - textarea_longueur;
  var reste_str   = (reste_nb>1) ? ' caractères restants' : ' caractère restant' ;
  var reste_class = (reste_nb>9) ? 'valide' : 'alerte' ;
  $('#'+textarea_obj.attr('id')+'_reste').html(reste_nb+reste_str).attr('class',reste_class);
}

// ////////////////////////////////////////////////////////////////////////////////////////////////////
// Gestion de la durée d’inactivité
// On utilise un cookie plutôt qu’une variable js car ceci permet de gérer plusieurs onglets.
// ////////////////////////////////////////////////////////////////////////////////////////////////////

var lastSessionCallKeep = Date.now();
var lastSessionCallKill = Date.now();
var timerConnexion = false;
var documentTitleMemo = document.title;

/**
 * Fonction pour remettre le compteur au maximum (cookie + affichage)
 *
 * @param void
 * @return void
 */
function initialiser_compteur()
{
  SetCookie('SACoche-compteur',Date.now());
  window.DUREE_AFFICHEE = window.DUREE_AUTORISEE;
  $('#clock').html(window.DUREE_AFFICHEE+' min').parent().attr('class','top clock_fixe');
}

/**
 * Fonction pour modifier l’état du compteur, et déconnecter si besoin
 *
 * @param void
 * @return void
 */
function tester_compteur()
{
  var now   = Date.now();
  var avant = entier(GetCookie('SACoche-compteur'));
  var duree_ecoulee  = Math.floor((now-avant)/60/1000);
  var duree_restante = window.DUREE_AUTORISEE-duree_ecoulee;
  if(duree_restante!=window.DUREE_AFFICHEE)
  {
    window.DUREE_AFFICHEE = Math.max(duree_restante,0);
    if(window.DUREE_AFFICHEE>5)
    {
      document.title = documentTitleMemo;
      $('#clock').html(window.DUREE_AFFICHEE+' min').parent().attr('class','top clock_fixe');
      if(window.DUREE_AFFICHEE%10==0)
      {
        // Fonction conserver_session_active() à appeler une fois toutes les 10min ; code placé ici pour éviter un appel après déconnection, et l’application inutile d’un 2nd compteur
        conserver_session_active();
      }
    }
    else
    {
      document.title = window.DUREE_AFFICHEE+'min'+'❗❗❗';
      $('#clock').html(window.DUREE_AFFICHEE+' min').parent().attr('class','top clock_anim');
      if(window.DUREE_AFFICHEE==0)
      {
        fermer_session_en_ajax('inactivite');
        document.title = 'Session expirée';
      }
    }
  }
}

/**
 * Fonction pour ne pas perdre la session : appel au serveur toutes les 10 minutes (en ajax)
 *
 * @param void
 * @return void
 */
function conserver_session_active()
{
  if (Date.now() - lastSessionCallKeep < 1000)
  {
    // pas plus d’un appel par seconde au cas où il y aurait un bug...
    return;
  }
  else
  {
    lastSessionCallKeep = Date.now();
    // Le numéro de la base et de l’utilisateur sont transmis en GET simplement pour avoir une trace dans les logs "accès www" de l’établissement concerné en cas d’erreur SQL (dans les logs "erreurs php", où cette info n’apparaît pas...).
    // Cela demeure peu pratique dans la mesure où il faut d’abord essayer de trouver l’IP correspondante via une recherche sur l’heure (pour faire le lien "erreurs php"->"accès www").
    $.ajax
    (
      {
        type : 'GET',
        url : 'ajax.php?page=conserver_session_active&log_id='+window.LOG_ID,
        data : '',
        dataType : 'json',
        error : function(jqXHR, textStatus, errorThrown)
        {
          $('div.jqibox').remove(); // Sinon il y a un pb d’affichage lors d’appels successifs
          $.prompt(
            afficher_json_message_erreur(jqXHR,textStatus)+'<br>Le travail en cours pourrait ne pas pouvoir être sauvegardé...',
            {
              title  : 'Avertissement'
            }
          );
        },
        success : function(responseJSON)
        {
          if(responseJSON['statut']==false)
          {
            $('div.jqibox').remove(); // Sinon il y a un pb d’affichage lors d’appels successifs
            $.prompt(
              responseJSON['value'] ,
              {
                title: 'Anomalie'
              }
            );
          }
        }
      }
    );
  }
}

/**
 * Fonction pour fermer la session : appel si le compteur arrive à zéro (en ajax)
 *
 * @param string motif   inactivite | redirection
 * @return bool
 */
function fermer_session_en_ajax(motif)
{
  var now = Date.now();
  if (now - lastSessionCallKill < 1000)
  {
    // pas plus d’un appel par seconde au cas où il y aurait un bug...
    return false;
  }
  else
  {
    lastSessionCallKill = now;
    $.ajax
    (
      {
        type : 'GET',
        url : 'ajax.php?page=fermer_session',
        data : '',
        dataType : 'json',
        error : function(jqXHR, textStatus, errorThrown)
        {
          console.error('Erreur ' + textStatus + ' lors de l’appel de ajax.php?page=fermer_session ', errorThrown);
          // Pas de traitement particulier prévu...
          return false;
        },
        success : function(responseJSON)
        {
          if(responseJSON['statut']==false)
          {
            // Pas de traitement particulier prévu...
            return false;
          }
          if(motif=='redirection')
          {
            window.document.location.href = window.DECONNEXION_REDIR ;
          }
          if(motif=='inactivite')
          {
            clearInterval(timerConnexion);
            $('#menu').remove();
            if(window.CONNEXION_USED=='normal')
            {
              var adresse = ( (window.PROFIL_TYPE!='webmestre') && (window.PROFIL_TYPE!='partenaire') && (window.PROFIL_TYPE!='developpeur') ) ? './index.php' : './index.php?'+window.PROFIL_TYPE ;
              $('#top_info').html('<div><span class="top expiration">Votre session a expiré. Vous êtes désormais déconnecté de SACoche !</span><br><span class="top connexion"><a href="'+adresse+'">Se reconnecter&hellip;</a></span></div>');
            }
            else
            {
              $('#top_info').html('<div><span class="top expiration">Session expirée. Vous êtes déconnecté de SACoche mais sans doute pas du SSO !</span><br><span class="top connexion"><a href="#" onclick="document.location.reload()">Recharger la page&hellip;</a></span></div>');
            }
            $.fancybox( '<div class="danger">Délai de '+window.DUREE_AUTORISEE+'min sans activité atteint &rarr; session fermée.<br>Toute action ultérieure ne sera pas enregistrée.</div>' );
          }
        }
      }
    );
  }
}

/**
 * Fonction pour lancer une mise à jour complémentaire de la base par morceaux
 *
 * @param void
 * @return void
 */
function maj_base_complementaire()
{
  $.ajax
  (
    {
      type : 'GET',
      url : 'ajax.php?page=maj_base_complementaire',
      data : '',
      dataType : 'json',
      error : function(jqXHR, textStatus, errorThrown)
      {
        console.error('Erreur ' + textStatus + ' lors de l’appel de ajax.php?page=maj_base_complementaire ', errorThrown);
        // Pas de traitement particulier prévu...
        return false;
      },
      success : function(responseJSON)
      {
        if(responseJSON['value'] == 'encore')
        {
          maj_base_complementaire();
        }
        else
        {
          // Pas de traitement particulier prévu...
          return false;
        }
      }
    }
  );
}

/**
 * Fonction pour tester une URL : extrait du plugin jQuery Validation
 *
 * @param string
 * @return bool
 */
function testURL(lien)
{
  return /^(https?|ftp):\/\/(((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:)*@)?(((\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5]))|((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)*(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?)(:\d*)?)(\/((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)+(\/(([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)*)*)?)?(\?((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|[\uE000-\uF8FF]|\/|\?)*)?(\#((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|\/|\?)*)?$/i.test(lien);
}

/**
 * Fonction pour tester une adresse mail : extrait du plugin jQuery Validation
 *
 * @param string
 * @return bool
 */
function test_courriel(adresse)
{
  return /^((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))$/i.test(adresse);
}
jQuery.validator.addMethod
(
  'courriel', function(value, element)
  {
    return this.optional(element) || test_courriel(value) ;
  }
  , 'courriel invalide'
);

/**
 * Ajout de méthodes pour jquery.validate.js
 */

// Méthode pour vérifier le format du numéro UAI
function test_uai_format(value)
{
  var uai = value.toUpperCase();
  var RegExp_Ramsese = /^[0-9]{7}[ABCDEFGHJKLMNPRSTUVWXYZ]{1}$/ ;
  var RegExp_Rhodes  = /^[0-9]{6}X[ABCDEFGHJKLMNPRSTUVWXYZ]{1}$/ ;
  return RegExp_Ramsese.test(uai) || RegExp_Rhodes.test(uai);
}
jQuery.validator.addMethod
(
  'uai_format', function(value, element)
  {
    return this.optional(element) || test_uai_format(value) ;
  }
  , 'il faut 7 chiffres suivis d’une lettre'
);

// Méthode pour vérifier la clef de contrôle du numéro UAI
function test_uai_clef(value)
{
  var uai = value.toUpperCase();
  if( uai.substring(6,7) != 'X' )
  {
    // Base RAMSESE (établissement du système éducatif français) : 7 chiffres suivis d’une lettre de contrôle basée sur le modulo 23 du nombre
    var uai_nombre = uai.substring(0,7);
    var reste = uai_nombre%23;
  }
  else
  {
    // Base RHODES (organismes de détachement hors éducation) : 6 chiffres suivis du caractère "X" puis d’une lettre de contrôle basée sur le modulo 23 du ( nombre * 8 + 1 )
    var uai_nombre = uai.substring(0,6);
    var reste = (uai_nombre*8+1)%23;
  }
  var uai_lettre = uai.substring(7,8);
  var alphabet = 'ABCDEFGHJKLMNPRSTUVWXYZ';
  var clef = alphabet.substring(reste,reste+1);
  return (clef==uai_lettre) ? true : false ;
}
jQuery.validator.addMethod
(
  'uai_clef', function(value, element)
  {
    return this.optional(element) || test_uai_clef(value) ;
  }
  , 'clef de contrôle incompatible'
);

// Méthode pour valider les dates de la forme jj/mm/aaaa (trouvé dans le zip du plugin, corrige en plus un bug avec Safari)
function test_dateITA( value , return_date_sql = false )
{
  var re = /^\d{1,2}\/\d{1,2}\/\d{4}$/ ;
  if( re.test(value))
  {
    var adata = value.split('/');
    var gg = entier(adata[0]);
    var mm = entier(adata[1]);
    var aaaa = entier(adata[2]);
    var xdata = new Date(aaaa,mm-1,gg);
    if ( ( xdata.getFullYear() == aaaa ) && ( xdata.getMonth () == mm - 1 ) && ( xdata.getDate() == gg ) )
      return return_date_sql ? adata[2]+'-'+adata[1]+'-'+adata[0] : true;
    else
      return false;
  }
  else
    return false;
}
jQuery.validator.addMethod
(
  'dateITA',
  function(value, element)
  {
    return this.optional(element) || test_dateITA(value);
  }, 
  'date JJ/MM/AAAA incorrecte'
);

/**
 * Ajout d’une méthode pour imposer des lettres
 */
jQuery.validator.addMethod
(
  'lettersonly', function(value, element)
  {
    return this.optional(element) || /^[a-z]+$/i.test( value ) ;
  }
  , 'lettres uniquement'
);

/**
 * Ajout d’une méthode pour tester la présence d’un mot
 */
jQuery.validator.addMethod
(
  'isWord', function(value, element, param)
  {
    return this.optional(element) || (value.match(new RegExp(param))) ;
  }
  , 'élément manquant'
);

/**
 * Ajout d’une méthode pour tester la syntaxe d’un domaine
 */
function test_domaine(domaine)
{
  return /^([a-zA-Z0-9]([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,6}$/i.test(domaine);
}
jQuery.validator.addMethod
(
  'domaine', function(value, element)
  {
    return this.optional(element) || test_domaine(value) ;
  }
  , 'élément manquant'
);

/**
 * Ajout d’une méthode pour tester la syntaxe d’une url en remplacement de la méthode "url" par défaut car elle n’accepte pas par exemple http://127.0.0.1 ou http://localhost
 */
jQuery.validator.addMethod
(
  'URL', function(value, element)
  {
    return this.optional(element) || testURL(value) ;
  }
  , 'élément manquant'
);

// Méthode pour n’accepter que des lettres (non accentuées) et des chiffres
function test_id(value)
{
  var re = /^[a-zA-Z0-9]+$/ ;
  return re.test(value);
}

/**
 * Ajout d’une alerte dans le DOM sans jQuery.
 * Utilisé par les deux tests qui suivent cette fonction.
 */
function ajout_alerte(texte)
{
  // Contenu
  var paragraphe = document.createElement('div');
  paragraphe.setAttribute('class', 'probleme');
  paragraphe.innerHTML = texte;
  // Emplacement
  var endroit = false;
  if( document.getElementById('titre_logo') !== null )
  {
    endroit = document.getElementById('titre_logo');
  }
  else if( document.getElementsByTagName('h1').length )
  {
    endroit = document.getElementsByTagName('h1').item(0);
  }
  // Insertion
  if(endroit)
  {
    // Il n’existe pas de méthode insertAfter pour insérer un nœud après un autre, cependant on peut l’émuler avec une combinaison de insertBefore et nextSibling.
    // @see https://developer.mozilla.org/fr/docs/DOM/element.insertBefore
    endroit.parentNode.insertBefore( paragraphe , endroit.nextSibling );
  }
}

/**
 * Alerte si usage frame / iframe
 * Écrit sans nécessiter jQuery car l’ENT d’Itop fait planter la bibliothèque sous IE (SACoche mis dans un iframe lui-même imbriqué récursivement dans 4 tableaux et 5 div, avec des scripts en pagaille).
 * Ajout du 2nd test (https://developer.mozilla.org/fr/docs/Web/API/Window/frameElement) afin d’éviter un faux positif avec l’extension Surfingkeys (https://github.com/brookhong/Surfingkeys).
 */
if( top.frames.length && window.frameElement )
{
  ajout_alerte('L’usage de cadres (frame/iframe) pour afficher <em>SACoche</em> est inapproprié et peut entrainer des dysfonctionnements.<br><a href="'+location.href+'" target="_blank" rel="noopener noreferrer">Ouvrir <em>SACoche</em> dans un nouvel onglet.</a>');
}

/**
 * Alerte si non acceptation des cookies
 * Peut se tester directement en javascript (éxécuté par le client) alors qu’en PHP il faut recharger une page (info envoyée au serveur dans les en-têtes)
 */
var accepteCookies;
if(typeof navigator.cookieEnabled !== 'undefined')
{
  accepteCookies = (navigator.cookieEnabled) ? true : false ;
}
else
{
  document.cookie = 'test';
  accepteCookies = (document.cookie.indexOf('test') !== -1) ? true : false ;
}
if(!accepteCookies)
{
  ajout_alerte('Pour utiliser <em>SACoche</em> vous devez configurer l’acceptation des cookies par votre navigateur.');
}

/**
 * Est appelé ainsi :
 * $(window).on( 'beforeunload', confirmOnLeave );
 * $(window).off('beforeunload', confirmOnLeave );
 */
var confirmOnLeave = function() {
  return 'ATTENTION : VOUS N’AVEZ PAS ENREGISTRÉ VOTRE TRAVAIL !';
};

/**
 * jQuery !
 */
$(document).ready
(
  function()
  {

    // Ajout d'une méthode
    $.fn.hideshow = function(bool) {
      if(bool) { $(this).show(0); }
      else     { $(this).hide(0); }
      return $(this);
    }

    /**
     * Initialisation
     */
    var nb_caracteres_max = 2000;
    display_infobulle();

    /**
     * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
     * Protection des liens mailto
     * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
     */

    /**
     * Fonction de décodage avec la méthode rot13 (on retire aussi 8 caractères aléatoires initiaux)
     * @param {string} encodedString
     */
    function mail_decrypter(encodedString)
    {
      function decodeOne (c) {
        return String.fromCharCode((c <= "Z" ? 90 : 122) >= (c = c.charCodeAt(0) + 13) ? c : c - 26);
      }
      return encodedString.substring(8).replace(/[a-zA-Z]/g, decodeOne);
    }

    /**
     * Au chargement de la page on se contente de placer les balises avec leur contenu textuel
     */
    if( typeof(window.meltoo) !== 'undefined' )
    {
      for ( var numero in window.meltoo )
      {
        var texte = window.meltoo[numero][2] ? mail_decrypter(window.meltoo[numero][1]) : window.meltoo[numero][1] ;
        $('#meltoo'+numero).replaceWith('<a href="" class="lien_mail" data-numero="'+numero+'">'+texte+'</a>');
      }
    }

    /**
     * Au clic sur un lien on va enfin chercher le contenu du mailto
     * Pour ceux qui ont un logiciel de messagerie installé, pas de souci.
     * Pour ceux qui n’en ont pas, l’adresse est reportée dans l’attribut title.
     */
    $(document).on
    (
      'click',
      'a.lien_mail',
      function()
      {
        var numero = $(this).data('numero');
        if( typeof(numero) !== 'undefined' )
        {
          var mailto = mail_decrypter(window.meltoo[numero][0]);
          var position_data = mailto.indexOf('?');
          var courriel = (position_data==-1) ? mailto : mailto.substring(0,position_data) ;
          $(this).attr('href','mailto:'+mailto).attr('title',decodeURIComponent(courriel));
        }
      }
    );

    /**
     * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
     * Divers
     * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
     */

    /**
     * Clic sur une image-lien afin d’afficher ou de masquer le détail d’une synthese ou d’un relevé socle
     */
    $(document).on
    (
      'click',
      'a[href="#toggle"]',
      function()
      {
        var id   = $(this).attr('id').substring(3); // 'to_' + id
        var class_old = $(this).attr('class');
        var class_new = (class_old=='toggle_plus') ? 'toggle_moins' : 'toggle_plus' ;
        $(this).attr('class',class_new);
        $('#'+id).toggle('fast');
        return false;
      }
    );

    /**
     * Clic sur un lien pour ouvrir une fenêtre d’aide en ligne (pop-up)
     */
    $(document).on
    (
      'click',
      'a.pop_up',
      function()
      {
        var adresse = $(this).attr('href');
        // Fenêtre principale ; si ce n’est pas le pop-up, on la redimensionne / repositionne
        if(window.name!='popup')
        {
          var largeur = Math.max( 1000 , screen.width - 600 );
          var hauteur = screen.height * 1 ;
          var gauche = 0 ;
          var haut   = 0 ;
          window.moveTo(gauche,haut);
          window.resizeTo(largeur,hauteur);
        }
        // Fenêtre pop-up
        var largeur = 600 ;
        var hauteur = screen.height * 1 ;
        var gauche = screen.width - largeur ;
        var haut   = 0 ;
        var w = window.open( adresse , 'popup' ,'toolbar=no,location=no,menubar=no,directories=no,status=no,scrollbars=yes,resizable=yes,copyhistory=no,width='+largeur+',height='+hauteur+',top='+haut+',left='+gauche ) ;
        w.focus() ;
        return false;
      }
    );

    /**
     * Plugin Impromptu - Options par défaut
     */
    jQuery.prompt.setDefaults({
      opacity: 0.7, // Combiné au background-color:#000 modifié dans le css
      zIndex : 9000 // Pour passer devant un fancybox
    });
    jQuery.prompt.setStateDefaults({
      focus  : null // Pas de focus particulier ; ne fonctionne qu’avec la syntaxe utilisant une collection d’étapes, pas la syntaxe directe simplifiée.
    });

    /**
     * Ajouter une méthode de tri au plugin TableSorter
     * @see https://mottie.github.io/tablesorter/docs/example-parsers.html
     */
    $.tablesorter.addParser
    (
      {
        id: 'date_fr',
        format: function(s, table, cell, cellIndex)
        {
          // format your data for normalization
          if(s=='-')
          {
            return 99991231;
          }
          var tab_date = s.split('/');
          if(tab_date.length==3)
          {
            return tab_date[2]+tab_date[1]+tab_date[0]; // Il s’agit bien d’une concaténation, pas d’une somme.
          }
          else
          {
            return 0;
          }
        },
        type: 'numeric'
      }
    );

    /**
     * Ajouter une méthode de tri au plugin TableSorter
     * @see https://mottie.github.io/tablesorter/docs/example-parsers.html
     */
    $.tablesorter.addParser
    (
      {
        id: 'FromData',
        format: function(s, table, cell, cellIndex)
        {
          return $(cell).data('sort');
        },
        type: 'numeric'
      }
    );

// ////////////////////////////////////////////////////////////////////////////////
// La suite n’est à exécuter que si l’on est connecté.
// Remarque : poursuivre l’analyse en l’état provoquerait des erreurs.
// ////////////////////////////////////////////////////////////////////////////////
    if(window.PAGE.substring(0,6)=='public') return false;
// ////////////////////////////////////////////////////////////////////////////////

    var is_menu_ouvert = false;

    /**
     * MENU - Déploiement au clic (pas au survol car les "tunnels forcés invisibles" sont pénibles (http://www.pompage.net/traduction/menu-survol-et-utilisateurs).
     */
    $('#menu').on
    (
      'click',
      'a',
      function()
      {
        var obj_ul = $(this).next('ul');
        if(typeof(obj_ul!=='undefined'))
        {
          var is_sous_niveau_ouvert = (obj_ul.css('display')=='block') ? true : false ;
          var is_premier_niveau = ($(this).hasClass('boussole')) ? true : false ;
          if(is_premier_niveau)
          {
            $(this).next('ul').css('display','none').find('ul').css('display','none');
            $(this).parent('li').css('background','#88F').find('li').css('background','#88F');
            if(window.MENU)
              {
                // À l’ouverture, style du sous-menu en cours mis en valeur pour faciliter le repérage
                // Fonctionne sauf pour les menus des bilans officiels car c’est plus complexe... mais bon c’est déjà ça.
                $('#menu_li_'+window.MENU).css('background','#55F').parent('ul').css('display','block').parent('li').css('background','#55F');
              }
          }
          else
          {
            $(this).parent().parent().find('ul').css('display','none');
            $(this).parent().parent().find('li').css('background','#88F');
          }
          if(is_sous_niveau_ouvert)
          {
            obj_ul.css('display','none');
          }
          else
          {
            obj_ul.css('display','block');
            $(this).parent('li').css('background','#55F');
          }
          if( is_premier_niveau && is_menu_ouvert )
          {
            is_menu_ouvert = false;
            $('#cadre_bas').css('opacity',1);
          }
          else
          {
            is_menu_ouvert = true;
            $('#cadre_bas').css('opacity',0.05);
          }
        }
      }
    );

    /**
     * MENU - Le masquer si on clique ailleurs.
     * @see http://www.codesynthesis.co.uk/code-snippets/use-jquery-to-hide-a-div-when-the-user-clicks-outside-of-it
     * @see http://stackoverflow.com/questions/1403615/use-jquery-to-hide-a-div-when-the-user-clicks-outside-of-it
     * @see http://stackoverflow.com/questions/152975/how-to-detect-a-click-outside-an-element
     * @see https://css-tricks.com/dangers-stopping-event-propagation/
     */
    $(document).on
    (
      'click',
      function(event)
      {
        if ( is_menu_ouvert && !$(event.target).closest('#menu').length )
        {
          $('a.boussole').click();
        }
      }
    );

    /**
     * Bascule entre un dispositif tactile ou un dispositif de pointage
     */
    $('#touchchange').click
    (
      function()
      {
        var new_value = $(this).children().hasClass('dispositif_pointage') ? 1 : 0 ;
        SetCookie('SACoche-is-tactile',new_value);
        document.location.reload();
      }
    );

    /**
     * Select multiples remplacés par une liste de checkbox (code plus lourd, mais résultat plus maniable pour l’utilisateur)
     * - modifier le style du parent d’un chekbox coché (non réalisable en css)
     * - réagir aux clics pour tout cocher / tout décocher / tout échanger / filtrer
     */
    $(document).on
    (
      'change',
      'span.select_multiple input',
      function()
      {
        if(this.checked)
        {
          $(this).parent().addClass('check');
        }
        else
        {
          $(this).parent().removeAttr('class');
        }
      }
    );

    $(document).on
    (
      'click',
      'span.check_multiple q.cocher_tout',
      function()
      {
        var obj_select_multiple = $(this).parent().prev();
        obj_select_multiple.find('input[type=checkbox]').prop('checked',true);
        obj_select_multiple.children('label').addClass('check');
      }
    );

    $(document).on
    (
      'click',
      'span.check_multiple q.cocher_rien',
      function()
      {
        var obj_select_multiple = $(this).parent().prev();
        obj_select_multiple.find('input[type=checkbox]').prop('checked',false);
        obj_select_multiple.children('label').removeAttr('class');
      }
    );

    $(document).on
    (
      'click',
      'span.check_multiple q.cocher_inverse',
      function()
      {
        var obj_select_multiple = $(this).parent().prev();
        obj_select_multiple.find('input[type=checkbox]').each
        (
          function()
          {
            if($(this).is(':checked'))
            {
              $(this).prop('checked',false);
              $(this).parent().removeAttr('class');
            }
            else
            {
              $(this).prop('checked',true);
              $(this).parent().addClass('check');
            }
          }
        );
      }
    );

    $(document).on
    (
      'click',
      'span.check_multiple q.filtrer',
      function()
      {
        var obj_input = $(this).next('input');
        if(obj_input.length)
        {
          // retirer l’input de saisie du filtre et réactiver les cases à cocher
          obj_input.remove();
          var obj_select_multiple = $(this).parent().prev();
          obj_select_multiple.find('label').each
          (
            function()
            {
              $(this).css('display','block').children('input').prop('disabled',false);
            }
          );
        }
        else
        {
          // ajouter l’input de saisie du filtre
          $(this).after('<input class="filter" type="text" value="" size="8">');
          $(this).next().focus();
        }
      }
    );

    $(document).on
    (
      'keyup',
      'span.check_multiple input',
      function()
      {
        var search = $(this).val().trim().latinize();
        var first = search.substring(0,1);
        if(first=='!')
        {
          search = search.substring(1);
          var test_compare = false;
        }
        else
        {
          var test_compare = true;
        }
        var regex = (search.length) ? new RegExp(search, 'i') : false ;
        var obj_select_multiple = $(this).parent().prev();
        obj_select_multiple.find('label').each
        (
          function()
          {
            if(regex)
            {
              var value = $(this).text().latinize();
              var test_result = regex.test(value);
            }
            else
            {
              var test_result = test_compare;
            }
            if(test_result==test_compare)
            {
              $(this).css('display','block').children('input').prop('disabled',false);
            }
            else
            {
              $(this).css('display','none').children('input').prop('disabled',true);
            }
          }
        );
      }
    );

    /**
     * Réagir aux clics pour déployer / replier des arbres (matières, items, socle, users)
     */
    $('.arbre_dynamique li span').siblings('ul').hide('fast');
    $(document).on
    (
      'click',
      '.arbre_dynamique li span',
      function()
      {
        $(this).siblings('ul').toggle();
      }
    );

    /**
     * Réagir aux clics pour cocher / décocher un ensemble de cases d’un arbre (items)
     */
    $('.arbre_check').on
    (
      'click',
      'q.cocher_tout',
      function()
      {
        $(this).parent().find('ul').show();
        $(this).parent().find('input[type=checkbox]').prop('checked',true);
      }
    );
    $('.arbre_check').on
    (
      'click',
      'q.cocher_rien',
      function()
      {
        $(this).parent().find('ul').hide();
        $(this).parent().find('input[type=checkbox]').prop('checked',false);
      }
    );

    /**
     * Réagir aux clics pour déployer / contracter l’ensemble d’un arbre à une étape donnée
     */
    $(document).on
    (
      'click',
      'q.deployer_m1 , q.deployer_m2 , q.deployer_n1 , q.deployer_n2 , q.deployer_n3',
      function()
      {
        var stade = $(this).attr('class').substring(9); // 'deployer_' + stade
        var id_arbre = $(this).parent().parent().attr('id');
        $('#'+id_arbre+' ul').css('display','none');
        switch(stade)
        {
          case 'n3' :  // item
            $('#'+id_arbre+' ul.ul_n3').css('display','block');
          case 'n2' :  // thème
            $('#'+id_arbre+' ul.ul_n2').css('display','block');
          case 'n1' :  // domaine
            $('#'+id_arbre+' ul.ul_n1').css('display','block');
          case 'm2' :  // niveau
            $('#'+id_arbre+' ul.ul_m2').css('display','block');
          case 'm1' :  // matière
            $('#'+id_arbre+' ul.ul_m1').css('display','block');
        }
      }
    );

    /**
     * Réagir aux clics quand on coche/décoche un élève d’une arborescence pour le répercuter sur d’autres regroupements
     */
    $('#zone_eleve').on
    (
      'click',
      'input[type=checkbox]',
      function()
      {
        var tab_id = $(this).attr('id').split('_');
        var id_debut = 'id_'+tab_id[1]+'_';
        var etat = ($(this).is(':checked')) ? true : false ;
        $('#zone_eleve').find('input[id^='+id_debut+']').prop('checked',etat);
      }
    );

    /**
     * Lien pour se déconnecter
     */
    $('#lien_deconnecter').click
    (
      function()
      {
        if(window.DECONNEXION_REDIR!='')
        {
          fermer_session_en_ajax('redirection');
        }
        else if( (window.PROFIL_TYPE!='webmestre') && (window.PROFIL_TYPE!='partenaire') && (window.PROFIL_TYPE!='developpeur') )
        {
          window.document.location.href = './index.php' ;
        }
        else
        {
          window.document.location.href = './index.php?'+window.PROFIL_TYPE ;
        }
      }
    );

    /**
     * Lien pour l'assistance
     */
    $('#lien_assistance').click
    (
      function()
      {
        var load_form = false;
        var url_projet = $('#logo').parent().attr('href');
        var texte = '<h3>Assistance</h3>'
                  + '<p>Chaque page de <em>SACoche</em> comporte un lien vers la documentation dédiée, sous la forme <span class="manuel">DOC&nbsp;: Page de documentation</span>&nbsp;: lisez-là&nbsp;!</p>'
                  + '<p>La documentation exhaustive est aussi disponible sur <a href="'+url_projet+'?page=documentation" target="_blank" rel="noopener noreferrer">le site portail du projet</a>&nbsp;: consultez-là&nbsp;!</p>'
                  + '<p>Vous avez aussi une liste de démarches à suivre depuis <a href="'+url_projet+'?page=support" target="_blank" rel="noopener noreferrer">la page Support / Contact</a>&nbsp;: regardez-les&nbsp;!</p>'
                  ;
        if( (window.PROFIL_TYPE=='directeur') || (window.PROFIL_TYPE=='administrateur') )
        {
          texte += '<form method="post" action="'+url_projet+'?page=support#toggle_anomalie_personnel" target="_blank"><fieldset><label id="label_assistance" class="loader">chargement&hellip;</label></fieldset></form>';
          load_form = true;
        }
        else if(window.PROFIL_TYPE=='professeur')
        {
          texte += '<p class="ascuce">En tant qu’enseignant vous pouvez <a href="'+url_projet+'?page=support#toggle_anomalie_personnel" target="_blank" rel="noopener noreferrer">nous envoyer un courriel</a>, mais nous n’aurons pas l’autorisation d’examiner les données de votre établissement, ce qui limitera nos possibilités d’action&nbsp;; nous vous recommandons donc de formuler ou faire formuler votre demande d’assistance <b>depuis un compte directeur ou administrateur</b>.</p>';
        }
        else if( (window.PROFIL_TYPE=='eleve') || (window.PROFIL_TYPE=='parent') )
        {
          texte += '<p class="danger">En tant qu’élève ou parent, si besoin, il faut <a href="'+url_projet+'?page=support#toggle_eleve_parent" target="_blank" rel="noopener noreferrer">vous rapprocher d’un administrateur de votre établissement</a>, qui contactera l’assistance <em>SACoche</em> si nécessaire.</p>';
        }
        $.fancybox( texte , { minHeight:200 } );
        if(load_form)
        {
          $.ajax
          (
            {
              type : 'POST',
              url : 'ajax.php?page=_form_assistance',
              dataType : 'json',
              error : function(jqXHR, textStatus, errorThrown)
              {
                $('#label_assistance').attr('class','alerte').html(afficher_json_message_erreur(jqXHR,textStatus));
              },
              success : function(responseJSON)
              {
                if(responseJSON['statut']==false)
                {
                  $('#label_assistance').attr('class','alerte').html(responseJSON['value']);
                }
                else
                {
                  $('#label_assistance').replaceWith(responseJSON['value']);
                }
              }
            }
          );
        }
      }
    );

    /**
     * Clic sur une cellule (remplace un champ label, impossible à définir sur plusieurs colonnes)
     */
    $('#table_action').on
    (
      'click',
      'td.label',
      function()
      { 
        $(this).parent().find('input[type=checkbox]:enabled').click();
      }
    );
    $('#table_radio').on
    (
      'click',
      'td.label',
      function()
      { 
        $(this).parent().find('input[type=radio]:enabled').click();
      }
    );

    /**
     * Clic sur un lien afin d’afficher ou de masquer un groupe d’options d’un formulaire
     */
    $('a.toggle').click
    (
      function()
      {
        $('div.toggle').toggle('slow');
        return false;
      }
    );

    /**
     * Clic sur une image-lien pour imprimer un referentiel en consultation
     */
    $(document).on
    (
      'click',
      'q.imprimer_arbre',
      function()
      {
        imprimer( $(this).closest('div').html() );
      }
    );

    /**
     * Clic sur un bouton pour choisir un fichier à uploader (le input[type=file] n’étant pas stylisable, il est caché)
     */
    $(document).on
    (
      'click',
      'button.fichier_import',
      function()
      {
        $(this).prev('input[type=file]').click();
      }
    );

    /**
     * Reporter une appréciation d’un catalogue dans un champ textarea
     */
    $(document).on
    (
      'change',
      'select.catalogue',
      function()
      {
        var catalogue_option = $(this).find('option:selected');
        if( catalogue_option.val() )
        {
          var catalogue_texte = catalogue_option.text();
          var catalogue_id = $(this).attr('id'); // catalogue_[nom-du-textarea]
          var textarea_id  = catalogue_id.substring(10);
          var textarea_obj = $('#'+textarea_id);
          textarea_obj.focus().val( textarea_obj.val() + catalogue_texte.replaceAll('%prenom%', catalogue_prenom).replaceAll('%prénom%', catalogue_prenom) + '\n' );
        }
      }
    );

    /**
     * Actualiser le formulaire select des options d’un catalogue d’appréciations
     */
    $(document).on
    (
      'click',
      'q.catalogue_actualiser',
      function()
      {
        var select_id = $(this).data('id');
        $('#'+select_id).css('opacity',0);
        $.ajax
        (
          {
            type : 'POST',
            url : 'ajax.php?page=compte_catalogue_appreciations',
            data : 'csrf='+window.CSRF+'&f_action=recuperer_options',
            dataType : 'json',
            error : function(jqXHR, textStatus, errorThrown)
            {
              $('#'+select_id).html('<option value="" disabled>'+afficher_json_message_erreur(jqXHR,textStatus)+'</option>').css('opacity',1);
            },
            success : function(responseJSON)
            {
              if(responseJSON['statut']==false)
              {
                $('#'+select_id).html('<option value="" disabled>'+responseJSON['value']+'</option>').css('opacity',1);
              }
              else
              {
                $('#'+select_id).html( responseJSON['value'] ).css('opacity',1);
                if( $('#source_catalogue').length )
                {
                  $('#source_catalogue').html( responseJSON['value'] );
                  initialiser_compteur();
                }
              }
            }
          }
        );
      }
    );

    /**
     * Gestion de la durée d’inactivité
     *
     * Fonction tester_compteur() à appeler régulièrement (un diviseur de 60s).
     */
    initialiser_compteur();
    timerConnexion = setInterval( tester_compteur, 15000);

    /**
     * Lancer une mise à jour complémentaire de la base par morceaux
     */
    if(window.BASE_MAJ_DECALEE)
    {
      maj_base_complementaire();
    }

    /**
     * Ajoute au document un calque qui est utilisé pour afficher un calendrier
     */
    $('<div id="calque"></div>').appendTo(document.body).hide();
    var leave_erreur = false;
    var champ_date_id = false;

    /**
     * Afficher le calque et le compléter : calendrier
     */
    $(document).on
    (
      'click',
      'q.date_calendrier',
      function(e)
      {
        var obj_calque = $('#calque');
        // Récupérer les infos associées
        champ_date_id   = $(this).prev().attr('id');    // champ dans lequel retourner les valeurs
        var date_fr = $(this).prev().val();
        var tab_date = date_fr.split('/');
        if(tab_date.length==3)
        {
          var jour  = tab_date[0];
          var mois  = tab_date[1];
          var annee = tab_date[2];
          var get_data = 'j='+jour+'&m='+mois+'&a='+annee;
        }
        else
        {
          var get_data='';
        }
        // Afficher le calque
        var posX = e.pageX-5;
        var posY = e.pageY-5;
        obj_calque.css('left',posX + 'px');
        obj_calque.css('top' ,posY + 'px');
        obj_calque.html('<label id="ajax_alerte_calque" class="loader">En cours&hellip;</label>').show();
        // Charger en Ajax le contenu du calque
        $.ajax
        (
          {
            type : 'GET',
            url : 'ajax.php?page=calque_date_calendrier',
            data : get_data,
            dataType : 'json',
            error : function(jqXHR, textStatus, errorThrown)
            {
              $('#ajax_alerte_calque').attr('class','alerte').html(afficher_json_message_erreur(jqXHR,textStatus));
              leave_erreur = true;
            },
            success : function(responseJSON)
            {
              if(responseJSON['statut']==true)
              {
                obj_calque.html(responseJSON['value']);
                leave_erreur = false;
              }
              else
              {
                $('#ajax_alerte_calque').attr('class','alerte').html(responseJSON['value']);
                leave_erreur = true;
              }
            }
          }
        );
      }
    );

    // Masquer le calque ; mouseout ne fonctionne pas à cause des éléments contenus dans le div ; mouseleave est mieux, mais pb qd même avec les select du calendrier
    $('#calque').mouseleave
    (
      function()
      {
        if(leave_erreur)
        {
          $('#calque').html('').hide();
        }
      }
    );

    // Fermer le calque
    $(document).on
    (
      'click',
      '#form_calque #fermer_calque',
      function()
      {
        $('#calque').html('').hide();
        return false;
      }
    );

    // Envoyer dans l’input une date du calendrier
    $(document).on
    (
      'click',
      '#choix_jour a',
      function()
      {
        var retour = $(this).attr('href').substring(0,10); // substring() car si l’identifiant de session est passé dans l’URL (session.use-trans-sid à ON) on peut récolter un truc comme "14/08/2012?SACoche-session=507ac2c6e1007ce8d311ab221fb41aeabaf879f79317c" !
        $('#'+champ_date_id).val( retour.replaceAll('-','/') ).change().focus();
        $('#calque').html('').hide();
        return false;
      }
    );

    // Recharger le calendrier
    function reload_calendrier(mois,annee)
    {
      $.ajax
      (
        {
          type : 'GET',
          url : 'ajax.php?page=calque_date_calendrier',
          data : 'm='+mois+'&a='+annee,
          dataType : 'json',
          error : function(jqXHR, textStatus, errorThrown)
          {
            $('#calque').html('<label class="alerte">'+afficher_json_message_erreur(jqXHR,textStatus)+'</label>');
          },
          success : function(responseJSON)
          {
            if(responseJSON['statut']==true)
            {
              $('#calque').html(responseJSON['value']);
            }
            else
            {
              $('#calque').html('<label class="alerte">'+responseJSON['value']+'</label>');
            }
          }
        }
      );
    }
    $(document).on
    (
      'change',
      '#navig_annee select',
      function()
      {
        var m = $('#m option:selected').val();
        var a = $('#a option:selected').val();
        reload_calendrier(m,a);
        return false;
      }
    );
    $(document).on
    (
      'click',
      '#navig_mois a',
      function()
      {
        var tab = $(this).attr('id').split('_'); // 'calendrier_' + mois + '_' + année
        var m = tab[1];
        var a = tab[2];
        reload_calendrier(m,a);
        return false;
      }
    );
    $(document).on
    (
      'click',
      '#navig_titre a',
      function()
      {
        var tab = $(this).attr('id').split('_'); // 'calendrier_' + mois + '_' + année
        var m = tab[1];
        var a = tab[2];
        reload_calendrier(m,a);
        return false;
      }
    );

    /**
     * Actions sur le cadre photo
     */

    if($('#cadre_photo').length)
    {
      // Voir / masquer une photo
      $('#cadre_photo').on( 'click', '#voir_photo',    function() { charger_photo_eleve(memo_eleve,'init'); } ); // Requière la variable memo_eleve
      $('#cadre_photo').on( 'click', '#masquer_photo', function() { $('#cadre_photo').html('<button id="voir_photo" type="button" class="voir_photo">Photo</button>'); } );
      // Élement saisissable / déplaçable
      $( '#cadre_photo' ).draggable({cursor:'move'});
    }

    /**
     * Gestion d’une demande d’évaluation par un élève depuis un bilan.
     * On peut aussi passer ici avec un compte parent, ou un compte personnel pour la page "evaluation_voir" (sinon c’est dans releve_html.js).
     */

    // Fonction suivant l’envoi du formulaire (avec jquery.form.js)
    // À déclarer en dehors d'un if{...} pour éviter un bug avec "user strict" sur d'anciennes versions de Firefox (45)
    function retour_form_erreur_demande_evaluation(jqXHR, textStatus, errorThrown)
    {
      $('#f_demande_evaluation_document').clearFields(); // Sinon si on fournit de nouveau un fichier de même nom alors l’événement change() ne se déclenche pas
      $('#bouton_choisir_demande_evaluation_document').prop('disabled',false);
      $('#ajax_demande_evaluation_document').attr('class','alerte').html(afficher_json_message_erreur(jqXHR,textStatus));
    }
    // Fonction suivant l’envoi du formulaire (avec jquery.form.js)
    // À déclarer en dehors d'un if{...} pour éviter un bug avec "user strict" sur d'anciennes versions de Firefox (45)
    function retour_form_valide_demande_evaluation(responseJSON)
    {
      $('#f_demande_evaluation_document').clearFields(); // Sinon si on fournit de nouveau un fichier de même nom alors l’événement change() ne se déclenche pas
      $('#bouton_choisir_demande_evaluation_document').prop('disabled',false);
      if(responseJSON['statut']==false)
      {
        $('#ajax_demande_evaluation_document').attr('class','alerte').html(responseJSON['value']);
      }
      else
      {
        initialiser_compteur();
        var doc_nom = responseJSON['nom'];
        var doc_url = responseJSON['url'];
        var doc_ext = doc_url.split('.').pop().toLowerCase();
        $('#f_doc_nom').val(doc_nom);
        $('#ajax_demande_evaluation_document').attr('class','valide').html('<a href="'+doc_url+'" target="_blank" rel="noopener noreferrer">'+doc_nom+'</a>');
      }
    }

    $(document).on
    (
      'click',
      'q.demander_add',
      function()
      {
        if(window.PROFIL_TYPE!='eleve')
        {
          // boite modale avec bouton de fermeture #fermer_demande_evaluation afin de pouvoir ré-afficher le fancybox de la page "evaluation_voir"
          $.fancybox( '<div class="astuce">Panier uniquement disposé pour information.<br>Les demandes d’évaluations s’effectuent depuis un compte élève.<br> <button id="fermer_demande_evaluation" type="button" class="retourner">Retour.</button></div>' , { modal:true } );
          return false;
        }
        else
        {
          // Récupérer les infos associées
          var obj_parent = $(this).parent(); // peut être un <td> (relevés) ou un <div> (synthèses)
          var matiere_id = obj_parent.data('matiere');
          var item_id    = obj_parent.data('item');
          var score      = $(this).data('score');
          var debut_date = $(this).data('date');
          // Récupérer le nombre de profs potentiellement concernés
          $.fancybox( '<label class="loader">'+'En cours&hellip;'+'</label>' );
          $.ajax
          (
            {
              type : 'POST',
              url : 'ajax.php?page=evaluation_demande_eleve_ajout',
              data : 'f_action=charger_formulaire'+'&'+'f_matiere_id='+matiere_id+'&'+'f_item_id='+item_id,
              dataType : 'json',
              error : function(jqXHR, textStatus, errorThrown)
              {
                $.fancybox( '<label class="alerte">'+afficher_json_message_erreur(jqXHR,textStatus)+'</label>' );
              },
              success : function(responseJSON)
              {
                if(responseJSON['statut']==false)
                {
                  $.fancybox( '<label class="alerte">'+responseJSON['value']+'</label>' );
                }
                else
                {
                  var contenu = '<h2>Formuler une demande d’évaluation</h2>'
                              + '<form action="#" method="post" id="form_demande_evaluation">'
                              + '<p class="b">'+responseJSON['item_nom']+'</p>'
                              + '<p><label class="tab">Destinaire(s) :</label><select id="f_demande_prof_id" name="f_prof_id">'+responseJSON['options_prof']+'</select></p>'
                              + '<p><label class="tab">Message (facultatif) :</label><textarea id="zone_message" name="f_message" rows="5" cols="75"></textarea><br><span class="tab"></span><label id="zone_message_reste"></label></p>'
                              + '<div><label class="tab">Document (facultatif) :</label><input id="f_demande_evaluation_document" type="file" name="userfile"><button id="bouton_choisir_demande_evaluation_document" type="button" class="fichier_import">Choisir un fichier.</button><label id="ajax_demande_evaluation_document"></label><input type="hidden" id="f_demande_evaluation_action" name="f_action" value=""><input id="f_doc_nom" name="f_doc_nom" type="hidden" value=""></div>'
                              + '<p><span class="tab"></span><input name="f_matiere_id" type="hidden" value="'+matiere_id+'"><input name="f_item_id" type="hidden" value="'+item_id+'"><input name="f_score" type="hidden" value="'+score+'"><input name="f_debut_date" type="hidden" value="'+debut_date+'">'
                              + '<button id="confirmer_demande_evaluation" type="button" class="valider">Confirmer.</button> <button id="fermer_demande_evaluation" type="button" class="annuler">Annuler.</button><label id="ajax_msg_confirmer_demande"></label></p>'
                              + '</form>';
                  $.fancybox( contenu , { modal:true } );
                  $('#form_demande_evaluation textarea').focus();
                  // Indiquer le nombre de caractères restants autorisés dans le textarea
                  // input permet d’intercepter à la fois les saisies au clavier et les copier-coller à la souris (clic droit)
                  $(document).on( 'input' , '#zone_message' , function() { afficher_textarea_reste( $(this) , nb_caracteres_max ); } );
                  // Le formulaire qui va être analysé et traité en AJAX
                  var formulaire_demande_evaluation = $('#form_demande_evaluation');
                  // Options d’envoi du formulaire (avec jquery.form.js)
                  var ajaxOptions_demande_evaluation =
                  {
                    url : 'ajax.php?page=evaluation_demande_eleve_ajout',
                    type : 'POST',
                    dataType : 'json',
                    clearForm : false,
                    resetForm : false,
                    target : '#ajax_demande_evaluation_document',
                    error : retour_form_erreur_demande_evaluation,
                    success : retour_form_valide_demande_evaluation
                  };

                  // Vérifications précédant l’envoi du formulaire, déclenchées au choix d’un fichier
                  $('#f_demande_evaluation_document').change
                  (
                    function()
                    {
                      var file = this.files[0];
                      if( typeof(file) == 'undefined' )
                      {
                        $('#ajax_demande_evaluation_document').removeAttr('class').html('');
                        return false;
                      }
                      else
                      {
                        $('#f_doc_nom').val('');
                        $('#f_demande_evaluation_action').val('uploader_document');
                        var fichier_nom = file.name;
                        var fichier_ext = fichier_nom.split('.').pop().toLowerCase();
                        // La liste suivante n’est pas exhaustive, une vérification plus complète est ensuite effectuée en PHP.
                        if( '.bat.com.exe.php.html.htm.xml.svg.'.indexOf('.'+fichier_ext+'.') !== -1 )
                        {
                          $('#ajax_demande_evaluation_document').attr('class','erreur').html('Format de fichier interdit.');
                          return false;
                        }
                        else
                        {
                          $('#bouton_choisir_demande_evaluation_document').prop('disabled',true);
                          $('#ajax_demande_evaluation_document').attr('class','loader').html('En cours&hellip;');
                          formulaire_demande_evaluation.submit();
                        }
                      }
                    }
                  );
                  // Envoi du formulaire (avec jquery.form.js)
                  formulaire_demande_evaluation.submit
                  (
                    function()
                    {
                      $(this).ajaxSubmit(ajaxOptions_demande_evaluation);
                      return false;
                    }
                  );
                }
                $('#form_demande_evaluation button').prop('disabled',false);
              }
            }
          );
        }
      }
    );

    $(document).on
    (
      'click',
      '#fermer_demande_evaluation',
      function()
      {
        if(window.PAGE!='evaluation_voir')
        {
          $.fancybox.close();
        }
        else
        {
          $.fancybox( { href:'#zone_eval_voir' } );
        }
        return false;
      }
    );

    $(document).on
    (
      'click',
      '#confirmer_demande_evaluation',
      function()
      {
        if( $('#f_demande_prof_id option:selected').val() === '' )
        {
          $('#ajax_msg_confirmer_demande').attr('class','erreur').html('Choisir un destinataire.');
        }
        else
        {
          $('#f_demande_evaluation_action').val('confirmer_ajout');
          $('#form_demande_evaluation button').prop('disabled',true);
          $('#ajax_msg_confirmer_demande').attr('class','loader').html('En cours&hellip;');
          $.ajax
          (
            {
              type : 'POST',
              url : 'ajax.php?page=evaluation_demande_eleve_ajout',
              data : $('#form_demande_evaluation').serialize(),
              dataType : 'json',
              error : function(jqXHR, textStatus, errorThrown)
              {
                $('#ajax_msg_confirmer_demande').attr('class','alerte').html(afficher_json_message_erreur(jqXHR,textStatus));
                $('#form_demande_evaluation button').prop('disabled',false);
              },
              success : function(responseJSON)
              {
                if(responseJSON['statut']==false)
                {
                  $('#ajax_msg_confirmer_demande').attr('class','alerte').html(responseJSON['value']);
                }
                else
                {
                  $('#form_demande_evaluation').html( responseJSON['value'] + '<p><span class="tab"></span><button id="fermer_demande_evaluation" type="button" class="retourner">Fermer.</button></p>' );
                  if (typeof(window.DUREE_AUTORISEE)!=='undefined')
                  {
                    initialiser_compteur(); // Ne modifier l’état du compteur que si l’appel ne provient pas d’une page HTML de bilan
                  }
                }
                $('#form_demande_evaluation button').prop('disabled',false);
              }
            }
          );
        }
      }
    );

  }
);
