diff -urN cahier-de-prepa3.2.0/admin/bas.php cahier-de-prepa4.0.0/admin/bas.php
--- cahier-de-prepa3.2.0/admin/bas.php	1970-01-01 01:00:00.000000000 +0100
+++ cahier-de-prepa4.0.0/admin/bas.php	2014-08-25 08:48:18.608678629 +0200
@@ -0,0 +1,34 @@
+</div>
+
+</div>
+<div id="bas">Ce site est réalisé par le logiciel <a href="http://cahier-de-prepa.fr">Cahier de prépa</a>, publié sous <a href="Licence_CeCILL_V2-fr.html">licence libre</a>.</div>
+
+<script type="text/javascript">
+$( function() {
+  $(window).on('load resize',function() {
+    if ( $('#menu').position().top + $('#menu').outerHeight(true) > $('#bas').position().top )  {
+      $('#menu h3').parent().children(':not(h3)').hide();
+      $('#menu h3').addClass('plie').parent().hover( function () {
+        $(this).children(':not(h3)').toggle();
+      });;
+      $('#menu h3').click( function () {
+        if ( $(this).hasClass('plie') )  {
+          $(this).removeClass('plie');
+          $(this).parent().off('mouseenter mouseleave');
+          $(this).parent().children(':not(h3)').show();
+        }
+        else
+          $(this).parent().children(':not(h3)').toggle();
+      });
+    }
+    else  {
+      $('#menu h3').parent().off('mouseenter mouseleave');
+      $('#menu h3').removeClass('plie').off('click');
+      $('#menu h3').parent().children(':not(h3)').show();
+    }
+  });
+});
+</script>
+
+</body>
+</html>
diff -urN cahier-de-prepa3.2.0/admin/cdt.php cahier-de-prepa4.0.0/admin/cdt.php
--- cahier-de-prepa3.2.0/admin/cdt.php	1970-01-01 01:00:00.000000000 +0100
+++ cahier-de-prepa4.0.0/admin/cdt.php	2014-08-27 02:21:43.109465991 +0200
@@ -0,0 +1,493 @@
+<?php
+// Sécurité et récupération des données de configuration
+define('OK',1);
+include('debut.php'); // fournit $mysqli et $matieres
+
+///////////////////////////////////////////////
+// Validation de la requête : matière, n, nb //
+///////////////////////////////////////////////
+
+// Recherche de la matière concernée
+if ( !empty($_REQUEST) )
+  foreach ( $matieres as $r )
+    if ( isset($_REQUEST[$r['cle']]) )  {
+      $matiere = $r;
+      $mid = $matiere['id'];
+      break;
+    }
+// Si mauvaise demande
+if ( !isset($matiere) )  {
+  if ( strlen($_SESSION['matieres']) )
+    exit('<p>Mauvais paramètre d\'accès à cette page. Vous n\'avez accès qu\'aux matières associées à votre compte et l\'adresse doit comporter la clé de la matière. Vous pouvez chager vos matières associées dans vos <a href="prefs">Préférences</a>.</p><p>Il est préférable de passer par les lien du menu&nbsp; <a href=".">retour à l\'accueil</a></p>');
+  else
+    exit('<p>Vous ne pourrez afficher cette page qu\'après avoir associé des matières à votre compte. Il faut aller dans vos <a href="prefs">Préférences</a> pour cela.</p>');
+}
+
+// Récupération des semaines
+$resultat = $mysqli->query("SELECT id, DATE_FORMAT(debut,'%w%Y%m%e') AS debut, vacances FROM semaines");
+$select_semaines = '';
+$semaines = array(0=>'');
+while ( $r = $resultat->fetch_assoc() )  {
+  switch ( $r['vacances'] )  {
+    case 0:
+      $select_semaines .= "\n        <option value=\"${r['id']}\">".format_date($r['debut']).'</option>';
+      $semaines[$r['id']] = 'Semaine du '.format_date($r['debut']);
+      $sid[] = $r['id'];
+      break;
+    case 1:
+      $select_semaines .= "\n        <option value=\"${r['id']}\" disabled>Vacances de Toussaint</option>";
+      $semaines[$r['id']] = ucfirst(format_date($r['debut']))."&nbsp;: Vacances de Toussaint";
+      break;
+    case 2:
+      $select_semaines .= "\n        <option value=\"${r['id']}\" disabled>Vacances de Noël</option>";
+      $semaines[$r['id']] = ucfirst(format_date($r['debut']))."&nbsp;: Vacances de Noël";
+      break;
+    case 3:
+      $select_semaines .= "\n        <option value=\"${r['id']}\" disabled>Vacances d'hiver</option>";
+      $semaines[$r['id']] = ucfirst(format_date($r['debut']))."&nbsp;: Vacances d'hiver";
+      break;
+    case 4:
+      $select_semaines .= "\n        <option value=\"${r['id']}\" disabled>Vacances de Pâques</option>";
+      $semaines[$r['id']] = ucfirst(format_date($r['debut']))."&nbsp;: Vacances de Pâques";
+      break;
+  }
+}
+$resultat->free();
+$ntotal = count($sid);
+
+// Gestion de la demande des semaines à afficher
+if ( isset($_REQUEST['depuisledebut']) )  {
+  $n = 1;
+  $m = 0;
+  $nb = $ntotal;
+}
+else  {
+  // Validation de n (numéro de semaine à voir)
+  if ( isset($_REQUEST['n']) && is_numeric($n = $_REQUEST['n']) )
+    while ( !in_array($n,$sid) )
+      $n = ( $n < $ntotal ) ? $n+1 : 1;
+  // Sans argument, on souhaite afficher la semaine actuelle
+  else {
+    $date = ( isset($_REQUEST['date']) && preg_match('/\d{4}-\d{2}-\d{2}/',$_REQUEST['date']) ) ? '\''.trim($_REQUEST['date'],'\'').'\'' : 'NOW()';
+    $resultat = $mysqli->query("SELECT IF(vacances,(SELECT id FROM semaines WHERE debut > $date AND vacances = 0 LIMIT 1),id) AS id
+                                FROM semaines WHERE debut < $date ORDER BY debut DESC LIMIT 1");
+    if ( $resultat->num_rows )  {
+      $r = $resultat->fetch_assoc();
+      $n = $r['id'];
+      $resultat->free();
+    }
+    else
+      $n = 1;
+  }
+  $m = array_search($n,$sid);
+
+  // Validation de nb (nombre de semaines à voir)
+  if ( isset($_REQUEST['nb']) && is_numeric($nb = $_REQUEST['nb']) )  {
+    if ( $nb < 1 )
+      $nb = 1;
+    elseif ( $nb > $ntotal - $m )
+      $nb = $ntotal - $m;
+  }
+  else
+    // Par défaut : affichage d'une seule semaine
+    $nb = 1;
+}
+
+// Récupération des types de séances
+$resultat = $mysqli->query("SELECT id, cle, deb_fin_pour, nb FROM `cdt-types` WHERE matiere = $mid");
+$select_types = $recherche_types = '';
+if ( isset($_REQUEST['type']) )  {
+  while ( $r = $resultat->fetch_assoc() )  {
+    $types[$r['id']] = $r;  
+    if ( $r['cle'] == $_REQUEST['type'] )  {
+      // Select uniquement pour le formulaire
+      if ( $r['nb'] )
+        $select_types .= "\n        <option value=\"${r['cle']}\" selected>les ${r['cle']}</option>";
+      $recherche_types = " AND t.cle = '${r['cle']}'";
+    }
+    else
+      $select_types .= "\n        <option value=\"${r['cle']}\">les ${r['cle']}</option>"; 
+  }
+}
+else
+  while ( $r = $resultat->fetch_assoc() )  {
+    // Select uniquement pour le formulaire
+      if ( $r['nb'] )
+      $select_types .= "\n        <option value=\"${r['cle']}\">les ${r['cle']}</option>"; 
+    $types[$r['id']] = $r;  
+  }
+$resultat->free();
+
+///////////////////
+// Modifications //
+///////////////////
+if ( isset($_REQUEST['id']) && is_numeric($id = $_REQUEST['id']) )  {
+  // Connexion à la base de données
+  $mysqli->close();
+  $mysqli = mysql_ecriture();
+
+  // Vérification que l'identifiant est valide. Défaut : id=0 (nouvelle entrée)
+  if ( $id )  {
+    $resultat = $mysqli->query("SELECT id FROM cdt WHERE matiere = $mid AND id = $id");
+    if ( $resultat->num_rows )
+      $resultat->free();
+    else
+      $id = 0;
+  }
+  
+  // Traitement d'un ajout/modification
+  if ( isset($_REQUEST['modifie']) && strlen($_REQUEST['texte']) )  {
+    
+    // Validation du jour : à mettre dans la bonne semaine
+    $jour = preg_filter('/(\d{2})\/(\d{2})\/(\d{4})/','$3-$2-$1',$_REQUEST['jour']);
+    $resultat = $mysqli->query("SELECT id FROM semaines WHERE debut <= '$jour' ORDER BY debut DESC LIMIT 1");
+    if ( $resultat->num_rows )  {
+      $r = $resultat->fetch_assoc();
+      $resultat->free();
+      if ( ( $s = array_search($r['id'],$sid) ) !== false )  {
+        // Correction éventuelle de n et nb pour que l'entrée soit visible après enregistrement
+        if ( ( $s < $m ) || ( $m+$nb <= $s ) )  {
+          $n = $r['id'];
+          $m = array_search($n,$sid);
+          $nb = 1;
+        }
+        
+        // Validation des autres données envoyées
+        $tid = ( array_key_exists($_REQUEST['tid'],$types) ) ? $_REQUEST['tid'] : key($types);
+        $h_debut = $h_fin = '0:00';
+        $pour = '0000/00/00';
+        switch ( $types[$tid]['deb_fin_pour'] )  {
+          case 1:
+            $h_fin = preg_filter('/(\d{1,2})h(\d{2})/','$1:$2',$_REQUEST['h_fin']);
+          case 0:
+            $h_debut = preg_filter('/(\d{1,2})h(\d{2})/','$1:$2',$_REQUEST['h_debut']);
+            break;
+          case 2:
+            $pour = preg_filter('/(\d{2})\/(\d{2})\/(\d{4})/','$3-$2-$1',$_REQUEST['pour']);
+          // Cas 3, 4 et 5 : h_debut, h_fin et pour restent nuls (cf l'aide)
+        }
+        $texte = $mysqli->real_escape_string($_REQUEST['texte']);
+        $demigroupe = ( isset($_REQUEST['demigroupe']) ) ? 1 : 0;
+
+        // Si $id > 0 : modification d'une entrée existante. Si $id = 0, nouvelle entrée
+        // Ordre : d'abord ce qui n'a pas de fin, ensuite les séances "normales", ensuite les "pour le"
+        if ( $id )
+          $message = ( requete('cdt',"UPDATE cdt SET semaine = ${r['id']},
+                                      jour = '$jour', h_debut = '$h_debut', h_fin = '$h_fin', pour = '$pour',
+                                      type = $tid, texte = '$texte', demigroupe = $demigroupe WHERE id = $id")
+          ) ? 'L\'entrée du cahier de texte a bien été modifiée.' : 'L\'entrée du cahier de texte n\'a pas pu être modifiée. Erreur MySQL n°'.$mysqli->errno.', «'.$mysqli->error.'».';
+        else  {
+          $cache = ( isset($_REQUEST['cache']) ) ? 1 : 0;
+          $message = ( requete('cdt',"INSERT INTO cdt SET semaine = ${r['id']}, matiere = $mid,
+                                      jour = '$jour', h_debut = '$h_debut', h_fin = '$h_fin', pour = '$pour',
+                                      type = $tid, texte = '$texte', demigroupe = $demigroupe, cache = $cache")
+          ) ? 'L\'entrée du cahier de texte a bien été ajoutée.' : 'L\'entrée du cahier de texte n\'a pas pu être ajoutée. Erreur MySQL n°'.$mysqli->errno.', «'.$mysqli->error.'».';
+        }
+        $mysqli->query('ALTER TABLE cdt ORDER BY jour,matiere,pour,h_debut,h_fin,type');
+      }
+      else
+        $message = 'La date saisie se situe pendant les vacances scolaires.';
+    }
+    else
+      $message = 'La date saisie se situe avant le début de l\'année scolaire.';
+  }
+
+  elseif ( $id )  {
+
+    // Positionnement "montré" (apparaît sur la partie publique)
+    if ( isset($_REQUEST['montre']) )
+      $message = ( requete('cdt',"UPDATE cdt SET cache = 0 WHERE id = $id") ) ? 'L\'entrée du cahier de texte a bien été diffusée, elle apparaît désormais sur la partie publique.' : 'L\'entrée du cahier de texte n\'a pas pu être diffusée. Erreur MySQL n°'.$mysqli->errno.', «'.$mysqli->error.'».';
+
+    // Positionnement "caché" (n'apparaît plus sur la partie publique)
+    elseif ( isset($_REQUEST['cache']) )
+      $message = ( requete('cdt',"UPDATE cdt SET cache = 1 WHERE id = $id") ) ? 'L\'entrée du cahier de texte n\'est plus diffusée&nbsp;: elle n\'apparaît plus dans la partie publique mais est toujours disponible ici pour modification ou diffusion.' : 'L\'entrée du cahier de texte n\'a pas pu être cachée. Erreur MySQL n°'.$mysqli->errno.', «'.$mysqli->error.'».';
+
+    // Suppression
+    elseif ( isset($_REQUEST['supprime']) || !strlen($texte) )
+      $message = ( requete('cdt',"DELETE FROM cdt WHERE id = $id") ) ? 'L\'entrée du cahier de texte a bien été supprimée.' : 'L\'entrée du cahier de texte n\'a pas pu être supprimée. Erreur MySQL n°'.$mysqli->errno.', «'.$mysqli->error.'».';
+  }
+    
+  // Mise à jour des champs 'cdt' dans la table 'matieres' (pour le menu)
+  $mysqli->query('UPDATE matieres SET cdt = IF((SELECT id FROM cdt WHERE matiere = matieres.id AND cache = 0 LIMIT 1),1,0)');
+
+  // Mise à jour des champs 'nb' et nb_v dans la table 'cdt-types'
+  $mysqli->query('UPDATE `cdt-types` SET
+    nb = (SELECT COUNT(cdt.id) FROM cdt WHERE cdt.type = `cdt-types`.id),
+    nb_v = (SELECT COUNT(cdt.id) FROM cdt WHERE cdt.type = `cdt-types`.id AND cdt.cache = 0)');
+
+  // Passage en connexion MySQL en lecture seulement
+  $mysqli->close();
+  $mysqli = mysql_lecture();
+}
+
+////////////
+/// HTML ///
+////////////
+$p = "cdt?${matiere['cle']}";
+$t = "Cahier de texte - ${matiere['nom']}";
+
+// Haut de page, menu et message
+include('haut.php');
+
+// Formulaire de la demande des semaines à afficher
+$select_semaines = str_replace("\"$n\"","\"$n\" selected",$select_semaines);
+echo <<<FIN
+
+  <div class="item" id="recherche">
+  <form action="" method="get">
+    <input type="hidden" name="${matiere['cle']}" value="">
+    <p>Afficher&nbsp;
+      <select name="type">
+        <option value="tout">tout</option>$select_types
+      </select>
+      <input type="submit" name="depuisledebut" id="depuisledebut" value="pour toute l'année">
+      ou&nbsp;pendant&nbsp;<input type="text" name="nb" value="$nb" size="2">&nbsp;semaine(s) à partir du&nbsp;
+      <select name="n">$select_semaines
+      </select>
+      <input type="submit" name="" value="OK">
+    </p>
+  </form>
+  </div>
+
+
+FIN;
+
+// Aide générale
+?>
+  <div class="item aide">
+    <h3>Aide et explications</h3>
+    <p>Vous pouvez ci-dessous modifier le cahier de texte de <?php echo $matiere['nom']; ?>.</p>
+    <p>Il est possible d'afficher ci-dessous uniquement un type d'entrée ou certaines semaines. Cela ne conditionne pas la saisie d'une nouvelle entrée (vous pouvez afficher l'ensemble des cours et entrer un td, afficher tout sur septembre et saisir une entrée en octobre).</p>
+    <p>Vous ne pouvez modifier que les cahiers de texte des matières qui vous sont associées. Elles sont modifiables dans vos <a href="prefs">Préférences</a>.</p>
+    <p>Vous pouvez modifier l'accès de l'ensemble du cahier de texte dans les préférences de chaque matière, à la page <a href="prefs">Préférences</a>.</p>
+    <h4>Nouvelle entrée</h4>
+    <p>Le <em>type</em> d'entrée modifie l'affichage : par exemple, un cours a une heure de début et une heure de fin, tandis qu'un devoir maison n'en a pas.</p>
+    <p>Les champs définissant le jour et l'heure apparaîtront si nécessaire lorsque vous modifiez le type d'entrée.</p>
+    <p>La présence ou non des champs pour un type d'entrée donné est <a href="cdt-types?<?php echo $matiere['cle']; ?>">modifiable indépendamment pour chaque matière</a>.</p>
+    <p>La case à cocher <em>Séance en demi-groupe</em> indique que la séance n'a pas eu lieu en classe entière.</p>
+    <p>Le texte doit être formaté en HTML&nbsp;: par exemple, chaque bloc de texte doit être encadré par &lt;p&gt; et &lt;/p&gt;. Il peut donc contenir des liens vers les autres pages du site, vers des documents du site, vers le web... N'hésitez pas à consulter l'aide qui apparaîtra en-dessous de la case de texte après avoir cliqué dessus, ainsi qu'à utiliser les boutons fournis.</p>
+    <p>La case à cocher <em>Ne pas diffuser sur la partie publique</em> permet de cacher temporairement ce programme de colle, par exemple pour le diffuser ultérieurement.</p>
+    <h4>Entrée existante</h4>
+    <p>Vous pouvez <em>cacher</em> une entrée existante et déjà diffusée, c'est-à-dire la rendre non visible sur la partie publique.</p>
+    <p>Vous pouvez <em>montrer</em> une entrée existante et cachée, c'est-à-dire la rendre à nouveau visible sur la partie publique.</p>
+    <p>Vous pouvez <em>supprimer</em> une entrée existante.</p>
+  </div>
+
+  <div class="item">
+    <p>Vous pouvez <a href="cdt-types?<?php echo $matiere['cle']; ?>">modifier les différents types d'entrées</a> du cahier de texte et ainsi d'adapter complètement et exactement l'affichage de votre cahier de texte. Il est notamment possible de ne plus entrer d'horaires (ou seulement certains), de regrouper les entrées par jour ou par semaine. Si vous le faites sans avoir modifié les types d'entrée, vous n'utilisez pas toutes les capacités de Cahier de Prépa&nbsp;! Ces modifications sont indépendants d'une matière à l'autre.</p>
+    <p>Vous pouvez <a href="cdt-seances?<?php echo $matiere['cle']; ?>">définir des boutons de raccourci</a>, propres à chaque matière, qui pré-rempliront les champs type, jour, heures et demi-groupe. Ils apparaîtront dans chaque formulaire d'entrée du cahier de texte.</p>
+  </div>
+
+<?php
+
+// Select des types d'entrée différent de celui du formulaire précédent
+$select_types = '';
+foreach ( $types as $type )
+  $select_types .= "\n          <option value=\"${type['id']}\">".ucfirst($type['cle']).'</option>';
+
+// Fonction d'affichage
+function affichage($r)  {
+  if ( $id = $r['id'] )  {
+    $semaine = array('','Lundi','Mardi','Mercredi','Jeudi','Vendredi','Samedi');
+    $pour = ( strlen($r['pour']) ) ? " pour le ${r['pour']}" : '';
+    switch ( $r['heure'] )  {
+       case 3:
+        $titre = "${semaine[$r['jour']]} ${r['date']}&nbsp;: ${r['titre']}";
+        break;
+      case 4:
+        $titre = "${semaine[$r['jour']]} ${r['date']}";
+        break;
+      case 5:
+        $titre = 'Cahier de texte de la semaine';
+        break;
+      default:
+        $titre = "${semaine[$r['jour']]} ${r['date']}${r['heure']}&nbsp;: ${r['titre']}$pour${r['demigroupe']}";
+    }
+    $valide1 = '';
+    $valide = "\n        <input type=\"submit\" name=\"modifie\" value=\"Valider\" title=\"Valider les modifications\">";
+    $suppr = "\n        <input type=\"submit\" name=\"supprime\" value=\"Supprimer\" title=\"Supprimer l'entrée\">";
+    $cache_nouveau = '';
+    if ( $r['cache'] )  {
+      $titre .= ' (entrée non diffusée sur la partie publique)';
+      $cache_classe = ' cache';
+      $cache = "\n        <input type=\"submit\" name=\"montre\" value=\"Montrer\" title=\"Diffuser l'entrée, la rendre visible sur la partie publique\">";
+    }
+    else  {
+      $cache_classe = '';
+      $cache = "\n        <input type=\"submit\" name=\"cache\" value=\"Cacher\" title=\"Ne plus diffuser l'entrée, la rendre invisible sur la partie publique\">";
+    }
+  }
+  else  {
+    $titre = 'Nouvelle entrée du cahier';
+    $valide1 = "\n    <input class=\"bouton\" type=\"submit\" name=\"modifie\" value=\"Valider\" title=\"Valider les modifications\">";
+    $valide = $cache = $cache_classe = $suppr = '';
+    $cache_nouveau = "\n      <p class=\"ligne\"><label for=\"cache\">Ne pas diffuser sur la partie publique&nbsp;: </label><input type=\"checkbox\" id=\"cache\" name=\"cache\" value=\"1\"></p>";
+  }
+  $demigroupe = ( strlen($r['demigroupe']) ) ? ' checked' : '';
+  $select_types = str_replace("\"${r['tid']}\"","\"${r['tid']}\" selected",$GLOBALS['select_types']);
+  $type = ( isset($_REQUEST['type']) ) ? "&amp;type=${_REQUEST['type']}" : '';
+  echo <<<FIN
+  <div class="item admin$cache_classe">
+  <form action="${GLOBALS['p']}&amp;n=${GLOBALS['n']}&amp;nb=${GLOBALS['nb']}$type" method="post">$valide1
+    <h3>$titre</h3>
+    <div>
+      <p class="boutons">$valide$cache$suppr
+      </p>
+      <p class="ligne"><label for="tid$id">Type&nbsp;:</label>
+        <select id="tid$id" name="tid">$select_types
+        </select>
+      </p>
+      <p class="ligne"><label for="jour$id">Jour&nbsp;: </label><input type="text" class="date" id="jour$id" name="jour" value="${r['date']}" size="8"></p>
+      <p class="ligne"><label for="h_debut$id">Heure de début&nbsp;: </label><input type="text" class="heure" id="h_debut$id" name="h_debut" value="${r['h_debut']}" size="5"></p>
+      <p class="ligne"><label for="h_fin$id">Heure de fin&nbsp;: </label><input type="text" class="heure" id="h_fin$id" name="h_fin" value="${r['h_fin']}" size="5"></p>
+      <p class="ligne"><label for="pour$id">Pour le&nbsp;: </label><input type="text" class="date" id="pour$id" name="pour" value="${r['pour']}" size="8"></p>
+      <p class="ligne"><label for="demigroupe$id">Séance en demi-groupe&nbsp;: </label><input type="checkbox" id="demigroupe$id" name="demigroupe" value="1"$demigroupe></p>
+      <textarea name="texte" class="mat${GLOBALS['mid']}" rows="10" cols="100">${r['texte']}</textarea>$cache_nouveau
+      <input type="hidden" name="id" value="$id">
+    </div>
+  </form>
+  </div>
+
+
+FIN;
+}
+
+// Formulaire vide
+affichage(array('id' => 0, 'texte' => '', 'pour' => '', 'date' => date('d/m/Y'),
+                'h_debut' => '08h00', 'h_fin' => '10h00', 'heure' => '', 'demigroupe' => '', 'cache' => 0, 'tid' => ''));
+
+// Liens de navigation vers semaines précédentes/suivantes
+$nav = '  <div class="item">';
+if ( $m )
+  $nav .= "\n    <a class=\"prec\" href=\"?${matiere['cle']}&amp;n=". $sid[max($m-$nb,0)] ."&amp;nb=$nb\">Semaines précédentes</a>";
+if (  $m+$nb < $ntotal )
+  $nav .= "\n    <a class=\"suiv\" href=\"?${matiere['cle']}&amp;n=". $sid[$m+$nb] ."&amp;nb=$nb\">Semaines suivantes</a>";
+$nav .= "\n  </div>\n\n";
+if ( strlen($nav) > 31 )
+  echo $nav;
+
+// Récupération des entrées
+$resultat = $mysqli->query("SELECT cdt.id, cdt.texte, cdt.semaine,
+                            DATE_FORMAT(cdt.jour,'%w') AS jour, DATE_FORMAT(cdt.jour,'%d/%m/%Y') AS date,
+                            CASE t.deb_fin_pour
+                              WHEN 0 THEN TIME_FORMAT(cdt.h_debut,' à %kh%i')
+                              WHEN 1 THEN CONCAT(TIME_FORMAT(cdt.h_debut,' de %kh%i'),TIME_FORMAT(cdt.h_fin,' à %kh%i'))
+                              WHEN 2 THEN ''
+                              ELSE t.deb_fin_pour END AS heure,
+                            TIME_FORMAT(cdt.h_debut,'%Hh%i') AS h_debut,
+                            TIME_FORMAT(cdt.h_fin,'%Hh%i') AS h_fin,
+                            IF(t.deb_fin_pour = 2,DATE_FORMAT(cdt.pour,'%d/%m/%Y'),'') AS pour,
+                            IF(cdt.demigroupe,' (en demi-groupe)','') AS demigroupe, cdt.cache, t.id AS tid, t.titre
+                            FROM cdt LEFT JOIN `cdt-types` AS t ON t.matiere = $mid AND t.id = cdt.type
+                            WHERE cdt.semaine BETWEEN $n AND ${sid[$m+$nb-1]} AND cdt.matiere = $mid $recherche_types");
+
+// Affichage des résultats
+if ( $resultat->num_rows )  {
+  // On ne garde que les semaines concernées. Il faut conserver la semaine précédente
+  // pour que puisse fonctionner le premier next à l'affichage.
+  $semaines = array_slice($semaines,$n-1,$sid[$m+$nb-1]-$n+2,true);
+  $s = 0;
+  $semaine = array('','Lundi','Mardi','Mercredi','Jeudi','Vendredi','Samedi');
+  while ( $r = $resultat->fetch_assoc() )  {
+    
+    // Nouvelle semaine
+    while ( ( $s != $r['semaine'] ) && next($semaines) )  {
+      echo '  <h2 class="cdtsemaine">'.current($semaines)."</h2>\n\n";
+      $s = key($semaines);
+    }
+    affichage($r);
+  }
+  $resultat->free();
+  // Liens de navigation vers semaines précédentes/suivantes
+  if ( strlen($nav) > 31 )
+    echo $nav;
+}
+else  {
+  if ( !isset($_REQUEST['type']) || ( $_REQUEST['type'] == 'tout' ) )
+    echo "  <h2>Le cahier de texte est vide pour les semaines choisies.</h2>\n\n";
+  else
+    echo "  <h2>Le cahier de texte ne contient pas de «&nbsp;${_REQUEST['type']}&nbsp;» pour les semaines choisies.</h2>\n\n";
+}
+
+// Récupération des boutons
+$resultat = $mysqli->query("SELECT id, nom, jour, TIME_FORMAT(h_debut,'%Hh%i') AS h_debut, TIME_FORMAT(h_fin,'%Hh%i') AS h_fin,
+                            type, demigroupe FROM `cdt-seances` WHERE matiere = $mid");
+$raccourcis = '';
+$raccourcisjs = '';
+if ( $resultat->num_rows )  {
+  while ( $r = $resultat->fetch_assoc() )  {
+    $raccourcis .= "<input type=\"button\" class=\"rac${r['id']}\" value=\"${r['nom']}\"> ";
+    $r['demigroupe'] = ( $r['demigroupe'] ) ? 'true' : 'false';
+    $raccourcisjs .= <<<FIN
+  $('.rac${r['id']}').click(function() {
+    $(this).parent().parent().find('[name="tid"]').val('${r['type']}').change();
+    $(this).parent().parent().find('[id^="h_debut"]').val('${r['h_debut']}');
+    $(this).parent().parent().find('[id^="h_fin"]').val('${r['h_fin']}');
+    $(this).parent().parent().find('[id^="demigroupe"]').attr('checked', ${r['demigroupe']});
+    var today=new Date();
+    var t=today.getDay();
+    t = ${r['jour']}-t;
+    if ( t>0 ) { t-=7; }
+    $(this).parent().parent().find('[id^="jour"]').datepick('setDate',t+'d');
+  });
+
+FIN;
+  }
+  $resultat->free();  
+}
+
+// Disparition automatique des heures et de la date d'échéance
+echo <<<FIN
+  <script type="text/javascript">
+$( function() {
+
+  // Modification du formulaire en fonction du type
+  $('[name="tid"]').change(function() {
+    var p1 = $(this).parent().parent().find('[id^="h_debut"]').parents('.ligne');
+    var p2 = $(this).parent().parent().find('[id^="h_fin"]').parents('.ligne');
+    var p3 = $(this).parent().parent().find('[id^="pour"]').parents('.ligne');
+    switch ( $(this).val() )  {
+FIN;
+foreach ( $types as $id => $type )
+  switch ( $type['deb_fin_pour'] )  {
+    case 0:
+      echo "\n      case '$id': p1.show(); p2.hide(); p3.hide(); break;";
+      break;
+    case 1:
+      echo "\n      case '$id': p1.show(); p2.show(); p3.hide(); break;";
+      break;
+    case 2:
+      echo "\n      case '$id': p1.hide(); p2.hide(); p3.show(); break;";
+      break;
+    default:
+      echo "\n      case '$id': p1.hide(); p2.hide(); p3.hide(); break;";
+  }
+echo <<<FIN
+
+    }
+  });
+  // Modification au chargement
+  $('[name="tid"]').change();
+
+  // Boutons de raccourcis
+  $('<p class="boutons">$raccourcis</p>').clone().insertBefore($('.admin form').find('.ligne:first'));
+$raccourcisjs
+  $('.date').datepick({dateFormat: 'dd/mm/yyyy', showTrigger: '<img src="../js/calendar-blue.gif">'});
+  $('.heure').timeEntry({timeSteps: [1, 15, 0], separator: 'h', spinnerImage: '../js/spinnerDefault.png'});
+
+  // URL plus lisible après clic du bouton "Toute l'année"
+  $('#depuisledebut').click(function() {
+    window.location.search = '?${matiere['cle']}&type='+$(this).prev('[name="type"]').val()+'&depuisledebut';
+    return false;
+  });
+
+});
+  </script>
+
+
+FIN;
+$mysqli->close();
+
+// Bas de page
+include('bas.php');
+?>
diff -urN cahier-de-prepa3.2.0/admin/cdt-seances.php cahier-de-prepa4.0.0/admin/cdt-seances.php
--- cahier-de-prepa3.2.0/admin/cdt-seances.php	1970-01-01 01:00:00.000000000 +0100
+++ cahier-de-prepa4.0.0/admin/cdt-seances.php	2014-08-26 23:29:44.241135786 +0200
@@ -0,0 +1,257 @@
+<?php
+// Sécurité et récupération des données de configuration
+define('OK',1);
+include('debut.php'); // fournit $mysqli et $matieres
+
+// Recherche de la matière concernée
+if ( !empty($_REQUEST) )
+  foreach ( $matieres as $r )
+    if ( isset($_REQUEST[$r['cle']]) )  {
+      $matiere = $r;
+      break;
+    }
+// Si mauvaise demande
+if ( !isset($matiere) )  {
+  if ( strlen($_SESSION['matieres']) )
+    exit('<p>Mauvais paramètre d\'accès à cette page. Vous n\'avez accès qu\'aux matières associées à votre compte et l\'adresse doit comporter la clé de la matière. Vous pouvez chager vos matières associées dans vos <a href="prefs">Préférences</a>.</p><p>Il est préférable de passer par les lien du menu&nbsp; <a href=".">retour à l\'accueil</a></p>');
+  else
+    exit('<p>Vous ne pourrez afficher cette page qu\'après avoir associé des matières à votre compte. Il faut aller dans vos <a href="prefs">Préférences</a> pour cela.</p>');
+}
+
+// Récupération des types de séances
+$types = array();
+$select_types = '';
+$resultat = $mysqli->query("SELECT id, cle, deb_fin_pour FROM `cdt-types` WHERE matiere = ${matiere['id']}");
+while ( $r = $resultat->fetch_assoc() )  {
+  $types[$r['id']] = $r;
+  $select_types .= "\n          <option value=\"${r['id']}\">".ucfirst($r['cle']).'</option>';
+}
+$resultat->free();
+
+///////////////////
+// Modifications //
+///////////////////
+if ( isset($_REQUEST['id']) && is_numeric($id = $_REQUEST['id']) )  {
+  // Connexion à la base de données
+  $mysqli->close();
+  $mysqli = mysql_ecriture();
+
+  // Vérification que l'identifiant est valide. Défaut : id=0 (nouveau raccourci)
+  if ( $id )  {
+    // Vérification de l'existence et récupération de l'ordre d'affichage
+    $resultat = $mysqli->query("SELECT ordre, nom, (SELECT max(ordre) FROM `cdt-seances` WHERE matiere = ${matiere['id']}) AS max
+                                FROM `cdt-seances` WHERE matiere = ${matiere['id']} AND id = $id");
+    if ( $resultat->num_rows )  {
+      $r = $resultat->fetch_assoc();
+      $ordre = $r['ordre'];
+      $resultat->free();
+    }
+    else
+      $id = 0;
+  }
+
+  // Traitement d'un ajout/modification
+  if ( isset($_REQUEST['modifie']) )  {
+    // Vérification des données envoyées
+    if ( !strlen($_REQUEST['nom']) )
+      $message = 'Il n\'est pas possible de valider un nom vide. Pour supprimer un raccourci, il faut cliquer sur Supprimer.';
+    else  {
+      // Validation des données envoyées
+      $nom = $mysqli->real_escape_string($_REQUEST['nom']);
+      $jour = ( is_numeric($_REQUEST['jour']) ) ? $_REQUEST['jour'] : 1;
+      $tid = ( array_key_exists($_REQUEST['tid'],$types) ) ? $_REQUEST['tid'] : key($types);
+      $h_debut = $h_fin = '0:00';
+      switch ( $types[$tid]['deb_fin_pour'] )  {
+        case 1:
+          $h_fin = preg_filter('/(\d{1,2})h(\d{2})/','$1:$2',$_REQUEST['h_fin']);
+        case 0:
+          $h_debut = preg_filter('/(\d{1,2})h(\d{2})/','$1:$2',$_REQUEST['h_debut']);
+      }
+      $demigroupe = ( isset($_REQUEST['demigroupe']) ) ? 1 : 0;
+      // Si identifiant, modification d'un raccourci existant
+      if ( $id )
+        $message = ( requete('cdt-seances',"UPDATE `cdt-seances` SET nom = '$nom', jour = $jour, h_debut = '$h_debut',
+                                            h_fin = '$h_fin', type = $tid, demigroupe = $demigroupe WHERE id = $id")
+        ) ? 'Le raccourci <em>'.stripslashes($nom).'</em> a bien été modifié.' : 'Le raccourci <em>'.stripslashes($nom).'</em> n\'a pas pu être modifié. Erreur MySQL n°'.$mysqli->errno.', «'.$mysqli->error.'».';
+      // Sinon, nouveau raccourci
+      else  {
+        $message = ( requete('cdt-seances',"INSERT INTO `cdt-seances` SET matiere = ${matiere['id']},
+                                            ordre = (SELECT IFNULL(max(cs.ordre)+1,1) FROM `cdt-seances` AS cs WHERE cs.matiere = ${matiere['id']}),
+                                            nom = '$nom', jour = $jour, h_debut = '$h_debut', h_fin = '$h_fin', type = $tid, demigroupe = $demigroupe")
+        ) ? 'Le raccourci <em>'.stripslashes($nom).'</em> a bien été ajouté.' : 'Le raccourci <em>'.stripslashes($nom).'</em> n\'a pas pu être ajouté. Erreur MySQL n°'.$mysqli->errno.', «'.$mysqli->error.'».';
+        $mysqli->query('ALTER TABLE `cdt-seances` ORDER BY ordre,matiere');
+      }
+    }
+  }
+
+  // Traitement d'une transformation/suppression
+  elseif ( $id )  {
+    
+    // Déplacement vers le haut
+    if ( isset($_REQUEST['monte']) && ( $ordre > 1 ) )  {
+      $message = ( requete('cdt-seances',"UPDATE `cdt-seances` SET ordre = (2*$ordre-1-ordre)
+                                          WHERE ( ordre = $ordre OR ordre = ($ordre-1) ) AND matiere = ${matiere['id']}")
+      ) ? "Le raccourci <em>${r['nom']}</em> a bien été monté d'une place." : "Le raccourci <em>${r['nom']}</em> n'a pas pu être déplacé. Erreur MySQL n°".$mysqli->errno.', «'.$mysqli->error.'».';
+      $mysqli->query('ALTER TABLE `cdt-seances` ORDER BY ordre,matiere');
+    }
+
+    // Déplacement vers le bas
+    elseif ( isset($_REQUEST['descend']) && ( $ordre < $r['max'] ) )  {
+      $message = ( requete('cdt-seances',"UPDATE `cdt-seances` SET ordre = (2*$ordre+1-ordre)
+                                          WHERE ( ordre = $ordre OR ordre = ($ordre+1) ) AND matiere = ${matiere['id']}")
+      ) ? "Le raccourci <em>${r['nom']}</em> a bien été descendu d'une place." : "Le raccourci <em>${r['nom']}</em> n'a pas pu être déplacé. Erreur MySQL n°".$mysqli->errno.', «'.$mysqli->error.'».';
+      $mysqli->query('ALTER TABLE `cdt-seances` ORDER BY ordre,matiere');
+    }
+
+    // Suppression
+    elseif ( isset($_REQUEST['supprime']) )
+      $message =  ( requete('cdt-seances',"DELETE FROM `cdt-seances` WHERE id = $id")
+                 && requete('cdt-seances',"UPDATE `cdt-seances` SET ordre = (ordre-1) WHERE ordre > $ordre AND matiere = ${matiere['id']}")
+      ) ? "Le raccourci <em>${r['nom']}</em> a bien été supprimé." : "Le raccourci <em>${r['nom']}</em> n'a pas pu être supprimé. Erreur MySQL n°".$mysqli->errno.', «'.$mysqli->error.'».';
+  }
+
+  // Passage en connexion MySQL en lecture seulement
+  $mysqli->close();
+  $mysqli = mysql_lecture();
+}
+
+//////////////
+//// HTML ////
+//////////////
+$p = "cdt?${matiere['cle']}";
+$t = "Modification des raccourcis du cahier de texte - ${matiere['nom']}";
+// Haut de page, menu et message
+include('haut.php');
+
+// Aide générale
+?>
+
+  <div>
+    <a href="cdt?<?php echo $matiere['cle']; ?>">Revenir au cahier de texte de <?php echo $matiere['nom']; ?></a>
+  </div>
+
+  <div class="item aide">
+    <h3>Aide et explications</h3>
+    <p>Vous pouvez créer et modifier ci-dessous les boutons de raccourci qui apparaissent dans les formulaires d'entrée du cahier de texte. Ces boutons sont propres à la matière <?php echo $matiere['nom']; ?>.</p>
+    <p>Le <em>nom</em> sera affiché sur le bouton. Il n'est visible que par vous, vous pouvez y mettre ce que vous souhaitez. Par exemple, &laquo;&nbsp;Cours du lundi&nbsp;&raquo;.</p>
+    <p>Les autres champs réglables correspondent à l'action du bouton&nbsp;: une fois le bouton de raccourci créé, un clic rentre ces valeurs dans le formulaire d'entrée du cahier de texte.</p>
+    <p>L'ordre d'apparition des boutons est modifiable grâce aux flèches.</p>
+  </div>
+
+<?php
+// Select sur les jours de la semaine
+$semaine = '
+        <option value="1">Lundi</option>
+        <option value="2">Mardi</option>
+        <option value="3">Mercredi</option>
+        <option value="4">Jeudi</option>
+        <option value="5">Vendredi</option>
+        <option value="6">Samedi</option>
+        <option value="0">Dimanche</option>
+';
+
+// Fonction d'affichage
+function affichage($r)  {
+  if ( $id = $r['id'] )  {
+    $titre = "Raccourci n°${r['ordre']}&nbsp;: ${r['nom']}";
+    $valide1 = '';
+    $valide = "\n        <input type=\"submit\" name=\"modifie\" value=\"Valider\" title=\"Valider les modifications\">";
+    $monte = ( $r['ordre'] == 1 ) ? '' : "\n        <input type=\"submit\" name=\"monte\" value=\"&uarr;\" title=\"Remonter le raccourci dans l'ordre d'apparition\">";
+    $descend = ( $r['ordre'] == $GLOBALS['max'] ) ? '' : "\n        <input type=\"submit\" name=\"descend\" value=\"&darr;\" title=\"Descendre le raccourci dans l'ordre d'apparition\">";
+    $suppr = ( $GLOBALS['max'] == 1 ) ? '' : "\n        <input type=\"submit\" name=\"supprime\" value=\"Supprimer\" title=\"Supprimer le raccourci\">";
+    $select_types = str_replace("\"${r['type']}\"","\"${r['type']}\" selected",$GLOBALS['select_types']);
+    $semaine = str_replace("\"${r['jour']}\"","\"${r['jour']}\" selected",$GLOBALS['semaine']);
+  }
+  else  {
+    $titre = 'Nouveau raccourci';
+    $valide1 = "\n    <input class=\"bouton\" type=\"submit\" name=\"modifie\" value=\"Valider\" title=\"Valider les modifications\">";
+    $valide = $monte = $descend = $suppr = '';
+    $select_types = $GLOBALS['select_types'];
+    $semaine = $GLOBALS['semaine'];
+  }
+  echo <<<FIN
+  <div class="item admin">
+  <form action="?${GLOBALS['mat']}" method="post">$valide1
+    <h3>$titre</h3>
+    <div>
+      <p class="boutons">$valide$monte$descend$suppr
+      </p>
+      <p class="ligne"><label for="nom$id">Nom&nbsp;: </label><input type="input" id="nom$id" name="nom" value="${r['nom']}" size="50"></p>
+      <p class="ligne"><label for="tid$id">Type&nbsp;:</label>
+        <select id="tid$id" name="tid">$select_types
+        </select>
+      </p>
+      <p class="ligne"><label for="jour$id">Jour&nbsp;:</label>
+        <select id="jour$id" name="jour">$semaine
+        </select>
+      </p>
+      <p class="ligne"><label for="h_debut$id">Heure de début&nbsp;: </label><input type="text" class="heure" id="h_debut$id" name="h_debut" value="${r['h_debut']}" size="5"></p>
+      <p class="ligne"><label for="h_fin$id">Heure de fin&nbsp;: </label><input type="text" class="heure" id="h_fin$id" name="h_fin" value="${r['h_fin']}" size="5"></p>
+      <p class="ligne"><label for="demigroupe$id">Séance en demi-groupe&nbsp;: </label><input type="checkbox" id="demigroupe$id" name="demigroupe" value="1"${r['demigroupe']}></p>
+      <input type="hidden" name="id" value="$id">
+    </div>
+  </form>
+  </div>
+
+
+FIN;
+  
+}
+
+// Formulaire vide pour un nouveau type
+$mat = $matiere['cle'];
+affichage(array('id' => 0, 'nom' => '', 'jour' => 1, 'type' => 0, 'h_debut' => '08h00', 'h_fin' => '10h00', 'demigroupe' => ''));
+
+// Affichage des types
+$resultat = $mysqli->query("SELECT max(ordre) FROM `cdt-seances` WHERE matiere = ${matiere['id']}");
+if ( $resultat->num_rows )  {
+  $r = $resultat->fetch_row();
+  $max = $r[0];
+  $resultat->free();
+  $resultat = $mysqli->query("SELECT id, ordre, nom, jour, type, IF(demigroupe=1,' checked','') AS demigroupe,
+                              TIME_FORMAT(h_debut,'%Hh%i') AS h_debut, TIME_FORMAT(h_fin,'%Hh%i') AS h_fin
+                              FROM `cdt-seances` WHERE matiere = ${matiere['id']}");
+  $mysqli->close();
+  while ( $r = $resultat->fetch_assoc() )
+    affichage($r);
+  $resultat->free();
+}
+
+echo <<<FIN
+
+  <script type="text/javascript">
+$( function() {
+  
+  // Modification du formulaire en fonction du type
+  $('[name="tid"]').change(function() {
+    var p1 = $(this).parent().parent().find('[id^="h_debut"]').parents('.ligne');
+    var p2 = $(this).parent().parent().find('[id^="h_fin"]').parents('.ligne');
+    switch ( $(this).val() )  {
+FIN;
+  foreach ( $types as $id => $type )
+    switch ( $type['deb_fin_pour'] )  {
+      case 0:
+        echo "\n      case '$id': p1.show(); p2.hide(); break;";
+        break;
+      case 1:
+        echo "\n      case '$id': p1.show(); p2.show(); break;";
+        break;
+      default:
+        echo "\n      case '$id': p1.hide(); p2.hide(); break;";
+    }
+  echo <<<FIN
+
+    }
+  });
+  // Modification au chargement
+  $('[name="tid"]').change();
+
+  $('.heure').timeEntry({timeSteps: [1, 15, 0], separator: 'h', spinnerImage: '../js/spinnerDefault.png'});
+  
+});
+  </script>
+
+FIN;
+// Bas de page
+include('bas.php');
+?>
diff -urN cahier-de-prepa3.2.0/admin/cdt-types.php cahier-de-prepa4.0.0/admin/cdt-types.php
--- cahier-de-prepa3.2.0/admin/cdt-types.php	1970-01-01 01:00:00.000000000 +0100
+++ cahier-de-prepa4.0.0/admin/cdt-types.php	2014-08-27 01:28:17.445363409 +0200
@@ -0,0 +1,230 @@
+<?php
+// Sécurité et récupération des données de configuration
+define('OK',1);
+include('debut.php'); // fournit $mysqli et $matieres
+
+// Recherche de la matière concernée
+if ( !empty($_REQUEST) )
+  foreach ( $matieres as $r )
+    if ( isset($_REQUEST[$r['cle']]) )  {
+      $matiere = $r;
+      break;
+    }
+// Si mauvaise demande
+if ( !isset($matiere) )  {
+  if ( strlen($_SESSION['matieres']) )
+    exit('<p>Mauvais paramètre d\'accès à cette page. Vous n\'avez accès qu\'aux matières associées à votre compte et l\'adresse doit comporter la clé de la matière. Vous pouvez chager vos matières associées dans vos <a href="prefs">Préférences</a>.</p><p>Il est préférable de passer par les lien du menu&nbsp; <a href=".">retour à l\'accueil</a></p>');
+  else
+    exit('<p>Vous ne pourrez afficher cette page qu\'après avoir associé des matières à votre compte. Il faut aller dans vos <a href="prefs">Préférences</a> pour cela.</p>');
+}
+
+///////////////////
+// Modifications //
+///////////////////
+if ( isset($_REQUEST['id']) && is_numeric($id = $_REQUEST['id']) )  {
+  // Connexion à la base de données
+  $mysqli->close();
+  $mysqli = mysql_ecriture();
+  
+  // Traitement d'un ajout/modification
+  if ( isset($_REQUEST['modifie']) )  {
+    // Vérification des données envoyées
+    if ( !strlen($_REQUEST['titre']) ||  !strlen($_REQUEST['cle']) )
+      $message = 'Il n\'est pas possible de valider un titre ou une clé vide. Pour supprimer un type, il faut cliquer sur Supprimer (et il faut qu\'aucune entrée de ce type-là soit dans le cahier de texte pour voir ce bouton).';
+    else  {
+      // Validation des données envoyées
+      $titre = $mysqli->real_escape_string($_REQUEST['titre']);
+      $cle = $mysqli->real_escape_string($_REQUEST['cle']);
+      $deb_fin_pour = ( is_numeric($_REQUEST['deb_fin_pour']) ) ? $_REQUEST['deb_fin_pour'] : 1;
+      // Si identifiant, modification d'un type existant
+      if ( $id )
+        $message = ( requete('cdt-types',"UPDATE `cdt-types` SET titre = '$titre', cle = '$cle', deb_fin_pour = $deb_fin_pour WHERE id = $id")
+        ) ? 'Le type <em>'.stripslashes($titre).'</em> a bien été modifié.' : 'Le type <em>'.stripslashes($titre).'</em> n\'a pas pu être modifié. Erreur MySQL n°'.$mysqli->errno.', «'.$mysqli->error.'».';
+      // Sinon, nouveau type
+      else  {
+        $message = ( requete('cdt-types',"INSERT INTO `cdt-types` SET matiere = ${matiere['id']},
+                                          ordre = (SELECT max(ct.ordre)+1 FROM `cdt-types` AS ct WHERE ct.matiere = ${matiere['id']}),
+                                          titre = '$titre', cle = '$cle', deb_fin_pour = $deb_fin_pour")
+        ) ? 'Le type <em>'.stripslashes($titre).'</em> a bien été ajouté.' : 'Le type <em>'.stripslashes($titre).'</em> n\'a pas pu être ajouté. Erreur MySQL n°'.$mysqli->errno.', «'.$mysqli->error.'».';
+        $mysqli->query('ALTER TABLE `cdt-types` ORDER BY ordre,matiere');
+      }
+    }
+  }
+
+  // Traitement d'une transformation/suppression
+  else  {
+    
+    // Vérification de l'existence et récupération de l'ordre d'affichage
+    $resultat = $mysqli->query("SELECT ordre, titre, nb, (SELECT max(ordre) FROM `cdt-types` WHERE matiere = ${matiere['id']}) AS max
+                                FROM `cdt-types` WHERE id = $id");
+    if ( $resultat->num_rows )  {
+      $r = $resultat->fetch_assoc();
+      $ordre = $r['ordre'];
+      $resultat->free();
+
+      // Déplacement vers le haut
+      if ( isset($_REQUEST['monte']) && ( $ordre > 1 ) )  {
+        $message = ( requete('cdt-types',"UPDATE `cdt-types` SET ordre = (2*$ordre-1-ordre)
+                                          WHERE ( ordre = $ordre OR ordre = ($ordre-1) ) AND matiere = ${matiere['id']}")
+        ) ? "Le type <em>${r['titre']}</em> a bien été monté d'une place." : "Le type <em>${r['titre']}</em> n'a pas pu être déplacé. Erreur MySQL n°".$mysqli->errno.', «'.$mysqli->error.'».';
+        $mysqli->query('ALTER TABLE `cdt-types` ORDER BY ordre,matiere');
+      }
+
+      // Déplacement vers le bas
+      elseif ( isset($_REQUEST['descend']) && ( $ordre < $r['max'] ) )  {
+        $message = ( requete('cdt-types',"UPDATE `cdt-types` SET ordre = (2*$ordre+1-ordre)
+                                          WHERE ( ordre = $ordre OR ordre = ($ordre+1) ) AND matiere = ${matiere['id']}")
+        ) ? "Le type <em>${r['titre']}</em> a bien été descendu d'une place." : "Le type <em>${r['titre']}</em> n'a pas pu être déplacé. Erreur MySQL n°".$mysqli->errno.', «'.$mysqli->error.'».';
+        $mysqli->query('ALTER TABLE `cdt-types` ORDER BY ordre,matiere');
+      }
+
+      // Suppression (seulement si non vide)
+      elseif ( isset($_REQUEST['supprime']) && ( $r['max'] > 1 ) && ( !$r['nb'] ) )
+        $message =  ( requete('cdt-types',"DELETE FROM `cdt-types` WHERE id = $id")
+                   && requete('cdt-types',"UPDATE `cdt-types` SET ordre = (ordre-1) WHERE ordre > $ordre AND matiere = ${matiere['id']}")
+        ) ? "Le type <em>${r['titre']}</em> a bien été supprimé." : "Le type <em>${r['titre']}</em> n'a pas pu être supprimé. Erreur MySQL n°".$mysqli->errno.', «'.$mysqli->error.'».';
+
+    }
+  }
+
+  // Passage en connexion MySQL en lecture seulement
+  $mysqli->close();
+  $mysqli = mysql_lecture();
+}
+
+//////////////
+//// HTML ////
+//////////////
+$p = "cdt?${matiere['cle']}";
+$t = "Modification des types du cahier de texte - ${matiere['nom']}";
+// Haut de page, menu et message
+include('haut.php');
+
+// Aide générale
+?>
+
+  <div>
+    <a href="cdt?<?php echo $matiere['cle']; ?>">Revenir au cahier de texte de <?php echo $matiere['nom']; ?></a>
+  </div>
+
+  <div class="item aide">
+    <h3>Aide et explications</h3>
+    <p>Vous pouvez modifier ci-dessous les types d'entrée du cahier de texte. Ces modifications sont propres à la matière <?php echo $matiere['nom']; ?>.</p>
+    <p>Le <em>titre</em> sera affiché au début de chaque entrée. Il doit s'agit d'un nom singulier et commençant par une majuscule. Il peut être relativement long. Par exemple&nbsp;: «&nbsp;Séance de travaux pratiques&nbsp;», «&nbsp;Interrogation de cours&nbsp;»</p>
+    <p>La <em>clé</em> sera affichée dans le menu déroulant de recherche, précédé de «&nbsp;les&nbsp», ainsi que dans l'adresse des pages qui affichent ce type d'entrée&nbsp;: il faut donc que ce soit un pluriel, court, en un mot, sans majucule au début (sauf s'il le faut). Par exemple, «&nbsp;TP&nbsp;», «&nbsp;interros&nbsp;».</p>
+    <p>Vous pouvez choisir les valeurs affichées pour chaque type d'entrée grâce à l'<em>affichage d'horaires</em>&nbsp;:</p>
+    <ul>
+      <li><em>Début seulement</em> conduit à ce qu'il n'y ait qu'une heure de début. Utile pour les interrogations par exemple.</li>
+      <li><em>Début et fin</em> conduit à ce qu'il y ait une heure de début et une heure de fin. C'est le cas le plus général.</li>
+      <li><em>Pas d'horaire mais date d'échéance</em> conduit à n'afficher qu'une date (&nbsp;pour le&nbsp;) et aucun horaire. Utile typiquement pour les devoirs maison.</li>
+      <li><em>Pas d'horaire ni date d'échéance</em> vous convient si vous souhaitez arrêter de saisir les heures. Par exemple, pour les devoirs surveillés du samedi matin, il suffit d'avoir la date. Le titre affiché pour une entrée de ce type sur la partie publique contiendra la date et le <em>titre</em> du type (par exemple &laquo;&nbsp;Samedi XX/XX/XXXX : Devoir surveillé&nbsp;&raquo;).</li>
+      <li><em>Entrée journalière</em> vous convient si vous souhaitez ne saisir qu'une entrée par jour. La différence avec la possibilité précédente et que l'on n'affichera pas le <em>titre</em> du type sur la partie publique. Cela est donc utile uniquement si vous souhaitez regrouper vos entrées par jour. Le titre affiché pour une entrée de ce type sur la partie publique ne contiendra que la date. Le meilleur <em>titre</em> sera alors certainement &laquo;&nbsp;Cours&nbsp;&raquo; (ne s'affichera que dans le formulaire de recherche en haut de page).</li>
+      <li><em>Entrée hebdomadaire</em> vous convient si vous souhaitez ne saisir qu'une entrée par semaine. Cela est donc utile uniquement si vous souhaitez regrouper vos entrées par semaine. Il n'y aura pas de titre pour une entrée de ce type sur la partie publique, car la date de chaque semaine est déjà écrite systématiquement.</li>
+    </ul>
+    <p>Un type d'entrée peut être <em>supprimé</em> uniquement s'il ne correspond à aucune entrée.</p>
+    <h4>Exemples de différentes possibilités</h4>
+    <p>La première possibilité, celle de base, correspond à tout renseigner&nbsp; chaque séance (cours, TD, TP) a une heure de début et une heure de fin. On obtient&nbsp;</p>
+    <ul>
+      <li>[ Cours, cours, Début et fin ]</li>
+      <li>[ Séance de travaux dirigés, TD, Début et fin ]</li>
+      <li>[ Séance de travaux pratiques, TP, Début et fin ]</li>
+      <li>[ Devoir surveillé, DS, Début et fin ]</li>
+      <li>[ Interrogation de cours, interros, Début seulement ]</li>
+      <li>[ Distribution de document, distributions, Début seulement ]</li>
+      <li>[ Devoir maison, DM, Pas d'horaire mais date d'échéance ]</li>
+    </ul>
+    <p>Une deuxième possibilité est de simplifier les entrées qui ont un horaire à peu près fixe ou qui n'arrivent qu'une fois par semaine. Cela peut être le cas des TP, des devoirs... Si on imagine le cas d'un collègue qui ne voit pas l'intérêt du type &laquo;&nbsp;Distribution de document&nbsp;&raquo;, cela donne&nbsp;:</p>
+    <ul>
+      <li>[ Cours, cours, Début et fin ]</li>
+      <li>[ Séance de travaux dirigés, TD, Début et fin ]</li>
+      <li>[ Séance de travaux pratiques, TP, Pas d'horaire ni date d'échéance ]</li>
+      <li>[ Devoir surveillé, DS, Pas d'horaire ni date d'échéance ]</li>
+      <li>[ Interrogation de cours, interros, Début seulement ]</li>
+      <li>[ Devoir maison, DM, Pas d'horaire mais date d'échéance ]</li>
+    </ul>
+    <p>Une troisième possibilité peut être de ne faire qu'une seule entrée pour les cours, TD. Mais de garder les TP, sans horaires (ou avec), de garder les DS... C'est-à-dire&nbsp;:</p>
+    <ul>
+      <li>[ Cours, cours, Entrée hebdomadaire ]</li>
+      <li>[ Séance de travaux pratiques, TP, Pas d'horaire ni date d'échéance ]</li>
+      <li>[ Devoir surveillé, DS, Pas d'horaire ni date d'échéance ]</li>
+    </ul>
+    <p>Quatrième idée, on peut choisir de regrouper les entrées par jour. Mais vouloir être capable de n'afficher que les DM, que les DS, ou tout le reste. Il faut donc avoir&nbsp;:</p>
+    <ul>
+      <li>[ Cours, cours, Entrée journalière ]</li>
+      <li>[ Devoir surveillé, DS, Pas d'horaire ni date d'échéance ]</li>
+      <li>[ Devoir maison, DM, Pas d'horaire mais date d'échéance ]</li>
+    </ul>
+    <p>Il est possible d'imaginer beaucoup plus de combinaisons. Il ne tient qu'à vous de trouver celle qui vous semble la meilleure.</p>
+    <p>Seuls les types qui correspondent effectivement à des entrées apparaissent dans le formulaire de recherche de la partie publique. Ce n'est donc pas un problème d'avoir des types qui ne servent finalement à rien, il n'apparaîtront pas dans le formulaire.</p>
+  </div>
+
+<?php
+
+// Fonction d'affichage
+$deb_fin_pour = '
+        <option value="0">Début seulement</option>
+        <option value="1">Début et fin</option>
+        <option value="2">Pas d\'horaire mais date d\'échéance</option>
+        <option value="3">Pas d\'horaire ni date d\'échéance</option>
+        <option value="4">Entrée journalière</option>
+        <option value="5">Entrée hebdomadaire</option>
+';
+function affichage($r)  {
+  if ( $id = $r['id'] )  {
+    $titre = "Type n°${r['ordre']}&nbsp;: ${r['titre']}";
+    if ( $r['nb'] )
+      $titre .= ( $r['nb'] > 1 ) ? " (${r['nb']} entrées dans le cahier de texte)" : " (${r['nb']} entrée dans le cahier de texte)";
+    $valide1 = '';
+    $valide = "\n      <input type=\"submit\" name=\"modifie\" value=\"Valider\" title=\"Valider les modifications\">";
+    $monte = ( $r['ordre'] == 1 ) ? '' : "\n      <input type=\"submit\" name=\"monte\" value=\"&uarr;\" title=\"Remonter le type dans l'ordre d'apparition\">";
+    $descend = ( $r['ordre'] == $GLOBALS['max'] ) ? '' : "\n      <input type=\"submit\" name=\"descend\" value=\"&darr;\" title=\"Descendre le type dans l'ordre d'apparition\">";
+    $suppr = ( ( $GLOBALS['max'] == 1 ) || ( $r['nb'] ) ) ? '' : "\n      <input type=\"submit\" name=\"supprime\" value=\"Supprimer\" title=\"Supprimer le type\">";
+    $deb_fin_pour = str_replace("\"${r['deb_fin_pour']}\"","\"${r['deb_fin_pour']}\" selected",$GLOBALS['deb_fin_pour']);
+  }
+  else  {
+    $titre = 'Nouveau type';
+    $valide1 = "\n    <input class=\"bouton\" type=\"submit\" name=\"modifie\" value=\"Valider\" title=\"Valider les modifications\">";
+    $valide = $monte = $descend = $suppr = '';
+    $deb_fin_pour = $GLOBALS['deb_fin_pour'];
+  }
+  echo <<<FIN
+  <div class="item admin">
+  <form action="?${GLOBALS['mat']}" method="post">$valide1
+    <h3>$titre</h3>
+    <p class="boutons">$valide$monte$descend$suppr
+    </p>
+    <p class="ligne"><label for="titre$id">Titre (singulier)&nbsp;: </label><input type="input" id="titre$id" name="titre" value="${r['titre']}" size="50"></p>
+    <p class="ligne"><label for="cle$id">Clé (pluriel)&nbsp;: </label><input type="input" id="cle$id" name="cle" value="${r['cle']}" size="50"></p>
+    <p class="ligne"><label for="deb_fin_pour$id">Affichage d'horaires&nbsp;:</label>
+      <select id="deb_fin_pour$id" name="deb_fin_pour">$deb_fin_pour
+      </select>
+    </p>
+    <input type="hidden" name="id" value="$id">
+  </form>
+  </div>
+
+
+FIN;
+  
+}
+
+// Formulaire vide pour un nouveau type
+$mat = $matiere['cle'];
+affichage(array('id' => 0, 'titre' => '', 'cle' => '', 'deb_fin_pour' => 1));
+
+// Affichage des types
+$resultat = $mysqli->query("SELECT MAX(ordre) FROM `cdt-types` WHERE matiere = ${matiere['id']}");
+$r = $resultat->fetch_row();
+$max = $r[0];
+$resultat->free();
+$resultat = $mysqli->query("SELECT id, ordre, titre, cle, deb_fin_pour, nb FROM `cdt-types` WHERE matiere = ${matiere['id']}");
+$mysqli->close();
+if ( $resultat->num_rows )  {
+  while ( $r = $resultat->fetch_assoc() )
+    affichage($r);
+  $resultat->free();
+}
+
+// Bas de page
+include('bas.php');
+?>
diff -urN cahier-de-prepa3.2.0/admin/colles.php cahier-de-prepa4.0.0/admin/colles.php
--- cahier-de-prepa3.2.0/admin/colles.php	1970-01-01 01:00:00.000000000 +0100
+++ cahier-de-prepa4.0.0/admin/colles.php	2014-08-26 10:21:56.059623198 +0200
@@ -0,0 +1,339 @@
+<?php
+// Sécurité et récupération des données de configuration
+define('OK',1);
+include('debut.php'); // fournit $mysqli et $matieres
+
+///////////////////////////////////////////////
+// Validation de la requête : matière, n, nb //
+///////////////////////////////////////////////
+
+// Recherche de la matière concernée
+if ( !empty($_REQUEST) )
+  foreach ( $matieres as $r )
+    if ( isset($_REQUEST[$r['cle']]) )  {
+      $matiere = $r;
+      $mid = $matiere['id'];
+      break;
+    }
+// Si mauvaise demande
+if ( !isset($matiere) )  {
+  if ( strlen($_SESSION['matieres']) )
+    exit('<p>Mauvais paramètre d\'accès à cette page. Vous n\'avez accès qu\'aux matières associées à votre compte et l\'adresse doit comporter la clé de la matière. Vous pouvez chager vos matières associées dans vos <a href="prefs">Préférences</a>.</p><p>Il est préférable de passer par les lien du menu&nbsp; <a href=".">retour à l\'accueil</a></p>');
+  else
+    exit('<p>Vous ne pourrez afficher cette page qu\'après avoir associé des matières à votre compte. Il faut aller dans vos <a href="prefs">Préférences</a> pour cela.</p>');
+}
+
+// Récupération des semaines
+$resultat = $mysqli->query("SELECT s.id AS sid, DATE_FORMAT(s.debut,'%w%Y%m%e') AS d, s.colle, s.vacances, c.id AS cid, c.cache
+                            FROM semaines AS s
+                            LEFT JOIN (SELECT id, semaine, cache FROM colles WHERE matiere = $mid) AS c ON c.semaine=s.id");
+$select_semaines = '';
+while ( $r = $resultat->fetch_assoc() )  {
+  switch ( $r['vacances'] )  {
+    case 0:
+      if ( $r['colle'] )  {
+        if ( is_null($r['cid']) )
+          $select_semaines .= "\n        <option value=\"${r['sid']}\">".format_date($r['d']).'</option>';
+        elseif ( $r['cache'] )
+          $select_semaines .= "\n        <option value=\"${r['sid']}\">".format_date($r['d']).' (non diffusé)</option>';
+        else
+          $select_semaines .= "\n        <option value=\"${r['sid']}\">".format_date($r['d']).' (déjà rempli)</option>';
+      }
+      else
+        $select_semaines .= "\n        <option value=\"${r['sid']}\" disabled>".format_date($r['d']).' (pas de colle)</option>';
+      $sid[] = $r['sid'];
+      break;
+    case 1:
+      $select_semaines .= "\n        <option value=\"${r['sid']}\" disabled>Vacances de Toussaint</option>";
+      break;
+    case 2:
+      $select_semaines .= "\n        <option value=\"${r['sid']}\" disabled>Vacances de Noël</option>";
+      break;
+    case 3:
+      $select_semaines .= "\n        <option value=\"${r['sid']}\" disabled>Vacances d'hiver</option>";
+      break;
+    case 4:
+      $select_semaines .= "\n        <option value=\"${r['sid']}\" disabled>Vacances de Pâques</option>";
+  }
+}
+$resultat->free();
+
+// Validation de n (numéro de semaine à voir)
+if ( isset($_REQUEST['n']) && is_numeric($n = $_REQUEST['n']) )
+  while ( !in_array($n,$sid) )
+    $n = ( $n < count($sid) ) ? $n+1 : 1;
+// Sans argument, on souhaite afficher le programme de colles de la semaine actuelle
+else {
+  $date = ( isset($_REQUEST['date']) && preg_match('/\d{4}-\d{2}-\d{2}/',$_REQUEST['date']) ) ? '\''.trim($_REQUEST['date'],'\'').'\'' : 'NOW()';
+  $resultat = $mysqli->query("SELECT IF(vacances,(SELECT id FROM semaines WHERE debut > ADDDATE($date,3) AND vacances = 0 LIMIT 1),id) AS id
+                              FROM semaines WHERE debut < ADDDATE($date,3) ORDER BY debut DESC LIMIT 1");
+  if ( $resultat->num_rows )  {
+    $r = $resultat->fetch_assoc();
+    $n = $r['id'];
+    $resultat->free();
+    if ( $m = array_search($n,$sid) )
+      $n = $sid[$m-1];
+  }
+  // Aucun résultat : l'année n'a pas encore commencé
+  else
+    $n = 0;
+}
+$m = array_search($n,$sid);
+
+// Validation de nb (nombre de semaines à voir)
+if ( isset($_REQUEST['nb']) && is_numeric($nb = $_REQUEST['nb']) ) {
+  if ( $nb < 1 )
+    $nb = 1;
+}
+else
+  // Par défaut : affichage de la semaine courante et des semaines précédente et suivante
+  $nb = 3;
+if ( $nb > count($sid) - $m )
+  $nb = count($sid) - $m;
+
+///////////////////
+// Modifications //
+///////////////////
+if ( isset($_REQUEST['id']) && is_numeric($id = $_REQUEST['id']) )  {
+  // Connexion à la base de données
+  $mysqli->close();
+  $mysqli = mysql_ecriture();
+
+  // Vérification que l'identifiant de la semaine est valide
+  $resultat = $mysqli->query("SELECT DATE_FORMAT(s.debut,'%e/%m') AS debut, c.id, c.cache, c.texte FROM semaines AS s
+                              LEFT JOIN (SELECT id, texte, semaine, cache FROM colles WHERE matiere = $mid) AS c ON c.semaine=s.id
+                              WHERE s.id = $id AND s.colle = 1");
+  if ( $resultat->num_rows ) {
+    $r = $resultat->fetch_assoc();
+    $cid = $r['id'];
+    $resultat->free();
+    $_REQUEST['n'] = $id;
+    
+    // Pour les informations récentes
+    $titre_recent = "<img class=\"icone\" src=\"icones/colle.png\"> Colles du ${r['debut']} en ".addslashes($matiere['nom']);
+    $lien_recent = 'colles?'.addslashes($matiere['cle'])."&amp;n=$id";
+
+    // Ajout d'un nouveau programme de colles. Il ne faut pas pouvoir insérer un programme vide par erreur
+    if ( isset($_REQUEST['modifie']) && strlen($_REQUEST['texte']) )  {
+      
+      // Validation des données envoyées
+      $texte = $mysqli->real_escape_string($_REQUEST['texte']);
+      
+      // Si $cid n'est pas NULL : modification d'un programme existant. Sinon, nouveau programme
+      if ( $cid )  {
+        if ( requete('colles',"UPDATE colles SET texte = '$texte' WHERE id = $cid") )  {
+          $message = "Le programme de colles de la semaine du ${r['debut']} a bien été modifié.";
+          // Ajout d'une information récente si programme diffusé
+          if ( !$r['cache'] )
+            recent($mysqli,2,$cid,$titre_recent,$lien_recent,$texte);
+        }
+        else
+          $message = "Le programme de colles de la semaine du ${r['debut']} n'a pas pu être modifié. Erreur MySQL n°".$mysqli->errno.', «'.$mysqli->error.'».';
+      }
+      else  {
+        $cache = ( isset($_REQUEST['cache']) ) ? 1 : 0;
+        if ( requete('colles',"INSERT INTO colles SET texte = '$texte', semaine = $id, matiere = $mid, cache = $cache") )  {
+          $message = "Le programme de colles de la semaine du ${r['debut']} a bien été ajouté.";
+          // Ajout d'information récente si programme diffusé
+          if ( !$cache )
+            recent($mysqli,2,$mysqli->insert_id,$titre_recent,$lien_recent,$texte);
+          $mysqli->query('ALTER TABLE colles ORDER BY matiere,semaine');
+        }
+        else
+          $message = "Le programme de colles de la semaine du ${r['debut']} n'a pas pu être ajouté. Erreur MySQL n°".$mysqli->errno.', «'.$mysqli->error.'».';
+      }
+    }
+
+    elseif ( $cid )  {
+
+      // Positionnement "montré" (apparaît sur la partie publique)
+      if ( isset($_REQUEST['montre']) )  {
+        if ( requete('colles',"UPDATE colles SET cache = 0 WHERE id = $cid") )  {
+          $message = "Le programme de colles de la semaine du ${r['debut']} a bien été diffusé, il apparaît désormais sur la partie publique.";
+          // Ajout d'information récente
+          recent($mysqli,2,$cid,$titre_recent,$lien_recent,$mysqli->real_escape_string($r['texte']));
+        }
+        else
+          $message = "Le programme de colles de la semaine du ${r['debut']} n'a pas pu être diffusé. Erreur MySQL n°".$mysqli->errno.', «'.$mysqli->error.'».';
+      }
+
+      // Positionnement "caché" (n'apparaît pas sur la partie publique)
+      elseif ( isset($_REQUEST['cache']) )  {
+        if ( requete('colles',"UPDATE colles SET cache = 1 WHERE id = $cid") )  {
+          $message = "Le programme de colles du ${r['debut']} n'est plus diffusé&nbsp;: il n'apparaît plus sur la partie publique mais est toujours disponible ici pour modification ou diffusion.";
+          // Suppression de l'éventuelle information récente
+          recent($mysqli,2,$cid);
+        }
+        else
+          $message = "Le programme de colles de la semaine du ${r['debut']} n'a pas pu être caché. Erreur MySQL n°".$mysqli->errno.', «'.$mysqli->error.'».';
+      }
+
+      // Suppression
+      elseif ( isset($_REQUEST['supprime']) || !strlen($_REQUEST['texte']) )  {
+        if ( requete('colles',"DELETE FROM colles WHERE id = $cid") )  {
+          $message = "Le programme de colles de la semaine du ${r['debut']} a bien été supprimé.";
+          // Suppression de l'éventuelle information récente
+          recent($mysqli,2,$cid);
+        }
+        else
+          $message = "Le programme de colles de la semaine du ${r['debut']} n'a pas pu être modifié. Erreur MySQL n°".$mysqli->errno.', «'.$mysqli->error.'».';
+      }
+    }
+  }
+  
+  // Mise à jour des champs 'colles' dans la table 'matieres' (pour le menu)
+  $mysqli->query('UPDATE matieres SET colles = IF((SELECT id FROM colles WHERE matiere = matieres.id AND cache = 0 LIMIT 1),1,0)');
+
+  // Passage en connexion MySQL en lecture seulement
+  $mysqli->close();
+  $mysqli = mysql_lecture();
+}
+
+////////////
+/// HTML ///
+////////////
+$p = "colles?${matiere['cle']}";
+$t = "Programme de colles - ${matiere['nom']}";
+
+// Haut de page, menu et message
+include('haut.php');
+
+// Formulaire de demande d'affichage
+$select_semaines = str_replace("\"$n\"","\"$n\" selected",$select_semaines);
+echo <<<FIN
+
+  <div class="item" id="recherche">
+  <form action="" method="get">
+    <input type="hidden" name="${matiere['cle']}" value="">
+    <p>Afficher&nbsp;<input type="text" name="nb" value="$nb" size="2">&nbsp;semaine(s) à partir du&nbsp;
+      <select name="n">$select_semaines
+      </select>
+      <input type="submit" name="" value="OK">
+    </p>
+  </form>
+  </div>
+
+
+FIN;
+
+// Aide générale
+?>
+  <div class="item aide">
+    <h3>Aide et explications</h3>
+    <p>Vous pouvez ci-dessous modifier les programmes de colles de <?php echo $matiere['nom']; ?>. Vous pouvez choisir les semaines à afficher ci-dessus.</p>
+    <p>Vous ne pouvez modifier que les programmes de colles des matières qui vous sont associées. Elles sont modifiables dans vos <a href="prefs">Préférences</a>.</p>
+    <p>Vous pouvez modifier l'accès de l'ensemble du programme de colles dans les préférences de chaque matière, à la page <a href="prefs">Préférences</a>.</p>
+    <h4>Nouveau programme de colles</h4>
+    <p>Vous pouvez choisir la <em>semaine</em> du programme de colles parmi les semaines où le programme n'est pas déjà renseigné, et où il est prévu des colles. Vous pouvez modifier les semaines avec ou sans colle sur la page du <a href="planning">Planning annuel</a>.</p>
+    <p>Le texte doit être formaté en HTML&nbsp;: par exemple, chaque bloc de texte doit être encadré par &lt;p&gt; et &lt;/p&gt;. Il peut donc contenir des liens vers les autres pages du site, vers des documents du site, vers le web... N'hésitez pas à consulter l'aide qui apparaîtra en-dessous de la case de texte après avoir cliqué dessus, ainsi qu'à utiliser les boutons fournis.</p>
+    <p>La case à cocher <em>Ne pas diffuser sur la partie publique</em> permet de cacher temporairement ce programme de colle, par exemple pour le diffuser ultérieurement.</p>
+    <h4>Programme de colles existant</h4>
+    <p>Vous pouvez <em>cacher</em> un programme de colles existant et déjà diffusé, c'est-à-dire le rendre non visible sur la partie publique. Cela supprime aussi l'information récente correspondante.</p>
+    <p>Vous pouvez <em>montrer</em> un programme de colles existant et caché, c'est-à-dire le rendre à nouveau visible sur la partie publique. Cela crée une nouvelle information récente.</p>
+    <p>Vous pouvez <em>supprimer</em> un programme de colles existant.</p>
+  </div>
+
+<?php
+
+// Affichage des semaines concernées
+if ( $n )  {
+
+  // Liens de navigation vers semaines précédentes/suivantes
+  $nav = '  <div class="item">';
+  if ( $m )
+    $nav .= "\n    <a class=\"prec\" href=\"?${matiere['cle']}&amp;n=". $sid[max($m-$nb,0)] ."&amp;nb=$nb\">Semaines précédentes</a>";
+  if (  $m+$nb < count($sid) )
+    $nav .= "\n    <a class=\"suiv\" href=\"?${matiere['cle']}&amp;n=". $sid[$m+$nb] ."&amp;nb=$nb\">Semaines suivantes</a>";
+  $nav .= "\n  </div>\n\n";
+  if ( $nb > 1 )
+    echo $nav;
+
+  // Récupération des données à afficher
+  $resultat = $mysqli->query("SELECT DATE_FORMAT(s.debut,'%w%Y%m%e') AS d, s.colle, s.vacances, c.texte, s.id, IF(c.cache,' checked','') AS cache 
+                              FROM semaines AS s
+                              LEFT JOIN (SELECT texte, semaine, cache FROM colles WHERE matiere = $mid) AS c ON c.semaine=s.id
+                              WHERE s.id BETWEEN $n AND ${sid[$m+$nb-1]}" );
+  // Gestion du titre
+  while ( $r = $resultat->fetch_assoc() )  {
+    switch ( $r['vacances'] )  {
+      case 0:
+        $titre = 'Semaine du '.format_date($r['d']);
+        break;
+      case 1:
+        $titre = ucfirst(format_date($r['d']))."&nbsp;: Vacances de Toussaint";
+        break;
+      case 2:
+        $titre = ucfirst(format_date($r['d']))."&nbsp;: Vacances de Noël";
+        break;
+      case 3:
+        $titre = ucfirst(format_date($r['d']))."&nbsp;: Vacances d'hiver";
+        break;
+      case 4:
+        $titre = ucfirst(format_date($r['d']))."&nbsp;: Vacances de Pâques";
+        break;
+    }
+    // Affichage
+    if ( $r['colle'] )  {
+      if ( is_null($r['texte']) )  {
+        $titre .= ' (à remplir)';
+        $valide1 = "\n    <input class=\"bouton\" type=\"submit\" name=\"modifie\" value=\"Valider\" title=\"Valider les modifications\">";
+        $classe_cache = $boutons = '';
+        $cache = "\n    <p class=\"ligne\"><label for=\"cache\">Ne pas diffuser sur la partie publique&nbsp;: </label><input type=\"checkbox\" id=\"cache\" name=\"cache\" value=\"1\"></p>";
+      }
+      else  {
+        if ( $r['cache'] )  {
+          $valide1 = $cache = '';
+          $classe_cache = ' cache';
+          $titre .= ' (programme non diffusé sur la partie publique)';
+          $boutons = "
+    <p class=\"boutons\">
+      <input type=\"submit\" name=\"modifie\" value=\"Valider\" title=\"Valider les modifications sur le texte\">
+      <input type=\"submit\" name=\"montre\" value=\"Montrer\" title=\"Diffuser le programme, le rendre visible sur la partie publique\">
+      <input type=\"submit\" name=\"supprime\" value=\"Supprimer\" title=\"Supprimer le programme de colles\">
+    </p>";
+        }
+        else  {
+          $valide1 = $cache = $classe_cache = '';
+          $boutons = "
+    <p class=\"boutons\">
+      <input type=\"submit\" name=\"modifie\" value=\"Valider\" title=\"Valider les modifications sur le texte\">
+      <input type=\"submit\" name=\"cache\" value=\"Cacher\" title=\"Ne plus diffuser le programme, le rendre invisible sur la partie public\">
+      <input type=\"submit\" name=\"supprime\" value=\"Supprimer\" title=\"Supprimer le programme de colles\">
+    </p>";
+        }
+      }
+      echo <<<FIN
+  <div class="item admin$classe_cache">
+  <form action="" method="post">$valide1
+    <h3>$titre</h3>$boutons
+    <textarea name="texte" class="mat$mid" rows="15" cols="100">${r['texte']}</textarea>$cache
+    <input type="hidden" name="id" value="${r['id']}">
+  </form>
+  </div>
+
+
+FIN;
+    }
+    else  {
+      $texte = ( $r['vacances'] ) ? '' : "\n    <p>Il n'y a normalement pas de colles cette semaine-là. S'il s'agit d'une erreur, cela est modifiable <a href=\"semaines\">sur la page des semaines</a></p>";
+      echo <<<FIN
+  <div class="item admin">
+    <h3>$titre</h3>$texte
+  </div>
+
+
+FIN;
+    }
+  }
+  $resultat->free();
+  // Liens de navigation vers semaines précédentes/suivantes
+  echo $nav;
+}
+else
+  echo "  <h2>L'année n'a pas encore commencé... Revenez à la rentrée&nbsp;!</h2>\n\n";
+$mysqli->close();
+
+
+// Bas de page
+include('bas.php');
+?>
diff -urN cahier-de-prepa3.2.0/admin/connect.php cahier-de-prepa4.0.0/admin/connect.php
--- cahier-de-prepa3.2.0/admin/connect.php	1970-01-01 01:00:00.000000000 +0100
+++ cahier-de-prepa4.0.0/admin/connect.php	2014-08-04 15:15:54.951361810 +0200
@@ -0,0 +1,11 @@
+<?php
+// Ce script sert à des connexion de type AJAX. Aucun HTML ne doit être renvoyé.
+define('AJAX',1);
+// Sécurité et récupération des données de configuration
+define('OK',1);
+include('debut.php'); // fournit $mysqli et $matieres
+$mysqli->close();
+
+// Si on arrive ici, cela signifie que l'on est connecté correctement
+exit(json_encode(array('etat'=>1)));
+?>
diff -urN cahier-de-prepa3.2.0/admin/debut.php cahier-de-prepa4.0.0/admin/debut.php
--- cahier-de-prepa3.2.0/admin/debut.php	1970-01-01 01:00:00.000000000 +0100
+++ cahier-de-prepa4.0.0/admin/debut.php	2014-08-27 17:47:50.987244170 +0200
@@ -0,0 +1,69 @@
+<?php
+// Sécurité : ne doit pas être exécuté s'il n'est pas lancé par un script autorisé
+if ( !defined('OK') )  exit();
+// Affichage des erreurs : à commenter en production
+//ini_set('display_errors',1); error_reporting(E_ALL); ini_set('display_startup_errors',1);
+
+// Récupération de la configuration statique
+include('../config.php');
+
+// Mode https obligatoire
+if ( $https && ( !isset($_SERVER['HTTPS']) || $_SERVER['HTTPS'] == 'off' ) )  {
+  header("Location: https://{$_SERVER['HTTP_HOST']}{$_SERVER['REQUEST_URI']}");
+  exit();
+}
+
+////////////////////////////////////////////////
+// Fonctions de connexion et d'enregistrement //
+////////////////////////////////////////////////
+// Chargement ici car éventuellement nécessaire pour login.php
+include('fonctions.php');
+
+//////////////
+// Session  //
+//////////////
+
+session_name(md5("$site-admin"));
+session_set_cookie_params(0,strchr($site,'/').'/',str_replace(strchr($site,'/'),'',$site));
+session_start();
+if ( isset($_SESSION[md5("$site-admin")]) )  {
+  if ( ( $_SESSION['time'] < time() ) || ( $_SESSION['client'] != $_SERVER['HTTP_USER_AGENT'] ) || ( $_SESSION['ip'] != $_SERVER['REMOTE_ADDR'] ) )  {
+    $message_login = 'Vous devez vous connecter à nouveau, suite à une longue durée d\'inactivité.';
+    // Suppression du cookie et des données de session
+    $_SESSION = array();
+    setcookie(session_name(),'',time()-3600);
+    session_regenerate_id(true);
+    include('login.php');
+  }
+  // Demande de déconnexion
+  elseif ( isset($_REQUEST['deconnexion']) )  {
+    $message = 'Vous avez bien été déconnecté.';
+    // Suppression du cookie et des données de session
+    $_SESSION = array();
+    setcookie(session_name(),'',time()-3600);
+    session_regenerate_id(true);
+    include('login.php');
+  }
+  // Tout est ok : session valide pendant timeout
+  else  {
+    $_SESSION['time'] = time()+$_SESSION['timeout'];
+  }
+}
+// Connexion obligatoire
+else
+  include('login.php');
+
+////////////////////////////////////////////////
+// Récupération des matières de l'utilisateur //
+////////////////////////////////////////////////
+$mysqli = mysql_lecture();
+$resultat = $mysqli->query("SELECT id, cle, nom FROM matieres WHERE FIND_IN_SET(id,'${_SESSION['matieres']}')
+                            ORDER BY FIND_IN_SET(id,'${_SESSION['matieres']}')");
+$matieres = array();
+if ( $resultat->num_rows )  {
+  while ( $r = $resultat->fetch_assoc() )
+    $matieres[] = $r;
+  $resultat->free();
+}
+
+?>
diff -urN cahier-de-prepa3.2.0/admin/docs.php cahier-de-prepa4.0.0/admin/docs.php
--- cahier-de-prepa3.2.0/admin/docs.php	1970-01-01 01:00:00.000000000 +0100
+++ cahier-de-prepa4.0.0/admin/docs.php	2014-08-27 17:51:49.987251818 +0200
@@ -0,0 +1,686 @@
+<?php
+// Sécurité et récupération des données de configuration
+define('OK',1);
+include('debut.php'); // fournit $mysqli et $matieres
+
+//////////////////////////////////////////////////////
+// Validation de la requête : répertoire ou matière //
+//////////////////////////////////////////////////////
+
+// Récupération de l'identifiant de répertoire demandé
+if ( isset($_REQUEST['rep']) && is_numeric($rid = $_REQUEST['rep']) )  {
+  $resultat = $mysqli->query("SELECT id FROM reps WHERE id = $rid AND FIND_IN_SET(matiere,'0,${_SESSION['matieres']}')");
+  if ( $resultat->num_rows )
+    $resultat->free();
+  else
+    unset($rid);
+}
+// Si une matière est demandée
+elseif ( !empty($_REQUEST) )  {
+  $resultat = $mysqli->query("SELECT r.id, m.cle FROM reps AS r LEFT JOIN matieres AS m ON r.matiere = m.id
+                              WHERE FIND_IN_SET(m.id,'${_SESSION['matieres']}') AND r.parent = 0");
+  if ( $resultat->num_rows )  {
+    while ( $r = $resultat->fetch_assoc() )
+      if ( isset($_REQUEST[$r['cle']]) )  {
+        $rid = $r['id'];
+        break;
+      }
+    $resultat->free();
+  }
+}
+else
+  $rid = 1;
+
+// Si mauvaise demande
+if ( !isset($rid) )  {
+  $mysqli->close();
+  exit('<p>Mauvais paramètre d\'accès à cette page. Vous n\'avez accès qu\'aux répertoires généraux ou correspondant aux matières associées à votre compte.</p><p><a href=".">Revenir à l\'interface d\'administration</a></p>');
+}
+
+/////////////////////////////////////
+// Liste des icônes pour affichage //
+/////////////////////////////////////
+$icones = array(
+  '.pdf' => 'pdf', '.ps' => 'pdf',
+  '.doc' => 'doc', '.odt' => 'doc', '.docx' => 'doc',
+  '.xls' => 'xls', '.ods' => 'xls', '.xlsx' => 'xls',
+  '.ppt' => 'ppt', '.odp' => 'ppt', '.pptx' => 'ppt',
+  '.jpg' => 'jpg', '.jpeg' => 'jpg', '.png' => 'jpg', '.gif' => 'jpg', '.svg' => 'jpg', '.tif' => 'jpg', '.tiff' => 'jpg', '.bmp' => 'jpg', '.ps' => 'jpg', '.eps' => 'jpg',
+  '.py' => 'python',
+  '.avi' => 'avi', '.mpeg' => 'avi', '.mpg' => 'avi', '.wmv' => 'avi', '.mp4' => 'avi', '.ogv' => 'avi', '.qt' => 'avi', '.mov' => 'avi', '.mkv' => 'avi', 'flv' => 'avi',
+  '.mp3' => 'mp3', '.ogg' => 'mp3', '.oga' => 'mp3', '.wma' => 'mp3', '.wav' => 'mp3', '.ra' => 'mp3', '.rm' => 'mp3',
+  '.txt' => 'txt', '.rtf' => 'txt',
+  '.zip' => 'exe', '.rar' => 'exe', '.7z' => 'zip',
+  '.exe' => 'exe', '.sh' => 'exe', '.ml' => 'exe', '.mw' => 'exe', '' => 'exe'
+);
+
+//////////////////////////////////
+// Modifications de répertoires //
+//////////////////////////////////
+if ( isset($_REQUEST['modifie']) || isset($_REQUEST['supprime']) || isset($_REQUEST['cree']) )  {
+  // Connexion à la base de données
+  $mysqli->close();
+  $mysqli = mysql_ecriture();
+
+  // Récupération des données du répertoire
+  $resultat = $mysqli->query("SELECT nom, parent, protection, nbrep+nbdoc AS nb, menu FROM reps WHERE id = $rid");
+  $rep = $resultat->fetch_assoc();
+  $resultat->free();
+
+  // Récupération des liens de parenté entre répertoires
+  $resultat = $mysqli->query("SELECT id, parents FROM reps WHERE FIND_IN_SET(matiere,'0,${_SESSION['matieres']}')");
+  while ( $r = $resultat->fetch_assoc() )
+    $parents[$r['id']] = $r['parents'];
+  $resultat->free();
+
+  // Traitement d'une modification du répertoire actuel
+  // Une partie est impossible si $rep['parent'] est nul (répertoires racines de matières)
+  if ( isset($_REQUEST['modifie']) )  {
+    $message = '';
+
+    // Modification du nom
+    if ( isset($_REQUEST['nom']) && ( $rep['parent'] ) && ( strlen($nom = $mysqli->real_escape_string($_REQUEST['nom'])) ) && ( $rep['nom'] != $nom ) )
+      $message = ( requete('reps',"UPDATE reps SET nom = '$nom' WHERE id = $rid") ) ? 'Le nom du répertoire <em>'.stripslashes($rep['nom'] = $nom).'</em> a bien été modifié.' : "Le nom du répertoire <em>${rep['nom']}</em> n'a pas pu être modifié. Erreur MySQL n°".$mysqli->errno.', «'.$mysqli->error.'».';
+
+    // Déplacement du répertoire
+    if ( isset($_REQUEST['parent']) && ( $rep['parent'] ) && isset($parents[$parent=$_REQUEST['parent']]) && ( $parent != $rep['parent'] ) && ( !in_array($rid,explode(',',$parents[$parent])) ) )  {
+      // Récupération de l'éventuelle nouvelle matière
+      $resultat = $mysqli->query("SELECT matiere FROM reps WHERE id = $parent");
+      $r = $resultat->fetch_assoc();
+      $resultat->free();
+      $mat = $r['matiere'];
+      $parents = "${parents[$parent]},$parent";
+      // Modifications
+      $message .= ( requete('reps',"UPDATE reps SET matiere = $mat, parent = $parent, parents = '$parents' WHERE id = $rid")
+                 && requete('reps',"UPDATE reps SET matiere = $mat, parents = '$parents,$rid' WHERE parent = $rid")
+                 && requete('docs',"UPDATE docs SET matiere = $mat, parents = '$parents,$rid' WHERE parent = $rid")
+      ) ? " Le répertoire <em>${rep['nom']}</em> a bien été déplacé." : " Le répertoire <em>${rep['nom']}</em> n'a pas pu être déplacé. Erreur MySQL n°".$mysqli->errno.', «'.$mysqli->error.'».';
+    }
+
+    // Modification de l'affichage dans le menu
+    $menu = isset($_REQUEST['menu']) ? 1 : 0;
+    if ( ( $rep['parent'] ) && ( $menu != $rep['menu'] ) )
+      $message .= ( requete('reps',"UPDATE reps SET menu = $menu WHERE id = $rid") ) ? " L'affichage dans le menu du répertoire <em>${rep['nom']}</em> a bien été modifié." : " L'affichage dans le menu du répertoire <em>${rep['nom']}</em> n'a pas pu être modifié. Erreur MySQL n°".$mysqli->errno.', «'.$mysqli->error.'».';
+
+    // Modification des droits d'accès au répertoire
+    $protection = ( in_array($_REQUEST['protection'],array(0,1,2,3,4)) ) ? $_REQUEST['protection'] : 0;
+    if ( $protection != $rep['protection'] )  {
+      if ( requete('reps',"UPDATE reps SET protection = $protection WHERE id = $rid") )  {
+        $message .=  " La visibilité du répertoire <em>${rep['nom']}</em> a bien été modifiée.";
+        $rep['protection'] = $protection;
+      }
+      else
+        $message .= " La visibilité du répertoire <em>${rep['nom']}</em> n'a pas pu être modifiée. Erreur MySQL n°".$mysqli->errno.', «'.$mysqli->error.'».';
+    }
+    
+    // Propagation des droits d'accès aux sous-répertoires et documents
+    if ( isset($_REQUEST['propagation']) && ( $rep['nb'] ) )
+      $message .=  ( requete('reps',"UPDATE reps SET protection = ${rep['protection']} WHERE FIND_IN_SET($rid,parents)")
+                  && requete('docs',"UPDATE docs SET protection = ${rep['protection']} WHERE FIND_IN_SET($rid,parents)")
+      ) ? " La visibilité du répertoire <em>${rep['nom']}</em> a bien été propagée à tout son contenu (sous-répertoires et documents)." : " La visibilité du répertoire <em>${rep['nom']}</em> a bien été propagée à tout son contenu (sous-répertoires et documents). Erreur MySQL n°".$mysqli->errno.', «'.$mysqli->error.'».';
+
+    if ( !strlen($message) )
+      unset($message);
+  }
+
+  // Suppression du répertoire actuel
+  elseif ( isset($_REQUEST['supprime']) && ( $rep['parent'] ) )  {
+    if ( requete('reps',"DELETE FROM reps WHERE id = $rid OR FIND_IN_SET($rid,parents)") )  {
+      $resultat = $mysqli->query("SELECT GROUP_CONCAT(CONCAT('../documents/',lien) SEPARATOR ' ') AS chemins
+                                  FROM docs WHERE FIND_IN_SET($rid,parents)");
+      $r = $resultat->fetch_assoc();
+      $resultat->free();
+      // Suppression physique ( $r['chemin'] est "null" si pas de document à supprimer)
+      if ( $r['chemins'] )
+        exec("rm -rf ${r['chemins']}");
+      requete('docs',"DELETE FROM docs WHERE FIND_IN_SET($rid,parents)");
+      $message = "La suppression du répertoire <em>${rep['nom']}</em> a bien été effectuée.";
+      $rid = $rep['parent'];
+    }
+    else
+      $message = "La suppression du répertoire <em>${rep['nom']}</em> n'a pas été effectuée. Erreur MySQL n°".$mysqli->errno.', «'.$mysqli->error.'».';
+  }
+
+  // Création d'un sous-répertoire
+  elseif ( isset($_REQUEST['cree']) && ( strlen($_REQUEST['nom']) ) )  {
+    $nom = $mysqli->real_escape_string($_REQUEST['nom']);
+    $protection = ( in_array($_REQUEST['protection'],array(0,1,2,3,4)) ) ? $_REQUEST['protection'] : 0;
+    $menu = isset($_REQUEST['menu']) ? 1 : 0;
+    $message = ( requete('reps',"INSERT INTO reps SET parent = $rid, parents = '${parents[$rid]},$rid',
+                                 nom = '$nom', matiere = (SELECT r.matiere FROM reps AS r WHERE r.id = $rid),
+                                 nbrep = 0, nbrep_v = 0, nbdoc = 0, nbdoc_v = 0, protection = $protection, menu = $menu")
+    ) ? 'Le répertoire <em>'.stripslashes($nom).'</em> a bien été créé.' : 'Le répertoire <em>'.stripslashes($nom).'</em> n\'a pas pu être créé. Erreur MySQL n°'.$mysqli->errno.', «'.$mysqli->error.'».';
+  }
+
+  // Mise à jour des champs nbrep/nbdoc dans la table 'reps'
+  $mysqli->query('UPDATE reps AS r SET 
+    nbrep = (SELECT COUNT(id) FROM (SELECT * FROM reps ) AS r1 WHERE r.id = r1.parent),
+    nbrep_v = (SELECT COUNT(id) FROM (SELECT * FROM reps WHERE reps.protection<4) AS r2 WHERE r.id = r2.parent),
+    nbdoc = (SELECT COUNT(id) FROM docs AS d WHERE r.id = d.parent),
+    nbdoc_v = (SELECT COUNT(id) FROM docs AS d WHERE r.id = d.parent AND d.protection<4)');
+  $mysqli->query('ALTER TABLE reps ORDER BY parents,nom');
+  // Mise à jour des champs 'docs' dans la table 'matieres' (pour le menu)
+  $mysqli->query('UPDATE matieres SET docs = (SELECT IF(SUM(nbdoc_v),1,0) FROM reps WHERE matiere = matieres.id)');
+
+  // Passage en connexion MySQL en lecture seulement
+  $mysqli->close();
+  $mysqli = mysql_lecture();
+}
+
+////////////////////////////////
+// Modifications de documents //
+////////////////////////////////
+elseif ( isset($_REQUEST['id']) && is_numeric($id = $_REQUEST['id']) )  {
+  // Connexion à la base de données
+  $mysqli->close();
+  $mysqli = mysql_ecriture();
+
+  // Récupération des données relatives au document
+  if ( $id )  {
+    $resultat = $mysqli->query("SELECT nom, protection, ext, lien FROM docs WHERE id = $id AND parent = $rid");
+    if ( $resultat->num_rows )  {
+      $doc = $resultat->fetch_assoc();
+      $resultat->free();
+    }
+    else
+      $id = 0;
+  }
+
+  // Fonction PHP pour le stockage dans la base MySQL de l'ordre "naturel" (1,2,10,11 et non 1,10,11,2)
+  // Remplace tout nombre par un nombre égal mais écrit sur 10 chiffres, complété par des zéros à gauche
+  function zpad($s) {
+    return preg_replace_callback('/(\d+)/', function($m){
+      return(str_pad($m[1],10,'0',STR_PAD_LEFT)); }
+    , $s);
+  }
+
+  // Traitement d'une modification d'un document
+  if ( isset($_REQUEST['modifie_doc']) && ( $id ) )  {
+    if ( !strlen($_REQUEST['nom']) )
+      $message = 'Il n\'est pas possible de valider un nom de fichier vide. Pour supprimer un fichier, il faut cliquer sur Supprimer.';
+    else  {
+      $message = '';
+
+      // Modification du nom
+      setlocale(LC_CTYPE, "fr_FR.UTF-8");
+      $nom = basename(str_replace($doc['ext'],'',str_replace('/','-',$_REQUEST['nom'])));
+      if ( $nom != $doc['nom'] )  {
+        // real_escape_string seulement pour la requête SQL
+        if ( requete('docs','UPDATE docs SET nom = \''.$mysqli->real_escape_string($nom).'\', nom_nat = \''.zpad($mysqli->real_escape_string($nom))."' WHERE id = $id") )  {
+          exec('mv ../documents/'.escapeshellarg("${doc['lien']}/${doc['nom']}${doc['ext']}").' ..documents/'.escapeshellarg("${doc['lien']}/$nom${doc['ext']}"));
+          $message = " Le nom du document <em>$nom</em> a bien été modifié.";
+          $doc['nom'] = $nom;
+        }
+        else
+          $message ="Le nom du document <em>${doc['nom']}</em> n'a pas pu être modifié. Erreur MySQL n°".$mysqli->errno.', «'.$mysqli->error.'».';
+      }
+
+      // Modification de l'accès
+      $protection = ( in_array($_REQUEST['protection'],array(0,1,2,3,4)) ) ? $_REQUEST['protection'] : 0;
+      if ( $protection != $doc['protection'] )
+        $message .= ( requete('docs',"UPDATE docs SET protection = $protection WHERE id = $id")
+        ) ? " L'accès au document <em>${doc['nom']}</em> a bien été modifié." : " L'accès au document <em>${doc['nom']}</em> n'a pas pu être modifié. Erreur MySQL n°".$mysqli->errno.', «'.$mysqli->error.'».';
+
+      // Déplacement dans un autre répertoire
+      if ( is_numeric($parent = $_REQUEST['parent']) && ( $parent != $rid ) )  {
+        $resultat = $mysqli->query("SELECT parents, matiere FROM reps WHERE id = $parent AND FIND_IN_SET(matiere,'0,${_SESSION['matieres']}')");
+        if ( $resultat->num_rows )  {
+          $r = $resultat->fetch_assoc();
+          $resultat->free();
+          $message .= ( requete('docs',"UPDATE docs SET parent = '$parent', parents = '${r['parents']},$parent',
+                                        matiere = ${r['matiere']} WHERE id = $id")
+          ) ? " Le document <em>${doc['nom']}</em> a bien été déplacé." : " Le document <em>${doc['nom']}</em> n'a pas pu être déplacé. Erreur MySQL n°".$mysqli->errno.', «'.$mysqli->error.'».';
+        }
+      }
+      
+      // Si modification(s), on met à jour les informations récentes éventuelles
+      if ( strlen($message) )  {
+        if ( $protection < 4 )  {
+          $resultat = $mysqli->query("SELECT GROUP_CONCAT( reps.nom ORDER BY FIND_IN_SET(reps.id,docs.parents) SEPARATOR '/' ) AS path
+                                      FROM docs LEFT JOIN reps ON FIND_IN_SET(reps.id,docs.parents) WHERE docs.id = $id");
+          $r = $resultat->fetch_assoc();
+          $resultat->free();
+          $path = $mysqli->real_escape_string("${r['path']}/${doc['nom']}");
+          $icone = array_key_exists(strtolower($doc['ext']),$icones) ? $icones[strtolower($doc['ext'])] : 'defaut';
+          // Document auparavant non visible
+          if ( $doc['protection'] == 4 )
+            recent($mysqli,3,$id,"<img class=\"icone\" src=\"icones/$icone.png\"> $path","download?id=$id","<p>Nouveau document&nbsp;: <a href=\"download?id=$id\">$path</a></p>");
+          // Document auparavant visible : on met à jour les chemins, sans mettre en avant le document
+          // Remarque : le RSS n'est pas modifié, le sera leur d'une nouvelle information récente
+          // Le lien de téléchargement est de toutes façons inchangé
+          else
+            $mysqli->query("UPDATE recents SET texte = '<p>Nouveau document&nbsp;: <a href=\"download?id=$id\">$path</a></p>', titre = '<img class=\"icone\" src=\"icones/$icone.png\"> $path' WHERE id = 3000+$id");
+        }
+        // Si $protection = 4, on cherche à supprimer.
+        else
+          recent($mysqli,3,$id);
+      }
+      else
+        unset($message);
+    }
+  }
+  
+  // Suppression d'un document
+  elseif ( isset($_REQUEST['supprime_doc']) && ( $id ) )  {
+    if ( requete('docs',"DELETE FROM docs WHERE id = $id") )  {
+      $message = "Le document <em>${doc['nom']}</em> a bien été supprimé.";
+      // Suppression physique
+      exec("rm -rf ../documents/${doc['lien']}");
+      // Mise à jour des informations récentes
+      if ( $doc['protection'] < 4 )
+        recent($mysqli,3,$id);
+    }
+    else
+      $message = "Le document <em>${doc['nom']}</em> n'a pas pu être supprimé. Erreur MySQL n°".$mysqli->errno.', «'.$mysqli->error.'».';
+  }
+
+  // Mise à jour d'un document
+  elseif ( isset($_REQUEST['envoie']) && ( $id ) )  {
+    // Changement d'extension suspect donc interdit
+    $ext = ( strpos($_FILES['fichier']['name'],'.') ) ? strrchr($_FILES['fichier']['name'],'.') : '';
+    if ( $ext != $doc['ext'] )
+      $message = "Le document <em>${doc['nom']}</em> n'a pas été mis à jour&nbsp;: l'extension n'est pas celle attendue.";
+    else  {
+      // Déplacement du document uploadé au bon endroit
+      if ( move_uploaded_file($_FILES['fichier']['tmp_name'],"../documents/${doc['lien']}/${doc['nom']}${doc['ext']}") )  {
+        // Gestion de la taille
+        $taille = intval($_FILES['fichier']['size']/1024);
+        $taille = ( $taille < 1024 ) ? "$taille&nbsp;ko" : intval($taille/1024).'&nbsp;Mo';
+        // Modifications dans la base de données
+        requete('docs',"UPDATE docs SET upload = DATE(NOW()), taille = '$taille' WHERE id = $id");
+        // Information récente si document visible
+        if ( $doc['protection'] < 4 )  {
+          $resultat = $mysqli->query("SELECT GROUP_CONCAT( reps.nom ORDER BY FIND_IN_SET(reps.id,docs.parents) SEPARATOR '/' ) AS path
+                                      FROM docs LEFT JOIN reps ON FIND_IN_SET(reps.id,docs.parents) WHERE docs.id = $id");
+          $r = $resultat->fetch_assoc();
+          $resultat->free();
+          $path = $mysqli->real_escape_string("${r['path']}/${doc['nom']}");
+          $icone = array_key_exists(strtolower($doc['ext']),$icones) ? $icones[strtolower($doc['ext'])] : 'defaut';
+          recent($mysqli,3,$id,"<img class=\"icone\" src=\"icones/$icone.png\"> $path","download?id=$id","<p>Nouvelle version du document&nbsp;: <a href=\"download?id=$id\">$path</a></p>");
+        }
+        $message = "Le document <em>${doc['nom']}</em> a bien été mis à jour.";
+      }
+      else
+        $message = "Le document <em>${doc['nom']}</em> n'a pas été mis à jour car le fichier envoyé n'est pas valide.";
+    }
+  }
+
+  // Envoi d'un nouveau document
+  elseif ( isset($_REQUEST['envoie']) )  {
+    // Vérifications des données envoyées (on fait confiance aux utilisateurs connectés pour ne pas envoyer de scripts malsains)
+    $nom = $_FILES['fichier']['name'];
+    $ext = ( strpos($nom,'.') ) ? strrchr($nom,'.') : '';
+    setlocale(LC_CTYPE, "fr_FR.UTF-8");
+    $nom = basename(str_replace('/','-',str_replace($ext,'', ( strlen($_REQUEST['nom']) ) ? $_REQUEST['nom'] : $nom )));
+    $protection = ( in_array($_REQUEST['protection'],array(0,1,2,3,4)) ) ? $_REQUEST['protection'] : 0;
+    // Création du répertoire particulier
+    $lien = substr(sha1(mt_rand()),0,15);
+    while ( is_dir("../documents/$lien") )
+      $lien = substr(sha1(mt_rand()),0,15);
+    mkdir("../documents/$lien");
+    // Gestion de la taille
+    $taille = intval($_FILES['fichier']['size']/1024);
+    $taille = ( $taille < 1024 ) ? "$taille&nbsp;ko" : intval($taille/1024).'&nbsp;Mo';
+    // Récupération des données du répertoire parent
+    $resultat = $mysqli->query("SELECT parents, matiere FROM reps WHERE id = $rid");
+    $r = $resultat->fetch_assoc();
+    $resultat->free();
+    // Déplacement du document uploadé au bon endroit
+    if ( move_uploaded_file($_FILES['fichier']['tmp_name'],"../documents/$lien/$nom$ext") )  {
+      // Écriture MySQL
+      if ( requete('docs',"INSERT INTO docs SET parent = $rid, parents = '${r['parents']},$rid',
+                           matiere = ${r['matiere']}, nom = '".$mysqli->real_escape_string($nom).'\',
+                           nom_nat = \''.zpad($mysqli->real_escape_string($nom))."', upload = DATE(NOW()),
+                           taille = '$taille', lien = '$lien',
+                           ext='".$mysqli->real_escape_string($ext)."', protection = $protection") )  {
+        $id = $mysqli->insert_id;
+        $message = "Le document <em>$nom</em> a bien été mis en ligne.";
+        // Mise à jour du répertoire et des informations récentes
+        if ( $protection < 4 )  {
+          $resultat = $mysqli->query("SELECT GROUP_CONCAT( reps.nom ORDER BY FIND_IN_SET(reps.id,docs.parents) SEPARATOR '/' ) AS path
+                                      FROM docs LEFT JOIN reps ON FIND_IN_SET(reps.id,docs.parents) WHERE docs.id = $id");
+          $r = $resultat->fetch_assoc();
+          $resultat->free();
+          $path = $mysqli->real_escape_string("${r['path']}/$nom");
+          $icone = array_key_exists(strtolower($ext),$icones) ? $icones[strtolower($ext)] : 'defaut';
+          recent($mysqli,3,$id,"<img class=\"icone\" src=\"icones/$icone.png\"> $path","download?id=$id","<p>Nouveau document&nbsp;: <a href=\"download?id=$id\">$path</a></p>");
+        }
+      }
+      else  {
+        // Retour en arrière
+        exec("rm -rf ../documents/$lien");
+        $message = "Le document <em>$nom</em> n'a pas été mis en ligne à cause d'une erreur lors des modifications dans la base de données MySQL. Erreur MySQL n°".$mysqli->errno.', «'.$mysqli->error.'».';
+      }
+    }
+    else
+      $message = "Le document <em>$nom</em> n'a pas été mis en ligne car le fichier envoyé n'est pas valide.";
+  }
+  
+  // Mise à jour des champs nbrep/nbdoc dans la table 'reps'
+  $mysqli->query('UPDATE reps AS r SET 
+    nbrep = (SELECT COUNT(id) FROM (SELECT * FROM reps ) AS r1 WHERE r.id = r1.parent),
+    nbrep_v = (SELECT COUNT(id) FROM (SELECT * FROM reps WHERE reps.protection<4) AS r2 WHERE r.id = r2.parent),
+    nbdoc = (SELECT COUNT(id) FROM docs AS d WHERE r.id = d.parent),
+    nbdoc_v = (SELECT COUNT(id) FROM docs AS d WHERE r.id = d.parent AND d.protection<4)');
+  $mysqli->query('ALTER TABLE docs ORDER BY parents,nom_nat');
+  // Mise à jour des champs 'docs' dans la table 'matieres' (pour le menu)
+  $mysqli->query('UPDATE matieres SET docs = (SELECT IF(SUM(nbdoc_v),1,0) FROM reps WHERE matiere = matieres.id)');
+    
+  // Passage en connexion MySQL en lecture seulement
+  $mysqli->close();
+  $mysqli = mysql_lecture();
+}
+
+//////////
+// HTML //
+//////////
+
+// Récupération des répertoires, dans le bon ordre pour un select
+function liste($rid,$n)  {
+  $resultat = $GLOBALS['mysqli']->query("SELECT id, nom FROM reps
+                                          WHERE parent = $rid AND FIND_IN_SET(matiere,'0,${_SESSION['matieres']}')");
+  while ( $r = $resultat->fetch_assoc() )  {
+    $GLOBALS['select_reps'] .= "        <option value=\"${r['id']}\">".str_repeat('&rarr;',$n)."${r['nom']}</option>\n";
+    liste($r['id'],$n+1);
+  }
+  $resultat->free();
+}
+$select_reps = '';
+liste(0,0);
+
+// Récupération des données, vérification de la protection, répertoires parents
+$resultat = $mysqli->query("SELECT r.nom, r.parent, r.parents, r.nbrep + r.nbdoc AS nb,
+                            r.protection, r.menu, m.cle, m.nom AS mat
+                            FROM reps AS r LEFT JOIN matieres AS m ON r.matiere = m.id
+                            WHERE r.id = $rid");
+$rep = $resultat->fetch_assoc();
+$resultat->free();
+
+$t = "Gestion des documents - ${rep['nom']}";
+$p = ( is_null($rep['cle']) ) ? "docs" : "docs?${rep['cle']}";
+// Haut de page, menu et message
+include('haut.php');
+
+// Répertoires parents
+$resultat = $mysqli->query("SELECT id, nom, IF(parent,'next','home') AS icone,
+                            IF(protection,'-lock','') AS protection
+                            FROM reps WHERE FIND_IN_SET(id,'${rep['parents']},$rid')");
+$parents = '';
+while ( $r = $resultat->fetch_assoc() )
+  $parents .= "\n    <a href=\"?rep=${r['id']}\"><img class=\"icone\" src=\"../icones/${r['icone']}${r['protection']}.png\">${r['nom']}</a>";
+$resultat->free();
+echo <<<FIN
+  
+  <div class="item" id="parents">$parents
+  </div>
+  
+  <div id="ordre">
+    <span>Ordre alphabétique&nbsp;<a href="?rep=$rid&amp;ordre=alpha"><img src="../icones/down.png" alt="croissant"></a>&nbsp;<a href="?rep=$rid&amp;ordre=alpha-r"><img src="../icones/up.png" alt="décroissant"></a></span>
+    <span>Ordre chronologique&nbsp;<a href="?rep=$rid&amp;ordre=chrono"><img src="../icones/down.png" alt="croissant"></a>&nbsp;<a href="?rep=$rid&amp;ordre=chrono-r"><img src="../icones/up.png" alt="décroissant"></a></span>
+  </div>
+
+FIN;
+
+// Affichage du répertoire et de son contenu
+if ( $rep['nb'] )  {
+  
+  function affichage($mysqli, $rid, $n)  {
+    $indent = str_pad('',2*$n);
+
+    // Sous-répertoires et récursivité
+    $resultat = $mysqli->query("SELECT id, nom, nbrep, nbdoc, protection FROM reps WHERE parent = $rid");
+    if ( $resultat->num_rows )  {
+      while ( $r = $resultat->fetch_assoc() )  {
+        if ( $r['nbrep']+$r['nbdoc'] )
+          $contenu = str_replace(array('0 répertoire, ',', 0 document'),'',"(${r['nbrep']} répertoire".( ( $r['nbrep'] > 1 ) ? 's' : '' ).", ${r['nbdoc']} document".( ( $r['nbdoc'] > 1 ) ? 's' : '' ));
+        else
+          $contenu = '(vide';
+        $contenu .= ( $r['protection'] == 4 ) ? '&nbsp;-&nbsp;non visible)' : ')';
+        $s = ( $r['protection'] ) ? '<p class="rep open lock"><img class="icone" src="../icones/rep-open-lock.png">' : '<p class="rep open"><img class="icone" src="../icones/rep-open.png">';
+        echo <<<FIN
+
+$indent<div class="rep">
+$indent  $s<a href="?rep=${r['id']}">${r['nom']}</a> $contenu</p>
+FIN;
+        affichage($mysqli,$r['id'],$n+1);
+        echo "\n$indent</div>";
+      }
+    }
+    $resultat->free();
+
+    // Documents
+    $ordre = '';
+    if ( isset($_REQUEST['ordre']) )
+      switch ( $_REQUEST['ordre'] )  {
+        case 'alpha':
+          $ordre = ' ORDER BY nom_nat ASC';
+          break;
+        case 'alpha-r':
+          $ordre = ' ORDER BY nom_nat DESC';
+          break;
+        case 'chrono':
+          $ordre = ' ORDER BY docs.upload ASC';
+          break;
+        case 'chrono-r':
+          $ordre = ' ORDER BY docs.upload DESC';
+      }
+    $resultat = $mysqli->query("SELECT id, nom, taille, DATE_FORMAT(upload,'%d/%m/%Y') AS upload,
+                                LOWER(ext) AS ext, protection FROM docs WHERE parent = $rid$ordre");
+    if ( $resultat->num_rows )  {
+      $icones = $GLOBALS['icones'];
+      while ( $r = $resultat->fetch_assoc() )  {
+        $icone = array_key_exists($r['ext'],$icones) ? $icones[$r['ext']] : 'defaut';
+        $id = $r['id'];
+        if ( $r['protection'] && ( $r['protection'] < 4 ) )
+          $icone .= '-lock';
+        $cache = ( $r['protection'] == 4 ) ? ' (non visible)' : '';
+        $select_reps = str_replace(array("\"$rid\"",'        '),array("\"$rid\" selected","$indent      "),$GLOBALS['select_reps']);
+        $select_protection = str_replace("\"${r['protection']}\"","\"${r['protection']}\" selected","
+$indent        <option value=\"0\">Visible de tous</option>
+$indent        <option value=\"1\">Visible pour les élèves, colleurs, profs</option>
+$indent        <option value=\"2\">Visible pour les colleurs et les profs</option>
+$indent        <option value=\"3\">Visible pour les profs uniquement</option>
+$indent        <option value=\"4\">Non visible</option>");
+        echo <<<FIN
+
+$indent<div class="fic admin">
+$indent  <span><img class="icone" src="../icones/$icone.png">${r['nom']}</span>$cache
+$indent  <form action="?rep=${GLOBALS['rid']}" method="post" enctype="multipart/form-data">
+$indent    <input class="bouton" type="submit" name="envoie" value="Envoyer">
+$indent    <p class="ligne"><label for="fichier$id">Mettre à jour&nbsp;: </label><input type="file" id="fichier$id" name="fichier"></p>
+$indent    <input type="hidden" name="id" value="$id">
+$indent  </form>
+$indent  <hr>
+$indent  <form action="?rep=${GLOBALS['rid']}" method="post">
+$indent    <p class="boutons"><input type="submit" name="modifie_doc" value="Valider" title="Valider les modifications"><input type="submit" name="supprime_doc" value="Supprimer" title="Supprimer ce document"></p>
+$indent    <p class="ligne"><label for="nomdoc$id">Nom à afficher&nbsp;: </label><input type="text" id="nom$id" name="nom" value="${r['nom']}" size="50"></p>
+$indent    <p class="ligne"><label for="parent$id">Répertoire&nbsp;: </label><select id="parent$id" name="parent">
+$select_reps$indent    </select></p>
+$indent    <p class="ligne"><label for="protecdoc$id">Accès&nbsp;: </label>
+$indent      <select id="protecdoc$id" name="protection">$select_protection
+$indent      </select>
+$indent    </p>
+$indent    <input type="hidden" name="id" value="$id">
+$indent    <p class="ligne"><label>Lien&nbsp;: </label><code>&lt;a href="download?id=$id"&gt;${r['nom']}&lt;/a&gt;</code></p>
+$indent    <p class="ligne"><a href="download?id=$id">Télécharger</a>|&nbsp;Taille&nbsp;: ${r['taille']}&nbsp;|&nbsp;Diffusé le ${r['upload']}</p>
+$indent  </form>
+$indent</div>
+FIN;
+      }
+      $resultat->free();
+    }
+  }
+
+  affichage($mysqli,$rid,1);
+  
+}
+else
+  echo "\n  <h3 class=\"warning\">Ce répertoire est vide.</h3>\n\n";
+
+/////////////////////////////////
+// Formulaires de modification //
+/////////////////////////////////
+
+// Taille maximale de fichier (pour l'aide)
+$taille = min(ini_get('upload_max_filesize'),ini_get('post_max_size'));
+if ( stristr($taille,'m') )
+  $taille = substr($taille,0,-1)*1048576;
+elseif ( stristr($taille,'k') )
+  $taille = substr($taille,0,-1)*1024;
+$taille = ( $taille < 1048576 ) ? intval($taille/1024).'&nbsp;ko' : intval($taille/1048576).'&nbsp;Mo';
+
+// Restrictions pour les modifications sur le répertoire
+$indication = $pas_suppr = $disabled = '';
+if ( $rep['nb'] )  {
+  $indication = "\n    <p>Attention, la suppression de ce répertoire entraînera de façon définitive la suppression de tous les sous-répertoires et de tous les documents qui s'y trouvent. Aucune confirmation ne sera demandée.</p>";
+}
+if ( !$rep['parent'] )  {
+  $disabled = $pas_suppr = ' disabled';
+  $indication = "\n    <p>Ce répertoire est un répertoire racine, il ne peut être ni supprimé, ni renommé, ni déplacé.</p>";
+  $select_reps = '        <option></option>';
+}  
+
+// Choix par défaut de la protection du nouveau document et du sous-répertoire
+$select_protection = str_replace("\"${rep['protection']}\"","\"${rep['protection']}\" selected",'
+        <option value="0">Visible de tous</option>
+        <option value="1">Visible pour les élèves, colleurs, profs</option>
+        <option value="2">Visible pour les colleurs et les profs</option>
+        <option value="3">Visible pour les profs uniquement</option>
+        <option value="4">Non visible</option>');
+
+  // HTML
+?>
+
+  <div class="item aide">
+    <h3>Aide et explications</h3>
+    <p>Vous pouvez ci-dessous modifier le répertoire <?php echo $rep['nom']; ?> ainsi que l'ensemble des documents contenus dans les sous-répertoires.</p>
+    <p>Les liens vers les répertoires et les documents sont garantis&nbsp;: aucune modification (déplacement, changement de nom...) réalisée sur les répertoires ou les documents ne peut modifier ces liens.</p>
+    <p>Le lien dans le menu vers le répertoire de premier niveau de chaque matière est généré automatiquement, seulement si des documents sont disponibles.</p>
+    <p>Seul le répertoire général, les répertoires correspondant aux matières qui vous sont associées et tous leurs sous-répertoires sont accessibles. Les matières qui vous sont associées sont modifiables dans vos <a href="prefs">Préférences</a>.</p>
+    <h4>Liste de sous-répertoires et documents</h4>
+    <p>Dans cette arborescence, les sous-répertoires se déplient en cliquant sur leur icône. Cliquer sur leur nom envoie vers la page correspondant au répertoire en question.</p>
+    <p>Cliquer sur l'icône ou le nom d'un document ouvre le formulaire de modification correspondant.</p>
+    <h4>Modification des documents déjà envoyés</h4>
+    <p>Les documents déjà envoyés sont modifiables en cliquant sur leur nom ou leur icône dans l'arborescence.</p>
+    <p>Vous pouvez <em>mettre à jour</em> un document déjà envoyé&nbsp;: cela évite de supprimer/recréer le document, et permet aux liens vers le document d'être toujours valables (l'adresse ne change pas). Une nouvelle information récente apparaîtra pour indiquer cette mise à jour.</p>
+    <p>Vous pouvez aussi renommer tout document. Le <em>nom à afficher</em> sera le nom réellement affiché sur le site web (sans extension&nbsp;: elle est détectée automatiquement). Cette case ne peut être vide.</p>
+    <p>Vous pouvez aussi déplacer tout document. Seul le répertoire général, les répertoires correspondant aux matières qui vous sont associées et tous leurs sous-répertoires peuvent accueillir vos documents. Les matières qui vous sont associées sont modifiables dans vos <a href="prefs">Préférences</a>.</p>
+    <p>Vous pouvez modifier l'<em>accès</em> du document parmi cinq possibilités&nbsp;:</p>
+    <ul>
+      <li><em>Visible de tous</em>&nbsp;: document accessible de tout visiteur, sans identification.</li>
+      <li><em>Visible pour les élèves, colleurs, professeurs</em>&nbsp;: document accessible uniquement par tout utilisateur après identification. Il faut donc avoir un compte validé (voir l'aide de la page <a href="utilisateurs">Utilisateurs</a>) pour y accéder.</li>
+      <li><em>Visible pour les colleurs, professeurs</em>&nbsp;: document accessible uniquement par les utilisateurs de type colleur ou professeur. Attention, si l'accès au répertoire est différent, le titre du document peut être visible par les élèves ou un visiteur non identifié.</li>
+      <li><em>Visible pour les professeurs</em>&nbsp;: document accessible uniquement par les utilisateurs de type professeur (vous et vos collègues).</li>
+      <li><em>Non visible</em>&nbsp;: document n'est pas encore visible en ligne. Cela peut être utile si vous souhaitez le mettre à disposition ultérieurement. C'est complètement sûr&nbsp;: vous pouvez mettre comme cela le prochain sujet de devoir ou même le corrigé.</li>
+    </ul>
+    <p>Les documents visibles après identification ont un cadenas dans leur icône. Les documents non visibles sont marqués comme tels.</p>
+    <p>Chaque formulaire déplié présente par ailleurs le lien vers le document (en HTML) ainsi qu'un lien de téléchargement si vous souhaitez vérifier immédiatement de quel document il s'agit.</p>
+    <h4>Préférences du répertoire</h4>
+    <p>Seul le répertoire <?php echo $rep['nom']; ?> est modifiable ici. Pour modifier le nom ou déplacer un autre répertoire, il faut afficher sa propre page en cliquant sur son nom dans l'arborescence.</p>    
+    <p>Modifier le <em>répertoire parent</em> revient à déplacer le répertoire et l'ensemble de son contenu. Le déplacement n'est possible qu'au sein du répertoire général ou des répertoires correspondant aux matières qui vous sont associées. Les matières qui vous sont associées sont modifiables dans vos <a href="prefs">Préférences</a>.</p>
+    <p>La case à cocher <em>Affichage du répertoire dans le menu</em> permet d'afficher un lien direct dans le menu (partie publique et interface d'administration) vers la page correspondant au répertoire.</p>
+    <p>Vous pouvez modifier l'<em>accès</em> du répertoire parmi cinq possibilités&nbsp;:</p>
+    <ul>
+      <li><em>Visible de tous</em>&nbsp;: répertoire accessible de tout visiteur, sans identification. Les titres des documents sont visibles, mais chaque document peut avoir son propre accès.</li>
+      <li><em>Visible pour les élèves, colleurs, professeurs</em>&nbsp;: répertoire accessible uniquement par tout utilisateur après identification. Il faut donc avoir un compte validé (voir l'aide de la page <a href="utilisateurs">Utilisateurs</a>) pour y accéder.</li>
+      <li><em>Visible pour les colleurs, professeurs</em>&nbsp;: répertoire accessible uniquement par les utilisateurs de type colleur ou professeur. Attention, si l'accès au répertoire est différent, le titre du document peut être visible par les élèves ou un visiteur non identifié.</li>
+      <li><em>Visible pour les professeurs</em>&nbsp;: répertoire accessible uniquement par les utilisateurs de type professeur (vous et vos collègues).</li>
+      <li><em>Non visible</em>&nbsp;: répertoire non visible en ligne. Il n'apparaît pas sur la partie publique. Cela peut être utile si vous souhaitez le mettre à disposition ultérieurement.</li>
+    </ul>
+    <p>La case à cocher <em>Propager le choix ci-dessus à chaque document/sous-répertoire</em> permet de faire suivre le choix précédent à l'ensemble du contenu du répertoire. Si elle est décochée, aucune modification supplémentaire n'a lieu. Si elle est cochée, une action sera effectuée même si la précédente case n'a pas changé d'état.</p>
+    <h4>Suppression d'un répertoire</h4>
+    <p>Les répertoires racines (<em>Général</em> ou ayant le nom d'une matière) ne peuvent être supprimés. Tous les autres peuvent être supprimés. La suppression d'un répertoire entraîne automatiquement la suppression de tous les sous-répertoires et de tous les documents qu'il contient.</p>
+    <h4>Nouveau document</h4>
+    <p>Il est possible d'envoyer un nouveau document grâce au formulaire. Le document sera automatiquement dans le répertoire <?php echo $rep['nom']; ?>. Si vous souhaitez envoyer votre document dans un autre répertoire, il faut aller sur la page de ce répertoire en cliquant sur son nom dans l'arborescence.</p>
+    <p>Le <em>nom à afficher</em> sera le nom réellement affiché sur le site web (sans extension&nbsp;: elle est détectée automatiquement lors de l'envoi). Si vous laissez cette case vide, le nom du fichier envoyé sera récupéré.</p>
+    <p>Vous pouvez choisir l'<em>accès</em> du document parmi cinq possibilités&nbsp;:</p>
+    <ul>
+      <li><em>Visible de tous</em>&nbsp;: document accessible de tout visiteur, sans identification.</li>
+      <li><em>Visible pour les élèves, colleurs, professeurs</em>&nbsp;: document accessible uniquement par tout utilisateur après identification. Il faut donc avoir un compte validé (voir l'aide de la page <a href="utilisateurs">Utilisateurs</a>) pour y accéder.</li>
+      <li><em>Visible pour les colleurs, professeurs</em>&nbsp;: document accessible uniquement par les utilisateurs de type colleur ou professeur. Attention, si l'accès au répertoire est différent, le titre du document peut être visible par les élèves ou un visiteur non identifié.</li>
+      <li><em>Visible pour les professeurs</em>&nbsp;: document accessible uniquement par les utilisateurs de type professeur (vous et vos collègues).</li>
+      <li><em>Non visible</em>&nbsp;: document n'est pas encore visible en ligne. Cela peut être utile si vous souhaitez le mettre à disposition ultérieurement. C'est complètement sûr&nbsp;: vous pouvez mettre comme cela le prochain sujet de devoir ou même le corrigé.</li>
+    </ul>
+    <p>La taille du fichier envoyé est limitée à <?php echo $taille; ?>. Tout document est modifiable et déplaçable après envoi, sans que cela ne modifie le lien vers le document.</p>
+    <h4>Nouveau sous-répertoire</h4>
+    <p>Il est possible de créer autant de sous-répertoires que vous le souhaitez. Le <em>nom</em> sera le nom du sous-répertoire.</p>
+    <p>La case à cocher <em>Affichage du répertoire dans le menu</em> permet d'afficher un lien direct dans le menu (partie publique et interface d'administration) vers la page correspondant au répertoire.</p>
+    <p>Vous pouvez définir l'<em>accès</em> du sous-répertoire parmi cinq possibilités&nbsp;:</p>
+    <ul>
+      <li><em>Visible de tous</em>&nbsp;: répertoire accessible de tout visiteur, sans identification. Les titres des documents sont visibles, mais chaque document peut avoir son propre accès.</li>
+      <li><em>Visible pour les élèves, colleurs, professeurs</em>&nbsp;: répertoire accessible uniquement par tout utilisateur après identification. Il faut donc avoir un compte validé (voir l'aide de la page <a href="utilisateurs">Utilisateurs</a>) pour y accéder.</li>
+      <li><em>Visible pour les colleurs, professeurs</em>&nbsp;: répertoire accessible uniquement par les utilisateurs de type colleur ou professeur. Attention, si l'accès au répertoire est différent, le titre du document peut être visible par les élèves ou un visiteur non identifié.</li>
+      <li><em>Visible pour les professeurs</em>&nbsp;: répertoire accessible uniquement par les utilisateurs de type professeur (vous et vos collègues).</li>
+      <li><em>Non visible</em>&nbsp;: répertoire non visible en ligne. Il n'apparaît pas sur la partie publique. Cela peut être utile si vous souhaitez le mettre à disposition ultérieurement.</li>
+    </ul>
+  </div>
+
+  <div class="item admin">
+  <form action="<?php echo "?rep=$rid"; ?>" method="post">
+    <input class="bouton" type="submit" name="modifie" value="Valider" title="Valider les modifications">
+    <input class="bouton" type="submit" name="supprime" value="Supprimer" title="Supprimer ce répertoire"<?php echo $pas_suppr;?>>
+    <h3>Modifier le répertoire <em><?php echo $rep['nom']; ?></em></h3>
+    <p class="ligne"><label for="nomrep">Nom&nbsp;: </label><input type="text" id="nomrep" name="nom" value="<?php echo $rep['nom']; ?>" size="50"<?php echo $disabled;?>></p>
+    <p class="ligne"><label for="parent">Répertoire parent&nbsp;: </label>
+      <select id="parent" name="parent"<?php echo $disabled;?>>
+<?php echo str_replace("\"${rep['parent']}\"","\"${rep['parent']}\" selected",$select_reps); ?>
+      </select>
+    </p>
+    <p class="ligne"><label for="menurep">Affichage du répertoire dans le menu&nbsp;: </label><input type="checkbox" id="menurep" name="menu" value="1"<?php echo ( $rep['menu'] ) ? ' checked' : ''; echo $disabled; ?>></p>
+    <p class="ligne"><label for="protecrep">Accès&nbsp;: </label>
+      <select id="protecrep" name="protection"><?php echo $select_protection; ?></select>
+    </p>
+    <p class="ligne"><label for="propagation">Propager le choix ci-dessus à chaque document/sous-répertoire&nbsp;: </label><input type="checkbox" id="propagation" name="propagation" value="1"></p><?php echo $indication;?>
+  </form>
+  </div>
+
+  <div class="item admin">
+  <form action="<?php echo "?rep=$rid"; ?>" method="post" enctype="multipart/form-data">
+    <input class="bouton" type="submit" name="envoie" value="Envoyer">
+    <h3>Déposer un document dans le répertoire <em><?php echo $rep['nom']; ?></em></h3>
+    <p class="ligne"><label for="nomdoc">Nom à afficher&nbsp;: </label><input type="text" id="nomdoc" name="nom" value="" size="50"></p>
+    <p class="ligne"><label for="fichier">Fichier&nbsp;: </label><input type="file" id="fichier" name="fichier"></p>
+    <p class="ligne"><label for="protecdoc">Accès&nbsp;: </label>
+      <select id="protecdoc" name="protection"><?php echo $select_protection; ?></select>
+    </p>
+    <input type="hidden" name="id" value="0">
+  </form>
+  </div>
+
+  <div class="item admin">
+  <form action="<?php echo "?rep=$rid"; ?>" method="post" enctype="multipart/form-data">
+    <input class="bouton" type="submit" name="cree" value="Créer">
+    <h3>Créer un sous-répertoire</h3>
+    <p class="ligne"><label for="nomssrep">Nom&nbsp;: </label><input type="text" id="nomssrep" name="nom" value="" size="50"></p>
+    <p class="ligne"><label for="menussrep">Affichage du répertoire dans le menu&nbsp;: </label><input type="checkbox" id="menussrep" name="menu" value="1"></p>
+    <p class="ligne"><label for="protecssrep">Accès&nbsp;: </label>
+      <select id="protecssrep" name="protection"><?php echo $select_protection; ?></select>
+    </p>
+  </form>
+  </div>
+
+  <script type="text/javascript">
+$( function() {
+  $('.fic span').replaceWith(function() { return '<a href="">'+$(this).html()+'</a>'; });
+  $('.fic > a').click( function () {
+    $(this).parent().find('form,hr').toggle();
+    $(this).parent().toggleClass('admin');
+    return false;
+  }).click();
+  $('p.rep img').css('cursor','pointer').click( function () {
+    var p = $(this).parent();
+    p.toggleClass('open');
+    p.parent().children('.fic,div.rep').toggle();
+    $(this).attr('src',function(i,val){ return val.replace(p.is('.open')?'rep':'rep-open',p.is('.open')?'rep-open':'rep'); });
+  });
+  $('p.rep img').attr('src',function(i,val){return val.replace('rep-open','rep');}).parent().removeClass('open');
+  $('div.rep .fic,div.rep div.rep').hide();
+});
+  </script>
+  
+<?php
+$mysqli->close();
+
+// Bas de page
+include('bas.php');
+?>
diff -urN cahier-de-prepa3.2.0/admin/download.php cahier-de-prepa4.0.0/admin/download.php
--- cahier-de-prepa3.2.0/admin/download.php	1970-01-01 01:00:00.000000000 +0100
+++ cahier-de-prepa4.0.0/admin/download.php	2014-08-04 17:59:12.243675325 +0200
@@ -0,0 +1,149 @@
+<?php
+// Sécurité et récupération des données de configuration
+define('OK',1);
+include('debut.php');
+
+///////////////////////////////////
+// Validation de la requête : id //
+///////////////////////////////////
+
+// Récupération du lien
+if ( isset($_REQUEST['id']) && is_numeric($id = $_REQUEST['id']) )  {
+  $resultat = $mysqli->query("SELECT nom, lien, ext FROM docs WHERE id = $id AND FIND_IN_SET(matiere,'0,${_SESSION['matieres']}')");
+  if ( $resultat->num_rows )  {
+    $f = $resultat->fetch_assoc();
+    $resultat->free();
+    $mysqli->close();
+
+    // Définition du type de fichier (entête HTML à envoyer)
+    switch ( $f['ext'] )  {
+      case '.pdf':
+        $type = 'application/pdf';
+        break;
+      case '.doc':
+        $type = 'application/msword';
+        break;
+      case '.odt':
+        $type = 'application/vnd.oasis.opendocument.text';
+        break;
+      case '.docx':
+        $type = 'application/vnd.openxmlformats-officedocument.wordprocessingml.document';
+        break;
+      case '.xls':
+        $type = 'application/vnd.ms-excel';
+        break;
+      case '.ods':
+        $type = 'application/vnd.oasis.opendocument.spreadsheet';
+        break;
+      case '.xlsx':
+        $type = 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet';
+        break;
+      case '.ppt':
+        $type = 'application/vnd.ms-powerpoint';
+        break;
+      case '.odp':
+        $type = 'application/vnd.oasis.opendocument.presentation';
+        break;
+      case '.pptx':
+        $type = 'application/vnd.openxmlformats-officedocument.presentationml.presentation';
+        break;
+      case '.jpg':
+      case '.jpeg':
+        $type = 'image/jpeg';
+        break;
+      case '.png':
+        $type = 'image/png';
+        break;
+      case '.gif':
+        $type = 'image/gif';
+        break;
+      case '.svg':
+        $type = 'image/svg+xml';
+        break;
+      case '.tif':
+      case '.tiff':
+        $type = 'image/tiff';
+        break;
+      case '.bmp':
+        $type = 'image/x-ms-bmp';
+        break;
+      case '.ps':
+      case '.eps':
+        $type = 'application/postscript';
+        break;
+      case '.avi':
+        $type = 'video/x-msvideo';
+        break;
+      case '.mpeg':
+      case '.mpg':
+        $type = 'video/mpeg';
+        break;
+      case '.wmv':
+        $type = 'video/x-ms-wmv';
+        break;
+      case '.mp4':
+        $type = 'video/mp4';
+        break;
+      case '.ogv':
+        $type = 'video/ogg';
+        break;
+      case '.qt':
+      case '.mov':
+        $type = 'video/quicktime';
+        break;
+      case '.mkv':
+        $type = 'video/x-matroska';
+        break;
+      case '.mp3':
+        $type = 'audio/mpeg';
+        break;
+      case '.ogg':
+      case '.oga':
+        $type = 'audio/ogg';
+        break;
+      case '.wma':
+        $type = 'audio/x-ms-wma';
+        break;
+      case '.wav':
+        $type = 'audio/x-wav';
+        break;
+      case '.ra':
+      case '.rm':
+        $type = 'audio/x-pn-realaudio';
+        break;
+      case '.txt':
+        $type = 'text/plain';
+        break;
+      case '.rtf':
+        $type = 'application/rtf';
+        break;
+      case '.zip':
+        $type = 'application/zip';
+        break;
+      case '.rar':
+        $type = 'application/rar';
+        break;
+      case '.7z':
+        $type = 'application/x-7z-compressed';
+        break;
+      case '.sh':
+        $type = 'text/x-sh';
+        break;
+      case '.py':
+        $type = 'text/x-python';
+        break;
+      default :
+        $type = 'application/octet-stream';
+    }
+
+    // Mise à disposition du fichier
+    header("Content-Type: $type");
+    header('Content-Disposition: attachment; filename="'.rawurlencode($f['nom']).$f['ext'].'"');
+    header("Location: ../documents/${f['lien']}/".rawurlencode($f['nom']).$f['ext']);
+  }
+  else  {
+    $mysqli->close();
+    exit('<p>Mauvais paramètre d\'accès à cette page. Vous n\'avez accès qu\'aux documents généraux ou correspondant aux matières associées à votre compte.</p><p><a href=".">Revenir à l\'interface d\'administration</a></p>');
+  }
+}
+?>
diff -urN cahier-de-prepa3.2.0/admin/fonctions.php cahier-de-prepa4.0.0/admin/fonctions.php
--- cahier-de-prepa3.2.0/admin/fonctions.php	1970-01-01 01:00:00.000000000 +0100
+++ cahier-de-prepa4.0.0/admin/fonctions.php	2014-08-24 23:56:01.951656652 +0200
@@ -0,0 +1,155 @@
+<?php
+// Sécurité : ne doit pas être exécuté s'il n'est pas lancé par un script autorisé
+if ( !defined('OK') )  exit();
+
+/////////////////////////////////////////////
+// Fonction de connexion à la base MySQL
+function mysql_lecture()  {
+  $mysqli = new mysqli($GLOBALS['serveur'],$GLOBALS['base'],$GLOBALS['mdp'],$GLOBALS['base']);
+  $mysqli->set_charset('utf8');
+  return $mysqli;
+}
+function mysql_ecriture()  {
+  $mysqli = new mysqli($GLOBALS['serveur'],$GLOBALS['base'].'-adm',$GLOBALS['mdp'],$GLOBALS['base']);
+  $mysqli->set_charset('utf8');
+  return $mysqli;
+}
+
+/////////////////////////////////////////////
+// Fonction d'affichage des semaines
+function format_date($date)  {
+  $semaine = array('dimanche','lundi','mardi','mercredi','jeudi','vendredi','samedi');
+  $mois = array('','janvier','février','mars','avril','mai','juin','juillet','août','septembre','octobre','novembre','décembre');
+  return $semaine[substr($date,0,1)].' '.substr($date,7).' '.$mois[intval(substr($date,5,2))].' '.substr($date,1,4);
+}
+
+/////////////////////////////////////////////
+// Fonction d'envoi de requêtes MySQL et d'enregistrement
+// A besoin d'une connexion MySQL en écriture déjà existante
+//  * sauvegarde une fois par mois la table complète
+//  * enregistre la requête
+//  * exécute la requête
+//  * renvoie le résultat de l'exécution
+function requete($table,$requete)  {
+  $mysqli = $GLOBALS['mysqli'];
+  if ( is_dir('../sauvegarde') && is_executable('../sauvegarde') && is_writable('../sauvegarde') )
+    $rep = '../sauvegarde';
+  elseif ( is_dir('sauvegarde') && is_executable('sauvegarde') && is_writable('sauvegarde') )
+    $rep = 'sauvegarde';
+  if ( isset($rep) )  {
+    // Sauvegarde de la table complète une seule fois par mois
+    $mois = date('Y-m');
+    $heure = date('d/m/Y à H:i:s');
+    if ( !file_exists("$rep/$table.$mois.php") )  {
+      $s = <<<FIN
+  <?php exit(); ?>
+-- Sauvegarde complète de la table $table le $heure
+TRUNCATE `$table`; 
+FIN;
+      $resultat = $mysqli->query("SHOW COLUMNS FROM `$table`");
+      $s1 = "INSERT INTO $table (";
+      while ( $r = $resultat->fetch_row() )
+        $s1 .= "`${r[0]}`,";
+      $s1 = substr($s1,0,-1).') VALUES';
+      $resultat->free();
+      // Récupération des données
+      $resultat = $mysqli->query("SELECT * FROM `$table`");
+      if ( $resultat->num_rows )  {
+        while ( $r = $resultat->fetch_row() )
+          $s1 .= "\n  ('".  str_replace('SEPARATEUR','\',\'',addslashes(implode('SEPARATEUR',$r)))  .'\'),';
+        $s1 = substr($s1,0,-1).';';
+        $resultat->free();
+      }
+      else
+        $s1 = '-- Table vide !';
+      $fichier = fopen("$rep/$table.$mois.php",'wb');
+      fwrite($fichier, "$s\n$s1\n");
+    }
+    else
+      $fichier = fopen("$rep/$table.$mois.php",'ab');
+    // Sauvegarde systématique de la requête
+    if ( isset($_SESSION['login'])  )
+      fwrite($fichier, "\n-- Requête de ${_SESSION['login']} (${_SESSION['ip']}) le $heure\n$requete;\n");
+    else  {
+      $login = ( isset($GLOBALS['utilisateur']) ) ? $GLOBALS['utilisateur']['login'] : $GLOBALS['login'];
+      fwrite($fichier, "\n-- Requête de $login (${_SERVER['REMOTE_ADDR']}) le $heure\n$requete;\n");
+    }
+    fclose($fichier);
+  }
+  return $mysqli->query($requete);
+}
+
+///////////////////////////////////////////
+// Fonction de mise à jour des nouveautés
+function recent($mysqli,$type,$id,$titre='',$lien='',$texte='')  {
+  // type : 1->informations, 2->programmes de colles, 3->documents
+  // id : celui de l'information/le programme de colles/le document
+
+  // Ajout dans la base de données, suppression si $titre est vide
+  if ( strlen($titre) )
+    $mysqli->query('REPLACE INTO recents SET id = '.($type*1000+$id).", heure = NOW(), titre = '$titre', lien = '$lien', texte = '$texte'");
+  else
+    $mysqli->query('DELETE FROM recents WHERE id = '.($type*1000+$id));
+  
+  // Pas la peine d'aller plus loin si la table n'a pas été modifiée.
+  if ( !($mysqli->affected_rows) )
+    return;
+
+  // Nettoyage des anciennes entrées
+  $mysqli->query('DELETE FROM recents WHERE DATEDIFF(NOW(),heure) > 30');
+  // Mise dans l'ordre, les plus récents en premier
+  $mysqli->query('ALTER TABLE recents ORDER BY heure DESC');
+
+  // Titre : on récupère celui de la première page
+  $resultat = $mysqli->query('SELECT titre FROM pages WHERE id = 1');
+  $r = $resultat->fetch_assoc();
+  $resultat->free();
+  $t = "${r['titre']}";
+  
+  // Regénération du flux RSS
+  $rep = '../documents/'.sha1($GLOBALS['base']);
+  $d = date(DATE_RSS);
+  $rss = <<<FIN
+<?xml version="1.0" encoding="UTF-8"?>
+<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
+  <channel>
+    <title>$t</title>
+    <atom:link href="http://${GLOBALS['site']}/$rep/rss.xml" rel="self" type="application/rss+xml" />
+    <link>http://${GLOBALS['site']}</link>
+    <description>$t</description>
+    <lastBuildDate>$d</lastBuildDate>
+    <language>fr-FR</language>
+
+
+FIN;
+  // Récupération des items
+  $resultat = $mysqli->query('SELECT UNIX_TIMESTAMP(heure) AS heure, titre, lien, texte FROM recents');
+  while ( $r = $resultat->fetch_assoc() )  {
+    $d = date(DATE_RSS,$r['heure']);
+    $titre = preg_replace('/^<[^>]*> /','',$r['titre']);
+    $texte = preg_replace('/href="([^h])/',"href=\"http://${GLOBALS['site']}/\\1",$r['texte']);
+    $rss .= <<<FIN
+    <item>
+      <title><![CDATA[$titre]]></title>
+      <link>http://${GLOBALS['site']}/${r['lien']}</link>
+      <guid isPermaLink="false">${r['heure']}</guid>
+      <description><![CDATA[$texte]]></description>
+      <pubDate>$d</pubDate>
+    </item>
+
+
+FIN;
+  }
+  $rss .= <<<FIN
+  </channel>
+</rss>
+
+FIN;
+
+  // Mise à jour du flux RSS
+  if ( !is_dir($rep) )  mkdir($rep);
+  $fichier = fopen($rep.'/rss.xml','wb');
+  fwrite($fichier, $rss);
+  fclose($fichier);
+}
+?>
diff -urN cahier-de-prepa3.2.0/admin/haut.php cahier-de-prepa4.0.0/admin/haut.php
--- cahier-de-prepa3.2.0/admin/haut.php	1970-01-01 01:00:00.000000000 +0100
+++ cahier-de-prepa4.0.0/admin/haut.php	2014-08-25 00:02:35.203669236 +0200
@@ -0,0 +1,127 @@
+<?php
+// Sécurité : ne doit pas être exécuté s'il n'est pas lancé par un script autorisé
+if ( !defined('OK') )  exit();
+
+/////////*/
+// HTML //
+//////////
+
+// La connexion MySQL (lien $mysqli) doit être obligatoirement établie par le script appelant.
+// $t et $p doivent être réglés par le script appelant
+// $t = titre de la page, $p = cle de la page
+?><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
+<html>
+<head>
+  <title><?php echo $t; ?></title>
+  <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+  <link rel="stylesheet" href="../css/style.css" type="text/css" media="screen">
+  <link rel="stylesheet" href="../css/print.css" type="text/css" media="print">
+  <link rel="stylesheet" href="../css/couleurs.css" type="text/css" media="screen">
+  <script type="text/javascript" src="../js/jquery.js"></script>
+  <script type="text/javascript" src="/MathJax/MathJax.js?config=TeX-AMS-MML_HTMLorMML"></script>
+  <link rel="stylesheet" href="../js/jquery.datepick.css" type="text/css">
+  <link rel="stylesheet" href="../js/jquery.timeentry.css" type="text/css">
+  <script type="text/javascript" src="../js/jquery.plugin.js"></script>
+  <script type="text/javascript" src="../js/jquery.datepick.js"></script>
+  <script type="text/javascript" src="../js/jquery.datepick-fr.js"></script>
+  <script type="text/javascript" src="../js/jquery.timeentry.js"></script>
+  <script type="text/javascript" src="../js/jquery.timeentry-fr.js"></script>
+  <script type="text/javascript" src="js.php"></script>
+  <script type="text/x-mathjax-config">
+    MathJax.Hub.Config({tex2jax: {inlineMath: [["$","$"],["\\(","\\)"]]}});
+  </script>
+</head>
+<body>
+<div id="global">
+
+<h1><?php echo $t; ?></h1>
+
+<div id="colonne">
+
+  <div id="menu">
+    <div>
+<?php
+  // Gestion du compte
+  $menu = <<<FIN
+      <h3>Mon compte</h3>
+      <a href=".">Accueil</a>
+      <a href="prefs">Préférences</a>
+      <a href="mail">Envoyer un mail</a>
+      <a href=".?deconnexion">Se déconnecter</a>
+    </div>
+    <div>
+      <h3>Pages d'informations</h3>
+FIN;
+
+  // Pages d'informations
+  $resultat = $mysqli->query("SELECT CONCAT_WS('/',m.cle,p.cle) AS cle, CONCAT_WS('/',m.nom,p.nom) AS nom
+                              FROM pages AS p LEFT JOIN matieres AS m ON p.mat = m.id
+                              WHERE FIND_IN_SET(p.mat,'0,${_SESSION['matieres']}')
+                              ORDER BY FIND_IN_SET(p.mat,'0,${_SESSION['matieres']}'), p.ordre");
+  while ( $r = $resultat->fetch_assoc() )
+    $menu .= "\n      <a href=\"pages?page=${r['cle']}\">${r['nom']}</a>";
+  $resultat->free();
+
+  // Documents
+  $menu .= <<<FIN
+
+      <a href="pages">Gestion des pages</a>
+    </div>
+    <div>
+      <h3>Documents</h3>
+      <a href="docs">Docs toutes matières</a>
+FIN;
+  $resultat = $mysqli->query('SELECT id, nom FROM reps WHERE matiere = 0 AND menu = 1');
+  if ( $resultat->num_rows )  {
+    while ( $r = $resultat->fetch_assoc() )
+      $menu .= "\n        <a class=\"menurep\" href=\"docs?rep=${r['id']}\">${r['nom']}</a>";        
+    $resultat->free();
+  }
+  foreach ( $matieres as $mat )  {
+      $menu .= "\n      <a href=\"docs?${mat['cle']}\">Docs ${mat['nom']}</a>";
+    $resultat = $mysqli->query("SELECT id, nom FROM reps WHERE matiere = ${mat['id']} AND menu = 1");
+    if ( $resultat->num_rows )  {
+      while ( $r = $resultat->fetch_assoc() )
+        $menu .= "\n        <a class=\"menurep\" href=\"docs?rep=${r['id']}\">${r['nom']}</a>";        
+      $resultat->free();
+    }
+  }
+
+  // Matières
+  foreach ( $matieres as $mat )
+    $menu .= <<<FIN
+
+    </div>
+    <div>
+      <h3>Matière <em>${mat['nom']}</em></h3>
+      <a href="colles?${mat['cle']}">Programme de colles</a>
+      <a href="cdt?${mat['cle']}">Cahier de texte</a>
+      <a href="notes?${mat['cle']}">Notes</a>
+FIN;
+
+// Affichage du menu
+echo str_replace("a href=\"$p\"","a id=\"actuel\" href=\"$p\"",$menu);
+?>
+
+    </div>
+    <div>
+      <h3>Gestion du site</h3>
+      <a href="site">Le titre du site</a>
+      <a href="utilisateurs">Les utilisateurs</a>
+      <!--<a href="trinomes">Les groupes de colles</a>-->
+      <a href="matieres">Les matières</a>
+      <a href="planning">Le planning annuel</a>
+    </div>
+    <div>
+      <a href="http://<?php echo $site; ?>">Retour au site public</a>
+      <a href=".?deconnexion">Se déconnecter</a>
+    </div>
+  </div>
+</div>
+
+<div id="contenu">
+<?php
+// Conditionnement du message éventuel
+if ( isset($message) )
+  echo "\n  <div class=\"warning\">$message</div>\n";
+?>
diff -urN cahier-de-prepa3.2.0/admin/index.php cahier-de-prepa4.0.0/admin/index.php
--- cahier-de-prepa3.2.0/admin/index.php	1970-01-01 01:00:00.000000000 +0100
+++ cahier-de-prepa4.0.0/admin/index.php	2014-08-25 18:26:33.585788872 +0200
@@ -0,0 +1,215 @@
+<?php
+// Sécurité et récupération des données de configuration
+define('OK',1);
+include('debut.php'); // fournit $mysqli et $matieres
+
+if ( !isset($message) )  {
+  // Recherche des demandes de création compte en attente
+  $resultat = $mysqli->query('SELECT id FROM utilisateurs WHERE login RLIKE \'^tmp[0-9]{5}\'');
+  if ( $n = $resultat->num_rows )  {
+    $message = 'Il y a actuellement '. ( ( $n > 1 ) ? $n.' utilisateurs' : '1 utilisateur' ) . ' en attente de validation de sa demande de compte sur ce Cahier de Prépa. Vous devez aller sur la page &laquo;&nbsp;<a href="utilisateurs">Utilisateurs</a>&nbsp;&raquo; pour réaliser cela.';
+    $resultat->free();
+  }
+  else  {
+    // Vérification du planning : si colles partout, ce n'est pas normal...
+    $resultat = $mysqli->query('SELECT COUNT(id) AS n FROM semaines WHERE colle = 0');
+    $r = $resultat->fetch_assoc();
+    if ( !$r['n'] )
+      $message = 'Le planning annuel n\'a pas été modifié. Vous avez la possibilité de spécifier quelles semaines sont des semaines de colles ou non, quelles semaines sont les semaines de vacances. Vous pouvez faire cela sur la page <a href="planning">Planning</a>.';
+    $resultat->free();
+  }
+}
+
+//////////////
+//// HTML ////
+//////////////
+// Haut de page, menu et message
+$p = ".";
+// Titre : on récupère celui de la première page
+$resultat = $mysqli->query('SELECT titre FROM pages WHERE id = 1');
+$r = $resultat->fetch_assoc();
+$resultat->free();
+$t = "${r['titre']} - Administration";
+include('haut.php');
+
+// Taille maximale de fichier
+$taille = min(ini_get('upload_max_filesize'),ini_get('post_max_size'));
+if ( stristr($taille,'m') )
+  $taille = substr($taille,0,-1)*1048576;
+elseif ( stristr($taille,'k') )
+  $taille = substr($taille,0,-1)*1024;
+$taille = ( $taille < 1048576 ) ? intval($taille/1024).'&nbsp;ko.' : intval($taille/1048576).'&nbsp;Mo';
+?>
+
+  <div>
+    <p>Bienvenue sur l'interface d'administration de votre Cahier de Prépa.</p>
+    <p>Cette page propose des possibilités de modifications rapide, mais vous pouvez gérer beaucoup plus de choses grâce aux liens du menu&nbsp;!</p>
+  </div>
+
+  <div class="item aide">
+    <h3>Aide et explications</h3>
+    <p>Vous pouvez ci-dessous effectuer les tâches quotidiennes les plus fréquentes&nbsp;: ajouter un programme de colle dans les matières qui vous sont associées, déposer un document dans le répertoire général ou dans celui d'une de vos matières, ou enregistrer une nouvelle information. Tous ces formulaires sont des raccourcis vers les différentes section de cette interface d'administration.</p>
+    <p>Les matières qui vous sont associées sont modifiables dans vos <a href="prefs">Préférences</a>.</p>
+    <h4>Programmes de colles</h4>
+    <p>Vous pouvez choisir la <em>semaine</em> du programme de colles parmi les semaines où le programme n'est pas déjà renseigné, et où il est prévu des colles. Vous pouvez modifier/supprimer les programmes de colles existants sur les pages <a href='colles'>Programme de colles</a> de chaque matière. Vous pouvez modifier les semaines avec ou sans colle sur la page du <a href="planning">Planning annuel</a>.</p>
+    <p>Le texte doit être formaté en HTML&nbsp;: par exemple, chaque bloc de texte doit être encadré par &lt;p&gt; et &lt;/p&gt;. Il peut donc contenir des liens vers les autres pages du site, vers des documents du site, vers le web... N'hésitez pas à consulter l'aide qui apparaîtra en-dessous de la case de texte après avoir cliqué dessus, ainsi qu'à utiliser les boutons fournis.</p>
+    <p>La case à cocher <em>Ne pas diffuser sur la partie publique</em> permet de cacher temporairement ce programme de colle, par exemple pour le diffuser ultérieurement.</p>
+    <h4>Document</h4>
+    <p>Le <em>nom à afficher</em> sera le nom réellement affiché sur le site web (sans extension&nbsp;: elle est détectée automatiquement lors de l'envoi). Si vous laissez cette case vide, le nom du fichier envoyé sera récupéré.</p>
+    <p>Seul le répertoire général, les répertoires correspondant aux matières qui vous sont associées et tous leurs sous-répertoires peuvent accueillir vos documents.</p>
+    <p>Vous pouvez choisir l'<em>accès</em> du document parmi cinq possibilités&nbsp;:</p>
+    <ul>
+      <li><em>Visible de tous</em>&nbsp;: document accessible de tout visiteur, sans identification.</li>
+      <li><em>Visible pour les élèves, colleurs, professeurs</em>&nbsp;: document accessible uniquement par tout utilisateur après identification. Il faut donc avoir un compte validé (voir l'aide de la page <a href="utilisateurs">Utilisateurs</a>) pour y accéder.</li>
+      <li><em>Visible pour les colleurs, professeurs</em>&nbsp;: document accessible uniquement par les utilisateurs de type colleur ou professeur. Attention, si l'accès au répertoire est différent, le titre du document peut être visible par les élèves ou un visiteur non identifié.</li>
+      <li><em>Visible pour les professeurs</em>&nbsp;: document accessible uniquement par les utilisateurs de type professeur (vous et vos collègues).</li>
+      <li><em>Non visible</em>&nbsp;: document n'est pas encore visible en ligne. Cela peut être utile si vous souhaitez le mettre à disposition ultérieurement. C'est complètement sûr&nbsp;: vous pouvez mettre comme cela le prochain sujet de devoir ou même le corrigé.</li>
+    </ul>
+    <p>Dans la page <a href="docs">Documents</a>, les répertoires et documents visibles après identification ont un cadenas dans leur icône. Les répertoires et documents non visibles sont marqués comme tels.</p>
+    <p>La taille du fichier envoyé est limitée à <?php echo $taille; ?>. Tout document est modifiable et déplaçable après envoi, sans que cela ne modifie le lien vers le document.</p>
+    <h4>Information</h4>
+    <p>Vous pouvez choisir la <em>page</em> sur laquelle se trouvera l'information. Pour créer d'autres pages, vous pouvez aller sur la page <a href="pages">Gestion des pages</a>.</p>
+    <p>Le <em>titre</em> sera affiché, un peu plus gros, au-dessus de l'information. Il peut rester vide (il n'y aura alors pas de titre affiché).</p>
+    <p>Le texte doit être formaté en HTML&nbsp;: par exemple, chaque bloc de texte doit être encadré par &lt;p&gt; et &lt;/p&gt;. Il peut donc contenir des liens vers les autres pages du site, vers des documents du site, vers le web... N'hésitez pas à consulter l'aide qui apparaîtra en-dessous de la case de texte après avoir cliqué dessus, ainsi qu'à utiliser les boutons fournis.</p>
+    <p>La case à cocher <em>Ne pas diffuser sur la partie publique</em> permet de cacher temporairement cette information, par exemple pour la diffuser ultérieurement.</p>
+  </div>
+<?php
+
+///////////////////////////////////
+// Nouveaux programmes de colles //
+///////////////////////////////////
+
+// Pour chaque matière
+foreach ( $matieres as $m )  {
+  // Récupération des semaines
+  $resultat = $mysqli->query("SELECT s.id AS sid, DATE_FORMAT(s.debut,'%w%Y%m%e') AS d, s.colle, s.vacances, c.id AS cid, c.cache
+                              FROM semaines AS s
+                              LEFT JOIN (SELECT id, semaine, cache FROM colles WHERE matiere = ${m['id']}) AS c ON c.semaine=s.id");
+  $select_semaines = '';
+  if ( $resultat->num_rows )  {
+    while ( $r = $resultat->fetch_assoc() )  {
+      switch ( $r['vacances'] )  {
+        case 0:
+          if ( $r['colle'] )  {
+            if ( is_null($r['cid']) )
+              $select_semaines .= "\n        <option value=\"${r['sid']}\">".format_date($r['d']).'</option>';
+            elseif ( $r['cache'] )
+              $select_semaines .= "\n        <option value=\"${r['sid']}\" disabled>".format_date($r['d']).' (non diffusé)</option>';
+            else
+              $select_semaines .= "\n        <option value=\"${r['sid']}\" disabled>".format_date($r['d']).' (déjà rempli)</option>';
+          }
+          else
+            $select_semaines .= "\n        <option value=\"${r['sid']}\" disabled>".format_date($r['d']).' (pas de colle)</option>';
+          break;
+        case 1:
+          $select_semaines .= "\n        <option value=\"${r['sid']}\" disabled>Vacances de Toussaint</option>";
+          break;
+        case 2:
+          $select_semaines .= "\n        <option value=\"${r['sid']}\" disabled>Vacances de Noël</option>";
+          break;
+        case 3:
+          $select_semaines .= "\n        <option value=\"${r['sid']}\" disabled>Vacances d'hiver</option>";
+          break;
+        case 4:
+          $select_semaines .= "\n        <option value=\"${r['sid']}\" disabled>Vacances de Pâques</option>";
+      }
+    }
+    $resultat->free();
+?>
+
+  <div class="item admin">
+  <form action="colles?<?php echo $m['cle']; ?>" method="post">
+    <input class="bouton" type="submit" name="modifie" value="Valider" title="Valider les modifications">
+    <h3>Enregistrer un programme de colles de <?php echo $m['nom']; ?></h3>
+    <p class="ligne"><label for="id<?php echo $m['id']; ?>">Semaine&nbsp;: </label>
+      <select id="id<?php echo $m['id']; ?>" name="id"><?php echo $select_semaines; ?>
+
+      </select>
+    </p>
+    <textarea name="texte" class="mat<?php echo $m['id']; ?>" rows="10" cols="100"></textarea>
+    <p class="ligne"><label for="cache<?php echo $m['id']; ?>">Ne pas diffuser sur la partie publique&nbsp;: </label><input type="checkbox" id="cache<?php echo $m['id']; ?>" name="cache" value="1"></p>
+  </form>
+  </div>
+<?php
+  }
+}
+
+///////////////////////
+// Envoi de document //
+///////////////////////
+// Récupération des répertoires, dans le bon ordre pour un select
+function liste($rid,$n)  {
+  $resultat = $GLOBALS['mysqli']->query("SELECT id, nom FROM reps
+                                         WHERE parent = $rid AND FIND_IN_SET(matiere,'0,${_SESSION['matieres']}')
+                                         ORDER BY FIND_IN_SET(matiere,'0,${_SESSION['matieres']}')");
+  while ( $r = $resultat->fetch_assoc() )  {
+    $GLOBALS['select_reps'] .= "        <option value=\"${r['id']}\">".str_repeat('&rarr;',$n)."${r['nom']}</option>\n";
+    liste($r['id'],$n+1);
+  }
+  $resultat->free();
+}
+$select_reps = '';
+liste(0,0);
+?>
+
+  <div class="item admin">
+  <form action="docs" method="post" enctype="multipart/form-data">
+    <input class="bouton" type="submit" name="envoie" value="Envoyer">
+    <h3>Déposer un document</h3>
+    <p class="ligne"><label for="nom">Nom à afficher&nbsp;: </label><input type="text" id="nom" name="nom" value="" size="50"></p>
+    <p class="ligne"><label for="fichier">Fichier&nbsp;: </label><input type="file" id="fichier" name="fichier"></p>
+    <p class="ligne"><label for="rep">Répertoire&nbsp;: </label>
+      <select id="rep" name="rep">
+<?php echo $select_reps; ?>
+      </select>
+    </p>
+    <p class="ligne"><label for="protection">Accès&nbsp;: </label>
+      <select id="protection" name="protection">
+        <option value="0">Visible de tous</option>
+        <option value="1">Visible pour les élèves, colleurs, profs</option>
+        <option value="2">Visible pour les colleurs et les profs</option>
+        <option value="3">Visible pour les profs uniquement</option>
+        <option value="4">Non visible</option>
+      </select>
+    </p>
+    <input type="hidden" name="id" value="0">
+  </form>
+  </div>
+<?php
+
+//////////////////////////
+// Nouvelle information //
+//////////////////////////
+$resultat = $mysqli->query("SELECT CONCAT_WS('/',m.cle,p.cle) AS cle, CONCAT_WS('/',m.nom,p.nom) AS nom
+                            FROM pages AS p LEFT JOIN matieres AS m ON p.mat = m.id
+                            WHERE FIND_IN_SET(p.mat,'0,${_SESSION['matieres']}')
+                            ORDER BY FIND_IN_SET(p.mat,'0,${_SESSION['matieres']}'), p.ordre");
+$select_pages = '';
+while ( $r = $resultat->fetch_assoc() )
+  $select_pages .= "        <option value=\"${r['cle']}\">${r['nom']}</option>\n";
+$resultat->free();
+?>
+
+  <div class="item admin">
+  <form action="pages" method="post">
+    <input class="bouton" type="submit" name="modifie" value="Valider" title="Valider les modifications sur le titre ou le texte">
+    <h3>Enregistrer une nouvelle information</h3>
+    <p class="ligne"><label for="page">Page&nbsp;: </label>
+      <select id="page" name="page">
+<?php echo $select_pages; ?>
+      </select>
+    </p>
+    <p class="transparent"><input class="ligne" type="text" name="titre" size=50 maxlength=65533 value="Titre"></p>
+    <textarea name="texte" class="mat0" rows="6" cols="100"></textarea>
+    <p class="ligne"><label for="cache">Ne pas diffuser sur la partie publique&nbsp;: </label><input type="checkbox" id="cache" name="cache" value="1"></p>
+    <input type="hidden" name="id" value="0">
+    <input type="hidden" name="action" value="info">
+  </form>
+  </div>
+
+<?php
+$mysqli->close();
+// Bas de page
+include('bas.php');
+exit();
+?>
diff -urN cahier-de-prepa3.2.0/admin/js.php cahier-de-prepa4.0.0/admin/js.php
--- cahier-de-prepa3.2.0/admin/js.php	1970-01-01 01:00:00.000000000 +0100
+++ cahier-de-prepa4.0.0/admin/js.php	2014-08-26 13:59:18.760040566 +0200
@@ -0,0 +1,309 @@
+<?php
+// Sécurité et récupération des données de configuration
+define('OK',1);
+include('debut.php'); // fournit $mysqli et $matieres
+
+header('Content-Type: application/javascript; charset=utf-8');
+
+// Récupération des répertoires, avec le chemin complet
+$resultat = $mysqli->query("SELECT id, nom, matiere,
+                            CONCAT( ( SELECT GROUP_CONCAT(nom SEPARATOR '/') FROM reps AS r WHERE FIND_IN_SET(r.id,reps.parents) ) ,'/') AS parents
+                            FROM reps WHERE nbdoc_v AND FIND_IN_SET(matiere,'0,${_SESSION['matieres']}')");
+if ( $resultat->num_rows )  {
+  $reps = array(0=>'');
+  $docs = array();
+  while ( $r = $resultat->fetch_assoc() )  {
+    if ( !isset($reps[$r['matiere']]) )
+      $reps[$r['matiere']] = '';
+    $reps[$r['matiere']] .= "<option value=\"${r['id']}\">".addslashes($r['parents'].$r['nom']).'</option>';
+    if ( $r['matiere'] )
+      $reps[0] .= "<option value=\"${r['id']}\">".addslashes($r['parents'].$r['nom']).'</option>';
+    // Récupération des documents
+    $docs[$r['id']] = '';
+    $res = $mysqli->query("SELECT id, nom FROM docs WHERE parent = ${r['id']} AND protection < 4");
+    while ( $d = $res->fetch_assoc() )  {
+      $docs[$r['id']] .= "<option value=\"${d['id']}\">".addslashes($d['nom']).'</option>';
+    }
+    $res->free();
+  }
+  $resultat->free();
+  $boutons_reps = '
+    <p class="boutons" id="boutons_reps">Insérer un document&nbsp;:\\
+      <select id="reps"></select>\\
+      <select id="doc"></select>\\
+      <input type="button" id="js_doc" value="Insérer">\\
+    </p>\\
+';
+}
+else
+  $boutons_reps = '';
+
+$mysqli->close();
+?>
+
+$( function() {
+
+  ///////////////////////
+  // Pliage des boîtes //
+  ///////////////////////
+
+  // Pliage des items et aides de l'interface d'administration
+  // .fic => fichiers dans docs.php ; .user => nouvel utilisateur dans utilisateurs.php
+  $('.aide h3,.admin:not(.fic, .nojs) h3').append(' <span>déplier</span>');
+  $('.aide h3 span,.admin:not(.fic, .user, .nojs) h3 span').click( function () {
+    $(this).parent().parent().children(':not(h3,.nonvisible)').toggle();
+    $(this).text($(this).text() == 'déplier' ? 'replier' : 'déplier');
+  });
+  // Premier pliage
+  $('.aide,.admin:not(.fic, .nojs) form').children(':not(h3)').hide();
+  // Recopie des boutons de modification d'ordre
+  $( "[name='monte'],[name='descend']" ).each(function(){
+    // Un bouton de validation doit se trouver en premier dans le formulaire pour
+    // valider par appui sur la touche entrée. 
+    // On place derrière le bouton de descente, puis le bouton de montée
+    $(this).prev("[name='modifie']").clone().addClass('nonvisible').hide().prependTo($(this).parents('form'));
+    $(this).clone().addClass('bouton').insertAfter($(this).parents('form').find('.nonvisible'));
+  });
+
+  ///////////////////////////
+  // Contrôle des textarea //
+  ///////////////////////////
+
+  // Texte si vide
+  $('textarea:not(.nojs):empty').addClass('texte_aide').html("\
+&lt;p&gt;Ce texte doit être formaté en HTML. C'est un langage simple de balisage, où chaque paragraphe doit être entouré par deux balises &laquo;&nbsp;p&nbsp;&raquo;, comme ici. L'ensemble de ce texte disparaîtra automatiquement dès que vous y écrirez quelque chose.&lt;/p&gt;\n\n\
+---------------------------------------------------------------------------------------------------\n\n\
+Pour ajouter un lien au sein de votre texte, la syntaxe est\n\
+            &lt;a href=\"l'adresse du lien\"&gt;le texte qui apparaît souligné&lt;/a&gt;\n\n\
+Pour ajouter un lien seul sur une ligne, mettez-le dans un paragraphe&nbsp;: &lt;p&gt;&lt;a href=\"l'adresse du lien\"&gt;le texte qui apparaît souligné&lt;/a&gt;&lt;/p&gt; (c'est ce que fait par défaut le bouton &laquo;&nbsp;Insérer&nbsp;&raquo;).\n\n\
+Différents formatages classiques sont disponibles dans les boutons qui s'affichent ci-dessous (cliquez ici si vous ne les voyez pas). N'hésitez pas à essayer, à regarder l'aide disponible et à utiliser le bouton &laquo;&nbsp;Prévisualiser le texte&nbsp;&raquo;.\n");
+
+  $('.texte_aide').on('keydown paste',function() { $(this).val('').off('keydown paste').removeClass('texte_aide'); });
+
+  // Boutons supplémentaires
+  $('\
+  <div id="js_controls">\
+    <div id="aide_js">\
+      <h4>Aide et explications <span></span> <input type="button" id="js_preview" value="Prévisualiser le texte"> <input type="button" id="js_suppr" value="Supprimer le texte"></h4>\
+      <p>Le texte doit être écrit en HTML&nbsp;: toute mise en forme est indiquée par deux balises, de début comme &lt;h3&gt; pour un gros titre, de fin comme &lt;/h3&gt;. Les boutons ci-dessus aident à «&nbsp;encadrer&nbsp;» un texte sélectionné des balises correctes.</p>\
+      <p>Comme en HTML, les retours à la ligne et les espaces multiples ne comptent pas. Un retour à la ligne dans un paragraphe (&lt;p&gt;Bla bla&lt;/p&gt;) ne crée pas un retour à la ligne visible au final. Pour ce faire, on peut utiliser la balise unique &lt;br&gt;, mais il vaut mieux mettre chaque paragraphe entre balises &lt;p&gt; et &lt;/p&gt;. Les espaces en début de ligne, de même, n\'apparaissent pas sur le site public et ne servent qu\'à la lisibilité ici.</p>\
+      <p class="tex2jax_ignore">Il est possible d\'insérer du code en LaTeX, sur une ligne séparée (balises \\[...\\] ou balises $$...$$) ou au sein d\'une phrase (balises $...$). Il faut ensuite taper du code en LaTeX à l\'intérieur. La prévisualisation est impossible&nbsp;: il faut valider le texte et aller voir le site public pour vérifier ce que l\'on a tapé.</p>\
+    </div>\<?php echo $boutons_reps; ?>
+    <p class="boutons">\
+      <input type="button" id="js_ligne" value="Paragraphe" title="Transformer la sélection en paragraphe">\
+      <input type="button" id="js_note" value="Par. important" title="Transformer la sélection en paragraphe important, qui s\'affichera en rouge. Seul le texte entre les balises est affiché, soit &lt;div class=&rdquo;note&rdquo;&gt;TEXTE&lt;/div&gt;">\
+      <input type="button" id="js_annonce" value="Par. très important" title="Transformer la sélection en paragraphe très important, qui s\'affichera en rouge et encadré. Seul le texte entre les balises est affiché, soit &lt;div class=&rdquo;annonce&rdquo;&gt;TEXTE&lt;/div&gt;">\
+      <input type="button" id="js_lien" value="Lien" title="Créer un lien : le titre est entre les balises, l\'adresse entre les guillemets après « href », soit &lt;a href=&rdquo;ADRESSE&rdquo;>TITRE</a>">\
+      <input type="button" id="js_gras" value="Gras" title="Mettre la sélection en gras">\
+      <input type="button" id="js_it" value="Italique" title="Mettre la sélection en italique">\
+      <input type="button" id="js_exp" value="Exposant" title="Mettre la sélection en exposant">\
+      <input type="button" id="js_ind" value="Indice" title="Mettre la sélection en indice">\
+    </p>\
+    <p class="boutons">\
+      <input type="button" id="js_h3" value="Titre de type chapitre (h3)" title="Insérer un titre « grand »">\
+      <input type="button" id="js_h4" value="Titre de type «&nbsp;I.&nbsp;» (h4)" title="Insérer un titre « premier niveau »">\
+      <input type="button" id="js_h5" value="Titre de type «&nbsp;1.&nbsp;» (h5)" title="Insérer un titre « deuxième niveau »">\
+      <input type="button" id="js_h6" value="Titre de type «&nbsp;a.&nbsp;» (h6)" title="Insérer un titre « troisième niveau »">\
+    </p>\
+    <p class="boutons">\
+      <input type="button" id="js_colle" value="Titre de prog. de colles (h4)" title="Insérer le titre d\'une sous-partie d\'un programme de colles.">\
+      <input type="button" id="js_ul" value="Liste à puces" title="Insérer une liste à puces (chaque &lt;li&gt; est une puce)">\
+      <input type="button" id="js_latex" value="Phrase en LaTeX" title="Insérer une ligne d\'équation en LaTeX - voir l\'aide pour les détails">\
+    </p>\
+    <p class="boutons">\
+      <input type="button" id="js_s1" value="(HTML) Insérer le caractère">&nbsp;<select id="select1"></select>\
+      <input type="button" id="js_s2" value="(HTML) Insérer la lettre">&nbsp;<select id="select2"></select></p>\
+  </div>').appendTo('body').toggle();
+
+  // Symboles
+  var symboles = [ '&forall;', '&exist;', '&part;', '&nabla;', '&prod;', '&sum;', '&plusmn;', '&radic;', '&infin;', '&int;', '&prop;', '&sim;', '&cong;', '&asymp;', '&ne;', '&equiv;', '&le;', '&ge;', '&sub;', '&sup;', '&nsub;', '&sube;', '&supe;', '&isin;', '&notin;', '&ni;', '&oplus;', '&otimes;', '&sdot;', '&and;', '&or;', '&cap;', '&cup;', '&real;', '&image;', '&empty;', '&deg;', '&prime;', '&micro;', '&larr;', '&uarr;', '&rarr;', '&darr;', '&harr;', '&lArr;', '&uArr;', '&rArr;', '&dArr;', '&hArr;' ];
+  for (var i=0, n=symboles.length, s=''; i<n; i++) {
+     s = symboles[i];
+     $('#select1').append('<option value="'+s+'">'+s+'</option>');
+  }
+  symboles = [ '&alpha;', '&beta;', '&gamma;', '&Delta;', '&delta;', '&epsilon;', '&eta;', '&Theta;', '&theta;', '&Lambda;', '&lambda;', '&mu;', '&nu;', '&xi;', '&Pi;', '&pi;', '&rho;', '&Sigma;', '&sigma;', '&tau;', '&upsilon;', '&Phi;', '&phi;', '&Psi;', '&psi;', '&Omega;', '&omega;' ];
+  for (i=0, n=symboles.length; i<n; i++) {
+     s = symboles[i];
+     $('#select2').append('<option value="'+s+'">'+s+'</option>');
+  }
+
+  // Aide
+  $('#aide_js h4 span').css('cursor','pointer').click( function () {
+    $(this).parent().parent().find('p').toggle();
+    $(this).text($(this).text() == '[déplier]' ? '[replier]' : '[déplier]');
+  });
+  $('#aide_js').find('p').hide();
+
+  // Le focus sur un textarea fait apparaître les boutons au bon endroit
+  $('textarea:not(.nojs)').focus( function() {
+    // Modification des répertoires en fonction de la matière correspondante
+<?php
+  if ( !empty($docs) )  {
+    $matieres = explode(',',"0,${_SESSION['matieres']}");
+    foreach ( $matieres as $m )  {
+      if ( isset($reps[$m]) )
+        echo <<<FIN
+    if ( $(this).hasClass('mat$m') )  {
+      $('#reps').html('${reps[$m]}').change();
+      $('#boutons_reps').show();
+    }
+
+FIN;
+      else
+        echo <<<FIN
+    if ( $(this).hasClass('mat$m') )
+      $('#boutons_reps').hide();
+
+FIN;
+    }
+  }
+?>
+    $('#active').prop('id','');
+    $(this).prop('id','active');
+    $('#js_controls').detach().appendTo($(this).parent()).show();
+    $('#aide_js').find('p').hide();
+    $('#aide_js h4 span').text('[déplier]');
+  });
+
+<?php
+  if ( !empty($docs) )  {
+    echo '
+  // Modification des documents en fonction du répertoire choisi
+  $(\'#reps\').change( function() {
+    switch ( $(this).val() )  {';
+    foreach ( $docs as $rep => $select )
+      echo "\n      case '$rep': $('#doc').html('$select'); break;";
+    echo '
+    }
+  });';
+  }
+?>
+
+  // Suppression
+  $('#js_suppr').click(function() { $('#active').val('').off('keydown paste').removeClass('texte_aide'); });
+  
+  // Prévisualisation
+  $('<div id="preview_fond"></div>').appendTo('body');
+  $('<div class="item admin" id="preview"><h2>Prévisualisation</h2><div></div><p>Cliquer n\'importe où pour revenir à l\'édition</p></div>').appendTo('body');
+  $('#preview_fond,#preview').hide().click(function() { $('#preview_fond,#preview').hide(); });
+  $('#js_preview').click(function() {
+    $('#preview>div').html($('#active').val());
+    $('#preview_fond,#preview').show();
+    MathJax.Hub.Queue(["Typeset",MathJax.Hub,"preview"]);
+    
+  });
+  
+  // Fonction d'insertion dans la textarea
+  function insert(debut,fin) {
+    if ( $('#active').hasClass('texte_aide') )
+      $('#active').val('').off('keydown paste').removeClass('texte_aide');
+    var ta = $('#active')[0];
+    if (document.selection) {
+      ta.focus();
+      sel = document.selection.createRange();
+      sel.text = debut+sel.text+fin;
+      ta.focus();
+    }
+    else if (ta.selectionStart || ta.selectionStart == '0') {
+      var d = ta.selectionStart;
+      var f = ta.selectionEnd;
+      var scrollTop = ta.scrollTop;
+      ta.value = ta.value.substring(0,d)+debut+ta.value.substring(d,f)+fin+ta.value.substring(f,ta.value.length);
+      ta.focus();
+      ta.selectionStart = d + debut.length;
+      ta.selectionEnd = f + debut.length;
+      ta.scrollTop = scrollTop;
+    } else {
+      ta.value += debut+fin;
+      ta.focus();
+    }
+  }
+  
+  // Actions d'insertion de balises
+  $('#js_doc').click(function() { insert('\u000A<p><a href="download?id='+$('#doc').val()+'">'+$('#doc option:selected').text()+'</a></p>',''); });
+  $('#js_ligne').click(function() { insert('<p>','</p>'); });
+  $('#js_lien').click(function() { insert('<a href="">','</a>'); });
+  $('#js_note').click(function() { insert('\u000A<div class="note">','</div>'); });
+  $('#js_annonce').click(function() { insert('\u000A<div class="annonce">','</div>'); });
+  $('#js_gras').click(function() { insert('<strong>','</strong>'); });
+  $('#js_it').click(function() { insert('<em>','</em>'); });
+  $('#js_exp').click(function() { insert('<sup>','</sup>'); });
+  $('#js_ind').click(function() { insert('<sub>','</sub>'); });
+  $('#js_h3').click(function() { insert('\u000A<h3>Chapitre ','</h3>'); });
+  $('#js_h4').click(function() { insert('\u000A<h4>I. ','</h4>'); });
+  $('#js_h5').click(function() { insert('\u000A<h5>1. ','</h5>'); });
+  $('#js_h6').click(function() { insert('\u000A<h6>a. ','</h6>'); });
+  $('#js_colle').click(function() { insert('\u000A<h4>','</h4>'); });
+  $('#js_ul').click(function() { insert('\u000A<ul>\u000A  <li>','</li>\u000A  <li></li>\u000A</ul>'); });
+  $('#js_latex').click(function() { insert('\u000A\\[','\\]'); });
+  $('#js_s1').click(function() { insert($('#select1').val(),''); });
+  $('#js_s2').click(function() { insert($('#select2').val(),''); });
+
+  ////////////////////////////////////////////
+  // Gestion du timeout et des reconnexions //
+  ////////////////////////////////////////////
+<?php
+$t = $_SESSION['timeout'];
+if ( $t <= 120 )  {
+  $t2 = 30;
+  $temps = '30 secondes';
+}
+else  {
+  $t2 = ( $t < 600 ) ? 120 : 300;
+  $temps = ($t2/60).' minutes';
+}
+$t1 = $t-$t2;
+?>
+
+  // Messages d'annonce de la future déconnexion
+  var html1 = 'Vous allez être déconnecté dans moins de <?php echo $temps; ?>. Toute modification non validée dans  <?php echo $temps; ?> sera perdue...<br><a href="#" id="connect">Cliquez ici</a> pour rester connecté.';
+  var html2 = 'Vous avez été déconnecté automatiquement. Si vous validez cette page, vos modifications seront perdues. Il suffit avant cela de taper à nouveau votre nom et votre mot de passe&nbsp;;\
+<form id="formlogin">\
+  <p class="ligne"><label for="login">Nom d\'utilisateur&nbsp;: </label><input type="text" name="login" id="logintmp"></p>\
+  <p class="ligne"><label for="motdepasse">Mot de passe&nbsp;: </label><input type="password" name="motdepasse" id="motdepassetmp"></p>\
+  <p><input type="submit" value="Envoyer"></p>\
+</form>';
+
+  // Affichage des messages
+  function afficher_timeout() {
+    $('#message').hide(200);
+    // Suppression de l'éventuelle attente
+    try{ window.clearTimeout(timeout); } catch(e){}
+    timeout = window.setTimeout( function() {
+      $('#message').html(html1).show(200);
+      $("#connect").click( function() { connect(); return false; });
+      timeout = window.setTimeout( function() {
+        $('#message').html(html2);
+        $('#formlogin').submit( function() { connect(); return false; });
+      } ,  <?php echo $t2; ?>000);
+    } ,  <?php echo $t1; ?>000);
+  }
+
+  // Vérification de la connexion et reconnexion
+  function connect() {
+    var login = '';
+    var motdepasse = '';
+    if ( $('#formlogin').length > 0 )  {
+      login = $('#logintmp').val();
+      motdepasse = $('#motdepassetmp').val();
+    }
+    $.post('connect',
+          { login : login , motdepasse : motdepasse },
+          function(data) {
+            if ( data.etat == 1 )  { afficher_timeout(); }
+            else  {
+              $('#message').html(html2);
+              $('#formlogin').submit( function() { connect(); return false; });
+            }
+          },
+          "json");
+  }
+
+  // Initialisation
+  var timeout;
+  $( function() {
+    $('<div id="message" class="warning"></div>').prependTo('body').css('display', 'none');
+    afficher_timeout();
+  });
+
+});
diff -urN cahier-de-prepa3.2.0/admin/login.php cahier-de-prepa4.0.0/admin/login.php
--- cahier-de-prepa3.2.0/admin/login.php	1970-01-01 01:00:00.000000000 +0100
+++ cahier-de-prepa4.0.0/admin/login.php	2014-08-24 23:58:23.099661168 +0200
@@ -0,0 +1,179 @@
+<?php
+// Sécurité : ne doit pas être exécuté s'il n'est pas lancé par un script autorisé
+if ( !defined('OK') )  exit();
+
+// Connexion à la base MySQL et vérification de la connexion
+$mysqli = new mysqli($serveur,$base,$mdp,$base);
+if ( $mysqli->connect_errno )
+  include('installation.php');
+$mysqli->set_charset('utf8');
+
+// Oubli d'identifiant/mot de passe -- copie dans /connect.php
+if ( isset($_REQUEST['oubli']) )  {
+  
+  // Traitement du formulaire
+  if ( isset($_REQUEST['mail']) )  {
+    // Recherche du mail dans la base de données
+    $resultat = $mysqli->query('SELECT id, login, mail FROM utilisateurs WHERE autorisation = 3');
+    while ( $r = $resultat->fetch_assoc() )
+      if ( $r['mail'] == $_REQUEST['mail'] )  {
+        $utilisateur = $r;
+        break;
+      }
+    $resultat->free();
+    if ( isset($utilisateur) )  {
+      // Connexion à la base de données avec les droits d'écriture
+      $mysqli->close();
+      $mysqli = mysql_ecriture();
+      // Réinitialisation du mot de passe
+      $lettres = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789?./,;:!";
+      $mdp = '';
+      for ( $i = 0; $i < 8; $i++ )
+        $mdp .= substr($lettres,mt_rand(0,68),1);
+      requete('utilisateurs','UPDATE utilisateurs SET genre = MOD(genre,5)+5, mdp = \''.sha1($mdp)."' WHERE id = ${utilisateur['id']}");
+      // Envoi du mail
+      mail($utilisateur['mail'],'[Cahier de Prépa] Modification de votre mot de passe',
+"Bonjour
+
+Vous avez demandé à modifier votre mot de passe, correspondant à l'identifiant ${utilisateur['login']}. Ce mot de passe a donc été temporairement réinitialisé à la valeur \"$mdp\". Vous devez vous connecter avec ce mot de passe et le modifier immédiatement ensuite : ce mot de passe est à usage unique et sera automatiquement effacé après votre prochaine connexion.
+
+Cordialement,
+-- 
+Cahier de Prépa
+","From: ne-pas-repondre@cahier-de-prepa.fr\r\nContent-type: text/plain; charset=UTF-8");
+      $message = 'Le mot de passe a été réinitialisé et un mail a été envoyé.';
+    }
+    else
+      $message = 'Le mail fourni n\'est pas dans la base de données.';
+  }
+
+  // Formulaire de récupération du mail si mdp non déjà réinitialisé
+  if ( !isset($utilisateur) )  {
+    $mysqli->close();
+?>
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
+<html>
+<head>
+  <title>Cahier de Prépa - Oubli du mot de passe</title>
+  <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+  <link rel="stylesheet" href="../css/style.css" type="text/css" media="screen">
+  <link rel="stylesheet" href="../css/couleurs.css" type="text/css" media="screen">
+  <link rel="stylesheet" href="../css/mobile.css" type="text/css" media="handheld, only screen and (max-device-width:480px)">
+</head>
+<body>
+
+<h1>Cahier de Prépa - Oubli du mot de passe</h1>
+<?php 
+    if ( isset($message) )
+      echo "\n  <div class=\"warning\">$message</div>\n";
+?>
+
+<form class="warning" action="." method="post">
+  <p>Un mail contenant l'identifiant et un nouveau mot de passe temporaire sera envoyé par mail à l'adresse ci-dessous si elle est bien enregistrée&nbsp;:</p>
+  <p class="ligne"><label for="mail">Adresse mail&nbsp;: </label><input type="text" name="mail" id="mail"></p>
+  <p><input type="submit" name="Ok" value="Envoyer"></p>
+  <input type="hidden" name="oubli" value="1">
+</form>
+
+<h3 class="warning"><a href="http://<?php echo $site; ?>">Revenir au site public</a></h3>
+
+</body>
+</html>
+<?php
+    exit();
+  }
+}
+
+// Si une demande de connexion est faite
+elseif ( isset($_REQUEST['login']) && isset($_REQUEST['motdepasse']) )  {
+
+  // Récupération des logins/mdp (professeurs uniquement) dans la base MySQL et comparaison
+  $resultat = $mysqli->query('SELECT id, login, mdp, matieres, timeout, genre FROM utilisateurs WHERE autorisation = 3');
+  if ( $resultat->num_rows )  {
+    while ( $r = $resultat->fetch_assoc() )
+      if ( ( $r['login'] == $_REQUEST['login'] ) && ( $r['mdp'] == sha1($_REQUEST['motdepasse']) ) )  {
+        // Interdiction de garder son identifiant de session
+        session_regenerate_id(true);
+        $_SESSION = array();
+        // Interdiction de pouvoir se connecter aux autres site sur le même serveur
+        $_SESSION[md5("$site-admin")] = true;
+        // Pour vérification/utilisation aux connexions ultérieures
+        $_SESSION['client'] = $_SERVER['HTTP_USER_AGENT'];
+        $_SESSION['ip'] = $_SERVER['REMOTE_ADDR'];
+        $_SESSION['login'] = $_POST['login'];
+        $_SESSION['id'] = $r['id'];
+        $_SESSION['matieres'] = $r['matieres'];
+        $_SESSION['timeout'] = $r['timeout'];
+        $_SESSION['time'] = time()+$_SESSION['timeout'];
+        $resultat->free();
+
+        // Si genre >= 5, cela signifie qu'on a demandé une régénération de mot de passe (connect.php partie publique)
+        if ( $r['genre'] >= 5 )  {
+          $mysqli->close();
+          $mysqli = mysql_ecriture();
+          requete('utilisateurs',"UPDATE utilisateurs SET mdp='pas de mot de passe', genre=MOD(genre,5) WHERE id = ${r['id']}");
+          $message = 'IMPORTANT : votre mot de passe a été réinitialisé.<br>Vous devez absolument le redéfinir maintenant, dans vos <a href="prefs">Préférences</a>.';
+        }
+        // Retour au script appelant
+        $mysqli->close();
+        return;
+      }
+    $resultat->free();
+  }
+  // Si pas d'utilisateur enregistré : retour au script d'installation
+  else  {
+    $mysqli->close();
+    include('installation.php');
+  }
+  
+  // Retour au script appelé par AJAX si c'est le cas (pas d'HTML)
+  if ( defined('AJAX') )  {
+    $mysqli->close();
+    exit(json_encode(array('etat'=>0)));
+  }
+
+  // Utilisateur inconnu ou mot de passe faux
+  $message = 'Cet identifiant/mot de passe n\'est pas correct&nbsp;!';
+}
+// Message par défaut
+else
+  $message = "Vous devez vous identifier pour accéder à cette partie du site. Elle n'est accessible qu'aux professeurs.";
+
+////////////
+/// HTML ///
+////////////
+
+// Titre : on récupère celui de la première page
+$resultat = $mysqli->query('SELECT titre FROM pages WHERE id = 1');
+$r = $resultat->fetch_assoc();
+$resultat->free();
+$mysqli->close();
+$t = "${r['titre']} - Interface d'administration";
+?>
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
+<html>
+<head>
+  <title><?php echo $t; ?></title>
+  <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+  <link rel="stylesheet" href="../css/style.css" type="text/css" media="screen">
+  <link rel="stylesheet" href="../css/couleurs.css" type="text/css" media="screen">
+  <link rel="stylesheet" href="../css/mobile.css" type="text/css" media="handheld, only screen and (max-device-width:480px)">
+</head>
+<body>
+
+<h1><?php echo $t; ?></h1>
+
+<div class="warning"><?php echo $message; ?></div>
+
+<form class="warning" action="" method="post">
+  <p class="ligne"><label for="login">Identifiant&nbsp;: </label><input type="text" name="login" id="login"></p>
+  <p class="ligne"><label for="motdepasse">Mot de passe&nbsp;: </label><input type="password" name="motdepasse" id="motdepasse"></p>
+  <p><input type="submit" name="Ok" value="Envoyer"></p>
+  <p id="oubli"><a href="?oubli">Identifiant ou mot de passe oublié&nbsp;?</a></p>
+</form>
+
+<h3 class="warning"><a href="http://<?php echo $site; ?>">Revenir au site public</a></h3>
+
+</body>
+</html>
+<?php exit(); ?>
diff -urN cahier-de-prepa3.2.0/admin/mail.php cahier-de-prepa4.0.0/admin/mail.php
--- cahier-de-prepa3.2.0/admin/mail.php	1970-01-01 01:00:00.000000000 +0100
+++ cahier-de-prepa4.0.0/admin/mail.php	2014-08-26 13:59:36.444041132 +0200
@@ -0,0 +1,185 @@
+<?php
+// Sécurité et récupération des données de configuration
+define('OK',1);
+include('debut.php'); // fournit $mysqli et $matieres
+
+// Récupération des préférences d'envoi (mailexp, mailsign, mailcopy) de l'utilisateur
+$resultat = $mysqli->query("SELECT nom, prenom, genre, mail, mailexp, IF(mailcopy,' checked','') AS mailcopybox, mailcopy
+                            FROM utilisateurs WHERE id = ${_SESSION['id']}");
+$u = $resultat->fetch_assoc();
+$resultat->free();
+
+// Impossible d'aller plus loin si le mail n'est pas renseigné
+if ( !strlen($u['mail']) )  {
+$p = "mail";
+$t = 'Envoi de mail';
+include('haut.php');
+?>
+  <div class="warning">Il est impossible d'envoyer des mails sans avoir renseigné une adresse mail qui sera écrite en tant qu'expéditeur. Il faut aller voir vos <a href="prefs">Préférences</a> pour mettre à jour votre identité.</div>
+
+<?php
+include('bas.php');
+exit();
+}
+
+// Champ expéditeur
+switch ( $u['genre'] )  {
+  case 1 : $g = 'M. '; break;
+  case 2 : $g = 'Mme '; break;
+  case 3 : $g = 'Melle '; break;
+}
+switch ( $u['mailexp'] )  {
+  case 1 : $exp = "$g ${u['nom']}"; break;
+  case 2 : $exp = "$g ${u['prenom']} ${u['nom']}"; break;
+  case 3 : $exp = "${u['prenom']} ${u['nom']}"; break;
+  case 4 : $exp = "${u['nom']}"; break;
+}
+
+///////////////////
+// Envoi du mail //
+///////////////////
+if ( isset($_REQUEST['valide']) && isset($_REQUEST['dest']) )  {
+  // Récupération des destinataires
+  $resultat = $mysqli->query("SELECT id, mail, CASE autorisation WHEN autorisation = 1 THEN CONCAT( prenom, ' ', nom )
+                                               ELSE CONCAT( CASE MOD(genre,5) WHEN 1 THEN 'M. ' WHEN 2 THEN 'Mme ' WHEN 3 THEN 'Melle ' END, nom ) END AS nom_complet
+                              FROM utilisateurs WHERE login NOT RLIKE '^tmp[0-9]{5}' AND LENGTH(mail) > 0 AND id != ${_SESSION['id']} ORDER BY autorisation DESC, nom");
+  while ( $r = $resultat->fetch_assoc() )
+    $utilisateurs[$r['id']] = $r;
+  $dests = '';
+  foreach ( $_REQUEST['dest'] as $i )
+    if ( isset($utilisateurs[$i]) )  {
+      $dests .= $utilisateurs[$i]['nom_complet'].' <'.$utilisateurs[$i]['mail'].'>, ';
+      unset($utilisateurs[$i]);
+    }
+  $dests = substr($dests,0,-2);
+  $bcc = ( $u['mailcopy'] ) ? "Bcc: ${u['mail']}, " : 'Bcc: ';
+  if ( isset($_REQUEST['dest_bcc']) )
+    foreach ( $_REQUEST['dest_bcc'] as $i )
+      if ( isset($utilisateurs[$i]) )  {
+        $bcc .= $utilisateurs[$i]['nom_complet'].' <'.$utilisateurs[$i]['mail'].'>, ';
+        unset($utilisateurs[$i]);
+      }
+  $bcc = ( strlen($bcc) == 4 ) ? '' : substr($bcc,0,-2);
+  mail($dests,$_REQUEST['sujet'],$_REQUEST['texte'],"From: $exp <${u['mail']}>\r\nContent-type: text/plain; charset=UTF-8\r\n$bcc");
+  $message = 'Le mail a bien été envoyé.';
+}
+
+
+//////////
+// HTML //
+//////////
+// Haut de page, menu et message
+$p = "mail";
+$t = 'Envoi de mail';
+include('haut.php');
+
+// Aide générale et formulaires
+?>
+
+  <div class="item aide">
+    <h3>Aide et explications</h3>
+    <p>Vous pouvez ci-dessous envoyer un mail aux utilisateurs ayant renseigné leur adresse mail.</p>
+    <p>Les cases à cocher <em>Copie</em> permettre d'ajouter les utilisateurs cochés dans la liste des destinataires. Il est obligatoire qu'au moins un utilisateur soit en copie.</p>
+    <p>Les cases à cocher <em>Copie cachée</em> permettre d'ajouter les utilisateurs cochés dans la liste des destinataires en copie cachée&nbsp;: les autres destinataires ne verront pas que ce mail a aussi été envoyé à ceux en copie cachée.</p>
+    <p>Les case à cocher sont grisées si l'utilisateur n'a pas renseigné son adresse mail.</p>
+    <p>Le <em>sujet</em> est le sujet du mail.</p>
+    <p>Le <em>contenu</em> est le corps du mail, en texte brut. Le mail envoyé ne sera pas formaté en HTML&nbsp;: il n'est pas possible de réaliser un formattage particulier (changer une taille d'écriture, une police, mettre de la couleur...). Par convrntion classique,</p>
+    <ul>
+      <li>écrire un mot entre astérisques (*) signifie le mettre en gras et appuyer sur ce mot.</li>
+      <li>écrire un mot entre slashes (/) signifie le mettre en italique pour indiquer qu'il faut y faire attention.</li>
+      <li>écrire en majuscules signifie que l'on en train d'hurler. :-)</li>
+    </ul>
+    <p>Vous pouvez aussi modifier légèrement ce qui apparaîtra comme expéditeur de tous vos mails (actuellement <code><?php echo "$exp &lt;${u['mail']}&gt;"; ?></code>). C'est une des préférences techniques de vos <a href="prefs">Préférences</a>.</p>
+    <p>Vous pouvez être de façon automatique en copie de tous vos mails. C'est une des préférences techniques de vos <a href="prefs">Préférences</a>.</p>
+  </div>
+
+<?php
+// Récupération des utilisateurs
+$resultat = $mysqli->query("SELECT id, login, IF(LENGTH(mail),'',' disabled') AS mail, autorisation,
+                            CONCAT( CASE MOD(genre,5) WHEN 1 THEN 'M. ' WHEN 2 THEN 'Mme ' WHEN 3 THEN 'Melle ' END, prenom, ' ', nom ) AS nom_complet
+                            FROM utilisateurs WHERE login NOT RLIKE '^tmp[0-9]{5}' AND id != ${_SESSION['id']} ORDER BY autorisation DESC, nom");
+?>
+  <div class="item admin nojs">
+  <form action="" method="post">
+    <input class="bouton" type="submit" name="valide" value="Envoyer le mail">
+    <h3>Destinataires</h3>
+    <table class="admin" id="dests">
+      <tbody>
+        <tr><td></td><td class="checks">Copie</td><td class="checks">Copie cachée</td></tr>
+<?php
+$a = 0;
+while ( $r = $resultat->fetch_assoc() )  {
+  if ( $a != $r['autorisation'] )  {
+    $a = $r['autorisation'];
+    switch ( $a )  {
+      case 1 : $t = 'Élèves'; break;
+      case 2 : $t = 'Colleurs'; break;
+      case 3 : $t = 'Professeurs'; break;
+    }
+    echo <<<FIN
+        <tr><th>$t</th><td></td><td></td></tr>
+
+FIN;
+    }
+  $nom = ( strlen($r['nom_complet']) ) ? $r['nom_complet'] : "<em>${r['login']}</em>";
+  echo "        <tr><td>$nom</td><td><input type=\"checkbox\" class=\"dest_c$a\" name=\"dest[]\" value=\"${r['id']}\"${r['mail']}></td><td><input type=\"checkbox\" class=\"dest_bcc$a\" name=\"dest_bcc[]\" value=\"${r['id']}\"${r['mail']}></td></tr>\n";
+}
+$resultat->free();
+$mysqli->close();
+?>
+      </tbody>
+    </table>
+
+  <script type="text/javascript">
+$( function() {
+  // Pliage du tableau des destinataires
+  $('#dests').hide().prev('h3').append(' <span id="deplie">déplier</span>');
+  $('#deplie').click( function () {
+    $('#dests').toggle();
+    $(this).text($(this).text() == 'déplier' ? 'replier' : 'déplier');
+  });
+
+  // Bouton de sélection multiple
+  $('#dests th').next().append('<input type="button" class="button_c" value="Tout cocher">');
+  $('#dests th').next().next().append('<input type="button" class="button_bcc" value="Tout cocher">');
+  $('.button_c,.button_bcc').click( function () {
+    var a = $(this).closest('tr').next().find('input[name=dest\\[\\]]').attr('class').substr(6);
+    var t1 = $(this).attr('class').substr(7);
+    var t2 = ( t1 == 'c' ? 'bcc' : 'c' );
+    if ( $(this).val() == 'Tout cocher' )  {
+      $(this).val('Tout décocher');
+      $('.dest_'+t1+a).prop("checked",true);
+      $('.dest_'+t2+a).prop("checked",false);
+      $(this).closest('tr').find('.button_'+t2).val('Tout cocher');
+    }
+    else  {
+      $(this).val('Tout cocher');
+      $('.dest_'+t1+a).prop("checked",false);
+    }
+  });
+  $('input[name*=dest]').click( function () {
+    if ( $(this).is(':checked') )
+      $(this).closest('tr').find( $(this).attr('class').length > 8 ? '.dest_c'+$(this).attr('class').substr(8) : '.dest_bcc'+$(this).attr('class').substr(6) ).prop("checked",false);
+  });
+});
+  </script>
+
+    <h3>Sujet</h3>
+    <textarea class="nojs" name="sujet" rows="1" cols="100"></textarea>
+    <input class="bouton" type="submit" name="valide" value="Envoyer le mail">
+    <h3>Contenu</h3>
+    <textarea class="nojs" name="texte" rows="30" cols="100"><?php echo "Bonjour à tous\n\n\n\n\n-- \n$exp"; ?></textarea>
+
+<?php
+// Pour info, l'expéditeur
+$u['mailcopy'] = ( $u['mailcopy'] ) ? 'Vous recevrez' : 'Vous ne recevrez pas';
+echo "
+  <p>L'expéditeur du mail apparaîtra comme &laquo;&nbsp;<code>$exp &lt;${u['mail']}&gt;</code>&nbsp;&raquo;. Ceci est modifiable dans vos <a href=\"prefs\">Préférences</a>.</p>
+  <p>${u['mailcopy']} une copie de ce mail dans votre boîte mail. Ceci est modifiable dans vos <a href=\"prefs\">Préférences</a>.</p>
+  </form>
+  </div>\n";
+
+
+// Bas de page
+include('bas.php');
+?>
diff -urN cahier-de-prepa3.2.0/admin/matieres.php cahier-de-prepa4.0.0/admin/matieres.php
--- cahier-de-prepa3.2.0/admin/matieres.php	1970-01-01 01:00:00.000000000 +0100
+++ cahier-de-prepa4.0.0/admin/matieres.php	2014-08-26 22:20:54.261003626 +0200
@@ -0,0 +1,259 @@
+<?php
+// Sécurité et récupération des données de configuration
+define('OK',1);
+include('debut.php'); // fournit $mysqli et $matieres
+
+///////////////////
+// Modifications //
+///////////////////
+
+// Ajout ou modification d'une matière
+if ( isset($_REQUEST['id']) && in_array($id = $_REQUEST['id'],explode(',',"0,${_SESSION['matieres']}")) && isset($_REQUEST['modifie']) )  {
+
+  // Récupération des données de la matière à modifier
+  if ( $id )  {
+    $resultat = $mysqli->query("SELECT cle, nom, colles_protection, cdt_protection FROM matieres WHERE id = $id");
+    $r = $resultat->fetch_assoc();
+  }
+  // Si $id = 0, nouvelle matière
+  else
+    $r = array('cle' => '', 'nom' => '', 'colles_protection' => 5, 'cdt_protection' => 5);
+
+  $requete = array();
+  $modif = array();
+  // Connexion à la base de données avec les droits d'écriture
+  $mysqli->close();
+  $mysqli = mysql_ecriture();
+
+  // Validation des données envoyées
+  $cle = $mysqli->real_escape_string($_REQUEST['cle']);
+  $nom = $mysqli->real_escape_string($_REQUEST['nom']);
+  if ( strlen($nom) && ( $nom != $r['nom'] ) )  {
+    $requete[] = "nom = '$nom'";
+    $modif[] = 'nom';
+    if ( $id )
+      requete('reps',"UPDATE reps SET nom = '$nom' WHERE matiere = $id AND parent = 0");
+  }
+  if ( strlen($cle) && ( $cle != $r['cle'] ) )  {
+    // Vérification que la clé souhaitée n'existe pas déjà
+    $resultat = $mysqli->query("SELECT id FROM matieres WHERE cle = '$cle'");
+    if ( $resultat->num_rows )
+      $resultat->free();
+    else  {
+      $requete[] = "cle = '$cle'";
+      $modif[] = 'clé';
+    }
+  }
+  if ( in_array($colles_protection = $_REQUEST['colles_protection'],array(0,1,2,3)) && ( $colles_protection != $r['colles_protection'] ) )  {
+    $requete[] = "colles_protection = $colles_protection";
+    $modif[] = 'accès aux programmes de colles';
+  }
+  if ( in_array($cdt_protection = $_REQUEST['cdt_protection'],array(0,1,2,3)) && ( $cdt_protection != $r['cdt_protection'] ) )  {
+    $requete[] = "cdt_protection = $cdt_protection";
+    $modif[] = 'accès au cahier de texte';
+  }
+
+  if ( empty($requete) )
+    $message = 'Aucune modification à effectuer.';
+
+  // Modification d'une matière existante
+  elseif ( $id )  {
+    $requete = implode(', ',$requete);
+    $modif = implode(', ',$modif);
+    if ( requete('matieres',"UPDATE matieres SET $requete WHERE id = $id") )
+      $message = "Les modifications ($modif) sur la matière &laquo;&nbsp;${r['nom']}&nbsp;&raquo; ont bien été réalisées.";
+    // Modification de $matieres, définie par debut.php et utilisée notamment pour le menu (haut.php)
+    $resultat = $mysqli->query("SELECT id, cle, nom FROM matieres
+                                WHERE FIND_IN_SET(id,'${_SESSION['matieres']}') ORDER BY FIND_IN_SET(id,'${_SESSION['matieres']}')");
+    $matieres = array();
+    while ( $r = $resultat->fetch_assoc() )
+      $matieres[] = $r;
+    $resultat->free();
+  }
+  
+  // Création d'une nouvelle matière
+  elseif ( count($modif) == 4 )  {
+    $requete = implode(', ',$requete);
+    if ( requete('matieres',"INSERT INTO matieres SET $requete, ordre = (SELECT MAX(m.ordre)+1 FROM matieres AS m)") )  {
+      $id = $mysqli->insert_id;
+      requete('reps',"INSERT INTO reps SET parent = 0, parents = '0', nom = '$nom', matiere = $id,
+                      nbrep = 0, nbrep_v = 0, nbdoc = 0, nbdoc_v = 0, protection = 0, menu = 0");
+      requete('cdt-types',"INSERT INTO `cdt-types` (matiere, ordre, cle, titre, deb_fin_pour) VALUES
+                           ($id, 1, 'cours', 'Cours', 1),
+                           ($id, 2, 'TD', 'Séance de travaux dirigés', 1),
+                           ($id, 3, 'TP', 'Séance de travaux pratiques', 1),
+                           ($id, 4, 'DS', 'Devoir surveillé', 1),
+                           ($id, 5, 'interros', 'Interrogation de cours', 0),
+                           ($id, 6, 'distributions', 'Distribution de document', 0),
+                           ($id, 7, 'DM', 'Devoir maison', 2)");
+      $message = 'La matière <em>'.stripslashes($nom).'</em> a bien été ajoutée.';
+    }
+  }
+  else
+    $message = 'La matière souhaitée n\'a pas pu être créée, car les données envoyées sont incorrectes. La clé ne peut pas être déjà utilisée par une autre matière, le nom et la clé doivent être non vides.';
+  
+  // Passage en connexion MySQL en lecture seulement
+  $mysqli->close();
+  $mysqli = mysql_lecture();
+}
+
+// Montée, descente, suppression : possible sur toutes les matières
+elseif ( isset($id) && is_numeric($id) )  {
+  
+  // Récupération des données de la matière à modifier
+  $resultat = $mysqli->query("SELECT nom, ordre, (SELECT MAX(m.ordre) FROM matieres AS m) AS max,
+                              colles+cdt+docs+notes AS nonvide FROM matieres WHERE id = $id");
+  if ( $resultat->num_rows )  {
+    $r = $resultat->fetch_assoc();
+    $ordre = $r['ordre'];
+
+    // Connexion à la base de données avec les droits d'écriture
+    $mysqli->close();
+    $mysqli = mysql_ecriture();
+
+    // Déplacement vers le haut
+    if ( isset($_REQUEST['monte']) && ( $ordre > 1 ) )
+      $message = ( requete('matieres',"UPDATE matieres SET ordre = (2*$ordre-1-ordre) WHERE ordre = $ordre OR ordre = ($ordre-1)")
+      ) ? "La matière <em>${r['nom']}</em> a bien été montée d'une place." : "La matière <em>${r['nom']}</em> n'a pas pu être déplacée. Erreur MySQL n°".$mysqli->errno.', «'.$mysqli->error.'».';
+
+    // Déplacement vers le bas
+    elseif ( isset($_REQUEST['descend']) && ( $ordre < $r['max'] ) )
+      $message = ( requete('matieres',"UPDATE matieres SET ordre = (2*$ordre+1-ordre) WHERE ordre = $ordre OR ordre = ($ordre+1)")
+      ) ? "La matière <em>${r['nom']}</em> a bien été descendue d'une place." : "La matière <em>${r['nom']}</em> n'a pas pu être déplacée. Erreur MySQL n°".$mysqli->errno.', «'.$mysqli->error.'».';
+
+    // Suppression
+    elseif ( isset($_REQUEST['supprime']) && ( $r['max'] > 1 ) && !$r['nonvide'] )  {
+      if ( requete('matieres',"DELETE FROM matieres WHERE id = $id") )  {
+        requete('matieres',"UPDATE matieres SET ordre = (ordre-1) WHERE ordre > $ordre");
+        requete('cdt-types',"DELETE FROM `cdt-types` WHERE matiere = $id");
+        requete('cdt-seances',"DELETE FROM `cdt-seances` WHERE matiere = $id");
+        requete('reps',"DELETE FROM reps WHERE matiere = $id");
+        requete('pages',"UPDATE pages SET mat = 0 WHERE mat = $id");
+        $message = "La matière <em>${r['nom']}</em> a bien été supprimée.";
+        requete('utilisateurs',"UPDATE utilisateurs SET matieres = TRIM(BOTH ',' FROM REPLACE(CONCAT(',',matieres,','),',$id,',',')) ");
+        if ( $mysqli->affected_rows )  {
+          $message .= '<br> Elle a été détachée de '.$mysqli->affected_rows.' utilisateur(s).';
+          // Modification de $matieres, définie par debut.php et utilisée notamment pour le menu (haut.php)
+          $m = explode(',',$_SESSION['matieres']);
+          if ( in_array($id,$m) )  {
+            unset($matieres[array_search($id,$m)]);
+            unset($m[array_search($id,$m)]);
+            $_SESSION['matieres'] = implode(',',$m);
+          }
+        }
+      }
+      else
+        $message = "La matière <em>${r['nom']}</em> n'a pas pu être supprimée. Erreur MySQL n°".$mysqli->errno.', «'.$mysqli->error.'».';
+    }
+
+    // Passage en connexion MySQL en lecture seulement
+    $mysqli->close();
+    $mysqli = mysql_lecture();
+  }
+}
+
+//////////
+// HTML //
+//////////
+// Haut de page, menu et message
+$p = "matieres";
+$t = 'Modifications des matieres';
+include('haut.php');
+?>
+
+  <div class="item aide">
+    <h3>Aide et explications</h3>
+    <p>Vous pouvez ci-dessous ajouter une matière ou modifier les matières existantes.</p>
+    <p>L'ordre d'apparition des matières dans le menu de la partie publique est modifiable grâce aux flèches. Si vous avez plusieurs matières associée à votre compte dans cette interface d'administration, leur ordre n'est pas modifiable ici&nbsp;: il faut aller voir vos <a href="prefs">Préférences</a>.</p>
+    <h4>Préférences de chaque matière</h4>
+    <p>Pour chaque matière associée à votre compte, vous pouvez modifier&nbsp;:</p>
+    <ul>
+      <li>le <em>nom complet</em> qui s'affiche dans le menu et dans les titres des pages. Mettez une majuscule au début.</li>
+      <li>la <em>clé dans l'adresse</em> qui est un mot-clé utilisé uniquement dans l'adresse des pages associées à la matière. Il vaut mieux que ce soit un mot unique, court et sans majuscule. Par exemple, «&nbsp;maths&nbsp;», «&nbsp;phys&nbsp;»...</li>
+    </ul>
+    <p>L'<em>accès au programmes de colles</em> et l'<em>accès au cahier de texte</em> peuvent être choisis parmi quatre possibilités&nbsp;:</p>
+    <ul>
+      <li><em>Visible de tous</em>&nbsp;: accessible de tout visiteur, sans identification.</li>
+      <li><em>Visible pour les élèves, colleurs, professeurs</em>&nbsp;: accessible uniquement par tout utilisateur après identification. Il faut donc avoir un compte validé (voir l'aide de la page <a href="utilisateurs">Utilisateurs</a>) pour y accéder.</li>
+      <li><em>Visible pour les colleurs, professeurs</em>&nbsp;: accessible uniquement par les utilisateurs de type colleur ou professeur.</li>
+      <li><em>Visible pour les professeurs</em>&nbsp;: accessible uniquement par les utilisateurs de type professeur (vous et vos collègues).</li>
+    </ul>
+    <p>Quel que soit ce choix, les liens dans le menu de la partie publique apparaîtront pour tout visiteur (si le cahier de texte/programme de colles est non vide uniquement), tout comme le lien vers les documents.</p>
+    <h4>Suppression d'une matière</h4>
+    <p>La suppression d'une matière est une modification importante et lourde de conséquence. Aussi, il n'est possible ici que de supprimer les matières&nbsp;:
+    <ul>
+      <li>qui vous sont associées</li>
+      <li>dont le cahier de texte, les programmes de colles, les documents et les notes sont complètement vides. Tous ces contenus peuvent être rapidement supprimés dans vos <a href="prefs">Préférences</a> (il faut aller dans les préférences de la matière).</li>
+    </ul>
+    <p>La matière à supprimer peut l'être même si elle est encore associée à un autre collègue. Elle en sera automatiquement détachée.</p>
+  </div>
+
+<?php
+// Formulaire pour chaque matière
+$select_protection = '
+        <option value="0">Visible de tous</option>
+        <option value="1">Visible pour les élèves, colleurs, profs</option>
+        <option value="2">Visible pour les colleurs et les profs</option>
+        <option value="3">Visible pour les profs uniquement</option>';
+function affichage($r)  {
+  $select_colles = str_replace("\"${r['colles_protection']}\"","\"${r['colles_protection']}\" selected",$GLOBALS['select_protection']);
+  $select_cdt = str_replace("\"${r['cdt_protection']}\"","\"${r['cdt_protection']}\" selected",$GLOBALS['select_protection']);
+  if ( $id = $r['id'] )  {
+    $titre = "Matière n°${r['ordre']}&nbsp;: ${r['nom']}";
+    $valide1 = '';
+    $valide = "\n      <input type=\"submit\" name=\"modifie\" value=\"Valider\" title=\"Valider les modifications\"${r['disabled']}>";
+    $monte = ( $r['ordre'] > 1 ) ? "\n      <input type=\"submit\" name=\"monte\" value=\"&uarr;\" title=\"Remonter la matière dans l'ordre d'apparition\">" : '';
+    $descend = ( $r['ordre'] < $GLOBALS['max'] ) ? "\n      <input type=\"submit\" name=\"descend\" value=\"&darr;\" title=\"Descendre la matière dans l'ordre d'apparition\">" : '';
+    if ( $r['ok'] )  {
+      $indication = ( $r['nonvide'] ) ? "\n    <p>Cette matière ne peut pas être supprimée car elle contient des données. Vous pouvez les supprimer en allant dans vos <a href=\"prefs\">préférences</a>.</p>" : '';
+    }
+    else  {
+      $indication = "\n    <p>Cette matière ne peut pas être modifiée tant qu'elle ne vous est pas associée. Ceci est modifiable dans vos <a href=\"prefs\">préférences</a>. Elle sera supprimable si tous les contenus sont supprimés avant.</p>";
+    }
+    $suppr = ( ( $GLOBALS['max'] > 1 ) ) ? "\n      <input type=\"submit\" name=\"supprime\" value=\"Supprimer\" title=\"Supprimer la matière\"".( ( $r['nonvide'] ) ? ' disabled' : '' ).'>' : '';
+  }
+  else  {
+    $titre = 'Nouvelle matière';
+    $valide1 = "\n    <input class=\"bouton\" type=\"submit\" name=\"modifie\" value=\"Valider\" title=\"Valider les modifications\">";
+    $valide = $monte = $descend = $suppr = $indication = '';
+  }
+  echo <<<FIN
+  <div class="item admin">
+  <form action="" method="post">$valide1
+    <h3>$titre</h3>
+    <p class="boutons">$valide$monte$descend$suppr
+    </p>
+    <p class="ligne"><label for="nom$id">Nom complet&nbsp;: </label><input type="text" id="nom$id" name="nom" value="${r['nom']}" size="50"${r['disabled']}></p>
+    <p class="ligne"><label for="cle$id">Clé dans l'adresse&nbsp;: </label><input type="text" id="cle$id" name="cle" value="${r['cle']}" size="30"${r['disabled']}></p>
+    <p class="ligne"><label for="colles_protection$id">Accès aux programmes de colles&nbsp;: </label>
+      <select id="colles_protection$id" name="colles_protection"${r['disabled']}>$select_colles
+      </select>
+    </p>
+    <p class="ligne"><label for="cdt_protection$id">Accès au cahier de texte&nbsp;: </label>
+      <select id="cdt_protection$id" name="cdt_protection"${r['disabled']}>$select_cdt
+      </select>
+    </p>
+    <input type="hidden" name="id" value="$id">$indication
+  </form>
+  </div>
+
+
+FIN;
+}
+
+// Formulaire vide pour une nouvelle matière et aide générale
+affichage(array('id' => 0, 'cle' => 'cle_de_la_matiere', 'nom' => 'Nom de la matière', 'colles_protection' => 0, 'cdt_protection' => 0, 'disabled' => ''));
+
+$resultat = $mysqli->query("SELECT id, ordre, cle, nom, colles+cdt+docs+notes AS nonvide, colles_protection, cdt_protection,
+                            FIND_IN_SET(id,'${_SESSION['matieres']}') AS ok,
+                            IF (FIND_IN_SET(id,'${_SESSION['matieres']}'),'',' disabled') AS disabled
+                            FROM matieres ORDER BY ordre");
+$max = $resultat->num_rows;
+$mysqli->close();
+while ( $r = $resultat->fetch_assoc() )
+  affichage($r);
+$resultat->free();
+
+// Bas de page
+include('bas.php');
+?>
diff -urN cahier-de-prepa3.2.0/admin/notes.php cahier-de-prepa4.0.0/admin/notes.php
--- cahier-de-prepa3.2.0/admin/notes.php	1970-01-01 01:00:00.000000000 +0100
+++ cahier-de-prepa4.0.0/admin/notes.php	2014-08-26 16:10:28.300292393 +0200
@@ -0,0 +1,158 @@
+<?php
+// Sécurité et récupération des données de configuration
+define('OK',1);
+include('debut.php'); // fournit $mysqli et $matieres
+
+////////////////////////////////////////
+// Validation de la requête : matière //
+////////////////////////////////////////
+
+// Recherche de la matière concernée
+if ( !empty($_REQUEST) )
+  foreach ( $matieres as $r )
+    if ( isset($_REQUEST[$r['cle']]) )  {
+      $matiere = $r;
+      $mid = $matiere['id'];
+      break;
+    }
+// Si mauvaise demande
+if ( !isset($matiere) )  {
+  if ( strlen($_SESSION['matieres']) )
+    exit('<p>Mauvais paramètre d\'accès à cette page. Vous n\'avez accès qu\'aux matières associées à votre compte et l\'adresse doit comporter la clé de la matière. Vous pouvez chager vos matières associées dans vos <a href="prefs">Préférences</a>.</p><p>Il est préférable de passer par les lien du menu&nbsp; <a href=".">retour à l\'accueil</a></p>');
+  else
+    exit('<p>Vous ne pourrez afficher cette page qu\'après avoir associé des matières à votre compte. Il faut aller dans vos <a href="prefs">Préférences</a> pour cela.</p>');
+}
+
+//////////////////////////////
+// Appel à notes-saisie.php //
+//////////////////////////////
+// * appelle haut.php
+// * réalise les modifications de la base
+// * fournit $eid, $eleves et $semaines
+// * fournit le contenu à afficher dans $aff
+include('notes-saisie.php');
+
+////////////////////////////////////
+// Consultation globale des notes //
+////////////////////////////////////
+
+// Select sur les colleurs
+$resultat = $mysqli->query("SELECT id, CONCAT(prenom,' ',nom) AS nom FROM utilisateurs WHERE autorisation > 1 AND FIND_IN_SET($mid,matieres) ORDER BY nom");
+$select_colleurs = '';
+while ( $r = $resultat->fetch_assoc() )  {
+  $select_colleurs .= "\n        <option value=\"${r['id']}\">${r['nom']}</option>";
+  $cid[] = $r['id'];
+}
+$resultat->free();
+
+// Select sur les semaines
+$select_semaines = preg_replace('/ disabled>(.*) \(\d+ notes déjà entrées\)/','>\1',$select_semaines);
+
+// Validation de la requête
+if ( isset($_REQUEST['colleur']) )  {
+  $sid = array_keys($semaines);
+
+  if ( !isset($_REQUEST['depuisledebut']) && is_numeric($n1 = $_REQUEST['n']) && is_numeric($nb = $_REQUEST['nb']) )  {
+    // Validation de n
+    while ( !isset($semaines[$n1]) )
+      $n1 = ( $n1 < 50 ) ? $n1+1 : key($semaines);
+    // Validation de nb
+    $m = array_search($n1,$sid);
+    if ( isset($sid[$m+$nb]) )  {
+      $n2 = $sid[$m+$nb-1];
+      $sid = array_slice($sid,$m,$nb);
+    }
+    else  {
+      $n2 = end($sid);
+      $sid = array_slice($sid,$m);
+      $nb = count($sid);
+    }
+  }
+  // Si toute l'année
+  else  {
+    $n1 = current($sid);
+    $n2 = end($sid);
+    $nb = count($sid);
+  }
+  $select_semaines = str_replace("\"$n1\"","\"$n1\" selected",$select_semaines);
+
+  // Validation du colleur
+  if ( in_array($colleur = $_REQUEST['colleur'],$cid) )  {
+    $select_colleurs = str_replace("\"$colleur\"","\"$colleur\" selected",$select_colleurs);
+    $colleur = "OR colleur != ${_REQUEST['colleur']}";
+  }
+  else
+    $colleur = '';
+}
+// Valeur par défaut
+else
+  $nb = 1;
+
+// Aide et formulaire de recherche
+?>
+
+  <div class="item aide">
+    <h3>Aide et explications</h3>
+    <p>Vous pouvez ci-dessous consulter les notes en <?php echo $matiere['nom']; ?> et en ajouter.</p>
+    <p>Vous ne pouvez consulter que les notes de colles des matières qui vous sont associées. Elles sont modifiables dans vos <a href="prefs">Préférences</a>.</p>
+    <h4>Consultation des notes de colles</h4>
+    <p>Pour consulter les notes, vous devez valider le premier formulaire ci-dessous, vous permettant de choisir éventuellement le colleur dont vous voulez voir les notes et la période sur laquelle vous souhaitez voir les notes. De façon générale, un clic sur le bouton <em>pour toute l'année</em> sera ce que vous souhaitez.</p>
+    <p>Le tableau qui s'affichera est récupérable dans un tableur (Excel, Calc...) par simple copié-collé. Répondez &laquo;&nbsp;automatique&nbsp;&raquo; lorsque votre tableur vous demandera comment vous souhaitez réaliser l'importation.</p>
+    <p>Le caractère &laquo;&nbsp;a&nbsp;&raquo; signifie que l'élève a été marqué absent par le colleur.</p>
+    <h4>Ajout de notes de colles</h4>
+    <p>Vous pouvez ajouter ou modifier des notes. Attention, un élève ne peut avoir plus d'une note dans la même matière une même semaine&nbsp;: si vous ajouter une telle note, elle ne sera pas validée.</p>
+    <p>Pour ajouter des notes sur une nouvelle semaine, vous devez obligatoirement sélectionner une semaine où vous n'avez pas encore mis de note dans cette matière. Il n'y a pas de limite de nombre d'élèves collés par semaine (toutes les notes d'une semaine sont à entrer en une seule fois).</p>
+    <p>Les notes déjà mises peuvent être modifiées&nbsp;: soit déplacées à une autre semaine, soit modifiées sur place, soit les deux simultanément.</p>
+    <p>Il est aussi possible de <em>supprimer</em> l'ensemble des notes d'une semaine.</p>
+  </div>
+
+  <h2>Consulter les notes de la classe</h2>
+
+  <div class="item" id="recherche">
+  <form action="" method="post">
+    <p>Afficher les notes de&nbsp;
+      <select name="colleur">
+        <option value="0">tous les colleurs</option><?php echo $select_colleurs; ?>
+      </select>
+      <input type="submit" name="depuisledebut" id="depuisledebut" value="pour toute l'année">
+      ou&nbsp;pendant&nbsp;<input type="text" name="nb" value="<?php echo $nb; ?>" size="2">&nbsp;semaine(s) à partir du&nbsp;
+      <select name="n"><?php echo $select_semaines; ?>
+      </select>
+      <input type="submit" name="" value="OK">
+    </p>
+  </form>
+  </div>
+
+<?php
+// Affichage du tableau de notes
+if ( isset($_REQUEST['colleur']) )  {
+  // Recherche des notes concernées
+  $resultat = $mysqli->query("SELECT nom, CONCAT('<td>',GROUP_CONCAT( IF(ISNULL(note) OR note = 'x' $colleur,'',note) ORDER BY sid SEPARATOR '</td><td>'),'</td>') AS notes
+                              FROM ( SELECT s.id AS sid, u.id AS eid, CONCAT('<td>',nom,' ',prenom,'</td>') AS nom
+                                     FROM semaines AS s LEFT JOIN utilisateurs AS u ON 1 WHERE colle AND s.id>=$n1 AND s.id<=$n2 AND u.autorisation=1 ORDER BY nom) AS t
+                              LEFT JOIN notes ON sid = semaine AND eid = eleve AND matiere = $mid GROUP BY eid ORDER BY nom,sid");
+  if ( $resultat->num_rows )  {
+    echo "  <table class=\"admin\">\n    <thead>\n      <tr><th class=\"semaines\">Nom</th>";
+    foreach ( $sid as $i)
+      echo '<td class="semaines"><span>'.str_pad(preg_replace('/.{5}(.{2})(.*)/','\2/\1',$semaines[$i]),5,'0',STR_PAD_LEFT).'</span></td>';
+    echo "</tr>\n    </thead>\n      <tbody>\n";
+    while ( $r = $resultat->fetch_assoc() )
+      if ( strlen(str_replace('<td></td>','',$r['notes'])) )
+        echo "        <tr>${r['nom']}${r['notes']}</tr>\n";
+      else
+        echo "        <tr>${r['nom']}<td class=\"pasnote\" colspan=\"$nb\">Pas encore de note pour cet élève</td></tr>\n";
+    $resultat->free();
+    echo "      </tbody>\n  </table>\n\n";
+  }
+  else
+    echo "\n  <div class=\"warning\">Il n'y a encore aucune note de colle en ${matiere['nom']} cette année.</div>\n\n";
+}
+
+// Affichage du résultat de notes.saisie.php
+echo "  <h2>Ajouter des notes</h2>\n$aff";
+
+$mysqli->close();
+
+// Bas de page
+include('bas.php');
+?>
diff -urN cahier-de-prepa3.2.0/admin/notes-saisie.php cahier-de-prepa4.0.0/admin/notes-saisie.php
--- cahier-de-prepa3.2.0/admin/notes-saisie.php	1970-01-01 01:00:00.000000000 +0100
+++ cahier-de-prepa4.0.0/admin/notes-saisie.php	2014-08-26 16:07:50.616287347 +0200
@@ -0,0 +1,216 @@
+<?php
+// Sécurité : ne doit pas être exécuté s'il n'est pas lancé par un script autorisé
+if ( !defined('OK') )  exit();
+
+// Lancé uniquement depuis notes.php (colleurs et profs) ou admin/notes.php (profs uniquement)
+// * authentification déjà faite
+// * debut.php déjà parcouru (et fonctions.php aussi)
+// * $mysqli déjà définie
+// * $matiere et $mid déjà définies
+
+// Récupération des élèves
+$resultat = $mysqli->query('SELECT id, CONCAT(nom,\' \',prenom) AS nom FROM utilisateurs WHERE autorisation = 1 ORDER BY nom');
+while ( $r = $resultat->fetch_assoc() )  {
+  $eid[] = $r['id'];
+  $eleves[] = $r;
+}
+$resultat->free();
+
+///////////////////
+// Modifications //
+///////////////////
+if ( isset($_REQUEST['id']) )  {
+  $id = $_REQUEST['id'];
+  
+  // Récupération des semaines contenant des notes
+  $resultat = $mysqli->query("SELECT s.id, COUNT(n.id) AS nb, DATE_FORMAT(s.debut,'%w%Y%m%e') AS d
+                              FROM semaines AS s LEFT JOIN (SELECT id, semaine FROM notes WHERE colleur = ${_SESSION['id']} AND matiere = $mid) AS n ON n.semaine = s.id
+                              WHERE s.colle GROUP BY s.id ORDER BY s.id");
+  while ( $r = $resultat->fetch_assoc() )
+    $sid[$r['id']] = $r;
+  $resultat->free();
+
+  // Connexion à la base de données avec les droits d'écriture
+  $mysqli->close();
+  $mysqli = mysql_ecriture();
+
+  // Ajout ou modification de notes 
+  if ( isset($_REQUEST['modifie']) && isset($sid[$s = $_REQUEST['semaine']]) && ( ( $id == $s ) || !$sid[$s]['nb'] ) )  {
+    // Suppression des notes existantes
+    if ( isset($sid[$id]) )  {
+      requete('notes',"DELETE FROM notes WHERE semaine = $id AND colleur = ${_SESSION['id']} AND matiere = $mid");
+      $action = ( $id == $s ) ? 'modification' : 'déplacement';
+    }
+    else
+      $action = 'ajout';
+
+    // Écriture des nouvelles notes
+    $requete = array();
+    $modif = array();
+    foreach ( $_REQUEST['notes'] as $eleve => $note )  {
+      if ( in_array($eleve,$eid) && ( is_numeric($note) || ( $note == 'a' ) ) )  {
+        $requete[] = "($s,$eleve,${_SESSION['id']},$mid,'$note')";
+        $modif[] = $eleves[array_search($eleve,$eid)]['nom'];
+      }
+    }
+
+    // Exécution et message
+    if ( empty($requete) )  {
+      if ( $action == 'ajout' )
+        $message = 'Aucune modification à effectuer.';
+      else
+        $message = ($mysqli->affected_rows).' notes ont bien été supprimées à la semaine du '.format_date($sid[$id]['d']).'.';
+    }
+    else  {
+      $n = count($requete);
+      $requete = implode(', ',$requete);
+      $modif = implode(', ',$modif);
+      if ( requete('notes',"INSERT INTO notes (semaine,eleve,colleur,matiere,note) VALUES $requete") ) {
+        switch ( $action )  {
+          case 'ajout':
+            $message = "$n notes ($modif) ont bien été ajoutées à la semaine du ".format_date($sid[$s]['d']).'.';
+            break;
+          case 'modification':
+            $message = "$n notes ($modif) ont bien été modifiées à la semaine du ".format_date($sid[$s]['d']).'.';
+            break;
+          case 'déplacement':
+            $message = "$n notes ($modif) ont bien été modifiées et déplacées de la semaine du ".format_date($sid[$id]['d']).' à la semaine du '.format_date($sid[$s]['d']).'.';
+        }
+        // Vérification qu'il n'y a pas deux notes de colles pour le même élève, la même semaine, dans la même matière
+        $resultat = $mysqli->query("SELECT n1.id AS id FROM (SELECT * FROM notes WHERE colleur = ${_SESSION['id']} AND matiere = $mid) AS n1
+                                    JOIN notes AS n2 USING (semaine,eleve,matiere) WHERE n1.id != n2.id");
+        if ( $resultat->num_rows )  {
+          while( $r = $resultat->fetch_assoc() )
+            requete('notes',"DELETE FROM notes WHERE id = ${r['id']}");
+          $resultat->free();
+          $message .= '<br>Attention, certaines de vos notes ont été automatiquement supprimées&nbsp;: il est impossible pour un élève d\'avoir la même semaine deux notes dans la même matière.';
+        }
+      }
+      else
+        $message = 'Les notes de la semaine du '.format_date($sid[$id]['d']).' n\'ont pas pu être supprimées. Erreur MySQL n°'.$mysqli->errno.', «'.$mysqli->error.'».';
+    }
+  }
+
+  // Suppression des notes d'une semaine
+  elseif ( isset($_REQUEST['supprime']) && isset($sid[$id]) && $sid[$id]['nb'] )
+    $message = ( requete('notes',"DELETE FROM notes WHERE semaine = $id AND colleur = ${_SESSION['id']} AND matiere = $mid") ) ? 'Les '.$sid[$id]['nb'].' notes de la semaine du '.format_date($sid[$id]['d']).' ont bien été supprimées.' : 'Les '.$sid[$id]['nb'].' notes de la semaine du '.format_date($sid[$id]['d']).' n\'ont pas pu être supprimées. Erreur MySQL n°'.$mysqli->errno.', «'.$mysqli->error.'».';
+
+  // Mise à jour des champs 'notes' dans la table 'matieres' (pour le menu)
+  $mysqli->query('UPDATE matieres SET notes = IF((SELECT id FROM notes WHERE matiere = matieres.id LIMIT 1),1,0)');
+
+  // Passage en connexion MySQL en lecture seulement
+  $mysqli->close();
+  $mysqli = mysql_lecture();
+}
+
+////////////
+/// HTML ///
+////////////
+$p = "notes?${matiere['cle']}";
+$t = "Notes de colles en ${matiere['nom']}";
+
+// Haut de page, menu et message
+include('haut.php');
+
+// Besoin de stocker l'affichage à émettre pour insérer ici du contenu dans admin/notes.php
+ob_start();
+
+// Récupération des semaines
+$resultat = $mysqli->query("SELECT s.id, DATE_FORMAT(s.debut,'%w%Y%m%e') AS d, s.colle, s.vacances, COUNT(n.id) AS nb
+                            FROM semaines AS s
+                            LEFT JOIN (SELECT id, semaine FROM notes WHERE colleur = ${_SESSION['id']} AND matiere = $mid) AS n ON n.semaine = s.id
+                            GROUP BY s.id ORDER BY s.id");
+$select_semaines = '';
+while ( $r = $resultat->fetch_assoc() )  {
+  switch ( $r['vacances'] )  {
+    case 0:
+      if ( $r['colle'] == 0 )
+        $select_semaines .= "\n        <option value=\"${r['id']}\" disabled>".format_date($r['d']).' (pas de colle)</option>';
+      else  {
+        $select_semaines .= ( $r['nb'] ) ? "\n        <option value=\"${r['id']}\" disabled>".format_date($r['d'])." (${r['nb']} notes déjà entrées)</option>" : "\n        <option value=\"${r['id']}\">".format_date($r['d']).'</option>';
+        $semaines[$r['id']] = $r['d'];
+      }
+      break;
+    case 1:
+      $select_semaines .= "\n        <option value=\"${r['id']}\" disabled>Vacances de Toussaint</option>";
+      break;
+    case 2:
+      $select_semaines .= "\n        <option value=\"${r['id']}\" disabled>Vacances de Noël</option>";
+      break;
+    case 3:
+      $select_semaines .= "\n        <option value=\"${r['id']}\" disabled>Vacances d'hiver</option>";
+      break;
+    case 4:
+      $select_semaines .= "\n        <option value=\"${r['id']}\" disabled>Vacances de Pâques</option>";
+  }
+}
+$resultat->free();
+
+// Fonction d'affichage
+function affichage($r,$d)  {
+  $s = $r['semaine'];
+  if ( strlen($d) )  {
+    $titre = 'Semaine du '.format_date($d)."&nbsp;: ${r['nb']} notes";
+    $valide = '';
+    $suppr = "\n      <input class=\"bouton\" type=\"submit\" name=\"supprime\" value=\"Supprimer\" title=\"Supprimer ces notes\">";
+    $semaine = 'Changer la semaine';
+    $select_semaines = str_replace("\"$s\" disabled","\"$s\" selected",$GLOBALS['select_semaines']);
+  }
+  else  {
+    $titre = 'Nouvelles notes';
+    $suppr = '';
+    $semaine = 'Semaine';
+    $select_semaines = str_replace("\"$s\"","\"$s\" selected",$GLOBALS['select_semaines']);
+    $s = 0;
+  }
+  echo <<<FIN
+
+  <div class="item admin">
+  <form action="" method="post">
+    <input class="bouton" type="submit" name="modifie" value="Valider" title="Valider ces notes">$suppr
+    <h3>$titre</h3>
+    <p class="ligne"><label for="sem$s">$semaine&nbsp;:</label>
+      <select id="sem$s" name="semaine">$select_semaines
+      </select>
+    </p>
+FIN;
+  // Affichage des élèves
+  $select_notes = '        <option value="x"></option><option value="a">Absent</option><option value="0">0</option><option value="1">1</option><option value="2">2</option><option value="3">3</option><option value="4">4</option><option value="5">5</option><option value="6">6</option><option value="7">7</option><option value="8">8</option><option value="9">9</option><option value="10">10</option><option value="11">11</option><option value="12">12</option><option value="13">13</option><option value="14">14</option><option value="15">15</option><option value="16">16</option><option value="17">17</option><option value="18">18</option><option value="19">19</option><option value="20">20</option>';
+  $eleves = $GLOBALS['eleves'];
+  $notes = array_combine(explode(',',$r['eleves']),explode(',',$r['notes']));
+  foreach ( $eleves as $eleve )  {
+    $id = $eleve['id'];
+    echo "
+    <p class=\"ligne\"><label for=\"eleve${id}_$s\">${eleve['nom']}</label>
+      <select id=\"eleve${id}_$s\" name=\"notes[$id]\">\n";
+    echo ( isset($notes[$id]) ) ? str_replace("\"${notes[$id]}\"","\"${notes[$id]}\" selected",$select_notes) : $select_notes;
+    echo "\n      </select>\n    </p>";
+  }
+  echo "
+    <input type=\"hidden\" name=\"id\" value=\"$s\">
+  </form>
+  </div>\n";
+}
+
+// Formulaire pour de nouvelles notes
+$resultat = $mysqli->query("SELECT id FROM semaines WHERE colle AND id > IFNULL( ( SELECT MAX( semaine ) FROM notes WHERE colleur = ${_SESSION['id']} AND matiere = $mid) , 0 ) LIMIT 1");
+$r = $resultat->fetch_assoc();
+$resultat->free();
+affichage(array('semaine'=>$r['id'],'nb'=>0,'eleves'=>'0','notes'=>'0'),'');
+                              
+// Récupération de l'ensemble des notes mises, regroupées par semaine
+$resultat = $mysqli->query("SELECT semaine, COUNT(id) AS nb,
+                            GROUP_CONCAT(eleve ORDER BY id SEPARATOR ',') AS eleves, GROUP_CONCAT(note ORDER BY id SEPARATOR ',') AS notes
+                            FROM notes WHERE colleur = ${_SESSION['id']} AND matiere = $mid GROUP BY semaine ORDER BY semaine DESC");
+// Affichage de l'ensmeble des notes de l'année
+if ( $resultat->num_rows )  {
+  while ( $r = $resultat->fetch_assoc() )
+    affichage($r,$semaines[$r['semaine']]);
+  $resultat->free();
+}
+else
+  echo "  <h2>Vous n'avez encore donné aucune note cette année.</h2>\n\n";
+
+// Le buffer est stocké dans $aff, à afficher dans le script appelant
+$aff = ob_get_clean();
+?>
diff -urN cahier-de-prepa3.2.0/admin/pages.php cahier-de-prepa4.0.0/admin/pages.php
--- cahier-de-prepa3.2.0/admin/pages.php	1970-01-01 01:00:00.000000000 +0100
+++ cahier-de-prepa4.0.0/admin/pages.php	2014-08-28 10:46:42.561200388 +0200
@@ -0,0 +1,487 @@
+<?php
+// Sécurité et récupération des données de configuration
+define('OK',1);
+include('debut.php'); // fournit $mysqli et $matieres
+
+// Recherche de la page concernée, variable "page"
+if ( isset($_REQUEST['page']) )  {
+  $resultat = $mysqli->query("SELECT p.id, CONCAT_WS('/',m.cle,p.cle) AS cle, p.nom, p.mat
+                              FROM pages AS p LEFT JOIN matieres AS m ON p.mat = m.id
+                              WHERE FIND_IN_SET(p.mat,'0,${_SESSION['matieres']}')");
+  if ( $resultat->num_rows )  {
+    while ( $r = $resultat->fetch_assoc() )
+      if ( $_REQUEST['page'] == $r['cle'] )  {
+        $pid = $r['id'];
+        $page = $r;
+        break;
+      }
+    $resultat->free();
+  }
+  // Pas de page par défaut : si pas de page demandée, gestion des pages
+}
+
+/////////////////////////////
+// Modifications des pages //
+/////////////////////////////
+if ( isset($_REQUEST['action']) && $_REQUEST['action'] == 'page' )  {
+  // Connexion à la base de données
+  $mysqli->close();
+  $mysqli = mysql_ecriture();
+  
+  // Vérification que l'identifiant est valide. Défaut : id=0 (nouvelle page)
+  if ( isset($pid) )
+    $id = $pid;
+  elseif ( is_numeric($_REQUEST['id']) )
+    $id = $_REQUEST['id'];
+  $resultat = $mysqli->query("SELECT id, nom, ordre, mat, (SELECT MAX(p.ordre) FROM pages AS p WHERE p.mat = pages.mat) AS max FROM pages WHERE id = $id");
+  if ( $resultat->num_rows )  {
+    $r = $resultat->fetch_assoc();
+    $resultat->free();
+  }
+  else
+    $id = 0;
+
+  // Ajout ou modification d'une page
+  if ( isset($_REQUEST['modifie']) )  {
+    // Vérification des données envoyées
+    if ( !strlen($_REQUEST['titre']) || !strlen($_REQUEST['cle']) || !strlen($_REQUEST['nom']) )
+      $message = 'Il n\'est pas possible de valider une page avec une clé, un nom ou un titre vide. Pour supprimer une page, il faut cliquer sur Supprimer.';
+    else  {
+      // Validation des données envoyées
+      $titre = $mysqli->real_escape_string($_REQUEST['titre']);
+      $bandeau = $mysqli->real_escape_string($_REQUEST['bandeau']);
+      $cle = $mysqli->real_escape_string($_REQUEST['cle']);
+      $nom = $mysqli->real_escape_string($_REQUEST['nom']);
+      $protection = ( in_array($_REQUEST['protection'],array(0,1,2,3)) ) ? $_REQUEST['protection'] : 0;
+      if ( !isset($_REQUEST['mat']) || !is_numeric($mat = $_REQUEST['mat']) || !in_array($mat,explode(',',$_SESSION['matieres'])) )
+        $mat = 0;
+      // Si identifiant numérique non nul, modification d'une page existante
+      if ( $id )  {
+        // Si matière identique
+        if ( $mat == $r['mat'] )
+          $message = ( requete('pages',"UPDATE pages SET cle = '$cle', nom = '$nom', titre = '$titre',
+                                        bandeau = '$bandeau', protection = $protection WHERE id = $id")
+          ) ? 'La page <em>'.stripslashes($nom).'</em> a bien été modifiée.' : "La page <em>${r['nom']}</em> n'a pas pu être modifiée. Erreur MySQL n°".$mysqli->errno.', «'.$mysqli->error.'».';
+        else  {
+          $resultat = $mysqli->query("SELECT IFNULL(MAX(ordre)+1,1) AS max FROM pages WHERE mat = $mat");
+          $m = $resultat->fetch_assoc();
+          $resultat->free();
+          $message = ( requete('pages',"UPDATE pages SET cle = '$cle', nom = '$nom', mat = $mat, titre = '$titre',
+                                        ordre = ${m['max']}, bandeau = '$bandeau', protection = $protection WHERE id = $id")
+                    && requete('pages',"UPDATE pages SET ordre = (ordre-1) WHERE mat = ${r['mat']} AND ordre > ${r['ordre']}")
+          ) ? 'La page <em>'.stripslashes($nom).'</em> a bien été modifiée.' : "La page <em>${r['nom']}</em> n'a pas pu être modifiée. Erreur MySQL n°".$mysqli->errno.', «'.$mysqli->error.'».';
+        }
+        // Correction des données de la page si $pid réglé
+        if ( isset($pid) )  {
+          $resultat = $mysqli->query("SELECT p.id, CONCAT_WS('/',m.cle,p.cle) AS cle, p.nom
+                                      FROM pages AS p LEFT JOIN matieres AS m ON p.mat = m.id
+                                      WHERE p.id = $pid");
+          $page = $resultat->fetch_assoc();
+          $resultat->free();
+        }
+      }
+      // Si id = 0, ajout d'une nouvelle page
+      else
+        $message = ( requete('pages',"INSERT INTO pages SET cle = '$cle', nom = '$nom', mat = $mat, titre = '$titre',
+                                      bandeau = '$bandeau', protection = $protection, ordre = (SELECT IFNULL(MAX(ordre)+1,1)
+                                      FROM pages AS p WHERE p.mat = $mat)")
+        ) ? 'La page «&nbsp'.stripslashes($nom).'</em> a bien été ajoutée.' : 'La page <em>'.stripslashes($nom).'</em> n\'a pas pu être ajoutée. Erreur MySQL n°'.$mysqli->errno.', «'.$mysqli->error.'».';
+    }
+  }
+
+  elseif ( $id )  {
+    $ordre = $r['ordre'];
+
+    // Déplacement vers le haut
+    if ( isset($_REQUEST['monte']) && ( $ordre > 1 ) )
+      $message = ( requete('pages',"UPDATE pages SET ordre = (2*$ordre-1-ordre) WHERE mat = ${r['mat']} AND ( ordre = $ordre OR ordre = ($ordre-1) )")
+      ) ? "La page <em>${r['nom']}</em> a bien été montée d'une place." : "La page <em>${r['nom']}</em> n'a pas pu être déplacée. Erreur MySQL n°".$mysqli->errno.', «'.$mysqli->error.'».';
+
+    // Déplacement vers le bas
+    elseif ( isset($_REQUEST['descend']) && ( $ordre < $r['max'] ) )
+      $message = ( requete('pages',"UPDATE pages SET ordre = (2*$ordre+1-ordre) WHERE mat = ${r['mat']} AND ( ordre = $ordre OR ordre = ($ordre+1) )")
+      ) ? "La page <em>${r['nom']}</em> a bien été descendue d'une place." : "La page <em>${r['nom']}</em> n'a pas pu être déplacée. Erreur MySQL n°".$mysqli->errno.', «'.$mysqli->error.'».';
+
+    // Suppression : possible uniquement hors page d'accueil, page n°1 sans matière associée (mat=0)
+    elseif ( isset($_REQUEST['supprime']) && ( $r['max']+$r['mat'] > 1 ) )
+      $message = ( requete('pages',"DELETE FROM pages WHERE id = $id")
+                && requete('infos',"DELETE FROM infos WHERE page = $id")
+                && requete('pages',"UPDATE pages SET ordre = (ordre-1) WHERE mat = ${r['mat']} AND ordre > $ordre")
+      ) ? "La page <em>${r['nom']}</em> a bien été supprimée, ainsi que les informations qu'elle comportait." : "La page <em>${r['nom']}</em> n'a pas pu être supprimée. Erreur MySQL n°".$mysqli->errno.', «'.$mysqli->error.'».';
+
+    // Suppression des informations
+    elseif ( isset($_REQUEST['vide']) )
+      $message = ( requete('infos',"DELETE FROM infos WHERE page = $id") ) ? "Toutes les informations de la page <em>${r['nom']}</em> ont bien été supprimées." : "Les informations de la page <em>${r['nom']}</em> n'ont pas pu être supprimées. Erreur MySQL n°".$mysqli->errno.', «'.$mysqli->error.'».';
+
+  }
+  $mysqli->query('ALTER TABLE pages ORDER BY mat,ordre');
+
+  // Passage en connexion MySQL en lecture seulement
+  $mysqli->close();
+  $mysqli = mysql_lecture();
+}
+
+////////////////////////////////////
+// Modifications des informations //
+////////////////////////////////////
+elseif ( isset($pid) && isset($_REQUEST['action']) && $_REQUEST['action'] == 'info' )  {
+  // Connexion à la base de données
+  $mysqli->close();
+  $mysqli = mysql_ecriture();
+  
+  // Vérification que l'identifiant est valide. Défaut : id=0 (nouvelle information)
+  if ( is_numeric($id = $_REQUEST['id']) && $id )  {
+    $resultat = $mysqli->query("SELECT id, ordre, cache, titre, texte, ( SELECT MAX(ordre) FROM infos WHERE page = $pid ) AS max
+                                FROM infos WHERE page = $pid AND id = $id");
+    if ( $resultat->num_rows )  {
+      $r = $resultat->fetch_assoc();
+      $resultat->free();
+    }
+    else
+      $id = 0;
+  }
+  else
+    $id = 0;
+
+  // Pour les informations récentes
+  $lien_recent = 'pages?'.addslashes($page['cle']);
+
+  // Traitement d'un ajout/modification
+  if ( isset($_REQUEST['modifie']) && strlen($_REQUEST['texte']) )  {
+
+    // Validation des données envoyées
+    $texte = $mysqli->real_escape_string($_REQUEST['texte']);
+    $titre = $mysqli->real_escape_string($_REQUEST['titre']);
+    $titre_recent = ( strlen($titre) ) ? $titre : 'Nouvelle information';
+
+    // Si $id > 0 : modification d'une information existante. Si $id = 0, nouvelle information
+    if ( $id )  {
+      if ( requete('infos',"UPDATE infos SET texte = '$texte', titre = '$titre' WHERE id = $id") )  {
+        $message = 'L\'information a bien été modifiée.';
+        // Ajout d'une information récente si information diffusée
+        if ( !$r['cache'] )
+          recent($mysqli,1,$id,"<img class=\"icone\" src=\"icones/info.png\"> $titre_recent",$lien_recent,$texte);
+      }
+      else
+        $message = 'L\'information n\'a pas pu être modifiée. Erreur MySQL n°'.$mysqli->errno.', «'.$mysqli->error.'».';
+    }
+    else  {
+      $cache = ( isset($_REQUEST['cache']) ) ? 1 : 0;
+      if ( requete('infos',"UPDATE infos SET ordre = (ordre+1) WHERE page = $pid")
+        && requete('infos',"INSERT INTO infos SET ordre = 1, page = $pid, texte = '$texte', titre = '$titre', cache = $cache") )  {
+          $message = 'L\'information a bien été ajoutée.';
+          // Ajout d'une information récente si information diffusée
+          if ( !$cache )
+            recent($mysqli,1,$mysqli->insert_id,"<img class=\"icone\" src=\"icones/info.png\"> $titre_recent",$lien_recent,$texte);
+          $mysqli->query('ALTER TABLE infos ORDER BY page,ordre');
+      }
+      else
+        $message = 'L\'information n\'a pas pu être ajoutée. Erreur MySQL n°'.$mysqli->errno.', «'.$mysqli->error.'».';
+    }
+  }
+
+  elseif ( $id )  {
+
+    // Déplacement vers le haut
+    if ( isset($_REQUEST['monte']) && ( $r['ordre'] > 1 ) )
+      $message = ( requete('infos',"UPDATE infos SET ordre = (2*${r['ordre']}-1-ordre) WHERE ( ordre = ${r['ordre']} OR ordre = (${r['ordre']}-1) ) AND page = $pid")
+                && $mysqli->query('ALTER TABLE infos ORDER BY page,ordre')
+      ) ? 'L\'information a bien été montée d\'une place.' : 'L\'information n\'a pas pu être déplacée. Erreur MySQL n°'.$mysqli->errno.', «'.$mysqli->error.'».';
+
+    // Déplacement vers le bas
+    elseif ( isset($_REQUEST['descend']) && ( $r['ordre'] < $r['max'] ) )
+      $message = ( requete('infos',"UPDATE infos SET ordre = (2*${r['ordre']}+1-ordre) WHERE ( ordre = ${r['ordre']} OR ordre = (${r['ordre']}+1) ) AND page = $pid")
+                && $mysqli->query('ALTER TABLE infos ORDER BY page,ordre')
+      ) ? 'L\'information a bien été descendue d\'une place.' : 'L\'information n\'a pas pu être déplacée. Erreur MySQL n°'.$mysqli->errno.', «'.$mysqli->error.'».';
+
+    // Positionnement "montré" (apparaît sur la partie publique)
+    elseif ( isset($_REQUEST['montre']) )  {
+      if ( requete('infos',"UPDATE infos SET cache = 0 WHERE id = $id") )  {
+        $message = 'L\'information a bien été diffusée, elle apparaît désormais sur la partie publique.';
+        // Ajout d'information récente
+        $titre = ( strlen($r['titre']) ) ? $mysqli->real_escape_string($r['titre']) : 'Nouvelle information';
+        recent($mysqli,1,$id,"<img class=\"icone\" src=\"icones/info.png\"> $titre_recent",$lien_recent,$mysqli->real_escape_string($r['texte']));
+      }
+      else
+        $message = 'L\'information n\'a pas pu être diffusée. Erreur MySQL n°'.$mysqli->errno.', «'.$mysqli->error.'».';
+    }
+
+    // Positionnement "caché" (n'apparaît pas sur la partie publique)
+    elseif ( isset($_REQUEST['cache']) )  {
+      if ( requete('infos',"UPDATE infos SET cache = 1 WHERE id = $id") )  {
+        $message = 'L\'information n\'est plus diffusée&nbsp;: elle n\'apparaît plus sur la partie publique mais est toujours disponible ici pour modification ou diffusion.';
+        // Suppression de l'éventuelle information récente
+        recent($mysqli,1,$id);
+      }
+      else
+        $message = 'L\'information n\'a pas pu être cachée. Erreur MySQL n°'.$mysqli->errno.', «'.$mysqli->error.'».';
+    }
+
+    // Suppression
+    elseif ( isset($_REQUEST['supprime']) || !strlen($_REQUEST['texte']) )  {
+      if ( requete('infos',"DELETE FROM infos WHERE id = $id") 
+        && requete('infos',"UPDATE infos SET ordre = (ordre-1) WHERE ordre > ${r['ordre']} AND page = $pid") )  {
+        $message = 'L\'information a bien été supprimée.';
+        // Suppression de l'éventuelle information récente
+        recent($mysqli,1,$id);
+      }
+      else
+        $message = 'L\'information n\'a pas pu être supprimée. Erreur MySQL n°'.$mysqli->errno.', «'.$mysqli->error.'».';
+    }
+  }
+
+  // Passage en connexion MySQL en lecture seulement
+  $mysqli->close();
+  $mysqli = mysql_lecture();
+}
+
+//////////
+// HTML //
+//////////
+// Haut de page, menu et message
+if ( isset( $pid ) )  {
+  $p = "pages?page=${page['cle']}";
+  $t = "Modification de la page «&nbsp;${page['nom']}&nbsp;»";
+  include('haut.php');
+?>
+
+  <div class="item aide">
+    <h3>Aide et explications</h3>
+    <p>Vous pouvez ci-dessous modifier la page &laquo;&nbsp;<?php echo $page['nom']; ?>&nbsp;&raquo;, ajouter une information ou modifier les informations existantes.</p>
+    <p>Pour supprimer cette page (et automatiquement toutes les informations qu'elle contient), il faut aller sur la page <a href="pages">Gestion des pages</a>.</p>
+    <p>L'ordre d'affichage des informations dans la page peut être modifié en cliquant sur les flèches.</p>
+    <h4>Préférences associées à cette page</h4>
+    <p>Concernant cette page, vous pouvez modifier&nbsp;:</p>
+    <ul>
+      <li>le <em>titre</em> qui sera affiché en haut de page et dans la barre de titre du navigateur. Par exemple, «&nbsp;À propos de l'ADS et du TIPE&nbsp;».</li>
+      <li>le <em>nom dans le menu</em> qui est affiché dans le menu en tant que lien vers la page. Il est préférable qu'il rentre sur une ligne, il faut donc le choisir assez court. Par exemple, «&nbsp;Informations ADS/TIPE&nbsp;».</li>
+      <li>la <em>clé dans l'adresse</em> qui est un mot-clé qui est utilisé uniquement dans l'adresse de la page. Par convention, il vaut mieux que ce soit un mot unique, court et sans majuscule. Par exemple, «&nbsp;ads-tipe&nbsp;».</li>
+      <li>la <em>matière associée</em> qui conditionne la place du lien dans le menu de la partie publique. Vous ne pouvez associer à cette page qu'une matière déjà associée à votre compte. Pour modifier les matières qui vous sont associées, il faut aller dans vos <a href="prefs">Préférences</a>.</li>
+      <li>le <em>texte de début</em>, qui sera affiché au-dessus des informations de la page. Il s'agit d'une ou deux phrases maximum. Il n'est affiché que si la page contient des informations. Cette case peut être laissée vide.</li>
+    </ul>
+    <p>L'<em>accès</em> à cette page peut être choisi parmi quatre possibilités&nbsp;:</p>
+    <ul>
+      <li><em>Visible de tous</em>&nbsp;: accessible de tout visiteur, sans identification.</li>
+      <li><em>Visible pour les élèves, colleurs, professeurs</em>&nbsp;: accessible uniquement par tout utilisateur après identification. Il faut donc avoir un compte validé (voir l'aide de la page <a href="utilisateurs">Utilisateurs</a>) pour y accéder.</li>
+      <li><em>Visible pour les colleurs, professeurs</em>&nbsp;: accessible uniquement par les utilisateurs de type colleur ou professeur.</li>
+      <li><em>Visible pour les professeurs</em>&nbsp;: accessible uniquement par les utilisateurs de type professeur (vous et vos collègues).</li>
+    </ul>
+    <p>Quel que soit ce choix, le lien dans le menu de la partie publique apparaîtra pour tout visiteur (identifié ou non).</p>
+    <h4>Suppression des informations d'une page</h4>
+    <p>Si la page contient une ou plusieurs informations, il est possible de toutes les supprimer (vider la page, sans la supprimer). Ce bouton apparaît dans les préférences de la page.</p>
+    <h4>Informations</h4>
+    <p>Le <em>titre</em> sera affiché, un peu plus gros, au-dessus de l'information. Il peut rester vide (il n'y aura alors pas de titre affiché).</p>
+    <p>Le texte doit être formaté en HTML&nbsp;: par exemple, chaque bloc de texte doit être encadré par &lt;p&gt; et &lt;/p&gt;. Il peut donc contenir des liens vers les autres pages du site, vers des documents du site, vers le web... N'hésitez pas à consulter l'aide qui apparaîtra en-dessous de la case de texte après avoir cliqué dessus, ainsi qu'à utiliser les boutons fournis.</p>
+    <p>La case à cocher <em>Ne pas diffuser sur la partie publique</em> permet de cacher temporairement cette information, par exemple pour la diffuser ultérieurement.</p>
+  </div>
+
+<?php
+}
+else  {
+  $p = "pages";
+  $t = 'Modifications des pages d\'informations';
+  include('haut.php');
+?>
+
+  <div class="item aide">
+    <h3>Aide et explications</h3>
+    <p>Vous pouvez ci-dessous ajouter une page d'information ou modifier les pages existantes. Les pages du <em>Menu général</em> sont affichées, sur la partie publique, en haut du menu. Les pages associées à une matière donnée y apparaissent au niveau de la matière.</p>
+    <p>La première page ne peut pas être déplacée dans une matière ni supprimée, car c'est la page d'accueil de la partie publique. Il est cependant tout à fait possible de modifier son <em>titre</em>, son <em>nom dans le menu</em> ou sa <em>clé dans l'adresse</em>. Pour information, le <em>titre</em> de la page d'accueil est repris à plusieurs endroits (page de connexion, page du flux RSS...) et est aussi modifiable sur la page <a href="site">Titre du site</a>.</p>
+    <p>L'ordre d'affichage des pages dans le menu de la partie publique peut être modifié en cliquant sur les flèches.</p>
+    <h4>Préférences associées à chaque page</h4>
+    <p>Pour chaque page, vous pouvez modifier&nbsp;:</p>
+    <ul>
+      <li>le <em>titre</em> qui sera affiché en haut de page et dans la barre de titre du navigateur. Par exemple, «&nbsp;À propos de l'ADS et du TIPE&nbsp;».</li>
+      <li>le <em>nom dans le menu</em> qui est affiché dans le menu en tant que lien vers la page. Il est préférable qu'il rentre sur une ligne, il faut donc le choisir assez court. Par exemple, «&nbsp;Informations ADS/TIPE&nbsp;».</li>
+      <li>la <em>clé dans l'adresse</em> qui est un mot-clé qui est utilisé uniquement dans l'adresse de la page. Par convention, il vaut mieux que ce soit un mot unique, court et sans majuscule. Par exemple, «&nbsp;ads-tipe&nbsp;».</li>
+      <li>la <em>matière associée</em> qui conditionne la place du lien dans le menu de la partie publique. Vous ne pouvez associer à une nouvelle page qu'une matière déjà associée à votre compte. Pour modifier les matières qui vous sont associées, il faut aller dans vos <a href="prefs">Préférences</a>.</li>
+      <li>le <em>texte de début</em>, qui sera affiché au-dessus des informations de la page. Il s'agit d'une ou deux phrases maximum. Il n'est affiché que si la page contient des informations. Cette case peut être laissée vide.</li>
+    </ul>
+    <p>L'<em>accès</em> à chaque page peut être choisi parmi quatre possibilités&nbsp;:</p>
+    <ul>
+      <li><em>Visible de tous</em>&nbsp;: accessible de tout visiteur, sans identification.</li>
+      <li><em>Visible pour les élèves, colleurs, professeurs</em>&nbsp;: accessible uniquement par tout utilisateur après identification. Il faut donc avoir un compte validé (voir l'aide de la page <a href="utilisateurs">Utilisateurs</a>) pour y accéder.</li>
+      <li><em>Visible pour les colleurs, professeurs</em>&nbsp;: accessible uniquement par les utilisateurs de type colleur ou professeur.</li>
+      <li><em>Visible pour les professeurs</em>&nbsp;: accessible uniquement par les utilisateurs de type professeur (vous et vos collègues).</li>
+    </ul>
+    <p>Quel que soit ce choix, le lien de chaque page dans le menu de la partie publique apparaîtra pour tout visiteur (identifié ou non).</p>
+    <h4>Suppression d'une page</h4>
+    <p>La suppression d'une page entraîne automatiquement la suppression de toutes les informations qui y étaient inscrites. La suppression n'est pas possible pour la première page du <em>Menu général</em>.</p>
+    <h4>Suppression des informations d'une page</h4>
+    <p>Si la page contient une ou plusieurs informations, il est possible de toutes les supprimer (vider la page, sans la supprimer).</p>
+  </div>
+
+<?php
+}
+
+// Select sur les matières
+$select_matieres = '<option value="O">Pas de matière associée</option>';
+foreach ( $matieres as $m )
+  $select_matieres .= "\n        <option value=\"${m['id']}\">${m['nom']}</option>";
+// Select sur la protection
+$select_protection = '
+        <option value="0">Visible de tous</option>
+        <option value="1">Visible pour les élèves, colleurs, profs</option>
+        <option value="2">Visible pour les colleurs et les profs</option>
+        <option value="3">Visible pour les profs uniquement</option>';
+
+// Fonction d'affichage des pages
+function affichage_page($r)  {
+  $id = $r['id'];
+  // Identifiant unique pour les champs du formulaire
+  $fid = "${r['mat']}$id";
+  // Si l'identifiant est nul, formulaire de nouvelle page
+  // Si l'identifiant est positif, on est sur la gestion des pages
+  // Si l'identifiant est négatif, on est sur la gestion de la page d'identifiant $pid
+  if ( $id == 0 )  {
+    $titre = 'Nouvelle page';
+    $valide1 = "\n    <input class=\"bouton\" type=\"submit\" name=\"modifie\" value=\"Valider\" title=\"Valider les modifications\">";
+    $valide = $monte = $descend = $suppr = $vide = $disabled = '';
+    // Présélections sur les select
+    $select_matieres = $GLOBALS['select_matieres'];  
+    $select_protection = $GLOBALS['select_protection'];
+  }
+  else  {
+    if ( $id > 0 )  {
+      $titre = "Page n°${r['ordre']}&nbsp;: ${r['nom']}";
+      $valide1 = '';
+      $valide = "\n      <input type=\"submit\" name=\"modifie\" value=\"Valider\" title=\"Valider les modifications\">";
+      $monte = ( $r['ordre'] > 1 ) ? "\n      <input type=\"submit\" name=\"monte\" value=\"&uarr;\" title=\"Remonter la page dans l'ordre d'apparition\">" : '';
+      $descend = ( $r['ordre'] < $GLOBALS['max'] ) ? "\n      <input type=\"submit\" name=\"descend\" value=\"&darr;\" title=\"Descendre la page dans l'ordre d'apparition\">" : '';
+      $suppr = ( $GLOBALS['max']+$r['mat'] > 1 ) ? "\n      <input type=\"submit\" name=\"supprime\" value=\"Supprimer\" title=\"Supprimer la page\">" : '';
+      $disabled = ( $r['ordre']+$r['mat'] == 1 ) ? ' disabled' : '';
+    }
+    else  {
+      $titre = 'Modifier les préférences de la page';
+      $valide1 = "\n    <input class=\"bouton\" type=\"submit\" name=\"modifie\" value=\"Valider\" title=\"Valider les modifications\">";
+      $valide = $monte = $descend = $suppr = '';
+      $id = -$r['id'];
+      $disabled = ( $r['ordre']+$r['mat'] == 1 ) ? ' disabled' : '';
+    }
+    // Bouton de suppression des informations
+    $vide = ( $r['n'] ) ? "\n    <p class=\"ligne\">Cette page contient ${r['n']} information".( ( $r['n'] > 1 ) ? 's' : '' ).".<input type=\"submit\" name=\"vide\" value=\"Supprimer toutes les informations de cette page\"></p>" : '';
+    // Présélections sur les select
+    $select_matieres = str_replace("\"${r['mat']}\"","\"${r['mat']}\" selected",$GLOBALS['select_matieres']);  
+    $select_protection = str_replace("\"${r['protection']}\"","\"${r['protection']}\" selected",$GLOBALS['select_protection']);  
+  }
+  // Affichage du formulaire
+  echo <<<FIN
+  <div class="item admin">
+  <form action="${GLOBALS['p']}" method="post">$valide1
+    <h3>$titre</h3>
+    <p class="boutons">$valide$monte$descend$suppr
+    </p>
+    <p class="ligne"><label for="titre$fid">Titre&nbsp;: </label><input type="text" id="titre$fid" name="titre" value="${r['titre']}" size="80"></p>
+    <p class="ligne"><label for="nom$fid">Nom dans le menu&nbsp;: </label><input type="text" id="nom$fid" name="nom" value="${r['nom']}" size="50"></p>
+    <p class="ligne"><label for="cle$fid">Clé dans l'adresse&nbsp;: </label><input type="text" id="cle$fid" name="cle" value="${r['cle']}" size="30"></p>
+    <p class="ligne"><label for="mat$fid">Matière associée&nbsp;: </label>
+      <select id="mat$fid" name="mat"$disabled>
+        $select_matieres
+      </select>
+    </p>
+    <p class="ligne"><label for="bandeau$fid">Texte de début&nbsp;:</label></p>
+    <textarea class="nojs" id="bandeau$fid" name="bandeau" rows="2" cols="100">${r['bandeau']}</textarea>
+    <p class="ligne"><label for="protection">Accès&nbsp;: </label>
+      <select id="protection" name="protection">$select_protection
+      </select>
+    </p>$vide
+    <input type="hidden" name="id" value="$id">
+    <input type="hidden" name="action" value="page">
+  </form>
+  </div>
+
+
+FIN;
+}
+
+// Fonction d'affichage des infos
+function affichage_info($r)  {
+  if ( $id = $r['id'] )  {
+    if ( $r['cache'] )  {
+      $diff = ' (information non diffusée sur la partie publique)';
+      $cache_classe = ' cache';
+      $cache = "\n      <input type=\"submit\" name=\"montre\" value=\"Montrer\" title=\"Diffuser l'information, la rendre visible sur la partie publique\">";
+    }
+    else  {
+      $cache_classe = $diff = '';
+      $cache = "\n      <input type=\"submit\" name=\"cache\" value=\"Cacher\" title=\"Ne plus diffuser l'information, la rendre invisible sur la partie publique\">";
+    }
+    
+    $titre = "Information n°${r['ordre']}&nbsp;: ".( ( strlen($r['titre']) ) ? $r['titre'] : '[Information sans titre]' ).$diff;
+    $valide1 = $cachecheck = '';
+    $valide = "\n      <input type=\"submit\" name=\"modifie\" value=\"Valider\" title=\"Valider les modifications sur le titre ou le texte\">";
+    $monte = ( $r['ordre'] > 1 ) ? "\n      <input type=\"submit\" name=\"monte\" value=\"&uarr;\" title=\"Remonter l'information dans l'ordre d'apparition\">" : '';
+    $descend = ( $r['ordre'] < $GLOBALS['max'] ) ? "\n      <input type=\"submit\" name=\"descend\" value=\"&darr;\" title=\"Descendre l'information dans l'ordre d'apparition\">" : '';
+    $suppr = "\n      <input type=\"submit\" name=\"supprime\" value=\"Supprimer\" title=\"Supprimer l'information\">";
+  }
+  else  {
+    $titre = 'Ajouter une nouvelle information';
+    $valide1 = "\n    <input class=\"bouton\" type=\"submit\" name=\"modifie\" value=\"Valider\" title=\"Valider les modifications\">";
+    $valide = $monte = $descend = $suppr = $cache_classe = $diff = $cache = '';
+    $cachecheck = "\n    <p class=\"ligne\"><label for=\"cache\">Ne pas diffuser sur la partie publique&nbsp;: </label><input type=\"checkbox\" id=\"cache\" name=\"cache\" value=\"1\"></p>";
+  }
+  echo <<<FIN
+  <div class="item admin$cache_classe">
+  <form action="${GLOBALS['p']}" method="post">$valide1
+    <h3>$titre</h3>
+    <p class="boutons">$valide$monte$descend$cache$suppr
+    </p>
+    <p class="transparent"><input class="ligne" type="text" name="titre" size=50 maxlength=65533 value="${r['titre']}"></p>
+    <textarea name="texte" class="mat${GLOBALS['mid']}" rows="10" cols="100">${r['texte']}</textarea>$cachecheck
+    <input type="hidden" name="id" value="${r['id']}">
+    <input type="hidden" name="action" value="info">
+  </form>
+  </div>
+
+
+FIN;
+}
+
+// Gestion des pages
+if ( !isset($pid) )  {
+  // Pour chaque matière
+  $matieres = array(-1=>array('id'=>0,'nom'=>'Menu général'))+$matieres;
+  foreach ( $matieres as $m )  {
+    echo "  <h2>${m['nom']}</h2>\n\n";
+    // Formulaire vide pour une nouvelle page et aide générale
+    affichage_page(array('id' => 0, 'cle' => 'cle_de_la_page', 'nom' => 'Nom de la page', 'titre' => 'Titre de la page', 'bandeau' => 'Texte (éventuel) marqué en haut de la page', 'mat' => $m['id']));
+    // Affichage des pages existantes
+    $resultat = $mysqli->query("SELECT p.id, p.ordre, p.cle, p.nom, p.mat, p.titre, p.bandeau, p.protection, COUNT(i.id) AS n
+                                FROM pages AS p LEFT JOIN infos AS i ON i.page = p.id WHERE p.mat = ${m['id']} GROUP BY p.id");
+    if ( $max = $resultat->num_rows )  {
+      while ( $r = $resultat->fetch_assoc() )
+        affichage_page($r);
+      $resultat->free();
+    }
+  }
+}
+
+// Gestion des informations
+else  {
+  // Formulaire de modification de la page
+  $resultat = $mysqli->query("SELECT (-p.id) AS id, p.ordre, p.cle, p.nom, p.mat, p.titre, p.bandeau, p.protection, COUNT(i.id) AS n
+                              FROM pages AS p LEFT JOIN infos AS i ON i.page = p.id WHERE p.id = $pid GROUP BY p.id");
+  $r = $resultat->fetch_assoc();
+  affichage_page($r);
+  $mid = $r['mat'];
+  $resultat->free();
+  
+  // Formulaire de nouvelle information
+  affichage_info(array('id' => 0, 'cache' => 0, 'titre' => 'Titre de l\'information', 'texte' => ''));
+  
+  // Formulaire de modification des informations
+  $resultat = $mysqli->query("SELECT id, ordre, cache, titre, texte FROM infos WHERE page = $pid");
+  if ( $max = $resultat->num_rows )  {
+    while ( $r = $resultat->fetch_assoc() )
+      affichage_info($r);
+    $resultat->free();
+  }
+  else
+    echo "  <h2>Il n'y a actuellement aucune information enregistrée pour cette page.</h2>\n\n";
+}
+
+$mysqli->close();
+
+// Bas de page
+include('bas.php');
+?>
diff -urN cahier-de-prepa3.2.0/admin/planning.php cahier-de-prepa4.0.0/admin/planning.php
--- cahier-de-prepa3.2.0/admin/planning.php	1970-01-01 01:00:00.000000000 +0100
+++ cahier-de-prepa4.0.0/admin/planning.php	2014-08-26 22:21:24.409004591 +0200
@@ -0,0 +1,127 @@
+<?php
+// Sécurité et récupération des données de configuration
+define('OK',1);
+include('debut.php'); // fournit $mysqli et $matieres
+
+///////////////////
+// Modifications //
+///////////////////
+if ( isset($_REQUEST['modifie']) )  {
+
+  // Connexion à la base de données avec les droits d'écriture
+  $mysqli->close();
+  $mysqli = mysql_ecriture();
+  
+  // Récupérations des semaines
+  $resultat = $mysqli->query('SELECT id, colle, vacances, DATE_FORMAT(debut,\'%d/%m/%Y\') AS debut FROM semaines');
+  $modif = array();
+  $colles = ( isset($_REQUEST['colles']) ) ? $_REQUEST['colles'] : array();
+  $vacances = $_REQUEST['vacances'];
+
+  // Validation des données envoyées et modifications
+  while ( $r = $resultat->fetch_assoc() )  {
+    if ( !is_numeric($v = $vacances[$r['id']]) )
+      $v = 0;
+    $c = ( isset($colles[$r['id']]) && !$v ) ? 1 : 0;
+    if ( ( $c != $r['colle'] ) || ( $v != $r['vacances'] ) )  {
+      requete('semaines',"UPDATE semaines SET colle = $c, vacances = $v WHERE id = ${r['id']}");
+      $modif[] = "semaine du ${r['debut']}";
+    }
+  }
+  
+  // Message à afficher
+  if ( empty($modif) )
+    $message = 'Aucune modification à effectuer.';
+  else
+    $message = ( empty($modif) ) ? 'Aucune modification à effectuer.' : 'Les modifications ('.implode(',',$modif).') ont bien été réalisées.';
+
+  // Passage en connexion MySQL en lecture seulement
+  $mysqli->close();
+  $mysqli = mysql_lecture();
+}
+
+//////////
+// HTML //
+//////////
+// Haut de page, menu et message
+$p = "semaines";
+$t = 'Modifications des semaines du planning annuel';
+include('haut.php');
+
+// Affichage de l'aide générale et du haut du tableau
+?>
+  
+  <div class="item aide">
+    <h3>Aide et explications</h3>
+    <p>Vous pouvez ci-dessous modifier le planning annuel, c'est-à-dire pour chaque semaine de l'année, préciser s'il s'agit&nbsp;:
+    <ul>
+      <li>d'une semaine de colle (case <em>Colle ou non</em> cochée), qui pourra recevoir des programmes de colles et des notes de colles.</li>
+      <li>d'une semaine sans colle (case <em>Colle ou non</em> décochée), qui ne pourra recevoir ni programmes de colles, ni notes de colles.</li>
+      <li>d'une semaine de vacances (colonne <em>Vacances</em>) qui ne pourra recevoir ni cahier de texte, ni programmes de colles, ni notes de colles.</li>
+    </ul>
+    <p>Les vacances de deux semaines (toutes en fait :-) ) sont donc à marquer deux fois.</p>
+    <p>Il est préférable de décocher la case <em>Colle ou non</em> lorsque l'on sait qu'il n'y aura pas de colle, comme souvent en début ou en fin d'année&nbsp;: cela modifie l'affichage des programmes de colles (&laquo;&nbsp;Il n'y a pas de colle cette semaine&nbsp;&raquo; au lieu de &laquo;&nbsp;Le programme de colles de cette semaine n'est pas défini.&nbsp;&raquo;), et évite les erreurs d'écriture des programmes.</p>
+  </div>
+
+  <div class="item admin nojs">
+    <form action="" method="post">
+      <input type="submit" class="bouton" name="modifie" value="Valider" title="Valider les modifications">
+      <h3>Modifier le planning annuel</h3>
+      <table class="admin">
+        <thead>
+          <tr>
+            <th>Début de semaine</th>
+            <th>Colle ou non</th>
+            <th>Vacances</th>
+          </tr>
+        </thead>
+        <tbody>
+<?php
+// Récupération et affichage des matières
+$semaine = array('Dimanche','Lundi','Mardi','Mercredi','Jeudi','Vendredi','Samedi');
+$resultat = $mysqli->query('SELECT id, DATE_FORMAT(debut,\'%w\') AS jour, DATE_FORMAT(debut,\'%d/%m/%Y\') AS debut, IF(colle=1,\' checked\',\'\') AS colle, vacances FROM semaines');
+$mysqli->close();
+while ( $r = $resultat->fetch_assoc() )  {
+  $select = str_replace("\"${r['vacances']}\"","\"${r['vacances']}\" selected",'<option value="0">Période scolaire</option><option value="1">Vacances de Toussaint</option><option value="2">Vacances de Noël</option><option value="3">Vacances d\'hiver</option><option value="4">Vacances de printemps</option>');
+  $r['jour'] = $semaine[$r['jour']];
+  echo <<<FIN
+          <tr>
+            <td>${r['jour']} ${r['debut']}</td>
+            <td><input type="checkbox" name="colles[${r['id']}]" value="1"${r['colle']}></td>
+            <td><select name="vacances[${r['id']}]">$select</select></td>
+          </tr>
+
+FIN;
+}
+$resultat->free();
+
+// Fin du formulaire
+?>
+        </tbody>
+      </table>
+
+    </form>
+  </div>
+
+  <script type="text/javascript">
+$( function() {
+  $('select').change(function () {
+    if ( $(this).val() == 0 )  {
+      $(this).parent().prev().children('input').prop('checked',true);
+    }
+    else  {
+      $(this).parent().prev().children('input').prop('checked',false);
+    }
+  });
+  $('input:checkbox').change(function () {
+    if ( $(this).is(':checked') )  {
+      $(this).parent().next().children('select').val(0);
+    }
+  });
+});
+  </script>
+
+<?php
+// Bas de page
+include('bas.php');
+?>
diff -urN cahier-de-prepa3.2.0/admin/prefs.php cahier-de-prepa4.0.0/admin/prefs.php
--- cahier-de-prepa3.2.0/admin/prefs.php	1970-01-01 01:00:00.000000000 +0100
+++ cahier-de-prepa4.0.0/admin/prefs.php	2014-08-26 22:21:02.293003883 +0200
@@ -0,0 +1,484 @@
+<?php
+// Sécurité et récupération des données de configuration
+define('OK',1);
+include('debut.php'); // fournit $mysqli et $matieres
+
+// Récupération des données de l'utilisateur
+$resultat = $mysqli->query("SELECT id, login, nom, prenom, genre, mail, mdp, matieres, timeout, mailexp, mailcopy
+                            FROM utilisateurs WHERE id = ${_SESSION['id']}");
+$u = $resultat->fetch_assoc();
+$resultat->free();
+
+///////////////////
+// Modifications //
+///////////////////
+if ( isset($_REQUEST['modifie']) )  {
+  
+  // Vérification du mot de passe
+  if ( ( sha1($_REQUEST['mdp0']) != $u['mdp'] ) && ( ( $u['mdp'] != 'pas de mot de passe' ) || ( $_REQUEST['action'] != 'mdp' ) ) )
+    $message = 'Le mot de passe fourni est incorrect. Les modifications n\'ont pas été effectuées.';
+  else  {
+
+    $requete = array();
+    $modif = array();
+    
+    // Validation des données envoyées
+    switch ( $_REQUEST['action'] )  {
+      case 'identite':
+        $login = $mysqli->real_escape_string($_REQUEST['login']);
+        $prenom = mb_convert_case($mysqli->real_escape_string($_REQUEST['prenom']),MB_CASE_TITLE,'UTF-8');
+        $nom = mb_convert_case($mysqli->real_escape_string($_REQUEST['nom']),MB_CASE_TITLE,'UTF-8');
+        $mail = strtolower($mysqli->real_escape_string($_REQUEST['mail']));
+        if ( in_array($genre = $_REQUEST['genre'],array(1,2,3)) && ( $genre != $u['genre'] ) )  {
+          $u['genre'] = $genre;
+          $requete[] = "genre = $genre";
+          $modif[] = 'genre';
+        }
+        if ( strlen($login) && ( $login != $u['login'] ) )  {
+          // Vérification que le login n'existe pas déjà
+          $resultat = $mysqli->query("SELECT id FROM utilisateurs WHERE login = '$login'");
+          if ( $resultat->num_rows )
+            $resultat->free();
+          else  {
+            $u['login'] = $login;
+            $requete[] = "login = '$login'";
+            $modif[] = 'identifiant';
+            $_SESSION['login'] = $login;
+          }
+        }
+        if ( strlen($nom) && ( $nom != $u['nom'] ) )  {
+          $u['nom'] = $nom;
+          $requete[] = "nom = '$nom'";
+          $modif[] = 'nom';
+        }
+        if ( strlen($prenom) && ( $prenom != $u['prenom'] ) )  {
+          $u['prenom'] = $prenom;
+          $requete[] = "prenom = '$prenom'";
+          $modif[] = 'prénom';
+        }
+        if ( ( $mail != $u['mail'] ) && ( filter_var($mail,FILTER_VALIDATE_EMAIL) || !strlen($mail) ) )  {
+          $u['mail'] = $mail;
+          $requete[] = "mail = '$mail'";
+          $modif[] = 'mail';
+        }
+        break;
+      
+      case 'mdp':
+        if ( strlen($mdp1 = $_REQUEST['mdp1']) && ( $mdp1 == $_REQUEST['mdp2'] ) && ( ( $mdp1 = sha1($mdp1) ) != $u['mdp'] ) )  {
+          $u['mdp'] = $mdp1;
+          $requete[] = "mdp = '$mdp1'";
+          $modif[] = 'mot de passe';
+        }
+        break;
+
+      case 'params':
+        $mailcopy = ( isset($_REQUEST['mailcopy']) ) ? 1 : 0;
+        if ( is_numeric( $timeout = $_REQUEST['timeout'] ) && ( $timeout != $u['timeout'] ) )  {
+          if ( $timeout < 60 )
+            $timeout = 60;
+          $u['timeout'] = $timeout;
+          $requete[] = "timeout = $timeout";
+          $modif[] = 'temps de connexion';
+          $_SESSION['timeout'] = $timeout;
+        }
+        if ( in_array($mailexp = $_REQUEST['mailexp'],array(1,2,3,4)) && ( $mailexp != $u['mailexp'] ) )  {
+          $u['mailexp'] = $mailexp;
+          $requete[] = "mailexp = $mailexp";
+          $modif[] = 'champ expéditeur des mails';
+        }
+        if ( $mailcopy != $u['mailcopy'] )  {
+          $u['mailcopy'] = $mailcopy;
+          $requete[] = "mailcopy = '$mailcopy'";
+          $modif[] = 'mise en copie des mails';
+        }
+        break;
+    
+      case 'matieres':
+        $m = ( isset($_REQUEST['matieres']) ) ? array_filter(array_unique($_REQUEST['matieres'],SORT_NUMERIC),'is_numeric') : array();
+        if ( ( $m = implode(',',$m) ) != $u['matieres'] )  {
+          $requete[] = "matieres = '$m'";
+          $modif[] = 'matières';
+          $_SESSION['matieres'] = $m;
+          // Modification de $matieres, définie par debut.php et utilisée notamment pour le menu (haut.php)
+          $resultat = $mysqli->query("SELECT id, cle, nom FROM matieres WHERE FIND_IN_SET(id,'$m') ORDER BY FIND_IN_SET(id,'$m')");
+          $matieres = array();
+          if ( $resultat->num_rows )  {
+            while ( $r = $resultat->fetch_assoc() )
+              $matieres[] = $r;
+            $resultat->free();
+          }
+        }
+        break;
+    }
+
+    if ( empty($requete) )
+      $message = 'Aucune modification à effectuer.';
+    else  {
+      $requete = implode(', ',$requete);
+      $modif = implode(', ',$modif);
+      // Connexion à la base de données avec les droits d'écriture
+      $mysqli->close();
+      $mysqli = mysql_ecriture();
+      if ( requete('utilisateurs',"UPDATE utilisateurs SET $requete WHERE id = ${u['id']}") )
+        $message = "Les modifications ($modif) ont bien été réalisées.";
+    }
+  }
+
+  // Passage en connexion MySQL en lecture seulement
+  $mysqli->close();
+  $mysqli = mysql_lecture();
+}  
+
+//////////////////////////////////////////////////////////////////
+// Modifications d'une matière : données, suppressions massives //
+//////////////////////////////////////////////////////////////////
+elseif ( isset($_REQUEST['id']) && in_array($id = $_REQUEST['id'],explode(',',$_SESSION['matieres'])) )  {
+
+  // Vérification du mot de passe
+  if ( sha1($_REQUEST['mdp0']) != $u['mdp'] )
+    $message = 'Le mot de passe fourni est incorrect. Les modifications n\'ont pas été effectuées.';
+  else  {
+    // Récupération des données de la matière à modifier
+    $resultat = $mysqli->query("SELECT cle, nom, colles_protection, cdt_protection FROM matieres WHERE id = $id");
+    $r = $resultat->fetch_assoc();
+    // Connexion à la base de données avec les droits d'écriture
+    $mysqli->close();
+    $mysqli = mysql_ecriture();
+
+    /////////////////////////////////////////////
+    // Modifications des données d'une matière //
+    /////////////////////////////////////////////
+    if ( isset($_REQUEST['modifie_matiere']) )  {
+      $requete = array();
+      $modif = array();
+
+      // Validation des données envoyées
+      $cle = $mysqli->real_escape_string($_REQUEST['cle']);
+      $nom = $mysqli->real_escape_string($_REQUEST['nom']);
+      if ( strlen($nom) && ( $nom != $r['nom'] ) )  {
+        $requete[] = "nom = '$nom'";
+        $modif[] = 'nom';
+        requete('reps',"UPDATE reps SET nom = '$nom' WHERE matiere = $id AND parent = 0");
+      }
+      if ( strlen($cle) && ( $cle != $r['cle'] ) )  {
+        $requete[] = "cle = '$cle'";
+        $modif[] = 'clé';
+      }
+      if ( in_array($colles_protection = $_REQUEST['colles_protection'],array(0,1,2,3)) && ( $colles_protection != $r['colles_protection'] ) )  {
+        $requete[] = "colles_protection = $colles_protection";
+        $modif[] = 'accès aux programmes de colles';
+      }
+      if ( in_array($cdt_protection = $_REQUEST['cdt_protection'],array(0,1,2,3)) && ( $cdt_protection != $r['cdt_protection'] ) )  {
+        $requete[] = "cdt_protection = $cdt_protection";
+        $modif[] = 'accès au cahier de texte';
+      }
+     
+      if ( empty($requete) )
+        $message = 'Aucune modification à effectuer.';
+      else  {
+        $requete = implode(', ',$requete);
+        $modif = implode(', ',$modif);
+        if ( requete('matieres',"UPDATE matieres SET $requete WHERE id = $id") )
+          $message = "Les modifications ($modif) sur la matière &laquo;&nbsp;${r['nom']}&nbsp;&raquo; ont bien été réalisées.";
+        // Modification de $matieres, définie par debut.php et utilisée notamment pour le menu (haut.php)
+        $resultat = $mysqli->query("SELECT id, cle, nom FROM matieres
+                                    WHERE FIND_IN_SET(id,'${_SESSION['matieres']}') ORDER BY FIND_IN_SET(id,'${_SESSION['matieres']}')");
+        $matieres = array();
+        if ( $resultat->num_rows )  {
+          while ( $r = $resultat->fetch_assoc() )
+            $matieres[] = $r;
+          $resultat->free();
+        }
+      }
+    }
+
+    ////////////////////////////////////////////////////////
+    // Suppression des programmes de colles d'une matière //
+    ////////////////////////////////////////////////////////
+    elseif ( isset($_REQUEST['supprime_colles']) )  {
+      if ( requete('colles',"DELETE FROM colles WHERE matiere = $id") )  {
+        requete('matieres',"UPDATE matieres SET colles = 0 WHERE id = $id");
+        $message = "Tous les programmes de colles de la matière &laquo;&nbsp;${r['nom']}&nbsp;&raquo; ont bien été supprimés.";
+      }
+    }
+    
+    //////////////////////////////////////////////////
+    // Suppression du cahier de texte d'une matière //
+    //////////////////////////////////////////////////
+    elseif ( isset($_REQUEST['supprime_cdt']) )  {
+      if ( requete('cdt',"DELETE FROM cdt WHERE matiere = $id") )  {
+        requete('matieres',"UPDATE matieres SET cdt = 0 WHERE id = $id");
+        $message = "L'ensemble du contenu du cahier de texte de la matière &laquo;&nbsp;${r['nom']}&nbsp;&raquo; a bien été supprimé.";
+      }
+    }
+
+    /////////////////////////////////////////////
+    // Suppression des documents d'une matière //
+    /////////////////////////////////////////////
+    elseif ( isset($_REQUEST['supprime_docs']) )  {
+      if ( requete('reps',"DELETE FROM reps WHERE matiere = $id AND parent > 0") )  {
+        $resultat = $mysqli->query("SELECT GROUP_CONCAT(CONCAT('../documents/',lien) SEPARATOR ' ') AS chemins FROM docs WHERE matiere = $id");
+        $p = $resultat->fetch_assoc();
+        $resultat->free();
+        // Suppression physique ( $r['chemin'] est "null" si pas de document à supprimer)
+        if ( $p['chemins'] )
+          exec("rm -rf ${p['chemins']}");
+        requete('docs',"DELETE FROM docs WHERE matiere = $id");
+        requete('matieres',"UPDATE matieres SET docs = 0 WHERE id = $id");
+        $message = "Tous les documents de la matière &laquo;&nbsp;${r['nom']}&nbsp;&raquo; ont bien été supprimés.";
+      }
+    }
+    
+    /////////////////////////////////////////
+    // Suppression des notes d'une matière //
+    /////////////////////////////////////////
+    elseif ( isset($_REQUEST['supprime_notes']) )  {
+      if ( requete('notes',"DELETE FROM notes WHERE matiere = $id") )  {
+        requete('matieres',"UPDATE matieres SET notes = 0 WHERE id = $id");
+      $message = "Toutes les notes de la matière &laquo;&nbsp;${r['nom']}&nbsp;&raquo; ont bien été supprimées.";
+      }
+    }
+  }
+
+  // Passage en connexion MySQL en lecture seulement
+  $mysqli->close();
+  $mysqli = mysql_lecture();
+}
+
+
+//////////////
+//// HTML ////
+//////////////
+// Haut de page, menu et message
+$p = "prefs";
+$t = 'Préférences';
+if ( $u['mdp'] == 'pas de mot de passe' )  {
+  if ( !isset($message) )
+    $message = 'IMPORTANT : votre mot de passe a été réinitialisé. Vous devez absolument le redéfinir ci-dessous.';
+  $mdp0 = '    <input type="hidden" name="mdp0" value="réinitialisé">';
+}
+else
+  $mdp0 = '    <p class="ligne"><label for="mdp02">Mot de passe actuel&nbsp;: </label><input type="password" id="mdp02" name="mdp0" value=""></p>';
+include('haut.php');
+
+?>
+
+  <div class="item aide">
+    <h3>Aide et explications</h3>
+    <p>Vous pouvez ci-dessous modifier les réglages qui vous concernent personnellement (identité, votre mot de passe, matières associées), ainsi que les réglages de chaque matière qui vous est associée. Pour valider chaque modification sur cette page, vous devrez renseigner votre mot de passe.</p>
+    <h4>Identité</h4>
+    <p>Vous pouvez modifier votre identité. L'<em>identifiant</em> ne sert qu'à la connexion, seuls les autres professeurs (via la page <a href="utilisateurs">Utilisateurs</a>) peuvent le voir. Les accents et les majuscules n'y posent pas problème. Le <em>prénom</em> et le <em>nom</em> sont obligatoires, l'<em>adresse mail</em> ne l'est pas. Cette dernière est cependant nécessaire pour activer la fonction <a href="mail">Envoi de mail</a> ou pour que des collègues puissent vous mettre en copie de mails envoyés via Cahier de Prépa et elle apparaîtra bien sûr comme expéditeur des mails que vous enverrez.</p>
+    <h4>Mot de passe</h4>
+    <p>Vous pouvez modifier votre <em>mot de passe</em>. Il faut bien entendu renseigner le <em>mot de passe actuel</em> et taper deux fois le <em>nouveau mot de passe</em> pour éviter les erreurs de frappe.</p>
+    <p>Les mots de passe sont chiffrés avant d'être stockés dans la base de données. Cela signifie que, sauf s'il est trop simple, votre mot de passe sera en complète sécurité : même l'administrateur du site, l'hébergeur ou d'éventuels pirates ne pourront jamais y avoir accès. Lors de la connexion, le mot de passe entré est chiffré à son tour et ce sont les deux chiffrements qui sont comparés. Attention&nbsp;: il est très dangereux que ce mot de passe soit évident, parce qu'un élève pourrait le deviner en vous voyant de loin le taper par exemple. Un bon mot de passe est un mot de passe d'au moins 8 caractères contenant des lettres, des chiffres et au moins un symbole parmi «&nbsp;?&nbsp;;&nbsp;:&nbsp;!&nbsp;.&nbsp;,-&nbsp;_&nbsp;». Mais vous faîtes ce que vous voulez...</p>
+    <h4>Les préferences techniques</h4>
+    <p>Quelques paramètres techniques sont modifiables&nbsp;:</p>
+    <ul>
+      <li>le <em>temps de déconnexion</em> est en secondes le temps au bout duquel, sans aucune action, votre session sera supprimée. À chaque chargement de page, le décompte repart de zéro. Par défaut, il est réglé à 15 minutes. Le but est d'éviter qu'un utilisateur passant derrière vous sur un ordinateur public puisse usurper votre identifiant. Si vous avez la bonne habitude de vous déconnecter systématiquement et de ne pas laisser seul un ordinateur public où vous êtes connecté, cela ne pose aucun problème de le régler à une durée plus longue, 1&nbsp;h par exemple.</li>
+      <li>le <em>champ expéditeur pour les mails</em> est le réglage de ce qui apparaîtra comme expéditeur de vos mails. Vous avez le choix entre des valeurs plus ou moins formelles, selon votre relation avec vos élèves.</li>
+      <li>la case à cocher <em>Étre en copie des mails envoyés</em> permet de recevoir en copie cachée (les destinataires ne le voient pas) les mails que vous envoyez via la page <a href="mail">Envoi de mail</a>.</li>
+    </ul>
+    <h4>Les matières qui vous sont associées</h4>
+    <p>Vous pouvez choisir quelles matières apparaissent dans le menu de gauche, dans votre interface d'administration, et dans quel ordre elles apparaissent. Ce choix ne modifie pas le menu de la partie publique (ni quelles matières y sont affichées, ni leur ordre d'apparition). Une matière peut être associée à plusieurs professeurs.</p>
+    <p>Vous ne pourrez modifier/consulter que les données (cahiers de texte, programmes de colles, documents, notes) des matières listées ici.</p>
+    <h4>Préférences de matière</h4>
+    <p>Pour chaque matière associée à votre compte, vous pouvez modifier&nbsp;:</p>
+    <ul>
+      <li>le <em>nom complet</em> qui s'affiche dans le menu et dans les titres des pages. Mettez une majuscule au début.</li>
+      <li>la <em>clé dans l'adresse</em> qui est un mot-clé utilisé uniquement dans l'adresse des pages associées à la matière. Il vaut mieux que ce soit un mot unique, court et sans majuscule. Par exemple, «&nbsp;maths&nbsp;», «&nbsp;phys&nbsp;»...</li>
+    </ul>
+    <p>L'<em>accès au programmes de colles</em> et l'<em>accès au cahier de texte</em> peuvent être choisis parmi quatre possibilités&nbsp;:</p>
+    <ul>
+      <li><em>Visible de tous</em>&nbsp;: accessible de tout visiteur, sans identification.</li>
+      <li><em>Visible pour les élèves, colleurs, professeurs</em>&nbsp;: accessible uniquement par tout utilisateur après identification. Il faut donc avoir un compte validé (voir l'aide de la page <a href="utilisateurs">Utilisateurs</a>) pour y accéder.</li>
+      <li><em>Visible pour les colleurs, professeurs</em>&nbsp;: accessible uniquement par les utilisateurs de type colleur ou professeur.</li>
+      <li><em>Visible pour les professeurs</em>&nbsp;: accessible uniquement par les utilisateurs de type professeur (vous et vos collègues).</li>
+    </ul>
+    <p>Quel que soit ce choix, les liens dans le menu de la partie publique apparaîtront pour tout visiteur (si le cahier de texte/programme de colles est non vide uniquement), tout comme le lien vers les documents.</p>
+  </div>
+<?php
+
+// Formulaire de modification de l'identité
+$select_genre = str_replace("\"${u['genre']}\"","\"${u['genre']}\" selected",'
+        <option value="1">M.</option>
+        <option value="2">Mme</option>
+        <option value="3">Melle</option>');
+
+echo <<<FIN
+  <div class="item admin">
+  <form action="" method="post">
+    <input class="bouton" type="submit" name="modifie" value="Valider" title="Valider les modifications">
+    <h3>Modifier mon identité</h3>
+    <p class="ligne"><label for="genre">M-Mme-Melle&nbsp;:</label>
+      <select name="genre" id="genre">$select_genre
+      </select>
+    </p>
+    <p class="ligne"><label for="login">Identifiant&nbsp;: </label><input type="text" id="login" name="login" value="${u['login']}" size="50"></p>
+    <p class="ligne"><label for="prenom">Prénom&nbsp;: </label><input type="text" id="prenom" name="prenom" value="${u['prenom']}" size="50"></p>
+    <p class="ligne"><label for="nom">Nom&nbsp;: </label><input type="text" id="nom" name="nom" value="${u['nom']}" size="50"></p>
+    <p class="ligne"><label for="mail">Adresse mail&nbsp;: </label><input type="text" id="mail" name="mail" value="${u['mail']}" size="50"></p>
+    <p class="ligne"><label for="mdp01">Mot de passe&nbsp;: </label><input type="password" id="mdp01" name="mdp0" value=""></p>
+    <input type="hidden" name="action" value="identite">
+  </form>
+  </div>
+
+FIN;
+
+// Formulaire de modification du mot de passe
+?>
+
+  <div class="item admin">
+  <form action="" method="post">
+    <input class="bouton" type="submit" name="modifie" value="Valider" title="Valider les modifications">
+    <h3>Modifier mon mot de passe</h3>
+<?php echo $mdp0; ?>
+
+    <p class="ligne"><label for="mdp1">Nouveau mot de passe&nbsp;: </label><input type="password" id="mdp1" name="mdp1" value=""></p>
+    <p class="ligne"><label for="mdp2">Confirmation&nbsp;: </label><input type="password" id="mdp2" name="mdp2" value=""></p>
+    <input type="hidden" name="action" value="mdp">
+  </form>
+  </div>
+
+<?php
+// Formulaire de modification des préférences techniques
+if ( strlen($u['mail']) )  {
+  switch ( $u['genre'] )  {
+    case 1 : $g = 'M. '; break;
+    case 2 : $g = 'Mme '; break;
+    case 3 : $g = 'Melle '; break;
+  }
+  $select_mailexp = str_replace("\"${u['mailexp']}\"","\"${u['mailexp']}\" selected","
+        <option value=\"1\">$g ${u['nom']}</option>
+        <option value=\"2\">$g ${u['prenom']} ${u['nom']}</option>
+        <option value=\"3\">${u['prenom']} ${u['nom']}</option>
+        <option value=\"3\">${u['nom']}</option>");
+}
+else
+  $select_mailexp = '
+        <option value="1">Il faut définir votre adresse mail pour pouvoir en envoyer.</option>';
+$u['mailcopy'] = ( $u['mailcopy']) ? ' checked' : '';
+echo <<<FIN
+
+  <div class="item admin">
+  <form action="" method="post">
+    <input class="bouton" type="submit" name="modifie" value="Valider" title="Valider les modifications">
+    <h3>Modifier mes préférences techniques</h3>
+    <p class="ligne"><label for="timeout">Temps de déconnexion&nbsp;: </label><input type="text" id="timeout" name="timeout" value="${u['timeout']}" size="3"></p>
+    <p class="ligne"><label for="mailexp">Champ expéditeur pour les mails&nbsp;:</label>
+      <select name="mailexp" id="mailexp">$select_mailexp
+      </select>
+    </p>
+    <p class="ligne"><label for="mailcopy">Étre en copie des mails envoyés&nbsp;: </label><input type="checkbox" id="mailcopy" name="mailcopy" value="1"${u['mailcopy']}></p>
+    <p class="ligne"><label for="mdp04">Mot de passe&nbsp;: </label><input type="password" id="mdp04" name="mdp0" value=""></p>
+    <input type="hidden" name="action" value="params">
+  </form>
+  </div>
+
+FIN;
+
+// Formulaire de modification des matières
+$resultat = $mysqli->query("SELECT id, nom, ordre FROM matieres ORDER BY ordre");
+$select = "    <p id=\"ligneXX\" class=\"ligne\"><label for=\"matiereXX\">Matière n°XX&nbsp;:</label>\n      <select name=matieres[XX]>\n";
+$max = $resultat->num_rows;
+while ( $r = $resultat->fetch_assoc() )
+  $select .= "        <option value=\"${r['id']}\">${r['nom']}</option>\n";
+$resultat->free();
+$select .= "      </select>\n    </p>\n";
+?>
+
+  <div class="item admin">
+  <form action="" method="post">
+    <input class="bouton" type="submit" name="modifie" value="Valider" title="Valider les modifications">
+    <h3>Modifier mes matières</h3>
+<?php
+$n = 0;
+foreach ( $matieres as $m )
+  echo str_replace("\"${m['id']}\"","\"${m['id']}\" selected",str_replace('XX',++$n,$select));
+?>
+    <p class="ligne"><label for="mdp03">Mot de passe&nbsp;: </label><input type="password" id="mdp03" name="mdp0" value=""></p>
+    <p class="boutons">
+      <input type="button" id="ajouter" value="Ajouter une matière">
+      <input type="button" id="retirer" value="Retirer la dernière matière">
+    </p>
+    <input type="hidden" name="action" value="matieres">
+  </form>
+  </div>
+<?php
+
+// Formulaire sur chaque matière enregistrée
+$select_protection = '
+        <option value="0">Visible de tous</option>
+        <option value="1">Visible pour les élèves, colleurs, profs</option>
+        <option value="2">Visible pour les colleurs et les profs</option>
+        <option value="3">Visible pour les profs uniquement</option>';
+$resultat = $mysqli->query("SELECT id, cle, nom, colles, cdt, docs, notes, colles_protection, cdt_protection
+                            FROM matieres WHERE FIND_IN_SET(id,'${_SESSION['matieres']}') ORDER BY FIND_IN_SET(id,'${_SESSION['matieres']}')");
+while ( $r = $resultat->fetch_assoc() )  {
+  $id = $r['id'];
+  if ( $r['colles'] + $r['cdt'] + $r['docs'] + $r['notes'] )  {
+    $boutons = '';//"\n    <p class=\"boutons\">";
+    if ( $r['colles'] )
+      $boutons .= "\n    <p class=\"ligne\"><label>Supprimer tous les programmes de colles&nbsp;</label><input type=\"submit\" name=\"supprime_colles\" value=\"Supprimer\"></p>";
+    if ( $r['cdt'] )
+      $boutons .= "\n    <p class=\"ligne\"><label>Supprimer tout le cahier de texte&nbsp;</label><input type=\"submit\" name=\"supprime_cdt\" value=\"Supprimer\"></p>";
+    if ( $r['docs'] )
+      $boutons .= "\n    <p class=\"ligne\"><label>Supprimer tous les documents&nbsp;</label><input type=\"submit\" name=\"supprime_docs\" value=\"Supprimer\"></p>";
+    if ( $r['notes'] )
+      $boutons .= "\n    <p class=\"ligne\"><label>Supprimer toutes les notes&nbsp;</label><input type=\"submit\" name=\"supprime_notes\" value=\"Supprimer\"></p>";
+  }
+  else
+    $boutons = '';
+  $select_colles = str_replace("\"${r['colles_protection']}\"","\"${r['colles_protection']}\" selected",$select_protection);
+  $select_cdt = str_replace("\"${r['cdt_protection']}\"","\"${r['cdt_protection']}\" selected",$select_protection);
+  echo <<<FIN
+
+  <div class="item admin">
+  <form action="" method="post">
+    <input class="bouton" type="submit" name="modifie_matiere" value="Valider" title="Valider les modifications">
+    <h3>Modifier la matière <em>${r['nom']}</em></h3>
+    <p class="ligne"><label for="nom$id">Nom complet&nbsp;: </label><input type="text" id="nom$id" name="nom" value="${r['nom']}" size="50"></p>
+    <p class="ligne"><label for="cle$id">Clé dans l'adresse&nbsp;: </label><input type="text" id="cle$id" name="cle" value="${r['cle']}" size="30"></p>
+    <p class="ligne"><label for="colles_protection$id">Accès aux programmes de colles&nbsp;: </label>
+      <select id="colles_protection$id" name="colles_protection">$select_colles
+      </select>
+    </p>
+    <p class="ligne"><label for="cdt_protection$id">Accès au cahier de texte&nbsp;: </label>
+      <select id="cdt_protection$id" name="cdt_protection">$select_cdt
+      </select>
+    </p>$boutons
+    <p class="ligne"><label for="mdp01$id">Mot de passe&nbsp;: </label><input type="password" id="mdp01$id" name="mdp0" value=""></p>
+    <input type="hidden" name="id" value="$id">
+    <input type="hidden" name="action" value="matiere">
+  </form>
+  </div>
+
+FIN;
+}
+?>
+
+  <script type="text/javascript">
+$( function() {
+  var n = <?php echo $n; ?>;
+  var select = '<?php echo str_replace(array("'","\n",'  '),array("\'",'',''),$select); ?>';
+  $('#ajouter').click(function () {
+    if ( n < <?php echo $max; ?> )  {
+      n++;
+      $( select.replace(/XX/g,n) ).insertBefore($('#mdp03').parent());
+    }
+  });
+  $('#retirer').click(function () {
+    if ( n )  {
+      $('#ligne'+n).remove();
+      n--;
+    }
+  });
+
+});
+  </script>
+
+<?php
+$mysqli->close();
+// Bas de page
+include('bas.php');
+exit();
+?>
diff -urN cahier-de-prepa3.2.0/admin/site.php cahier-de-prepa4.0.0/admin/site.php
--- cahier-de-prepa3.2.0/admin/site.php	1970-01-01 01:00:00.000000000 +0100
+++ cahier-de-prepa4.0.0/admin/site.php	2014-08-26 13:59:56.288041767 +0200
@@ -0,0 +1,61 @@
+<?php
+// Sécurité et récupération des données de configuration
+define('OK',1);
+include('debut.php'); // fournit $mysqli et $matieres
+
+// Récupération du titre
+$resultat = $mysqli->query("SELECT titre FROM pages WHERE id = 1");
+$r = $resultat->fetch_assoc();
+$titre = $r['titre'];
+$resultat->free();
+
+// Modification
+if ( isset($_REQUEST['modifie']) )  {
+  // Connexion à la base de données
+  $mysqli->close();
+  $mysqli = mysql_ecriture();
+  
+  // Validation des données envoyées
+  $titre_nouveau = $mysqli->real_escape_string($_REQUEST['titre']);
+  if ( strlen($titre_nouveau) && ( $titre_nouveau != $titre ) )  {
+    if ( requete('pages',"UPDATE pages SET titre = '$titre_nouveau' WHERE id = 1") )  {
+      $message = 'Le titre du site a bien été modifié.';
+      $titre = $titre_nouveau;
+    }
+    else
+      $message = 'Le titre du site n\'a pas pu être modifié. Erreur MySQL n°'.$mysqli->errno.', «'.$mysqli->error.'».';
+  }
+
+  // Passage en connexion MySQL en lecture seulement
+  $mysqli->close();
+  $mysqli = mysql_lecture();
+}
+
+//////////
+// HTML //
+//////////
+// Haut de page, menu et message
+$p = "site";
+$t = 'Titre du site';
+include('haut.php');
+$mysqli->close();
+?>
+
+  <div class="item aide">
+    <h3>Aide et explications</h3>
+    <p>Vous pouvez modifier ci-dessous le titre du site. Il s'agit en réalité du titre de la première page, aussi modifiable sur la page <a href="pages">Gestion des pages</a>.</p>
+    <p>Ce titre sera affiché sur les pages de connexion de la partie publique et de l'interface d'administration, sur la page permettant d'accéder au flux RSS.</p>
+  </div>
+
+  <div class="item admin nojs">
+  <form action="" method="post">
+    <input class="bouton" type="submit" name="modifie" value="Valider" title="Valider ce titre">
+    <h3>Modifier le titre du site</h3>
+    <p class="ligne"><label for="titre">Titre actuel&nbsp;: </label><input type="text" id="titre$fid" name="titre" value="<?php echo $titre; ?>" size="80"></p>
+  </form>
+  </div>
+
+<?php
+// Bas de page
+include('bas.php');
+?>
diff -urN cahier-de-prepa3.2.0/admin/utilisateurs.php cahier-de-prepa4.0.0/admin/utilisateurs.php
--- cahier-de-prepa3.2.0/admin/utilisateurs.php	1970-01-01 01:00:00.000000000 +0100
+++ cahier-de-prepa4.0.0/admin/utilisateurs.php	2014-08-26 17:42:35.388469260 +0200
@@ -0,0 +1,375 @@
+<?php
+// Sécurité et récupération des données de configuration
+define('OK',1);
+include('debut.php'); // fournit $mysqli et $matieres
+
+// Fonction d'envoi du mail de création/validation du compte
+function envoi_mail($type_action,$type_compte,$mail,$prenom,$nom,$login,$mdp)  {
+  switch ($type_action)  {
+    case 1:
+      $action = 'vous créer un compte'; 
+      $mdp = "\nVotre mot de passe est \"$mdp\". Ce mot de passe est temporaire : vous devrez obligatoirement le modifier dès votre prochaine connexion. Le mot de passe est entièrement chiffré avant son stockage dans la base de données, vous pouvez donc utiliser un mot de passe qui vous est familier. Il est conseillé, de façon générale, de mettre un mot de passe d'au moins 8 caractères, n'étant pas un mot du dictionnaire, comportant de lettres minuscules et majuscules, des chiffres et au moins un signe parmi «?,.;/:§!».\n"; 
+      break;
+    case 2:
+      $action = 'valider votre compte';
+      break;
+  }
+  switch ($type_compte)  {
+    case 1:
+      $texte = 'La connexion s\'effectue par le lien «Se connecter» situé en bas du menu. Vous aurez alors accès aux contenus que vos professeurs mettent en ligne de façon privée, ainsi qu\'aux éventuelles notes de colles que les colleurs entreront dans la base de données.';
+      break;
+    case 2:
+      $texte = 'La connexion s\'effectue par le lien «Se connecter» situé en bas du menu. Vous aurez alors accès aux contenus que les professeurs pourraient en ligne de façon privée, réservés aux élèves ou uniquement aux colleurs. Vous pourrez aussi mettre des notes de colles.';
+      break;
+    case 3:
+      $texte = 'Vous pouvez vous connecter à l\'interface d\'administration par le lien situé en bas du menu. Vous aurez alors la possibilité de :
+* écrire des informations sur le site pour les élèves
+* proposer les programmes de colles
+* remplir votre cahier de texte
+* mettre en ligne des documents
+* envoyer des mails à tous vos élèves, à vos colleurs, à vos collègues
+* consulter les notes de colles de vos colleurs
+* modifier le site : ajouter/modifier des pages d\'informations, des matières, ajouter/supprimer des utilisateurs...
+
+Vous pouvez vous associer à une ou plusieurs matières. Chaque contenu (chaque document, chaque page d\'information, le programme de colles, le cahier de texte) peut être rendu privé individuellement, et accessible soit pour les élèves et les colleurs, soit pour les colleurs uniquement, soit pour vous-même (et vos collègues) uniquement. 
+
+Une page «Préférences» vous permettra de modifier votre mot de passe et l\'association des matières à votre compte.
+
+N\'hésitez pas à faire part de toute remarque visant à améliorer Cahier de Prépa à l\'auteur du logiciel via <admin@cahier-de-prepa.fr>.';
+      break;
+  }
+  mail($mail,'[Cahier de Prépa] Création de votre compte',
+"Bonjour $prenom $nom
+
+L'équipe pédagogique en charge du Cahier de Prépa se trouvant à l'adresse <http://${GLOBALS['site']}> vient de $action.
+
+Votre identifiant est \"$login\" (sans les guillemets, attention à respecter les majuscules).
+$mdp
+$texte
+
+Cordialement,
+-- 
+Cahier de Prépa
+","From: ne-pas-repondre@cahier-de-prepa.fr\r\nContent-type: text/plain; charset=UTF-8");
+  
+}
+
+///////////////////
+// Modifications //
+///////////////////
+// Ajout d'un nouvel utilisateur
+if ( isset($_REQUEST['ajoute']) )  {
+
+  // Validation des données envoyées
+  $genre = ( in_array($_REQUEST['genre'],array(1,2,3)) ) ? $_REQUEST['genre']+5 : 5;
+  $prenom = mb_convert_case($mysqli->real_escape_string($_REQUEST['prenom']),MB_CASE_TITLE,'UTF-8');
+  $nom = mb_convert_case($mysqli->real_escape_string($_REQUEST['nom']),MB_CASE_TITLE,'UTF-8');
+  $mail = strtolower($mysqli->real_escape_string($_REQUEST['mail']));
+  $mdp1 = sha1($_REQUEST['mdp1']);
+  $autor = ( in_array($_REQUEST['autorisation'],array(1,2,3)) ) ? $_REQUEST['autorisation'] : 1;
+  $matiere = ( ( $autor > 1 ) && isset($_REQUEST['matiere']) && is_numeric($_REQUEST['matiere']) ) ? $_REQUEST['matiere'] : '';
+  // Login automatiquement généré
+  $login = mb_strtolower(mb_substr($prenom,0,1,'UTF-8').str_replace(' ','_',$nom),'UTF-8');
+  // Vérification que le login n'existe pas
+  $resultat = $mysqli->query("SELECT id FROM utilisateurs WHERE login RLIKE '^${login}[0-9]*'");  
+  if ( $resultat->num_rows )  {
+    $login = $login.$resultat->num_rows;
+    $resultat->free();
+  }
+  // Écriture dans la base de données'
+  if ( $mdp1 != sha1($_REQUEST['mdp2']) )
+    $message = 'Les mots de passe donnés ne sont pas identiques.';
+  elseif ( strlen($mail) && !filter_var($mail,FILTER_VALIDATE_EMAIL) )
+    $message = 'L\'adresse mail est incorrecte.';
+  elseif ( strlen($prenom) && strlen($nom) && strlen($mdp1) )  {
+    // Connexion à la base de données avec les droits d'écriture
+    $mysqli->close();
+    $mysqli = mysql_ecriture();
+    if ( requete('utilisateurs',"INSERT INTO utilisateurs SET login = '$login', genre = $genre, prenom = '$prenom', nom = '$nom', mail = '$mail',
+                                 mdp = '$mdp1', autorisation = $autor, matieres = '$matiere', timeout = 900, mailexp = 1, mailcopy = 1") )  {
+      $message = 'Le compte de '.stripslashes("$prenom $nom").' a bien été enregistré.';
+      if ( isset($_REQUEST['envoimail']) )  {
+        envoi_mail(1,$autor,$mail,stripslashes($prenom),stripslashes($nom),stripslashes($login),$_REQUEST['mdp1']);
+        $message .= " Un mail lui a été envoyé.";
+      }
+    }
+    // Passage en connexion MySQL en lecture seulement
+    $mysqli->close();
+    $mysqli = mysql_lecture();
+  }
+}
+
+// Validations de comptes en attente, suppressions
+elseif ( isset($_REQUEST['modifie']) )  {
+
+  // Vérification du mot de passe
+  $resultat = $mysqli->query("SELECT mdp FROM utilisateurs WHERE id = ${_SESSION['id']}");
+  $u = $resultat->fetch_assoc();
+  $resultat->free();
+  if ( sha1($_REQUEST['mdp']) != $u['mdp'] )
+    $message = 'Le mot de passe fourni est incorrect. Les modifications n\'ont pas été effectuées.';
+  else  {
+    $message = array();
+    // Connexion à la base de données avec les droits d'écriture
+    $mysqli->close();
+    $mysqli = mysql_ecriture();
+  
+    // Validation de comptes temporaires
+    if ( isset($_REQUEST['valide']) && is_array($ids = $_REQUEST['valide']) )  {
+      $ids = array_filter(array_unique($ids,SORT_NUMERIC),'is_numeric');
+      $liste = array();
+      foreach ( $ids as $u )  {
+        $resultat = $mysqli->query("SELECT SUBSTRING(login,9) AS l, (SELECT COUNT(u2.id) FROM utilisateurs AS u2 WHERE u2.login=l) AS n,
+                                    autorisation, mail, prenom, nom
+                                    FROM utilisateurs AS u1 WHERE id = $u AND login RLIKE '^tmp[0-9]{5}'");
+        if ( $resultat->num_rows )  {
+          $r = $resultat->fetch_assoc();
+          $login = ( $r['n'] ) ? $r['l'].$r['n'] : $r['l'];
+          if ( requete('utilisateurs',"UPDATE utilisateurs SET login = '$login' WHERE id = $u") )  {
+            $liste[] = $r['prenom'].' '.$r['nom'];
+            if ( strlen($r['mail']) )
+              envoi_mail(2,$r['autorisation'],$r['mail'],$r['prenom'],$r['nom'],$login,'');
+          }
+          $resultat->free();
+        }
+      }
+      if ( !empty($liste) )
+        $message[] = 'Les comptes suivants ont été validés&nbsp;: '.implode(', ',$liste);
+    }
+
+    // Suppression de comptes
+    if ( isset($_REQUEST['supprime']) && is_array($ids = $_REQUEST['supprime']) )  {
+      $ids = array_filter(array_unique($ids,SORT_NUMERIC),'is_numeric');
+      $liste = array();
+      foreach ( $ids as $u )  {
+        $resultat = $mysqli->query("SELECT prenom, nom FROM utilisateurs WHERE id = $u");
+        if ( $resultat->num_rows )  {
+          $r = $resultat->fetch_assoc();
+          if ( requete('utilisateurs',"DELETE FROM utilisateurs WHERE id = $u") )
+            $liste[] = $r['prenom'].' '.$r['nom'];
+          $resultat->free();
+        }
+      }
+      if ( !empty($liste) )
+        $message[] = ' Les comptes suivants ont été supprimés&nbsp;: '.implode(', ',$liste);
+    }
+    
+    // Message
+    $message = empty($message) ? 'Aucune action à effectuer.' : implode('<br>',$message);
+
+    // Passage en connexion MySQL en lecture seulement
+    $mysqli->close();
+    $mysqli = mysql_lecture();
+  }
+}
+
+//////////
+// HTML //
+//////////
+// Haut de page, menu et message
+$p = "utilisateurs";
+$t = 'Modifications des utilisateurs';
+include('haut.php');
+
+// Récupération des matières
+$resultat = $mysqli->query('SELECT id, nom FROM matieres');
+$select_matieres = '';
+while ( $r = $resultat->fetch_assoc() )
+  $select_matieres .= "        <option value=\"${r['id']}\">${r['nom']}</option>\n";
+$resultat->free();
+
+// Aide générale et formulaires
+?>
+
+  <div class="item aide">
+    <h3>Aide et explications</h3>
+    <p>Vous pouvez ci-dessous ajouter un utilisateur, consulter et supprimer les utilisateurs existants. Les utilisateurs sont séparés en trois types&nbsp;:</p>
+    <ul>
+      <li>Les <em>professeurs</em> peuvent accéder automatiquement à cette interface d'administration. Ils peuvent être associés ou non à une ou plusieurs matières (chaque professeur peut modifier les matières qui lui sont associées sur sa page de <a href="prefs">Préférences</a>).</li>
+      <li>Les <em>colleurs</em> peuvent mettre des notes en se connectant sur la partie publique. Ils n'ont pas accès à l'interface d'administration. Ils sont associés à une matière.</li>
+      <li>Les élèves peuvent voir leurs notes s'il y en a.</li>
+    </ul>
+    <p>Mis à part les notes, les utilisateurs ayant un compte ce Cahier de Prépa peuvent se connecter sur la partie publique pour accéder aux contenus dont l'accès a été protégé. Il y a quatre niveaux de protection&nbsp;:</p>
+    <ul>
+      <li>visible de tout visiteur, sans identification</li>
+      <li>visible uniquement pour les utilisateurs ayant un compte, qu'ils soient élèves, colleurs ou professeurs</li>
+      <li>visible uniquement pour les utilisateurs de type colleur ou professeur</li>
+      <li>visible uniquement pour les professeurs</li>
+    </ul>
+    <p>L'accès à chaque contenu est à paramétrer de façon individuelle, pour chaque contenu.</p>
+    <h4>Nouvel utilisateur</h4>
+    <p>Vous pouvez créer un nouvel utilisateur. Il faut renseigner son identité complète (<em>prénom</em> et <em>nom</em> sont obligatoires).</p>
+    <p>L'<em>identifiant</em> ne sert qu'à la connexion, seuls la personnes concernée et les professeurs peuvent le voir (dans la liste d'utilisateurs ci-dessous). Les accents et les majuscules n'y posent pas problème.</p>
+    <p>L'<em>adresse mail</em> n'est pas obligatoire. Elle est cependant nécessaire pour recevoir les mails des professeurs envoyés via Cahier de Prépa ou, pour les professeurs, pour activer la fonction <a href="mail">Envoi de mail</a>. Qu'il s'agisse d'un élève, d'un colleur ou d'un professeur, l'utilisateur pourra modifier son identité et son adresse mail ultérieurement, dans ses Préférences.</p>
+    <p>Vous pouvez taper un <em>mot de passe</em> ou utiliser le <em>générateur automatique</em>. Dans les deux cas, <strong>ce mot de passe est obligatoirement temporaire</strong>&nbsp;: il sera automatiquement effacé à la première connexion de l'utilisateur, qui devra alors en entrer un nouveau. Ce n'est donc pas un problème que vous l'ayez vu ou qu'il soit écrit à côté du générateur, il sera très vite réinitialisé.</p>
+    <p>La case à cocher <em>Envoyer un mail de confirmation à l'intéressé</em> vous permet de le prévenir automatiquement. Il recevra ainsi par mail l'adresse du site, son identifiant et son mot de passe.</p>
+    <p>Pour les colleurs, vous devez obligatoirement associer une matière, qui ne sera plus modifiable. Pour les professeurs, vous pouvez ou non associer une matière. Votre collègue pourra ensuite modifier ce choix dans ses Préférences.</p>
+    <h4>Création d'utilisateurs via la partie publique</h4>
+    <p>Depuis le lien <code>Se connecter</code> de la partie publique, un visiteur peut envoyer une demande de création de compte, uniquement de type élève ou colleur. Cette création n'est effective que si vous ou vos collègues la validez ici. L'ensemble des demandes apparait dans une nouvelle case <em>Utilisateurs en attente</em>. L'existance de demandes de création en attente, s'il y en a, est mentionnée sur la page d'<a href=".">accueil</a> de cette interface d'administration.</p>
+    <p>Tout compte non encore validé n'a bien sûr pas accès aux documents dont l'accès est protégé.</p>
+    <p>La validation/suppression des demandes de création de compte requiert votre mot de passe.</p>
+    <p>Remarque&nbsp;: les comptes de type professeur doivent obligatoirement être créés sur cette page. Il ne peuvent pas faire l'objet de demande de création depuis la partie publique.</p>
+    <h4>Liste des utilisateurs</h4>
+    <p>Chaque utilisateur est supprimable. Ces modifications requièrent votre mot de passe.</p>
+  </div>
+
+  <div class="item admin user">
+  <form action="" method="post" id="ajoute">
+    <input class="bouton" type="submit" name="ajoute" value="Valider" title="Valider les modifications">
+    <h3>Créer un nouvel utilisateur</h3>
+    <p class="ligne"><label for="genre">M&nbsp;/&nbsp;Mme&nbsp;/&nbsp;Melle&nbsp;:</label>
+      <select name="genre" id="genre">
+        <option value="1">M.</option>
+        <option value="2">Mme</option>
+        <option value="3">Melle</option>
+      </select>
+    </p>
+    <p class="ligne"><label for="prenom">Prénom&nbsp;: </label><input type="text" id="prenom" name="prenom" value="" size="50"></p>
+    <p class="ligne"><label for="nom">Nom&nbsp;: </label><input type="text" id="nom" name="nom" value="" size="50"></p>
+    <p class="ligne"><label for="mail">Adresse mail&nbsp;: </label><input type="text" id="mail" name="mail" value="" size="50"></p>
+    <p class="ligne"><label for="mdp1">Mot de passe temporaire&nbsp;: </label><input type="password" id="mdp1" name="mdp1" value=""></p>
+    <p class="ligne"><label for="mdp2">Confirmation&nbsp;: </label><input type="password" id="mdp2" name="mdp2" value=""></p>
+    <p class="ligne"><label for="autorisation">Type d'utilisateur&nbsp;:</label>
+      <select name="autorisation" id="autorisation">
+        <option value="1">Élève</option>
+        <option value="2">Colleur</option>
+        <option value="3">Professeur</option>
+      </select>
+    </p>
+    <p class="ligne"><label for="matiere">Matière&nbsp;: </label>
+      <select  id="matiere" name="matiere">
+<?php echo $select_matieres; ?>
+        <option value="0">Pas de matière associée</option>
+      </select>
+    </p>
+    <p class="ligne"><label for="envoimail">Envoyer un mail de confirmation à l'intéressé</label><input type="checkbox" id="envoimail" name="envoimail" value="1" checked></p>
+    <p>Pour tout professeur, la(les) matière(s) associée(s) est(sont) modifiable(s) par lui-même, dans ses <em>Préférences</em>, mais il est souvent plus simple de lui associer directement la bonne matière. Il est possible de créer une matière <a href="matieres">ici</a> (votre collègue pourra le faire après sa connexion si vous ne le faites pas maintenant).</p>
+  </form>
+  </div>
+
+  <script type="text/javascript">
+$( function() {
+  // Ajout d'un générateur de mot de passe
+  $("#ajoute p:last-child").before('<p><input type="button" value="Générer un mot de passe" id="generer_mdp">&nbsp;<span></span></p>');
+  $('#generer_mdp').click( function () {
+    var c = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789,;:!?.';
+    var p = '';
+    for ( var i = 0 ; i < 8 ; i++ )
+      p += c.charAt(1 + Math.floor(Math.random()*68));
+    $(this).next('span').html("Valeur recopiée&nbsp;:&nbsp;<code>"+p+"</code>");
+    $('#mdp1,#mdp2').val(p);
+  });
+  $('#generer_mdp').parent().toggle();
+
+  // Affichage du choix de matière uniquement si colleur ou professeur
+  $('#autorisation').change( function() { 
+    switch ( $(this).val() )  {
+      case '1' : $('#matiere').parent().hide(); break;
+      case '2' : $('#matiere').parent().show().find('option:last').prop('disabled',true); break;
+      case '3' : $('#matiere').parent().show().find('option:last').prop('disabled',false); break;
+    }
+  });
+  $('#matiere').parent().hide();
+  
+  // Remplace le pliage 
+  $('.user h3 span').click( function () {
+    $(this).parent().parent().children(':not(h3)').toggle();
+    if ( $(this).text() == 'déplier' )  {
+      $(this).text('replier');
+      switch ( $('#autorisation').val() )  {
+        case '1' : $('#matiere').parent().hide(); break;
+        case '2' :
+        case '3' : $('#matiere').parent().show(); break;
+      }
+    }
+    else  {
+      $(this).text('déplier');
+      $('#matiere').parent().hide();
+    }
+  });
+    
+});
+  </script>
+
+<?php
+// Utilisateurs "temporaires" -- demandes de création de compte en attente
+$resultat = $mysqli->query('SELECT id, mail, CASE autorisation WHEN 1 THEN \'Élève\' WHEN 2 THEN \'Colleur\' END AS autorisation,
+                            CONCAT( CASE MOD(genre,5) WHEN 1 THEN \'M. \' WHEN 2 THEN \'Mme \' WHEN 3 THEN \'Melle \' END, prenom, \' \', nom ) AS nom_complet
+                            FROM utilisateurs WHERE login RLIKE \'^tmp[0-9]{5}\' ORDER BY nom');
+if ( $n = $resultat->num_rows )  {
+  $u = ( $n > 1 ) ? $n.' utilisateurs' : '1 utilisateur';
+?>
+
+  <div class="item admin">
+  <form action="" method="post">
+    <input class="bouton" type="submit" name="modifie" value="Valider" title="Valider les modifications">
+    <h3><?php echo $u; ?> en attente</h3>
+    <p>C'est assez urgent&nbsp;: il y a actuellement <?php echo $u; ?> en attente de votre aval pour pouvoir se connecter réellement.</p>
+    <p>N'acceptez pas une demande qui vous paraît étrange. Un mail de confirmation sera envoyé automatiquement aux personnes concernées après votre acceptation.</p>
+    <table class="admin">
+      <thead>
+        <tr><th>Nom</th><th>Mail</th><th>Type</th><th class="checks">Valider</th><th class="checks">Supprimer</th></tr>
+      </thead>
+      <tbody>
+
+<?php
+  while ( $r = $resultat->fetch_assoc() )
+    echo "<tr><td>${r['nom_complet']}</td><td>${r['mail']}</td><td>${r['autorisation']}</td><td><input type=\"checkbox\" name=\"valide[]\" value=\"${r['id']}\"></td><td><input type=\"checkbox\" name=\"supprime[]\" value=\"${r['id']}\"></td></tr>\n";
+?>
+      </tbody>
+    </table>
+    <p class="ligne"><label for="mdp01">Mot de passe&nbsp;: </label><input type="password" id="mdp01" name="mdp" value=""></p>
+    <p>Votre mot de passe est nécessaire pour valider la modification.</p>
+  </form>
+  </div>
+  
+<?php
+  $resultat->free();
+}
+
+// Utilisateurs "réels"
+$resultat = $mysqli->query('SELECT id, login, mail, autorisation,
+                            CONCAT( CASE MOD(genre,5) WHEN 1 THEN \'M. \' WHEN 2 THEN \'Mme \' WHEN 3 THEN \'Melle \' END, prenom, \' \', nom ) AS nom_complet
+                            FROM utilisateurs WHERE login NOT RLIKE \'^tmp[0-9]{5}\' ORDER BY autorisation DESC, nom');
+?>
+
+  <div class="item admin nojs">
+  <form action="" method="post">
+    <input class="bouton" type="submit" name="modifie" value="Valider" title="Valider les modifications">
+    <h3>Liste des utilisateurs</h3>
+    <table class="admin">
+      <tbody>
+
+<?php
+$a = 0;
+while ( $r = $resultat->fetch_assoc() )  {
+  if ( $a != $r['autorisation'] )  {
+    $a = $r['autorisation'];
+    switch ( $a )  {
+      case 1 : $t = 'Élèves'; break;
+      case 2 : $t = 'Colleurs'; break;
+      case 3 : $t = 'Professeurs'; break;
+    }
+    echo <<<FIN
+        <tr><th colspan="4">$t</th></tr>
+        <tr><th>Nom</th><th>Identifiant</th><th>Mail</th><th class="checks">Supprimer</th></tr>
+      
+FIN;
+    }
+  echo "<tr><td>${r['nom_complet']}</td><td>${r['login']}</td><td>${r['mail']}</td><td><input type=\"checkbox\" name=\"supprime[]\" value=\"${r['id']}\"></td></tr>\n";
+}
+?>
+      </tbody>
+    </table>
+    <p class="ligne"><label for="mdp02">Mot de passe&nbsp;: </label><input type="password" id="mdp02" name="mdp" value=""></p>
+    <p>Votre mot de passe est nécessaire pour valider la modification.</p>
+  </form>
+  </div>
+
+<?php
+$resultat->free();
+$mysqli->close();
+
+// Bas de page
+include('bas.php');
+?>
diff -urN cahier-de-prepa3.2.0/admin.php cahier-de-prepa4.0.0/admin.php
--- cahier-de-prepa3.2.0/admin.php	2013-12-27 11:25:14.174363180 +0100
+++ cahier-de-prepa4.0.0/admin.php	1970-01-01 01:00:00.000000000 +0100
@@ -1,196 +0,0 @@
-<?php
-// Sécurité : ne doit pas être exécuté s'il n'est pas lancé par un script autorisé
-if ( !defined('OK') )  exit();
-
-// Ce fichier est la page d'accueil de l'interface d'administration. Il ne peut
-//  être lancé que par index.php, si aucun argument n'est fourni. On est donc
-//  déjà passé par debut.php et la connexion MySQL est déjà ouverte.
-
-//////////////
-//// HTML ////
-//////////////
-// Haut de page, menu et message
-$p = ".$urladmin2";
-$t = 'Cahier de Prépa - Administration';
-$mysqli = new mysqli($serveur,$base,$mdp,$base);
-$mysqli->set_charset('utf8');
-include('haut.php');
-
-// Taille maximale de fichier
-$taille = min(ini_get('upload_max_filesize'),ini_get('post_max_size'));
-if ( stristr($taille,'m') )
-  $taille = substr($taille,0,-1)*1048576;
-elseif ( stristr($taille,'k') )
-  $taille = substr($taille,0,-1)*1024;
-$taille = ( $taille < 1048576 ) ? intval($taille/1024).'&nbsp;ko.' : intval($taille/1048576).'&nbsp;Mo';
-?>
-
-  <div>
-    <p>Bienvenue sur l'interface d'administration de Cahier de Prépa.</p>
-    <p>Cette page propose des possibilités de modifications rapide, mais vous pouvez gérer beaucoup plus de choses grâce aux liens du menu&nbsp;!</p>
-  </div>
-
-  <div class="item aide">
-    <h3>Aide et explications</h3>
-    <p>Vous pouvez ci-dessous effctuer les tâches quotidiennes les plus fréquentes&nbsp;: ajouter un programme de colle dans les matières qui vous sont associées, déposer un document dans n'importe quel répertoire du site, ou enregistrer une nouvelle information. Tous ces formulaires sont des raccourcis vers les différentes section de cette interface d'administration.</p>
-    <h4>Programmes de colles</h4>
-    <p>La case à cocher <em>Ne pas diffuser sur la partie publique</em> permet de cacher temporairement ce programme de colle, par exemple pour le diffuser ultérieurement.</p>
-    <h4>Document</h4>
-    <p>Le <em>nom à afficher</em> sera le nom réellement affiché sur le site web (sans extension&nbsp;: elle est détectée automatiquement lors de l'envoi). Si vous laissez cette case vide, le nom du fichier envoyé sera récupéré.</p>
-    <p>Vous pouvez choisir la visibilité du document parmi trois possibilités&nbsp;:</p>
-    <ul>
-      <li><em>Visible de tous</em>&nbsp;: document accessible de tout visiteur</li>
-      <li><em>Visible après identification</em>&nbsp; document accessible uniquement après identification, par exemple pour que seuls vos élèves les voient. Il faut alors <a href="utilisateurs">créer un utilisateur de type élève</a>.</li>
-      <li><em>Non visible</em>&nbsp;: le document n'est pas encore visible en ligne. Cela peut être utile si vous souhaitez le mettre à disposition ultérieurement. C'est complètement sûr&nbsp;: vous pouvez mettre comme cela le prochain sujet de devoir ou même le corrigé.</li>
-    </ul>
-    <p>La taille du fichier envoyé est limitée à <?php echo $taille; ?>. Tout document est modifiable et déplaçable, sans que cela ne modifie le lien vers le document.</p>
-    <h4>Information</h4>
-    <p>La case à cocher <em>Ne pas diffuser sur la partie publique</em> permet de cacher temporairement cette information, par exemple pour la diffuser ultérieurement.</p>
-  </div>
-<?php
-
-///////////////////////////////////
-// Nouveaux programmes de colles //
-///////////////////////////////////
-
-// Fonction d'affichage des semaines
-function format_date($date)  {
-  $semaine = array('dimanche','lundi','mardi','mercredi','jeudi','vendredi','samedi');
-  $mois = array('','janvier','février','mars','avril','mai','juin','juillet','août','septembre','octobre','novembre','décembre');
-  return $semaine[substr($date,0,1)].' '.substr($date,7).' '.$mois[intval(substr($date,5,2))].' '.substr($date,1,4);
-}
-
-// Pour chaque matière
-foreach ( $matieres as $m )  {
-  // Récupération des semaines
-  $resultat = $mysqli->query("SELECT s.id AS sid, DATE_FORMAT(s.debut,'%w%Y%m%e') AS d, s.colle, s.vacances, c.id AS cid, c.cache
-                              FROM semaines AS s
-                              LEFT JOIN (SELECT id, semaine, cache FROM colles WHERE matiere = ${m['id']}) AS c ON c.semaine=s.id");
-  $select_semaines = '';
-  if ( $resultat->num_rows )  {
-    while ( $r = $resultat->fetch_assoc() )  {
-      switch ( $r['vacances'] )  {
-        case 0:
-          if ( $r['colle'] )  {
-            if ( is_null($r['cid']) )
-              $select_semaines .= "\n        <option value=\"${r['sid']}\">".format_date($r['d']).'</option>';
-            elseif ( $r['cache'] )
-              $select_semaines .= "\n        <option value=\"${r['sid']}\" disabled>".format_date($r['d']).' (non diffusé)</option>';
-            else
-              $select_semaines .= "\n        <option value=\"${r['sid']}\" disabled>".format_date($r['d']).' (déjà rempli)</option>';
-          }
-          else
-            $select_semaines .= "\n        <option value=\"${r['sid']}\" disabled>".format_date($r['d']).' (pas de colle)</option>';
-          break;
-        case 1:
-          $select_semaines .= "\n        <option value=\"${r['sid']}\" disabled>Vacances de Toussaint</option>";
-          break;
-        case 2:
-          $select_semaines .= "\n        <option value=\"${r['sid']}\" disabled>Vacances de Noël</option>";
-          break;
-        case 3:
-          $select_semaines .= "\n        <option value=\"${r['sid']}\" disabled>Vacances d'hiver</option>";
-          break;
-        case 4:
-          $select_semaines .= "\n        <option value=\"${r['sid']}\" disabled>Vacances de Pâques</option>";
-      }
-    }
-    $resultat->free();
-?>
-
-  <div class="item admin">
-  <form action="colles?<?php echo "${m['cle']}$urladmin"; ?>" method="post">
-    <input class="bouton" type="submit" name="modifie" value="Valider" title="Valider les modifications">
-    <h3>Enregistrer un programme de colles de <?php echo $m['nom']; ?></h3>
-    <p class="ligne"><label for="id<?php echo $m['id']; ?>">Semaine&nbsp;: </label>
-      <select id="id<?php echo $m['id']; ?>" name="id"><?php echo $select_semaines; ?>
-
-      </select>
-    </p>
-    <textarea name="texte" rows="10" cols="100"></textarea>
-    <p class="ligne"><label for="cache<?php echo $m['id']; ?>">Ne pas diffuser sur la partie publique&nbsp;: </label><input type="checkbox" id="cache<?php echo $m['id']; ?>" name="cache" value="1"></p>
-  </form>
-  </div>
-<?php
-  }
-}
-
-///////////////////////
-// Envoi de document //
-///////////////////////
-// Récupération des répertoires, dans le bon ordre pour un select
-function liste($rid,$n)  {
-  $resultat = $GLOBALS['mysqli']->query("SELECT id, nom, parents FROM reps WHERE parent = $rid");
-  while ( $r = $resultat->fetch_assoc() )  {
-    $GLOBALS['select_reps'] .= "        <option value=\"${r['id']}\">".str_repeat('&rarr;',$n)."${r['nom']}</option>\n";
-    liste($r['id'],$n+1);
-  }
-  $resultat->free();
-}
-$select_reps = '';
-liste(0,0);
-// Sélection automatique du répertoire racine de la matière
-$resultat = $mysqli->query("SELECT id FROM reps WHERE matiere = {$matieres[0]['id']} AND parent = 0");
-$r = $resultat->fetch_assoc();
-$resultat->free();
-$select_reps = str_replace("\"${r['id']}\"","\"${r['id']}\" selected",$select_reps);
-// Choix par défaut de la protection
-$select_protection = str_replace("\"${_SESSION['protection']}\"","\"${_SESSION['protection']}\" selected",'<option value="0">Visible de tous</option><option value="1">Visible après identification</option>');
-?>
-
-  <div class="item admin">
-  <form action="docs<?php echo $urladmin2; ?>" method="post" enctype="multipart/form-data">
-    <input class="bouton" type="submit" name="envoie" value="Envoyer">
-    <h3>Déposer un document</h3>
-    <p class="ligne"><label for="nom">Nom à afficher&nbsp;: </label><input type="text" id="nom" name="nom" value="" size="50"></p>
-    <p class="ligne"><label for="fichier">Fichier&nbsp;: </label><input type="file" id="fichier" name="fichier"></p>
-    <p class="ligne"><label for="rep">Répertoire&nbsp;: </label>
-      <select id="rep" name="rep">
-<?php echo $select_reps; ?>
-      </select>
-    </p>
-    <p class="ligne"><label for="protection">Accès&nbsp;: </label>
-      <select id="protection" name="protection"><?php echo $select_protection; ?><option value="2">Non visible</option></select>
-    </p>
-    <input type="hidden" name="id" value="0">
-  </form>
-  </div>
-<?php
-
-//////////////////////////
-// Nouvelle information //
-//////////////////////////
-$resultat = $mysqli->query("SELECT CONCAT_WS('/',m.cle,p.cle) AS cle, CONCAT_WS('/',m.nom,p.nom) AS nom
-                            FROM pages AS p LEFT JOIN matieres AS m ON p.mat = m.id
-                            WHERE FIND_IN_SET(p.mat,'0,${_SESSION['matieres']}')
-                            ORDER BY FIND_IN_SET(p.mat,'0,${_SESSION['matieres']}'), p.ordre");
-$select_pages = '';
-while ( $r = $resultat->fetch_assoc() )
-  $select_pages .= "        <option value=\"${r['cle']}\">${r['nom']}</option>\n";
-$resultat->free();
-?>
-
-  <div class="item admin">
-  <form action=".<?php echo $urladmin2; ?>" method="post">
-    <input class="bouton" type="submit" name="modifie" value="Valider" title="Valider les modifications sur le titre ou le texte">
-    <h3>Enregistrer une nouvelle information</h3>
-    <p class="ligne"><label for="page">Page&nbsp;: </label>
-      <select id="page" name="page">
-<?php echo $select_pages; ?>
-      </select>
-    </p>
-    <p class="transparent"><input class="ligne" type="text" name="titre" size=50 maxlength=65533 value="Titre"></p>
-    <textarea name="texte" rows="6" cols="100"><p>Texte</p></textarea>
-    <p class="ligne"><label for="cache">Ne pas diffuser sur la partie publique&nbsp;: </label><input type="checkbox" id="cache" name="cache" value="1"></p>
-    <input type="hidden" name="id" value="0">
-  </form>
-  </div>
-
-  <script type="text/javascript" src="textarea.js.php"></script>
-
-<?php
-$mysqli->close();
-// Bas de page
-include('bas.php');
-exit();
-?>
diff -urN cahier-de-prepa3.2.0/bas.php cahier-de-prepa4.0.0/bas.php
--- cahier-de-prepa3.2.0/bas.php	2013-10-22 17:02:37.368418261 +0200
+++ cahier-de-prepa4.0.0/bas.php	2014-08-24 23:40:00.955625900 +0200
@@ -1,33 +1,34 @@
 </div>
 
-<?php
-if ( $admin )  {
-?>
-  <script type="text/javascript">
+</div>
+<div id="bas">Ce site est réalisé par le logiciel <a href="http://cahier-de-prepa.fr">Cahier de prépa</a>, publié sous <a href="Licence_CeCILL_V2-fr.html">licence libre</a>.</div>
+
+<script type="text/javascript">
 $( function() {
-  // Pliage des items et aides de l'interface d'administration
-  $('.aide h3,.admin h3').append(' <span>déplier</span>');
-  $('.aide h3 span,.admin h3 span').click( function () {
-    $(this).parent().parent().children(':not(h3,.nonvisible)').toggle();
-    $(this).text($(this).text() == 'déplier' ? 'replier' : 'déplier');
-  });
-  // Premier pliage
-  $('.aide,.admin form').children(':not(h3)').hide();
-  // Recopie des boutons de modification d'ordre
-  $( "[name='monte'],[name='descend']" ).each(function(){
-    // Un bouton de validation doit se trouver en premier dans le formulaire pour
-    // valider par appui sur la touche entrée. 
-    // On place derrière le bouton de descente, puis le bouton de montée
-    $(this).prev("[name='modifie']").clone().addClass('nonvisible').hide().prependTo($(this).parents('form'));
-    $(this).clone().addClass('bouton').insertAfter($(this).parents('form').find('.nonvisible'));
+  $(window).on('load resize',function() {
+    if ( $('#menu').position().top + $('#menu').outerHeight(true) > $('#bas').position().top )  {
+      $('#menu h3').parent().children(':not(h3)').hide();
+      $('#menu h3').addClass('plie').parent().hover( function () {
+        $(this).children(':not(h3)').toggle();
+      });;
+      $('#menu h3').click( function () {
+        if ( $(this).hasClass('plie') )  {
+          $(this).removeClass('plie');
+          $(this).parent().off('mouseenter mouseleave');
+          $(this).parent().children(':not(h3)').show();
+        }
+        else
+          $(this).parent().children(':not(h3)').toggle();
+      });
+    }
+    else  {
+      $('#menu h3').parent().off('mouseenter mouseleave');
+      $('#menu h3').removeClass('plie').off('click');
+      $('#menu h3').parent().children(':not(h3)').show();
+    }
   });
 });
-  </script>
+</script>
 
-<?php
-}
-?>
-</div>
-<div id="bas">Ce site est réalisé par le logiciel <a href="http://cahier-de-prepa.fr">Cahier de prépa</a>, publié sous <a href="Licence_CeCILL_V2-fr.html">licence libre</a>.</div>
 </body>
 </html>
diff -urN cahier-de-prepa3.2.0/cdt.php cahier-de-prepa4.0.0/cdt.php
--- cahier-de-prepa3.2.0/cdt.php	2013-12-25 22:58:10.570164009 +0100
+++ cahier-de-prepa4.0.0/cdt.php	2014-08-27 02:16:32.785456061 +0200
@@ -7,11 +7,9 @@
 // Validation de la requête : matière, n, nb //
 ///////////////////////////////////////////////
 
-// Recherche de la matière concernée (sur la partie publique, affichage seulement
-// si existe ; sur l'interface d'administration, affichage sans condition)
+// Recherche de la matière concernée (affichage seulement si le cdt existe)
 $mysqli = premiere_connexion();
-$mysqli->set_charset('utf8');
-$resultat = $mysqli->query('SELECT id, cle, nom, cdt FROM matieres WHERE MOD(cdt,2) OR '.var_export($admin,true));
+$resultat = $mysqli->query('SELECT id, cle, nom, cdt_protection FROM matieres WHERE cdt');
 if ( $resultat->num_rows )  {
   if ( !empty($_REQUEST) )
     while ( $r = $resultat->fetch_assoc() )
@@ -25,6 +23,14 @@
     $matiere = $resultat->fetch_assoc();
   }
   $resultat->free();
+  
+  // Si la page est protégée et l'utilisateur non autorisé
+  while ( $matiere['cdt_protection'] > $autorisation )  {
+    $p = "cdt?${matiere['cle']}";
+    $t = "Cahier de texte - ${matiere['nom']}";
+    include('login.php');
+  }
+
 }
 // Si aucune matière présentant son cahier de texte n'est enregistrée
 else  {
@@ -32,56 +38,57 @@
   exit('Cette page ne contient aucune information.');
 }
 
-// Fonction d'affichage des semaines
+// Fonction d'affichage des dates
 function format_date($date)  {
   $semaine = array('dimanche','lundi','mardi','mercredi','jeudi','vendredi','samedi');
   $mois = array('','janvier','février','mars','avril','mai','juin','juillet','août','septembre','octobre','novembre','décembre');
   return $semaine[substr($date,0,1)].' '.substr($date,7).' '.$mois[intval(substr($date,5,2))].' '.substr($date,1,4);
 }
 
-// Récupération des semaines
+// Récupération de l'ensemble des semaines
 $resultat = $mysqli->query("SELECT id, DATE_FORMAT(debut,'%w%Y%m%e') AS debut, vacances FROM semaines");
 $select_semaines = '';
-if ( $resultat->num_rows )  {
-  while ( $r = $resultat->fetch_assoc() )  {
-    switch ( $r['vacances'] )  {
-      case 0:
-        $select_semaines .= "\n        <option value=\"${r['id']}\">".format_date($r['debut']).'</option>';
-        $sid[] = $r['id'];
-        break;
-      case 1:
-        $select_semaines .= "\n        <option value=\"${r['id']}\" disabled>Vacances de Toussaint</option>";
-        break;
-      case 2:
-        $select_semaines .= "\n        <option value=\"${r['id']}\" disabled>Vacances de Noël</option>";
-        break;
-      case 3:
-        $select_semaines .= "\n        <option value=\"${r['id']}\" disabled>Vacances d'hiver</option>";
-        break;
-      case 4:
-        $select_semaines .= "\n        <option value=\"${r['id']}\" disabled>Vacances de Pâques</option>";
-        break;
-    }
+$semaines = array(0=>'');
+while ( $r = $resultat->fetch_assoc() )  {
+  switch ( $r['vacances'] )  {
+    case 0:
+      $select_semaines .= "\n        <option value=\"${r['id']}\">".format_date($r['debut']).'</option>';
+      $semaines[$r['id']] = 'Semaine du '.format_date($r['debut']);
+      $sid[] = $r['id'];
+      break;
+    case 1:
+      $select_semaines .= "\n        <option value=\"${r['id']}\" disabled>Vacances de Toussaint</option>";
+      $semaines[$r['id']] = ucfirst(format_date($r['debut']))."&nbsp;: Vacances de Toussaint";
+      break;
+    case 2:
+      $select_semaines .= "\n        <option value=\"${r['id']}\" disabled>Vacances de Noël</option>";
+      $semaines[$r['id']] = ucfirst(format_date($r['debut']))."&nbsp;: Vacances de Noël";
+      break;
+    case 3:
+      $select_semaines .= "\n        <option value=\"${r['id']}\" disabled>Vacances d'hiver</option>";
+      $semaines[$r['id']] = ucfirst(format_date($r['debut']))."&nbsp;: Vacances d'hiver";
+      break;
+    case 4:
+      $select_semaines .= "\n        <option value=\"${r['id']}\" disabled>Vacances de Pâques</option>";
+      $semaines[$r['id']] = ucfirst(format_date($r['debut']))."&nbsp;: Vacances de Pâques";
+      break;
   }
-  $resultat->free();
-}
-else  {
-  $mysqli->close();
-  include('installation.php');
 }
+$resultat->free();
+$ntotal = count($sid);
+
+// Gestion de la demande des semaines à afficher
 
-// Demande de visualisation de toute l'année
 if ( isset($_REQUEST['depuisledebut']) )  {
   $n = 1;
-  $m = array_search($n,$sid);
-  $nb = count($sid);
+  $m = 0;
+  $nb = $ntotal;
 }
 else  {
   // Validation de n (numéro de semaine à voir)
-  if ( isset($_REQUEST['n']) && is_numeric($n = $_REQUEST['n']) )  {
-    if ( !in_array($n,$sid) )
-      $n = 1;
-  }
+  if ( isset($_REQUEST['n']) && is_numeric($n = $_REQUEST['n']) )
+    while ( !in_array($n,$sid) )
+      $n = ( $n < $ntotal ) ? $n+1 : 1;
   // Sans argument, on souhaite afficher la semaine actuelle
   else {
     $date = ( isset($_REQUEST['date']) && preg_match('/\d{4}-\d{2}-\d{2}/',$_REQUEST['date']) ) ? '\''.trim($_REQUEST['date'],'\'').'\'' : 'NOW()';
@@ -101,150 +108,42 @@
   if ( isset($_REQUEST['nb']) && is_numeric($nb = $_REQUEST['nb']) )  {
     if ( $nb < 1 )
       $nb = 1;
-    if ( $nb > count($sid) - $m )
-      $nb = count($sid) - $m;
+    elseif ( $nb > $ntotal - $m )
+      $nb = $ntotal - $m;
   }
   else
     // Par défaut : affichage d'une seule semaine
     $nb = 1;
 }
 
-// Récupération des types de séances
-$types = array();
-$resultat = $mysqli->query("SELECT id, cle, deb_fin_pour FROM `cdt-types` WHERE matiere = ${matiere['id']}");
-while ( $r = $resultat->fetch_assoc() )
-  $types[$r['id']] = $r;  
-$resultat->free();
-
-///////////////////
-// Modifications //
-///////////////////
-if ( ( $admin ) && isset($_REQUEST['id']) && is_numeric($id = $_REQUEST['id']) )  {
-  // Connexion à la base de données
-  $mysqli->close();
-  $mysqli = new mysqli($serveur,"$base-adm",$mdp,$base);
-  $mysqli->set_charset('utf8');
-
-  // Vérification que l'identifiant est valide. Défaut : id=0 (nouvelle entrée)
-  if ( $id )  {
-    $resultat = $mysqli->query("SELECT id FROM cdt WHERE matiere = ${matiere['id']} AND id = $id");
-    if ( $resultat->num_rows )
-      $resultat->free();
-    else
-      $id = 0;
-  }
-
-  // Sauvegarde de la table contenant les données
-  sauvegarde_mysql('cdt');
-  
-  // Traitement d'un ajout/modification
-  if ( isset($_REQUEST['modifie']) && strlen($_REQUEST['texte']) )  {
-    
-    // Validation du jour : à mettre dans la bonne semaine
-    $jour = preg_filter('/(\d{2})\/(\d{2})\/(\d{4})/','$3-$2-$1',$_REQUEST['jour']);
-    $resultat = $mysqli->query("SELECT id FROM semaines WHERE debut <= '$jour' ORDER BY debut DESC LIMIT 1");
-    if ( $resultat->num_rows )  {
-      $r = $resultat->fetch_assoc();
-      $resultat->free();
-      // Correction éventuelle de n et nb pour que l'entrée soit visible après enregistrement
-      $s = array_search($r['id'],$sid);
-      if ( ( $s < $m ) || ( $m+$nb <= $s ) )  {
-        $n = $r['id'];
-        $m = array_search($n,$sid);
-        $nb = 1;
-      }
-      
-      // Validation des autres données envoyées
-      $tid = ( array_key_exists($_REQUEST['tid'],$types) ) ? $_REQUEST['tid'] : key($types);
-      $h_debut = $h_fin = '0:00';
-      $pour = '0000/00/00';
-      switch ( $types[$tid]['deb_fin_pour'] )  {
-        case 1:
-          $h_fin = preg_filter('/(\d{1,2})h(\d{2})/','$1:$2',$_REQUEST['h_fin']);
-        case 0:
-          $h_debut = preg_filter('/(\d{1,2})h(\d{2})/','$1:$2',$_REQUEST['h_debut']);
-          break;
-        case 2:
-          $pour = preg_filter('/(\d{2})\/(\d{2})\/(\d{4})/','$3-$2-$1',$_REQUEST['pour']);
-      }
-      $texte = $mysqli->real_escape_string($_REQUEST['texte']);
-      $demigroupe = ( isset($_REQUEST['demigroupe']) ) ? 1 : 0;
-
-      // Si $id > 0 : modification d'une entrée existante. Si $id = 0, nouvelle entrée
-      // Ordre : d'abord ce qui n'a pas de fin, ensuite les séances "normales", ensuite les "pour le"
-      if ( $id )
-        $message = ( $mysqli->query("UPDATE cdt SET semaine = ${r['id']},
-                                     jour = '$jour', h_debut = '$h_debut', h_fin = '$h_fin', pour = '$pour',
-                                     type = $tid, texte = '$texte', demigroupe = $demigroupe WHERE id = $id")
-                  && $mysqli->query('ALTER TABLE cdt ORDER BY jour,matiere,pour,h_debut,h_fin,type')
-        ) ? 'L\'entrée du cahier de texte a bien été modifiée.' : 'L\'entrée du cahier de texte n\'a pas pu être modifiée. Erreur MySQL n°'.$mysqli->errno.', «'.$mysqli->error.'».';
-      else  {
-        $cache = ( isset($_REQUEST['cache']) ) ? 1 : 0;
-        $message = ( $mysqli->query("INSERT INTO cdt SET semaine = ${r['id']}, matiere = ${matiere['id']},
-                                     jour = '$jour', h_debut = '$h_debut', h_fin = '$h_fin', pour = '$pour',
-                                     type = $tid, texte = '$texte', demigroupe = $demigroupe, cache = $cache")
-                  && $mysqli->query('ALTER TABLE cdt ORDER BY jour,matiere,pour,h_debut,h_fin,type')
-        ) ? 'L\'entrée du cahier de texte a bien été ajoutée.' : 'L\'entrée du cahier de texte n\'a pas pu être ajoutée. Erreur MySQL n°'.$mysqli->errno.', «'.$mysqli->error.'».';
-      }
+// Récupération des types de séances, pour le formulaire de recherche
+$resultat = $mysqli->query("SELECT cle FROM `cdt-types` WHERE matiere = ${matiere['id']} AND nb_v");
+$select_types = $recherche_types = '';
+if ( isset($_REQUEST['type']) )  {
+  while ( $r = $resultat->fetch_assoc() )
+    if ( $r['cle'] == $_REQUEST['type'] )  {
+      $select_types .= "\n        <option value=\"${r['cle']}\" selected>les ${r['cle']}</option>";
+      $recherche_types = " AND t.cle = '${r['cle']}'";
     }
     else
-      $message = 'La date saisie se situe hors de l\'année scolaire.';
-  }
-
-  elseif ( $id )  {
-
-    // Positionnement "montré" (apparaît sur la partie publique)
-    if ( isset($_REQUEST['montre']) )
-      $message = ( $mysqli->query("UPDATE cdt SET cache = 0 WHERE id = $id") ) ? 'L\'entrée du cahier de texte a bien été diffusée, elle apparaît désormais sur la partie publique.' : 'L\'entrée du cahier de texte n\'a pas pu être diffusée. Erreur MySQL n°'.$mysqli->errno.', «'.$mysqli->error.'».';
-
-    // Positionnement "caché" (n'apparaît plus sur la partie publique)
-    elseif ( isset($_REQUEST['cache']) )
-      $message = ( $mysqli->query("UPDATE cdt SET cache = 1 WHERE id = $id") ) ? 'L\'entrée du cahier de texte n\'est plus diffusée&nbsp;: elle n\'apparaît plus dans la partie publique mais est toujours disponible ici pour modification ou diffusion.' : 'L\'entrée du cahier de texte n\'a pas pu être cachée. Erreur MySQL n°'.$mysqli->errno.', «'.$mysqli->error.'».';
-
-    // Suppression
-    elseif ( isset($_REQUEST['supprime']) || !strlen($texte) )
-      $message = ( $mysqli->query("DELETE FROM cdt WHERE id = $id") ) ? 'L\'entrée du cahier de texte a bien été supprimée.' : 'L\'entrée du cahier de texte n\'a pas pu être supprimée. Erreur MySQL n°'.$mysqli->errno.', «'.$mysqli->error.'».';
-  }
-    
-  // Mise à jour des champs 'cdt' dans la table 'matieres' (pour le menu)
-  $mysqli->query('UPDATE matieres SET cdt = cdt - MOD(cdt,2) + IF((SELECT id FROM cdt WHERE matiere = matieres.id AND cache = 0 
-  LIMIT 1),1,0)');
-
-  // Passage en connexion MySQL en lecture seulement
-  $mysqli->close();
-  $mysqli = new mysqli($serveur,$base,$mdp,$base);
-  $mysqli->set_charset('utf8');
+      $select_types .= "\n        <option value=\"${r['cle']}\">les ${r['cle']}</option>"; 
 }
+else
+  while ( $r = $resultat->fetch_assoc() )
+    $select_types .= "\n        <option value=\"${r['cle']}\">les ${r['cle']}</option>"; 
+$resultat->free();
 
 ////////////
 /// HTML ///
 ////////////
-$p = "cdt?${matiere['cle']}$urladmin";
+$p = "cdt?${matiere['cle']}";
 $t = "Cahier de texte - ${matiere['nom']}";
 
-// Demander l'identification si la page est protégée et l'utilisateur non connecté
-if ( ( $matiere['cdt'] == 3 ) && !$admin && !$lecteur )
-  include('login_lecture.php');
-
 // Haut de page, menu et message
 include('haut.php');
 
-// Formulaire de demande d'affichage
+// Formulaire de la demande des semaines à afficher
 $select_semaines = str_replace("\"$n\"","\"$n\" selected",$select_semaines);
-$select_types = $recherche_types = '';
-if ( isset($_REQUEST['type']) )
-  foreach ( $types as $type )
-    if ( $type['cle'] == $_REQUEST['type'] )  {
-      $select_types .= "\n        <option value=\"${type['cle']}\" selected>les ${type['cle']}</option>";
-      $recherche_types = " AND t.cle = '${type['cle']}'";
-    }
-    else
-      $select_types .= "\n        <option value=\"${type['cle']}\">les ${type['cle']}</option>"; 
-else
-  foreach ( $types as $type )
-    $select_types .= "\n        <option value=\"${type['cle']}\">les ${type['cle']}</option>"; 
-
-$u = strlen($urladmin) ? "\n    <input type=\"hidden\" name=\"admin\" value=\"\">" : '';
 echo <<<FIN
 
   <div class="item" id="recherche">
@@ -259,186 +158,76 @@
       <select name="n">$select_semaines
       </select>
       <input type="submit" name="" value="OK">
-    </p>$u
-  </form>
-  </div>
-
-
-FIN;
-
-// Aide générale pour l'interface d'administration
-if ( $admin )  {
-?>
-  <div class="item aide">
-    <h3>Aide et explications</h3>
-    <p>Vous pouvez ci-dessous modifier votre cahier de texte. Vous pouvez choisir les semaines à afficher ci-dessus.</p>
-    <p>La case à cocher <em>Ne pas diffuser sur la partie publique</em> permet de cacher temporairement cette entrée du cahier de texte, par exemple pour le diffuser ultérieurement.</p>
-    <p>Vous pouvez <em>cacher</em> ou <em>montrer</em> une entrée de cahier de texte existante, c'est-à-dire la diffuser ou non sur la partie publique.</p>
-    <p>Vous pouvez supprimer une entrée de cahier de texte existante par le bouton <em>Supprimer</em> ou en validant un texte vide.</p>
-    <p>Les semaines sont réglables <a href="semaines">ici</a>.</p>
-    <p>Il est possible de permettre l'accès à la page affichant ce cahier de texte uniquement aux visiteurs qui se sont identifiés. Cela est réglable avec vos <a href="prefs">préférences</a>. Il faut alors <a href="utilisateurs">créer un compte</a> de type élève pour permettre aux élèves d'accéder à cette page.</p>
-  </div>
-
-  <div class="item">
-    <p>Vous pouvez <a href="cdt-seances?<?php echo $matiere['cle']; ?>">définir des boutons de raccourci</a>, qui pré-rempliront les champs type, date, heures et demi-groupe. Ils apparaîtront dans chaque formulaire d'entrée du cahier de texte. Ces boutons sont propres à chaque matière.</p>
-    <p>Les types d'entrées du cahier de texte sont aussi <a href="cdt-types?<?php echo $matiere['cle']; ?>">modifiables</a>, indépendamment pour chaque matière.</p>
-  </div>
-
-<?php
-
-  // Select des types d'entrée différent du précédent
-  $select_types = '';
-  foreach ( $types as $type )
-    $select_types .= "\n          <option value=\"${type['id']}\">".ucfirst($type['cle']).'</option>';
-}
-
-// Fonction d'affichage
-function affichage($r)  {
-  $semaine = array('','Lundi','Mardi','Mercredi','Jeudi','Vendredi','Samedi');
-  $pour = ( strlen($r['heure']) ) ? '' : " pour le ${r['pour']}";
-  // Affichage partie administrative
-  if ( $GLOBALS['admin'] )  {
-    if ( $id = $r['id'] )  {
-      $debut = "${semaine[$r['jour']]} ${r['date']}${r['heure']}&nbsp;: ${r['titre']}$pour${r['demigroupe']}";
-      $valide1 = '';
-      $valide = "\n        <input type=\"submit\" name=\"modifie\" value=\"Valider\" title=\"Valider les modifications\">";
-      $suppr = "\n        <input type=\"submit\" name=\"supprime\" value=\"Supprimer\" title=\"Supprimer l'entrée\">";
-      $cache_nouveau = '';
-      if ( $r['cache'] )  {
-        $debut .= ' (entrée non diffusée sur la partie publique)';
-        $cache_classe = ' cache';
-        $cache = "\n        <input type=\"submit\" name=\"montre\" value=\"Montrer\" title=\"Diffuser l'entrée, la rendre visible sur la partie publique\">";
-      }
-      else  {
-        $cache_classe = '';
-        $cache = "\n        <input type=\"submit\" name=\"cache\" value=\"Cacher\" title=\"Ne plus diffuser l'entrée, la rendre invisible sur la partie publique\">";
-      }
-    }
-    else  {
-      $debut = 'Nouvelle entrée du cahier';
-      $valide1 = "\n    <input class=\"bouton\" type=\"submit\" name=\"modifie\" value=\"Valider\" title=\"Valider les modifications\">";
-      $valide = $cache = $cache_classe = $suppr = '';
-      $cache_nouveau = "\n      <p class=\"ligne\"><label for=\"cache\">Ne pas diffuser sur la partie publique&nbsp;: </label><input type=\"checkbox\" id=\"cache\" name=\"cache\" value=\"1\"></p>";
-    }
-    $demigroupe = ( strlen($r['demigroupe']) ) ? ' checked' : '';
-    $select_types = str_replace("\"${r['tid']}\"","\"${r['tid']}\" selected",$GLOBALS['select_types']);
-    $type = ( isset($_REQUEST['type']) ) ? "&amp;type=${_REQUEST['type']}" : '';
-    echo <<<FIN
-  <div class="item cdt admin$cache_classe">
-  <form action="${GLOBALS['p']}&amp;n=${GLOBALS['n']}&amp;nb=${GLOBALS['nb']}$type${GLOBALS['urladmin']}" method="post">$valide1
-    <h3>$debut</h3>
-    <div>
-      <p class="boutons">$valide$cache$suppr
-      </p>
-      <p class="ligne"><label for="tid$id">Type&nbsp;:</label>
-        <select id="tid$id" name="tid">$select_types
-        </select>
-      </p>
-      <p class="ligne"><label for="jour$id">Jour&nbsp;: </label><input type="text" class="date" id="jour$id" name="jour" value="${r['date']}" size="8"></p>
-      <p class="ligne"><label for="h_debut$id">Heure de début&nbsp;: </label><input type="text" class="heure" id="h_debut$id" name="h_debut" value="${r['h_debut']}" size="5"></p>
-      <p class="ligne"><label for="h_fin$id">Heure de fin&nbsp;: </label><input type="text" class="heure" id="h_fin$id" name="h_fin" value="${r['h_fin']}" size="5"></p>
-      <p class="ligne"><label for="pour$id">Pour le&nbsp;: </label><input type="text" class="date" id="pour$id" name="pour" value="${r['pour']}" size="8"></p>
-      <p class="ligne"><label for="demigroupe$id">Séance en demi-groupe&nbsp;: </label><input type="checkbox" id="demigroupe$id" name="demigroupe" value="1"$demigroupe></p>
-      <textarea name="texte" rows="10" cols="100">${r['texte']}</textarea>$cache_nouveau
-      <input type="hidden" name="id" value="$id">
-    </div>
+    </p>
   </form>
   </div>
 
 
 FIN;
-  }
-  elseif ( !$r['cache'] )
-    // Affichage partie publique
-    echo <<<FIN
-    <div class="item cdt">
-      <p class="titre">${semaine[$r['jour']]} ${r['date']}${r['heure']}&nbsp;: ${r['titre']}$pour${r['demigroupe']}</p>
-      <div>
-  ${r['texte']}
-      </div>
-    </div>
-
-
-FIN;
-}
-
-// Gestion des titres de nouvelles semaines
-$resultat = $mysqli->query("SELECT id, DATE_FORMAT(debut,'%w%Y%m%e') AS debut, vacances 
-                            FROM semaines WHERE id BETWEEN $n AND ${sid[$m+$nb-1]}" );
-$semaines = array(0=>'');
-while ( $r = $resultat->fetch_assoc() )
-  switch ( $r['vacances'] )  {
-    case 0:
-      $semaines[$r['id']] = 'Semaine du '.format_date($r['debut']);
-      break;
-    case 1:
-      $semaines[$r['id']] = ucfirst(format_date($r['debut']))."&nbsp;: Vacances de Toussaint";
-      break;
-    case 2:
-      $semaines[$r['id']] = ucfirst(format_date($r['debut']))."&nbsp;: Vacances de Noël";
-      break;
-    case 3:
-      $semaines[$r['id']] = ucfirst(format_date($r['debut']))."&nbsp;: Vacances d'hiver";
-      break;
-    case 4:
-      $semaines[$r['id']] = ucfirst(format_date($r['debut']))."&nbsp;: Vacances de Pâques";
-      break;
-  }
-$resultat->free();
-
-// Formulaire vide
-if ( $admin )
-  affichage(array('id' => 0, 'texte' => '&lt;p&gt;Texte&lt;/p&gt;', 'pour' => '00/00/0000', 'date' => date('d/m/Y'),
-                  'h_debut' => '08h00', 'h_fin' => '10h00', 'heure' => '', 'demigroupe' => '', 'cache' => 0, 'tid' => ''));
 
 // Liens de navigation vers semaines précédentes/suivantes
 $nav = '  <div class="item">';
 if ( $m )
-  $nav .= "\n    <a class=\"prec\" href=\"?${matiere['cle']}&amp;n=". $sid[max($m-$nb,0)] ."&amp;nb=$nb$urladmin\">Semaines précédentes</a>";
-if (  $m+$nb < count($sid) )
-  $nav .= "\n    <a class=\"suiv\" href=\"?${matiere['cle']}&amp;n=". $sid[$m+$nb] ."&amp;nb=$nb$urladmin\">Semaines suivantes</a>";
+  $nav .= "\n    <a class=\"prec\" href=\"?${matiere['cle']}&amp;n=". $sid[max($m-$nb,0)] ."&amp;nb=$nb\">Semaines précédentes</a>";
+if (  $m+$nb < $ntotal )
+  $nav .= "\n    <a class=\"suiv\" href=\"?${matiere['cle']}&amp;n=". $sid[$m+$nb] ."&amp;nb=$nb\">Semaines suivantes</a>";
 $nav .= "\n  </div>\n\n";
-echo $nav;
+if ( strlen($nav) > 31 )
+  echo $nav;
 
 // Récupération des entrées
-$resultat = $mysqli->query("SELECT cdt.id, cdt.texte, cdt.semaine,
+$resultat = $mysqli->query("SELECT cdt.texte, cdt.semaine,
                             DATE_FORMAT(cdt.jour,'%w') AS jour, DATE_FORMAT(cdt.jour,'%d/%m/%Y') AS date,
                             CASE t.deb_fin_pour
                               WHEN 0 THEN TIME_FORMAT(cdt.h_debut,' à %kh%i')
                               WHEN 1 THEN CONCAT(TIME_FORMAT(cdt.h_debut,' de %kh%i'),TIME_FORMAT(cdt.h_fin,' à %kh%i'))
-                              ELSE '' END AS heure,
-                            TIME_FORMAT(cdt.h_debut,'%Hh%i') AS h_debut,
-                            TIME_FORMAT(cdt.h_fin,'%Hh%i') AS h_fin,
-                            DATE_FORMAT(cdt.pour,'%d/%m/%Y') AS pour,
-                            IF(cdt.demigroupe,' (en demi-groupe)','') AS demigroupe, cdt.cache, t.id AS tid, t.titre
+                              WHEN 2 THEN ''
+                              ELSE t.deb_fin_pour END AS heure,
+                            IF(t.deb_fin_pour = 2,CONCAT(' pour le ',DATE_FORMAT(cdt.pour,'%d/%m/%Y')),'') AS pour,
+                            IF(cdt.demigroupe,' (en demi-groupe)','') AS demigroupe, t.titre
                             FROM cdt LEFT JOIN `cdt-types` AS t ON t.matiere = ${matiere['id']} AND t.id = cdt.type
-                            WHERE cdt.semaine BETWEEN $n AND ${sid[$m+$nb-1]} AND cdt.matiere = ${matiere['id']} $recherche_types");
+                            WHERE cdt.cache = 0 AND cdt.semaine BETWEEN $n AND ${sid[$m+$nb-1]} AND cdt.matiere = ${matiere['id']} $recherche_types");
 
 // Affichage des résultats
 if ( $resultat->num_rows )  {
-  $s = $j = 0;
+  // On ne garde que les semaines concernées. Il faut conserver la semaine précédente
+  // pour que puisse fonctionner le premier next à l'affichage.
+  $semaines = array_slice($semaines,$n-1,$sid[$m+$nb-1]-$n+2,true);
+  $s = 0;
   $semaine = array('','Lundi','Mardi','Mercredi','Jeudi','Vendredi','Samedi');
-
   while ( $r = $resultat->fetch_assoc() )  {
     
-    // Affichage d'une div par jour
-    if ( ( $j != $r['jour'] ) || ( $s != $r['semaine'] ) )  {
-      if ( $j )
-        echo "  </div>\n\n";
-        // Nouvelle semaine
-        while ( ( $s != $r['semaine'] ) && next($semaines) )  {
-          echo '  <h2 class="cdtsemaine">'.current($semaines)."</h2>\n\n";
-          $s = key($semaines);
-        }
-      echo "  <div class=\"cdtjour\">\n\n";
-      $j = $r['jour'];
+    // Nouvelle semaine
+    while ( ( $s != $r['semaine'] ) && next($semaines) )  {
+      echo '  <h2 class="cdtsemaine">'.current($semaines)."</h2>\n\n";
+      $s = key($semaines);
     }
-    affichage($r);
+    switch ( $r['heure'] )  {
+      case 3:
+        $titre = "\n      <p class=\"titre\">${semaine[$r['jour']]} ${r['date']}&nbsp;: ${r['titre']}</p>";
+        break;
+      case 4:
+        $titre = "\n      <p class=\"titre\">${semaine[$r['jour']]} ${r['date']}</p>";
+        break;
+      case 5:
+        $titre = '';
+        break;
+      default:
+        $titre = "\n      <p class=\"titre\">${semaine[$r['jour']]} ${r['date']}${r['heure']}&nbsp;: ${r['titre']}${r['pour']}${r['demigroupe']}</p>";
+    }
+    echo <<<FIN
+  <div class="item cdt">$titre
+${r['texte']}
+  </div>
+
+
+FIN;
+
   }
   $resultat->free();
-  echo "  </div>\n\n";
   // Liens de navigation vers semaines précédentes/suivantes
-  echo $nav;
+  if ( strlen($nav) > 31 )
+    echo $nav;
 }
 else  {
   if ( !isset($_REQUEST['type']) || ( $_REQUEST['type'] == 'tout' ) )
@@ -447,87 +236,6 @@
     echo "  <h2>Le cahier de texte ne contient pas de «&nbsp;${_REQUEST['type']}&nbsp;» pour les semaines choisies.</h2>\n\n";
 }
 
-// Gestion des boutons de l'interface d'administration
-if ( $admin )  {
-
-  // Récupération des boutons
-  $resultat = $mysqli->query("SELECT id, nom, jour, TIME_FORMAT(h_debut,'%Hh%i') AS h_debut, TIME_FORMAT(h_fin,'%Hh%i') AS h_fin,
-                              type, demigroupe FROM `cdt-seances` WHERE matiere = ${matiere['id']}");
-  $raccourcis = '';
-  $raccourcisjs = '';
-  if ( $resultat->num_rows )  {
-    while ( $r = $resultat->fetch_assoc() )  {
-      $raccourcis .= "<input type=\"button\" class=\"rac${r['id']}\" value=\"${r['nom']}\"> ";
-      $r['demigroupe'] = ( $r['demigroupe'] ) ? 'true' : 'false';
-      $raccourcisjs .= <<<FIN
-  $('.rac${r['id']}').click(function() {
-    $(this).parent().parent().find('[name="tid"]').val('${r['type']}').change();
-    $(this).parent().parent().find('[id^="h_debut"]').val('${r['h_debut']}');
-    $(this).parent().parent().find('[id^="h_fin"]').val('${r['h_fin']}');
-    $(this).parent().parent().find('[id^="demigroupe"]').attr('checked', ${r['demigroupe']});
-    var today=new Date();
-    var t=today.getDay();
-    t = ${r['jour']}-t;
-    if ( t>0 ) { t-=7; }
-    $(this).parent().parent().find('[id^="jour"]').datepick('setDate',t+'d');
-  });
-
-FIN;
-    }
-    $resultat->free();  
-  }
-
-  // Disparition automatique des heures et de la date d'échéance
-  echo <<<FIN
-  <script type="text/javascript">
-$( function() {
-
-  // Modification du formulaire en fonction du type
-  $('[name="tid"]').change(function() {
-    var p1 = $(this).parent().parent().find('[id^="h_debut"]').parents('.ligne');
-    var p2 = $(this).parent().parent().find('[id^="h_fin"]').parents('.ligne');
-    var p3 = $(this).parent().parent().find('[id^="pour"]').parents('.ligne');
-    switch ( $(this).val() )  {
-FIN;
-  foreach ( $types as $id => $type )
-    switch ( $type['deb_fin_pour'] )  {
-      case 0:
-        echo "\n      case '$id': p1.show(); p2.hide(); p3.hide(); break;";
-        break;
-      case 1:
-        echo "\n      case '$id': p1.show(); p2.show(); p3.hide(); break;";
-        break;
-      case 2:
-        echo "\n      case '$id': p1.hide(); p2.hide(); p3.show(); break;";
-    }
-  echo <<<FIN
-
-    }
-  });
-  // Modification au chargement
-  $('[name="tid"]').change();
-
-  // Boutons de raccourcis
-  $('<p class="boutons">$raccourcis</p>').clone().insertBefore($('.admin form').find('.ligne:first'));
-$raccourcisjs
-  $('.date').datepick({dateFormat: 'dd/mm/yyyy', showTrigger: '<img src="js/calendar-blue.gif">'});
-  $('.heure').timeEntry({timeSteps: [1, 15, 0],separator: 'h'});
-
-  // URL plus lisible après clic du bouton "Toute l'année"
-  $('#depuisledebut').click(function() {
-    window.location.search = '?${matiere['cle']}&type='+$(this).prev('[name="type"]').val()+'&depuisledebut';
-    return false;
-  });
-
-});
-  </script>
-
-  <script type="text/javascript" src="textarea.js.php?m=${matiere['id']}"></script>
-  
-
-FIN;
-
-}
 $mysqli->close();
 
 // Bas de page
diff -urN cahier-de-prepa3.2.0/cdt-seances.php cahier-de-prepa4.0.0/cdt-seances.php
--- cahier-de-prepa3.2.0/cdt-seances.php	2013-12-25 22:58:46.854165170 +0100
+++ cahier-de-prepa4.0.0/cdt-seances.php	1970-01-01 01:00:00.000000000 +0100
@@ -1,260 +0,0 @@
-<?php
-// Sécurité et récupération des données de configuration
-// Mode admin obligatoire (OK=2)
-define('OK',2);
-include('debut.php');
-
-// Recherche de la matière concernée
-if ( !empty($_REQUEST) )  {
-  $mysqli = premiere_connexion();
-  $mysqli->set_charset('utf8');
-  $resultat = $mysqli->query('SELECT id, cle, nom FROM matieres');
-  while ( $r = $resultat->fetch_assoc() )
-    if ( isset($_REQUEST[$r['cle']]) )  {
-      $matiere = $r;
-      break;
-    }
-  $resultat->free();
-}
-// Si mauvaise demande
-if ( !isset($matiere) )
-  exit('Mauvais paramètre d\'accès à cette page');
-
-// Récupération des types de séances
-$types = array();
-$resultat = $mysqli->query("SELECT id, cle, deb_fin_pour FROM `cdt-types` WHERE matiere = ${matiere['id']}");
-while ( $r = $resultat->fetch_assoc() )
-  $types[$r['id']] = $r;  
-$resultat->free();
-
-///////////////////
-// Modifications //
-///////////////////
-if ( isset($_REQUEST['id']) && is_numeric($id = $_REQUEST['id']) )  {
-  // Connexion à la base de données
-  $mysqli->close();
-  $mysqli = new mysqli($serveur,"$base-adm",$mdp,$base);
-  $mysqli->set_charset('utf8');
-
-  // Vérification que l'identifiant est valide. Défaut : id=0 (nouveau raccourci)
-  if ( $id )  {
-    // Vérification de l'existence et récupération de l'ordre d'affichage
-    $resultat = $mysqli->query("SELECT ordre, nom, (SELECT max(ordre) FROM `cdt-seances` WHERE matiere = ${matiere['id']}) AS max
-                                FROM `cdt-seances` WHERE matiere = ${matiere['id']} AND id = $id");
-    if ( $resultat->num_rows )  {
-      $r = $resultat->fetch_assoc();
-      $ordre = $r['ordre'];
-      $resultat->free();
-    }
-    else
-      $id = 0;
-  }
-
-  // Sauvegarde de la table contenant les données
-  sauvegarde_mysql('cdt-seances');
-  
-  // Traitement d'un ajout/modification
-  if ( isset($_REQUEST['modifie']) )  {
-    // Vérification des données envoyées
-    if ( !strlen($_REQUEST['nom']) )
-      $message = 'Il n\'est pas possible de valider un nom vide. Pour supprimer un raccourci, il faut cliquer sur Supprimer.';
-    else  {
-      // Validation des données envoyées
-      $nom = $mysqli->real_escape_string($_REQUEST['nom']);
-      $jour = ( is_numeric($_REQUEST['jour']) ) ? $_REQUEST['jour'] : 1;
-      $tid = ( array_key_exists($_REQUEST['tid'],$types) ) ? $_REQUEST['tid'] : key($types);
-      $h_debut = $h_fin = '0:00';
-      switch ( $types[$tid]['deb_fin_pour'] )  {
-        case 1:
-          $h_fin = preg_filter('/(\d{1,2})h(\d{2})/','$1:$2',$_REQUEST['h_fin']);
-        case 0:
-          $h_debut = preg_filter('/(\d{1,2})h(\d{2})/','$1:$2',$_REQUEST['h_debut']);
-      }
-      $demigroupe = ( isset($_REQUEST['demigroupe']) ) ? 1 : 0;
-      // Si identifiant, modification d'un raccourci existant
-      if ( $id )
-        $message = ( $mysqli->query("UPDATE `cdt-seances` SET nom = '$nom', jour = $jour, h_debut = '$h_debut', h_fin = '$h_fin',
-                                     type = $tid, demigroupe = $demigroupe WHERE id = $id")
-        ) ? 'Le raccourci <em>'.stripslashes($nom).'</em> a bien été modifiée.' : 'Le raccourci <em>'.stripslashes($nom).'</em> n\'a pas pu être modifiée. Erreur MySQL n°'.$mysqli->errno.', «'.$mysqli->error.'».';
-      // Sinon, nouveau raccourci
-      else  {
-        $message = ( $mysqli->query("INSERT INTO `cdt-seances` SET matiere = ${matiere['id']},
-                                     ordre = (SELECT IFNULL(max(cs.ordre)+1,1) FROM `cdt-seances` AS cs WHERE cs.matiere = ${matiere['id']}),
-                                     nom = '$nom', jour = $jour, h_debut = '$h_debut', h_fin = '$h_fin', type = $tid, demigroupe = $demigroupe")
-                  && $mysqli->query('ALTER TABLE `cdt-seances` ORDER BY ordre,matiere')
-        ) ? 'Le raccourci <em>'.stripslashes($nom).'</em> a bien été ajouté.' : 'Le raccourci <em>'.stripslashes($nom).'</em> n\'a pas pu être ajouté. Erreur MySQL n°'.$mysqli->errno.', «'.$mysqli->error.'».';
-      }
-    }
-  }
-
-  // Traitement d'une transformation/suppression
-  elseif ( $id )  {
-    
-    // Déplacement vers le haut
-    if ( isset($_REQUEST['monte']) && ( $ordre > 1 ) )
-      $message = ( $mysqli->query("UPDATE `cdt-seances` SET ordre = (2*$ordre-1-ordre) WHERE ( ordre = $ordre OR ordre = ($ordre-1) ) AND matiere = ${matiere['id']}")
-                && $mysqli->query('ALTER TABLE `cdt-seances` ORDER BY ordre,matiere')
-      ) ? "Le raccourci <em>${r['nom']}</em> a bien été monté d'une place." : "Le raccourci <em>${r['nom']}</em> n'a pas pu être déplacé. Erreur MySQL n°".$mysqli->errno.', «'.$mysqli->error.'».';
-
-    // Déplacement vers le bas
-    elseif ( isset($_REQUEST['descend']) && ( $ordre < $r['max'] ) )
-      $message = ( $mysqli->query("UPDATE `cdt-seances` SET ordre = (2*$ordre+1-ordre) WHERE ( ordre = $ordre OR ordre = ($ordre+1) ) AND matiere = ${matiere['id']}")
-                && $mysqli->query('ALTER TABLE `cdt-seances` ORDER BY ordre,matiere')
-      ) ? "Le raccourci <em>${r['nom']}</em> a bien été descendu d'une place." : "Le raccourci <em>${r['nom']}</em> n'a pas pu être déplacé. Erreur MySQL n°".$mysqli->errno.', «'.$mysqli->error.'».';
-
-    // Suppression
-    elseif ( isset($_REQUEST['supprime']) )
-      $message =  ( $mysqli->query("DELETE FROM `cdt-seances` WHERE id = $id")
-                 && $mysqli->query("UPDATE `cdt-seances` SET ordre = (ordre-1) WHERE ordre > $ordre AND matiere = ${matiere['id']}")
-      ) ? "Le raccourci <em>${r['nom']}</em> a bien été supprimé." : "Le raccourci <em>${r['nom']}</em> n'a pas pu être supprimé. Erreur MySQL n°".$mysqli->errno.', «'.$mysqli->error.'».';
-  }
-
-  // Passage en connexion MySQL en lecture seulement
-  $mysqli->close();
-  $mysqli = new mysqli($serveur,$base,$mdp,$base);
-  $mysqli->set_charset('utf8');
-}
-
-//////////////
-//// HTML ////
-//////////////
-$p = "cdt?${matiere['cle']}$urladmin";
-$t = "Modification des raccourcis du cahier de texte - ${matiere['nom']}";
-// Haut de page, menu et message
-include('haut.php');
-
-// Aide générale
-?>
-
-  <div>
-    <a href="cdt?<?php echo $matiere['cle'].$urladmin; ?>">Revenir au cahier de texte de <?php echo $matiere['nom']; ?></a>
-  </div>
-
-  <div class="item aide">
-    <h3>Aide et explications</h3>
-    <p>Vous pouvez créer et modifier ici les boutons de raccourci qui apparaissent dans les formulaires d'entrée du cahier de texte.</p>
-    <p>Le <em>nom</em> sera affiché sur le bouton. Leur ordre d'apparition est modifiable grâce aux flèches.</p>
-    <p>Ces boutons sont propres à la matière <?php echo $matiere['nom']; ?>.</p>
-    <p>Lors de la saisie d'une entrée du cahier de texte, cliquer sur un bouton de raccourci modifiera automatiquement les valeurs spécifiées (type d'entrée, date, heure de début, éventuellement heure de fin, demi-groupe).</p>
-  </div>
-
-<?php
-// Selects pour les formulaires
-$semaine = '
-        <option value="1">Lundi</option>
-        <option value="2">Mardi</option>
-        <option value="3">Mercredi</option>
-        <option value="4">Jeudi</option>
-        <option value="5">Vendredi</option>
-        <option value="6">Samedi</option>
-        <option value="0">Dimanche</option>
-';
-$select_types = '';
-foreach ( $types as $type )
-  $select_types .= "\n          <option value=\"${type['id']}\">".ucfirst($type['cle']).'</option>';
-
-// Fonction d'affichage
-function affichage($r)  {
-  if ( $id = $r['id'] )  {
-    $debut = "Raccourci n°${r['ordre']}&nbsp;: ${r['nom']}";
-    $valide1 = '';
-    $valide = "\n        <input type=\"submit\" name=\"modifie\" value=\"Valider\" title=\"Valider les modifications\">";
-    $monte = ( $r['ordre'] == 1 ) ? '' : "\n        <input type=\"submit\" name=\"monte\" value=\"&uarr;\" title=\"Remonter le raccourci dans l'ordre d'apparition\">";
-    $descend = ( $r['ordre'] == $GLOBALS['max'] ) ? '' : "\n        <input type=\"submit\" name=\"descend\" value=\"&darr;\" title=\"Descendre le raccourci dans l'ordre d'apparition\">";
-    $suppr = ( $GLOBALS['max'] == 1 ) ? '' : "\n        <input type=\"submit\" name=\"supprime\" value=\"Supprimer\" title=\"Supprimer le raccourci\">";
-    $select_types = str_replace("\"${r['type']}\"","\"${r['type']}\" selected",$GLOBALS['select_types']);
-    $semaine = str_replace("\"${r['jour']}\"","\"${r['jour']}\" selected",$GLOBALS['semaine']);
-  }
-  else  {
-    $debut = 'Nouveau raccourci';
-    $valide1 = "\n    <input class=\"bouton\" type=\"submit\" name=\"modifie\" value=\"Valider\" title=\"Valider les modifications\">";
-    $valide = $monte = $descend = $suppr = '';
-    $select_types = $GLOBALS['select_types'];
-    $semaine = $GLOBALS['semaine'];
-  }
-  echo <<<FIN
-  <div class="item admin">
-  <form action="?${GLOBALS['mat']}${GLOBALS['urladmin']}" method="post">$valide1
-    <h3>$debut</h3>
-    <div>
-      <p class="boutons">$valide$monte$descend$suppr
-      </p>
-      <p class="ligne"><label for="nom$id">Nom&nbsp;: </label><input type="input" id="nom$id" name="nom" value="${r['nom']}" size="50"></p>
-      <p class="ligne"><label for="tid$id">Type&nbsp;:</label>
-        <select id="tid$id" name="tid">$select_types
-        </select>
-      </p>
-      <p class="ligne"><label for="jour$id">Jour&nbsp;:</label>
-        <select id="jour$id" name="jour">$semaine
-        </select>
-      </p>
-      <p class="ligne"><label for="h_debut$id">Heure de début&nbsp;: </label><input type="text" class="heure" id="h_debut$id" name="h_debut" value="${r['h_debut']}" size="5"></p>
-      <p class="ligne"><label for="h_fin$id">Heure de fin&nbsp;: </label><input type="text" class="heure" id="h_fin$id" name="h_fin" value="${r['h_fin']}" size="5"></p>
-      <p class="ligne"><label for="demigroupe$id">Séance en demi-groupe&nbsp;: </label><input type="checkbox" id="demigroupe$id" name="demigroupe" value="1"${r['demigroupe']}></p>
-      <input type="hidden" name="id" value="$id">
-    </div>
-  </form>
-  </div>
-
-
-FIN;
-  
-}
-
-// Formulaire vide pour un nouveau type
-$mat = $matiere['cle'];
-affichage(array('id' => 0, 'nom' => '', 'jour' => 1, 'type' => 0, 'h_debut' => '08h00', 'h_fin' => '10h00', 'demigroupe' => ''));
-
-// Affichage des types
-$resultat = $mysqli->query("SELECT max(ordre) FROM `cdt-seances` WHERE matiere = ${matiere['id']}");
-if ( $resultat->num_rows )  {
-  $r = $resultat->fetch_row();
-  $max = $r[0];
-  $resultat->free();
-  $resultat = $mysqli->query("SELECT id, ordre, nom, jour, type, IF(demigroupe=1,' checked','') AS demigroupe,
-                              TIME_FORMAT(h_debut,'%Hh%i') AS h_debut, TIME_FORMAT(h_fin,'%Hh%i') AS h_fin
-                              FROM `cdt-seances` WHERE matiere = ${matiere['id']}");
-  $mysqli->close();
-  while ( $r = $resultat->fetch_assoc() )
-    affichage($r);
-  $resultat->free();
-}
-
-echo <<<FIN
-
-  <script type="text/javascript">
-$( function() {
-  
-  // Modification du formulaire en fonction du type
-  $('[name="tid"]').change(function() {
-    var p1 = $(this).parent().parent().find('[id^="h_debut"]').parents('.ligne');
-    var p2 = $(this).parent().parent().find('[id^="h_fin"]').parents('.ligne');
-    switch ( $(this).val() )  {
-FIN;
-  foreach ( $types as $id => $type )
-    switch ( $type['deb_fin_pour'] )  {
-      case 0:
-        echo "\n      case '$id': p1.show(); p2.hide(); break;";
-        break;
-      case 1:
-        echo "\n      case '$id': p1.show(); p2.show(); break;";
-        break;
-      case 2:
-        echo "\n      case '$id': p1.hide(); p2.hide(); break;";
-    }
-  echo <<<FIN
-
-    }
-  });
-  // Modification au chargement
-  $('[name="tid"]').change();
-
-  $('.heure').timeEntry({timeSteps: [1, 15, 0],separator: 'h'});
-  
-});
-  </script>
-
-FIN;
-// Bas de page
-include('bas.php');
-?>
diff -urN cahier-de-prepa3.2.0/cdt-types.php cahier-de-prepa4.0.0/cdt-types.php
--- cahier-de-prepa3.2.0/cdt-types.php	2013-12-25 22:59:06.922165812 +0100
+++ cahier-de-prepa4.0.0/cdt-types.php	1970-01-01 01:00:00.000000000 +0100
@@ -1,199 +0,0 @@
-<?php
-// Sécurité et récupération des données de configuration
-// Mode admin obligatoire (OK=2)
-define('OK',2);
-include('debut.php');
-
-// Recherche de la matière concernée
-if ( !empty($_REQUEST) )  {
-  $mysqli = premiere_connexion();
-  $mysqli->set_charset('utf8');
-  $resultat = $mysqli->query('SELECT id, cle, nom FROM matieres');
-  while ( $r = $resultat->fetch_assoc() )
-    if ( isset($_REQUEST[$r['cle']]) )  {
-      $matiere = $r;
-      break;
-    }
-  $resultat->free();
-}
-// Si mauvaise demande
-if ( !isset($matiere) )
-  exit('Mauvais paramètre d\'accès à cette page');
-
-///////////////////
-// Modifications //
-///////////////////
-if ( isset($_REQUEST['id']) && is_numeric($id = $_REQUEST['id']) )  {
-  // Connexion à la base de données
-  $mysqli->close();
-  $mysqli = new mysqli($serveur,"$base-adm",$mdp,$base);
-  $mysqli->set_charset('utf8');
-  
-  // Traitement d'un ajout/modification
-  if ( isset($_REQUEST['modifie']) )  {
-    // Vérification des données envoyées
-    if ( !strlen($_REQUEST['titre']) ||  !strlen($_REQUEST['cle']) )
-      $message = 'Il n\'est pas possible de valider un titre ou une clé vide. Pour supprimer un type, il faut cliquer sur Supprimer (et il faut qu\'aucune entrée de ce type-là soit dans le cahier de texte pour voir ce bouton).';
-    else  {
-      // Sauvegarde de la table contenant les données
-      sauvegarde_mysql('cdt-types');
-      // Validation des données envoyées
-      $titre = $mysqli->real_escape_string($_REQUEST['titre']);
-      $cle = $mysqli->real_escape_string($_REQUEST['cle']);
-      $deb_fin_pour = ( is_numeric($_REQUEST['deb_fin_pour']) ) ? $_REQUEST['deb_fin_pour'] : 1;
-      // Si identifiant, modification d'un type existant
-      if ( $id )
-        $message = ( $mysqli->query("UPDATE `cdt-types` SET titre = '$titre', cle = '$cle', deb_fin_pour = $deb_fin_pour WHERE id = $id")
-        ) ? 'Le type <em>'.stripslashes($titre).'</em> a bien été modifiée.' : 'Le type <em>'.stripslashes($titre).'</em> n\'a pas pu être modifiée. Erreur MySQL n°'.$mysqli->errno.', «'.$mysqli->error.'».';
-      // Sinon, nouveau type
-      else  {
-        $message = ( $mysqli->query("INSERT INTO `cdt-types` SET matiere = ${matiere['id']},
-                                     ordre = (SELECT max(ct.ordre)+1 FROM `cdt-types` AS ct WHERE ct.matiere = ${matiere['id']}),
-                                     titre = '$titre', cle = '$cle', deb_fin_pour = $deb_fin_pour")
-                  && $mysqli->query('ALTER TABLE `cdt-types` ORDER BY ordre,matiere')
-        ) ? 'Le type <em>'.stripslashes($titre).'</em> a bien été ajouté.' : 'Le type <em>'.stripslashes($titre).'</em> n\'a pas pu être ajouté. Erreur MySQL n°'.$mysqli->errno.', «'.$mysqli->error.'».';
-      }
-    }
-  }
-
-  // Traitement d'une transformation/suppression
-  else  {
-    
-    // Vérification de l'existence et récupération de l'ordre d'affichage
-    $resultat = $mysqli->query("SELECT ordre, titre, (SELECT max(ordre) FROM `cdt-types` WHERE matiere = ${matiere['id']}) AS max
-                                FROM `cdt-types` WHERE id = $id");
-    if ( $resultat->num_rows )  {
-      $r = $resultat->fetch_assoc();
-      $ordre = $r['ordre'];
-      $resultat->free();
-
-      // Sauvegarde de la table contenant les données
-      sauvegarde_mysql('cdt-types');
-
-      // Déplacement vers le haut
-      if ( isset($_REQUEST['monte']) && ( $ordre > 1 ) )
-        $message = ( $mysqli->query("UPDATE `cdt-types` SET ordre = (2*$ordre-1-ordre) WHERE ( ordre = $ordre OR ordre = ($ordre-1) ) AND matiere = ${matiere['id']}")
-                  && $mysqli->query('ALTER TABLE `cdt-types` ORDER BY ordre,matiere')
-        ) ? "Le type <em>${r['titre']}</em> a bien été monté d'une place." : "Le type <em>${r['titre']}</em> n'a pas pu être déplacé. Erreur MySQL n°".$mysqli->errno.', «'.$mysqli->error.'».';
-
-      // Déplacement vers le bas
-      elseif ( isset($_REQUEST['descend']) && ( $ordre < $r['max'] ) )
-        $message = ( $mysqli->query("UPDATE `cdt-types` SET ordre = (2*$ordre+1-ordre) WHERE ( ordre = $ordre OR ordre = ($ordre+1) ) AND matiere = ${matiere['id']}")
-                  && $mysqli->query('ALTER TABLE `cdt-types` ORDER BY ordre,matiere')
-        ) ? "Le type <em>${r['titre']}</em> a bien été descendu d'une place." : "Le type <em>${r['titre']}</em> n'a pas pu être déplacé. Erreur MySQL n°".$mysqli->errno.', «'.$mysqli->error.'».';
-
-      // Suppression
-      elseif ( isset($_REQUEST['supprime']) && ( $r['max'] > 1 ) )  {
-        // Ne pas supprimer un type utilisé dans le cahier de texte.
-        $resultat = $mysqli->query("SELECT id FROM cdt WHERE type = $id AND matiere = ${matiere['id']}");
-        if ( $resultat->num_rows )  {
-          $resultat->free();
-          $message = "Le type <em>${r['titre']}</em> n'a pas pu être supprimé car des entrées du cahier de texte sont de ce type. Il faut d'abord les supprimer.";
-        }
-        else
-          $message =  ( $mysqli->query("DELETE FROM `cdt-types` WHERE id = $id")
-                     && $mysqli->query("UPDATE `cdt-types` SET ordre = (ordre-1) WHERE ordre > $ordre AND matiere = ${matiere['id']}")
-          ) ? "Le type <em>${r['titre']}</em> a bien été supprimé." : "Le type <em>${r['titre']}</em> n'a pas pu être supprimé. Erreur MySQL n°".$mysqli->errno.', «'.$mysqli->error.'».';
-      }
-    }
-  }
-
-  // Passage en connexion MySQL en lecture seulement
-  $mysqli->close();
-  $mysqli = new mysqli($serveur,$base,$mdp,$base);
-  $mysqli->set_charset('utf8');
-}
-
-//////////////
-//// HTML ////
-//////////////
-$p = "cdt?${matiere['cle']}$urladmin";
-$t = "Modification des types du cahier de texte - ${matiere['nom']}";
-// Haut de page, menu et message
-include('haut.php');
-
-// Aide générale
-?>
-
-  <div>
-    <a href="cdt?<?php echo $matiere['cle'].$urladmin; ?>">Revenir au cahier de texte de <?php echo $matiere['nom']; ?></a>
-  </div>
-
-  <div class="item aide">
-    <h3>Aide et explications</h3>
-    <p>Vous pouvez modifier ici les types d'entrée du cahier de texte. Ces modifications sont propres à la matière <?php echo $matiere['nom']; ?>.</p>
-    <p>Le <em>titre</em> sera affiché au début de chaque entrée. Il doit s'agit d'un nom singulier et commençant par une majuscule. Il peut être relativement long. Par exemple&nbsp;: «&nbsp;Séance de travaux pratiques&nbsp;», «&nbsp;Interrogation de cours&nbsp;»</p>
-    <p>La <em>clé</em> sera affichée dans le menu déroulant de recherche, précédé de «&nbsp;les&nbsp», ainsi que dans l'adresse des pages qui affichent ce type d'entrée&nbsp;: il faut donc que ce soit un pluriel, court, en un mot, sans majucule au début (sauf s'il le faut). Par exemple, «&nbsp;TP&nbsp;», «&nbsp;interros&nbsp;».</p>
-    <p>Si la case <em>Affiche une heure de fin</em> est décochée, les entrées de ce type n'auront qu'une heure de début&nbsp;: ce peut être le cas pour les événements qui ne durent pas (interrogations de cours, devoirs maison par exemple).</p>
-    <p>Pour supprimer un type, il est nécessaire qu'aucune entrée de ce type n'existe dans le cahier de texte.</p>
-  </div>
-
-<?php
-
-// Fonction d'affichage
-$deb_fin_pour = '
-        <option value="0">Début seulement</option>
-        <option value="1">Début et fin</option>
-        <option value="2">Pas d\'horaire mais date d\'échéance</option>
-';
-function affichage($r)  {
-  if ( $id = $r['id'] )  {
-    $debut = "Type n°${r['ordre']}&nbsp;: ${r['titre']}";
-    if ( $r['n'] )
-      $debut .= ( $r['n'] > 1 ) ? " (${r['n']} entrées dans le cahier de texte)" : " (${r['n']} entrée dans le cahier de texte)";
-    $valide1 = '';
-    $valide = "\n      <input type=\"submit\" name=\"modifie\" value=\"Valider\" title=\"Valider les modifications\">";
-    $monte = ( $r['ordre'] == 1 ) ? '' : "\n      <input type=\"submit\" name=\"monte\" value=\"&uarr;\" title=\"Remonter le type dans l'ordre d'apparition\">";
-    $descend = ( $r['ordre'] == $GLOBALS['max'] ) ? '' : "\n      <input type=\"submit\" name=\"descend\" value=\"&darr;\" title=\"Descendre le type dans l'ordre d'apparition\">";
-    $suppr = ( ( $GLOBALS['max'] == 1 ) || ( $r['n'] ) ) ? '' : "\n      <input type=\"submit\" name=\"supprime\" value=\"Supprimer\" title=\"Supprimer le type\">";
-    $deb_fin_pour = str_replace("\"${r['deb_fin_pour']}\"","\"${r['deb_fin_pour']}\" selected",$GLOBALS['deb_fin_pour']);
-  }
-  else  {
-    $debut = 'Nouveau type';
-    $valide1 = "\n    <input class=\"bouton\" type=\"submit\" name=\"modifie\" value=\"Valider\" title=\"Valider les modifications\">";
-    $valide = $monte = $descend = $suppr = '';
-    $deb_fin_pour = $GLOBALS['deb_fin_pour'];
-  }
-  echo <<<FIN
-  <div class="item admin">
-  <form action="?${GLOBALS['mat']}${GLOBALS['urladmin']}" method="post">$valide1
-    <h3>$debut</h3>
-    <p class="boutons">$valide$monte$descend$suppr
-    </p>
-    <p class="ligne"><label for="titre$id">Titre (singulier)&nbsp;: </label><input type="input" id="titre$id" name="titre" value="${r['titre']}" size="50"></p>
-    <p class="ligne"><label for="cle$id">Clé (pluriel)&nbsp;: </label><input type="input" id="cle$id" name="cle" value="${r['cle']}" size="50"></p>
-    <p class="ligne"><label for="deb_fin_pour$id">Affichage d'horaires&nbsp;:</label>
-      <select id="deb_fin_pour$id" name="deb_fin_pour">$deb_fin_pour
-      </select>
-    </p>
-    <input type="hidden" name="id" value="$id">
-  </form>
-  </div>
-
-
-FIN;
-  
-}
-
-// Formulaire vide pour un nouveau type
-$mat = $matiere['cle'];
-affichage(array('id' => 0, 'titre' => '', 'cle' => '', 'deb_fin_pour' => 1));
-
-// Affichage des types
-$resultat = $mysqli->query("SELECT MAX(ordre) FROM `cdt-types` WHERE matiere = ${matiere['id']}");
-$r = $resultat->fetch_row();
-$max = $r[0];
-$resultat->free();
-$resultat = $mysqli->query("SELECT ct.id, ct.ordre, ct.titre, ct.cle, ct.deb_fin_pour, COUNT(cdt.id) AS n
-                            FROM `cdt-types` AS ct LEFT JOIN cdt ON cdt.type = ct.id 
-                            WHERE ct.matiere = ${matiere['id']} GROUP BY ct.ordre");
-$mysqli->close();
-if ( $resultat->num_rows )  {
-  while ( $r = $resultat->fetch_assoc() )
-    affichage($r);
-  $resultat->free();
-}
-
-// Bas de page
-include('bas.php');
-?>
diff -urN cahier-de-prepa3.2.0/CHANGELOG.php cahier-de-prepa4.0.0/CHANGELOG.php
--- cahier-de-prepa3.2.0/CHANGELOG.php	2013-12-30 23:46:41.144081201 +0100
+++ cahier-de-prepa4.0.0/CHANGELOG.php	2014-08-28 01:25:17.108122449 +0200
@@ -1,130 +1,150 @@
-Version actuelle : 3.1.1 (30/12/13)
+Version actuelle : 4.0.0 (28/08/14)
 ===================
 Changements :
 1.0   31/08/11 Première version
-1.0.1 01/09/11 Bugs dans installation.php, ajout du planning de spé
-1.0.2 03/09/11 Bugs dans installation.php (merci O. Bouverot)
+1.0.1 01/09/11 Correction de bugs pour l'installation, ajout du planning de spé
+1.0.2 03/09/11 Correction de bugs pour l'installation (merci O. Bouverot)
 1.0.3 14/09/11 Séparation des fichiers README et CHANGELOG
-        Bug d'affichage du menu pour la partie administrative (merci O. Bouverot)
-        Problème d'accents dans upload.php et repertoires.php (merci C. Chevalier)
-1.0.4 14/09/11 Bug dans docs.php et upload.php pour les liens de téléchargement
-        lorsqu'il y a des accents pour certains navigateurs (merci T. Chaboud)
-1.0.5 15/09/11 Bug dans colles.php pour l'affichage du programme à modifier,
-        mauvais choix de matière si plusieurs disponibles (merci C. Chevalier)
-1.0.6 25/09/11 Bug d'affichage dans IE 7 : menu masqué (merci S. Boissière)
-        Corrections des boutons d'édition du cahier de texte dans textarea.js
-        Réglage du serveur MySQL dans config.php (attention, config.php doit
-        être modifié pour une mise à jour : ajouter "$serveur='localhost';")
-        Passage à 100 caractères pour les nom et titres de documents et de répertoires
-        (il faut modifier cela dans la base MySQL pour les bases déjà installées)
-1.0.7 26/09/11 Régression sur installation.php (merci O. Bouverot)
+  Bug d'affichage du menu pour la partie d'administration (merci O. Bouverot)
+  Problème d'accents pour les répertoires et documents (merci C. Chevalier)
+1.0.4 14/09/11 Correction de bug pour les liens de téléchargement
+  lorsqu'il y a des accents pour certains navigateurs (merci T. Chaboud)
+1.0.5 15/09/11 Correction de bug pour le programme de colle à modifier, mauvais
+  choix de matière si plusieurs disponibles (merci C. Chevalier)
+1.0.6 25/09/11 Correction de bug d'affichage du menu (IE7 ; merci S. Boissière)
+  Corrections des boutons d'édition du cahier de texte
+  Réglage du serveur MySQL dans le fichier de configuration
+  Passage à 100 caractères pour les nom et titres de documents et de répertoires
+1.0.7 26/09/11 Régression sur l'installation (merci O. Bouverot)
 2.0.1 05/09/12 Réécriture d'une grande partie des scripts. Ajout de nombreuses
-        fonctionnalités :
-        * accès en lecture protégé par mot de passe, de façon spécifique
-        * refonte et simplification de l'interface d'administration (et de l'aide)
-        * gestion des répertoires/documents (possibilité de renommer/déplacer les
-          répertoires et documents ne modifie plus les liens)
-        * affichage des répertoires et documents améliorée (avec icones)
-        * page d'accueil permettant une saisie rapide des données récurrentes
-        * possibilité de rendre non accessible chaque information/document séparément
-2.0.2 06/09/12 Corrections de bugs divers dans admin.php, colles.php, docs_admin.php,
-        haut.php, index.php, installation.php et matieres.php (merci O. Bouverot)
-2.1.0 09/09/12 Correction d'importants bugs de connexions avec IE, ajout des académies
-        dans installation.php, correction de bugs mineurs dans admin.php et pages.php.
-        Ajout de la possibilité de taper des commandes LaTeX via MathJax (merci à O.
-        Bouverot pour l'idée !)
-        Suppression du .htaccess (configuration à mettre dans la configuration Apache)
-2.1.1 12/09/12 Correction d'un important bug dans index.php sur la modification des pages
-        Correction de légers bugs dans docs_admin.php et pages.php
-        Correction d'une coquille dans debut.php et installation.php
-        Correction du bug des formulaires de sélection dans colles.php et cdt.php
-2.1.2 13/09/12 Correction d'un bug d'id multiples sur l'interface d'administration,
-        rendant inopérants les boutons de séances du cahier de texte
-2.1.3 18/11/12 Correction de la mise en ordre des documents/répertoires (docs_admin.php)
-        Correction de l'erreur 1690 (overflow handling) affectant MySQL 5.5.5 et suivants 
+  fonctionnalités :
+  * accès en lecture protégé par mot de passe, de façon spécifique
+  * refonte et simplification de l'interface d'administration (et de l'aide)
+  * gestion des répertoires/documents (possibilité de renommer/déplacer les
+    répertoires et documents ne modifie plus les liens)
+  * affichage des répertoires et documents améliorée (avec icônes)
+  * page d'accueil permettant une saisie rapide des données récurrentes
+  * possibilité de rendre non accessible chaque information/document séparément
+2.0.2 06/09/12 Corrections de bugs divers (merci O. Bouverot)
+2.1.0 09/09/12 Correction d'importants bugs de connexions avec IE, ajout des
+  académies à l'installation, correction de bugs mineurs.
+  Ajout de la possibilité de taper des commandes LaTeX via MathJax (merci à O.
+  Bouverot pour l'idée !)
+  Suppression du .htaccess (configuration à mettre dans la configuration Apache)
+2.1.1 12/09/12 Correction de bugs divers (modification des pages, moteur
+  interne, formulaires)
+2.1.2 13/09/12 Correction d'un bug sur les boutons de séances du cahier de texte
+2.1.3 18/11/12 Correction de la mise en ordre des documents/répertoires
+  Correction d'un bug affectant MySQL 5.5.5 et suivants 
 2.2.0 11/01/13 Nouvelles fonctionnalités :
-        * il n'est plus possible de mettre son utilisateur sans matière
-        * les composantes d'une matière (cahier de texte, programme de colles, documents)
-        sont désactivées par défaut
-        * une matière n'est plus affichée dans le menu si les trois composantes sont désactivées
-        * pages de documents : l'icone de répertoire devient celle d'un répertoire
-        ouvert lorsqu'un répertoire est ouvert
-        * on peut ajouter des liens vers les documents disponibles directement dans l'interface
-        administrative (admin.php, cdp.php, colles.php, index.php)
-        * un avertissement s'affiche pour prévenir la déconnexion et permet la reconnexion
-        sans recharger la page (donc sans perdre les données)
-3.0.0 23/08/13 Réécriture d'une partie du moteur interne. Ajout de nouvelles fonctionnalités :
-        * nouvelle gestion des informations récentes, dans une table séparée, affichées
-        sur le côté dans la partie publique
-        * flux RSS disponible pour les informations récentes
-        * modifications dans l'interface d'administration : possibilité de monter/descendre
-        des items plus facilement ; simplification globale ; aide réécrite et complétée
-        * plusieurs matières peuvent être affichées dans le menu de l'interface d'administration
-        * gestion des documents facilité : possibilité de mettre à jour un document
-        * ajout d'une page de préférences utilisateur, possibilité de valider par défaut 
-        la protection par mot de passe des documents
-        * clarification de la création et utilisation de compte utilisateur de type élève
-        * initialisation possible du planning des semaines
-        Correction du bug concernant l'enregistrement de document commençant par un accent
-3.0.1 27/08/13 Correction d'un bug pour la suppression des répertoires dans docs.php (merci
-        A. Senger), d'un bug de variable non définie dans index.php, d'une virgule manquante
-        dans le code SQL dans installation.php.
-        Correction d'un problème de définition de la table recents dans installation.php et
-        MAJSQL.sql. Ajout d'une balise link pour le flux RSS dans haut.php (merci A. Carrade).
-3.0.2 28/08/13 Correction d'un bug faisant apparaître, dans l'interface d'administration, comme
-        vides les répertoires ne contenant que des documents non visibles, dans docs.php ;
-        modification de la table reps dans installation.php et MAJSQL.php (merci PH. Jondot).
-        Suppression du exit de CHANGELOG.php
-3.0.3 03/09/13 Correction d'un bug dans docs.php modifiant les dates d'envoi et tailles de tous
-        les documents lors de la mise à jour d'un seul (merci PH. Jondot)
+  * il n'est plus possible de mettre son utilisateur sans matière
+  * les composantes d'une matière (cahier de texte, programme de colles,
+  documents) sont désactivées par défaut
+  * une matière n'est plus affichée dans le menu si les trois composantes sont
+  désactivées
+  * pages de documents : l'icône de répertoire devient celle d'un répertoire
+  ouvert lorsqu'un répertoire est ouvert
+  * on peut ajouter des liens vers les documents disponibles directement dans
+  les textarea de l'interface d'administration
+  * un avertissement s'affiche pour prévenir la déconnexion et permet la
+  reconnexion sans recharger la page (donc sans perdre les données tapées)
+3.0.0 23/08/13 Réécriture d'une partie du moteur interne. Ajout de nouvelles
+  fonctionnalités :
+  * nouvelle gestion des informations récentes, dans une table séparée,
+  affichées sur le côté dans la partie publique
+  * flux RSS disponible pour les informations récentes
+  * modifications dans l'interface d'administration : possibilité de
+  monter/descendre des items plus facilement ; simplification globale ; aide
+  réécrite et complétée
+  * plusieurs matières peuvent être affichées dans le menu de l'interface
+  d'administration
+  * gestion des documents facilitée : possibilité de mettre à jour un document
+  * ajout d'une page de préférences utilisateur, possibilité de valider par
+  défaut la protection par mot de passe des documents
+  * clarification de l''utilisation de compte utilisateur de type élève
+  * initialisation possible du planning des semaines
+  Correction d'un bug pour l'enregistrement de document commençant par un accent
+3.0.1 27/08/13 Correction d'un bug pour la suppression des répertoires (merci
+  A. Senger), et de bugs divers, notamment à l'installation.
+  Ajout d'une balise link pour le flux RSS dans haut.php (merci A. Carrade).
+3.0.2 28/08/13 Correction d'un bug sur l'affichage des répertoires ne contenant
+  que des documents non visibles (merci PH. Jondot).
+3.0.3 03/09/13 Correction d'un bug modifiant les dates d'envoi et tailles de
+  tous les documents lors de la mise à jour d'un seul (merci PH. Jondot)
 3.1.0 24/10/13 Nouvelles fonctionnalités :
-        * Modifications des cahiers de texte (possibilité de voir toute l'année, affiche/efface
-        les horaires en fonction du type de séance dans l'interface administrative)
-        * Possibilité de prévisualiser les textes tapés en textarea
-        * Icones dans les informations récentes, meilleure présentation des informations récentes,
-        liens pour les documents dans le flux RSS
-        Corrections de bugs :
-        * Textarea pour les informations (admin.php et index.php) qui ne s'affiche pas au dépliage
-        sous certaines versions de Safari/Safari Mobile (merci F. Evrard)
-        * Rechargement anormal de la page courante après reconnexion via javascript (timeout.js)
-        * Affichage des documents dans l'ordre "naturel" (merci PJ. Desnoux)
-        * Affichage de l'icone pour les documents à extension en majuscule
-        * Liens incorrects dans le flux RSS
-3.1.1 03/11/13 Correction d'un bug sur l'affichage du cahier de texte (merci O. Bouverot) et d'un
-        problème de définition MySQL des liens des informations récentes (merci A. Carrade)
+  * Modifications des cahiers de texte (voir toute l'année, affiche/efface
+  les horaires en fonction du type de séance dans l'interface d'administration)
+  * Possibilité de prévisualiser les textes tapés en textarea
+  * Icônes dans les informations récentes, meilleure présentation des
+  informations récentes, liens pour les documents dans le flux RSS
+  Corrections de bugs :
+  * Textarea pour les informations qui ne s'affiche pas au dépliage sous
+  certaines versions de Safari/Safari Mobile (merci F. Evrard)
+  * Rechargement anormal de la page courante après reconnexion via javascript
+  * Affichage des documents dans l'ordre "naturel" (merci PJ. Desnoux)
+  * Affichage de l'icône pour les documents à extension en majuscule
+  * Liens incorrects dans le flux RSS
+3.1.1 03/11/13 Correction d'un bug sur l'affichage du cahier de texte (merci
+  O. Bouverot) et sur les liens des informations récentes (merci A. Carrade)
 3.2.0 30/12/13 Améliorations et nouvelles fonctionnalités :
-        * Modification de la page de lecture du flux RSS : affichage complet + lien
-        * Ordre d'affichage des documents modifiable
-        * Possibilité d'associer des pages d'informations à une matière
-        * Possibilité d'afficher dans le menu des liens directs vers des répertoires
-        Corrections de bugs :
-        * Noms de documents contenant un / tronqués (merci E. Blanc)
-        * Propagation de la demande d'identification pour les répertoires et documents mal réalisée
+  * Modification de la page de lecture du flux RSS : affichage complet + lien
+  * Ordre d'affichage des documents modifiable
+  * Possibilité d'associer des pages d'informations à une matière
+  * Possibilité d'afficher dans le menu des liens directs vers des répertoires
+  Corrections de bugs :
+  * Noms de documents contenant un / tronqués (merci E. Blanc)
+  * Propagation de la demande d'identification pour les répertoires et documents
+  mal réalisée
+4.0.0 28/08/14 Réécriture d'une partie du moteur interne. Séparation des
+  fichiers de la partie publique et de l'interface d'administration.
+  Nouvelles fonctionnalités :
+  * Nouvelle gestion des utilisateurs : comptes élèves/colleurs/professeurs
+  personnels, gestion de l'oubli du mot de passe, demande de création de compte
+  * Amélioration de la protection d'accès des contenus (plus de possibilités)
+  * Préférences supplémentaires, identité complète, adresse mail, timeout
+  * Possibilité d'envoi de mail avec choix de destinataires
+  * Gestion des notes de colles :
+    * saisie par les colleurs sur la partie publique et par les professeurs sur
+    l'interface d'administration
+    * visualisation par les élèves une fois connectés
+    * visualisation par les professeurs dans l'interface d'administration
+  * Possibilité de regrouper les entrées des cahiers de texte par jour ou par
+  semaine, et de gérer l'ensemble beaucoup plus précisément
+  * Possibilité de mettre un titre vide dans les informations
+  * Possibilité de supprimer toutes les informations d'une page
+  * Possibilité de supprimer les répertoires non vides et leur contenu
+  * Possibilité de supprimer toutes les entrées du cahier de texte, tous les
+  programmes de colles, tous les documents, toutes les notes pour une matière
+  * Suppression simplifiée d'une matière
+  * Modifications dans l'organisation de l'interface d'administration
+  * Titre du site unifié et modifiable
+  * Développement important de l'aide
+  * Meilleur cloisonnement des matières dans l'interface d'administration
+  * Simplification de la gestion du planning annuel
+  * Nouvelle méthode de sauvegarde plus efficace
+  * Prise en charge de JQuery 1.11 (gain de rapidité)
+  * Simplification du fichier de configuration
+  * Simplification de l'installation
+  * Menu automatiquement minimisé s'il ne rentre pas dans la fenêtre
 ===================
 
-[ 3.3 ] Février 2014
- * gestion des informations récentes : choix des notifications, suppressions/modifications
- * gestion *des* flux RSS : plusieurs flux avec choix possible de ce qui y apparait
- * gestion personnelle du time-out
+Todo :
 
-[ 3.4 ] Avril 2014
- * suppression multiple d'informations, d'informations récentes
- * exportation des données
- 
-[ 4.0 ] Août 2014
- * vraie version mobile
- * vérification des saisies de texte et aide au formatage en HTML, ajout automatique
- de balises <p>, suppression des fin de lignes et lignes vides finales
- * choix du regroupement dans le cahier de texte, par séance, par jour, par semaine
- * accès protégé hors élèves (profs et colleurs)
- * programmes de colles/cahiers de texte en pdf : gestion spéciale
- * choix du regroupement des colles, par semaine ou quinzaine
- * gestion des sauvegardes automatiques : élimination des anciennes, exportation
- * paramétrage des styles pour les titres, des couleurs
- * copié/collé depuis Word ?
- * agenda ?
- * tags dans les cahiers de texte
- * nouvelles matières : interdiction de garder la clé par défaut/d'avoir une clé déjà existante
- * Bug : si semaines mises à jour, il faut modifier les cahiers de texte et les programmes de colles
-dans la mesure du possible.
- * Bug : le déplacement d'un répertoire ne modifie pas les informations récentes ni le flux RSS
+[ 4.1 ] Novembre 2014
+ * Gestion des groupes de colles
+ * Gestion des informations récentes : suppression/modification
+ * Récupération des notes de colles en .xls
+
+[ 4.2 ] Avril 2015
+ * Récupération des documents en .zip
+ * Version mobile
+ * Agenda
+ * Récupération des données de la base (via la sauvegarde)
+
+[ 5.0 ] Août 2015
+  * Flux RSS multiples et paramétrables
+  * Vérification des saisies de texte et aide au formatage en HTML, ajout
+  automatique de balises <p>, suppression des lignes vides initiales/finales...
+  * Paramétrage des styles pour les titres, des couleurs
+  * Copié/collé depuis Word
+  * Tags dans les cahiers de texte
diff -urN cahier-de-prepa3.2.0/colles.php cahier-de-prepa4.0.0/colles.php
--- cahier-de-prepa3.2.0/colles.php	2013-12-25 22:59:41.702166925 +0100
+++ cahier-de-prepa4.0.0/colles.php	2014-08-26 22:49:10.425057904 +0200
@@ -3,11 +3,13 @@
 define('OK',1);
 include('debut.php');
 
-// Recherche de la matière concernée (sur la partie publique, affichage seulement
-// si existe ; sur l'interface d'administration, affichage sans condition)
+///////////////////////////////////////////////
+// Validation de la requête : matière, n, nb //
+///////////////////////////////////////////////
+
+// Recherche de la matière concernée (affichage seulement si le programme existe)
 $mysqli = premiere_connexion();
-$mysqli->set_charset('utf8');
-$resultat = $mysqli->query('SELECT id, cle, nom, colles FROM matieres WHERE MOD(colles,2) OR '.var_export($admin,true));
+$resultat = $mysqli->query('SELECT id, cle, nom, colles_protection FROM matieres WHERE colles');
 if ( $resultat->num_rows )  {
   if ( !empty($_REQUEST) )
     while ( $r = $resultat->fetch_assoc() )
@@ -21,6 +23,14 @@
     $matiere = $resultat->fetch_assoc();
   }
   $resultat->free();
+  
+  // Si la page est protégée et l'utilisateur non autorisé
+  while ( $matiere['colles_protection'] > $autorisation )  {
+    $p = "colles?${matiere['cle']}";
+    $t = "Programme de colles - ${matiere['nom']}";
+    include('login.php');
+  }
+
 }
 // Si aucune matière présentant son programme de colles n'est enregistrée
 else  {
@@ -28,113 +38,6 @@
   exit('Cette page ne contient aucune information.');
 }
 
-///////////////////
-// Modifications //
-///////////////////
-if ( ( $admin ) && isset($_REQUEST['id']) && is_numeric($id = $_REQUEST['id']) )  {
-  // Connexion à la base de données
-  $mysqli->close();
-  $mysqli = new mysqli($serveur,"$base-adm",$mdp,$base);
-  $mysqli->set_charset('utf8');
-
-  // Vérification que l'identifiant de la semaine est valide
-  $resultat = $mysqli->query("SELECT DATE_FORMAT(s.debut,'%e/%m') AS debut, c.id, c.cache, c.texte FROM semaines AS s
-                              LEFT JOIN (SELECT id, texte, semaine, cache FROM colles WHERE matiere = ${matiere['id']}) AS c ON c.semaine=s.id
-                              WHERE s.id = $id AND s.colle = 1");
-  if ( $resultat->num_rows ) {
-    $r = $resultat->fetch_assoc();
-    $cid = $r['id'];
-    $resultat->free();
-    $_REQUEST['n'] = $id;
-    
-    // Sauvegarde de la table contenant les données
-    sauvegarde_mysql('colles');
-
-    // Pour les informations récentes
-    $titre_recent = "<img class=\"icone\" src=\"icones/colle.png\"> Colles du ${r['debut']} en ".addslashes($matiere['nom']);
-    $lien_recent = 'colles?'.addslashes($matiere['cle'])."&amp;n=$id";
-
-    // Ajout d'un nouveau programme de colles. Il ne faut pas pouvoir insérer un programme vide par erreur
-    if ( isset($_REQUEST['modifie']) && strlen($_REQUEST['texte']) )  {
-      
-      // Validation des données envoyées
-      $texte = $mysqli->real_escape_string($_REQUEST['texte']);
-      
-      // Si $cid n'est pas NULL : modification d'un programme existant. Sinon, nouveau programme
-      if ( $cid )  {
-        if ( $mysqli->query("UPDATE colles SET texte = '$texte' WHERE id = $cid") )  {
-          $message = "Le programme de colles de la semaine du ${r['debut']} a bien été modifié.";
-          // Ajout d'une information récente si programme diffusé
-          if ( !$r['cache'] )
-            recent($mysqli,2,$cid,$titre_recent,$lien_recent,$texte);
-        }
-        else
-          $message = "Le programme de colles de la semaine du ${r['debut']} n'a pas pu être modifié. Erreur MySQL n°".$mysqli->errno.', «'.$mysqli->error.'».';
-      }
-      else  {
-        $cache = ( isset($_REQUEST['cache']) ) ? 1 : 0;
-        if ( $mysqli->query("INSERT INTO colles SET texte = '$texte', semaine = $id, matiere = ${matiere['id']}, cache = $cache") )  {
-          $message = "Le programme de colles de la semaine du ${r['debut']} a bien été ajouté.";
-          // Ajout d'information récente si programme diffusé
-          if ( !$cache )
-            recent($mysqli,2,$mysqli->insert_id,$titre_recent,$lien_recent,$texte);
-          $mysqli->query('ALTER TABLE colles ORDER BY matiere,semaine');
-        }
-        else
-          $message = "Le programme de colles de la semaine du ${r['debut']} n'a pas pu être ajouté. Erreur MySQL n°".$mysqli->errno.', «'.$mysqli->error.'».';
-      }
-    }
-
-    elseif ( $cid )  {
-
-      // Positionnement "montré" (apparaît sur la partie publique)
-      if ( isset($_REQUEST['montre']) )  {
-        if ( $mysqli->query("UPDATE colles SET cache = 0 WHERE id = $cid") )  {
-          $message = "Le programme de colles de la semaine du ${r['debut']} a bien été diffusé, il apparaît désormais sur la partie publique.";
-          // Ajout d'information récente
-          recent($mysqli,2,$cid,$titre_recent,$lien_recent,$mysqli->real_escape_string($r['texte']));
-        }
-        else
-          $message = "Le programme de colles de la semaine du ${r['debut']} n'a pas pu être diffusé. Erreur MySQL n°".$mysqli->errno.', «'.$mysqli->error.'».';
-      }
-
-      // Positionnement "caché" (n'apparaît pas sur la partie publique)
-      elseif ( isset($_REQUEST['cache']) )  {
-        if ( $mysqli->query("UPDATE colles SET cache = 1 WHERE id = $cid") )  {
-          $message = "Le programme de colles du ${r['debut']} n'est plus diffusé&nbsp;: il n'apparaît plus sur la partie publique mais est toujours disponible ici pour modification ou diffusion.";
-          // Suppression de l'éventuelle information récente
-          recent($mysqli,2,$cid);
-        }
-        else
-          $message = "Le programme de colles de la semaine du ${r['debut']} n'a pas pu être caché. Erreur MySQL n°".$mysqli->errno.', «'.$mysqli->error.'».';
-      }
-
-      // Suppression
-      elseif ( isset($_REQUEST['supprime']) || !strlen($_REQUEST['texte']) )  {
-        if ( $mysqli->query("DELETE FROM colles WHERE id = $cid") )  {
-          $message = "Le programme de colles de la semaine du ${r['debut']} a bien été supprimé.";
-          // Suppression de l'éventuelle information récente
-          recent($mysqli,2,$cid);
-        }
-        else
-          $message = "Le programme de colles de la semaine du ${r['debut']} n'a pas pu être modifié. Erreur MySQL n°".$mysqli->errno.', «'.$mysqli->error.'».';
-      }
-    }
-  }
-  
-  // Mise à jour des champs 'colles' dans la table 'matieres' (pour le menu)
-  $mysqli->query('UPDATE matieres SET colles = colles - MOD(colles,2) + IF((SELECT id FROM colles WHERE matiere = matieres.id AND cache = 0 LIMIT 1),1,0)');
-
-  // Passage en connexion MySQL en lecture seulement
-  $mysqli->close();
-  $mysqli = new mysqli($serveur,$base,$mdp,$base);
-  $mysqli->set_charset('utf8');
-}
-
-//////////////////////////////////////
-// Validation de la requête : n, nb //
-//////////////////////////////////////
-
 // Fonction d'affichage des semaines
 function format_date($date)  {
   $semaine = array('dimanche','lundi','mardi','mercredi','jeudi','vendredi','samedi');
@@ -142,56 +45,37 @@
   return $semaine[substr($date,0,1)].' '.substr($date,7).' '.$mois[intval(substr($date,5,2))].' '.substr($date,1,4);
 }
 
-// Récupération des semaines
-$resultat = $mysqli->query("SELECT s.id AS sid, DATE_FORMAT(s.debut,'%w%Y%m%e') AS d, s.colle, s.vacances, c.id AS cid, c.cache
+// Récupération de l'ensemble des semaines
+$resultat = $mysqli->query("SELECT s.id AS sid, DATE_FORMAT(s.debut,'%w%Y%m%e') AS d, s.colle, s.vacances,
+                            IF(c.id IS NULL OR c.cache, ' disabled','') AS disabled
                             FROM semaines AS s
                             LEFT JOIN (SELECT id, semaine, cache FROM colles WHERE matiere = ${matiere['id']}) AS c ON c.semaine=s.id");
 $select_semaines = '';
-if ( $resultat->num_rows )  {
-  while ( $r = $resultat->fetch_assoc() )  {
-    switch ( $r['vacances'] )  {
-      case 0:
-        if ( $admin )  {
-          if ( $r['colle'] )  {
-            if ( is_null($r['cid']) )
-              $select_semaines .= "\n        <option value=\"${r['sid']}\">".format_date($r['d']).'</option>';
-            elseif ( $r['cache'] )
-              $select_semaines .= "\n        <option value=\"${r['sid']}\">".format_date($r['d']).' (non diffusé)</option>';
-            else
-              $select_semaines .= "\n        <option value=\"${r['sid']}\">".format_date($r['d']).' (déjà rempli)</option>';
-          }
-          else
-            $select_semaines .= "\n        <option value=\"${r['sid']}\" disabled>".format_date($r['d']).' (pas de colle)</option>';
-        }
-        else
-          $select_semaines .= ( is_null($r['cid']) || $r['cache'] ) ? "\n        <option value=\"${r['sid']}\" disabled>".format_date($r['d']).'</option>' : "\n        <option value=\"${r['sid']}\">".format_date($r['d']).'</option>';
-        $sid[] = $r['sid'];
-        break;
-      case 1:
-        $select_semaines .= "\n        <option value=\"${r['sid']}\" disabled>Vacances de Toussaint</option>";
-        break;
-      case 2:
-        $select_semaines .= "\n        <option value=\"${r['sid']}\" disabled>Vacances de Noël</option>";
-        break;
-      case 3:
-        $select_semaines .= "\n        <option value=\"${r['sid']}\" disabled>Vacances d'hiver</option>";
-        break;
-      case 4:
-        $select_semaines .= "\n        <option value=\"${r['sid']}\" disabled>Vacances de Pâques</option>";
-    }
+while ( $r = $resultat->fetch_assoc() )  {
+  switch ( $r['vacances'] )  {
+    case 0:
+      $select_semaines .= "\n        <option value=\"${r['sid']}\"${r['disabled']}>".format_date($r['d']).'</option>';
+      $sid[] = $r['sid'];
+      break;
+    case 1:
+      $select_semaines .= "\n        <option value=\"${r['sid']}\" disabled>Vacances de Toussaint</option>";
+      break;
+    case 2:
+      $select_semaines .= "\n        <option value=\"${r['sid']}\" disabled>Vacances de Noël</option>";
+      break;
+    case 3:
+      $select_semaines .= "\n        <option value=\"${r['sid']}\" disabled>Vacances d'hiver</option>";
+      break;
+    case 4:
+      $select_semaines .= "\n        <option value=\"${r['sid']}\" disabled>Vacances de Pâques</option>";
   }
-  $resultat->free();
-}
-else  {
-  $mysqli->close();
-  include('installation.php');
 }
+$resultat->free();
 
 // Validation de n (numéro de semaine à voir)
-if ( isset($_REQUEST['n']) && is_numeric($n = $_REQUEST['n']) )  {
-  if ( !in_array($n,$sid) )
-    $n = 1;
-}
+if ( isset($_REQUEST['n']) && is_numeric($n = $_REQUEST['n']) )
+  while ( !in_array($n,$sid) )
+    $n = ( $n < count($sid) ) ? $n+1 : 1;
 // Sans argument, on souhaite afficher le programme de colles de la semaine actuelle
 else {
   $date = ( isset($_REQUEST['date']) && preg_match('/\d{4}-\d{2}-\d{2}/',$_REQUEST['date']) ) ? '\''.trim($_REQUEST['date'],'\'').'\'' : 'NOW()';
@@ -201,8 +85,6 @@
     $r = $resultat->fetch_assoc();
     $n = $r['id'];
     $resultat->free();
-    if ( $admin && ( $m = array_search($n,$sid) ) )
-      $n = $sid[$m-1];
   }
   // Aucun résultat : l'année n'a pas encore commencé
   else
@@ -214,30 +96,24 @@
 if ( isset($_REQUEST['nb']) && is_numeric($nb = $_REQUEST['nb']) ) {
   if ( $nb < 1 )
     $nb = 1;
+  elseif ( $nb > count($sid) - $m )
+    $nb = count($sid) - $m;
 }
 else
-  // Par défaut : affichage de la semaine courante en partie publique, affichage
-  // aussi des semaines précédente et suivante sur l'interface d'administration
-  $nb = 1 + 2*$admin;
-if ( $nb > count($sid) - $m )
-  $nb = count($sid) - $m;
+  // Par défaut : affichage d'une seule semaine
+  $nb = 1;
 
 ////////////
 /// HTML ///
 ////////////
-$p = "colles?${matiere['cle']}$urladmin";
+$p = "colles?${matiere['cle']}";
 $t = "Programme de colles - ${matiere['nom']}";
 
-// Demander l'identification si la page est protégée et l'utilisateur non connecté
-if ( ( $matiere['colles'] == 3 ) && !$admin && !$lecteur )
-  include('login_lecture.php');
-
 // Haut de page, menu et message
 include('haut.php');
 
-// Formulaire de demande d'affichage
+// Formulaire de la demande des semaines à afficher
 $select_semaines = str_replace("\"$n\"","\"$n\" selected",$select_semaines);
-$u = strlen($urladmin) ? "\n    <input type=\"hidden\" name=\"admin\" value=\"\">" : '';
 echo <<<FIN
 
   <div class="item" id="recherche">
@@ -247,53 +123,31 @@
       <select name="n">$select_semaines
       </select>
       <input type="submit" name="" value="OK">
-    </p>$u
+    </p>
   </form>
   </div>
 
 
 FIN;
 
-// Aide générale pour l'interface d'administration
-if ( $admin )  {
-?>
-  <div class="item aide">
-    <h3>Aide et explications</h3>
-    <p>Vous pouvez ci-dessous modifier les programmes de colles. Vous pouvez choisir les semaines à afficher ci-dessus.</p>
-    <p>La case à cocher <em>Ne pas diffuser sur la partie publique</em> permet de cacher temporairement ce programme de colle, par exemple pour le diffuser ultérieurement.</p>
-    <p>Vous pouvez <em>cacher</em> ou <em>montrer</em> un programme de colles existant, c'est-à-dire le diffuser ou non sur la partie publique.</p>
-    <p>Vous pouvez supprimer un programme de colles existant par le bouton <em>Supprimer</em> ou en validant un texte vide.</p>
-    <p>Les semaines sont réglables <a href="semaines">ici</a>.</p>
-    <p>Il est possible de permettre l'accès à la page affichant ces programmes de colles uniquement aux visiteurs qui se sont identifiés. Cela est réglable avec vos <a href="prefs">préférences</a>. Il faut alors <a href="utilisateurs">créer un compte</a> de type élève pour permettre aux élèves d'accéder à cette page.</p>
-  </div>
-
-<?php
-}
-
 // Affichage des semaines concernées
 if ( $n )  {
 
   // Liens de navigation vers semaines précédentes/suivantes
   $nav = '  <div class="item">';
   if ( $m )
-    $nav .= "\n    <a class=\"prec\" href=\"?${matiere['cle']}&amp;n=". $sid[max($m-$nb,0)] ."&amp;nb=$nb$urladmin\">Semaines précédentes</a>";
+    $nav .= "\n    <a class=\"prec\" href=\"?${matiere['cle']}&amp;n=". $sid[max($m-$nb,0)] ."&amp;nb=$nb\">Semaines précédentes</a>";
   if (  $m+$nb < count($sid) )
-    $nav .= "\n    <a class=\"suiv\" href=\"?${matiere['cle']}&amp;n=". $sid[$m+$nb] ."&amp;nb=$nb$urladmin\">Semaines suivantes</a>";
+    $nav .= "\n    <a class=\"suiv\" href=\"?${matiere['cle']}&amp;n=". $sid[$m+$nb] ."&amp;nb=$nb\">Semaines suivantes</a>";
   $nav .= "\n  </div>\n\n";
   if ( $nb > 1 )
     echo $nav;
 
-  if ( $admin )
   // Récupération des données à afficher
-    $resultat = $mysqli->query("SELECT DATE_FORMAT(s.debut,'%w%Y%m%e') AS d, s.colle, s.vacances, c.texte, s.id, IF(c.cache,' checked','') AS cache 
-                                FROM semaines AS s
-                                LEFT JOIN (SELECT texte, semaine, cache FROM colles WHERE matiere = ${matiere['id']}) AS c ON c.semaine=s.id
-                                WHERE s.id BETWEEN $n AND ${sid[$m+$nb-1]}" );
-  else
-    $resultat = $mysqli->query("SELECT DATE_FORMAT(s.debut,'%w%Y%m%e') AS d, s.colle, s.vacances, c.texte 
-                                FROM semaines AS s
-                                LEFT JOIN (SELECT texte, semaine FROM colles WHERE matiere = ${matiere['id']} AND cache = 0) AS c ON c.semaine=s.id
-                                WHERE s.id BETWEEN $n AND ${sid[$m+$nb-1]}" );
+  $resultat = $mysqli->query("SELECT DATE_FORMAT(s.debut,'%w%Y%m%e') AS d, s.colle, s.vacances, c.texte 
+                              FROM semaines AS s
+                              LEFT JOIN (SELECT texte, semaine FROM colles WHERE matiere = ${matiere['id']} AND cache = 0) AS c ON c.semaine=s.id
+                              WHERE s.id BETWEEN $n AND ${sid[$m+$nb-1]}" );
   // Gestion du titre
   while ( $r = $resultat->fetch_assoc() )  {
     switch ( $r['vacances'] )  {
@@ -313,65 +167,13 @@
         $titre = ucfirst(format_date($r['d']))."&nbsp;: Vacances de Pâques";
         break;
     }
-    // Affichage, différent en partie publique/interface d'administration, et pour
-    //  une semaine avec ou sans colles
-    if ( $admin )  {
-      if ( $r['colle'] )  {
-        if ( is_null($r['texte']) )  {
-          $titre .= ' (à remplir)';
-          $valide1 = "\n    <input class=\"bouton\" type=\"submit\" name=\"modifie\" value=\"Valider\" title=\"Valider les modifications\">";
-          $classe_cache = $boutons = '';
-          $cache = "\n    <p class=\"ligne\"><label for=\"cache\">Ne pas diffuser sur la partie publique&nbsp;: </label><input type=\"checkbox\" id=\"cache\" name=\"cache\" value=\"1\"></p>";
-        }
-        else  {
-          if ( $r['cache'] )  {
-            $valide1 = $cache = '';
-            $classe_cache = ' cache';
-            $titre .= ' (programme non diffusé sur la partie publique)';
-            $boutons = "
-    <p class=\"boutons\">
-      <input type=\"submit\" name=\"modifie\" value=\"Valider\" title=\"Valider les modifications sur le texte\">
-      <input type=\"submit\" name=\"montre\" value=\"Montrer\" title=\"Diffuser le programme, le rendre visible sur la partie publique\">
-      <input type=\"submit\" name=\"supprime\" value=\"Supprimer\" title=\"Supprimer le programme de colles\">
-    </p>";
-          }
-          else  {
-            $valide1 = $cache = $classe_cache = '';
-            $boutons = "
-    <p class=\"boutons\">
-      <input type=\"submit\" name=\"modifie\" value=\"Valider\" title=\"Valider les modifications sur le texte\">
-      <input type=\"submit\" name=\"cache\" value=\"Cacher\" title=\"Ne plus diffuser le programme, le rendre invisible sur la partie public\">
-      <input type=\"submit\" name=\"supprime\" value=\"Supprimer\" title=\"Supprimer le programme de colles\">
-    </p>";
-          }
-        }
-        echo <<<FIN
-  <div class="item admin$classe_cache">
-  <form action="" method="post">$valide1
-    <h3>$titre</h3>$boutons
-    <textarea name="texte" rows="15" cols="100">${r['texte']}</textarea>$cache
-    <input type="hidden" name="id" value="${r['id']}">
-  </form>
-  </div>
-
-
-FIN;
-      }
-      else  {
-        $texte = ( $r['vacances'] ) ? '' : "\n    <p>Il n'y a normalement pas de colles cette semaine-là. S'il s'agit d'une erreur, cela est modifiable <a href=\"semaines\">sur la page des semaines</a></p>";
-        echo <<<FIN
-  <div class="item admin">
-    <h3>$titre</h3>$texte
-  </div>
-
-
-FIN;
-      }
-    }
-    else  {
-      if ( $r['colle'] )  {
-        $texte = ( is_null($r['texte']) ) ? '    <p>Le programme de colles de cette semaine n\'est pas défini.</p>' : $r['texte'];
-        echo <<<FIN
+    
+    // Affichage
+    if ( $r['colle'] )
+      $texte = ( is_null($r['texte']) ) ? '    <p>Le programme de colles de cette semaine n\'est pas défini.</p>' : $r['texte'];
+    else
+      $texte = ( $r['vacances'] ) ? '' : '    <p>Il n\'y a pas de colles cette semaine.</p>';
+      echo <<<FIN
   <div class="item colle">
     <h3>$titre</h3>
 $texte
@@ -379,20 +181,9 @@
 
 
 FIN;
-      }
-      else  {
-        $texte = ( $r['vacances'] ) ? '' : "\n    <p>Il n'y a pas de colles cette semaine.</p>";
-        echo <<<FIN
-  <div class="item admin">
-    <h3>$titre</h3>$texte
-  </div>
-
-
-FIN;
-      }
-    }
   }
   $resultat->free();
+  
   // Liens de navigation vers semaines précédentes/suivantes
   echo $nav;
 }
@@ -400,9 +191,6 @@
   echo "  <h2>L'année n'a pas encore commencé... Revenez à la rentrée&nbsp;!</h2>\n\n";
 $mysqli->close();
 
-if ( $admin )
-  echo "  <script type=\"text/javascript\" src=\"textarea.js.php?m=${matiere['id']}\"></script>\n\n";
-
 // Bas de page
 include('bas.php');
 ?>
diff -urN cahier-de-prepa3.2.0/config.php cahier-de-prepa4.0.0/config.php
--- cahier-de-prepa3.2.0/config.php	2012-09-06 17:33:08.000000000 +0200
+++ cahier-de-prepa4.0.0/config.php	2014-08-27 02:41:41.241504331 +0200
@@ -1,45 +1,19 @@
 <?php
-/////////////////////////////////////////////////
 // Fichier de configuration de Cahier de Prépa //
-/////////////////////////////////////////////////
-// Vous devez ici configurer votre Cahier de Prépa
-/* Une fois ce fichier rempli et sauvegardé, vous devez :
- * Vous assurer qu'un serveur MySQL fonctionne sur le même serveur. Vous aurez
-   besoin du mot de passe root de ce serveur.
- * Vous assurer que les répertoires documents et sauvegarde sont accessibles en
-   écriture par l'utilisateur d'Apache (ce sera vérifié pendant l'installation).
- * Aller dans votre navigateur et charger le site pour terminer l'installation.
-*/
 
-// Nom de la classe et du lycée. Le site affichera "la classe $classe" et 
-// "le lycée $lycee" dans ses titres.
-$classe = '';
-$lycee = '';
-
-// Adresses web de la partie publique et de l'interface d'administration
+// Adresse web
 // Ne pas mettre le protocole ("http://" ou "https://") ni de "/" final
-/* Plusieurs possibilités sont offertes. Par exemple :
- * $site='classe-lycee.fr' et $siteadmin='admin.classe-lycee.fr'
- * $site='classe-lycee.fr' et $siteadmin='classe-lycee.fr/admin'
- * $site='www.domaine.com/classe' et $siteadmin='www.domaine.com/classeadmin'
- * $site='(une des valeurs précédentes)' et $siteadmin=''
-Dans le cas où $site et$siteadmin ne dirigent pas vers le même répertoire, il
-faut les rendre identiques, par exemple avec un lien symbolique entre les deux.
-Dans le cas où $siteadmin est vide, les adresses de l'interface sont fabriquées
-en rajoutant le paramètre "?admin" à la fin.*/
 $site = '';
-$siteadmin = '';
 
-// Réglage pour forcer l'affichage du site d'administration en https
-// Valeurs possibles : true ou false. Il est fortement conseillé de laisser true.
+// Forçage du https pour toute authentification
 $https = true;
 
 // Accès à la base de données MySQL
 /* $serveur est le nom du serveur. localhost est la valeur par défaut.
  * $base est le nom de la base de données utilisée. 12 caractères maximum.
- * $mdp est le mot de passe qui sera utilisé en interne uniquement, et demandé une
-fois unique pour le script d'installation que vous devez à lancer pour terminer
-l'installation. */
+ * $mdp est le mot de passe qui sera utilisé en interne uniquement, et demandé
+une fois unique par le script d'installation que vous devez à lancer pour
+terminer l'installation. */
 $serveur = 'localhost';
 $base = '';
 $mdp = '';
diff -urN cahier-de-prepa3.2.0/connect.php cahier-de-prepa4.0.0/connect.php
--- cahier-de-prepa3.2.0/connect.php	2013-01-04 15:02:46.000000000 +0100
+++ cahier-de-prepa4.0.0/connect.php	2014-08-26 14:45:57.292130120 +0200
@@ -1,11 +1,177 @@
 <?php
 // Sécurité et récupération des données de configuration
-// Mode admin obligatoire (OK=2)
-define('OK',2);
-// Ce script sert à des connexion de type AJAX. Aucun HTML ne doit être renvoyé.
-define('AJAX',1);
+define('OK',1);
 include('debut.php');
+$p = 'connect';
+// Titre : on récupère celui de la première page
+$mysqli = premiere_connexion();
+$resultat = $mysqli->query('SELECT titre FROM pages WHERE id = 1');
+$r = $resultat->fetch_assoc();
+$resultat->free();
+$t = "${r['titre']}";
 
-// Si on arrive ici, cela signifie que l'on est connecté correctement
-exit(json_encode(array('etat'=>1)));
+
+// Oubli d'identifiant/mot de passe -- copie dans /admin/login.php
+if ( isset($_REQUEST['oubli']) )  {
+
+  // Fonctions de connexion/enregistrement
+  include('admin/fonctions.php');
+
+  // Traitement du formulaire
+  if ( isset($_REQUEST['mail']) )  {
+    // Recherche du mail dans la base de données
+    $resultat = $mysqli->query('SELECT id, login, mail FROM utilisateurs');
+    while ( $r = $resultat->fetch_assoc() )
+      if ( $r['mail'] == $_REQUEST['mail'] )  {
+        $utilisateur = $r;
+        break;
+      }
+    $resultat->free();
+    if ( isset($utilisateur) )  {
+      // Connexion à la base de données avec les droits d'écriture
+      $mysqli->close();
+      $mysqli = mysql_ecriture();
+      // Réinitialisation du mot de passe
+      $lettres = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789?./,;:!";
+      $mdp = '';
+      for ( $i = 0; $i < 8; $i++ )
+        $mdp .= substr($lettres,mt_rand(0,68),1);
+      requete('utilisateurs','UPDATE utilisateurs SET genre = MOD(genre,5)+5, mdp = \''.sha1($mdp)."' WHERE id = ${utilisateur['id']}");
+      // Envoi du mail
+      mail($utilisateur['mail'],'[Cahier de Prépa] Modification de votre mot de passe',
+"Bonjour
+
+Vous avez demandé à modifier votre mot de passe, correspondant à l'identifiant ${utilisateur['login']}. Ce mot de passe a donc été temporairement réinitialisé à la valeur \"$mdp\". Vous devez vous connecter avec ce mot de passe et le modifier immédiatement ensuite : ce mot de passe est à usage unique et sera automatiquement effacé après votre prochaine connexion.
+
+Cordialement,
+-- 
+Cahier de Prépa
+","From: ne-pas-repondre@cahier-de-prepa.fr\r\nContent-type: text/plain; charset=UTF-8");
+      $message = 'Le mot de passe a été réinitialisé et un mail a été envoyé.';
+    }
+    else
+      $message = 'Le mail fourni n\'est pas dans la base de données.';
+  }
+
+  // Formulaire de récupération du mail si mdp non déjà réinitialisé
+  if ( !isset($utilisateur) )  {
+    $t .= ' - Oubli du mot de passe';
+    include('haut.php');
+    $mysqli->close();
+?>
+<form class="warning" action="connect" method="post">
+  <p>Un mail contenant l'identifiant et un nouveau mot de passe temporaire sera envoyé par mail à l'adresse ci-dessous si elle est bien enregistrée&nbsp;:</p>
+  <p class="ligne"><label for="mail">Adresse mail&nbsp;: </label><input type="text" name="mail" id="mail"></p>
+  <p><input type="submit" name="Ok" value="Envoyer"></p>
+  <input type="hidden" name="oubli" value="1">
+</form>
+<?php
+    include('bas.php');
+    exit();
+  }
+}
+
+// Création de compte
+elseif ( isset($_REQUEST['creation']) )  {
+
+  // Fonctions de connexion/enregistrement
+  include('admin/fonctions.php');
+
+  // Traitement du formulaire
+  if ( isset($_REQUEST['valide']) )  {
+    // Validation des données envoyées
+    $genre = ( in_array($_REQUEST['genre'],array(1,2,3)) ) ? $_REQUEST['genre'] : 0;
+    $prenom = mb_convert_case($mysqli->real_escape_string($_REQUEST['prenom']),MB_CASE_TITLE,'UTF-8');
+    $nom = mb_convert_case($mysqli->real_escape_string($_REQUEST['nom']),MB_CASE_TITLE,'UTF-8');
+    $mail = strtolower($mysqli->real_escape_string($_REQUEST['mail']));
+    $mdp1 = sha1($_REQUEST['mdp1']);
+    $autor = ( $_REQUEST['autorisation'] == 2 ) ? 2 : 1;
+    $matiere = ( ( $autor == 2 ) && isset($_REQUEST['matiere']) && is_numeric($_REQUEST['matiere']) ) ? $_REQUEST['matiere'] : '';
+    // Login temporaire : connexion impossible tant que le compte n'est pas validé
+    // À la validation, les 8 premiers caractères seront supprimés
+    $login = 'tmp'.mt_rand(10000,99999).mb_strtolower(mb_substr($prenom,0,1,'UTF-8').str_replace(' ','_',$nom),'UTF-8');
+    // Écriture dans la base de données'
+    if ( $mdp1 != sha1($_REQUEST['mdp2']) )
+      $message = 'Les mots de passe donnés ne sont pas identiques.';
+    elseif ( strlen($mail) && !filter_var($mail,FILTER_VALIDATE_EMAIL) )
+      $message = 'L\'adresse mail est incorrecte.';
+    elseif ( strlen($prenom) && strlen($nom) && strlen($mdp1) )  {
+      // Connexion à la base de données avec les droits d'écriture
+      $mysqli->close();
+      $mysqli = mysql_ecriture();
+      if ( requete('utilisateurs',"INSERT INTO utilisateurs SET login = '$login', genre = $genre, prenom = '$prenom', nom = '$nom',
+                                   mail = '$mail', mdp = '$mdp1', autorisation = $autor, matieres = '$matiere', timeout = 900") )
+        $message = "La demande de création a bien été enregistrée. Vous recevrez un mail dès qu'elle aura été traitée par l'équipe pédagogique.";
+      $mysqli->close();
+      $mysqli = mysql_lecture();
+    }
+  }
+  
+  // Affichage du formulaire de demande
+  $t .= ' - Création de compte';
+  include('haut.php');
+  
+  // Récupération des matières
+  $resultat = $mysqli->query('SELECT id, nom FROM matieres');
+  $select_matieres = '';
+  while ( $r = $resultat->fetch_assoc() )
+    $select_matieres .= "        <option value=\"${r['id']}\">${r['nom']}</option>\n";
+  $mysqli->close();  
+?>
+  <div class="item admin">
+  <form action="" method="post">
+    <input class="bouton" type="submit" name="valide" value="Valider" title="Valider les modifications">
+    <h3>Demander une création de compte</h3>
+    <p class="ligne"><label for="genre">M&nbsp;/&nbsp;Mme&nbsp;/&nbsp;Melle&nbsp;:</label>
+      <select name="genre" id="genre">
+        <option value="1">M.</option>
+        <option value="2">Mme</option>
+        <option value="3">Melle</option>
+      </select>
+    </p>
+    <p class="ligne"><label for="prenom">Prénom&nbsp;: </label><input type="text" id="prenom" name="prenom" value="" size="50"></p>
+    <p class="ligne"><label for="nom">Nom&nbsp;: </label><input type="text" id="nom" name="nom" value="" size="50"></p>
+    <p class="ligne"><label for="mail">Adresse mail&nbsp;: </label><input type="text" id="mail" name="mail" value="" size="50"></p>
+    <p class="ligne"><label for="mdp1">Mot de passe&nbsp;: </label><input type="password" id="mdp1" name="mdp1" value=""></p>
+    <p class="ligne"><label for="mdp2">Confirmation&nbsp;: </label><input type="password" id="mdp2" name="mdp2" value=""></p>
+    <p class="ligne"><label for="autorisation">Élève ou colleur&nbsp;:</label>
+      <select name="autorisation" id="autorisation">
+        <option value="1">Élève</option>
+        <option value="2">Colleur</option>
+      </select>
+    </p>
+    <p class="ligne"><label for="matiere">Matière&nbsp;:</label>
+      <select name="matiere" id="matiere">
+<?php echo $select_matieres; ?>
+      </select>
+    </p>
+    <p>Une fois complétée, cette demande sera envoyée pour approbation à l'équipe pédagogique. La validation de votre demande sera faite manuellement et ne sera donc sûrement pas immédiate. Vous recevrez une confirmation par mail une fois cette validation réalisée.</p>
+    <p>L'adresse mail servira uniquement à l'envoi des mails par l'équipe pédagogique ou à la récupération d'un nouveau mot de passe en cas d'oubli. Renseignez une adresse que vous consultez fréquemment.</p>
+    <p>Le mot de passe vous est complètement personnel et ne sera divulgué à personne. Il est chiffré avant son stockage dans la base de données.</p>
+  </form>
+  </div>
+
+  <script type="text/javascript">
+$( function() {
+  $('#autorisation').change( function() { 
+    switch ( $(this).val() )  {
+      case '1' : $('#matiere').parent().hide(); break;
+      case '2' : $('#matiere').parent().show(); break;
+    }
+  });
+  $('#matiere').parent().hide();
+});
+  </script>
+
+<?php
+  include('bas.php');
+  exit();
+}
+
+// Gestion de la connexion
+$t .= ' - Connexion';
+include('login.php');
+
+// On n'arrive ici que si la connexion est établie : accès à l'espace utilisateur
+header("Location: https://$site/prefs");
 ?>
diff -urN cahier-de-prepa3.2.0/css/couleurs.css cahier-de-prepa4.0.0/css/couleurs.css
--- cahier-de-prepa3.2.0/css/couleurs.css	2013-12-25 15:37:23.913317712 +0100
+++ cahier-de-prepa4.0.0/css/couleurs.css	2014-08-26 10:29:37.467637963 +0200
@@ -9,7 +9,7 @@
 /* Style particuliers */
 .warning, .annonce { color: #A31F35; border: 1px solid #882C3C; }
 .note { color: #A31F35; }
-.prec, .suiv, #ordre { color: #555; }
+.prec, .suiv, #ordre, #oubli a { color: #555; }
 #bas { color: #444; background-color: #C4D2B6;  border-top: 1px solid black;}
 .admin, .cdt, .colle, .recents, #recherche, #parents { border: 1px solid #16210B; }
 
@@ -28,3 +28,5 @@
 #preview { background-color: #EEFFDD; }
 #preview > h2 { color: #444; background-color: #C4D2B6;  border-bottom: 1px solid black;}
 #preview > p { color: #444; background-color: #C4D2B6;  border-top: 1px solid black;}
+td.pasnote { color: #882C3C; }
+.texte_aide { color: #466; }
diff -urN cahier-de-prepa3.2.0/css/style.css cahier-de-prepa4.0.0/css/style.css
--- cahier-de-prepa3.2.0/css/style.css	2013-12-29 22:35:40.005180033 +0100
+++ cahier-de-prepa4.0.0/css/style.css	2014-08-27 18:25:54.703317249 +0200
@@ -17,10 +17,10 @@
 div, form { margin: 0; padding: 0; }
 img { border: none; }
 #colonne { width: 280px; float: left; margin: -1em 30px 3em; }
-#menu, #recent { padding: 1em 20px; }
-#recent { margin-top: 2em; }
+#menu {  padding: 1em 20px 0.7em; }
+#recent { margin-top: 2em; padding: 1em 20px; }
 #contenu { position: relative; margin: 0 30px 4em 340px; padding: 0 20px; }
-#bas { font-size: 0.8em; text-align: center; width: 100%; position: relative; left: 0; bottom: 0; margin-top: -3em; height: 3em; padding: 1em 0; clear: both; }
+#bas { font-size: 0.8em; text-align: center; width: 100%; position: fixed; left: 0; bottom: 0; height: 3em; padding: 0.8em 0 0 0; clear: both; }
 
 /* Styles particuliers */
 .warning { text-align: center; width: 50%; margin: 1em auto; padding: 0.5em 3%; }
@@ -31,9 +31,10 @@
 #ordre { font-size: 0.8em; margin: 0 4% 1.5em; }
 #ordre img { height: 1em; vertical-align: sub; }
 #ordre span + span { margin-left: 5%; }
+#oubli { font-size: 0.8em; }
 
 /* Liens non soulignés */
-.prec, .suiv, h3 a, #menu a, #recent a, #bas a { text-decoration: none; }
+.prec, .suiv, h3 a, #menu a, #recent a, #bas a, #oubli a { text-decoration: none; }
 
 /* Ombres : propriétés CSS3 incomprises d'IE8 et inférieurs */
 #menu, #recent, .admin, .cdt, .colle, .recents {
@@ -48,7 +49,8 @@
 }
 
 /* Dans le menu et dans les informations récentes */
-#menu div+div { margin-top: 0.3em; padding-top: 0.5em; }
+#menu div { padding-bottom: 0.2em; }
+#menu div+div { padding: 0.5em 0 0.2em; }
 #menu h3, #recent h3 { text-align: center; margin: 0 5% 0.5em; padding: 0.2em 5px; }
 #menu h4 { text-align: center; margin: 0; }
 #menu a { display: block; margin-bottom: 0.2em; padding: 0; }
@@ -72,12 +74,13 @@
 
 /* Pages d'informations, cahier de texte et programme de colles */
 .item { position: relative; margin-bottom: 1em; padding: 0 1% 1em; }
-.titre { text-decoration: underline; margin-top: 1em; }
-.item > div { margin-top: 1em; }
 .item, .item div, .item p { text-align: justify; }
-.cdt p { padding: 0; }
+.cdt { padding: 1em 1% 1.5em; }
+.cdt p { padding: 0; margin: 0.5em 2% 0; }
+.titre { text-decoration: underline; }
+.cdtsemaine { margin-top: 1.2em; }
 #recherche { padding: 0.5em 1%; }
-.cdtsemaine { margin: 1em 0; padding-top: 0.2em; }
+.notes { font-weight: 700; }
 
 /* Documents */
 #parents { margin: 0 2% 0.5em; padding: 1em 1%; }
@@ -91,7 +94,6 @@
 .icone, .rep a, .fic a, #parents a, p.rep input { margin-right: 0.8em; }
 
 /* Interface d'administration */
-#deconnexion { text-align: center; margin-bottom: 1em; }
 .aide { padding-top: 0.1em; }
 .aide + .aide { margin-top: -1em; }
 .bouton { margin: 0 4% 0 10px; float: right; }
@@ -101,15 +103,21 @@
 #aide_js p, p.boutons, p.ligne, p.symboles { padding: 0; width: 92%; margin: 0.5em 4% 0.2em; }
 input.ligne, textarea { width: 92%; margin: 0.5em 4% 0.2em; }
 p.ligne label { font-weight: 700; vertical-align: sub; }
-p.ligne input, p.ligne select, p.ligne code { width: 60%; float: right; }
+p.ligne input, p.ligne select, p.ligne code { width: 60%; float: right; margin-top: -0.2em; }
 p.ligne input[type="checkbox"] { width: 1em;}
 p.ligne code { padding-top: 0.4em; }
 p.ligne + textarea { margin-top: 0; }
-table#semaines, table#utilisateurs { width: 80%; margin: 1em 10%; border-collapse: collapse; }
-table#semaines td, table#semaines th, table#utilisateurs td, table#utilisateurs th { text-align: center; padding: 0.2em 5px; }
-table#semaines th, table#utilisateurs th { font-size: 1.2em; font-weight: 700; }
-table#semaines td, table#semaines th, table#utilisateurs td, table#utilisateurs th { width: 33.33%; }
-.timeEntry_control { float: right; margin: 3px 5px 0 0; }
+table.admin { width: 80%; margin: 1em 10%; border-collapse: collapse; }
+table.admin td { text-align: center; padding: 0.2em 5px; }
+table.admin th { font-size: 1.2em; font-weight: 700; padding: 0.2em 3%; }
+.checks { padding: 0.2em 0 !important; text-align: center; width: 10%; }
+th.semaines { vertical-align: bottom; padding: 1.3em 0 !important; text-align: center; }
+td.semaines { padding-bottom: 1em !important; vertical-align: bottom; }
+td.semaines span { display: block; font-weight: 700; margin: 0; padding: 0; width: 1.2em; 
+  transform: rotate(-90deg); -webkit-transform: rotate(-90deg);	-moz-transform: rotate(-90deg); -ms-transform: rotate(-90deg); -o-transform: rotate(-90deg);
+  filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=3); white-space: nowrap; zoom: 1; }
+td.pasnote { font-style: italic; }
+.timeEntry-control { float: right; margin: 3px 5px 0 0; }
 .datepick-trigger { float: right; margin: 5px 7px 0 0; }
 #message { position: fixed; left: 25%; z-index: 1; background-color: #FFBBBB; }
 #preview_fond { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: #000; opacity: 0.2; filter: alpha(opacity=20); z-index: 1000; }
diff -urN cahier-de-prepa3.2.0/debut.php cahier-de-prepa4.0.0/debut.php
--- cahier-de-prepa3.2.0/debut.php	2013-12-25 22:43:28.674135788 +0100
+++ cahier-de-prepa4.0.0/debut.php	2014-08-27 17:47:27.715243425 +0200
@@ -2,12 +2,49 @@
 // Sécurité : ne doit pas être exécuté s'il n'est pas lancé par un script autorisé
 if ( !defined('OK') )  exit();
 // Affichage des erreurs : à commenter en production
-//ini_set('display_errors',1);
+//ini_set('display_errors',1); error_reporting(E_ALL); ini_set('display_startup_errors',1);
 
 // Récupération de la configuration statique
 include('config.php');
 
-// Fonction de connexion à la base MySQL "mysqli(hote,user,mdp,base)" et vérification de la connexion
+//////////////
+// Session  //
+//////////////
+
+session_name(md5($site));
+session_set_cookie_params(0,strchr($site,'/').'/',str_replace(strchr($site,'/'),'',$site));
+session_start();
+$autorisation = 0;
+if ( isset($_SESSION[md5("$site")]) )  {
+  if ( ( $_SESSION['time'] < time() ) || ( $_SESSION['client'] != $_SERVER['HTTP_USER_AGENT'] ) || ( $_SESSION['ip'] != $_SERVER['REMOTE_ADDR'] ) )  {
+    $message_login = 'Vous devez vous connecter à nouveau, suite à une longue durée d\'inactivité.';
+    // Suppression du cookie et des données de session
+    $_SESSION = array();
+    setcookie(session_name(),'',time()-3600);
+    session_regenerate_id(true);
+  }
+  // Demande de déconnexion
+  elseif ( isset($_REQUEST['deconnexion']) )  {
+    $message = 'Vous avez bien été déconnecté.';
+    // Suppression du cookie et des données de session
+    $_SESSION = array();
+    setcookie(session_name(),'',time()-3600);
+    session_regenerate_id(true);
+  }
+  // Tout est ok : session valide pendant timeout
+  else  {
+    $_SESSION['time'] = time()+$_SESSION['timeout'];
+    $autorisation = $_SESSION['autorisation'];
+  }
+}
+
+// Mode https obligatoire si connecté
+if ( $autorisation && $https && ( $_SERVER['HTTPS'] != 'on' ) )  {
+  header("Location: https://{$_SERVER['HTTP_HOST']}{$_SERVER['REQUEST_URI']}");
+  exit();
+}
+
+// Fonction de connexion à la base MySQL et vérification de la connexion
 function premiere_connexion()  {
   $mysqli = new mysqli($GLOBALS['serveur'],$GLOBALS['base'],$GLOBALS['mdp'],$GLOBALS['base']);
   // Vérification de la connexion
@@ -17,216 +54,4 @@
   return $mysqli;
 }
 
-///////////////////////////////////////////
-// Bascule en interface d'administration //
-///////////////////////////////////////////
-// Accès à l'interface d'administration :
-//  * Si on se connecte à un script qui l'oblige (par OK=2)
-//  * Si on se connecte à $siteadmin
-//  * Si $siteadmin est vide et qu'on ajoute "&admin" aux urls
-if ( ( OK == 2 ) || ( $_SERVER['SERVER_NAME'].dirname($_SERVER['SCRIPT_NAME']) == $siteadmin ) || isset($_REQUEST['admin']) )  {
-
-  // Mode https ou non
-  if ( $https && ( $_SERVER['HTTPS'] != 'on' ) )  {
-    header("Location: https://{$_SERVER['HTTP_HOST']}{$_SERVER['REQUEST_URI']}");
-    exit();
-  }
-
-  // Gestion de la session
-  $admin = false;
-  $s = ( strlen($siteadmin) ) ? $siteadmin : $site;
-  session_name(md5("$site-admin"));
-  session_set_cookie_params(0,strchr($s,'/').'/',str_replace(strchr($s,'/'),'',$s));
-  session_start();
-  
-  // Si l'utilisateur est déjà connecté, on vérifie ses données
-  if ( isset($_SESSION[md5($site)]) )  {
-    $admin = true;
-    if ( ( $_SESSION['client'] != $_SERVER['HTTP_USER_AGENT'] ) || ( $_SESSION['ip'] != $_SERVER['REMOTE_ADDR'] ) )  {
-      $admin = false;
-      $message = 'Vous avez été déconnecté automatiquement suite à une action anormale. Si cela se reproduit, n\'hésitez pas à le signaler.';
-    }
-    // Si l'utilisateur veut se déconnecter, on le déconnecte
-    elseif ( isset($_REQUEST['deconnexion']) )  {
-      $admin = false;
-      $message = 'Vous avez bien été déconnecté.';
-    }
-    elseif ( $_SESSION['time'] < time() )  {
-      $admin = false;
-      $message = 'Vous avez été déconnecté automatiquement suite à une longue durée d\'inactivité. Merci de vous connecter à nouveau.';
-    }
-    // Déconnexion : suppression du cookie et des données de session
-    if ( !$admin )  {
-      $_SESSION = array();
-      setcookie(session_name(),'',time()-3600);
-      session_regenerate_id(true);
-    }
-    // Tout est ok : session valide pour 15 minutes encore
-    else  {
-      $_SESSION['time'] = time()+900;
-      // Remarque : pas de session_write_close() pour mettre à jour par prefs.php
-    }
-  }
-  
-  // Gestion de la connexion
-  if ( !$admin )
-    include('login_admin.php');
-
-  /////////////////////////////////////////////
-  // Fonction de sauvegarde des données MySQL
-  function sauvegarde_mysql($table)  {
-    if ( !is_dir('sauvegarde') || !is_executable('sauvegarde') || !is_writable('sauvegarde') )
-      return;
-    // Vérification que la sauvegarde est nécessaire (pas encore faite aujourd'hui)
-    $jour = date('Y-m-d');
-    if ( file_exists("sauvegarde/$table.$jour.php") )
-      return;
-    $s = <<<FIN
-<?php exit(); ?>
-------------------------------------------
--- Sauvegarde de la table $table  | $jour
-------------------------------------------
-
-
-FIN;
-    // Connexion à la base
-    $mysqli = new mysqli($GLOBALS['serveur'],$GLOBALS['base'],$GLOBALS['mdp'],$GLOBALS['base']);
-    $mysqli->set_charset('utf8');
-
-    // Récupération de la structure complète si besoin
-    $resultat = $mysqli->query("SHOW CREATE TABLE `$table`");
-    $r = $resultat->fetch_row();
-    $s .= "${r[1]}\n\n";
-    $resultat->free();
-
-    // Récupération de la structure
-    $resultat = $mysqli->query("SHOW COLUMNS FROM `$table`");
-    $s1 = "\nINSERT INTO $table (";
-    while ( $r = $resultat->fetch_row() )
-      $s1 .= "`${r[0]}`,";
-    $s1 = substr($s1,0,-1).') VALUES';
-    $resultat->free();
-    
-    // Récupération des données
-    $resultat = $mysqli->query("SELECT * FROM `$table`");
-    if ( $resultat->num_rows )  {
-      while ( $r = $resultat->fetch_row() )
-        $s1 .= "\n  ('".  str_replace('SEPARATEUR','\',\'',addslashes(implode('SEPARATEUR',$r)))  .'\'),';
-      $s1 = substr($s1,0,-1);
-      $resultat->free();
-    }
-    else
-      $s1 = '';
-    $mysqli->close();
-    
-    // Écriture dans le fichier de sauvegarde
-    $fichier = fopen("sauvegarde/$table.$jour.php",'wb');
-    fwrite($fichier, $s.$s1);
-    fclose($fichier);
-  }
-  
-  ///////////////////////////////////////////
-  // Fonction de mise à jour des nouveautés
-  function recent($mysqli,$type,$id,$titre='',$lien='',$texte='')  {
-    // type : 1->informations, 2->programmes de colles, 3->documents
-    // id : celui de l'information/le programme de colles/le document
-
-    // Sauvegarde de la table contenant les données
-    sauvegarde_mysql('recents');    
-
-    // Ajout dans la base de données, suppression si $titre est vide
-    if ( strlen($titre) )
-      $mysqli->query('REPLACE INTO recents SET id = '.($type*1000+$id).", heure = NOW(), titre = '$titre', lien = '$lien', texte = '$texte'");
-    else
-      $mysqli->query('DELETE FROM recents WHERE id = '.($type*1000+$id));
-    
-    // Pas la peine d'aller plus loin si la table n'a pas été modifiée.
-    if ( !($mysqli->affected_rows) )
-      return;
-
-    // Nettoyage des anciennes entrées
-    $mysqli->query('DELETE FROM recents WHERE DATEDIFF(NOW(),heure) > 30');
-    // Mise dans l'ordre, les plus récents en premier
-    $mysqli->query('ALTER TABLE recents ORDER BY heure DESC');
-    
-    // Regénération du flux RSS
-    $rep = 'documents/'.sha1($GLOBALS['base']);
-    $d = date(DATE_RSS);
-    $rss = <<<FIN
-<?xml version="1.0" encoding="UTF-8"?>
-<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
-  <channel>
-    <title>Cahier de prépa - ${GLOBALS['classe']} ${GLOBALS['lycee']}</title>
-    <atom:link href="http://${GLOBALS['site']}/$rep/rss.xml" rel="self" type="application/rss+xml" />
-    <link>http://${GLOBALS['site']}</link>
-    <description>Le site de la ${GLOBALS['classe']} du lycée ${GLOBALS['lycee']}</description>
-    <lastBuildDate>$d</lastBuildDate>
-    <language>fr-FR</language>
-
-
-FIN;
-    // Récupération des items
-    $resultat = $mysqli->query('SELECT UNIX_TIMESTAMP(heure) AS heure, titre, lien, texte FROM recents');
-    while ( $r = $resultat->fetch_assoc() )  {
-      $d = date(DATE_RSS,$r['heure']);
-      $titre = preg_replace('/^<[^>]*> /','',$r['titre']);
-      $texte = preg_replace('/href="([^h])/',"href=\"http://${GLOBALS['site']}/\\1",$r['texte']);
-      $rss .= <<<FIN
-    <item>
-      <title><![CDATA[$titre]]></title>
-      <link>http://${GLOBALS['site']}/${r['lien']}</link>
-      <guid isPermaLink="false">${r['heure']}</guid>
-      <description><![CDATA[$texte]]></description>
-      <pubDate>$d</pubDate>
-    </item>
-
-
-FIN;
-    }
-    $rss .= <<<FIN
-  </channel>
-</rss>
-
-FIN;
-
-    // Mise à jour du flux RSS
-    if ( !is_dir($rep) )  mkdir($rep);
-    $fichier = fopen($rep.'/rss.xml','wb');
-    fwrite($fichier, $rss);
-    fclose($fichier);
-  }
-  
-  // Paramètre supplémentaire "&admin" à passer dans les urls si $siteadmin est vide
-  $urladmin = ( strlen($siteadmin) ) ? '' : '&amp;admin';
-  $urladmin2 = ( strlen($siteadmin) ) ? '' : '?admin';
-  $lecteur = true;
-}
-
-/////////////////////////////////////////////////
-// Protection en lecture de la partie publique //
-/////////////////////////////////////////////////
-if ( !isset($admin) )  {
-  $admin = $lecteur = false;
-  $urladmin = '';
-
-  session_name(md5($site));
-  session_set_cookie_params(0,strchr($site,'/').'/',str_replace(strchr($site,'/'),'',$site));
-  session_start();
-  if ( isset($_SESSION[md5("$site-lecture")]) )  {
-    $lecteur = true;
-    if ( ( $_SESSION['time'] < time() ) || ( $_SESSION['client'] != $_SERVER['HTTP_USER_AGENT'] ) || ( $_SESSION['ip'] != $_SERVER['REMOTE_ADDR'] ) || ( isset($_REQUEST['deconnexion']) ) )  {
-      $lecteur = false;
-      $message2 = 'Vous devez vous connecter à nouveau, suite à une longue durée d\'inactivité.';
-      // Suppression du cookie et des données de session
-      $_SESSION = array();
-      setcookie(session_name(),'',time()-3600);
-      session_regenerate_id(true);
-    }
-    // Tout est ok : session valide pour 1 heure encore
-    else  {
-      $_SESSION['time'] = time()+3600;
-      session_write_close();
-    }
-  }
-}
 ?>
diff -urN cahier-de-prepa3.2.0/definition_semaines.php cahier-de-prepa4.0.0/definition_semaines.php
--- cahier-de-prepa3.2.0/definition_semaines.php	2013-08-23 03:22:47.447302883 +0200
+++ cahier-de-prepa4.0.0/definition_semaines.php	1970-01-01 01:00:00.000000000 +0100
@@ -1,39 +0,0 @@
-<?php
-switch ( $_REQUEST['zone'] )  {
-  case 'A':
-    $semaines = <<<FIN
-INSERT INTO semaines (debut, colle, vacances) VALUES
-  ('2013-09-03',0,0),('2013-09-09',0,0),('2013-09-16',0,0),('2013-09-23',1,0),('2013-09-30',1,0),('2013-10-07',1,0),('2013-10-14',1,0),
-  ('2013-10-21',0,1),('2013-11-04',1,0),('2013-11-12',1,0),('2013-11-18',1,0),('2013-11-25',1,0),('2013-12-02',1,0),('2013-12-09',1,0),
-  ('2013-12-16',1,0),('2013-12-23',0,2),('2014-01-06',1,0),('2014-01-13',1,0),('2014-01-20',1,0),('2014-01-27',1,0),('2014-02-03',1,0),
-  ('2014-02-10',1,0),('2014-02-17',1,0),('2014-02-24',1,0),('2014-03-03',0,3),('2014-03-17',1,0),('2014-03-24',1,0),('2014-03-31',1,0),
-  ('2014-04-07',1,0),('2014-04-14',1,0),('2014-04-22',1,0),('2014-04-28',0,4),('2014-05-12',1,0),('2014-05-20',1,0),('2014-05-26',1,0),
-  ('2014-06-02',1,0),('2014-06-10',1,0),('2014-06-16',1,0),('2014-06-23',1,0),('2014-06-30',1,0);
-
-FIN;
-    break;
-  case 'B':
-    $semaines = <<<FIN
-INSERT INTO semaines (debut, colle, vacances) VALUES
-  ('2013-09-03',0,0),('2013-09-09',0,0),('2013-09-16',0,0),('2013-09-23',1,0),('2013-09-30',1,0),('2013-10-07',1,0),('2013-10-14',1,0),
-  ('2013-10-21',0,1),('2013-11-04',1,0),('2013-11-12',1,0),('2013-11-18',1,0),('2013-11-25',1,0),('2013-12-02',1,0),('2013-12-09',1,0),
-  ('2013-12-16',1,0),('2013-12-23',0,2),('2014-01-06',1,0),('2014-01-13',1,0),('2014-01-20',1,0),('2014-01-27',1,0),('2014-02-03',1,0),
-  ('2014-02-10',1,0),('2014-02-17',1,0),('2014-02-24',0,3),('2014-03-10',1,0),('2014-03-17',1,0),('2014-03-24',1,0),('2014-03-31',1,0),
-  ('2014-04-07',1,0),('2014-04-14',1,0),('2014-04-21',0,4),('2014-05-05',1,0),('2014-05-12',1,0),('2014-05-20',1,0),('2014-05-26',1,0),
-  ('2014-06-02',1,0),('2014-06-10',1,0),('2014-06-16',1,0),('2014-06-23',1,0),('2014-06-30',1,0);
-
-FIN;
-    break;
-  case 'C':
-    $semaines = <<<FIN
-INSERT INTO semaines (debut, colle, vacances) VALUES
-  ('2013-09-03',0,0),('2013-09-09',0,0),('2013-09-16',0,0),('2013-09-23',1,0),('2013-09-30',1,0),('2013-10-07',1,0),('2013-10-14',1,0),
-  ('2013-10-21',0,1),('2013-11-04',1,0),('2013-11-12',1,0),('2013-11-18',1,0),('2013-11-25',1,0),('2013-12-02',1,0),('2013-12-09',1,0),
-  ('2013-12-16',1,0),('2013-12-23',0,2),('2014-01-06',1,0),('2014-01-13',1,0),('2014-01-20',1,0),('2014-01-27',1,0),('2014-02-03',1,0),
-  ('2014-02-10',1,0),('2014-02-17',0,3),('2014-03-03',1,0),('2014-03-10',1,0),('2014-03-17',1,0),('2014-03-24',1,0),('2014-03-31',1,0),
-  ('2014-04-07',1,0),('2014-04-14',0,4),('2014-04-28',1,0),('2014-05-05',1,0),('2014-05-12',1,0),('2014-05-20',1,0),('2014-05-26',1,0),
-  ('2014-06-02',1,0),('2014-06-10',1,0),('2014-06-16',1,0),('2014-06-23',1,0),('2014-06-30',1,0);
-
-FIN;
-}
-?>
diff -urN cahier-de-prepa3.2.0/def_sql.php cahier-de-prepa4.0.0/def_sql.php
--- cahier-de-prepa3.2.0/def_sql.php	1970-01-01 01:00:00.000000000 +0100
+++ cahier-de-prepa4.0.0/def_sql.php	2014-08-28 12:35:05.745408491 +0200
@@ -0,0 +1,224 @@
+<?php
+// Définition de la base de données
+// À utiliser en tant qu'utilisateur root
+// Champs à remplacer : $base, $mdp, $serveur, $cle_matiere, $nom_matiere,
+// $login, $nom, $prenom, $genre, $mot_de_passe, $mail, $titre
+//
+// Ces définitions sont utilisables en php directement ou en shell linux par
+// sed '1,/FIN/d ; N;$!P;$!D;$d' def_sql.php | sed "s/\\\$base/$BASE/g;s/\\\$serveur/$SERVEUR/g;..."
+
+$requete = <<< FIN
+
+DROP DATABASE IF EXISTS `$base`;
+CREATE DATABASE `$base` DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;
+DELETE FROM mysql.user WHERE User = '$base' OR User = '$base-adm';
+INSERT INTO mysql.user (Host, User, Password) 
+  VALUES ('$serveur', '$base', PASSWORD('$mdp')),
+         ('$serveur', '$base-adm', PASSWORD('$mdp'));
+DELETE FROM mysql.db WHERE Db = '$base';
+INSERT INTO mysql.db (Host, Db, User, Select_priv, Insert_priv, Update_priv, Delete_priv, Alter_priv, Drop_priv) 
+  VALUES ('$serveur', '$base', '$base', 'Y', 'N', 'N', 'N', 'N', 'N'),
+         ('$serveur', '$base','$base-adm', 'Y', 'Y', 'Y', 'Y', 'Y', 'Y');
+FLUSH PRIVILEGES;
+USE `$base`;
+
+CREATE TABLE `matieres` (
+  `id` tinyint(2) unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT,
+  `ordre` tinyint(2) unsigned NOT NULL,
+  `cle` varchar(50) NOT NULL,
+  `nom` varchar(50) NOT NULL,
+  `colles` tinyint(1) unsigned NOT NULL,
+  `cdt` tinyint(1) unsigned NOT NULL,
+  `docs` tinyint(1) unsigned NOT NULL,
+  `notes` tinyint(1) unsigned NOT NULL,
+  `colles_protection` tinyint(1) unsigned NOT NULL,
+  `cdt_protection` tinyint(1) unsigned NOT NULL,
+  KEY `colles` (`colles`),
+  KEY `cdt` (`cdt`),
+  KEY `docs` (`docs`),
+  KEY `notes` (`notes`)
+) ENGINE=MyISAM DEFAULT CHARSET=utf8;
+
+CREATE TABLE `utilisateurs` (
+  `id` tinyint(2) unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT,
+  `login` varchar(50) NOT NULL UNIQUE,
+  `nom` varchar(50) NOT NULL,
+  `prenom` varchar(50) NOT NULL,
+  `genre` tinyint(1) UNSIGNED NOT NULL,
+  `mail` varchar(50) NOT NULL,
+  `autorisation` tinyint(1) UNSIGNED NOT NULL,
+  `mdp` char(40) NOT NULL,
+  `matieres` varchar(15) NOT NULL,
+  `timeout` smallint(4) UNSIGNED NOT NULL,
+  `mailexp` tinyint(1) UNSIGNED NOT NULL,
+  `mailcopy` tinyint(1) UNSIGNED NOT NULL
+) ENGINE=MyISAM DEFAULT CHARSET=utf8;
+
+CREATE TABLE `pages` (
+  `id` tinyint(2) unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT,
+  `ordre` tinyint(2) unsigned NOT NULL,
+  `cle` varchar(50) NOT NULL,
+  `mat` tinyint(2) NOT NULL,
+  `nom` varchar(50) NOT NULL,
+  `titre` text NOT NULL,
+  `bandeau` text NOT NULL,
+  `protection` tinyint(1) unsigned NOT NULL
+) ENGINE=MyISAM DEFAULT CHARSET=utf8;
+
+CREATE TABLE `infos` (
+  `id` smallint(4) unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT,
+  `ordre` tinyint(2) unsigned NOT NULL,
+  `page` tinyint(2) unsigned NOT NULL,
+  `cache` tinyint(1) unsigned NOT NULL,
+  `titre` text NOT NULL,
+  `texte` text NOT NULL,
+  KEY `ordre` (`ordre`,`page`),
+  KEY `cache` (`cache`)
+) ENGINE=MyISAM DEFAULT CHARSET=utf8;
+
+CREATE TABLE `semaines` (
+  `id` tinyint(2) unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT,
+  `debut` date NOT NULL,
+  `colle` tinyint(1) unsigned NOT NULL,
+  `vacances` tinyint(1) unsigned NOT NULL,
+  KEY `debut` (`debut`),
+  KEY `colle` (`colle`)
+) ENGINE=MyISAM DEFAULT CHARSET=utf8;
+
+CREATE TABLE `colles` (
+  `id` tinyint(3) unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT,
+  `semaine` tinyint(2) unsigned NOT NULL,
+  `matiere` tinyint(2) unsigned NOT NULL,
+  `texte` text NOT NULL,
+  `cache` tinyint(1) NOT NULL,
+  KEY `semaine` (`semaine`),
+  KEY `matiere` (`matiere`),
+  KEY `cache` (`cache`)
+) ENGINE=MyISAM DEFAULT CHARSET=utf8;
+
+CREATE TABLE `cdt` (
+  `id` smallint(3) unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT,
+  `matiere` tinyint(2) unsigned NOT NULL,
+  `semaine` tinyint(2) unsigned NOT NULL,
+  `jour` date NOT NULL,
+  `h_debut` time NOT NULL,
+  `h_fin` time NOT NULL,
+  `pour` date NOT NULL,
+  `type` tinyint(2) unsigned NOT NULL,
+  `texte` text NOT NULL,
+  `demigroupe` tinyint(1) unsigned NOT NULL,
+  `cache` tinyint(1) unsigned NOT NULL,
+  KEY `matiere` (`matiere`),
+  KEY `semaine` (`semaine`),
+  KEY `type` (`type`),
+  KEY `cache` (`cache`)
+) ENGINE=MyISAM DEFAULT CHARSET=utf8;
+
+CREATE TABLE `cdt-types` (
+  `id` tinyint(2) unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT,
+  `matiere` tinyint(2) unsigned NOT NULL,
+  `ordre` tinyint(2) unsigned NOT NULL,
+  `titre` varchar(50) NOT NULL,
+  `cle` varchar(20) NOT NULL,
+  `deb_fin_pour` tinyint(1) unsigned NOT NULL,
+  `nb` tinyint(2) unsigned NOT NULL,
+  `nb_v` tinyint(2) unsigned NOT NULL,
+  KEY `matiere` (`matiere`)
+) ENGINE=MyISAM DEFAULT CHARSET=utf8;
+
+CREATE TABLE `cdt-seances` (
+  `id` tinyint(2) unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT,
+  `matiere` tinyint(2) unsigned NOT NULL,
+  `ordre` tinyint(2) unsigned NOT NULL,
+  `nom` varchar(40) NOT NULL,
+  `jour` tinyint(1) unsigned NOT NULL,
+  `h_debut` time NOT NULL,
+  `h_fin` time NOT NULL,
+  `type` tinyint(3) unsigned NOT NULL,
+  `demigroupe` tinyint(1) unsigned NOT NULL,
+  KEY `matiere` (`matiere`)
+) ENGINE=MyISAM DEFAULT CHARSET=utf8;
+
+CREATE TABLE `reps` (
+  `id` tinyint(3) unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT,
+  `parent` tinyint(3) unsigned NOT NULL,
+  `parents` varchar(50) NOT NULL,
+  `matiere` tinyint(2) unsigned NOT NULL,
+  `nom` varchar(100) NOT NULL,
+  `nbrep` tinyint(2) unsigned NOT NULL,
+  `nbrep_v` tinyint(2) unsigned NOT NULL,
+  `nbdoc` tinyint(2) unsigned NOT NULL,
+  `nbdoc_v` tinyint(2) unsigned NOT NULL,
+  `protection` tinyint(1) unsigned NOT NULL,
+  `menu` tinyint(1) unsigned NOT NULL,
+  KEY `parent` (`parent`),
+  KEY `matiere` (`matiere`)
+) ENGINE=MyISAM  DEFAULT CHARSET=utf8;
+
+CREATE TABLE `docs` (
+  `id` smallint(3) unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT,
+  `parent` tinyint(3) unsigned NOT NULL,
+  `parents` varchar(50) NOT NULL,
+  `matiere` tinyint(2) unsigned NOT NULL,
+  `nom` varchar(100) NOT NULL,
+  `nom_nat` VARCHAR(100) NOT NULL,
+  `upload` date NOT NULL,
+  `taille` varchar(12) NOT NULL,
+  `lien` char(15) NOT NULL,
+  `ext` varchar(5) NOT NULL,
+  `protection` tinyint(1) unsigned NOT NULL,
+  KEY `parent` (`parent`),
+  KEY `matiere` (`matiere`),
+  KEY `nom_nat` (`nom_nat`)
+) ENGINE=MyISAM  DEFAULT CHARSET=utf8;
+
+CREATE TABLE `recents` (
+  `id` smallint(5) unsigned NOT NULL PRIMARY KEY,
+  `heure` datetime NOT NULL,
+  `titre` varchar(200) NOT NULL,
+  `lien` varchar(30) NOT NULL,
+  `texte` text NOT NULL,
+  KEY `heure` (`heure`)
+) ENGINE=MyISAM  DEFAULT CHARSET=utf8;
+
+CREATE TABLE notes (
+  `id` smallint(5) UNSIGNED NOT NULL PRIMARY KEY,
+  `semaine` tinyint(2) unsigned NOT NULL,
+  `eleve` tinyint(2) unsigned NOT NULL,
+  `colleur` tinyint(2) unsigned NOT NULL,
+  `matiere` tinyint(2) unsigned NOT NULL,
+  `note` varchar(2) NOT NULL
+) ENGINE=MyISAM  DEFAULT CHARSET=utf8;
+
+INSERT INTO utilisateurs (id,login,genre,prenom,nom,mail,mdp,autorisation,matieres,timeout,mailexp,mailcopy)
+  VALUES (1, '$login', $genre, '$prenom', '$nom', '$mail', '$mot_de_passe', 3, '1', 900, 1, 1);
+
+INSERT INTO matieres (id,ordre,cle,nom,colles,cdt,docs,notes,colles_protection,cdt_protection)
+  VALUES (1, 1, '$cle_matiere', '$nom_matiere', 0, 0, 0, 0, 0, 0);
+
+INSERT INTO reps (id,parent,parents,matiere,nom,nbrep,nbrep_v,nbdoc,nbdoc_v,protection,menu)
+  VALUES (1, 0, '0', 0, 'Général', 0, 0, 0, 0, 0, 0),
+         (2, 0, '0', 1, '$nom_matiere', 0, 0, 0, 0, 0, 0);
+
+INSERT INTO pages (ordre,cle,nom,titre,bandeau,protection)
+  VALUES (1, 'accueil', 'Accueil', '$titre', 'Dernières informations importantes', 0);
+
+INSERT INTO `cdt-types` (matiere,ordre,cle,titre,deb_fin_pour)
+  VALUES (1, 1, 'cours', 'Cours', 1),
+         (1, 2, 'TD', 'Séance de travaux dirigés', 1),
+         (1, 3, 'TP', 'Séance de travaux pratiques', 1),
+         (1, 4, 'DS', 'Devoir surveillé', 1),
+         (1, 5, 'interros', 'Interrogation de cours', 0),
+         (1, 6, 'distributions', 'Distribution de document', 0),
+         (1, 7, 'DM', 'Devoir maison', 2);
+
+INSERT INTO semaines (debut) VALUES ('2014-09-02'),('2014-09-08'),('2014-09-15'),('2014-09-22'),('2014-09-29'),
+  ('2014-10-06'),('2014-10-13'),('2014-10-20'),('2014-10-27'),('2014-11-03'),('2014-11-10'),('2014-11-17'),('2014-11-24'),
+  ('2014-12-01'),('2014-12-08'),('2014-12-15'),('2014-12-22'),('2014-12-29'),('2015-01-05'),('2015-01-12'),('2015-01-19'),('2015-01-26'),
+  ('2015-02-02'),('2015-02-09'),('2015-02-16'),('2015-02-23'),('2015-03-02'),('2015-03-09'),('2015-03-16'),('2015-03-23'),('2015-03-30'),
+  ('2015-04-07'),('2015-04-13'),('2015-04-20'),('2015-04-27'),('2015-05-04'),('2015-05-11'),('2015-05-18'),('2015-05-26'),
+  ('2015-06-01'),('2015-06-08'),('2015-06-15'),('2015-06-22'),('2015-06-29');
+UPDATE semaines SET colle = 1;
+
+FIN;
+?>
diff -urN cahier-de-prepa3.2.0/docs.php cahier-de-prepa4.0.0/docs.php
--- cahier-de-prepa3.2.0/docs.php	2014-01-01 14:35:08.232551845 +0100
+++ cahier-de-prepa4.0.0/docs.php	2014-08-26 01:07:30.838558707 +0200
@@ -3,440 +3,75 @@
 define('OK',1);
 include('debut.php');
 
-///////////////////////////////////////////////
-// Validation de la requête : rep ou matière //
-///////////////////////////////////////////////
+//////////////////////////////////////////////////////
+// Validation de la requête : répertoire ou matière //
+//////////////////////////////////////////////////////
 
-// Récupération de l'identifiant de répertoire demandé
+// Récupération des données du répertoire demandé
 $mysqli = premiere_connexion();
+$requete = 'SELECT r.id, r.nom, r.parent, r.parents, r.nbrep_v + r.nbdoc_v AS nb, r.protection, m.cle, m.nom AS mat
+            FROM reps AS r LEFT JOIN matieres AS m ON r.matiere = m.id';
+// Requête non nulle : soit un numéro de répertoire, soit une clé de matière
 if ( isset($_REQUEST['rep']) && is_numeric($rid = $_REQUEST['rep']) )  {
-  $resultat = $mysqli->query("SELECT id FROM reps WHERE id = $rid");
-  if ( $resultat->num_rows )
+  $resultat = $mysqli->query("$requete WHERE r.protection < 4 AND r.id = $rid");
+  if ( $resultat->num_rows )  {
+    $rep = $resultat->fetch_assoc();
     $resultat->free();
-  else
-    $rid = 1;
+  }
 }
-else  {
-  // Si une matière est demandée
-  if ( !empty($_REQUEST) )  {
-    $resultat = $mysqli->query('SELECT r.id, m.cle FROM reps AS r LEFT JOIN matieres AS m ON r.matiere = m.id');
-    if ( $resultat->num_rows )  {
-      while ( $r = $resultat->fetch_assoc() )
+elseif ( !empty($_REQUEST) )  {
+  $resultat = $mysqli->query("$requete WHERE r.protection < 4 AND r.parent = 0");
+  if ( $resultat->num_rows )  {
+    while ( $r = $resultat->fetch_assoc() )
       if ( isset($_REQUEST[$r['cle']]) )  {
+        $rep = $r;
         $rid = $r['id'];
         break;
       }
-      $resultat->free();
-    }
+    $resultat->free();
   }
-  // Valeur par défaut
-  if ( !isset($rid) )
-    $rid = 1;
 }
-
-/////////////////////////////////////
-// Liste des icones pour affichage //
-/////////////////////////////////////
-$icones = array(
-  '.pdf' => 'pdf', '.ps' => 'pdf',
-  '.doc' => 'doc', '.odt' => 'doc', '.docx' => 'doc',
-  '.xls' => 'xls', '.ods' => 'xls', '.xlsx' => 'xls',
-  '.ppt' => 'ppt', '.odp' => 'ppt', '.pptx' => 'ppt',
-  '.jpg' => 'jpg', '.jpeg' => 'jpg', '.png' => 'jpg', '.gif' => 'jpg', '.svg' => 'jpg', '.tif' => 'jpg', '.tiff' => 'jpg', '.bmp' => 'jpg', '.ps' => 'jpg', '.eps' => 'jpg',
-  '.py' => 'python',
-  '.avi' => 'avi', '.mpeg' => 'avi', '.mpg' => 'avi', '.wmv' => 'avi', '.mp4' => 'avi', '.ogv' => 'avi', '.qt' => 'avi', '.mov' => 'avi', '.mkv' => 'avi', 'flv' => 'avi',
-  '.mp3' => 'mp3', '.ogg' => 'mp3', '.oga' => 'mp3', '.wma' => 'mp3', '.wav' => 'mp3', '.ra' => 'mp3', '.rm' => 'mp3',
-  '.txt' => 'txt', '.rtf' => 'txt',
-  '.zip' => 'exe', '.rar' => 'exe', '.7z' => 'zip',
-  '.exe' => 'exe', '.sh' => 'exe', '.ml' => 'exe', '.mw' => 'exe', '' => 'exe'
-);
-
-//////////////////////////////////
-// Modifications de répertoires //
-//////////////////////////////////
-if ( ( $admin ) && ( isset($_REQUEST['modifie']) || isset($_REQUEST['supprime']) || isset($_REQUEST['cree']) ) )  {
-  // Connexion à la base de données
-  $mysqli->close();
-  $mysqli = new mysqli($serveur,"$base-adm",$mdp,$base);
-  $mysqli->set_charset('utf8');
-
-  // Récupération des données du répertoire
-  $resultat = $mysqli->query("SELECT nom, parent, protection, nbrep+nbdoc_v+nbdoc_nv AS nb, menu FROM reps WHERE id = $rid");
+// Valeur par défaut : répertoire "Général", id = 1 (même si non visible)
+if ( !isset($rep) )  {
+  $resultat = $mysqli->query("$requete WHERE r.id = 1");
   $rep = $resultat->fetch_assoc();
   $resultat->free();
-
-  // Récupération des liens de parenté entre répertoires
-  $resultat = $mysqli->query("SELECT id, parents FROM reps");
-  while ( $r = $resultat->fetch_assoc() )
-    $parents[$r['id']] = $r['parents'];
-  $resultat->free();
-
-  // Sauvegarde des tables contenant les données -- une fois pour toutes
-  sauvegarde_mysql('reps');
-  sauvegarde_mysql('docs');
-
-  // Traitement d'une modification du répertoire actuel
-  // Une partie est impossible si $rep['parent'] est nul (répertoires racines de matières)
-  if ( isset($_REQUEST['modifie']) )  {
-    $message = '';
-
-    // Modification du nom
-    if ( isset($_REQUEST['nom']) && ( $rep['parent'] ) && ( strlen($nom = $_REQUEST['nom']) ) && ( $rep['nom'] != $nom ) )  {
-      $nom = $mysqli->real_escape_string($_REQUEST['nom']);
-      $message = ( $mysqli->query("UPDATE reps SET nom = '$nom' WHERE id = $rid")
-                && $mysqli->query('ALTER TABLE reps ORDER BY parents,nom')
-      ) ? 'Le nom du répertoire <em>'.stripslashes($rep['nom'] = $nom).'</em> a bien été modifié.' : "Le nom du répertoire <em>${rep['nom']}</em> n'a pas pu être modifié. Erreur MySQL n°".$mysqli->errno.', «'.$mysqli->error.'».';
-    }
-
-    // Déplacement du répertoire
-    if ( isset($_REQUEST['parent']) && ( $rep['parent'] ) && isset($parents[$parent=$_REQUEST['parent']]) && ( $parent != $rep['parent'] ) && ( !in_array($rid,explode(',',$parents[$parent])) ) )  {
-      // Récupération de l'éventuelle nouvelle matière
-      $resultat = $mysqli->query("SELECT matiere FROM reps WHERE id = $parent");
-      $r = $resultat->fetch_assoc();
-      $resultat->free();
-      $mat = $r['matiere'];
-      $parents = "${parents[$parent]},$parent";
-      // Modifications
-      $message .= ( $mysqli->query("UPDATE reps SET matiere = $mat, parent = $parent, parents = '$parents' WHERE id = $rid")
-                 && $mysqli->query('ALTER TABLE reps ORDER BY parents,nom')
-                 && $mysqli->query("UPDATE reps SET nbrep = (nbrep + 1) WHERE id = $parent")
-                 && $mysqli->query("UPDATE reps SET nbrep = (nbrep - 1) WHERE id = ${rep['parent']}")
-                 && $mysqli->query("UPDATE reps SET matiere = $mat, parents = '$parents,$rid' WHERE parent = $rid")
-                 && $mysqli->query("UPDATE docs SET matiere = $mat, parents = '$parents,$rid' WHERE parent = $rid")
-      ) ? " Le répertoire <em>${rep['nom']}</em> a bien été déplacé." : " Le répertoire <em>${rep['nom']}</em> n'a pas pu être déplacé. Erreur MySQL n°".$mysqli->errno.', «'.$mysqli->error.'».';
-    }
-
-    // Modification de l'affichage dans le menu
-    $menu = isset($_REQUEST['menu']) ? 1 : 0;
-    if ( ( $rep['parent'] ) && ( $menu != $rep['menu'] ) )
-      $message .= ( $mysqli->query("UPDATE reps SET menu = $menu WHERE id = $rid") ) ? " L'affichage dans le menu du répertoire <em>${rep['nom']}</em> a bien été modifié." : " L'affichage dans le menu du répertoire <em>${rep['nom']}</em> n'a pas pu être modifié. Erreur MySQL n°".$mysqli->errno.', «'.$mysqli->error.'».';
-
-    // Modification de la demande d'identification pour accéder au répertoire
-    $protection = isset($_REQUEST['protection']) ? 1 : 0;
-    if ( $protection != $rep['protection'] )  {
-      if ( $mysqli->query("UPDATE reps SET protection = $protection WHERE id = $rid") )  {
-        $message .=  " La visibilité du répertoire <em>${rep['nom']}</em> a bien été modifiée.";
-        $rep['protection'] = $protection;
-      }
-      else
-        $message .= " La visibilité du répertoire <em>${rep['nom']}</em> n'a pas pu être modifiée. Erreur MySQL n°".$mysqli->errno.', «'.$mysqli->error.'».';
-    }
-    
-    // Propagation de demande d'identification aux sous-répertoires et documents
-    if ( isset($_REQUEST['propagation']) && ( $rep['nb'] ) )
-      $message .=  ( $mysqli->query("UPDATE reps SET protection = ${rep['protection']} WHERE FIND_IN_SET($rid,parents)")
-                  && $mysqli->query("UPDATE docs SET protection = ${rep['protection']} WHERE FIND_IN_SET($rid,parents) AND protection < 2")
-      ) ? " La visibilité du répertoire <em>${rep['nom']}</em> a bien été propagée à tout son contenu (sous-répertoires et documents)." : " La visibilité du répertoire <em>${rep['nom']}</em> a bien été propagée à tout son contenu (sous-répertoires et documents). Erreur MySQL n°".$mysqli->errno.', «'.$mysqli->error.'».';
-
-    if ( !strlen($message) )
-      unset($message);
-  }
-
-  // Suppression du répertoire actuel
-  elseif ( isset($_REQUEST['supprime']) && ( $rep['parent'] ) && ( !$rep['nb'] ) )  {
-    $message = ( $mysqli->query("DELETE FROM reps WHERE id = $rid")
-               && $mysqli->query("UPDATE reps SET nbrep = (nbrep - 1) WHERE id = ${rep['parent']}")
-    ) ? "La suppression du répertoire <em>${rep['nom']}</em> a bien été effectuée." : "La suppression du répertoire <em>${rep['nom']}</em> n'a pas été effectuée. Erreur MySQL n°".$mysqli->errno.', «'.$mysqli->error.'».';
-    $rid = $rep['parent'];
-  }
-
-  // Création d'un sous-répertoire
-  elseif ( isset($_REQUEST['cree']) && ( strlen($_REQUEST['nom']) ) )  {
-    $nom = $mysqli->real_escape_string($_REQUEST['nom']);
-    $protection = isset($_REQUEST['protection']) ? 1 : 0;
-    $menu = isset($_REQUEST['menu']) ? 1 : 0;
-    $message = ( $mysqli->query("INSERT INTO reps SET parent = $rid, parents = '${parents[$rid]},$rid', nom = '$nom', matiere = (SELECT r.matiere FROM reps AS r WHERE r.id = $rid), nbrep = 0, nbdoc_v = 0, nbdoc_nv = 0, protection = $protection, menu = $menu")
-              && $mysqli->query("UPDATE reps SET nbrep = (nbrep + 1) WHERE id = $rid")
-              && $mysqli->query('ALTER TABLE reps ORDER BY parents,nom')
-    ) ? 'Le répertoire <em>'.stripslashes($nom).'</em> a bien été créé.' : 'Le répertoire <em>'.stripslashes($nom).'</em> n\'a pas pu être créé. Erreur MySQL n°'.$mysqli->errno.', «'.$mysqli->error.'».';
-  }
-
-  // Mise à jour des champs 'docs' dans la table 'matieres' (pour le menu)
-  $mysqli->query('UPDATE matieres SET docs = (SELECT IF(SUM(nbdoc_v),1,0) FROM reps WHERE matiere = matieres.id)');
-  // Passage en connexion MySQL en lecture seulement
-  $mysqli->close();
-  $mysqli = new mysqli($serveur,$base,$mdp,$base);
-  $mysqli->set_charset('utf8');
+  $rid = 1;
 }
 
-////////////////////////////////
-// Modifications de documents //
-////////////////////////////////
-elseif ( ( $admin ) && isset($_REQUEST['id']) && is_numeric($id = $_REQUEST['id']) )  {
-  // Connexion à la base de données
-  $mysqli->close();
-  $mysqli = new mysqli($serveur,"$base-adm",$mdp,$base);
-  $mysqli->set_charset('utf8');
-
-  // Récupération des données relatives au document
-  if ( $id )  {
-    $resultat = $mysqli->query("SELECT nom, protection, parent, ext, lien FROM docs WHERE id = $id");
-    if ( $resultat->num_rows )  {
-      $doc = $resultat->fetch_assoc();
-      $resultat->free();
-    }
-    else
-      $id = 0;
-  }
-
-  // Sauvegarde des tables contenant les données -- une fois pour toutes
-  sauvegarde_mysql('reps');
-  sauvegarde_mysql('docs');
-
-  // Fonction PHP pour le stockage dans la base MySQL de l'ordre "naturel" (1,2,10,11 et non 1,10,11,2)
-  // Remplace tout nombre par un nombre égal mais écrit sur 10 chiffres, complété par des zéros à gauche
-  function zpad($s) {
-    return preg_replace_callback('/(\d+)/', function($m){
-      return(str_pad($m[1],10,'0',STR_PAD_LEFT)); }
-    , $s);
-  }
-
-  // Traitement d'une modification d'un document
-  if ( isset($_REQUEST['modifie_doc']) && ( $id ) )  {
-    if ( !strlen($_REQUEST['nom']) )
-      $message = 'Il n\'est pas possible de valider un nom de fichier vide. Pour supprimer un fichier, il faut cliquer sur Supprimer.';
-    else  {
-      $message = '';
-
-      // Modification du nom
-      setlocale(LC_CTYPE, "fr_FR.UTF-8");
-      $nom = basename(str_replace($doc['ext'],'',str_replace('/','-',$_REQUEST['nom'])));
-      if ( $nom != $doc['nom'] )  {
-        // real_escape_string seulement pour la requête SQL
-        if ( $mysqli->query('UPDATE docs SET nom = \''.$mysqli->real_escape_string($nom).'\', nom_nat = \''.zpad($mysqli->real_escape_string($nom))."' WHERE id = $id") )  {
-          exec('mv documents/'.escapeshellarg("${doc['lien']}/${doc['nom']}${doc['ext']}").' documents/'.escapeshellarg("${doc['lien']}/$nom${doc['ext']}"));
-          $mysqli->query('ALTER TABLE docs ORDER BY parents,nom_nat');
-          $message = " Le nom du document <em>$nom</em> a bien été modifié.";
-          $doc['nom'] = $nom;
-        }
-        else
-          $message ="Le nom du document <em>${doc['nom']}</em> n'a pas pu être modifié. Erreur MySQL n°".$mysqli->errno.', «'.$mysqli->error.'».';
-      }
-
-      // Modification de l'accès
-      // Si $protection = 2, on enlève un document du répertoire parent ; sinon on ajoute
-      $protection = ( in_array($_REQUEST['protection'],array(0,1,2)) ) ? $_REQUEST['protection'] : 0;
-      if ( $protection != $doc['protection'] )  {
-         $a = ($protection==2) - ($doc['protection']==2);
-         $message .= ( $mysqli->query("UPDATE docs SET protection = $protection WHERE id = $id")
-                   && $mysqli->query("UPDATE reps SET nbdoc_v = (nbdoc_v - ($a)), nbdoc_nv = (nbdoc_nv + ($a)) WHERE id = ${doc['parent']}") 
-        ) ? " L'accès au document <em>${doc['nom']}</em> a bien été modifié." : " L'accès au document <em>${doc['nom']}</em> n'a pas pu être modifié. Erreur MySQL n°".$mysqli->errno.', «'.$mysqli->error.'».';
-      }
-
-      // Déplacement dans un autre répertoire
-      if ( is_numeric($parent = $_REQUEST['parent']) && ( $parent != $doc['parent'] ) )  {
-        $resultat = $mysqli->query("SELECT parents, matiere FROM reps WHERE id = $parent");
-        if ( $resultat->num_rows )  {
-          $r = $resultat->fetch_assoc();
-          $resultat->free();
-          if ( $mysqli->query("UPDATE docs SET parent = '$parent', parents = '${r['parents']},$parent',
-                               matiere = ${r['matiere']} WHERE id = $id") )  {
-            $champ = ( $protection < 2 ) ? 'nbdoc_v' : 'nbdoc_nv';
-            $mysqli->query("UPDATE reps SET $champ = ($champ -1) WHERE id = ${doc['parent']}");
-            $mysqli->query("UPDATE reps SET $champ = ($champ + 1) WHERE id = $parent");
-            $mysqli->query('ALTER TABLE docs ORDER BY parents,nom_nat');
-            $message .= " Le document <em>${doc['nom']}</em> a bien été déplacé.";
-          }
-          else
-            $message .= " Le document <em>${doc['nom']}</em> n'a pas pu être déplacé. Erreur MySQL n°".$mysqli->errno.', «'.$mysqli->error.'».';
-        }
-      }
-      
-      // Si modification(s), on met à jour les informations récentes éventuelles
-      if ( strlen($message) )  {
-        if ( $protection < 2 )  {
-          $resultat = $mysqli->query("SELECT GROUP_CONCAT( reps.nom ORDER BY FIND_IN_SET(reps.id,docs.parents) SEPARATOR '/' ) AS path
-                                      FROM docs LEFT JOIN reps ON FIND_IN_SET(reps.id,docs.parents) WHERE docs.id = $id");
-          $r = $resultat->fetch_assoc();
-          $resultat->free();
-          $path = $mysqli->real_escape_string("${r['path']}/${doc['nom']}");
-          $icone = array_key_exists(strtolower($doc['ext']),$icones) ? $icones[strtolower($doc['ext'])] : 'defaut';
-          // Document auparavant non visible
-          if ( $doc['protection'] == 2 )
-            recent($mysqli,3,$id,"<img class=\"icone\" src=\"icones/$icone.png\"> $path","download?id=$id","<p>Nouveau document&nbsp;: <a href=\"download?id=$id\">$path</a></p>");
-          // Document auparavant visible : on met à jour les chemins, sans mettre en avant le document
-          // Remarque : le RSS n'est pas modifié, le sera leur d'une nouvelle information récente
-          // Le lien de téléchargement est de toutes façons inchangé
-          else
-            $mysqli->query("UPDATE recents SET texte = '<p>Nouveau document&nbsp;: <a href=\"download?id=$id\">$path</a></p>', titre = '<img class=\"icone\" src=\"icones/$icone.png\"> $path' WHERE id = 3000+$id");
-        }
-        // Si $protection = 2, on cherche à supprimer.
-        else
-          recent($mysqli,3,$id);
-      }
-      else
-        unset($message);
-    }
-  }
-  
-  // Suppression d'un document
-  elseif ( isset($_REQUEST['supprime_doc']) && ( $id ) )  {
-    if ( $mysqli->query("DELETE FROM docs WHERE id = $id") )  {
-      $message = "Le document <em>${doc['nom']}</em> a bien été supprimé.";
-      // Suppression physique
-      exec("rm -rf documents/${doc['lien']}");
-      // Mise à jour du répertoire
-      $champ = ( $doc['protection'] < 2 ) ? 'nbdoc_v' : 'nbdoc_nv';
-      $mysqli->query("UPDATE reps SET $champ = ($champ - 1) WHERE id = ${doc['parent']}");
-      // Suppression de l'éventuelle information récente
-      recent($mysqli,3,$id);
-    }
-    else
-      $message = "Le document <em>${doc['nom']}</em> n'a pas pu être supprimé. Erreur MySQL n°".$mysqli->errno.', «'.$mysqli->error.'».';
-  }
-
-  // Mise à jour d'un document
-  elseif ( isset($_REQUEST['envoie']) && ( $id ) )  {
-    // Changement d'extension suspect donc interdit
-    $ext = ( strpos($_FILES['fichier']['name'],'.') ) ? strrchr($_FILES['fichier']['name'],'.') : '';
-    if ( $ext != $doc['ext'] )
-      $message = "Le document <em>${doc['nom']}</em> n'a pas été mis à jour&nbsp;: l'extension n'est pas celle attendue.";
-    else  {
-      // Déplacement du document uploadé au bon endroit
-      if ( move_uploaded_file($_FILES['fichier']['tmp_name'],"documents/${doc['lien']}/${doc['nom']}${doc['ext']}") )  {
-        // Gestion de la taille
-        $taille = intval($_FILES['fichier']['size']/1024);
-        $taille = ( $taille < 1024 ) ? "$taille&nbsp;ko" : intval($taille/1024).'&nbsp;Mo';
-        // Modifications dans la base de données
-        $mysqli->query("UPDATE docs SET upload = DATE(NOW()), taille = '$taille' WHERE id = $id");
-        // Information récente si document visible
-        if ( $doc['protection'] < 2 )  {
-          $resultat = $mysqli->query("SELECT GROUP_CONCAT( reps.nom ORDER BY FIND_IN_SET(reps.id,docs.parents) SEPARATOR '/' ) AS path
-                                      FROM docs LEFT JOIN reps ON FIND_IN_SET(reps.id,docs.parents) WHERE docs.id = $id");
-          $r = $resultat->fetch_assoc();
-          $resultat->free();
-          $path = $mysqli->real_escape_string("${r['path']}/${doc['nom']}");
-          $icone = array_key_exists(strtolower($doc['ext']),$icones) ? $icones[strtolower($doc['ext'])] : 'defaut';
-          recent($mysqli,3,$id,"<img class=\"icone\" src=\"icones/$icone.png\"> $path","download?id=$id","<p>Nouvelle version du document&nbsp;: <a href=\"download?id=$id\">$path</a></p>");
-        }
-        $message = "Le document <em>${doc['nom']}</em> a bien été mis à jour.";
-      }
-      else
-        $message = "Le document <em>${doc['nom']}</em> n'a pas été mis à jour car le fichier envoyé n'est pas valide.";
-    }
-  }
-
-  // Envoi d'un nouveau document
-  elseif ( isset($_REQUEST['envoie']) )  {
-    // Vérifications des données envoyées (on fait confiance aux utilisateurs connectés pour ne pas envoyer de scripts malsains)
-    $nom = $_FILES['fichier']['name'];
-    $ext = ( strpos($nom,'.') ) ? strrchr($nom,'.') : '';
-    setlocale(LC_CTYPE, "fr_FR.UTF-8");
-    $nom = basename(str_replace('/','-',str_replace($ext,'', ( strlen($_REQUEST['nom']) ) ? $_REQUEST['nom'] : $nom )));
-    $protection = ( in_array($_REQUEST['protection'],array(0,1,2)) ) ? $_REQUEST['protection'] : 0;
-    // Création du répertoire particulier
-    $lien = substr(sha1(mt_rand()),0,15);
-    while ( is_dir("documents/$lien") )
-      $lien = substr(sha1(mt_rand()),0,15);
-    mkdir("documents/$lien");
-    // Gestion de la taille
-    $taille = intval($_FILES['fichier']['size']/1024);
-    $taille = ( $taille < 1024 ) ? "$taille&nbsp;ko" : intval($taille/1024).'&nbsp;Mo';
-    // Récupération des données du répertoire parent
-    $resultat = $mysqli->query("SELECT parents, matiere FROM reps WHERE id = $rid");
-    $r = $resultat->fetch_assoc();
-    $resultat->free();
-    // Déplacement du document uploadé au bon endroit
-    if ( move_uploaded_file($_FILES['fichier']['tmp_name'],"documents/$lien/$nom$ext") )  {
-      // Écriture MySQL
-      if ( $mysqli->query("INSERT INTO docs SET parent = $rid, parents = '${r['parents']},$rid',
-                           matiere = ${r['matiere']}, nom = '".$mysqli->real_escape_string($nom).'\',
-                           nom_nat = \''.zpad($mysqli->real_escape_string($nom))."', upload = DATE(NOW()),
-                           taille = '$taille', lien = '$lien',
-                           ext='".$mysqli->real_escape_string($ext)."', protection = $protection") )  {
-        $id = $mysqli->insert_id;
-        $mysqli->query('ALTER TABLE docs ORDER BY parents,nom_nat');
-        $message = "Le document <em>$nom</em> a bien été mis en ligne.";
-        // Mise à jour du répertoire
-        $champ = ( $protection < 2 ) ? 'nbdoc_v' : 'nbdoc_nv';
-        $mysqli->query("UPDATE reps SET $champ = ($champ + 1) WHERE id = $rid");
-        // Si le document est visible, ajout d'un information récente
-        if ( $protection < 2 )  {
-          $resultat = $mysqli->query("SELECT GROUP_CONCAT( reps.nom ORDER BY FIND_IN_SET(reps.id,docs.parents) SEPARATOR '/' ) AS path
-                                      FROM docs LEFT JOIN reps ON FIND_IN_SET(reps.id,docs.parents) WHERE docs.id = $id");
-          $r = $resultat->fetch_assoc();
-          $resultat->free();
-          $path = $mysqli->real_escape_string("${r['path']}/$nom");
-          $icone = array_key_exists(strtolower($ext),$icones) ? $icones[strtolower($ext)] : 'defaut';
-          recent($mysqli,3,$id,"<img class=\"icone\" src=\"icones/$icone.png\"> $path","download?id=$id","<p>Nouveau document&nbsp;: <a href=\"download?id=$id\">$path</a></p>");
-        }
-      }
-      else  {
-        // Retour en arrière
-        exec("rm -rf documents/$lien");
-        $message = "Le document <em>$nom</em> n'a pas été mis en ligne à cause d'une erreur lors des modifications dans la base de données MySQL. Erreur MySQL n°".$mysqli->errno.', «'.$mysqli->error.'».';
-      }
-    }
-    else
-      $message = "Le document <em>$nom</em> n'a pas été mis en ligne car le fichier envoyé n'est pas valide.";
-  }
-  
-  // Mise à jour des champs 'docs' dans la table 'matieres' (pour le menu)
-  $mysqli->query('UPDATE matieres SET docs = (SELECT IF(SUM(nbdoc_v),1,0) FROM reps WHERE matiere = matieres.id)');
-    
-  // Passage en connexion MySQL en lecture seulement
-  $mysqli->close();
-  $mysqli = new mysqli($serveur,$base,$mdp,$base);
-  $mysqli->set_charset('utf8');
-}
+// Répertoires parents
+$resultat = $mysqli->query("SELECT id, nom, IF(parent,'next','home') AS icone,
+                            IF(protection>$autorisation,'-lock','') AS protection
+                            FROM reps WHERE FIND_IN_SET(id,'${rep['parents']},$rid')");
+$parents = '  <div class="item" id="parents">';
+while ( $r = $resultat->fetch_assoc() )
+  $parents .= "\n    <a href=\"?rep=${r['id']}\"><img class=\"icone\" src=\"icones/${r['icone']}${r['protection']}.png\">${r['nom']}</a>";
+$resultat->free();
+$parents .= "\n  </div>";
 
 //////////
 // HTML //
 //////////
-
-if ( $admin )  {
-  // Récupération des répertoires, dans le bon ordre pour un select
-  function liste($rid,$n)  {
-    $resultat = $GLOBALS['mysqli']->query("SELECT id, nom, parents FROM reps WHERE parent = $rid");
-    while ( $r = $resultat->fetch_assoc() )  {
-      $GLOBALS['select_reps'] .= "        <option value=\"${r['id']}\">".str_repeat('&rarr;',$n)."${r['nom']}</option>\n";
-      liste($r['id'],$n+1);
-    }
-    $resultat->free();
-  }
-  $select_reps = '';
-  liste(0,0);
+if ( is_null($rep['cle']) )  {
+  $t = 'Documents à télécharger';
+  $p = 'docs';
+}
+else  {
+  $t = "Documents à télécharger - ${rep['mat']}";
+  $p = "docs?${rep['cle']}";
 }
 
-// Récupération des données, vérification de la protection, répertoires parents
-$resultat = $mysqli->query('SELECT r.nom, r.parent, r.parents, r.nbrep + r.nbdoc_v'.( ($admin) ? ' + r.nbdoc_nv' : '' )." AS nb,
-                            r.protection, r.menu, m.cle, m.nom AS mat
-                            FROM reps AS r LEFT JOIN matieres AS m ON r.matiere = m.id
-                            WHERE r.id = $rid");
-if ( $resultat->num_rows )  {
-  $rep = $resultat->fetch_assoc();
-  $resultat->free();
+// Si le répertoire est protégé et l'utilisateur non autorisé
+while ( $rep['protection'] > $autorisation )
+  include('login.php');
 
-  if ( $admin )  {
-    $t = "Gestion des documents - ${rep['nom']}";
-    $p = ( is_null($rep['cle']) ) ? "docs$urladmin2" : "docs?${rep['cle']}$urladmin";
-  }
-  elseif ( is_null($rep['cle']) )  {
-    $t = 'Documents à télécharger';
-    $p = 'docs';
-  }
-  else  {
-    $t = "Documents à télécharger - ${rep['mat']}";
-    $p = "docs?${rep['cle']}";
-  }
+// Haut de page, menu et message
+include('haut.php');
 
-  // Demander l'identification si le répertoire est protégé et l'utilisateur non connecté
-  if ( $rep['protection'] && !$lecteur && !$admin )
-    include('login_lecture.php');
-
-  // Haut de page, menu et message
-  include('haut.php');
-
-  // Répertoires parents
-  $resultat = $mysqli->query("SELECT id, nom, protection FROM reps WHERE FIND_IN_SET(id,'${rep['parents']},$rid')");
-  $parents = '';
-  while ( $r = $resultat->fetch_assoc() )
-    $parents .= "\n    <a href=\"?rep=${r['id']}\"><img class=\"icone\" src=\"icones/".( ( strlen($parents) ) ? 'next' : 'home' ).( ( $r['protection'] ) ? '-lock' : '' ).".png\">${r['nom']}</a>";
-  $resultat->free();
-  echo <<<FIN
+// Affichage des répertoires parents et des demandes d'ordre
+echo <<<FIN
   
-  <div class="item" id="parents">$parents
-  </div>
+$parents
   
   <div id="ordre">
     <span>Ordre alphabétique&nbsp;<a href="?rep=$rid&amp;ordre=alpha"><img src="icones/down.png" alt="croissant"></a>&nbsp;<a href="?rep=$rid&amp;ordre=alpha-r"><img src="icones/up.png" alt="décroissant"></a></span>
@@ -445,259 +80,108 @@
 
 FIN;
 
-  // Affichage du répertoire et de son contenu
-  if ( $rep['nb'] )  {
-    
-    function affichage($mysqli, $rid, $n, $lecteur,$admin)  {
-      $indent = str_pad('',2*$n);
-
-      // Sous-répertoires et récursivité
-      $resultat = $mysqli->query('SELECT id, nom, nbrep, nbdoc_v'.( ($admin) ? ' + nbdoc_nv' : '' )." AS nbdoc, protection FROM reps WHERE parent = $rid");
-      if ( $resultat->num_rows )  {
-        while ( $r = $resultat->fetch_assoc() )  {
-          // Affichage du contenu si pas de protection ou utilisateur identifié
-          if ( ( !$r['protection'] ) || $lecteur || $admin )  {
-            if ( $r['nbrep']+$r['nbdoc'] )
-              $contenu = str_replace(array('0 répertoire,',', 0 document'),'',"(${r['nbrep']} répertoire".( ( $r['nbrep'] > 1 ) ? 's' : '' ).", ${r['nbdoc']} document".( ( $r['nbdoc'] > 1 ) ? 's' : '' ).')');
-            else
-              $contenu = '(vide)';
-            $s = ( $admin && $r['protection'] ) ? '<p class="rep open lock"><img class="icone" src="icones/rep-open-lock.png">' : '<p class="rep open"><img class="icone" src="icones/rep-open.png">';
-            echo <<<FIN
+// Affichage du répertoire et de son contenu
+if ( $rep['nb'] )  {
+  
+  // Liste des icônes pour affichage
+  $icones = array(
+    '.pdf' => 'pdf',
+    '.doc' => 'doc', '.odt' => 'doc', '.docx' => 'doc',
+    '.xls' => 'xls', '.ods' => 'xls', '.xlsx' => 'xls',
+    '.ppt' => 'ppt', '.odp' => 'ppt', '.pptx' => 'ppt',
+    '.jpg' => 'jpg', '.jpeg' => 'jpg', '.png' => 'jpg', '.gif' => 'jpg', '.svg' => 'jpg', '.tif' => 'jpg', '.tiff' => 'jpg', '.bmp' => 'jpg', '.ps' => 'jpg', '.eps' => 'jpg',
+    '.py' => 'python',
+    '.avi' => 'avi', '.mpeg' => 'avi', '.mpg' => 'avi', '.wmv' => 'avi', '.mp4' => 'avi', '.ogv' => 'avi', '.qt' => 'avi', '.mov' => 'avi', '.mkv' => 'avi', 'flv' => 'avi',
+    '.mp3' => 'mp3', '.ogg' => 'mp3', '.oga' => 'mp3', '.wma' => 'mp3', '.wav' => 'mp3', '.ra' => 'mp3', '.rm' => 'mp3',
+    '.txt' => 'txt', '.rtf' => 'txt',
+    '.zip' => 'zip', '.rar' => 'zip', '.7z' => 'zip',
+    '.exe' => 'exe', '.sh' => 'exe', '.ml' => 'exe', '.mw' => 'exe', '' => 'exe'
+  );
+  
+  // Ordre d'affichage demandé
+  if ( isset($_REQUEST['ordre']) )  {
+    $ordre = array('alpha'=>'ORDER BY nom_nat ASC','alpha-r'=>'ORDER BY nom_nat DESC',
+                   'chrono'=>'ORDER BY docs.upload ASC','chrono-r'=>' ORDER BY docs.upload DESC');
+    $ordre = array_key_exists($_REQUEST['ordre'],$ordre) ? $ordre[$_REQUEST['ordre']] : '';
+  }
+  else
+    $ordre = '';
+
+  function affichage($mysqli, $rid, $n, $autorisation)  {
+    $indent = str_pad('',2*$n);
+
+    // Sous-répertoires et récursivité
+    $resultat = $mysqli->query("SELECT id, nom, nbrep_v AS nbrep, nbdoc_v AS nbdoc,
+                                IF(protection>$autorisation,1,0) AS protection
+                                FROM reps WHERE parent = $rid AND protection < 4");
+    if ( $resultat->num_rows )  {
+      while ( $r = $resultat->fetch_assoc() )  {
+        // Affichage du contenu si autorisé
+        if ( $r['protection'] )
+          echo <<<FIN
 
 $indent<div class="rep">
-$indent  $s<a href="?rep=${r['id']}${GLOBALS['urladmin']}">${r['nom']}</a> $contenu</p>
+$indent  <p class="rep lock"><img class="icone" src="icones/rep-lock.png"><a href="?rep=${r['id']}">${r['nom']}</a></p>
+$indent</div>\n
 FIN;
-            affichage($mysqli,$r['id'],$n+1,$lecteur,$admin);
-            echo "\n$indent</div>";
-          }
+        else  {
+          if ( $r['nbrep']+$r['nbdoc'] )
+            $contenu = str_replace(array('0 répertoire,',', 0 document'),'',"(${r['nbrep']} répertoire".( ( $r['nbrep'] > 1 ) ? 's' : '' ).", ${r['nbdoc']} document".( ( $r['nbdoc'] > 1 ) ? 's' : '' ).'&nbsp;)');
           else
-            echo <<<FIN
+            $contenu = '(vide)';
+          echo <<<FIN
 
 $indent<div class="rep">
-$indent  <p class="rep lock"><img class="icone" src="icones/rep-lock.png"><a href="?rep=${r['id']}${GLOBALS['urladmin']}">${r['nom']}</a></p>
-$indent</div>
+$indent  <p class="rep open"><img class="icone" src="icones/rep-open.png"><a href="?rep=${r['id']}">${r['nom']}</a> $contenu</p>
 FIN;
+          affichage($mysqli,$r['id'],$n+1,$autorisation);
+          echo "\n$indent</div>\n";
         }
-        $resultat->free();
       }
+      $resultat->free();
+    }
 
-      // Documents
-      $ordre = '';
-      if ( isset($_REQUEST['ordre']) )
-        switch ( $_REQUEST['ordre'] )  {
-          case 'alpha':
-            $ordre = ' ORDER BY nom_nat ASC';
-            break;
-          case 'alpha-r':
-            $ordre = ' ORDER BY nom_nat DESC';
-            break;
-          case 'chrono':
-            $ordre = ' ORDER BY docs.upload ASC';
-            break;
-          case 'chrono-r':
-            $ordre = ' ORDER BY docs.upload DESC';
-        }
-      $protection = ( $admin ) ? '' : ' AND protection < 2';
-      $resultat = $mysqli->query("SELECT id, nom, taille, DATE_FORMAT(upload,'%d/%m/%Y') AS upload, LOWER(ext) AS ext, protection FROM docs WHERE parent = $rid$protection$ordre");
-      if ( $resultat->num_rows )  {
-        $icones = $GLOBALS['icones'];
-        while ( $r = $resultat->fetch_assoc() )  {
-          $icone = array_key_exists($r['ext'],$icones) ? $icones[$r['ext']] : 'defaut';
-          if ( $admin )  {
-            $id = $r['id'];
-            if ( $r['protection'] == 1 )
-              $icone .= '-lock';
-            $cache = ( $r['protection'] == 2 ) ? ' (non diffusé)' : '';
-            $select_reps = str_replace(array("\"$rid\"",'        '),array("\"$rid\" selected","$indent      "),$GLOBALS['select_reps']);
-            $select_protection = str_replace("\"${r['protection']}\"","\"${r['protection']}\" selected",'<option value="0">Visible de tous</option><option value="1">Visible après identification</option><option value="2">Non visible</option>');
-            echo <<<FIN
-
-$indent<div class="fic admin">
-$indent  <span><img class="icone" src="icones/$icone.png">${r['nom']}</span>$cache
-$indent  <form action="?rep=${GLOBALS['rid']}${GLOBALS['urladmin']}" method="post" enctype="multipart/form-data">
-$indent    <input class="bouton" type="submit" name="envoie" value="Envoyer">
-$indent    <p class="ligne"><label for="fichier$id">Mettre à jour&nbsp;: </label><input type="file" id="fichier$id" name="fichier"></p>
-$indent    <input type="hidden" name="id" value="$id">
-$indent  </form>
-$indent  <hr>
-$indent  <form action="?rep=${GLOBALS['rid']}${GLOBALS['urladmin']}" method="post">
-$indent    <p class="boutons"><input type="submit" name="modifie_doc" value="Valider" title="Valider les modifications"><input type="submit" name="supprime_doc" value="Supprimer" title="Supprimer ce document"></p>
-$indent    <p class="ligne"><label for="nomdoc$id">Nom à afficher&nbsp;: </label><input type="text" id="nom$id" name="nom" value="${r['nom']}" size="50"></p>
-$indent    <p class="ligne"><label for="parent$id">Répertoire&nbsp;: </label><select id="parent$id" name="parent">
-$select_reps$indent    </select></p>
-$indent    <p class="ligne"><label for="protecdoc$id">Accès&nbsp;: </label>
-$indent      <select id="protecdoc$id" name="protection">$select_protection</select>
-$indent    </p>
-$indent    <input type="hidden" name="id" value="$id">
-$indent    <p class="ligne"><label>Lien&nbsp;: </label><code>&lt;a href="download?id=$id"&gt;${r['nom']}&lt;/a&gt;</code></p>
-$indent    <p class="ligne"><a href="download?id=$id">Télécharger</a>|&nbsp;Taille&nbsp;: ${r['taille']}&nbsp;|&nbsp;Diffusé le ${r['upload']}</p>
-$indent  </form>
-$indent</div>
-FIN;
-          }
-          else  {
-            if ( $r['protection'] && !$lecteur )
-              $icone .= '-lock';
-            echo "
-$indent<p class=\"fic\"><a href=\"download?id=${r['id']}\"><img class=\"icone\" src=\"icones/$icone.png\">${r['nom']}</a> (${r['upload']}, ${r['taille']})</p>";
-          }
-        }
-        $resultat->free();
+    // Documents
+    $resultat = $mysqli->query("SELECT id, nom, taille, DATE_FORMAT(upload,'%d/%m/%Y') AS upload,
+                                LOWER(ext) AS ext, IF(protection>$autorisation,'-lock','') AS protection
+                                FROM docs WHERE parent = $rid AND protection < 4 ${GLOBALS['ordre']}");
+    if ( $resultat->num_rows )  {
+      $icones = $GLOBALS['icones'];
+      while ( $r = $resultat->fetch_assoc() )  {
+        $icone = array_key_exists($r['ext'],$icones) ? $icones[$r['ext']] : 'defaut';
+        echo "
+$indent<p class=\"fic\"><a href=\"download?id=${r['id']}\"><img class=\"icone\" src=\"icones/$icone${r['protection']}.png\">${r['nom']}</a> (${r['upload']}, ${r['taille']})</p>";
       }
+      $resultat->free();
     }
-
-    affichage($mysqli,$rid,1,$lecteur,$admin);
-    
   }
-  else
-    echo "\n  <h3 class=\"warning\">Ce répertoire est vide.</h3>\n\n";
-}
-else  {
-  // Haut de page, menu et message
-  $p = ( $admin ) ? "docs$urladmin2" : 'docs';
-  $t = 'Documents à télécharger';
-  include('haut.php');
-  echo "\n  <h3 class=\"warning\">Ce répertoire n'existe pas.</h3>\n\n";
-}
 
-// Interface d'administration : formulaires de modification
-if ( $admin )  {
+  // Lancement de la fonction récursive sur le répertoire à afficher
+  affichage($mysqli,$rid,1,$autorisation);
+  
+}
+else
+  echo "\n  <h3 class=\"warning\">Ce répertoire est vide.</h3>\n\n";
 
-  // Taille maximale de fichier (pour l'aide)
-  $taille = min(ini_get('upload_max_filesize'),ini_get('post_max_size'));
-  if ( stristr($taille,'m') )
-    $taille = substr($taille,0,-1)*1048576;
-  elseif ( stristr($taille,'k') )
-    $taille = substr($taille,0,-1)*1024;
-  $taille = ( $taille < 1048576 ) ? intval($taille/1024).'&nbsp;ko' : intval($taille/1048576).'&nbsp;Mo';
-
-  // Restrictions pour les modifications sur le répertoire
-  $indication = $pas_suppr = $disabled = '';
-  if ( $rep['nb'] )  {
-    $pas_suppr = ' disabled';
-    $indication = "\n    <p>Ce répertoire ne peut pas être supprimé car il n'est pas vide.</p>";
-  }
-  if ( !$rep['parent'] )  {
-    $disabled = $pas_suppr = ' disabled';
-    $indication = "\n    <p>Ce répertoire est un répertoire racine, il ne peut être ni supprimé, ni renommé, ni déplacé.</p>";
-    $select_reps = '        <option></option>';
-  }  
-
-  // Choix par défaut de la protection du nouveau document et du sous-répertoire
-  $protection = (int)( $rep['protection'] || $_SESSION['protection'] );
-  $select_protection = str_replace("\"$protection\"","\"$protection\" selected",'<option value="0">Visible de tous</option><option value="1">Visible après identification</option>');
 
-  // HTML
+$mysqli->close();
 ?>
 
-  <div class="item aide">
-    <h3>Aide et explications</h3>
-    <p>Vous pouvez ici modifier le répertoire <?php echo $rep['nom']; ?> ainsi que l'ensemble des documents contenus dans les sous-répertoires.</p>
-    <p>Les répertoires se déplient en cliquant sur leur icone, sont à leur tour modifiables en cliquant sur leur nom. Les documents sont modifiables en cliquant sur leur nom ou leur icone.</p>
-    <p>Les liens vers les répertoires et les documents ne sont jamais modifiés par un déplacement ou un changement de nom.</p>
-    <p>Le lien dans le menu vers le répertoire de premier niveau est généré automatiquement, seulement si des documents sont disponibles.</p>
-    <h4>Préférences du répertoire</h4>
-    <p>Seul le répertoire <?php echo $rep['nom']; ?> est modifiable ici. Pour modifier le nom ou déplacer un autre répertoire, il faut afficher sa propre page en cliquant sur son nom.</p>    
-    <p>Il est possible de déplacer le répertoire <?php echo $rep['nom']; ?> en modifiant son répertoire parent.</p>
-    <p>La case à cocher <em>Affichage du répertoire dans le menu</em> permet d'afficher un lien direct dans le menu (partie publique et interface d'administration) vers la page correspondant au répertoire.</p>
-    <p>La case à cocher <em>Demande d'identification pour l'affichage du contenu</em> permet de restreindre la visibilité du répertoire aux visiteurs qui se sont identifiés. Dans ce cas, après <a href="utilisateurs">avoir créé un compte pour les élèves ici</a>, ces informations seront accessibles aux élèves auxquels vous aurez donné l'identifiant et le mot de passe choisis.</p>
-    <p>La case à cocher <em>Propager le choix ci-dessus à chaque document/sous-répertoire</em> permet de faire suivre le choix précédent à l'ensemble du contenu du répertoire. Si elle est décochée, aucune modification supplémentaire n'a lieu. Si elle est cochée, une action sera effectuée même si la précédente case n'a pas changé d'état.</p>
-    <p>Seuls les répertoires vides peuvent être supprimés. Le répertoire <em>Général</em> et les répertoires à la racine de chaque matière ne peuvent être renommés, déplacés ou supprimés.</p>
-    <h4>Nouveau document</h4>
-    <p>Vous pouvez envoyer des fichiers grâce au formulaire. Le <em>nom à afficher</em> sera le nom réellement affiché sur le site web (sans extension&nbsp;: elle est détectée automatiquement lors de l'envoi). Si vous laissez cette case vide, le nom du fichier envoyé sera récupéré.</p>
-    <p>Vous pouvez choisir la visibilité du document parmi trois possibilités&nbsp;:</p>
-    <ul>
-      <li><em>Visible de tous</em>&nbsp;: document accessible de tout visiteur</li>
-      <li><em>Visible après identification</em>&nbsp;: document accessible uniquement après identification, par exemple pour que seuls vos élèves les voient. Il faut alors <a href="utilisateurs">créer un utilisateur de type élève</a>.</li>
-      <li><em>Non visible</em>&nbsp;: le document n'est pas encore visible en ligne. Cela peut être utile si vous souhaitez le mettre à disposition ultérieurement. C'est complètement sûr&nbsp;: vous pouvez mettre comme cela le prochain sujet de devoir ou même le corrigé.</li>
-    </ul>
-    <p>La taille du fichier envoyé est limitée à <?php echo $taille; ?>. Tout document est modifiable et déplaçable, sans que cela ne modifie le lien vers le document.</p>
-    <h4>Nouveau sous-répertoire</h4>
-    <p>Il est possible de créer autant de sous-répertoire que vous le souhaitez. La case à cocher <em>Demande d'identification pour l'affichage du contenu</em> permet de restreindre la visibilité du répertoire aux visiteurs qui se sont identifiés. Dans ce cas, après <a href="utilisateurs">avoir créé un compte pour les élèves ici</a>, ces informations seront accessibles aux élèves auxquels vous aurez donné l'identifiant et le mot de passe choisis.</p>
-    <h4>Modification des documents déjà envoyés</h4>
-    <p>Les documents sont modifiables en cliquant sur leur nom ou leur icone.</p>
-    <p>Vous pouvez <em>mettre à jour</em> un document déjà envoyé&nbsp;: cela évite de supprimer/recréer le document, et permet aux liens vers le document d'être toujours valables (l'adresse ne change pas).</p>
-    <p>Vous pouvez aussi renommer et déplacer tout document. Le lien correspondant ne changera pas.</p>
-    <p>Vous pouvez choisir la visibilité du document parmi trois possibilités&nbsp;:</p>
-    <ul>
-      <li><em>Visible de tous</em>&nbsp;: document accessible de tout visiteur</li>
-      <li><em>Visible après identification</em>&nbsp;: document accessible uniquement après identification, par exemple pour que seuls vos élèves les voient. Il faut alors <a href="utilisateurs">créer un utilisateur de type élève</a>.</li>
-      <li><em>Non visible</em>&nbsp;: le document n'est pas encore visible en ligne. Cela peut être utile si vous souhaitez le mettre à disposition ultérieurement. C'est complètement sûr&nbsp;: vous pouvez mettre comme cela le prochain sujet de devoir ou même le corrigé.</li>
-    </ul>
-  </div>
-
-  <div class="item admin">
-  <form action="<?php echo "?rep=$rid$urladmin"; ?>" method="post">
-    <input class="bouton" type="submit" name="modifie" value="Valider" title="Valider les modifications">
-    <input class="bouton" type="submit" name="supprime" value="Supprimer" title="Supprimer ce répertoire"<?php echo $pas_suppr;?>>
-    <h3>Modifier le répertoire <em><?php echo $rep['nom']; ?></em></h3>
-    <p class="ligne"><label for="nomrep">Nom&nbsp;: </label><input type="text" id="nomrep" name="nom" value="<?php echo $rep['nom']; ?>" size="50"<?php echo $disabled;?>></p>
-    <p class="ligne"><label for="parent">Répertoire parent&nbsp;: </label>
-      <select id="parent" name="parent"<?php echo $disabled;?>>
-<?php echo str_replace("\"${rep['parent']}\"","\"${rep['parent']}\" selected",$select_reps); ?>
-      </select>
-    </p>
-    <p class="ligne"><label for="menurep">Affichage du répertoire dans le menu&nbsp;: </label><input type="checkbox" id="menurep" name="menu" value="1"<?php echo ( $rep['menu'] ) ? ' checked' : ''; echo $disabled; ?>></p>
-    <p class="ligne"><label for="protecrep">Demande d'identification pour l'affichage du contenu&nbsp;: </label><input type="checkbox" id="protecrep" name="protection" value="1"<?php echo ( $rep['protection'] ) ? ' checked' : ''; ?>></p>
-    <p class="ligne"><label for="propagation">Propager le choix ci-dessus à chaque document/sous-répertoire&nbsp;: </label><input type="checkbox" id="propagation" name="propagation" value="1"></p><?php echo $indication;?>
-  </form>
-  </div>
-
-  <div class="item admin">
-  <form action="<?php echo "?rep=$rid$urladmin"; ?>" method="post" enctype="multipart/form-data">
-    <input class="bouton" type="submit" name="envoie" value="Envoyer">
-    <h3>Déposer un document dans le répertoire <em><?php echo $rep['nom']; ?></em></h3>
-    <p class="ligne"><label for="nomdoc">Nom à afficher&nbsp;: </label><input type="text" id="nomdoc" name="nom" value="" size="50"></p>
-    <p class="ligne"><label for="fichier">Fichier&nbsp;: </label><input type="file" id="fichier" name="fichier"></p>
-    <p class="ligne"><label for="protecdoc">Accès&nbsp;: </label>
-      <select id="protecdoc" name="protection"><?php echo $select_protection; ?><option value="2">Non visible</option></select>
-    </p>
-    <input type="hidden" name="id" value="0">
-  </form>
-  </div>
-
-  <div class="item admin">
-  <form action="<?php echo "?rep=$rid$urladmin"; ?>" method="post" enctype="multipart/form-data">
-    <input class="bouton" type="submit" name="cree" value="Créer">
-    <h3>Créer un sous-répertoire</h3>
-    <p class="ligne"><label for="nomssrep">Nom&nbsp;: </label><input type="text" id="nomssrep" name="nom" value="" size="50"></p>
-    <p class="ligne"><label for="menussrep">Affichage du répertoire dans le menu&nbsp;: </label><input type="checkbox" id="menussrep" name="menu" value="1"></p>
-    <p class="ligne"><label for="protecssrep">Demande d'identification pour l'affichage du contenu&nbsp;: </label><input type="checkbox" id="protecssrep" name="protection" value="1"<?php echo ( $protection ) ? ' checked' : ''; ?>></p>
-  </form>
-  </div>
-
-  <script type="text/javascript">
-$( function() {
-  $('.fic span').replaceWith(function() { return '<a href="">'+$(this).html()+'</a>'; });
-  $('.fic > a').click( function () {
-    $(this).parent().find('form,hr').toggle();
-    $(this).parent().toggleClass('admin');
-    return false;
-  }).click();
-});
-  </script>
-<?php
-}
-
-$lock = ( $admin ) ? '' : ':not(.lock)';
-?>
 
   <script type="text/javascript">
 $( function() {
-  $('p.rep<?php echo $lock; ?> img').css('cursor','pointer').click( function () {
+  $('p.rep:not(.lock) img').css('cursor','pointer').click( function () {
     var p = $(this).parent();
     p.toggleClass('open');
     p.parent().children('.fic,div.rep').toggle();
     $(this).attr('src',function(i,val){ return val.replace(p.is('.open')?'rep':'rep-open',p.is('.open')?'rep-open':'rep'); });
   });
-  $('p.rep<?php echo $lock; ?> img').attr('src',function(i,val){return val.replace('rep-open','rep');}).parent().removeClass('open');
+  $('p.rep:not(.lock) img').attr('src',function(i,val){return val.replace('rep-open','rep');}).parent().removeClass('open');
   $('div.rep .fic,div.rep div.rep').hide();
 });
   </script>
   
 <?php
-$mysqli->close();
-
 // Bas de page
 include('bas.php');
 ?>
diff -urN cahier-de-prepa3.2.0/download.php cahier-de-prepa4.0.0/download.php
--- cahier-de-prepa3.2.0/download.php	2013-09-04 22:14:23.390653290 +0200
+++ cahier-de-prepa4.0.0/download.php	2014-08-04 01:35:34.161786739 +0200
@@ -3,41 +3,36 @@
 define('OK',1);
 include('debut.php');
 
-// Vérification du document, de la protection et récupération du lien
+///////////////////////////////////
+// Validation de la requête : id //
+///////////////////////////////////
+
+// Récupération du lien
 if ( isset($_REQUEST['id']) && is_numeric($id = $_REQUEST['id']) )  {
   $mysqli = premiere_connexion();
-  $resultat = $mysqli->query("SELECT id, parents, matiere, nom, lien, ext, protection FROM docs WHERE id = $id AND protection < 2");
+  $resultat = $mysqli->query("SELECT d.id, d.parents, d.nom, d.lien, d.ext, d.protection, m.cle
+                              FROM docs AS d LEFT JOIN matieres AS m ON d.matiere = m.id
+                              WHERE d.id = $id AND protection < 4");
   if ( $resultat->num_rows )  {
     $f = $resultat->fetch_assoc();
     $resultat->free();
 
-    // Si protection
-    if ( $f['protection'] && !$lecteur )  {
-
-      // $p et $t à définir pour le menu et le titre
-      $resultat = $mysqli->query("SELECT cle FROM matieres WHERE id = ${f['matiere']}");
-      if ( $resultat->num_rows )  {
-        $r = $resultat->fetch_assoc();
-        $p = "docs?${r['cle']}";
-        $resultat->free();
-      }
-      else
-        $p = 'docs';
+    // Si le document est protégé et l'utilisateur non autorisé
+    while ( $f['protection'] > $autorisation )  {
+      $p = ( strlen($f['cle']) ) ? "docs?${f['cle']}" : 'docs';
       $t = "Documents à télécharger - ${f['nom']}";
-
       // Répertoires parents
-      $resultat = $mysqli->query("SELECT id, nom FROM reps WHERE FIND_IN_SET(id,'${f['parents']}') AND protection = 0");
-      if ( $resultat->num_rows )  {
-        $parents = '';
-        while ( $r = $resultat->fetch_assoc() )
-          $parents .= "\n    <a href=\"docs?rep=${r['id']}\"><img class=\"icone\" src=\"icones/".( ( strlen($parents) ) ? 'next' : 'home' ).".png\">${r['nom']}</a>";
-        $parents = "\n  <div class=\"item rep\" id=\"parents\">$parents\n  </div>\n";
-        $resultat->free();
-      }
-      
-      // Vérification et formulaire éventuel
-      include('login_lecture.php');
+      $resultat = $mysqli->query("SELECT id, nom, IF(parent,'next','home') AS icone,
+                                  IF(protection>$autorisation,'-lock','') AS protection
+                                  FROM reps WHERE FIND_IN_SET(id,'${f['parents']}')");
+      $parents = '  <div class="item" id="parents">';
+      while ( $r = $resultat->fetch_assoc() )
+        $parents .= "\n    <a href=\"docs?rep=${r['id']}\"><img class=\"icone\" src=\"icones/${r['icone']}${r['protection']}.png\">${r['nom']}</a>";
+      $resultat->free();
+      $parents .= "\n  </div>";
+      include('login.php');
     }
+    $mysqli->close();
 
     // Définition du type de fichier (entête HTML à envoyer)
     switch ( $f['ext'] )  {
@@ -165,5 +160,7 @@
     header('Content-Disposition: attachment; filename="'.rawurlencode($f['nom']).$f['ext'].'"');
     header("Location: documents/${f['lien']}/".rawurlencode($f['nom']).$f['ext']);
   }
+  else
+    $mysqli->close();
 }
 ?>
diff -urN cahier-de-prepa3.2.0/haut.php cahier-de-prepa4.0.0/haut.php
--- cahier-de-prepa3.2.0/haut.php	2013-12-30 00:41:13.253421098 +0100
+++ cahier-de-prepa4.0.0/haut.php	2014-08-28 02:10:02.324208377 +0200
@@ -15,28 +15,7 @@
   <link rel="stylesheet" href="css/couleurs.css?v=4" type="text/css" media="screen">
   <script type="text/javascript" src="js/jquery.js"></script>
   <script type="text/javascript" src="/MathJax/MathJax.js?config=TeX-AMS-MML_HTMLorMML"></script>
-<?php
-if ( $lecteur )
-  echo "  <link rel=\"alternate\" type=\"application/rss+xml\" title=\"Flux RSS\" href=\"http://cahier-de-prepa.fr/$site/documents/".sha1($GLOBALS['base'])."/rss.xml\">\n";
-else  {
-  $resultat = $mysqli->query('SELECT id FROM utilisateurs WHERE matieres = \'0\'');
-  if ( $resultat->num_rows )
-    $resultat->free();
-  else
-    echo "  <link rel=\"alternate\" type=\"application/rss+xml\" title=\"Flux RSS\" href=\"http://cahier-de-prepa.fr/$site/documents/".sha1($GLOBALS['base'])."/rss.xml\">\n";
-}
-if ( $admin )
-echo <<< FIN
-  <link rel="stylesheet" href="js/jquery.datepick.css" type="text/css">
-  <link rel="stylesheet" href="js/jquery.timeentry.css" type="text/css">
-  <script type="text/javascript" src="js/jquery.datepick.js"></script>
-  <script type="text/javascript" src="js/jquery.datepick-fr.js"></script>
-  <script type="text/javascript" src="js/jquery.timeentry.js"></script>
-  <script type="text/javascript" src="js/jquery.timeentry-fr.js"></script>
-  <script type="text/javascript" src="js/timeout.js"></script>
-
-FIN;
-?>
+  <link rel="alternate" type="application/rss+xml" title="Flux RSS" href="http://cahier-de-prepa.fr/$site/documents/<?php echo sha1($base); ?>/rss.xml">
   <script type="text/x-mathjax-config">
     MathJax.Hub.Config({tex2jax: {inlineMath: [["$","$"],["\\(","\\)"]]}});
   </script>
@@ -50,168 +29,138 @@
 
   <div id="menu">
 <?php
-// Menu de l'interface d'administration et bouton de déconnexion
-// Rappel : $p doit être déjà définie (clé correspondant à la page)
-if ( $admin )  {
-  
-  // Début : déconnexion, accueil, documents toutes matières
-  $menu = <<<FIN
-    <div>
-      <form id="deconnexion" action="" method="post"><input type="submit" name="deconnexion" value="Se déconnecter"></form>
-      <a href=".$urladmin2">Accueil</a>
-      <a href="prefs$urladmin2">Préférences</a>
-      <a href="docs$urladmin2">Documents (toutes matières)</a>
-FIN;
-  $resultat = $mysqli->query("SELECT id, nom FROM reps WHERE matiere = 0 AND menu = 1");
-  if ( $resultat->num_rows )  {
-    while ( $r = $resultat->fetch_assoc() )
-      $menu .= "\n        <a class=\"menurep\" href=\"docs?rep=${r['id']}$urladmin\">${r['nom']}</a>";        
-    $resultat->free();
-  }
-  $menu .= "\n    </div>\n";
+// Récupération et affichage des pages
+$menu = '    <div>';
+$resultat = $mysqli->query('SELECT cle, nom FROM pages WHERE mat = 0');
+while ( $r = $resultat->fetch_assoc() )
+  $menu .= "\n      <a href=\".?${r['cle']}\">${r['nom']}</a>";
+$resultat->free();
+// Page de téléchargement
+$menu .= "\n      <a href=\"docs\">Documents à télécharger</a>";
+$resultat = $mysqli->query("SELECT id, nom FROM reps WHERE matiere = 0 AND menu = 1");
+if ( $resultat->num_rows )  {
+  while ( $r = $resultat->fetch_assoc() )
+    $menu .= "\n        <a class=\"menurep\" href=\"docs?rep=${r['id']}\">${r['nom']}</a>";        
+  $resultat->free();
+}
+$menu .= "\n    </div>";
 
-  // Matières à afficher dans le menu
-  $resultat = $mysqli->query("SELECT id, cle, nom, colles, cdt, docs FROM matieres WHERE FIND_IN_SET(id,'${_SESSION['matieres']}') ORDER BY FIND_IN_SET(id,'${_SESSION['matieres']}')");
-  $matieres = array();
+// Récupération et affichage des matières
+$resultat = $mysqli->query('SELECT m.id, m.cle, m.nom, m.colles, m.cdt, m.docs,
+                            GROUP_CONCAT(CONCAT(m.cle,\'/\',p.cle) SEPARATOR \'//\') AS pcle,
+                            GROUP_CONCAT(p.nom SEPARATOR \'//\') AS pnom
+                            FROM matieres AS m LEFT JOIN pages AS p ON p.mat = m.id
+                            WHERE m.colles+m.cdt+m.docs OR p.nom IS NOT NULL GROUP BY m.id ORDER BY m.ordre, p.ordre');
+if ( $resultat->num_rows )  {
   while ( $r = $resultat->fetch_assoc() )  {
-    // Stockage utilisé dans admin.php, prefs.php, pages.php
-    $matieres[] = $r;
-    // Pour le menu
-    $menu .= <<<FIN
-    <div>
-      <h3>Matière <em>${r['nom']}</em></h3>
-      <a href="colles?${r['cle']}$urladmin">Programme de colles</a>
-      <a href="cdt?${r['cle']}$urladmin">Cahier de texte</a>
-      <a href="docs?${r['cle']}$urladmin">Documents</a>
-FIN;
+    $menu .= "\n    <div>\n      <h3>${r['nom']}</h3>";
+    if ( !is_null($r['pcle']) )  {
+      $pcle = explode('//',$r['pcle']);
+      $pnom = explode('//',$r['pnom']);
+      $nom = $pnom[0];
+      foreach ( $pcle as $cle )  {
+        $menu .= "\n      <a href=\".?$cle\">$nom</a>";
+        $nom = next($pnom);
+      }
+    }
+    if ( $r['colles'] )
+      $menu .= "\n      <a href=\"colles?${r['cle']}\">Programme de colles</a>";
+    if ( $r['cdt'] )
+      $menu .= "\n      <a href=\"cdt?${r['cle']}\">Cahier de texte</a>";
     if ( $r['docs'] )  {
+      $menu .= "\n      <a href=\"docs?${r['cle']}\">Documents à télécharger</a>";
       $resultat_doc = $mysqli->query("SELECT id, nom FROM reps WHERE matiere = ${r['id']} AND menu = 1");
       if ( $resultat_doc->num_rows )  {
         while ( $d = $resultat_doc->fetch_assoc() )
-          $menu .= "\n        <a class=\"menurep\" href=\"docs?rep=${d['id']}$urladmin\">${d['nom']}</a>";        
+          $menu .= "\n        <a class=\"menurep\" href=\"docs?rep=${d['id']}\">${d['nom']}</a>";        
         $resultat_doc->free();
       }
     }
-    $menu .= "\n    </div>\n";
+    $menu .= "\n    </div>";
   }
   $resultat->free();
+}
 
-  // Pages d'informations
-  $menu .= <<<FIN
-    <div>
-      <h3>Pages d'informations</h3>
-FIN;
-  $resultat = $mysqli->query("SELECT CONCAT_WS('/',m.cle,p.cle) AS cle, CONCAT_WS('/',m.nom,p.nom) AS nom
-                              FROM pages AS p LEFT JOIN matieres AS m ON p.mat = m.id
-                              WHERE FIND_IN_SET(p.mat,'0,${_SESSION['matieres']}')
-                              ORDER BY FIND_IN_SET(p.mat,'0,${_SESSION['matieres']}'), p.ordre");
-  while ( $r = $resultat->fetch_assoc() )
-    $menu .= "\n      <a href=\".?${r['cle']}$urladmin\">${r['nom']}</a>";
-  $resultat->free();
-  
-  // Paramètres du site et lien vers la partie publique
-  $menu .= <<<FIN
-
-    </div>
-    <div>
-      <h3>Paramètres du site</h3>
-      <a href="utilisateurs$urladmin2">Les utilisateurs</a>
-      <a href="matieres$urladmin2">Les matières</a>
-      <a href="pages$urladmin2">Les pages du site</a>
-      <a href="semaines$urladmin2">Les semaines du planning annuel</a>
-    </div>
+// Liens vers l'espace utilisateur
+switch ( $autorisation )  {
+  // Si pas encore connecté
+  case 0:
+    $menu .= "
     <div>
-      <a href="http://$site/">Voir le site public (pensez à revenir vous déconnecter...)</a>
-    </div>
-FIN;
-  
-}
-else  {
-  // Menu de la partie publique : pages d'informations, page de
-  // téléchargement des documents, pages des programmes de colles/cahiers
-  // de texte/téléchargement pour chaque matière.
-  // Rappel : $p doit être déjà définie (clé correspondant à la page)
-
-  // Récupération et affichage des pages
-  $menu = '    <div>';
-  $resultat = $mysqli->query('SELECT cle, nom FROM pages WHERE mat = 0');
-  while ( $r = $resultat->fetch_assoc() )
-    $menu .= "\n      <a href=\".?${r['cle']}\">${r['nom']}</a>";
-  $resultat->free();
-  // Page de téléchargement
-  $menu .= "\n      <a href=\"docs\">Documents à télécharger</a>";
-  $resultat = $mysqli->query("SELECT id, nom FROM reps WHERE matiere = 0 AND menu = 1");
-  if ( $resultat->num_rows )  {
+      <h3>Espace utilisateur</h3>
+      <a href=\"connect\">Se connecter</a>
+    </div>";
+    break;
+  // Si compte élève
+  case 1:
+    $notes = '';
+    $resultat = $mysqli->query("SELECT cle, nom FROM matieres WHERE notes ORDER BY ordre");
     while ( $r = $resultat->fetch_assoc() )
-      $menu .= "\n        <a class=\"menurep\" href=\"docs?rep=${r['id']}\">${r['nom']}</a>";        
+      $notes .= "\n      <a href=\"notes?${r['cle']}\">Mes notes de colles en ${r['nom']}</a>";
     $resultat->free();
-  }
-  $menu .= "\n    </div>";
-
-  // Récupération et affichage des matières
-  $resultat = $mysqli->query('SELECT m.id, m.cle, m.nom, MOD(m.colles,2) AS colles, MOD(m.cdt,2) AS cdt, m.docs,
-                              GROUP_CONCAT(CONCAT(m.cle,\'/\',p.cle) SEPARATOR \'//\') AS pcle,
-                              GROUP_CONCAT(p.nom SEPARATOR \'//\') AS pnom
-                              FROM matieres AS m LEFT JOIN pages AS p ON p.mat = m.id
-                              WHERE MOD(m.colles,2)+MOD(m.cdt,2)+m.docs OR p.nom IS NOT NULL GROUP BY m.id ORDER BY m.ordre, p.ordre');
-  if ( $resultat->num_rows )  {
-    while ( $r = $resultat->fetch_assoc() )  {
-      $menu .= "\n    <div>\n      <h3>${r['nom']}</h3>";
-      if ( !is_null($r['pcle']) )  {
-        $pcle = explode('//',$r['pcle']);
-        $pnom = explode('//',$r['pnom']);
-        $nom = $pnom[0];
-        foreach ( $pcle as $cle )  {
-          $menu .= "\n      <a href=\".?$cle\">$nom</a>";
-          $nom = next($pnom);
-        }
-      }
-      if ( $r['colles'] )
-        $menu .= "\n      <a href=\"colles?${r['cle']}\">Programme de colles</a>";
-      if ( $r['cdt'] )
-        $menu .= "\n      <a href=\"cdt?${r['cle']}\">Cahier de texte</a>";
-      if ( $r['docs'] )  {
-        $menu .= "\n      <a href=\"docs?${r['cle']}\">Documents à télécharger</a>";
-        $resultat_doc = $mysqli->query("SELECT id, nom FROM reps WHERE matiere = ${r['id']} AND menu = 1");
-        if ( $resultat_doc->num_rows )  {
-          while ( $d = $resultat_doc->fetch_assoc() )
-            $menu .= "\n        <a class=\"menurep\" href=\"docs?rep=${d['id']}\">${d['nom']}</a>";        
-          $resultat_doc->free();
-        }
-      }
-      $menu .= "\n    </div>";
+    $menu .= "
+    <div>
+      <h3>Espace utilisateur</h3>
+      <a href=\"prefs\">Mes préférences</a>$notes
+      <a href=\"connect?deconnexion\">Se déconnecter</a>
+      
+    </div>";
+    break;
+  // Si compte colleur/professeur
+  default:
+    /*/ Si une seule matière, pas de matière à spécifier pour l'ajout de notes de colles
+    if ( is_numeric($_SESSION['matieres']) )  {
+      $resultat = $mysqli->query("SELECT cle FROM matieres WHERE id = ${_SESSION['matieres']}");
+      $r = $resultat->fetch_assoc();
+      $notes = "\n      <a href=\"notes?${r['cle']}\">Ajouter des notes de colles</a>";
+      $resultat->free();
+    }*/
+    if ( is_numeric($_SESSION['matieres']) )  {
+      if ( substr($p,0,5) == 'notes' )
+        $p = 'notes';
+      $notes = "\n      <a href=\"notes\">Ajouter des notes de colles</a>";
     }
-    $resultat->free();
-  }
-  
-  // Lien vers l'interface d'administration
-  $proto = ( $https ) ? 'https://' : 'http://';
-  $menu .= "\n    <div>\n      <a href=\"".( ( strlen($siteadmin) ) ? $proto.$siteadmin : "$proto$site/?admin" )."\">Administration du site</a>\n    </div>";
+    else  {
+      $notes = '';
+      $resultat = $mysqli->query("SELECT cle, nom FROM matieres WHERE FIND_IN_SET(id,'${_SESSION['matieres']}')
+                                  ORDER BY FIND_IN_SET(id,'${_SESSION['matieres']}')");
+      while ( $r = $resultat->fetch_assoc() )
+        $notes .= "\n      <a href=\"notes?${r['cle']}\">Ajouter des notes de colles en ${r['nom']}</a>";
+      $resultat->free();
+    }
+    $menu .= "
+    <div>
+      <h3>Espace utilisateur</h3>
+      <a href=\"prefs\">Mes préférences</a>$notes
+      <a href=\"connect?deconnexion\">Se déconnecter</a>
+      
+    </div>";
 }
 
 // Affichage du menu
 echo str_replace("a href=\"$p\"","a id=\"actuel\" href=\"$p\"",$menu);
 ?>
 
+    <div>
+      <a href="admin/">Administration du site</a>
+    </div>
   </div>
 
 <?php
-// Affichage des informations récentes : seulement sur la partie publique
-if ( !$admin )  {
-  $resultat = $mysqli->query('SELECT titre, lien FROM recents WHERE DATEDIFF(NOW(),heure) < 10 LIMIT 20');
-  if ( $resultat->num_rows )  {
-    $aff = '';
-    while ( $r = $resultat->fetch_assoc() )
-      $aff .= "\n    <a href=\"${r['lien']}\" title=\"".substr($r['titre'],strpos($r['titre'],'>')+2)."\">${r['titre']}</a>";
-    $resultat->free();
-    echo <<<FIN
+// Affichage des informations récentes
+$resultat = $mysqli->query('SELECT titre, lien FROM recents WHERE DATEDIFF(NOW(),heure) < 10 LIMIT 20');
+if ( $resultat->num_rows )  {
+  $aff = '';
+  while ( $r = $resultat->fetch_assoc() )
+    $aff .= "\n    <a href=\"${r['lien']}\" title=\"".substr($r['titre'],strpos($r['titre'],'>')+2)."\">${r['titre']}</a>";
+  $resultat->free();
+  echo <<<FIN
   <div id="recent">
     <h3><a href="rss" id="rss"><img class="icone" src="icones/rss.png" alt="Flux RSS"></a> Infos récentes </h3>$aff
   </div>
 
 
 FIN;
-  }
 }
 ?>
 </div>
Les fichiers binaires cahier-de-prepa3.2.0/icones/big/no.png et cahier-de-prepa4.0.0/icones/big/no.png sont différents
Les fichiers binaires cahier-de-prepa3.2.0/icones/big/ok.png et cahier-de-prepa4.0.0/icones/big/ok.png sont différents
Les fichiers binaires cahier-de-prepa3.2.0/icones/no.png et cahier-de-prepa4.0.0/icones/no.png sont différents
Les fichiers binaires cahier-de-prepa3.2.0/icones/ok.png et cahier-de-prepa4.0.0/icones/ok.png sont différents
diff -urN cahier-de-prepa3.2.0/index.php cahier-de-prepa4.0.0/index.php
--- cahier-de-prepa3.2.0/index.php	2013-12-29 00:08:46.926594005 +0100
+++ cahier-de-prepa4.0.0/index.php	2014-08-25 17:58:47.861735569 +0200
@@ -3,340 +3,63 @@
 define('OK',1);
 include('debut.php');
 
-// Recherche de la page concernée
+////////////////////////////////////////////
+// Validation de la requête : cle de page //
+////////////////////////////////////////////
+
+// Recherche de la page concernée : $_REQUEST['cle'] existe.
 $mysqli = premiere_connexion();
-$resultat = $mysqli->query('SELECT p.id, CONCAT_WS(\'/\',m.cle,p.cle) AS cle, p.mat,
-                                   p.titre, p.nom, p.bandeau, p.protection, p.ordre
+$resultat = $mysqli->query('SELECT p.id, CONCAT_WS(\'/\',m.cle,p.cle) AS cle,
+                                   p.titre, p.bandeau, p.protection
                             FROM pages AS p LEFT JOIN matieres AS m ON p.mat = m.id');
-if ( $resultat->num_rows )  {
-  if ( !empty($_REQUEST) )  {
-    // Si l'argument est donné sous la forme page=cle (pour admin.php, en POST)
-    if ( isset($_REQUEST['page']) )
-      $_REQUEST[$_REQUEST['page']] = '';
-    while ( $r = $resultat->fetch_assoc() )
-      if ( isset($_REQUEST[$r['cle']]) )  {
-        $page = $r;
-        $pid = $page['id'];
-        break;
-      }
-  }
-  // Page par défaut pour la partie publique : la première page
-  if ( !isset($page) )  {
-    // Page par défaut pour l'interface d'administration : admin.php
-    if ( $admin )
-      include('admin.php');
-    $resultat->data_seek(0);
-    $page = $resultat->fetch_assoc();
-    $pid = $page['id'];
-  }
-  $resultat->free();
-}
-else
-  include('installation.php');
-
-///////////////////
-// Modifications //
-///////////////////
-if ( ( $admin ) && isset($_REQUEST['titre']) )  {
-  // Connexion à la base de données
-  $mysqli->close();
-  $mysqli = new mysqli($serveur,"$base-adm",$mdp,$base);
-  $mysqli->set_charset('utf8');
-  
-  // Traitement des modifications d'une information
-  if ( isset($_REQUEST['id']) && is_numeric($id = $_REQUEST['id']) )  {
-
-    // Vérification que l'identifiant est valide. Défaut : id=0 (nouvelle information)
-    if ( $id )  {
-      $resultat = $mysqli->query("SELECT id, ordre, cache, titre, texte, ( SELECT MAX(ordre) FROM infos WHERE page = $pid ) AS max
-                                  FROM infos WHERE page = $pid AND id = $id");
-      if ( $resultat->num_rows )  {
-        $r = $resultat->fetch_assoc();
-        $resultat->free();
-      }
-      else
-        $id = 0;
-    }
-
-    // Sauvegarde de la table contenant les données
-    sauvegarde_mysql('infos');
-
-    // Pour les informations récentes
-    $lien_recent = '.?'.addslashes($page['cle']);
-
-    // Traitement d'un ajout/modification
-    if ( isset($_REQUEST['modifie']) && strlen($_REQUEST['texte']) )  {
-
-      // Validation des données envoyées
-      $texte = $mysqli->real_escape_string($_REQUEST['texte']);
-      $titre = $mysqli->real_escape_string($_REQUEST['titre']);
-
-      // Si $id > 0 : modification d'une information existante. Si $id = 0, nouvelle information
-      if ( $id )  {
-        if ( $mysqli->query("UPDATE infos SET texte = '$texte', titre = '$titre' WHERE id = $id") )  {
-          $message = 'L\'information a bien été modifiée.';
-          // Ajout d'une information récente si information diffusée
-          if ( !$r['cache'] )
-            recent($mysqli,1,$id,"<img class=\"icone\" src=\"icones/info.png\"> $titre",$lien_recent,$texte);
-        }
-        else
-          $message = 'L\'information n\'a pas pu être modifiée. Erreur MySQL n°'.$mysqli->errno.', «'.$mysqli->error.'».';
-      }
-      else  {
-        $cache = ( isset($_REQUEST['cache']) ) ? 1 : 0;
-        if ( $mysqli->query("UPDATE infos SET ordre = (ordre+1) WHERE page = $pid")
-          && $mysqli->query("INSERT INTO infos SET ordre = 1, page = $pid, texte = '$texte', titre = '$titre', cache = $cache") )  {
-            $message = 'L\'information a bien été ajoutée.';
-            // Ajout d'une information récente si information diffusée
-            if ( !$cache )
-              recent($mysqli,1,$mysqli->insert_id,"<img class=\"icone\" src=\"icones/info.png\"> $titre",$lien_recent,$texte);
-            $mysqli->query('ALTER TABLE infos ORDER BY page,ordre');
-        }
-        else
-          $message = 'L\'information n\'a pas pu être ajoutée. Erreur MySQL n°'.$mysqli->errno.', «'.$mysqli->error.'».';
-      }
-    }
-
-    elseif ( $id )  {
-
-      // Déplacement vers le haut
-      if ( isset($_REQUEST['monte']) && ( $r['ordre'] > 1 ) )
-        $message = ( $mysqli->query("UPDATE infos SET ordre = (2*${r['ordre']}-1-ordre) WHERE ( ordre = ${r['ordre']} OR ordre = (${r['ordre']}-1) ) AND page = $pid")
-                  && $mysqli->query('ALTER TABLE infos ORDER BY page,ordre')
-        ) ? 'L\'information a bien été montée d\'une place.' : 'L\'information n\'a pas pu être déplacée. Erreur MySQL n°'.$mysqli->errno.', «'.$mysqli->error.'».';
-
-      // Déplacement vers le bas
-      elseif ( isset($_REQUEST['descend']) && ( $r['ordre'] < $r['max'] ) )
-        $message = ( $mysqli->query("UPDATE infos SET ordre = (2*${r['ordre']}+1-ordre) WHERE ( ordre = ${r['ordre']} OR ordre = (${r['ordre']}+1) ) AND page = $pid")
-                  && $mysqli->query('ALTER TABLE infos ORDER BY page,ordre')
-        ) ? 'L\'information a bien été descendue d\'une place.' : 'L\'information n\'a pas pu être déplacée. Erreur MySQL n°'.$mysqli->errno.', «'.$mysqli->error.'».';
-
-      // Positionnement "montré" (apparaît sur la partie publique)
-      elseif ( isset($_REQUEST['montre']) )  {
-        if ( $mysqli->query("UPDATE infos SET cache = 0 WHERE id = $id") )  {
-          $message = 'L\'information a bien été diffusée, elle apparaît désormais sur la partie publique.';
-          // Ajout d'information récente
-          recent($mysqli,1,$id,'<img class="icone" src="icones/info.png"> '.$mysqli->real_escape_string($r['titre']),$lien_recent,$mysqli->real_escape_string($r['texte']));
-        }
-        else
-          $message = 'L\'information n\'a pas pu être diffusée. Erreur MySQL n°'.$mysqli->errno.', «'.$mysqli->error.'».';
-      }
-
-      // Positionnement "caché" (n'apparaît pas sur la partie publique)
-      elseif ( isset($_REQUEST['cache']) )  {
-        if ( $mysqli->query("UPDATE infos SET cache = 1 WHERE id = $id") )  {
-          $message = 'L\'information n\'est plus diffusée&nbsp;: elle n\'apparaît plus sur la partie publique mais est toujours disponible ici pour modification ou diffusion.';
-          // Suppression de l'éventuelle information récente
-          recent($mysqli,1,$id);
-        }
-        else
-          $message = 'L\'information n\'a pas pu être cachée. Erreur MySQL n°'.$mysqli->errno.', «'.$mysqli->error.'».';
-      }
-
-      // Suppression
-      elseif ( isset($_REQUEST['supprime']) || !strlen($_REQUEST['texte']) )  {
-        if ( $mysqli->query("DELETE FROM infos WHERE id = $id") 
-          && $mysqli->query("UPDATE infos SET ordre = (ordre-1) WHERE ordre > ${r['ordre']} AND page = $pid") )  {
-          $message = 'L\'information a bien été supprimée.';
-          // Suppression de l'éventuelle information récente
-          recent($mysqli,1,$id);
-        }
-        else
-          $message = 'L\'information n\'a pas pu être supprimée. Erreur MySQL n°'.$mysqli->errno.', «'.$mysqli->error.'».';
-      }
-    }
-  }
-
-  // Traitement des modifications de la page
-  elseif ( isset($_REQUEST['modifie_page']) )  {
-    if ( !strlen($_REQUEST['nom']) || !strlen($_REQUEST['cle']) )
-      $message = 'Il n\'est pas possible de valider une page sans clé ou sans nom.';
-    else  {
-      // Vérifications des données envoyées
-      $nom = $mysqli->real_escape_string($_REQUEST['nom']);
-      $titre = $mysqli->real_escape_string($_REQUEST['titre']);
-      $cle = $mysqli->real_escape_string($_REQUEST['cle']);
-      $bandeau = $mysqli->real_escape_string($_REQUEST['bandeau']);
-      $protection = ( isset($_REQUEST['protection']) ) ? 1 : 0;
-      $mat = ( is_numeric($_REQUEST['mat']) ) ? $_REQUEST['mat'] : 0;
-      // Si matière identique
-      if ( $mat == $page['mat'] )
-        $message = ( $mysqli->query("UPDATE pages SET cle = '$cle', nom = '$nom', titre = '$titre',
-                                     bandeau = '$bandeau', protection = $protection WHERE id = $pid")
-        ) ? 'Les données de la page ont bien été modifiées.' : 'Les données de la page n\'ont pas pu être modifiées. Erreur MySQL n°'.$mysqli->errno.', «'.$mysqli->error.'».';
-      else  {
-        $resultat = $mysqli->query("SELECT IFNULL(MAX(ordre)+1,1) AS max FROM pages WHERE mat = $mat");
-        $m = $resultat->fetch_assoc();
-        $resultat->free();
-        $message = ( $mysqli->query("UPDATE pages SET cle = '$cle', nom = '$nom', mat = $mat, titre = '$titre',
-                                     ordre = ${m['max']}, bandeau = '$bandeau', protection = $protection WHERE id = $pid")
-                  && $mysqli->query("UPDATE pages SET ordre = (ordre-1) WHERE mat = ${page['mat']} AND ordre > ${page['ordre']}")
-                  && $mysqli->query('ALTER TABLE pages ORDER BY mat,ordre')
-        ) ? 'Les données de la page ont bien été modifiées.' : 'Les données de la page n\'ont pas pu être modifiées. Erreur MySQL n°'.$mysqli->errno.', «'.$mysqli->error.'».';
-      }
-      
-      // Mise à jour des données de la page
-      $resultat = $mysqli->query("SELECT CONCAT_WS('/',m.cle,p.cle) AS cle, p.mat,
-                                         p.titre, p.nom, p.bandeau, p.protection, p.ordre
-                                  FROM pages AS p LEFT JOIN matieres AS m ON p.mat = m.id WHERE p.id = $pid");
-      $page = $resultat->fetch_assoc();
-      $resultat->free();
+if ( !empty($_REQUEST) )  {
+  while ( $r = $resultat->fetch_assoc() )
+    if ( isset($_REQUEST[$r['cle']]) )  {
+      $page = $r;
+      break;
     }
-  }
+}
+// Page par défaut : la première
+if ( !isset($page) )  {
+  $resultat->data_seek(0);
+  $page = $resultat->fetch_assoc();
+}
+$resultat->free();
 
-  // Passage en connexion MySQL en lecture seulement
-  $mysqli->close();
-  $mysqli = new mysqli($serveur,$base,$mdp,$base);
-  $mysqli->set_charset('utf8');
+// Si la page est protégée et l'utilisateur non autorisé
+while ( $page['protection'] > $autorisation )  {
+  $p = ".?${page['cle']}";
+  $t = $page['titre'];
+  include('login.php');
 }
 
 //////////////
 //// HTML ////
 //////////////
-$p = ".?${page['cle']}$urladmin";
+$p = ".?${page['cle']}";
 $t = $page['titre'];
 
-// Interface d'administration
-if ( $admin ) {
+// Haut de page, menu et message
+include('haut.php');
 
-  // Haut de page, menu et message
-  $t = "Modification de la page «&nbsp;${page['nom']}&nbsp;»";
-  include('haut.php');
-
-  // Aide générale  
-?>
-
-  <div class="item aide">
-    <h3>Aide et explications</h3>
-    <p>Vous pouvez ci-dessous modifier les préférences associées à la page et ajouter/modifier les informations qui s'y trouvent.</p>
-    <h4>Préférences associées à la page</h4>
-    <ul>
-      <li>Le <em>titre</em> sera affiché en haut de page et dans la barre de titre du navigateur. Par exemple, «&nbsp;À propos de l'ADS et du TIPE&nbsp;».</li>
-      <li>Le <em>nom dans le menu</em> est affiché dans le menu en tant que lien vers la page. Il est préférable qu'il rentre sur une ligne, il faut donc le choisir assez court. Par exemple, «&nbsp;Informations ADS/TIPE&nbsp;».</li>
-      <li>La <em>clé</em> est un mot-clé qui est utilisé uniquement dans l'adresse de la page (<code>http://<?php echo $site; ?>/?[clé]</code>). Il vaut mieux que ce soit un mot unique, court et sans majuscule. Par exemple, «&nbsp;ads-tipe&nbsp;».</li>
-      <li>Le <em>texte de début</em> sera affiché au-dessus des informations de la page. Il s'agit d'une ou deux phrases maximum.</li>
-    </ul>
-    <p>La case à cocher <em>Demande d'identification pour l'affichage de la page </em> permet de restreindre la visibilité de la page aux visiteurs qui se sont identifiés. Dans ce cas, après <a href="utilisateurs">avoir créé un compte pour les élèves ici</a>, ces informations seront accessibles aux élèves auxquels vous aurez donné l'identifiant et le mot de passe choisis.</p>
-    <h4>Informations</h4>
-    <p>Pour l'ajout d'information, la case à cocher <em>Ne pas diffuser sur la partie publique</em> permet de cacher temporairement cette information, par exemple pour la diffuser ultérieurement.</p>
-    <p>Vous pouvez modifier le titre ou le texte d'une information existante, ou changer l'ordre d'apparition des différentes informations de la page.</p>
-    <p>Vous pouvez <em>cacher</em> ou <em>montrer</em> une information existante, c'est-à-dire la diffuser ou non sur la partie publique.</p>
-    <p>Vous pouvez supprimer une information existante par le bouton <em>Supprimer</em> ou en validant un texte vide.</p>
-  </div>
-  
-<?php
-
-  // Select sur les matières
-  $select_matieres = '<option value="O">Pas de matière associée</option>';
-  $resultat = $mysqli->query("SELECT id, nom FROM matieres WHERE FIND_IN_SET(id,'${_SESSION['matieres']}')");
+// Affichage des informations diffusées
+$resultat = $mysqli->query("SELECT IF(LENGTH(titre),CONCAT('<h3>',titre,'</h3>'),'') AS titre, texte FROM infos WHERE page = ${page['id']} AND cache = 0");
+if ( $resultat->num_rows )  {
+  echo "\n  <h2>${page['bandeau']}</h2>\n";
   while ( $r = $resultat->fetch_assoc() )
-    $select_matieres .= "<option value=\"${r['id']}\">${r['nom']}</option>";
-  $resultat->free();
-  $select_matieres = str_replace("\"${page['mat']}\"","\"${page['mat']}\" selected",$select_matieres);  
-  $disabled = ( $page['ordre']+$page['mat'] == 1 ) ? ' disabled' : '';
-
-  // Formulaire de modification des données de la page et formulaire d'ajout d'information
-  $protection = ( $page['protection'] ) ? ' checked' : '';
-  $page['cle'] = basename($page['cle']);
-  echo <<<FIN
-  <div class="item admin">
-  <form action="$p" method="post">
-    <input class="bouton" type="submit" name="modifie_page" value="Valider">
-    <h3>Modifier les préférences de la page</h3>
-    <p class="ligne"><label for="titre">Titre&nbsp;: </label><input type="text" id="titre" name="titre" value="${page['titre']}" size="80"></p>
-    <p class="ligne"><label for="nom">Nom dans le menu&nbsp;: </label><input type="text" id="nom" name="nom" value="${page['nom']}" size="50"></p>
-    <p class="ligne"><label for="cle">Clé dans l'adresse&nbsp;: </label><input type="text" id="cle" name="cle" value="${page['cle']}" size="30"></p>
-    <p class="ligne"><label for="mat">Matière associée&nbsp;: </label>
-      <select id="mat" name="mat"$disabled>$select_matieres</select>
-    </p>
-    <p class="ligne"><label for="bandeau">Texte de début&nbsp;:</label></p>
-    <textarea id="bandeau" name="bandeau" rows="2" cols="100">${page['bandeau']}</textarea>
-    <p class="ligne"><label for="protection">Demande d'identification pour l'affichage de la page&nbsp;: </label><input type="checkbox" id="protection" name="protection" value="1"$protection></p>
-  </form>
-  </div>
-
-  <div class="item admin">
-  <form action="$p" method="post">
-    <input class="bouton" type="submit" name="modifie" value="Valider" title="Valider les modifications de la page">
-    <h3>Ajouter une nouvelle information</h3>
-    <p class="transparent"><input class="ligne" type="text" name="titre" size=50 maxlength=65533 value="Titre de l'information"></p>
-    <textarea name="texte" rows="10" cols="100">&lt;p&gt;Texte de l'information&lt;/p&gt;</textarea>
-    <p class="ligne"><label for="cache">Ne pas diffuser sur la partie publique&nbsp;: </label><input type="checkbox" id="cache" name="cache" value="1"></p>
-    <input type="hidden" name="id" value="0">
-  </form>
-  </div>
-
-FIN;
-
-  // Affichage des informations
-  $resultat = $mysqli->query("SELECT id, ordre, cache, titre, texte FROM infos WHERE page = $pid");
-  $mysqli->close();
-  if ( $max = $resultat->num_rows )  {
-    while ( $r = $resultat->fetch_assoc() )  {
-      $monte = ( $r['ordre'] == 1 ) ? '' : "\n      <input type=\"submit\" name=\"monte\" value=\"&uarr;\" title=\"Remonter l'information dans l'ordre d'apparition\">";
-      $descend = ( $r['ordre'] == $max ) ? '' : "\n      <input type=\"submit\" name=\"descend\" value=\"&darr;\" title=\"Descendre l'information dans l'ordre d'apparition\">";
-      if ( $r['cache'] )  {
-        $diff = ' (information non diffusée sur la partie publique)';
-        $cache_classe = ' cache';
-        $cache = "\n      <input type=\"submit\" name=\"montre\" value=\"Montrer\" title=\"Diffuser l'information, la rendre visible sur la partie publique\">";
-      }
-      else  {
-        $cache_classe = $diff = '';
-        $cache = "\n      <input type=\"submit\" name=\"cache\" value=\"Cacher\" title=\"Ne plus diffuser l'information, la rendre invisible sur la partie publique\">";
-      }
-      echo <<<FIN
-
-  <div class="item admin$cache_classe">
-  <form action="$p" method="post">
-    <h3>Information n°${r['ordre']}&nbsp;: ${r['titre']}$diff</h3>
-    <p class="boutons">
-      <input type="submit" name="modifie" value="Valider" title="Valider les modifications sur le titre ou le texte">$monte$descend$cache
-      <input type="submit" name="supprime" value="Supprimer" title="Supprimer l'information">
-    </p>
-    <p class="transparent"><input class="ligne" type="text" name="titre" size=50 maxlength=65533 value="${r['titre']}"></p>
-    <textarea name="texte" rows="10" cols="100">${r['texte']}</textarea>
-    <input type="hidden" name="id" value="${r['id']}">
-  </form>
-  </div>
-
-FIN;
-    }
-    $resultat->free();
-  }
-  else
-    echo "\n  <h2>Il n'y actuellement aucune information enregistrée pour cette page.</h2>\n\n";
-
-  echo "\n  <script type=\"text/javascript\" src=\"textarea.js.php\"></script>\n\n";
-}
-
-// Partie publique
-else  {
-
-  // Demander l'identification si la page est protégée et l'utilisateur non connecté
-  if ( $page['protection'] && !$lecteur )
-    include('login_lecture.php');
-
-  // Haut de page, menu et message
-  include('haut.php');
-  // Affichage des informations diffusées
-  $resultat = $mysqli->query("SELECT titre, texte FROM infos WHERE page = $pid AND cache = 0");
-  $mysqli->close();
-  if ( $resultat->num_rows )  {
-    echo "\n  <h2>${page['bandeau']}</h2>\n";
-    while ( $r = $resultat->fetch_assoc() )
-      echo <<<FIN
+    echo <<<FIN
 
   <div class="item info">
-    <h3>${r['titre']}</h3>
+    ${r['titre']}
 ${r['texte']}
   </div>
 
 FIN;
-    $resultat->free();
-  }
-  else
-    echo "  <h2>Cette page est actuellement vide.</h2>\n\n";
+  $resultat->free();
 }
+else
+  echo "  <h2>Cette page est actuellement vide.</h2>\n\n";
+$mysqli->close();
 
 // Bas de page
 include('bas.php');
diff -urN cahier-de-prepa3.2.0/installation.php cahier-de-prepa4.0.0/installation.php
--- cahier-de-prepa3.2.0/installation.php	2013-12-26 22:34:19.660883030 +0100
+++ cahier-de-prepa4.0.0/installation.php	2014-08-28 11:52:44.385327167 +0200
@@ -1,7 +1,7 @@
 <?php
 // Script d'installation de Cahier de Prépa
-// Vérifie la configuration et l'existence de la base de données
 // Aide à l'installation et à la configuration
+// Vérifie l'existence de la base de données et des répertoires
 
 // Headers HTML
 $header = <<<FIN
@@ -18,17 +18,6 @@
 
 <h1>Cahier de Prépa&nbsp;: installation</h1>
 
-<div class="item">
-  <h3>Bienvenue sur <a href="http://cahier-de-prepa.fr">Cahier de Prépa</a> et merci d'essayer ce gestionnaire de site&nbsp;!</h3>
-  <p><a href="http://cahier-de-prepa.fr">Cahier de Prépa</a> est un gestionnaire de sites web pour la communication des professeurs vers leurs élèves de CPGE. Il permet de&nbsp;:</p>
-  <ul>
-    <li>Faire passer des informations de façon rapide sur des thèmes précis grâce à des pages dédiées</li>
-    <li>Annoncer le programme de colle (pour les élèves et les colleurs)</li>
-    <li>Tenir à jour le cahier de texte</li>
-    <li>Mettre à disposition les documents distribués ou non</li>
-  </ul>
-</div>
-
 FIN;
 $footer = <<<FIN
 
@@ -39,10 +28,7 @@
 // Recharge de la page si elle est lancée par un autre script (donc config.php déjà appelé)
 if ( defined('OK') )  {
   $proto = ( $GLOBALS['https'] ) ? 'https://' : 'http://';
-  if ( strlen($GLOBALS['siteadmin']) )
-    header("Location: $proto${GLOBALS['siteadmin']}/installation");
-  else
-    header("Location: $proto${GLOBALS['site']}/installation");
+  header("Location: $proto${GLOBALS['site']}/installation");
   exit();
 }
 
@@ -54,7 +40,7 @@
 else
   exit($header.'<h3 class="warning admin">Le fichier de configuration n\'existe pas ou n\'est pas lisible. Il doit s\'appeler config.php, se trouver au même endroit que l\'ensemble des fichiers constituant le site et être lisible par l\'utilisateur d\'Apache (<code>'.`whoami | tr -d '\n'`.'</code>).</h3>'.$footer);
 // Vérification des données du fichier de configuration
-if ( !isset($site) || !isset($siteadmin) || !isset($classe) || !isset($lycee) || !isset($https) || !isset($base) || !isset($mdp) )
+if ( !isset($site) || !isset($https) || !isset($serveur) || !isset($base) || !isset($mdp) )
   exit($header.'<h3 class="warning admin">Le fichier de configuration est incomplet, il manque des données nécessaire au fonctionnement du site. Il faut le modifier manuellement.</h3>'.$footer);
 if ( strlen($base) > 12 )
   exit($header.'<h3 class="warning admin">Le nom de la base de données est trop long. Il ne doit pas être supérieur à 12 caractères. Il faut corriger cela dans le fichier de configuration manuellement.</h3>'.$footer);
@@ -62,589 +48,264 @@
 ///////////////////////////////
 // Connexion obligatoire à partir d'ici, en entrant le mot de passe du fichier de configuration
 ///////////////////////////////
-$s = ( strlen($siteadmin) ) ? $siteadmin : $site;
-session_name(md5("$site-admin"));
-session_set_cookie_params(0,strchr($s,'/').'/',str_replace(strchr($s,'/'),'',$s));
+session_name(md5("$site-install"));
+session_set_cookie_params(0,strchr($site,'/').'/',str_replace(strchr($site,'/'),'',$site));
 session_start();
-if ( isset($_SESSION[md5($site)]) )  {
-  if ( !isset($_REQUEST['deconnexion']) && ( $_SESSION['client'] == $_SERVER['HTTP_USER_AGENT'] ) && ( $_SESSION['ip'] == $_SERVER['REMOTE_ADDR'] ) )
-    $admin = true;
-  else  $admin = false;
-}
-elseif ( isset($_REQUEST['motdepasse']) && ( $_REQUEST['motdepasse'] == $mdp ) )  {
-  $admin = true;
+if ( isset($_REQUEST['motdepasse']) && ( $_REQUEST['motdepasse'] == $mdp ) )  {
   // Interdiction de garder son identifiant de session
   session_regenerate_id(true);
   $_SESSION = array();
   // Pour vérification aux connexions ultérieures
-  $_SESSION[md5($site)] = true;
+  $_SESSION[md5("$site")] = true;
   $_SESSION['client'] = $_SERVER['HTTP_USER_AGENT'];
   $_SESSION['ip'] = $_SERVER['REMOTE_ADDR'];
+  $_SESSION['time'] = time()+600;
 }
-else  $admin = false;
-if ( !$admin )  {
+elseif ( !isset($_SESSION[md5("$site")]) || ( $_SESSION['time'] < time() ) || isset($_REQUEST['deconnexion']) || ( $_SESSION['client'] != $_SERVER['HTTP_USER_AGENT'] ) || ( $_SESSION['ip'] != $_SERVER['REMOTE_ADDR'] ) )  {
+  // Suppression du cookie et des données de session
   $_SESSION = array();
   setcookie(session_name(),'',time()-3600);
   session_regenerate_id(true);
   echo <<<FIN
 $header
-<form class="warning admin" action="" method="post">
-  <h3>Pour aller plus loin, vous devez entrer le mot de passe contenu dans le fichier de configuration.</h3>
-  <p><input type="password" name="motdepasse"> <input type="submit" name="Ok" value="Envoyer"></p>
-</form>
-$footer
-FIN;
-  exit();
-}
-// Autoriser la connexion directe à l'interface d'administration uniquement si l'utilisateur et la matière sont définis (passage par l'étape 2)
-$_SESSION['time'] = ( isset($_SESSION['ip']) ) ? time()+900 : time();
-
-///////////////////////////////
-// Codes MySQL des différentes étapes
-///////////////////////////////
-$code_etape1 = <<<FIN
-DROP DATABASE IF EXISTS `$base`;
-CREATE DATABASE `$base` DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;
-DELETE FROM mysql.user WHERE User = '$base' OR User = '$base-adm';
-INSERT INTO mysql.user (Host, User, Password) 
-  VALUES ('$serveur', '$base', PASSWORD('$mdp')),
-         ('$serveur', '$base-adm', PASSWORD('$mdp'));
-DELETE FROM mysql.db WHERE Db = '$base';
-INSERT INTO mysql.db (Host, Db, User, Select_priv, Insert_priv, Update_priv, Delete_priv, Alter_priv, Drop_priv) 
-  VALUES ('$serveur', '$base', '$base', 'Y', 'N', 'N', 'N', 'N', 'N'),
-         ('$serveur', '$base','$base-adm', 'Y', 'Y', 'Y', 'Y', 'Y', 'Y');
-FLUSH PRIVILEGES;
-
-USE `$base`;
-CREATE TABLE `matieres` (
-  `id` tinyint(2) unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT,
-  `ordre` tinyint(2) unsigned NOT NULL,
-  `cle` varchar(50) NOT NULL,
-  `nom` varchar(50) NOT NULL,
-  `colles` tinyint(1) unsigned NOT NULL,
-  `cdt` tinyint(1) unsigned NOT NULL,
-  `docs` tinyint(1) unsigned NOT NULL,
-  KEY `colles` (`colles`),
-  KEY `cdt` (`cdt`),
-  KEY `docs` (`docs`)
-) ENGINE=MyISAM DEFAULT CHARSET=utf8;
-
-CREATE TABLE `utilisateurs` (
-  `id` tinyint(2) unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT,
-  `nom` varchar(50) NOT NULL,
-  `mdp` char(40) NOT NULL,
-  `matieres` varchar(15) NOT NULL,
-  `protection` tinyint(1) NOT NULL
-) ENGINE=MyISAM DEFAULT CHARSET=utf8;
-
-CREATE TABLE `pages` (
-  `id` tinyint(2) unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT,
-  `ordre` tinyint(2) unsigned NOT NULL,
-  `cle` varchar(50) NOT NULL,
-  `mat` tinyint(2) NOT NULL,
-  `nom` varchar(50) NOT NULL,
-  `titre` text NOT NULL,
-  `bandeau` text NOT NULL,
-  `protection` tinyint(1) unsigned NOT NULL
-) ENGINE=MyISAM DEFAULT CHARSET=utf8;
-
-CREATE TABLE `infos` (
-  `id` smallint(4) unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT,
-  `ordre` tinyint(2) unsigned NOT NULL,
-  `page` tinyint(2) unsigned NOT NULL,
-  `cache` tinyint(1) unsigned NOT NULL,
-  `titre` text NOT NULL,
-  `texte` text NOT NULL,
-  KEY `ordre` (`ordre`,`page`),
-  KEY `cache` (`cache`)
-) ENGINE=MyISAM DEFAULT CHARSET=utf8;
-
-CREATE TABLE `semaines` (
-  `id` tinyint(2) unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT,
-  `debut` date NOT NULL,
-  `colle` tinyint(1) unsigned NOT NULL,
-  `vacances` tinyint(1) unsigned NOT NULL,
-  KEY `debut` (`debut`),
-  KEY `colle` (`colle`)
-) ENGINE=MyISAM DEFAULT CHARSET=utf8;
-
-CREATE TABLE `colles` (
-  `id` tinyint(3) unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT,
-  `semaine` tinyint(2) unsigned NOT NULL,
-  `matiere` tinyint(2) unsigned NOT NULL,
-  `texte` text NOT NULL,
-  `cache` tinyint(1) NOT NULL,
-  KEY `semaine` (`semaine`),
-  KEY `matiere` (`matiere`),
-  KEY `cache` (`cache`)
-) ENGINE=MyISAM DEFAULT CHARSET=utf8;
-
-CREATE TABLE `cdt` (
-  `id` smallint(3) unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT,
-  `matiere` tinyint(2) unsigned NOT NULL,
-  `semaine` tinyint(2) unsigned NOT NULL,
-  `jour` date NOT NULL,
-  `h_debut` time NOT NULL,
-  `h_fin` time NOT NULL,
-  `pour` date NOT NULL,
-  `type` tinyint(2) unsigned NOT NULL,
-  `texte` text NOT NULL,
-  `demigroupe` tinyint(1) unsigned NOT NULL,
-  `cache` tinyint(1) unsigned NOT NULL,
-  KEY `matiere` (`matiere`),
-  KEY `semaine` (`semaine`),
-  KEY `type` (`type`),
-  KEY `cache` (`cache`)
-) ENGINE=MyISAM DEFAULT CHARSET=utf8;
-
-CREATE TABLE `cdt-types` (
-  `id` tinyint(2) unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT,
-  `matiere` tinyint(2) unsigned NOT NULL,
-  `ordre` tinyint(2) unsigned NOT NULL,
-  `titre` varchar(50) NOT NULL,
-  `cle` varchar(20) NOT NULL,
-  `deb_fin_pour` tinyint(1) unsigned NOT NULL,
-  KEY `matiere` (`matiere`)
-) ENGINE=MyISAM DEFAULT CHARSET=utf8;
-
-CREATE TABLE `cdt-seances` (
-  `id` tinyint(2) unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT,
-  `matiere` tinyint(2) unsigned NOT NULL,
-  `ordre` tinyint(2) unsigned NOT NULL,
-  `nom` varchar(40) NOT NULL,
-  `jour` tinyint(1) unsigned NOT NULL,
-  `h_debut` time NOT NULL,
-  `h_fin` time NOT NULL,
-  `type` tinyint(3) unsigned NOT NULL,
-  `demigroupe` tinyint(1) unsigned NOT NULL,
-  KEY `matiere` (`matiere`)
-) ENGINE=MyISAM DEFAULT CHARSET=utf8;
-
-CREATE TABLE `reps` (
-  `id` tinyint(3) unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT,
-  `parent` tinyint(3) unsigned NOT NULL,
-  `parents` varchar(50) NOT NULL,
-  `matiere` tinyint(2) unsigned NOT NULL,
-  `nom` varchar(100) NOT NULL,
-  `nbrep` tinyint(2) unsigned NOT NULL,
-  `nbdoc_v` tinyint(2) unsigned NOT NULL,
-  `nbdoc_nv` tinyint(2) unsigned NOT NULL,
-  `protection` tinyint(1) unsigned NOT NULL,
-  `menu` tinyint(1) unsigned NOT NULL,
-  KEY `parent` (`parent`),
-  KEY `matiere` (`matiere`)
-) ENGINE=MyISAM  DEFAULT CHARSET=utf8;
-
-CREATE TABLE `docs` (
-  `id` smallint(3) unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT,
-  `parent` tinyint(3) unsigned NOT NULL,
-  `parents` varchar(50) NOT NULL,
-  `matiere` tinyint(2) unsigned NOT NULL,
-  `nom` varchar(100) NOT NULL,
-  `nom_nat` VARCHAR(100) NOT NULL,
-  `upload` date NOT NULL,
-  `taille` varchar(12) NOT NULL,
-  `lien` char(15) NOT NULL,
-  `ext` varchar(5) NOT NULL,
-  `protection` tinyint(1) unsigned NOT NULL,
-  KEY `parent` (`parent`),
-  KEY `matiere` (`matiere`),
-  KEY `nom_nat` (`nom_nat`)
-) ENGINE=MyISAM  DEFAULT CHARSET=utf8;
-
-CREATE TABLE `recents` (
-  `id` smallint(5) unsigned NOT NULL PRIMARY KEY,
-  `heure` datetime NOT NULL,
-  `titre` varchar(200) NOT NULL,
-  `lien` varchar(30) NOT NULL,
-  `texte` text NOT NULL,
-  KEY `heure` (`heure`)
-) ENGINE=MyISAM  DEFAULT CHARSET=utf8;
-
-FIN;
 
-$code_etape2 = <<<FIN
-TRUNCATE matieres;
-INSERT INTO matieres (id,ordre,cle,nom,colles,cdt,docs) VALUES (1,1,'[cle_matiere]','[nom_matiere]',0,0,0);
-TRUNCATE utilisateurs;
-INSERT INTO utilisateurs (nom,mdp,matieres) VALUES ('[utilisateur]',SHA1('[mot_de_passe]'),'1');
-FIN;
-
-$c = addslashes($classe);
-$l = addslashes($lycee);
-$code_etape3 = <<<FIN
-TRUNCATE reps;
-INSERT INTO reps (id,parent,parents,matiere,nom,nbdoc_v,nbdoc_nv,nbrep,protection)
- VALUES (1, 0, '0', 0, 'Général', 0, 0, 0, 0),
-        (2, 0, '0', 1, (SELECT nom FROM matieres WHERE id = 1), 0, 0, 0, 0);
-TRUNCATE pages;
-INSERT INTO pages (ordre,cle,nom,titre,bandeau,protection)
-  VALUES (1,'accueil','Accueil','La classe $c du lycée $l','Dernières informations importantes',0);
-TRUNCATE `cdt-types`;
-INSERT INTO `cdt-types` (`matiere`, `ordre`, `cle`, `titre`, `deb_fin_pour`) VALUES
-(1, 1, 'cours', 'Cours', 1),
-(1, 2, 'TD', 'Séance de travaux dirigés', 1),
-(1, 3, 'TP', 'Séance de travaux pratiques', 1),
-(1, 4, 'DS', 'Devoir surveillé', 1),
-(1, 5, 'interros', 'Interrogation de cours', 0),
-(1, 6, 'distributions', 'Distribution de document', 0),
-(1, 7, 'DM', 'Devoir maison', 2);
-
-TRUNCATE semaines;
-
--- suivi des définitions des semaines de la zone choisie
+<div class="item">
+  <h3>Bienvenue sur <a href="http://cahier-de-prepa.fr">Cahier de Prépa</a> et merci d'essayer ce gestionnaire de site&nbsp;!</h3>
+  <p><a href="http://cahier-de-prepa.fr">Cahier de Prépa</a> est un gestionnaire de sites web pour la communication des professeurs vers leurs élèves de CPGE. Il permet de&nbsp;:</p>
+  <ul>
+    <li>Faire passer des informations de façon rapide sur des thèmes précis grâce à des pages dédiées</li>
+    <li>Annoncer le programme de colle (pour les élèves et les colleurs)</li>
+    <li>Tenir à jour le cahier de texte</li>
+    <li>Mettre à disposition les documents distribués ou non, de façon sécurisée</li>
+    <li>Saisir et consulter des notes de colles</li>
+  </ul>
+</div>
 
+<form class="warning admin" action="" method="post">
+<h3>Pour aller plus loin, vous devez entrer le mot de passe contenu dans le fichier de configuration.</h3>
+<p><input type="password" name="motdepasse"> <input type="submit" name="Ok" value="Envoyer"></p>
+</form>
 FIN;
-if ( isset($_REQUEST['zone']) )  {
-  include('definition_semaines.php');
-  $code_etape3 .= $semaines;
+  exit($footer);
 }
+// Tout est ok : session valide pendant 10 minutes
+else
+  $_SESSION['time'] = time()+600;
 
-///////////////////////////////
-// Installation
-///////////////////////////////
-if ( isset($_REQUEST['etape']) )  {
-  switch ( $_REQUEST['etape'] )  {
-    case 1:
-      $mysqli = new mysqli($serveur,'root',$_REQUEST['mdp'],'mysql');
-      if ( $mysqli->connect_errno )
-        $message = 'La connexion à la base de données est impossible. Le mot de passe root est certainement faux. Erreur MySQL n°'.$mysqli->connect_errno.', «'.$mysqli->connect_error.'».';
-      else  {
-        $mysqli->set_charset('utf8');
-        $mysqli->multi_query($code_etape1);
-        $mysqli->close();
-      }
-    break;
-    case 2:
-      if ( strlen($_REQUEST['mdp1']) && ( $_REQUEST['mdp1'] == $_REQUEST['mdp2'] ) )  {
-        $mysqli = new mysqli($serveur,"$base-adm",$mdp,$base);
-        $mysqli->set_charset('utf8');
-        $user = $mysqli->real_escape_string($_REQUEST['user']);
-        $clemat = $mysqli->real_escape_string($_REQUEST['cle']);
-        $code_etape2 = str_replace('[mot_de_passe]',$_REQUEST['mdp1'],$code_etape2);
-        $code_etape2 = str_replace('[utilisateur]',$user,$code_etape2);
-        $code_etape2 = str_replace('[cle_matiere]',$clemat,$code_etape2);
-        $code_etape2 = str_replace('[nom_matiere]',$mysqli->real_escape_string($_REQUEST['matiere']),$code_etape2);
-        $mysqli->multi_query($code_etape2);
-        if ($mysqli->errno)
-          $message = 'Erreur MySQL n°'.$mysqli->errno.', «'.$mysqli->error.'».';
-        $mysqli->close();
-      }
-      else
-        $message = 'Le mot de passe n\'a pas été correctement entré. Il ne peut pas être vide et vous devez l\'écrire de façon identique dans les deux cases.';
-    break;
-    case 3:
-      $mysqli = new mysqli($serveur,"$base-adm",$mdp,$base);
-      $mysqli->set_charset('utf8');
-      $mysqli->multi_query($code_etape3);
-      if ($mysqli->errno)
-        $message = 'Erreur MySQL n°'.$mysqli->errno.', «'.$mysqli->error.'».';
-      $mysqli->close();
-  }
-  // Pour prendre en compte les modifications avant les tests qui suivent
-  sleep(1);
-}
+////////////////////////////
+// Fabrication de la base //
+////////////////////////////
 
-///////////////////////////////
-// Vérifications
-///////////////////////////////
-// Étape 1 : Vérification de la connexion pour l'utilisateur "public" (en lecture)
+// Vérification de la connexion. Pas de connexion signifie qu'il faut réaliser l'installation
 $mysqli = new mysqli($serveur,$base,$mdp);
-if ( $mysqli->connect_errno || !($mysqli->select_db($base)) )  $etape = 1;
-// Vérification de la connexion pour l'utilisateur "administration" (en écriture)
-else  {
-  $mysqli->close();
-  $mysqli = new mysqli($serveur,"$base-adm",$mdp);
-  if ( $mysqli->connect_errno || !($mysqli->select_db($base)) )  $etape = 1;
+if ( $mysqli->connect_errno || !($mysqli->select_db($base)) )  {
+  $message = '';
   
-  // Étape 1bis : Vérification de la définition des tables
-  else  {
-    $resultat = $mysqli->query('SHOW TABLES');
-    $tables = array('cdt','cdt-seances','cdt-types','colles','docs','infos','matieres','pages','reps','semaines','utilisateurs');
-    if ( $resultat->num_rows )  {
-      while ( $r = $resultat->fetch_row() )  $tables = array_diff($tables,$r);
-      $resultat->free();
-    }
-    if ( !empty($tables) )  $etape = 1;
-    
-    // Étape 2 : Vérification de l'existence d'au moins un administrateur
+  //////////////////////////////
+  // Récupération des données //
+  //////////////////////////////
+  if ( isset($_REQUEST['mdproot']) )  {
+    // Vérification du mot de passe root
+    $mysqli = new mysqli($serveur,'root',$_REQUEST['mdproot'],'mysql');
+    if ( $mysqli->connect_errno )
+      $message = '<div class="warning">La connexion à la base de données est impossible. Le mot de passe root est certainement incorrect. Erreur MySQL n°'.$mysqli->connect_errno.', «'.$mysqli->connect_error.'».</div>';
     else  {
-      $resultat = $mysqli->query('SELECT id, nom, mdp, matieres, protection FROM utilisateurs LIMIT 1');
-      if ( !($resultat->num_rows) )  $etape = 2;
-      
-      // Étape 2bis : Vérification de l'existence d'au moins une matière
+      // Validation des données
+      $mysqli->set_charset('utf8');
+      $genre = ( in_array($_REQUEST['genre'],array(1,2,3)) ) ? $_REQUEST['genre']+5 : 5;
+      $prenom = mb_convert_case($mysqli->real_escape_string($_REQUEST['prenom']),MB_CASE_TITLE,'UTF-8');
+      $nom = mb_convert_case($mysqli->real_escape_string($_REQUEST['nom']),MB_CASE_TITLE,'UTF-8');
+      $mail = strtolower($mysqli->real_escape_string($_REQUEST['mail']));
+      $mot_de_passe = sha1($_REQUEST['mot_de_passe']);
+      // Login automatiquement généré
+      $login = mb_strtolower(mb_substr($prenom,0,1,'UTF-8').str_replace(' ','_',$nom),'UTF-8');
+      $nom_matiere = $mysqli->real_escape_string($_REQUEST['nom_matiere']);
+      $cle_matiere = $mysqli->real_escape_string($_REQUEST['cle_matiere']);
+      $titre = $mysqli->real_escape_string($_REQUEST['titre']);
+      if ( !strlen($prenom) || !strlen($nom) || !filter_var($mail,FILTER_VALIDATE_EMAIL) || !strlen($mot_de_passe) || !strlen($nom_matiere) || !strlen($cle_matiere) || !strlen($titre) )
+        $message = '<div class="warning">Toutes les données sont obligatoires. L\'adresse mail doit être valide.</div>';
       else  {
-        $r = $resultat->fetch_assoc();
-        $_SESSION['id'] = $r['id'];
-        $_SESSION['nom'] = $r['nom'];
-        $_SESSION['mdp'] = $r['mdp'];
-        $_SESSION['matieres'] = $r['matieres'];
-        $_SESSION['protection'] = $r['protection'];
-        $resultat->free();
-        $resultat = $mysqli->query('SELECT id FROM matieres LIMIT 1');
-        if ( !($resultat->num_rows) )  $etape = 2;
-        
-        // Étape 3 : Vérification de la présence d'au moins un répertoire
+        // Traitement des données
+        include('def_sql.php');
+        $mysqli->set_charset('utf8');
+        $mysqli->multi_query($requete);
+        if ( $mysqli->errno )  {
+          $message = 'Quelque chose n\'a pas fonctionné. Erreur MySQL n°'.$mysqli->errno.', «'.$mysqli->error.'».';
+          $mysqli->close();
+        }
         else  {
-          $r = $resultat->fetch_assoc();
-          $resultat->free();
-          $resultat = $mysqli->query('SELECT id FROM reps LIMIT 1');
-          if ( !($resultat->num_rows) )  $etape = 3;
-          
-          // Étape 3bis : Vérification de la présence d'au moins une page
-          else  {
-            $resultat->free();
-            $resultat = $mysqli->query('SELECT id FROM pages LIMIT 1');
-            if ( !($resultat->num_rows) )  $etape = 3;
-          
-            // Étape 3ter : Vérification de la présence d'au moins un type de séances
-            else  {
-              $resultat->free();
-              $resultat = $mysqli->query('SELECT id FROM `cdt-types` LIMIT 1');
-              if ( !($resultat->num_rows) )  $etape = 3;
-            
-              // Étape 3quater : Vérification de la présence d'au moins une semaine
-              else  {
-                $resultat->free();
-                $resultat = $mysqli->query('SELECT debut FROM semaines LIMIT 1');
-                if ( !($resultat->num_rows) )  $etape = 3;
-                else  $resultat->free();
-              }
-            }
-          }
+          $mysqli->close();
+          sleep(1);
+          $mysqli = new mysqli($serveur,$base,$mdp,$base);
         }
+        if ( $mysqli->connect_errno )
+          $message = '<div class="warning">La requête semble avoir fonctionné, mais la connexion à la base n\'est pas possible. Erreur MySQL n°'.$mysqli->connect_errno.', «'.$mysqli->connect_error.'».</div>';
       }
     }
   }
-  $mysqli->close();
-}
-if ( !isset($etape) )  {
-  // Vérification du répertoire "documents"
-  if ( !is_dir('documents') )
-    $erreur[] = 'Le répertoire «&nbsp;documents&nbsp;» n\'existe pas à la racine du site web.';
-  elseif ( !is_readable('documents') || !is_executable('documents') || !is_writable('documents') )
-    $erreur[] = 'Le répertoire «&nbsp;documents&nbsp;» n\'a pas les bons droits d\'accès. Il doit être accessible en lecture et en écriture. Vous pouvez taper en console&nbsp;:<br>
-    <code>cd '.dirname(__FILE__).' && sudo chgrp '.`id -gn | tr -d '\n'`.' documents && sudo chmod g+w documents</code>';
-  else 
-    $reps[] = 'Le répertoire «&nbsp;documents&nbsp;» n\'a visiblement aucun problème.';
-  // Vérification du répertoire "sauvegarde"
-  if ( !is_dir('sauvegarde') )
-    $erreur[] = 'Le répertoire «&nbsp;sauvegarde&nbsp;» n\'existe pas à la racine du site web.';
-  elseif ( !is_readable('sauvegarde') || !is_executable('sauvegarde') || !is_writable('sauvegarde') )
-    $erreur[] = 'Le répertoire «&nbsp;sauvegarde&nbsp;» n\'a pas les bons droits d\'accès. Il doit être accessible en lecture et en écriture. Vous pouvez taper en console&nbsp;:<br>
-    <code>cd '.dirname(__FILE__).' && sudo chgrp '.`id -gn | tr -d '\n'`.' sauvegarde && sudo chmod g+w sauvegarde</code>';
-  else 
-    $reps[] = 'Le répertoire «&nbsp;sauvegarde&nbsp;» n\'a visiblement aucun problème.';
-  // Vérification de la quantité de données téléchargeable
-  if ( '100M' != ini_get('upload_max_filesize') )
-    $erreur[] = 'Les directives d\'augmentation de la taille des documents envoyés sur le site, dans le fichier <code>.htaccess</code> ne sont visiblement pas prises en compte. Cela signifie qu\'elle est relativement faible (a priori 2 Mo), que les documents relativement lourds ne pourront pas être envoyés. Les documents classiques ne devraient cependant pas être affectés.<br> Pour modifier cela, vous devez obliger le serveur à prendre en compte les <code>.htaccess</code>. Il s\'agit en général de la directive <code>AllowOverride All</code> à mettre dans les fichiers de configuration d\'Apache.';
-}
-
-// HTML
-echo "$header
-<form style=\"position: absolute; top: 2.5em; left: 5%;\" action=\"\" method=\"post\"><input type=\"submit\" name=\"deconnexion\" value=\"Se déconnecter\"></form>
-
-";
-if ( isset($message) )
-  echo "\n<p class=\"warning\">$message</p>\n";
-
-// Si on est sur une des étapes d'installation : on exécute
-if ( isset($etape) )  {
-?>
-
-<div class="item admin">
-  <p>Vous êtes actuellement sur la page d'installation de votre <a href="http://cahier-de-prepa.fr">Cahier de Prépa</a>. Cette installation se déroule en 3 étapes&nbsp;:</p>
-  <ul>
-    <li<?php if ( $etape == 1 )  echo ' style="font-weight: 700;"'; ?>>Étape n°1&nbsp;: Création des tables de la base de données et des accès internes à la base</li>
-    <li<?php if ( $etape == 2 )  echo ' style="font-weight: 700;"'; ?>>Étape n°2&nbsp;: Création d'un compte pour l'interface d'administration</li>
-    <li<?php if ( $etape == 3 )  echo ' style="font-weight: 700;"'; ?>>Étape n°3&nbsp;: Ajout des données minimales (première page, planning annuel, types de séances)</li>
-  </ul>
-</div>
 
-<?php
-  switch ( $etape )  {
-    // Étape 1
-    case 1:
-?>
-<div class="item admin">
-  <h3>Étape 1&nbsp;: Création des tables de la base de données et des accès internes à la base</h3>
-  <p>La première étape de l'installation correspond à la création des tables de la base de données ainsi que des droits d'accès et de modification à la base de données.</p>
-  <p>Cette étape correspond à entrer un certain code au serveur <code>MySQL</code>, avec des droits d'administrateur sur le serveur. Vous pouvez le faire vous-même si vous le souhaitez, grâce au code ci-dessous, ou la laisser ce site le faire automatiquement en entrant ci-dessous le mot de passe root du serveur <code>MySQL</code>. Ce mot de passe n'est noté nulle part et ne sert qu'à cette étape.</p>
-</div>
-
-<form class="warning admin" action="" method="post">
-  <h4>Mot de passe root du serveur <code>MySQL</code>&nbsp;:</h4>
-  <p><input type="password" name="mdp"> <input type="submit" name="Ok" value="Envoyer"><input type="hidden" name="etape" value="1"></p>
-</form>
+  ////////////////////////////////////////////
+  // Formulaire de récupération des données //
+  ////////////////////////////////////////////
+  if ( !isset($_REQUEST['mdproot']) || strlen($message) )  {
+    echo <<<FIN
+$header
+$message
+<form action="" method="post">
+<div class="item"><input class="bouton" type="submit" name="ok" value="Envoyer"></div>
 
-<div class="item" id="code">
-  <h4>Code <code>MySQL</code> envoyé à cette étape</h4>
-  <pre style="margin: 0.5em 2%; padding: 0 4%;">
-<?php echo $code_etape1; ?>
-  </pre>
-  <p>Remarque&nbsp;: Si des données existent partiellement, elles seront automatiquement supprimées.</p>
+<div class="item">
+  <h3>Mot de passe root MySQL</h3>
+  <p>La création des tables de la base de données nécessite les droits d'administrateur sur le serveur. Inscrivez ci-dessous le mot de passe root du serveur <code>MySQL</code>. Ce mot de passe n'est noté ni envoyé nulle part (à part au serveur MySQL défini dans le fichier de configuration).</p>
 </div>
-<?php
-    break;
 
-    // Étape 2
-    case 2:
-?>
-<div class="item admin">
-  <h3>Étape 2&nbsp;: Création d'un compte pour l'interface d'administration</h3>
-  <p>Il faut maintenant créer votre compte pour vous connecter à l'interface d'administration. Le nom n'est utilisé qu'à la connexion et n'est jamais affiché sur la partie publique du site, il est donc possible de mettre un prénom ou ce que l'on veut. Les accents ne posent pas de problème. Le mot de passe est à entrer deux fois pour éviter les erreurs de saisie.</p>
-  <p>Les mots de passe sont chiffrés avant d'être stockés dans la base de données&nbsp;: il n'y a donc aucun souci sur leur confidentialité. Même l'administrateur du site, l'hébergeur ou d'éventuels pirates ne pourront jamais y avoir accès. Lors de la connexion, le mot de passe entré est chiffré à son tour et ce sont les deux chiffrements qui sont comparés. Un bon mot de passe est un mot de passe d'au moins 8 caractères contenant des lettres, des chiffres et au moins un symbole parmi «&nbsp;?&nbsp;;&nbsp;:&nbsp;!&nbsp;.&nbsp;,-&nbsp;_&nbsp;».</p>
-  <p>Vous devez de plus spécifier une matière. Elle vous sera automatiquement associée. Ceci sera modifiable dans l'interface d'administration.</p>
-  <p>Le <em>nom de la matière</em> est le nom qui sera affiché dans le menu et dans les titres des pages associées à la matière. Mettre un nom plutôt long et éventuellement en plusieurs mots est possible. Par exemple «&nbsp;Mathématiques&nbsp;», «&nbsp;Sciences Physiques&nbsp;».</p>
-  <p>La <em>clé</em> est un mot-clé qui sera utilisé uniquement dans les adresses web vers les pages associées à la matière. Il vaut mieux que ce soit un mot unique, court et sans majuscule. Par exemple «&nbsp;maths&nbsp;», «&nbsp;physique&nbsp;».</p>
-  <p>Toutes ces données seront entièrement modifiables par l'interface d'administration. D'autres utilisateurs et d'autres matières pourront aussi être créés et modifiés ultérieurement.</p>
+<div class="warning">
+  <p class="ligne"><label for="mdproot">Mot de passe root du serveur MySQL&nbsp;: </label><input type="password" name="mdproot" id="mdproot"></p>
 </div>
 
-<div class="item admin">
-<form action="" method="post">
-  <p class="ligne"><label for="user">Nom d'utilisateur&nbsp;: </label><input type="input" id="user" name="user" value="" size="50"></p>
-  <p class="ligne"><label for="mdp1">Mot de passe&nbsp;: </label><input type="password" id="mdp1" name="mdp1" value="" size="50"></p>
-  <p class="ligne"><label for="mdp2">Confirmation&nbsp;: </label><input type="password" id="mdp2" name="mdp2" value="" size="50"></p>
-  <p class="ligne"><label for="matiere">Nom de la matière&nbsp;: </label><input type="input" id="matiere" name="matiere" value="" size="50"></p>
-  <p class="ligne"><label for="cle">Clé dans les adresses&nbsp;: </label><input type="input" id="cle" name="cle" value="" size="50"></p>
-  <p style="text-align: center;"><input type="submit" name="Ok" value="Envoyer"><input type="hidden" name="etape" value="2"></p>
-</form>
+<div class="item">
+  <h3>Création d'un compte professeur</h3>
+  <p>Entrez ci-dessous les coordonnées d'un des professeurs de la classe. Il pourra, une fois connecté, créer les autres comptes</p>
 </div>
 
-<div class="item" id="code">
-  <h4>Code <code>MySQL</code> envoyé à cette étape</h4>
-  <pre style="margin: 0.5em 2%; padding: 0 4%;">
-<?php echo $code_etape2; ?>
-  </pre>
-  <p>Remarque&nbsp;: Si des utilisateurs ou des matières existent, ils seront automatiquement supprimés.</p>
+<div class="warning">
+  <p class="ligne"><label for="genre">M&nbsp;/&nbsp;Mme&nbsp;/&nbsp;Melle&nbsp;:</label>
+    <select name="genre" id="genre">
+      <option value="1">M.</option>
+      <option value="2">Mme</option>
+      <option value="3">Melle</option>
+    </select>
+  </p>
+  <p class="ligne"><label for="prenom">Prénom&nbsp;: </label><input type="text" id="prenom" name="prenom" value="" size="50"></p>
+  <p class="ligne"><label for="nom">Nom&nbsp;: </label><input type="text" id="nom" name="nom" value="" size="50"></p>
+  <p class="ligne"><label for="mail">Adresse mail&nbsp;: </label><input type="text" id="mail" name="mail" value="" size="50"></p>
+  <p class="ligne"><label for="mot_de_passe">Mot de passe temporaire&nbsp;: </label><input type="text" id="mot_de_passe" name="mot_de_passe" value=""></p>
+  <p>Le mot de passe ci-dessus reste affiché en clair : il est temporaire, sera obligatoirement modifié à la première connexion de l'utilisateur. On peut se permettre de mettre un mot de passe simple.</p>
 </div>
-<?php
-    break;
 
-    // Étape 3
-    case 3:
-?>
-<div class="item admin">
-  <h3>Étape 3&nbsp;: Ajout des données minimales</h3>
-  <p>Votre <a href="http://cahier-de-prépa.fr">Cahier de Prépa</a> a besoin pour fonctionner correctement d'un minimum de données&nbsp;:</p>
+<div class="item">
+  <h3>Création d'une première matière</h3>
+  <p>Afin de faciliter la première rencontre avec l'interface d'administration, nous allons créer une matière qui sera associée au compte défini ci-dessus.</p>
   <ul>
-    <li>Un répertoire de base et un répertoire spécifique à votre matière. Leur nom sont automatiques, mais modifiables dans l'interface d'administration.</li>
-    <li>Une page d'accueil&nbsp;: elle affichera automatiquement les informations que vous entrez sur le site (nouveaux programmes de colles, nouvelles informations, nouveaux documents...). Vous pourrez aussi y ajouter manuellement des informations, et créer d'autres pages spécifiques aux sujets que vous souhaitez.</li>
-    <li>Des types de séances (cours, TD, TP...) qui servent à différentier les différentes entrées de votre cahier de texte. Chaque matière aura ses propres types de séances.</li>
-    <li>Le planning annuel, sur lequel est basé l'affichage du cahier de texte et des programmes de colles. Il intègre les vacances, il faut donc renseigner ci-dessous la zone scolaire dans laquelle vous vous trouvez.</li>
+    <li>Le <em>nom complet</em> s'affichera dans le menu et dans les titres des pages. Mettez une majuscule au début.</li>
+    <li>La <em>clé dans l'adresse</em> est un mot-clé utilisé uniquement dans l'adresse des pages associées à la matière. Il vaut mieux que ce soit un mot unique, court et sans majuscule. Par exemple, «&nbsp;maths&nbsp;», «&nbsp;phys&nbsp;»...</li>
   </ul>
-  <p>Toutes ces données seront entièrement modifiables ultérieurement dans l'interface d'administration. Il s'agit simplement d'une base minimale pour le bon fonctionnement du site.</p>
-  <form action="" method="post">
-    <p class="ligne"><label for="zone">Académie ou zone&nbsp;: </label>
-      <select name="zone">
-        <option value="B">Aix-Marseille</option>
-        <option value="B">Amiens</option>
-        <option value="B">Besançon</option>
-        <option value="C">Bordeaux</option>
-        <option value="A">Caen</option>
-        <option value="A">Clermont-Ferrand</option>
-        <option value="C">Créteil</option>
-        <option value="B">Dijon</option>
-        <option value="A">Grenoble</option>
-        <option value="B">Lille</option>
-        <option value="B">Limoges</option>
-        <option value="A">Lyon</option>
-        <option value="A">Montpellier</option>
-        <option value="A">Nancy-Metz</option>
-        <option value="A">Nantes</option>
-        <option value="B">Nice</option>
-        <option value="B">Orléans-Tours</option>
-        <option value="C">Paris</option>
-        <option value="B">Poitiers</option>
-        <option value="B">Reims</option>
-        <option value="A">Rennes</option>
-        <option value="B">Rouen</option>
-        <option value="B">Strasbourg</option>
-        <option value="A">Toulouse</option>
-        <option value="C">Versailles</option>
-        <option value="A">A</option>
-        <option value="B">B</option>
-        <option value="C">C</option>
-      </select>
-
-      
-    </p>
-    <p style="text-align: center;"><input type="submit" name="Ok" value="Envoyer"><input type="hidden" name="etape" value="3"></p>
-  </form>
 </div>
 
-<div class="item" id="code">
-  <h4>Code <code>MySQL</code> envoyé à cette étape</h4>
-  <pre style="margin: 0.5em 2%; padding: 0 4%;">
-<?php echo $code_etape3; ?>
-  </pre>
-  <p>Remarque&nbsp;: Si des répertoires, des pages, des types de séances ou des semaines existent, ils seront automatiquement supprimés.</p>
-</div>
-
-<?php  
-  }
-}
-// Sinon, on affiche la configuration et on regarde l'état des répertoires documents et sauvegarde
-else  {
-  $proto = ( $https ) ? 'https://' : 'http://';
-  $site = ( strlen($siteadmin) ) ? $siteadmin : "$site/?admin";
-?>
-
-<div class="item admin">
-  <h3>Votre <a href="http://cahier-de-prepa.fr">Cahier de Prépa</a> est correctement installé.</h3>
-  <p>Vous pouvez le modifier grâce à <a href="<?php echo $proto.$site; ?>">l'interface d'administration</a>.</p>
+<div class="warning">
+  <p class="ligne"><label for="nom_matiere">Nom complet&nbsp;: </label><input type="text" id="nom_matiere" name="nom_matiere" value="" size="50"></p>
+  <p class="ligne"><label for="cle_matiere">Clé dans l'adresse&nbsp;: </label><input type="text" id="cle_matiere" name="cle_matiere" value=""" size="30"></p>
 </div>
 
 <div class="item">
-  <h3>Vérification des répertoires où le site doit pouvoir écrire</h3>
-  <p>Le répertoire «&nbsp;documents&nbsp;» est le répertoire où vous pourrez envoyer des documents pour qu'ils soient téléchargeables par les élèves. Lorsque vous envoyez un document via le site, le site copie le document dans le répertoire&nbsp;: il doit donc pouvoir écrire à l'intérieur de ce répertoire.</p>
-  <p>Le répertoire «&nbsp;sauvegarde&nbsp;» permet au site de faire des sauvegarde de la base de données. Chaque jour, au moment de la première modification d'une table, le site la sauvegarde automatiquement dans un fichier à l'intérieur du répertoire «&nbsp;sauvegarde&nbsp;»&nbsp;: il doit donc pouvoir écrire à l'intérieur de ce répertoire.</p>
-
-<?php
-  if ( isset($reps) )  {
-    $reps = implode("</li>\n  <li>",$reps);
-    echo <<<FIN
-  <ul>
-    <li>$reps</li>
-  </ul>
-
-
-FIN;
-  }
-
-  if ( isset($erreur) )  {
-    $erreur = implode("</p>\n<p>",$erreur);
-    echo <<<FIN
+  <h3>Titre du site</h3>
+  <p>Il faut enfin renseigner ici le titre du site, qui sera aussi celui de la page d'accueil. Il sera modifiable par les professeurs après la création.</p>
 </div>
 
-<div class="warning admin">
-  <p>$erreur</p>
+<div class="warning">
+  <p class="ligne"><label for="titre">Titre du site&nbsp;: </label><input type="text" id="titre" name="titre" value="" size="80"></p>
 </div>
 
-
+<div class="item"><input class="bouton" type="submit" name="ok" value="Envoyer"></div>
+</form>
+$footer
 FIN;
+  exit();
   }
 }
-?>
 
-</div>
+/////////////////////////////////////
+// Vérifications de l'installation //
+/////////////////////////////////////
+function affiche($message,$status)  {
+  if ( $status )
+    echo "\n<div class=\"item admin cdt\"><p><img class=\"icone\" src=\"icones/ok.png\">$message</p></div>\n";
+  else
+    echo "\n<div class=\"item admin cdt\"><p><img class=\"icone\" src=\"icones/no.png\">$message</p></div>\n";
+}
 
-<script type="text/javascript">
+// Si on est arrivé ici, la base est nécessairement créée.
+echo <<<FIN
+$header
+<div class="warning admin">
+  <p>Votre Cahier de Prépa semble correctement installé. Vous pouvez vous en servir immédiatement&nbsp;:</p>
+  <p>Partie publique&nbsp;: <a href="http://$site">http://$site/</a></p>
+  <p>Interface d'adminstration&nbsp;: <a href="http://$site/admin/">http://$site/admin/</a></p>
+</div>
 
-$( function() {
+FIN;
 
-  $('#code h4').append(' <span>[déplier]</span>');
-  $("#code h4 span").css('cursor','pointer').click( function () {
-    $(this).parent().parent().find('pre,p').toggle();
-    $(this).text($(this).text() == '[déplier]' ? '[replier]' : '[déplier]');
-  });
-  $('#code').find('pre,p').hide();
+// Récupération du login si on vient de créer la base
+$resultat = $mysqli->query('SELECT login FROM utilisateurs WHERE id = 1 AND genre > 5');
+if ( $resultat->num_rows )  {
+  $r = $resultat->fetch_assoc();
+  echo "\n<div class=\"item admin cdt\"><p>L'identifiant du compte professeur créé est ${r['login']} (sans majucule).</p></div>\n";
+  $resultat->free();
+}
 
-});
+// Connexion en lecture
+affiche('Connexion à la base de donnée en lecture',1);
 
-</script>
+// Connexion en écriture
+$mysqli->close();
+$mysqli = new mysqli($serveur,"$base-adm",$mdp,$base);
+affiche('Connexion à la base de donnée en écriture', !($mysqli->connect_errno));
+if ( !($mysqli->connect_errno) )
+  $mysqli = new mysqli($serveur,$base,$mdp,$base);
+
+// Tables dans la base de données
+$resultat = $mysqli->query('SHOW TABLES');
+$tables = array('cdt','cdt-seances','cdt-types','colles','docs','infos','matieres','notes','pages','recents','reps','semaines','utilisateurs');
+if ( $resultat->num_rows )  {
+  while ( $r = $resultat->fetch_row() )  $tables = array_diff($tables,$r);
+  $resultat->free();
+}
+affiche('Définition des tables',empty($tables));
 
+// La base de données doit contenir au moins un utilisateur
+$resultat = $mysqli->query('SELECT id FROM utilisateurs WHERE autorisation = 3 LIMIT 1');
+affiche('Présence d\'au moins un utilisateur de type professeur',$resultat->num_rows);
+$resultat->free();
+
+// La base de données doit contenir la page d'identifiant 1
+$resultat = $mysqli->query('SELECT id FROM pages WHERE id = 1');
+affiche('Présence de la page d\'accueil',$resultat->num_rows);
+$resultat->free();
+
+// La base de données doit contenir la page d'identifiant 1
+$resultat = $mysqli->query('SELECT id FROM reps WHERE id = 1');
+affiche('Présence du répertoire Général',$resultat->num_rows);
+$resultat->free();
+
+// La base de données doit contenir 44 semaines en 2014
+$resultat = $mysqli->query('SELECT id FROM semaines WHERE debut > \'2014-08-01\' AND debut < \'2015-08-01\'');
+affiche('Définition des semaines',$resultat->num_rows == 44);
+$resultat->free();
+$mysqli->close();
+
+// Vérification du répertoire "documents"
+if ( !is_dir('documents') )
+  affiche('Le répertoire «&nbsp;documents&nbsp;» n\'existe pas à la racine du site web.',0);
+elseif ( !is_readable('documents') || !is_executable('documents') || !is_writable('documents') )
+  affiche('Le répertoire «&nbsp;documents&nbsp;» n\'a pas les bons droits d\'accès. Il doit être accessible en lecture et en écriture. Vous pouvez taper en console&nbsp;:<br>
+  <code>cd '.dirname($_SERVER['SCRIPT_FILENAME']).' && sudo chgrp '.`id -gn | tr -d '\n'`.' documents && sudo chmod g+w documents</code>',0);
+else 
+  affiche('Répertoire «&nbsp;documents&nbsp;» accessible en lecture et écriture',1);
+
+// Vérification du répertoire "sauvegarde"
+if ( !is_dir('sauvegarde') )
+  affiche('Le répertoire «&nbsp;sauvegarde&nbsp;» n\'existe pas à la racine du site web.',0);
+elseif ( !is_readable('sauvegarde') || !is_executable('sauvegarde') || !is_writable('sauvegarde') )
+  affiche('Le répertoire «&nbsp;sauvegarde&nbsp;» n\'a pas les bons droits d\'accès. Il doit être accessible en lecture et en écriture. Vous pouvez taper en console&nbsp;:<br>
+  <code>cd '.dirname($_SERVER['SCRIPT_FILENAME']).' && sudo chgrp '.`id -gn | tr -d '\n'`.' sauvegarde && sudo chmod g+w sauvegarde</code>',0);
+else 
+  affiche('Répertoire «&nbsp;sauvegarde&nbsp;» accessible en lecture et écriture',1);
+
+// Vérification de la quantité de données téléchargeable
+if ( '2M' == ini_get('upload_max_filesize') )
+  affiche('Les documents envoyés ne pourront excéder 2&nbsp;Mo, ce qui est plutôt faible. Il serait bien d\'augmenter cette limite. Plus d\'informations sur la page <a href="http://cahier-de-prepa.fr/technique">technique</a> de cahier-de-prepa.fr.',0);
+else
+  affiche('La taille des documents envoyés peut aller jusqu\'à'.ini_get('upload_max_filesize').'o. Cela est modifiable dans la configuration du serveur web. Plus d\'informations sur la page <a href="http://cahier-de-prepa.fr/technique">technique</a> de cahier-de-prepa.fr.',1);
 
-</body>
-</html>
-<?php exit(); ?>
+exit($footer);
+?>
diff -urN cahier-de-prepa3.2.0/js/jquery.datepick.css cahier-de-prepa4.0.0/js/jquery.datepick.css
--- cahier-de-prepa3.2.0/js/jquery.datepick.css	2012-08-15 11:52:54.000000000 +0200
+++ cahier-de-prepa4.0.0/js/jquery.datepick.css	2014-08-02 00:17:26.316107106 +0200
@@ -1,4 +1,4 @@
-/* Default styling for jQuery Datepicker v4.0.0. */
+/* Default styling for jQuery Datepicker v5.0.0. */
 .datepick {
 	background-color: #fff;
 	color: #000;
@@ -12,8 +12,12 @@
 .datepick-rtl {
 	direction: rtl;
 }
+.datepick-popup {
+	z-index: 1000;
+}
 .datepick-disable {
 	position: absolute;
+	z-index: 100;
 	background-color: white;
 	opacity: 0.5;
 	filter: alpha(opacity=50);
@@ -96,6 +100,20 @@
 	padding-right: 0%;
 	text-align: left;
 }
+.datepick-month-nav {
+	float: left;
+	background-color: #777;
+	text-align: center;
+}
+.datepick-month-nav div {
+	float: left;
+	width: 12.5%;
+	margin: 1%;
+	padding: 1%;
+}
+.datepick-month-nav span {
+	color: #888;
+}
 .datepick-month-row {
 	clear: left;
 }
@@ -113,7 +131,10 @@
 }
 .datepick-month-header select, .datepick-month-header input {
 	height: 1.4em;
+	margin: 0em;
+	padding: 0em;
 	border: none;
+	font-size: 100%;
 }
 .datepick-month-header input {
 	position: absolute;
@@ -149,6 +170,7 @@
 .datepick-month td.datepick-week * {
 	background-color: #777;
 	color: #fff;
+	border: none;
 }
 .datepick-month a {
 	display: block;
@@ -194,14 +216,3 @@
 .datepick-clear-fix {
 	clear: both;
 }
-.datepick-cover {
-    display: none;
-    display/**/: block;
-    position: absolute;
-    z-index: -1;
-    filter: mask();
-    top: -1px;
-    left: -1px;
-    width: 100px;
-    height: 100px;
-}
diff -urN cahier-de-prepa3.2.0/js/jquery.datepick-fr.js cahier-de-prepa4.0.0/js/jquery.datepick-fr.js
--- cahier-de-prepa3.2.0/js/jquery.datepick-fr.js	2012-08-15 11:52:54.000000000 +0200
+++ cahier-de-prepa4.0.0/js/jquery.datepick-fr.js	2014-08-02 00:17:46.728107759 +0200
@@ -2,7 +2,7 @@
    French localisation for jQuery Datepicker.
    Stéphane Nahmani (sholby@sholby.net). */
 (function($) {
-	$.datepick.regional['fr'] = {
+	$.datepick.regionalOptions['fr'] = {
 		monthNames: ['Janvier','Février','Mars','Avril','Mai','Juin',
 		'Juillet','Août','Septembre','Octobre','Novembre','Décembre'],
 		monthNamesShort: ['Jan','Fév','Mar','Avr','Mai','Jun',
@@ -25,5 +25,5 @@
 		dayStatus: '\'Choisir\' le DD d MM', defaultStatus: 'Choisir la date',
 		isRTL: false
 	};
-	$.datepick.setDefaults($.datepick.regional['fr']);
+	$.datepick.setDefaults($.datepick.regionalOptions['fr']);
 })(jQuery);
diff -urN cahier-de-prepa3.2.0/js/jquery.datepick.js cahier-de-prepa4.0.0/js/jquery.datepick.js
--- cahier-de-prepa3.2.0/js/jquery.datepick.js	2012-08-15 11:52:57.000000000 +0200
+++ cahier-de-prepa4.0.0/js/jquery.datepick.js	2014-08-02 00:17:35.612107404 +0200
@@ -1,1999 +1,6 @@
-﻿/* http://keith-wood.name/datepick.html
-   Date picker for jQuery v4.0.1.
+/* http://keith-wood.name/datepick.html
+   Date picker for jQuery v5.0.0.
    Written by Keith Wood (kbwood{at}iinet.com.au) February 2010.
-   Dual licensed under the GPL (http://dev.jquery.com/browser/trunk/jquery/GPL-LICENSE.txt) and 
-   MIT (http://dev.jquery.com/browser/trunk/jquery/MIT-LICENSE.txt) licenses. 
+   Licensed under the MIT (https://github.com/jquery/jquery/blob/master/MIT-LICENSE.txt) licence. 
    Please attribute the author if you use it. */
-
-(function($) { // Hide scope, no $ conflict
-
-/* Datepicker manager. */
-function Datepicker() {
-	this._defaults = {
-		pickerClass: '', // CSS class to add to this instance of the datepicker
-		showOnFocus: true, // True for popup on focus, false for not
-		showTrigger: null, // Element to be cloned for a trigger, null for none
-		showAnim: 'show', // Name of jQuery animation for popup, '' for no animation
-		showOptions: {}, // Options for enhanced animations
-		showSpeed: 'normal', // Duration of display/closure
-		popupContainer: null, // The element to which a popup calendar is added, null for body
-		alignment: 'bottom', // Alignment of popup - with nominated corner of input:
-			// 'top' or 'bottom' aligns depending on language direction,
-			// 'topLeft', 'topRight', 'bottomLeft', 'bottomRight'
-		fixedWeeks: false, // True to always show 6 weeks, false to only show as many as are needed
-		firstDay: 0, // First day of the week, 0 = Sunday, 1 = Monday, ...
-		calculateWeek: this.iso8601Week, // Calculate week of the year from a date, null for ISO8601
-		monthsToShow: 1, // How many months to show, cols or [rows, cols]
-		monthsOffset: 0, // How many months to offset the primary month by
-		monthsToStep: 1, // How many months to move when prev/next clicked
-		monthsToJump: 12, // How many months to move when large prev/next clicked
-		changeMonth: true, // True to change month/year via drop-down, false for navigation only
-		yearRange: 'c-10:c+10', // Range of years to show in drop-down: 'any' for direct text entry
-			// or 'start:end', where start/end are '+-nn' for relative to today
-			// or 'c+-nn' for relative to the currently selected date
-			// or 'nnnn' for an absolute year
-		shortYearCutoff: '+10', // Cutoff for two-digit year in the curretn century
-		showOtherMonths: false, // True to show dates from other months, false to not show them
-		selectOtherMonths: false, // True to allow selection of dates from other months too
-		defaultDate: null, // Date to show if no other selected
-		selectDefaultDate: false, // True to pre-select the default date if no other is chosen
-		minDate: null, // The minimum selectable date
-		maxDate: null, // The maximum selectable date
-		dateFormat: 'mm/dd/yyyy', // Format for dates
-		autoSize: false, // True to size the input field according to the date format
-		rangeSelect: false, // Allows for selecting a date range on one date picker
-		rangeSeparator: ' - ', // Text between two dates in a range
-		multiSelect: 0, // Maximum number of selectable dates, zero for single select
-		multiSeparator: ',', // Text between multiple dates
-		onDate: null, // Callback as a date is added to the datepicker
-		onShow: null, // Callback just before a datepicker is shown
-		onChangeMonthYear: null, // Callback when a new month/year is selected
-		onSelect: null, // Callback when a date is selected
-		onClose: null, // Callback when a datepicker is closed
-		altField: null, // Alternate field to update in synch with the datepicker
-		altFormat: null, // Date format for alternate field, defaults to dateFormat
-		constrainInput: true, // True to constrain typed input to dateFormat allowed characters
-		commandsAsDateFormat: false, // True to apply formatDate to the command texts
-		commands: this.commands // Command actions that may be added to a layout by name
-	};
-	this.regional = {
-		'': { // US/English
-			monthNames: ['January', 'February', 'March', 'April', 'May', 'June',
-			'July', 'August', 'September', 'October', 'November', 'December'],
-			monthNamesShort: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
-			dayNames: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'],
-			dayNamesShort: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
-			dayNamesMin: ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa'],
-			dateFormat: 'mm/dd/yyyy', // See options on formatDate
-			firstDay: 0, // The first day of the week, Sun = 0, Mon = 1, ...
-			renderer: this.defaultRenderer, // The rendering templates
-			prevText: '&lt;Prev', // Text for the previous month command
-			prevStatus: 'Show the previous month', // Status text for the previous month command
-			prevJumpText: '&lt;&lt;', // Text for the previous year command
-			prevJumpStatus: 'Show the previous year', // Status text for the previous year command
-			nextText: 'Next&gt;', // Text for the next month command
-			nextStatus: 'Show the next month', // Status text for the next month command
-			nextJumpText: '&gt;&gt;', // Text for the next year command
-			nextJumpStatus: 'Show the next year', // Status text for the next year command
-			currentText: 'Current', // Text for the current month command
-			currentStatus: 'Show the current month', // Status text for the current month command
-			todayText: 'Today', // Text for the today's month command
-			todayStatus: 'Show today\'s month', // Status text for the today's month command
-			clearText: 'Clear', // Text for the clear command
-			clearStatus: 'Clear all the dates', // Status text for the clear command
-			closeText: 'Close', // Text for the close command
-			closeStatus: 'Close the datepicker', // Status text for the close command
-			yearStatus: 'Change the year', // Status text for year selection
-			monthStatus: 'Change the month', // Status text for month selection
-			weekText: 'Wk', // Text for week of the year column header
-			weekStatus: 'Week of the year', // Status text for week of the year column header
-			dayStatus: 'Select DD, M d, yyyy', // Status text for selectable days
-			defaultStatus: 'Select a date', // Status text shown by default
-			isRTL: false // True if language is right-to-left
-		}};
-	$.extend(this._defaults, this.regional['']);
-	this._disabled = [];
-}
-
-$.extend(Datepicker.prototype, {
-	dataName: 'datepick',
-	
-	/* Class name added to elements to indicate already configured with datepicker. */
-	markerClass: 'hasDatepick',
-
-	_popupClass: 'datepick-popup', // Marker for popup division
-	_triggerClass: 'datepick-trigger', // Marker for trigger element
-	_disableClass: 'datepick-disable', // Marker for disabled element
-	_coverClass: 'datepick-cover', // Marker for iframe backing element
-	_monthYearClass: 'datepick-month-year', // Marker for month/year inputs
-	_curMonthClass: 'datepick-month-', // Marker for current month/year
-	_anyYearClass: 'datepick-any-year', // Marker for year direct input
-	_curDoWClass: 'datepick-dow-', // Marker for day of week
-	
-	commands: { // Command actions that may be added to a layout by name
-		// name: { // The command name, use '{button:name}' or '{link:name}' in layouts
-		//		text: '', // The field in the regional settings for the displayed text
-		//		status: '', // The field in the regional settings for the status text
-		//      // The keystroke to trigger the action
-		//		keystroke: {keyCode: nn, ctrlKey: boolean, altKey: boolean, shiftKey: boolean},
-		//		enabled: fn, // The function that indicates the command is enabled
-		//		date: fn, // The function to get the date associated with this action
-		//		action: fn} // The function that implements the action
-		prev: {text: 'prevText', status: 'prevStatus', // Previous month
-			keystroke: {keyCode: 33}, // Page up
-			enabled: function(inst) {
-				var minDate = inst.curMinDate();
-				return (!minDate || $.datepick.add($.datepick.day(
-					$.datepick.add($.datepick.newDate(inst.drawDate),
-					1 - inst.get('monthsToStep') - inst.get('monthsOffset'), 'm'), 1), -1, 'd').
-					getTime() >= minDate.getTime()); },
-			date: function(inst) {
-				return $.datepick.day($.datepick.add($.datepick.newDate(inst.drawDate),
-					-inst.get('monthsToStep') - inst.get('monthsOffset'), 'm'), 1); },
-			action: function(inst) {
-				$.datepick.changeMonth(this, -inst.get('monthsToStep')); }
-		},
-		prevJump: {text: 'prevJumpText', status: 'prevJumpStatus', // Previous year
-			keystroke: {keyCode: 33, ctrlKey: true}, // Ctrl + Page up
-			enabled: function(inst) {
-				var minDate = inst.curMinDate();
-				return (!minDate || $.datepick.add($.datepick.day(
-					$.datepick.add($.datepick.newDate(inst.drawDate),
-					1 - inst.get('monthsToJump') - inst.get('monthsOffset'), 'm'), 1), -1, 'd').
-					getTime() >= minDate.getTime()); },
-			date: function(inst) {
-				return $.datepick.day($.datepick.add($.datepick.newDate(inst.drawDate),
-					-inst.get('monthsToJump') - inst.get('monthsOffset'), 'm'), 1); },
-			action: function(inst) {
-				$.datepick.changeMonth(this, -inst.get('monthsToJump')); }
-		},
-		next: {text: 'nextText', status: 'nextStatus', // Next month
-			keystroke: {keyCode: 34}, // Page down
-			enabled: function(inst) {
-				var maxDate = inst.get('maxDate');
-				return (!maxDate || $.datepick.day($.datepick.add($.datepick.newDate(inst.drawDate),
-					inst.get('monthsToStep') - inst.get('monthsOffset'), 'm'), 1).
-					getTime() <= maxDate.getTime()); },
-			date: function(inst) {
-				return $.datepick.day($.datepick.add($.datepick.newDate(inst.drawDate),
-					inst.get('monthsToStep') - inst.get('monthsOffset'), 'm'), 1); },
-			action: function(inst) {
-				$.datepick.changeMonth(this, inst.get('monthsToStep')); }
-		},
-		nextJump: {text: 'nextJumpText', status: 'nextJumpStatus', // Next year
-			keystroke: {keyCode: 34, ctrlKey: true}, // Ctrl + Page down
-			enabled: function(inst) {
-				var maxDate = inst.get('maxDate');
-				return (!maxDate || $.datepick.day($.datepick.add($.datepick.newDate(inst.drawDate),
-					inst.get('monthsToJump') - inst.get('monthsOffset'), 'm'), 1).
-					getTime() <= maxDate.getTime()); },
-			date: function(inst) {
-				return $.datepick.day($.datepick.add($.datepick.newDate(inst.drawDate),
-					inst.get('monthsToJump') - inst.get('monthsOffset'), 'm'), 1); },
-			action: function(inst) {
-				$.datepick.changeMonth(this, inst.get('monthsToJump')); }
-		},
-		current: {text: 'currentText', status: 'currentStatus', // Current month
-			keystroke: {keyCode: 36, ctrlKey: true}, // Ctrl + Home
-			enabled: function(inst) {
-				var minDate = inst.curMinDate();
-				var maxDate = inst.get('maxDate');
-				var curDate = inst.selectedDates[0] || $.datepick.today();
-				return (!minDate || curDate.getTime() >= minDate.getTime()) &&
-					(!maxDate || curDate.getTime() <= maxDate.getTime()); },
-			date: function(inst) {
-				return inst.selectedDates[0] || $.datepick.today(); },
-			action: function(inst) {
-				var curDate = inst.selectedDates[0] || $.datepick.today();
-				$.datepick.showMonth(this, curDate.getFullYear(), curDate.getMonth() + 1); }
-		},
-		today: {text: 'todayText', status: 'todayStatus', // Today's month
-			keystroke: {keyCode: 36, ctrlKey: true}, // Ctrl + Home
-			enabled: function(inst) {
-				var minDate = inst.curMinDate();
-				var maxDate = inst.get('maxDate');
-				return (!minDate || $.datepick.today().getTime() >= minDate.getTime()) &&
-					(!maxDate || $.datepick.today().getTime() <= maxDate.getTime()); },
-			date: function(inst) { return $.datepick.today(); },
-			action: function(inst) { $.datepick.showMonth(this); }
-		},
-		clear: {text: 'clearText', status: 'clearStatus', // Clear the datepicker
-			keystroke: {keyCode: 35, ctrlKey: true}, // Ctrl + End
-			enabled: function(inst) { return true; },
-			date: function(inst) { return null; },
-			action: function(inst) { $.datepick.clear(this); }
-		},
-		close: {text: 'closeText', status: 'closeStatus', // Close the datepicker
-			keystroke: {keyCode: 27}, // Escape
-			enabled: function(inst) { return true; },
-			date: function(inst) { return null; },
-			action: function(inst) { $.datepick.hide(this); }
-		},
-		prevWeek: {text: 'prevWeekText', status: 'prevWeekStatus', // Previous week
-			keystroke: {keyCode: 38, ctrlKey: true}, // Ctrl + Up
-			enabled: function(inst) {
-				var minDate = inst.curMinDate();
-				return (!minDate || $.datepick.add($.datepick.newDate(inst.drawDate), -7, 'd').
-					getTime() >= minDate.getTime()); },
-			date: function(inst) { return $.datepick.add($.datepick.newDate(inst.drawDate), -7, 'd'); },
-			action: function(inst) { $.datepick.changeDay(this, -7); }
-		},
-		prevDay: {text: 'prevDayText', status: 'prevDayStatus', // Previous day
-			keystroke: {keyCode: 37, ctrlKey: true}, // Ctrl + Left
-			enabled: function(inst) {
-				var minDate = inst.curMinDate();
-				return (!minDate || $.datepick.add($.datepick.newDate(inst.drawDate), -1, 'd').
-					getTime() >= minDate.getTime()); },
-			date: function(inst) { return $.datepick.add($.datepick.newDate(inst.drawDate), -1, 'd'); },
-			action: function(inst) { $.datepick.changeDay(this, -1); }
-		},
-		nextDay: {text: 'nextDayText', status: 'nextDayStatus', // Next day
-			keystroke: {keyCode: 39, ctrlKey: true}, // Ctrl + Right
-			enabled: function(inst) {
-				var maxDate = inst.get('maxDate');
-				return (!maxDate || $.datepick.add($.datepick.newDate(inst.drawDate), 1, 'd').
-					getTime() <= maxDate.getTime()); },
-			date: function(inst) { return $.datepick.add($.datepick.newDate(inst.drawDate), 1, 'd'); },
-			action: function(inst) { $.datepick.changeDay(this, 1); }
-		},
-		nextWeek: {text: 'nextWeekText', status: 'nextWeekStatus', // Next week
-			keystroke: {keyCode: 40, ctrlKey: true}, // Ctrl + Down
-			enabled: function(inst) {
-				var maxDate = inst.get('maxDate');
-				return (!maxDate || $.datepick.add($.datepick.newDate(inst.drawDate), 7, 'd').
-					getTime() <= maxDate.getTime()); },
-			date: function(inst) { return $.datepick.add($.datepick.newDate(inst.drawDate), 7, 'd'); },
-			action: function(inst) { $.datepick.changeDay(this, 7); }
-		}
-	},
-
-	/* Default template for generating a datepicker. */
-	defaultRenderer: {
-		// Anywhere: '{l10n:name}' to insert localised value for name,
-		// '{link:name}' to insert a link trigger for command name,
-		// '{button:name}' to insert a button trigger for command name,
-		// '{popup:start}...{popup:end}' to mark a section for inclusion in a popup datepicker only,
-		// '{inline:start}...{inline:end}' to mark a section for inclusion in an inline datepicker only
-		// Overall structure: '{months}' to insert calendar months
-		picker: '<div class="datepick">' +
-		'<div class="datepick-nav">{link:prev}{link:today}{link:next}</div>{months}' +
-		'{popup:start}<div class="datepick-ctrl">{link:clear}{link:close}</div>{popup:end}' +
-		'<div class="datepick-clear-fix"></div></div>',
-		// One row of months: '{months}' to insert calendar months
-		monthRow: '<div class="datepick-month-row">{months}</div>',
-		// A single month: '{monthHeader:dateFormat}' to insert the month header -
-		// dateFormat is optional and defaults to 'MM yyyy',
-		// '{weekHeader}' to insert a week header, '{weeks}' to insert the month's weeks
-		month: '<div class="datepick-month"><div class="datepick-month-header">{monthHeader}</div>' +
-		'<table><thead>{weekHeader}</thead><tbody>{weeks}</tbody></table></div>',
-		// A week header: '{days}' to insert individual day names
-		weekHeader: '<tr>{days}</tr>',
-		// Individual day header: '{day}' to insert day name
-		dayHeader: '<th>{day}</th>',
-		// One week of the month: '{days}' to insert the week's days, '{weekOfYear}' to insert week of year
-		week: '<tr>{days}</tr>',
-		// An individual day: '{day}' to insert day value
-		day: '<td>{day}</td>',
-		// jQuery selector, relative to picker, for a single month
-		monthSelector: '.datepick-month',
-		// jQuery selector, relative to picker, for individual days
-		daySelector: 'td',
-		// Class for right-to-left (RTL) languages
-		rtlClass: 'datepick-rtl',
-		// Class for multi-month datepickers
-		multiClass: 'datepick-multi',
-		// Class for selectable dates
-		defaultClass: '',
-		// Class for currently selected dates
-		selectedClass: 'datepick-selected',
-		// Class for highlighted dates
-		highlightedClass: 'datepick-highlight',
-		// Class for today
-		todayClass: 'datepick-today',
-		// Class for days from other months
-		otherMonthClass: 'datepick-other-month',
-		// Class for days on weekends
-		weekendClass: 'datepick-weekend',
-		// Class prefix for commands
-		commandClass: 'datepick-cmd',
-		// Extra class(es) for commands that are buttons
-		commandButtonClass: '',
-		// Extra class(es) for commands that are links
-		commandLinkClass: '',
-		// Class for disabled commands
-		disabledClass: 'datepick-disabled'
-	},
-
-	/* Override the default settings for all datepicker instances.
-	   @param  settings  (object) the new settings to use as defaults
-	   @return  (Datepicker) this object */
-	setDefaults: function(settings) {
-		$.extend(this._defaults, settings || {});
-		return this;
-	},
-
-	_ticksTo1970: (((1970 - 1) * 365 + Math.floor(1970 / 4) - Math.floor(1970 / 100) +
-		Math.floor(1970 / 400)) * 24 * 60 * 60 * 10000000),
-	_msPerDay: 24 * 60 * 60 * 1000,
-
-	ATOM: 'yyyy-mm-dd', // RFC 3339/ISO 8601
-	COOKIE: 'D, dd M yyyy',
-	FULL: 'DD, MM d, yyyy',
-	ISO_8601: 'yyyy-mm-dd',
-	JULIAN: 'J',
-	RFC_822: 'D, d M yy',
-	RFC_850: 'DD, dd-M-yy',
-	RFC_1036: 'D, d M yy',
-	RFC_1123: 'D, d M yyyy',
-	RFC_2822: 'D, d M yyyy',
-	RSS: 'D, d M yy', // RFC 822
-	TICKS: '!',
-	TIMESTAMP: '@',
-	W3C: 'yyyy-mm-dd', // ISO 8601
-
-	/* Format a date object into a string value.
-	   The format can be combinations of the following:
-	   d  - day of month (no leading zero)
-	   dd - day of month (two digit)
-	   o  - day of year (no leading zeros)
-	   oo - day of year (three digit)
-	   D  - day name short
-	   DD - day name long
-	   w  - week of year (no leading zero)
-	   ww - week of year (two digit)
-	   m  - month of year (no leading zero)
-	   mm - month of year (two digit)
-	   M  - month name short
-	   MM - month name long
-	   yy - year (two digit)
-	   yyyy - year (four digit)
-	   @  - Unix timestamp (s since 01/01/1970)
-	   !  - Windows ticks (100ns since 01/01/0001)
-	   '...' - literal text
-	   '' - single quote
-	   @param  format    (string) the desired format of the date (optional, default datepicker format)
-	   @param  date      (Date) the date value to format
-	   @param  settings  (object) attributes include:
-	                     dayNamesShort    (string[]) abbreviated names of the days from Sunday (optional)
-	                     dayNames         (string[]) names of the days from Sunday (optional)
-	                     monthNamesShort  (string[]) abbreviated names of the months (optional)
-	                     monthNames       (string[]) names of the months (optional)
-						 calculateWeek    (function) function that determines week of the year (optional)
-	   @return  (string) the date in the above format */
-	formatDate: function(format, date, settings) {
-		if (typeof format != 'string') {
-			settings = date;
-			date = format;
-			format = '';
-		}
-		if (!date) {
-			return '';
-		}
-		format = format || this._defaults.dateFormat;
-		settings = settings || {};
-		var dayNamesShort = settings.dayNamesShort || this._defaults.dayNamesShort;
-		var dayNames = settings.dayNames || this._defaults.dayNames;
-		var monthNamesShort = settings.monthNamesShort || this._defaults.monthNamesShort;
-		var monthNames = settings.monthNames || this._defaults.monthNames;
-		var calculateWeek = settings.calculateWeek || this._defaults.calculateWeek;
-		// Check whether a format character is doubled
-		var doubled = function(match, step) {
-			var matches = 1;
-			while (iFormat + matches < format.length && format.charAt(iFormat + matches) == match) {
-				matches++;
-			}
-			iFormat += matches - 1;
-			return Math.floor(matches / (step || 1)) > 1;
-		};
-		// Format a number, with leading zeroes if necessary
-		var formatNumber = function(match, value, len, step) {
-			var num = '' + value;
-			if (doubled(match, step)) {
-				while (num.length < len) {
-					num = '0' + num;
-				}
-			}
-			return num;
-		};
-		// Format a name, short or long as requested
-		var formatName = function(match, value, shortNames, longNames) {
-			return (doubled(match) ? longNames[value] : shortNames[value]);
-		};
-		var output = '';
-		var literal = false;
-		for (var iFormat = 0; iFormat < format.length; iFormat++) {
-			if (literal) {
-				if (format.charAt(iFormat) == "'" && !doubled("'")) {
-					literal = false;
-				}
-				else {
-					output += format.charAt(iFormat);
-				}
-			}
-			else {
-				switch (format.charAt(iFormat)) {
-					case 'd': output += formatNumber('d', date.getDate(), 2); break;
-					case 'D': output += formatName('D', date.getDay(),
-						dayNamesShort, dayNames); break;
-					case 'o': output += formatNumber('o', this.dayOfYear(date), 3); break;
-					case 'w': output += formatNumber('w', calculateWeek(date), 2); break;
-					case 'm': output += formatNumber('m', date.getMonth() + 1, 2); break;
-					case 'M': output += formatName('M', date.getMonth(),
-						monthNamesShort, monthNames); break;
-					case 'y':
-						output += (doubled('y', 2) ? date.getFullYear() :
-							(date.getFullYear() % 100 < 10 ? '0' : '') + date.getFullYear() % 100);
-						break;
-					case '@': output += Math.floor(date.getTime() / 1000); break;
-					case '!': output += date.getTime() * 10000 + this._ticksTo1970; break;
-					case "'":
-						if (doubled("'")) {
-							output += "'";
-						}
-						else {
-							literal = true;
-						}
-						break;
-					default:
-						output += format.charAt(iFormat);
-				}
-			}
-		}
-		return output;
-	},
-
-	/* Parse a string value into a date object.
-	   See formatDate for the possible formats, plus:
-	   * - ignore rest of string
-	   @param  format    (string) the expected format of the date ('' for default datepicker format)
-	   @param  value     (string) the date in the above format
-	   @param  settings  (object) attributes include:
-	                     shortYearCutoff  (number) the cutoff year for determining the century (optional)
-	                     dayNamesShort    (string[]) abbreviated names of the days from Sunday (optional)
-	                     dayNames         (string[]) names of the days from Sunday (optional)
-	                     monthNamesShort  (string[]) abbreviated names of the months (optional)
-	                     monthNames       (string[]) names of the months (optional)
-	   @return  (Date) the extracted date value or null if value is blank
-	   @throws  errors if the format and/or value are missing,
-	            if the value doesn't match the format,
-	            or if the date is invalid */
-	parseDate: function(format, value, settings) {
-		if (value == null) {
-			throw 'Invalid arguments';
-		}
-		value = (typeof value == 'object' ? value.toString() : value + '');
-		if (value == '') {
-			return null;
-		}
-		format = format || this._defaults.dateFormat;
-		settings = settings || {};
-		var shortYearCutoff = settings.shortYearCutoff || this._defaults.shortYearCutoff;
-		shortYearCutoff = (typeof shortYearCutoff != 'string' ? shortYearCutoff :
-			this.today().getFullYear() % 100 + parseInt(shortYearCutoff, 10));
-		var dayNamesShort = settings.dayNamesShort || this._defaults.dayNamesShort;
-		var dayNames = settings.dayNames || this._defaults.dayNames;
-		var monthNamesShort = settings.monthNamesShort || this._defaults.monthNamesShort;
-		var monthNames = settings.monthNames || this._defaults.monthNames;
-		var year = -1;
-		var month = -1;
-		var day = -1;
-		var doy = -1;
-		var shortYear = false;
-		var literal = false;
-		// Check whether a format character is doubled
-		var doubled = function(match, step) {
-			var matches = 1;
-			while (iFormat + matches < format.length && format.charAt(iFormat + matches) == match) {
-				matches++;
-			}
-			iFormat += matches - 1;
-			return Math.floor(matches / (step || 1)) > 1;
-		};
-		// Extract a number from the string value
-		var getNumber = function(match, step) {
-			doubled(match, step);
-			var size = [2, 3, 4, 11, 20]['oy@!'.indexOf(match) + 1];
-			var digits = new RegExp('^-?\\d{1,' + size + '}');
-			var num = value.substring(iValue).match(digits);
-			if (!num) {
-				throw 'Missing number at position {0}'.replace(/\{0\}/, iValue);
-			}
-			iValue += num[0].length;
-			return parseInt(num[0], 10);
-		};
-		// Extract a name from the string value and convert to an index
-		var getName = function(match, shortNames, longNames, step) {
-			var names = (doubled(match, step) ? longNames : shortNames);
-			for (var i = 0; i < names.length; i++) {
-				if (value.substr(iValue, names[i].length) == names[i]) {
-					iValue += names[i].length;
-					return i + 1;
-				}
-			}
-			throw 'Unknown name at position {0}'.replace(/\{0\}/, iValue);
-		};
-		// Confirm that a literal character matches the string value
-		var checkLiteral = function() {
-			if (value.charAt(iValue) != format.charAt(iFormat)) {
-				throw 'Unexpected literal at position {0}'.replace(/\{0\}/, iValue);
-			}
-			iValue++;
-		};
-		var iValue = 0;
-		for (var iFormat = 0; iFormat < format.length; iFormat++) {
-			if (literal) {
-				if (format.charAt(iFormat) == "'" && !doubled("'")) {
-					literal = false;
-				}
-				else {
-					checkLiteral();
-				}
-			}
-			else {
-				switch (format.charAt(iFormat)) {
-					case 'd': day = getNumber('d'); break;
-					case 'D': getName('D', dayNamesShort, dayNames); break;
-					case 'o': doy = getNumber('o'); break;
-					case 'w': getNumber('w'); break;
-					case 'm': month = getNumber('m'); break;
-					case 'M': month = getName('M', monthNamesShort, monthNames); break;
-					case 'y':
-						var iSave = iFormat;
-						shortYear = !doubled('y', 2);
-						iFormat = iSave;
-						year = getNumber('y', 2);
-						break;
-					case '@':
-						var date = this._normaliseDate(new Date(getNumber('@') * 1000));
-						year = date.getFullYear();
-						month = date.getMonth() + 1;
-						day = date.getDate();
-						break;
-					case '!':
-						var date = this._normaliseDate(
-							new Date((getNumber('!') - this._ticksTo1970) / 10000));
-						year = date.getFullYear();
-						month = date.getMonth() + 1;
-						day = date.getDate();
-						break;
-					case '*': iValue = value.length; break;
-					case "'":
-						if (doubled("'")) {
-							checkLiteral();
-						}
-						else {
-							literal = true;
-						}
-						break;
-					default: checkLiteral();
-				}
-			}
-		}
-		if (iValue < value.length) {
-			throw 'Additional text found at end';
-		}
-		if (year == -1) {
-			year = this.today().getFullYear();
-		}
-		else if (year < 100 && shortYear) {
-			year += (shortYearCutoff == -1 ? 1900 : this.today().getFullYear() -
-				this.today().getFullYear() % 100 - (year <= shortYearCutoff ? 0 : 100));
-		}
-		if (doy > -1) {
-			month = 1;
-			day = doy;
-			for (var dim = this.daysInMonth(year, month); day > dim;
-					dim = this.daysInMonth(year, month)) {
-				month++;
-				day -= dim;
-			}
-		}
-		var date = this.newDate(year, month, day);
-		if (date.getFullYear() != year || date.getMonth() + 1 != month || date.getDate() != day) {
-			throw 'Invalid date';
-		}
-		return date;
-	},
-
-	/* A date may be specified as an exact value or a relative one.
-	   @param  dateSpec     (Date or number or string) the date as an object or string
-	                        in the given format or an offset - numeric days from today,
-	                        or string amounts and periods, e.g. '+1m +2w'
-	   @param  defaultDate  (Date) the date to use if no other supplied, may be null
-	   @param  currentDate  (Date) the current date as a possible basis for relative dates,
-	                        if null today is used (optional)
-	   @param  dateFormat   (string) the expected date format - see formatDate above (optional)
-	   @param  settings     (object) attributes include:
-	                        shortYearCutoff  (number) the cutoff year for determining the century (optional)
-	                        dayNamesShort    (string[7]) abbreviated names of the days from Sunday (optional)
-	                        dayNames         (string[7]) names of the days from Sunday (optional)
-	                        monthNamesShort  (string[12]) abbreviated names of the months (optional)
-	                        monthNames       (string[12]) names of the months (optional)
-	   @return  (Date) the decoded date */
-	determineDate: function(dateSpec, defaultDate, currentDate, dateFormat, settings) {
-		if (currentDate && typeof currentDate != 'object') {
-			settings = dateFormat;
-			dateFormat = currentDate;
-			currentDate = null;
-		}
-		if (typeof dateFormat != 'string') {
-			settings = dateFormat;
-			dateFormat = '';
-		}
-		var offsetString = function(offset) {
-			try {
-				return $.datepick.parseDate(dateFormat, offset, settings);
-			}
-			catch (e) {
-				// Ignore
-			}
-			offset = offset.toLowerCase();
-			var date = (offset.match(/^c/) && currentDate ? $.datepick.newDate(currentDate) : null) ||
-				$.datepick.today();
-			var pattern = /([+-]?[0-9]+)\s*(d|w|m|y)?/g;
-			var matches = pattern.exec(offset);
-			while (matches) {
-				date = $.datepick.add(date, parseInt(matches[1], 10), matches[2] || 'd');
-				matches = pattern.exec(offset);
-			}
-			return date;
-		};
-		defaultDate = (defaultDate ? $.datepick.newDate(defaultDate) : null);
-		dateSpec = (dateSpec == null ? defaultDate :
-			(typeof dateSpec == 'string' ? offsetString(dateSpec) : (typeof dateSpec == 'number' ?
-			(isNaN(dateSpec) || dateSpec == Infinity || dateSpec == -Infinity ? defaultDate :
-			$.datepick.add($.datepick.today(), dateSpec, 'd')) : $.datepick._normaliseDate(dateSpec))));
-		return dateSpec;
-	},
-
-	/* Find the number of days in a given month.
-	   @param  year   (Date) the date to get days for or
-	                  (number) the full year
-	   @param  month  (number) the month (1 to 12)
-	   @return  (number) the number of days in this month */
-	daysInMonth: function(year, month) {
-		var date = (year.getFullYear ? year : this.newDate(year, month, 1));
-		return 32 - new Date(date.getFullYear(), date.getMonth(), 32).getDate();
-	},
-
-	/* Calculate the day of the year for a date.
-	   @param  year   (Date) the date to get the day-of-year for or
-	                  (number) the full year
-	   @param  month  (number) the month (1-12)
-	   @param  day    (number) the day
-	   @return  (number) the day of the year */
-	dayOfYear: function(year, month, day) {
-		var date = (year.getFullYear ? year : this.newDate(year, month, day));
-		var newYear = this.newDate(date.getFullYear(), 1, 1);
-		return (date.getTime() - newYear.getTime()) / this._msPerDay + 1;
-	},
-
-	/* Set as calculateWeek to determine the week of the year based on the ISO 8601 definition.
-	   @param  year   (Date) the date to get the week for or
-	                  (number) the full year
-	   @param  month  (number) the month (1-12)
-	   @param  day    (number) the day
-	   @return  (number) the number of the week within the year that contains this date */
-	iso8601Week: function(year, month, day) {
-		var checkDate = (year.getFullYear ? new Date(year.getTime()) : this.newDate(year, month, day));
-		// Find Thursday of this week starting on Monday
-		checkDate.setDate(checkDate.getDate() + 4 - (checkDate.getDay() || 7));
-		var time = checkDate.getTime();
-		checkDate.setMonth(0); // Compare with Jan 1
-		checkDate.setDate(1);
-		return Math.floor(Math.round((time - checkDate) / 86400000) / 7) + 1;
-	},
-
-	/* Return today's date.
-	   @return  (Date) today */
-	today: function() {
-		return this._normaliseDate(new Date());
-	},
-
-	/* Return a new date.
-	   @param  year   (Date) the date to clone or
-					  (number) the year
-	   @param  month  (number) the month (1-12)
-	   @param  day    (number) the day
-	   @return  (Date) the date */
-	newDate: function(year, month, day) {
-		return (!year ? null : this._normaliseDate(
-			year.getFullYear ? new Date(year.getTime()) : new Date(year, month - 1, day)));
-	},
-
-	/* Standardise a date into a common format - zero all time values.
-	   Also cater for JavaScript peculiarities when swapping to/from
-	   daylight saving at midnight. Hours may reset to previous night when midnight
-	   changeover, but then cannot generate midnight datetime, so jump to 1AM.
-	   @param  date  (Date) the date to standardise
-	   @return  (Date) the normalised date */
-	_normaliseDate: function(date) {
-		if (!date) {
-			return date;
-		}
-		date.setHours(0);
-		date.setMinutes(0);
-		date.setSeconds(0);
-		date.setMilliseconds(0);
-		date.setHours(date.getHours() > 12 ? date.getHours() + 2 : 0);
-		return date;
-	},
-
-	/* Set the year for a date.
-	   @param  date  (Date) the original date
-	   @param  year  (number) the new year
-	   @return  the updated date */
-	year: function(date, year) {
-		date.setFullYear(year);
-		return this._normaliseDate(date);
-	},
-
-	/* Set the month for a date.
-	   @param  date   (Date) the original date
-	   @param  month  (number) the new month (1-12)
-	   @return  the updated date */
-	month: function(date, month) {
-		date.setMonth(month - 1);
-		return this._normaliseDate(date);
-	},
-
-	/* Set the day for a date.
-	   @param  date  (Date) the original date
-	   @param  day   (number) the new day of the month
-	   @return  the updated date */
-	day: function(date, day) {
-		date.setDate(day);
-		return this._normaliseDate(date);
-	},
-
-	/* Add a number of periods to a date.
-	   @param  date    (Date) the original date
-	   @param  amount  (number) the number of periods
-	   @param  period  (string) the type of period d/w/m/y
-	   @return  the updated date */
-	add: function(date, amount, period) {
-		if (period == 'd' || period == 'w') {
-			date.setDate(date.getDate() + amount * (period == 'w' ? 7 : 1));
-		}
-		else {
-			var year = date.getFullYear() + (period == 'y' ? amount : 0);
-			var month = date.getMonth() + (period == 'm' ? amount : 0);
-			date.setTime(this._normaliseDate(new Date(year, month,
-				Math.min(date.getDate(), this.daysInMonth(year, month + 1)))).getTime());
-		}
-		return date;
-	},
-
-	/* Attach the datepicker functionality to an input field.
-	   @param  target    (element) the control to affect
-	   @param  settings  (object) the custom options for this instance */
-	_attachPicker: function(target, settings) {
-		target = $(target);
-		if (target.hasClass(this.markerClass)) {
-			return;
-		}
-		target.addClass(this.markerClass);
-		var inst = {target: target, selectedDates: [], drawDate: null, pickingRange: false,
-			inline: ($.inArray(target[0].nodeName.toLowerCase(), ['div', 'span']) > -1),
-			get: function(name) { // Get a setting value, defaulting if necessary
-				var value = this.settings[name] !== undefined ?
-					this.settings[name] : $.datepick._defaults[name];
-				if ($.inArray(name, ['defaultDate', 'minDate', 'maxDate']) > -1) { // Decode date settings
-					value = $.datepick.determineDate(
-						value, null, this.selectedDates[0], this.get('dateFormat'), inst.getConfig());
-				}
-				return value;
-			},
-			curMinDate: function() {
-				return (this.pickingRange ? this.selectedDates[0] : this.get('minDate'));
-			},
-			getConfig: function() {
-				return {dayNamesShort: this.get('dayNamesShort'), dayNames: this.get('dayNames'),
-					monthNamesShort: this.get('monthNamesShort'), monthNames: this.get('monthNames'),
-					calculateWeek: this.get('calculateWeek'),
-					shortYearCutoff: this.get('shortYearCutoff')};
-			}
-		};
-		$.data(target[0], this.dataName, inst);
-		var inlineSettings = ($.fn.metadata ? target.metadata() : {});
-		inst.settings = $.extend({}, settings || {}, inlineSettings || {});
-		if (inst.inline) {
-			this._update(target[0]);
-		}
-		else {
-			this._attachments(target, inst);
-			target.bind('keydown.' + this.dataName, this._keyDown).
-				bind('keypress.' + this.dataName, this._keyPress).
-				bind('keyup.' + this.dataName, this._keyUp);
-		}
-	},
-
-	/* Retrieve the settings for a datepicker control.
-	   @param  target  (element) the control to affect
-	   @param  name    (string) the name of the setting (optional)
-	   @return  (object) the current instance settings (name == 'all') or
-	            (object) the default settings (no name) or
-	            (any) the setting value (name supplied) */
-	options: function(target, name) {
-		var inst = $.data(target, this.dataName);
-		return (inst ? (name ? (name == 'all' ?
-			inst.settings : inst.settings[name]) : $.datepick._defaults) : {});
-	},
-
-	/* Reconfigure the settings for a datepicker control.
-	   @param  target    (element) the control to affect
-	   @param  settings  (object) the new options for this instance or
-	                     (string) an individual property name
-	   @param  value     (any) the individual property value (omit if settings is an object) */
-	option: function(target, settings, value) {
-		target = $(target);
-		if (!target.hasClass(this.markerClass)) {
-			return;
-		}
-		settings = settings || {};
-		if (typeof settings == 'string') {
-			var name = settings;
-			settings = {};
-			settings[name] = value;
-		}
-		var inst = $.data(target[0], this.dataName);
-		var dates = inst.selectedDates;
-		extendRemove(inst.settings, settings);
-		this.setDate(target[0], dates, null, false, true);
-		inst.pickingRange = false;
-		inst.drawDate = $.datepick.newDate(this._checkMinMax(
-			(settings.defaultDate ? inst.get('defaultDate') : inst.drawDate) ||
-			inst.get('defaultDate') || $.datepick.today(), inst));
-		if (!inst.inline) {
-			this._attachments(target, inst);
-		}
-		if (inst.inline || inst.div) {
-			this._update(target[0]);
-		}
-	},
-
-	/* Attach events and trigger, if necessary.
-	   @param  target  (jQuery) the control to affect
-	   @param  inst    (object) the current instance settings */
-	_attachments: function(target, inst) {
-		target.unbind('focus.' + this.dataName);
-		if (inst.get('showOnFocus')) {
-			target.bind('focus.' + this.dataName, this.show);
-		}
-		if (inst.trigger) {
-			inst.trigger.remove();
-		}
-		var trigger = inst.get('showTrigger');
-		inst.trigger = (!trigger ? $([]) :
-			$(trigger).clone().removeAttr('id').addClass(this._triggerClass)
-				[inst.get('isRTL') ? 'insertBefore' : 'insertAfter'](target).
-				click(function() {
-					if (!$.datepick.isDisabled(target[0])) {
-						$.datepick[$.datepick.curInst == inst ?
-							'hide' : 'show'](target[0]);
-					}
-				}));
-		this._autoSize(target, inst);
-		if (inst.get('selectDefaultDate') && inst.get('defaultDate') &&
-				inst.selectedDates.length == 0) {
-			this.setDate(target[0], $.datepick.newDate(inst.get('defaultDate') || $.datepick.today()));
-		}
-	},
-
-	/* Apply the maximum length for the date format.
-	   @param  inst  (object) the current instance settings */
-	_autoSize: function(target, inst) {
-		if (inst.get('autoSize') && !inst.inline) {
-			var date = new Date(2009, 10 - 1, 20); // Ensure double digits
-			var dateFormat = inst.get('dateFormat');
-			if (dateFormat.match(/[DM]/)) {
-				var findMax = function(names) {
-					var max = 0;
-					var maxI = 0;
-					for (var i = 0; i < names.length; i++) {
-						if (names[i].length > max) {
-							max = names[i].length;
-							maxI = i;
-						}
-					}
-					return maxI;
-				};
-				date.setMonth(findMax(inst.get(dateFormat.match(/MM/) ? // Longest month
-					'monthNames' : 'monthNamesShort')));
-				date.setDate(findMax(inst.get(dateFormat.match(/DD/) ? // Longest day
-					'dayNames' : 'dayNamesShort')) + 20 - date.getDay());
-			}
-			inst.target.attr('size', $.datepick.formatDate(dateFormat, date, inst.getConfig()).length);
-		}
-	},
-
-	/* Remove the datepicker functionality from a control.
-	   @param  target  (element) the control to affect */
-	destroy: function(target) {
-		target = $(target);
-		if (!target.hasClass(this.markerClass)) {
-			return;
-		}
-		var inst = $.data(target[0], this.dataName);
-		if (inst.trigger) {
-			inst.trigger.remove();
-		}
-		target.removeClass(this.markerClass).empty().unbind('.' + this.dataName);
-		if (inst.get('autoSize') && !inst.inline) {
-			target.removeAttr('size');
-		}
-		$.removeData(target[0], this.dataName);
-	},
-
-	/* Apply multiple event functions.
-	   Usage, for example: onShow: multipleEvents(fn1, fn2, ...)
-	   @param  fns  (function...) the functions to apply */
-	multipleEvents: function(fns) {
-		var funcs = arguments;
-		return function(args) {
-			for (var i = 0; i < funcs.length; i++) {
-				funcs[i].apply(this, arguments);
-			}
-		};
-	},
-
-	/* Enable the datepicker and any associated trigger.
-	   @param  target  (element) the control to use */
-	enable: function(target) {
-		var $target = $(target);
-		if (!$target.hasClass(this.markerClass)) {
-			return;
-		}
-		var inst = $.data(target, this.dataName);
-		if (inst.inline)
-			$target.children('.' + this._disableClass).remove().end().
-				find('button,select').attr('disabled', '').end().
-				find('a').attr('href', 'javascript:void(0)');
-		else {
-			target.disabled = false;
-			inst.trigger.filter('button.' + this._triggerClass).
-				attr('disabled', '').end().
-				filter('img.' + this._triggerClass).
-				css({opacity: '1.0', cursor: ''});
-		}
-		this._disabled = $.map(this._disabled,
-			function(value) { return (value == target ? null : value); }); // Delete entry
-	},
-
-	/* Disable the datepicker and any associated trigger.
-	   @param  target  (element) the control to use */
-	disable: function(target) {
-		var $target = $(target);
-		if (!$target.hasClass(this.markerClass))
-			return;
-		var inst = $.data(target, this.dataName);
-		if (inst.inline) {
-			var inline = $target.children(':last');
-			var offset = inline.offset();
-			var relOffset = {left: 0, top: 0};
-			inline.parents().each(function() {
-				if ($(this).css('position') == 'relative') {
-					relOffset = $(this).offset();
-					return false;
-				}
-			});
-			var zIndex = $target.css('zIndex');
-			zIndex = (zIndex == 'auto' ? 0 : parseInt(zIndex, 10)) + 1;
-			$target.prepend('<div class="' + this._disableClass + '" style="' +
-				'width: ' + inline.outerWidth() + 'px; height: ' + inline.outerHeight() +
-				'px; left: ' + (offset.left - relOffset.left) + 'px; top: ' +
-				(offset.top - relOffset.top) + 'px; z-index: ' + zIndex + '"></div>').
-				find('button,select').attr('disabled', 'disabled').end().
-				find('a').removeAttr('href');
-		}
-		else {
-			target.disabled = true;
-			inst.trigger.filter('button.' + this._triggerClass).
-				attr('disabled', 'disabled').end().
-				filter('img.' + this._triggerClass).
-				css({opacity: '0.5', cursor: 'default'});
-		}
-		this._disabled = $.map(this._disabled,
-			function(value) { return (value == target ? null : value); }); // Delete entry
-		this._disabled.push(target);
-	},
-
-	/* Is the first field in a jQuery collection disabled as a datepicker?
-	   @param  target  (element) the control to examine
-	   @return  (boolean) true if disabled, false if enabled */
-	isDisabled: function(target) {
-		return (target && $.inArray(target, this._disabled) > -1);
-	},
-
-	/* Show a popup datepicker.
-	   @param  target  (event) a focus event or
-	                   (element) the control to use */
-	show: function(target) {
-		target = target.target || target;
-		var inst = $.data(target, $.datepick.dataName);
-		if ($.datepick.curInst == inst) {
-			return;
-		}
-		if ($.datepick.curInst) {
-			$.datepick.hide($.datepick.curInst, true);
-		}
-		if (inst) {
-			// Retrieve existing date(s)
-			inst.lastVal = null;
-			inst.selectedDates = $.datepick._extractDates(inst, $(target).val());
-			inst.pickingRange = false;
-			inst.drawDate = $.datepick._checkMinMax($.datepick.newDate(inst.selectedDates[0] ||
-				inst.get('defaultDate') || $.datepick.today()), inst);
-			inst.prevDate = $.datepick.newDate(inst.drawDate);
-			$.datepick.curInst = inst;
-			// Generate content
-			$.datepick._update(target, true);
-			// Adjust position before showing
-			var offset = $.datepick._checkOffset(inst);
-			var zIndex = $(target).css('zIndex');
-			zIndex = (zIndex == 'auto' ? 0 : parseInt(zIndex, 10)) + 1;
-			inst.div.css({left: offset.left, top: offset.top, zIndex: zIndex});
-			// And display
-			var showAnim = inst.get('showAnim');
-			var showSpeed = inst.get('showSpeed');
-			showSpeed = (showSpeed == 'normal' ? '_default' : showSpeed);
-			var postProcess = function() {
-				var borders = $.datepick._getBorders(inst.div);
-				inst.div.find('.' + $.datepick._coverClass). // IE6- only
-					css({left: -borders[0], top: -borders[1],
-						width: inst.div.outerWidth() + borders[0],
-						height: inst.div.outerHeight() + borders[1]});
-			};
-			if ($.effects && $.effects[showAnim]) {
-				inst.div.show(showAnim, inst.get('showOptions'), showSpeed, postProcess);
-			}
-			else {
-				inst.div[showAnim || 'show']((showAnim ? showSpeed : ''), postProcess);
-			}
-			if (!showAnim) {
-				postProcess();
-			}
-		}
-	},
-
-	/* Extract possible dates from a string.
-	   @param  inst  (object) the current instance settings
-	   @param  text  (string) the text to extract from
-	   @return  (CDate[]) the extracted dates */
-	_extractDates: function(inst, datesText) {
-		if (datesText == inst.lastVal) {
-			return;
-		}
-		inst.lastVal = datesText;
-		var dateFormat = inst.get('dateFormat');
-		var multiSelect = inst.get('multiSelect');
-		var rangeSelect = inst.get('rangeSelect');
-		datesText = datesText.split(multiSelect ? inst.get('multiSeparator') :
-			(rangeSelect ? inst.get('rangeSeparator') : '\x00'));
-		var dates = [];
-		for (var i = 0; i < datesText.length; i++) {
-			try {
-				var date = $.datepick.parseDate(dateFormat, datesText[i], inst.getConfig());
-				if (date) {
-					var found = false;
-					for (var j = 0; j < dates.length; j++) {
-						if (dates[j].getTime() == date.getTime()) {
-							found = true;
-							break;
-						}
-					}
-					if (!found) {
-						dates.push(date);
-					}
-				}
-			}
-			catch (e) {
-				// Ignore
-			}
-		}
-		dates.splice(multiSelect || (rangeSelect ? 2 : 1), dates.length);
-		if (rangeSelect && dates.length == 1) {
-			dates[1] = dates[0];
-		}
-		return dates;
-	},
-
-	/* Update the datepicker display.
-	   @param  target  (event) a focus event or
-	                   (element) the control to use
-	   @param  hidden  (boolean) true to initially hide the datepicker */
-	_update: function(target, hidden) {
-		target = $(target.target || target);
-		var inst = $.data(target[0], $.datepick.dataName);
-		if (inst) {
-			if (inst.inline) {
-				target.html(this._generateContent(target[0], inst));
-			}
-			else if ($.datepick.curInst == inst) {
-				if (!inst.div) {
-					inst.div = $('<div></div>').addClass(this._popupClass).
-						css({display: (hidden ? 'none' : 'static'), position: 'absolute',
-							left: target.offset().left,
-							top: target.offset().top + target.outerHeight()}).
-						appendTo($(inst.get('popupContainer') || 'body'));
-				}
-				inst.div.html(this._generateContent(target[0], inst));
-				target.focus();
-			}
-			if (inst.inline || $.datepick.curInst == inst) {
-				var onChange = inst.get('onChangeMonthYear');
-				if (onChange && (!inst.prevDate ||
-						inst.prevDate.getFullYear() != inst.drawDate.getFullYear() ||
-						inst.prevDate.getMonth() != inst.drawDate.getMonth())) {
-					onChange.apply(target[0], [inst.drawDate.getFullYear(), inst.drawDate.getMonth() + 1]);
-				}
-			}
-		}
-	},
-
-	/* Update the input field and any alternate field with the current dates.
-	   @param  target  (element) the control to use
-	   @param  keyUp   (boolean, internal) true if coming from keyUp processing */
-	_updateInput: function(target, keyUp) {
-		var inst = $.data(target, this.dataName);
-		if (inst) {
-			var value = '';
-			var altValue = '';
-			var sep = (inst.get('multiSelect') ? inst.get('multiSeparator') :
-				inst.get('rangeSeparator'));
-			var dateFormat = inst.get('dateFormat');
-			var altFormat = inst.get('altFormat') || dateFormat;
-			for (var i = 0; i < inst.selectedDates.length; i++) {
-				value += (keyUp ? '' : (i > 0 ? sep : '') + $.datepick.formatDate(
-					dateFormat, inst.selectedDates[i], inst.getConfig()));
-				altValue += (i > 0 ? sep : '') + $.datepick.formatDate(
-					altFormat, inst.selectedDates[i], inst.getConfig());
-			}
-			if (!inst.inline && !keyUp) {
-				$(target).val(value);
-			}
-			$(inst.get('altField')).val(altValue);
-			var onSelect = inst.get('onSelect');
-			if (onSelect && !keyUp && !inst.inSelect) {
-				inst.inSelect = true; // Prevent endless loops
-				onSelect.apply(target, [inst.selectedDates]);
-				inst.inSelect = false;
-			}
-		}
-	},
-
-	/* Retrieve the size of left and top borders for an element.
-	   @param  elem  (jQuery) the element of interest
-	   @return  (number[2]) the left and top borders */
-	_getBorders: function(elem) {
-		var convert = function(value) {
-			var extra = ($.browser.msie ? 1 : 0);
-			return {thin: 1 + extra, medium: 3 + extra, thick: 5 + extra}[value] || value;
-		};
-		return [parseFloat(convert(elem.css('border-left-width'))),
-			parseFloat(convert(elem.css('border-top-width')))];
-	},
-
-	/* Check positioning to remain on the screen.
-	   @param  inst  (object) the current instance settings
-	   @return  (object) the updated offset for the datepicker */
-	_checkOffset: function(inst) {
-		var base = (inst.target.is(':hidden') && inst.trigger ? inst.trigger : inst.target);
-		var offset = base.offset();
-		var isFixed = false;
-		$(inst.target).parents().each(function() {
-			isFixed |= $(this).css('position') == 'fixed';
-			return !isFixed;
-		});
-		if (isFixed && $.browser.opera) { // Correction for Opera when fixed and scrolled
-			offset.left -= document.documentElement.scrollLeft;
-			offset.top -= document.documentElement.scrollTop;
-		}
-		var browserWidth = (!$.browser.mozilla || document.doctype ?
-			document.documentElement.clientWidth : 0) || document.body.clientWidth;
-		var browserHeight = (!$.browser.mozilla || document.doctype ?
-			document.documentElement.clientHeight : 0) || document.body.clientHeight;
-		if (browserWidth == 0) {
-			return offset;
-		}
-		var alignment = inst.get('alignment');
-		var isRTL = inst.get('isRTL');
-		var scrollX = document.documentElement.scrollLeft || document.body.scrollLeft;
-		var scrollY = document.documentElement.scrollTop || document.body.scrollTop;
-		var above = offset.top - inst.div.outerHeight() -
-			(isFixed && $.browser.opera ? document.documentElement.scrollTop : 0);
-		var below = offset.top + base.outerHeight();
-		var alignL = offset.left;
-		var alignR = offset.left + base.outerWidth() - inst.div.outerWidth() -
-			(isFixed && $.browser.opera ? document.documentElement.scrollLeft : 0);
-		var tooWide = (offset.left + inst.div.outerWidth() - scrollX) > browserWidth;
-		var tooHigh = (offset.top + inst.target.outerHeight() + inst.div.outerHeight() -
-			scrollY) > browserHeight;
-		if (alignment == 'topLeft') {
-			offset = {left: alignL, top: above};
-		}
-		else if (alignment == 'topRight') {
-			offset = {left: alignR, top: above};
-		}
-		else if (alignment == 'bottomLeft') {
-			offset = {left: alignL, top: below};
-		}
-		else if (alignment == 'bottomRight') {
-			offset = {left: alignR, top: below};
-		}
-		else if (alignment == 'top') {
-			offset = {left: (isRTL || tooWide ? alignR : alignL), top: above};
-		}
-		else { // bottom
-			offset = {left: (isRTL || tooWide ? alignR : alignL),
-				top: (tooHigh ? above : below)};
-		}
-		offset.left = Math.max((isFixed ? 0 : scrollX), offset.left - (isFixed ? scrollX : 0));
-		offset.top = Math.max((isFixed ? 0 : scrollY), offset.top - (isFixed ? scrollY : 0));
-		return offset;
-	},
-
-	/* Close date picker if clicked elsewhere.
-	   @param  event  (MouseEvent) the mouse click to check */
-	_checkExternalClick: function(event) {
-		if (!$.datepick.curInst) {
-			return;
-		}
-		var target = $(event.target);
-		if (!target.parents().andSelf().hasClass($.datepick._popupClass) &&
-				!target.hasClass($.datepick.markerClass) &&
-				!target.parents().andSelf().hasClass($.datepick._triggerClass)) {
-			$.datepick.hide($.datepick.curInst);
-		}
-	},
-
-	/* Hide a popup datepicker.
-	   @param  target     (element) the control to use or
-	                      (object) the current instance settings
-	   @param  immediate  (boolean) true to close immediately without animation */
-	hide: function(target, immediate) {
-		var inst = $.data(target, this.dataName) || target;
-		if (inst && inst == $.datepick.curInst) {
-			var showAnim = (immediate ? '' : inst.get('showAnim'));
-			var showSpeed = inst.get('showSpeed');
-			showSpeed = (showSpeed == 'normal' ? '_default' : showSpeed);
-			var postProcess = function() {
-				inst.div.remove();
-				inst.div = null;
-				$.datepick.curInst = null;
-				var onClose = inst.get('onClose');
-				if (onClose) {
-					onClose.apply(target, [inst.selectedDates]);
-				}
-			};
-			inst.div.stop();
-			if ($.effects && $.effects[showAnim]) {
-				inst.div.hide(showAnim, inst.get('showOptions'), showSpeed, postProcess);
-			}
-			else {
-				var hideAnim = (showAnim == 'slideDown' ? 'slideUp' :
-					(showAnim == 'fadeIn' ? 'fadeOut' : 'hide'));
-				inst.div[hideAnim]((showAnim ? showSpeed : ''), postProcess);
-			}
-			if (!showAnim) {
-				postProcess();
-			}
-		}
-	},
-
-	/* Handle keystrokes in the datepicker.
-	   @param  event  (KeyEvent) the keystroke
-	   @return  (boolean) true if not handled, false if handled */
-	_keyDown: function(event) {
-		var target = event.target;
-		var inst = $.data(target, $.datepick.dataName);
-		var handled = false;
-		if (inst.div) {
-			if (event.keyCode == 9) { // Tab - close
-				$.datepick.hide(target);
-			}
-			else if (event.keyCode == 13) { // Enter - select
-				$.datepick.selectDate(target,
-					$('a.' + inst.get('renderer').highlightedClass, inst.div)[0]);
-				handled = true;
-			}
-			else { // Command keystrokes
-				var commands = inst.get('commands');
-				for (var name in commands) {
-					var command = commands[name];
-					if (command.keystroke.keyCode == event.keyCode &&
-							!!command.keystroke.ctrlKey == !!(event.ctrlKey || event.metaKey) &&
-							!!command.keystroke.altKey == event.altKey &&
-							!!command.keystroke.shiftKey == event.shiftKey) {
-						$.datepick.performAction(target, name);
-						handled = true;
-						break;
-					}
-				}
-			}
-		}
-		else { // Show on 'current' keystroke
-			var command = inst.get('commands').current;
-			if (command.keystroke.keyCode == event.keyCode &&
-					!!command.keystroke.ctrlKey == !!(event.ctrlKey || event.metaKey) &&
-					!!command.keystroke.altKey == event.altKey &&
-					!!command.keystroke.shiftKey == event.shiftKey) {
-				$.datepick.show(target);
-				handled = true;
-			}
-		}
-		if (handled) {
-			event.preventDefault();
-			event.stopPropagation();
-		}
-		inst.ctrlKey = ((event.keyCode < 48 && event.keyCode != 32) ||
-			event.ctrlKey || event.metaKey);
-		return !handled;
-	},
-
-	/* Filter keystrokes in the datepicker.
-	   @param  event  (KeyEvent) the keystroke
-	   @return  (boolean) true if allowed, false if not allowed */
-	_keyPress: function(event) {
-		var target = event.target;
-		var inst = $.data(target, $.datepick.dataName);
-		if (inst && inst.get('constrainInput')) {
-			var ch = String.fromCharCode(event.keyCode || event.charCode);
-			var allowedChars = $.datepick._allowedChars(inst);
-			return (event.metaKey || inst.ctrlKey || ch < ' ' ||
-				!allowedChars || allowedChars.indexOf(ch) > -1);
-		}
-		return true;
-	},
-
-	/* Determine the set of characters allowed by the date format.
-	   @param  inst  (object) the current instance settings
-	   @return  (string) the set of allowed characters, or null if anything allowed */
-	_allowedChars: function(inst) {
-		var dateFormat = inst.get('dateFormat');
-		var allowedChars = (inst.get('multiSelect') ? inst.get('multiSeparator') :
-			(inst.get('rangeSelect') ? inst.get('rangeSeparator') : ''));
-		var literal = false;
-		var hasNum = false;
-		for (var i = 0; i < dateFormat.length; i++) {
-			var ch = dateFormat.charAt(i);
-			if (literal) {
-				if (ch == "'" && dateFormat.charAt(i + 1) != "'") {
-					literal = false;
-				}
-				else {
-					allowedChars += ch;
-				}
-			}
-			else {
-				switch (ch) {
-					case 'd': case 'm': case 'o': case 'w':
-						allowedChars += (hasNum ? '' : '0123456789'); hasNum = true; break;
-					case 'y': case '@': case '!':
-						allowedChars += (hasNum ? '' : '0123456789') + '-'; hasNum = true; break;
-					case 'J':
-						allowedChars += (hasNum ? '' : '0123456789') + '-.'; hasNum = true; break;
-					case 'D': case 'M': case 'Y':
-						return null; // Accept anything
-					case "'":
-						if (dateFormat.charAt(i + 1) == "'") {
-							allowedChars += "'";
-						}
-						else {
-							literal = true;
-						}
-						break;
-					default:
-						allowedChars += ch;
-				}
-			}
-		}
-		return allowedChars;
-	},
-
-	/* Synchronise datepicker with the field.
-	   @param  event  (KeyEvent) the keystroke
-	   @return  (boolean) true if allowed, false if not allowed */
-	_keyUp: function(event) {
-		var target = event.target;
-		var inst = $.data(target, $.datepick.dataName);
-		if (inst && !inst.ctrlKey && inst.lastVal != inst.target.val()) {
-			try {
-				var dates = $.datepick._extractDates(inst, inst.target.val());
-				if (dates.length > 0) {
-					$.datepick.setDate(target, dates, null, true);
-				}
-			}
-			catch (event) {
-				// Ignore
-			}
-		}
-		return true;
-	},
-
-	/* Clear an input and close a popup datepicker.
-	   @param  target  (element) the control to use */
-	clear: function(target) {
-		var inst = $.data(target, this.dataName);
-		if (inst) {
-			inst.selectedDates = [];
-			this.hide(target);
-			if (inst.get('selectDefaultDate') && inst.get('defaultDate')) {
-				this.setDate(target,
-					$.datepick.newDate(inst.get('defaultDate') ||$.datepick.today()));
-			}
-			else {
-				this._updateInput(target);
-			}
-		}
-	},
-
-	/* Retrieve the selected date(s) for a datepicker.
-	   @param  target  (element) the control to examine
-	   @return  (CDate[]) the selected date(s) */
-	getDate: function(target) {
-		var inst = $.data(target, this.dataName);
-		return (inst ? inst.selectedDates : []);
-	},
-
-	/* Set the selected date(s) for a datepicker.
-	   @param  target   (element) the control to examine
-	   @param  dates    (CDate or number or string or [] of these) the selected date(s)
-	   @param  endDate  (CDate or number or string) the ending date for a range (optional)
-	   @param  keyUp    (boolean, internal) true if coming from keyUp processing
-	   @param  setOpt   (boolean, internal) true if coming from option processing */
-	setDate: function(target, dates, endDate, keyUp, setOpt) {
-		var inst = $.data(target, this.dataName);
-		if (inst) {
-			if (!$.isArray(dates)) {
-				dates = [dates];
-				if (endDate) {
-					dates.push(endDate);
-				}
-			}
-			var dateFormat = inst.get('dateFormat');
-			var minDate = inst.get('minDate');
-			var maxDate = inst.get('maxDate');
-			var curDate = inst.selectedDates[0];
-			inst.selectedDates = [];
-			for (var i = 0; i < dates.length; i++) {
-				var date = $.datepick.determineDate(
-					dates[i], null, curDate, dateFormat, inst.getConfig());
-				if (date) {
-					if ((!minDate || date.getTime() >= minDate.getTime()) &&
-							(!maxDate || date.getTime() <= maxDate.getTime())) {
-						var found = false;
-						for (var j = 0; j < inst.selectedDates.length; j++) {
-							if (inst.selectedDates[j].getTime() == date.getTime()) {
-								found = true;
-								break;
-							}
-						}
-						if (!found) {
-							inst.selectedDates.push(date);
-						}
-					}
-				}
-			}
-			var rangeSelect = inst.get('rangeSelect');
-			inst.selectedDates.splice(inst.get('multiSelect') ||
-				(rangeSelect ? 2 : 1), inst.selectedDates.length);
-			if (rangeSelect) {
-				switch (inst.selectedDates.length) {
-					case 1: inst.selectedDates[1] = inst.selectedDates[0]; break;
-					case 2: inst.selectedDates[1] =
-						(inst.selectedDates[0].getTime() > inst.selectedDates[1].getTime() ?
-						inst.selectedDates[0] : inst.selectedDates[1]); break;
-				}
-				inst.pickingRange = false;
-			}
-			inst.prevDate = (inst.drawDate ? $.datepick.newDate(inst.drawDate) : null);
-			inst.drawDate = this._checkMinMax($.datepick.newDate(inst.selectedDates[0] ||
-				inst.get('defaultDate') || $.datepick.today()), inst);
-			if (!setOpt) {
-				this._update(target);
-				this._updateInput(target, keyUp);
-			}
-		}
-	},
-
-	/* Perform a named action for a datepicker.
-	   @param  target  (element) the control to affect
-	   @param  action  (string) the name of the action */
-	performAction: function(target, action) {
-		var inst = $.data(target, this.dataName);
-		if (inst && !this.isDisabled(target)) {
-			var commands = inst.get('commands');
-			if (commands[action] && commands[action].enabled.apply(target, [inst])) {
-				commands[action].action.apply(target, [inst]);
-			}
-		}
-	},
-
-	/* Set the currently shown month, defaulting to today's.
-	   @param  target  (element) the control to affect
-	   @param  year    (number) the year to show (optional)
-	   @param  month   (number) the month to show (1-12) (optional)
-	   @param  day     (number) the day to show (optional) */
-	showMonth: function(target, year, month, day) {
-		var inst = $.data(target, this.dataName);
-		if (inst && (day != null ||
-				(inst.drawDate.getFullYear() != year || inst.drawDate.getMonth() + 1 != month))) {
-			inst.prevDate = $.datepick.newDate(inst.drawDate);
-			var show = this._checkMinMax((year != null ?
-				$.datepick.newDate(year, month, 1) : $.datepick.today()), inst);
-			inst.drawDate = $.datepick.newDate(show.getFullYear(), show.getMonth() + 1, 
-				(day != null ? day : Math.min(inst.drawDate.getDate(),
-				$.datepick.daysInMonth(show.getFullYear(), show.getMonth() + 1))));
-			this._update(target);
-		}
-	},
-
-	/* Adjust the currently shown month.
-	   @param  target  (element) the control to affect
-	   @param  offset  (number) the number of months to change by */
-	changeMonth: function(target, offset) {
-		var inst = $.data(target, this.dataName);
-		if (inst) {
-			var date = $.datepick.add($.datepick.newDate(inst.drawDate), offset, 'm');
-			this.showMonth(target, date.getFullYear(), date.getMonth() + 1);
-		}
-	},
-
-	/* Adjust the currently shown day.
-	   @param  target  (element) the control to affect
-	   @param  offset  (number) the number of days to change by */
-	changeDay: function(target, offset) {
-		var inst = $.data(target, this.dataName);
-		if (inst) {
-			var date = $.datepick.add($.datepick.newDate(inst.drawDate), offset, 'd');
-			this.showMonth(target, date.getFullYear(), date.getMonth() + 1, date.getDate());
-		}
-	},
-
-	/* Restrict a date to the minimum/maximum specified.
-	   @param  date  (CDate) the date to check
-	   @param  inst  (object) the current instance settings */
-	_checkMinMax: function(date, inst) {
-		var minDate = inst.get('minDate');
-		var maxDate = inst.get('maxDate');
-		date = (minDate && date.getTime() < minDate.getTime() ? $.datepick.newDate(minDate) : date);
-		date = (maxDate && date.getTime() > maxDate.getTime() ? $.datepick.newDate(maxDate) : date);
-		return date;
-	},
-
-	/* Retrieve the date associated with an entry in the datepicker.
-	   @param  target  (element) the control to examine
-	   @param  elem    (element) the selected datepicker element
-	   @return  (CDate) the corresponding date, or null */
-	retrieveDate: function(target, elem) {
-		var inst = $.data(target, this.dataName);
-		return (!inst ? null : this._normaliseDate(
-			new Date(parseInt(elem.className.replace(/^.*dp(-?\d+).*$/, '$1'), 10))));
-	},
-
-	/* Select a date for this datepicker.
-	   @param  target  (element) the control to examine
-	   @param  elem    (element) the selected datepicker element */
-	selectDate: function(target, elem) {
-		var inst = $.data(target, this.dataName);
-		if (inst && !this.isDisabled(target)) {
-			var date = this.retrieveDate(target, elem);
-			var multiSelect = inst.get('multiSelect');
-			var rangeSelect = inst.get('rangeSelect');
-			if (multiSelect) {
-				var found = false;
-				for (var i = 0; i < inst.selectedDates.length; i++) {
-					if (date.getTime() == inst.selectedDates[i].getTime()) {
-						inst.selectedDates.splice(i, 1);
-						found = true;
-						break;
-					}
-				}
-				if (!found && inst.selectedDates.length < multiSelect) {
-					inst.selectedDates.push(date);
-				}
-			}
-			else if (rangeSelect) {
-				if (inst.pickingRange) {
-					inst.selectedDates[1] = date;
-				}
-				else {
-					inst.selectedDates = [date, date];
-				}
-				inst.pickingRange = !inst.pickingRange;
-			}
-			else {
-				inst.selectedDates = [date];
-			}
-			inst.prevDate = $.datepick.newDate(date);
-			this._updateInput(target);
-			if (inst.inline || inst.pickingRange || inst.selectedDates.length <
-					(multiSelect || (rangeSelect ? 2 : 1))) {
-				this._update(target);
-			}
-			else {
-				this.hide(target);
-			}
-		}
-	},
-
-	/* Generate the datepicker content for this control.
-	   @param  target  (element) the control to affect
-	   @param  inst    (object) the current instance settings
-	   @return  (jQuery) the datepicker content */
-	_generateContent: function(target, inst) {
-		var renderer = inst.get('renderer');
-		var monthsToShow = inst.get('monthsToShow');
-		monthsToShow = ($.isArray(monthsToShow) ? monthsToShow : [1, monthsToShow]);
-		inst.drawDate = this._checkMinMax(
-			inst.drawDate || inst.get('defaultDate') || $.datepick.today(), inst);
-		var drawDate = $.datepick.add(
-			$.datepick.newDate(inst.drawDate), -inst.get('monthsOffset'), 'm');
-		// Generate months
-		var monthRows = '';
-		for (var row = 0; row < monthsToShow[0]; row++) {
-			var months = '';
-			for (var col = 0; col < monthsToShow[1]; col++) {
-				months += this._generateMonth(target, inst, drawDate.getFullYear(),
-					drawDate.getMonth() + 1, renderer, (row == 0 && col == 0));
-				$.datepick.add(drawDate, 1, 'm');
-			}
-			monthRows += this._prepare(renderer.monthRow, inst).replace(/\{months\}/, months);
-		}
-		var picker = this._prepare(renderer.picker, inst).replace(/\{months\}/, monthRows).
-			replace(/\{weekHeader\}/g, this._generateDayHeaders(inst, renderer)) +
-			($.browser.msie && parseInt($.browser.version, 10) < 7 && !inst.inline ?
-			'<iframe src="javascript:void(0);" class="' + this._coverClass + '"></iframe>' : '');
-		// Add commands
-		var commands = inst.get('commands');
-		var asDateFormat = inst.get('commandsAsDateFormat');
-		var addCommand = function(type, open, close, name, classes) {
-			if (picker.indexOf('{' + type + ':' + name + '}') == -1) {
-				return;
-			}
-			var command = commands[name];
-			var date = (asDateFormat ? command.date.apply(target, [inst]) : null);
-			picker = picker.replace(new RegExp('\\{' + type + ':' + name + '\\}', 'g'),
-				'<' + open +
-				(command.status ? ' title="' + inst.get(command.status) + '"' : '') +
-				' class="' + renderer.commandClass + ' ' +
-				renderer.commandClass + '-' + name + ' ' + classes +
-				(command.enabled(inst) ? '' : ' ' + renderer.disabledClass) + '">' +
-				(date ? $.datepick.formatDate(inst.get(command.text), date, inst.getConfig()) :
-				inst.get(command.text)) + '</' + close + '>');
-		};
-		for (var name in commands) {
-			addCommand('button', 'button type="button"', 'button', name,
-				renderer.commandButtonClass);
-			addCommand('link', 'a href="javascript:void(0)"', 'a', name,
-				renderer.commandLinkClass);
-		}
-		picker = $(picker);
-		if (monthsToShow[1] > 1) {
-			var count = 0;
-			$(renderer.monthSelector, picker).each(function() {
-				var nth = ++count % monthsToShow[1];
-				$(this).addClass(nth == 1 ? 'first' : (nth == 0 ? 'last' : ''));
-			});
-		}
-		// Add datepicker behaviour
-		var self = this;
-		picker.find(renderer.daySelector + ' a').hover(
-				function() { $(this).addClass(renderer.highlightedClass); },
-				function() {
-					(inst.inline ? $(this).parents('.' + self.markerClass) : inst.div).
-						find(renderer.daySelector + ' a').
-						removeClass(renderer.highlightedClass);
-				}).
-			click(function() {
-				self.selectDate(target, this);
-			}).end().
-			find('select.' + this._monthYearClass + ':not(.' + this._anyYearClass + ')').
-			change(function() {
-				var monthYear = $(this).val().split('/');
-				self.showMonth(target, parseInt(monthYear[1], 10), parseInt(monthYear[0], 10));
-			}).end().
-			find('select.' + this._anyYearClass).
-			click(function() {
-				$(this).css('visibility', 'hidden').
-					next('input').css({left: this.offsetLeft, top: this.offsetTop,
-					width: this.offsetWidth, height: this.offsetHeight}).show().focus();
-			}).end().
-			find('input.' + self._monthYearClass).
-			change(function() {
-				try {
-					var year = parseInt($(this).val(), 10);
-					year = (isNaN(year) ? inst.drawDate.getFullYear() : year);
-					self.showMonth(target, year, inst.drawDate.getMonth() + 1, inst.drawDate.getDate());
-				}
-				catch (e) {
-					alert(e);
-				}
-			}).keydown(function(event) {
-				if (event.keyCode == 13) { // Enter
-					$(event.target).change();
-				}
-				else if (event.keyCode == 27) { // Escape
-					$(event.target).hide().prev('select').css('visibility', 'visible');
-					inst.target.focus();
-				}
-			});
-		// Add command behaviour
-		picker.find('.' + renderer.commandClass).click(function() {
-				if (!$(this).hasClass(renderer.disabledClass)) {
-					var action = this.className.replace(
-						new RegExp('^.*' + renderer.commandClass + '-([^ ]+).*$'), '$1');
-					$.datepick.performAction(target, action);
-				}
-			});
-		// Add classes
-		if (inst.get('isRTL')) {
-			picker.addClass(renderer.rtlClass);
-		}
-		if (monthsToShow[0] * monthsToShow[1] > 1) {
-			picker.addClass(renderer.multiClass);
-		}
-		var pickerClass = inst.get('pickerClass');
-		if (pickerClass) {
-			picker.addClass(pickerClass);
-		}
-		// Resize
-		$('body').append(picker);
-		var width = 0;
-		picker.find(renderer.monthSelector).each(function() {
-			width += $(this).outerWidth();
-		});
-		picker.width(width / monthsToShow[0]);
-		// Pre-show customisation
-		var onShow = inst.get('onShow');
-		if (onShow) {
-			onShow.apply(target, [picker, inst]);
-		}
-		return picker;
-	},
-
-	/* Generate the content for a single month.
-	   @param  target    (element) the control to affect
-	   @param  inst      (object) the current instance settings
-	   @param  year      (number) the year to generate
-	   @param  month     (number) the month to generate
-	   @param  renderer  (object) the rendering templates
-	   @param  first     (boolean) true if first of multiple months
-	   @return  (string) the month content */
-	_generateMonth: function(target, inst, year, month, renderer, first) {
-		var daysInMonth = $.datepick.daysInMonth(year, month);
-		var monthsToShow = inst.get('monthsToShow');
-		monthsToShow = ($.isArray(monthsToShow) ? monthsToShow : [1, monthsToShow]);
-		var fixedWeeks = inst.get('fixedWeeks') || (monthsToShow[0] * monthsToShow[1] > 1);
-		var firstDay = inst.get('firstDay');
-		var leadDays = ($.datepick.newDate(year, month, 1).getDay() - firstDay + 7) % 7;
-		var numWeeks = (fixedWeeks ? 6 : Math.ceil((leadDays + daysInMonth) / 7));
-		var showOtherMonths = inst.get('showOtherMonths');
-		var selectOtherMonths = inst.get('selectOtherMonths') && showOtherMonths;
-		var dayStatus = inst.get('dayStatus');
-		var minDate = (inst.pickingRange ? inst.selectedDates[0] : inst.get('minDate'));
-		var maxDate = inst.get('maxDate');
-		var rangeSelect = inst.get('rangeSelect');
-		var onDate = inst.get('onDate');
-		var showWeeks = renderer.week.indexOf('{weekOfYear}') > -1;
-		var calculateWeek = inst.get('calculateWeek');
-		var today = $.datepick.today();
-		var drawDate = $.datepick.newDate(year, month, 1);
-		$.datepick.add(drawDate, -leadDays - (fixedWeeks && (drawDate.getDay() == firstDay) ? 7 : 0), 'd');
-		var ts = drawDate.getTime();
-		// Generate weeks
-		var weeks = '';
-		for (var week = 0; week < numWeeks; week++) {
-			var weekOfYear = (!showWeeks ? '' : '<span class="dp' + ts + '">' +
-				(calculateWeek ? calculateWeek(drawDate) : 0) + '</span>');
-			var days = '';
-			for (var day = 0; day < 7; day++) {
-				var selected = false;
-				if (rangeSelect && inst.selectedDates.length > 0) {
-					selected = (drawDate.getTime() >= inst.selectedDates[0] &&
-						drawDate.getTime() <= inst.selectedDates[1]);
-				}
-				else {
-					for (var i = 0; i < inst.selectedDates.length; i++) {
-						if (inst.selectedDates[i].getTime() == drawDate.getTime()) {
-							selected = true;
-							break;
-						}
-					}
-				}
-				var dateInfo = (!onDate ? {} :
-					onDate.apply(target, [drawDate, drawDate.getMonth() + 1 == month]));
-				var selectable = (dateInfo.selectable != false) &&
-					(selectOtherMonths || drawDate.getMonth() + 1 == month) &&
-					(!minDate || drawDate.getTime() >= minDate.getTime()) &&
-					(!maxDate || drawDate.getTime() <= maxDate.getTime());
-				days += this._prepare(renderer.day, inst).replace(/\{day\}/g,
-					(selectable ? '<a href="javascript:void(0)"' : '<span') +
-					' class="dp' + ts + ' ' + (dateInfo.dateClass || '') +
-					(selected && (selectOtherMonths || drawDate.getMonth() + 1 == month) ?
-					' ' + renderer.selectedClass : '') +
-					(selectable ? ' ' + renderer.defaultClass : '') +
-					((drawDate.getDay() || 7) < 6 ? '' : ' ' + renderer.weekendClass) +
-					(drawDate.getMonth() + 1 == month ? '' : ' ' + renderer.otherMonthClass) +
-					(drawDate.getTime() == today.getTime() && (drawDate.getMonth() + 1) == month ?
-					' ' + renderer.todayClass : '') +
-					(drawDate.getTime() == inst.drawDate.getTime() && (drawDate.getMonth() + 1) == month ?
-					' ' + renderer.highlightedClass : '') + '"' +
-					(dateInfo.title || (dayStatus && selectable) ? ' title="' +
-					(dateInfo.title || $.datepick.formatDate(
-					dayStatus, drawDate, inst.getConfig())) + '"' : '') + '>' +
-					(showOtherMonths || (drawDate.getMonth() + 1) == month ?
-					dateInfo.content || drawDate.getDate() : '&nbsp;') +
-					(selectable ? '</a>' : '</span>'));
-				$.datepick.add(drawDate, 1, 'd');
-				ts = drawDate.getTime();
-			}
-			weeks += this._prepare(renderer.week, inst).replace(/\{days\}/g, days).
-				replace(/\{weekOfYear\}/g, weekOfYear);
-		}
-		var monthHeader = this._prepare(renderer.month, inst).match(/\{monthHeader(:[^\}]+)?\}/);
-		monthHeader = (monthHeader[0].length <= 13 ? 'MM yyyy' :
-			monthHeader[0].substring(13, monthHeader[0].length - 1));
-		monthHeader = (first ? this._generateMonthSelection(
-			inst, year, month, minDate, maxDate, monthHeader, renderer) :
-			$.datepick.formatDate(monthHeader, $.datepick.newDate(year, month, 1), inst.getConfig()));
-		var weekHeader = this._prepare(renderer.weekHeader, inst).
-			replace(/\{days\}/g, this._generateDayHeaders(inst, renderer));
-		return this._prepare(renderer.month, inst).replace(/\{monthHeader(:[^\}]+)?\}/g, monthHeader).
-			replace(/\{weekHeader\}/g, weekHeader).replace(/\{weeks\}/g, weeks);
-	},
-
-	/* Generate the HTML for the day headers.
-	   @param  inst      (object) the current instance settings
-	   @param  renderer  (object) the rendering templates
-	   @return  (string) a week's worth of day headers */
-	_generateDayHeaders: function(inst, renderer) {
-		var firstDay = inst.get('firstDay');
-		var dayNames = inst.get('dayNames');
-		var dayNamesMin = inst.get('dayNamesMin');
-		var header = '';
-		for (var day = 0; day < 7; day++) {
-			var dow = (day + firstDay) % 7;
-			header += this._prepare(renderer.dayHeader, inst).replace(/\{day\}/g,
-				'<span class="' + this._curDoWClass + dow + '" title="' +
-				dayNames[dow] + '">' + dayNamesMin[dow] + '</span>');
-		}
-		return header;
-	},
-
-	/* Generate selection controls for month.
-	   @param  inst         (object) the current instance settings
-	   @param  year         (number) the year to generate
-	   @param  month        (number) the month to generate
-	   @param  minDate      (CDate) the minimum date allowed
-	   @param  maxDate      (CDate) the maximum date allowed
-	   @param  monthHeader  (string) the month/year format
-	   @return  (string) the month selection content */
-	_generateMonthSelection: function(inst, year, month, minDate, maxDate, monthHeader) {
-		if (!inst.get('changeMonth')) {
-			return $.datepick.formatDate(
-				monthHeader, $.datepick.newDate(year, month, 1), inst.getConfig());
-		}
-		// Months
-		var monthNames = inst.get('monthNames' + (monthHeader.match(/mm/i) ? '' : 'Short'));
-		var html = monthHeader.replace(/m+/i, '\\x2E').replace(/y+/i, '\\x2F');
-		var selector = '<select class="' + this._monthYearClass +
-			'" title="' + inst.get('monthStatus') + '">';
-		for (var m = 1; m <= 12; m++) {
-			if ((!minDate || $.datepick.newDate(year, m, $.datepick.daysInMonth(year, m)).
-					getTime() >= minDate.getTime()) &&
-					(!maxDate || $.datepick.newDate(year, m, 1).getTime() <= maxDate.getTime())) {
-				selector += '<option value="' + m + '/' + year + '"' +
-					(month == m ? ' selected="selected"' : '') + '>' +
-					monthNames[m - 1] + '</option>';
-			}
-		}
-		selector += '</select>';
-		html = html.replace(/\\x2E/, selector);
-		// Years
-		var yearRange = inst.get('yearRange');
-		if (yearRange == 'any') {
-			selector = '<select class="' + this._monthYearClass + ' ' + this._anyYearClass +
-				'" title="' + inst.get('yearStatus') + '">' +
-				'<option>' + year + '</option></select>' +
-				'<input class="' + this._monthYearClass + ' ' + this._curMonthClass +
-				month + '" value="' + year + '">';
-		}
-		else {
-			yearRange = yearRange.split(':');
-			var todayYear = $.datepick.today().getFullYear();
-			var start = (yearRange[0].match('c[+-].*') ? year + parseInt(yearRange[0].substring(1), 10) :
-				((yearRange[0].match('[+-].*') ? todayYear : 0) + parseInt(yearRange[0], 10)));
-			var end = (yearRange[1].match('c[+-].*') ? year + parseInt(yearRange[1].substring(1), 10) :
-				((yearRange[1].match('[+-].*') ? todayYear : 0) + parseInt(yearRange[1], 10)));
-			selector = '<select class="' + this._monthYearClass +
-				'" title="' + inst.get('yearStatus') + '">';
-			var min = $.datepick.add($.datepick.newDate(start + 1, 1, 1), -1, 'd');
-			min = (minDate && minDate.getTime() > min.getTime() ? minDate : min).getFullYear();
-			var max = $.datepick.newDate(end, 1, 1);
-			max = (maxDate && maxDate.getTime() < max.getTime() ? maxDate : max).getFullYear();
-			for (var y = min; y <= max; y++) {
-				if (y != 0) {
-					selector += '<option value="' + month + '/' + y + '"' +
-						(year == y ? ' selected="selected"' : '') + '>' + y + '</option>';
-				}
-			}
-			selector += '</select>';
-		}
-		html = html.replace(/\\x2F/, selector);
-		return html;
-	},
-
-	/* Prepare a render template for use.
-	   Exclude popup/inline sections that are not applicable.
-	   Localise text of the form: {l10n:name}.
-	   @param  text  (string) the text to localise
-	   @param  inst  (object) the current instance settings
-	   @return  (string) the localised text */
-	_prepare: function(text, inst) {
-		var replaceSection = function(type, retain) {
-			while (true) {
-				var start = text.indexOf('{' + type + ':start}');
-				if (start == -1) {
-					return;
-				}
-				var end = text.substring(start).indexOf('{' + type + ':end}');
-				if (end > -1) {
-					text = text.substring(0, start) +
-						(retain ? text.substr(start + type.length + 8, end - type.length - 8) : '') +
-						text.substring(start + end + type.length + 6);
-				}
-			}
-		};
-		replaceSection('inline', inst.inline);
-		replaceSection('popup', !inst.inline);
-		var pattern = /\{l10n:([^\}]+)\}/;
-		var matches = null;
-		while (matches = pattern.exec(text)) {
-			text = text.replace(matches[0], inst.get(matches[1]));
-		}
-		return text;
-	}
-});
-
-/* jQuery extend now ignores nulls!
-   @param  target  (object) the object to extend
-   @param  props   (object) the new settings
-   @return  (object) the updated object */
-function extendRemove(target, props) {
-	$.extend(target, props);
-	for (var name in props)
-		if (props[name] == null || props[name] == undefined)
-			target[name] = props[name];
-	return target;
-};
-
-/* Attach the datepicker functionality to a jQuery selection.
-   @param  command  (string) the command to run (optional, default 'attach')
-   @param  options  (object) the new settings to use for these instances (optional)
-   @return  (jQuery) for chaining further calls */
-$.fn.datepick = function(options) {
-	var otherArgs = Array.prototype.slice.call(arguments, 1);
-	if ($.inArray(options, ['getDate', 'isDisabled', 'options', 'retrieveDate']) > -1) {
-		return $.datepick[options].apply($.datepick, [this[0]].concat(otherArgs));
-	}
-	return this.each(function() {
-		if (typeof options == 'string') {
-			$.datepick[options].apply($.datepick, [this].concat(otherArgs));
-		}
-		else {
-			$.datepick._attachPicker(this, options || {});
-		}
-	});
-};
-
-/* Initialise the datepicker functionality. */
-$.datepick = new Datepicker(); // singleton instance
-
-$(function() {
-	$(document).mousedown($.datepick._checkExternalClick).
-		resize(function() { $.datepick.hide($.datepick.curInst); });
-});
-
-})(jQuery);
+(function($){var E='datepick';$.JQPlugin.createPlugin({name:E,defaultRenderer:{picker:'<div class="datepick">'+'<div class="datepick-nav">{link:prev}{link:today}{link:next}</div>{months}'+'{popup:start}<div class="datepick-ctrl">{link:clear}{link:close}</div>{popup:end}'+'<div class="datepick-clear-fix"></div></div>',monthRow:'<div class="datepick-month-row">{months}</div>',month:'<div class="datepick-month"><div class="datepick-month-header">{monthHeader}</div>'+'<table><thead>{weekHeader}</thead><tbody>{weeks}</tbody></table></div>',weekHeader:'<tr>{days}</tr>',dayHeader:'<th>{day}</th>',week:'<tr>{days}</tr>',day:'<td>{day}</td>',monthSelector:'.datepick-month',daySelector:'td',rtlClass:'datepick-rtl',multiClass:'datepick-multi',defaultClass:'',selectedClass:'datepick-selected',highlightedClass:'datepick-highlight',todayClass:'datepick-today',otherMonthClass:'datepick-other-month',weekendClass:'datepick-weekend',commandClass:'datepick-cmd',commandButtonClass:'',commandLinkClass:'',disabledClass:'datepick-disabled'},commands:{prev:{text:'prevText',status:'prevStatus',keystroke:{keyCode:33},enabled:function(a){var b=a.curMinDate();return(!b||F.add(F.day(F._applyMonthsOffset(F.add(F.newDate(a.drawDate),1-a.options.monthsToStep,'m'),a),1),-1,'d').getTime()>=b.getTime())},date:function(a){return F.day(F._applyMonthsOffset(F.add(F.newDate(a.drawDate),-a.options.monthsToStep,'m'),a),1)},action:function(a){F.changeMonth(this,-a.options.monthsToStep)}},prevJump:{text:'prevJumpText',status:'prevJumpStatus',keystroke:{keyCode:33,ctrlKey:true},enabled:function(a){var b=a.curMinDate();return(!b||F.add(F.day(F._applyMonthsOffset(F.add(F.newDate(a.drawDate),1-a.options.monthsToJump,'m'),a),1),-1,'d').getTime()>=b.getTime())},date:function(a){return F.day(F._applyMonthsOffset(F.add(F.newDate(a.drawDate),-a.options.monthsToJump,'m'),a),1)},action:function(a){F.changeMonth(this,-a.options.monthsToJump)}},next:{text:'nextText',status:'nextStatus',keystroke:{keyCode:34},enabled:function(a){var b=a.get('maxDate');return(!b||F.day(F._applyMonthsOffset(F.add(F.newDate(a.drawDate),a.options.monthsToStep,'m'),a),1).getTime()<=b.getTime())},date:function(a){return F.day(F._applyMonthsOffset(F.add(F.newDate(a.drawDate),a.options.monthsToStep,'m'),a),1)},action:function(a){F.changeMonth(this,a.options.monthsToStep)}},nextJump:{text:'nextJumpText',status:'nextJumpStatus',keystroke:{keyCode:34,ctrlKey:true},enabled:function(a){var b=a.get('maxDate');return(!b||F.day(F._applyMonthsOffset(F.add(F.newDate(a.drawDate),a.options.monthsToJump,'m'),a),1).getTime()<=b.getTime())},date:function(a){return F.day(F._applyMonthsOffset(F.add(F.newDate(a.drawDate),a.options.monthsToJump,'m'),a),1)},action:function(a){F.changeMonth(this,a.options.monthsToJump)}},current:{text:'currentText',status:'currentStatus',keystroke:{keyCode:36,ctrlKey:true},enabled:function(a){var b=a.curMinDate();var c=a.get('maxDate');var d=a.selectedDates[0]||F.today();return(!b||d.getTime()>=b.getTime())&&(!c||d.getTime()<=c.getTime())},date:function(a){return a.selectedDates[0]||F.today()},action:function(a){var b=a.selectedDates[0]||F.today();F.showMonth(this,b.getFullYear(),b.getMonth()+1)}},today:{text:'todayText',status:'todayStatus',keystroke:{keyCode:36,ctrlKey:true},enabled:function(a){var b=a.curMinDate();var c=a.get('maxDate');return(!b||F.today().getTime()>=b.getTime())&&(!c||F.today().getTime()<=c.getTime())},date:function(a){return F.today()},action:function(a){F.showMonth(this)}},clear:{text:'clearText',status:'clearStatus',keystroke:{keyCode:35,ctrlKey:true},enabled:function(a){return true},date:function(a){return null},action:function(a){F.clear(this)}},close:{text:'closeText',status:'closeStatus',keystroke:{keyCode:27},enabled:function(a){return true},date:function(a){return null},action:function(a){F.hide(this)}},prevWeek:{text:'prevWeekText',status:'prevWeekStatus',keystroke:{keyCode:38,ctrlKey:true},enabled:function(a){var b=a.curMinDate();return(!b||F.add(F.newDate(a.drawDate),-7,'d').getTime()>=b.getTime())},date:function(a){return F.add(F.newDate(a.drawDate),-7,'d')},action:function(a){F.changeDay(this,-7)}},prevDay:{text:'prevDayText',status:'prevDayStatus',keystroke:{keyCode:37,ctrlKey:true},enabled:function(a){var b=a.curMinDate();return(!b||F.add(F.newDate(a.drawDate),-1,'d').getTime()>=b.getTime())},date:function(a){return F.add(F.newDate(a.drawDate),-1,'d')},action:function(a){F.changeDay(this,-1)}},nextDay:{text:'nextDayText',status:'nextDayStatus',keystroke:{keyCode:39,ctrlKey:true},enabled:function(a){var b=a.get('maxDate');return(!b||F.add(F.newDate(a.drawDate),1,'d').getTime()<=b.getTime())},date:function(a){return F.add(F.newDate(a.drawDate),1,'d')},action:function(a){F.changeDay(this,1)}},nextWeek:{text:'nextWeekText',status:'nextWeekStatus',keystroke:{keyCode:40,ctrlKey:true},enabled:function(a){var b=a.get('maxDate');return(!b||F.add(F.newDate(a.drawDate),7,'d').getTime()<=b.getTime())},date:function(a){return F.add(F.newDate(a.drawDate),7,'d')},action:function(a){F.changeDay(this,7)}}},defaultOptions:{pickerClass:'',showOnFocus:true,showTrigger:null,showAnim:'show',showOptions:{},showSpeed:'normal',popupContainer:null,alignment:'bottom',fixedWeeks:false,firstDay:0,calculateWeek:null,monthsToShow:1,monthsOffset:0,monthsToStep:1,monthsToJump:12,useMouseWheel:true,changeMonth:true,yearRange:'c-10:c+10',shortYearCutoff:'+10',showOtherMonths:false,selectOtherMonths:false,defaultDate:null,selectDefaultDate:false,minDate:null,maxDate:null,dateFormat:'mm/dd/yyyy',autoSize:false,rangeSelect:false,rangeSeparator:' - ',multiSelect:0,multiSeparator:',',onDate:null,onShow:null,onChangeMonthYear:null,onSelect:null,onClose:null,altField:null,altFormat:null,constrainInput:true,commandsAsDateFormat:false,commands:{}},regionalOptions:{'':{monthNames:['January','February','March','April','May','June','July','August','September','October','November','December'],monthNamesShort:['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'],dayNames:['Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday'],dayNamesShort:['Sun','Mon','Tue','Wed','Thu','Fri','Sat'],dayNamesMin:['Su','Mo','Tu','We','Th','Fr','Sa'],dateFormat:'mm/dd/yyyy',firstDay:0,renderer:{},prevText:'&lt;Prev',prevStatus:'Show the previous month',prevJumpText:'&lt;&lt;',prevJumpStatus:'Show the previous year',nextText:'Next&gt;',nextStatus:'Show the next month',nextJumpText:'&gt;&gt;',nextJumpStatus:'Show the next year',currentText:'Current',currentStatus:'Show the current month',todayText:'Today',todayStatus:'Show today\'s month',clearText:'Clear',clearStatus:'Clear all the dates',closeText:'Close',closeStatus:'Close the datepicker',yearStatus:'Change the year',monthStatus:'Change the month',weekText:'Wk',weekStatus:'Week of the year',dayStatus:'Select DD, M d, yyyy',defaultStatus:'Select a date',isRTL:false}},_getters:['getDate','isDisabled','isSelectable','retrieveDate'],_disabled:[],_popupClass:E+'-popup',_triggerClass:E+'-trigger',_disableClass:E+'-disable',_monthYearClass:E+'-month-year',_curMonthClass:E+'-month-',_anyYearClass:E+'-any-year',_curDoWClass:E+'-dow-',_ticksTo1970:(((1970-1)*365+Math.floor(1970/4)-Math.floor(1970/100)+Math.floor(1970/400))*24*60*60*10000000),_msPerDay:24*60*60*1000,ATOM:'yyyy-mm-dd',COOKIE:'D, dd M yyyy',FULL:'DD, MM d, yyyy',ISO_8601:'yyyy-mm-dd',JULIAN:'J',RFC_822:'D, d M yy',RFC_850:'DD, dd-M-yy',RFC_1036:'D, d M yy',RFC_1123:'D, d M yyyy',RFC_2822:'D, d M yyyy',RSS:'D, d M yy',TICKS:'!',TIMESTAMP:'@',W3C:'yyyy-mm-dd',formatDate:function(f,g,h){if(typeof f!=='string'){h=g;g=f;f=''}if(!g){return''}f=f||this.defaultOptions.dateFormat;h=h||{};var i=h.dayNamesShort||this.defaultOptions.dayNamesShort;var j=h.dayNames||this.defaultOptions.dayNames;var k=h.monthNamesShort||this.defaultOptions.monthNamesShort;var l=h.monthNames||this.defaultOptions.monthNames;var m=h.calculateWeek||this.defaultOptions.calculateWeek;var n=function(a,b){var c=1;while(s+c<f.length&&f.charAt(s+c)===a){c++}s+=c-1;return Math.floor(c/(b||1))>1};var o=function(a,b,c,d){var e=''+b;if(n(a,d)){while(e.length<c){e='0'+e}}return e};var p=function(a,b,c,d){return(n(a)?d[b]:c[b])};var q='';var r=false;for(var s=0;s<f.length;s++){if(r){if(f.charAt(s)==="'"&&!n("'")){r=false}else{q+=f.charAt(s)}}else{switch(f.charAt(s)){case'd':q+=o('d',g.getDate(),2);break;case'D':q+=p('D',g.getDay(),i,j);break;case'o':q+=o('o',this.dayOfYear(g),3);break;case'w':q+=o('w',m(g),2);break;case'm':q+=o('m',g.getMonth()+1,2);break;case'M':q+=p('M',g.getMonth(),k,l);break;case'y':q+=(n('y',2)?g.getFullYear():(g.getFullYear()%100<10?'0':'')+g.getFullYear()%100);break;case'@':q+=Math.floor(g.getTime()/1000);break;case'!':q+=g.getTime()*10000+this._ticksTo1970;break;case"'":if(n("'")){q+="'"}else{r=true}break;default:q+=f.charAt(s)}}}return q},parseDate:function(g,h,j){if(h==null){throw'Invalid arguments';}h=(typeof h==='object'?h.toString():h+'');if(h===''){return null}g=g||this.defaultOptions.dateFormat;j=j||{};var k=j.shortYearCutoff||this.defaultOptions.shortYearCutoff;k=(typeof k!=='string'?k:this.today().getFullYear()%100+parseInt(k,10));var l=j.dayNamesShort||this.defaultOptions.dayNamesShort;var m=j.dayNames||this.defaultOptions.dayNames;var n=j.monthNamesShort||this.defaultOptions.monthNamesShort;var o=j.monthNames||this.defaultOptions.monthNames;var p=-1;var q=-1;var r=-1;var s=-1;var t=false;var u=false;var v=function(a,b){var c=1;while(A+c<g.length&&g.charAt(A+c)===a){c++}A+=c-1;return Math.floor(c/(b||1))>1};var w=function(a,b){var c=v(a,b);var d=[2,3,c?4:2,11,20]['oy@!'.indexOf(a)+1];var e=new RegExp('^-?\\d{1,'+d+'}');var f=h.substring(z).match(e);if(!f){throw'Missing number at position {0}'.replace(/\{0\}/,z);}z+=f[0].length;return parseInt(f[0],10);};var x=function(a,b,c,d){var e=(v(a,d)?c:b);for(var i=0;i<e.length;i++){if(h.substr(z,e[i].length).toLowerCase()===e[i].toLowerCase()){z+=e[i].length;return i+1;}}throw'Unknown name at position {0}'.replace(/\{0\}/,z);};var y=function(){if(h.charAt(z)!==g.charAt(A)){throw'Unexpected literal at position {0}'.replace(/\{0\}/,z);}z++;};var z=0;for(var A=0;A<g.length;A++){if(u){if(g.charAt(A)==="'"&&!v("'")){u=false;}else{y();}}else{switch(g.charAt(A)){case'd':r=w('d');break;case'D':x('D',l,m);break;case'o':s=w('o');break;case'w':w('w');break;case'm':q=w('m');break;case'M':q=x('M',n,o);break;case'y':var B=A;t=!v('y',2);A=B;p=w('y',2);break;case'@':var C=this._normaliseDate(new Date(w('@')*1000));p=C.getFullYear();q=C.getMonth()+1;r=C.getDate();break;case'!':var C=this._normaliseDate(new Date((w('!')-this._ticksTo1970)/10000));p=C.getFullYear();q=C.getMonth()+1;r=C.getDate();break;case'*':z=h.length;break;case"'":if(v("'")){y();}else{u=true;}break;default:y();}}}if(z<h.length){throw'Additional text found at end';}if(p===-1){p=this.today().getFullYear();}else if(p<100&&t){p+=(k===-1?1900:this.today().getFullYear()-this.today().getFullYear()%100-(p<=k?0:100));}if(s>-1){q=1;r=s;for(var D=this.daysInMonth(p,q);r>D;D=this.daysInMonth(p,q)){q++;r-=D;}}var C=this.newDate(p,q,r);if(C.getFullYear()!==p||C.getMonth()+1!==q||C.getDate()!==r){throw'Invalid date';}return C;},determineDate:function(f,g,h,i,j){if(h&&typeof h!=='object'){j=i;i=h;h=null;}if(typeof i!=='string'){j=i;i='';}var k=function(a){try{return F.parseDate(i,a,j);}catch(e){}a=a.toLowerCase();var b=(a.match(/^c/)&&h?F.newDate(h):null)||F.today();var c=/([+-]?[0-9]+)\s*(d|w|m|y)?/g;var d=null;while(d=c.exec(a)){b=F.add(b,parseInt(d[1],10),d[2]||'d');}return b;};g=(g?F.newDate(g):null);f=(f==null?g:(typeof f==='string'?k(f):(typeof f==='number'?(isNaN(f)||f===Infinity||f===-Infinity?g:F.add(F.today(),f,'d')):F.newDate(f))));return f;},daysInMonth:function(a,b){b=(a.getFullYear?a.getMonth()+1:b);a=(a.getFullYear?a.getFullYear():a);return this.newDate(a,b+1,0).getDate();},dayOfYear:function(a,b,c){var d=(a.getFullYear?a:F.newDate(a,b,c));var e=F.newDate(d.getFullYear(),1,1);return Math.floor((d.getTime()-e.getTime())/F._msPerDay)+1;},iso8601Week:function(a,b,c){var d=(a.getFullYear?new Date(a.getTime()):F.newDate(a,b,c));d.setDate(d.getDate()+4-(d.getDay()||7));var e=d.getTime();d.setMonth(0,1);return Math.floor(Math.round((e-d)/F._msPerDay)/7)+1;},today:function(){return this._normaliseDate(new Date());},newDate:function(a,b,c){return(!a?null:(a.getFullYear?this._normaliseDate(new Date(a.getTime())):new Date(a,b-1,c,12)));},_normaliseDate:function(a){if(a){a.setHours(12,0,0,0);}return a;},year:function(a,b){a.setFullYear(b);return this._normaliseDate(a);},month:function(a,b){a.setMonth(b-1);return this._normaliseDate(a);},day:function(a,b){a.setDate(b);return this._normaliseDate(a);},add:function(a,b,c){if(c==='d'||c==='w'){this._normaliseDate(a);a.setDate(a.getDate()+b*(c==='w'?7:1));}else{var d=a.getFullYear()+(c==='y'?b:0);var e=a.getMonth()+(c==='m'?b:0);a.setTime(F.newDate(d,e+1,Math.min(a.getDate(),this.daysInMonth(d,e+1))).getTime());}return a;},_applyMonthsOffset:function(a,b){var c=b.options.monthsOffset;if($.isFunction(c)){c=c.apply(b.elem[0],[a]);}return F.add(a,-c,'m');},_init:function(){this.defaultOptions.commands=this.commands;this.defaultOptions.calculateWeek=this.iso8601Week;this.regionalOptions[''].renderer=this.defaultRenderer;this._super();},_instSettings:function(b,c){return{selectedDates:[],drawDate:null,pickingRange:false,inline:($.inArray(b[0].nodeName.toLowerCase(),['div','span'])>-1),get:function(a){if($.inArray(a,['defaultDate','minDate','maxDate'])>-1){return F.determineDate(this.options[a],null,this.selectedDates[0],this.options.dateFormat,this.getConfig());}return this.options[a];},curMinDate:function(){return(this.pickingRange?this.selectedDates[0]:this.get('minDate'));},getConfig:function(){return{dayNamesShort:this.options.dayNamesShort,dayNames:this.options.dayNames,monthNamesShort:this.options.monthNamesShort,monthNames:this.options.monthNames,calculateWeek:this.options.calculateWeek,shortYearCutoff:this.options.shortYearCutoff};}};},_postAttach:function(a,b){if(b.inline){b.drawDate=F._checkMinMax(F.newDate(b.selectedDates[0]||b.get('defaultDate')||F.today()),b);b.prevDate=F.newDate(b.drawDate);this._update(a[0]);if($.fn.mousewheel){a.mousewheel(this._doMouseWheel);}}else{this._attachments(a,b);a.on('keydown.'+b.name,this._keyDown).on('keypress.'+b.name,this._keyPress).on('keyup.'+b.name,this._keyUp);if(a.attr('disabled')){this.disable(a[0]);}}},_optionsChanged:function(b,c,d){if(d.calendar&&d.calendar!==c.options.calendar){var e=function(a){return(typeof c.options[a]==='object'?null:c.options[a]);};d=$.extend({defaultDate:e('defaultDate'),minDate:e('minDate'),maxDate:e('maxDate')},d);c.selectedDates=[];c.drawDate=null;}var f=c.selectedDates;$.extend(c.options,d);this.setDate(b[0],f,null,false,true);c.pickingRange=false;c.drawDate=F.newDate(this._checkMinMax((c.options.defaultDate?c.get('defaultDate'):c.drawDate)||c.get('defaultDate')||F.today(),c));if(!c.inline){this._attachments(b,c);}if(c.inline||c.div){this._update(b[0]);}},_attachments:function(a,b){a.off('focus.'+b.name);if(b.options.showOnFocus){a.on('focus.'+b.name,this.show);}if(b.trigger){b.trigger.remove();}var c=b.options.showTrigger;b.trigger=(!c?$([]):$(c).clone().removeAttr('id').addClass(this._triggerClass)[b.options.isRTL?'insertBefore':'insertAfter'](a).click(function(){if(!F.isDisabled(a[0])){F[F.curInst===b?'hide':'show'](a[0]);}}));this._autoSize(a,b);var d=this._extractDates(b,a.val());if(d){this.setDate(a[0],d,null,true);}var e=b.get('defaultDate');if(b.options.selectDefaultDate&&e&&b.selectedDates.length===0){this.setDate(a[0],F.newDate(e||F.today()));}},_autoSize:function(d,e){if(e.options.autoSize&&!e.inline){var f=F.newDate(2009,10,20);var g=e.options.dateFormat;if(g.match(/[DM]/)){var h=function(a){var b=0;var c=0;for(var i=0;i<a.length;i++){if(a[i].length>b){b=a[i].length;c=i;}}return c;};f.setMonth(h(e.options[g.match(/MM/)?'monthNames':'monthNamesShort']));f.setDate(h(e.options[g.match(/DD/)?'dayNames':'dayNamesShort'])+20-f.getDay());}e.elem.attr('size',F.formatDate(g,f,e.getConfig()).length);}},_preDestroy:function(a,b){if(b.trigger){b.trigger.remove();}a.empty().off('.'+b.name);if(b.inline&&$.fn.mousewheel){a.unmousewheel();}if(!b.inline&&b.options.autoSize){a.removeAttr('size');}},multipleEvents:function(b){var c=arguments;return function(a){for(var i=0;i<c.length;i++){c[i].apply(this,arguments);}};},enable:function(b){b=$(b);if(!b.hasClass(this._getMarker())){return;}var c=this._getInst(b);if(c.inline){b.children('.'+this._disableClass).remove().end().find('button,select').prop('disabled',false).end().find('a').attr('href','javascript:void(0)');}else{b.prop('disabled',false);c.trigger.filter('button.'+this._triggerClass).prop('disabled',false).end().filter('img.'+this._triggerClass).css({opacity:'1.0',cursor:''});}this._disabled=$.map(this._disabled,function(a){return(a===b[0]?null:a);});},disable:function(b){b=$(b);if(!b.hasClass(this._getMarker())){return;}var c=this._getInst(b);if(c.inline){var d=b.children(':last');var e=d.offset();var f={left:0,top:0};d.parents().each(function(){if($(this).css('position')==='relative'){f=$(this).offset();return false;}});var g=b.css('zIndex');g=(g==='auto'?0:parseInt(g,10))+1;b.prepend('<div class="'+this._disableClass+'" style="'+'width: '+d.outerWidth()+'px; height: '+d.outerHeight()+'px; left: '+(e.left-f.left)+'px; top: '+(e.top-f.top)+'px; z-index: '+g+'"></div>').find('button,select').prop('disabled',true).end().find('a').removeAttr('href');}else{b.prop('disabled',true);c.trigger.filter('button.'+this._triggerClass).prop('disabled',true).end().filter('img.'+this._triggerClass).css({opacity:'0.5',cursor:'default'});}this._disabled=$.map(this._disabled,function(a){return(a===b[0]?null:a);});this._disabled.push(b[0]);},isDisabled:function(a){return(a&&$.inArray(a,this._disabled)>-1);},show:function(a){a=$(a.target||a);var b=F._getInst(a);if(F.curInst===b){return;}if(F.curInst){F.hide(F.curInst,true);}if(!$.isEmptyObject(b)){b.lastVal=null;b.selectedDates=F._extractDates(b,a.val());b.pickingRange=false;b.drawDate=F._checkMinMax(F.newDate(b.selectedDates[0]||b.get('defaultDate')||F.today()),b);b.prevDate=F.newDate(b.drawDate);F.curInst=b;F._update(a[0],true);var c=F._checkOffset(b);b.div.css({left:c.left,top:c.top});var d=b.options.showAnim;var e=b.options.showSpeed;e=(e==='normal'&&$.ui&&parseInt($.ui.version.substring(2))>=8?'_default':e);if($.effects&&($.effects[d]||($.effects.effect&&$.effects.effect[d]))){var f=b.div.data();for(var g in f){if(g.match(/^ec\.storage\./)){f[g]=b._mainDiv.css(g.replace(/ec\.storage\./,''));}}b.div.data(f).show(d,b.options.showOptions,e);}else{b.div[d||'show'](d?e:0);}}},_extractDates:function(a,b){if(b===a.lastVal){return;}a.lastVal=b;b=b.split(a.options.multiSelect?a.options.multiSeparator:(a.options.rangeSelect?a.options.rangeSeparator:'\x00'));var c=[];for(var i=0;i<b.length;i++){try{var d=F.parseDate(a.options.dateFormat,b[i],a.getConfig());if(d){var f=false;for(var j=0;j<c.length;j++){if(c[j].getTime()===d.getTime()){f=true;break;}}if(!f){c.push(d);}}}catch(e){}}c.splice(a.options.multiSelect||(a.options.rangeSelect?2:1),c.length);if(a.options.rangeSelect&&c.length===1){c[1]=c[0];}return c;},_update:function(a,b){a=$(a.target||a);var c=F._getInst(a);if(!$.isEmptyObject(c)){if(c.inline||F.curInst===c){if($.isFunction(c.options.onChangeMonthYear)&&(!c.prevDate||c.prevDate.getFullYear()!==c.drawDate.getFullYear()||c.prevDate.getMonth()!==c.drawDate.getMonth())){c.options.onChangeMonthYear.apply(a[0],[c.drawDate.getFullYear(),c.drawDate.getMonth()+1]);}}if(c.inline){a.html(this._generateContent(a[0],c));}else if(F.curInst===c){if(!c.div){c.div=$('<div></div>').addClass(this._popupClass).css({display:(b?'none':'static'),position:'absolute',left:a.offset().left,top:a.offset().top+a.outerHeight()}).appendTo($(c.options.popupContainer||'body'));if($.fn.mousewheel){c.div.mousewheel(this._doMouseWheel);}}c.div.html(this._generateContent(a[0],c));a.focus();}}},_updateInput:function(a,b){var c=this._getInst(a);if(!$.isEmptyObject(c)){var d='';var e='';var f=(c.options.multiSelect?c.options.multiSeparator:c.options.rangeSeparator);var g=c.options.altFormat||c.options.dateFormat;for(var i=0;i<c.selectedDates.length;i++){d+=(b?'':(i>0?f:'')+F.formatDate(c.options.dateFormat,c.selectedDates[i],c.getConfig()));e+=(i>0?f:'')+F.formatDate(g,c.selectedDates[i],c.getConfig());}if(!c.inline&&!b){$(a).val(d);}$(c.options.altField).val(e);if($.isFunction(c.options.onSelect)&&!b&&!c.inSelect){c.inSelect=true;c.options.onSelect.apply(a,[c.selectedDates]);c.inSelect=false;}}},_getBorders:function(b){var c=function(a){return{thin:1,medium:3,thick:5}[a]||a;};return[parseFloat(c(b.css('border-left-width'))),parseFloat(c(b.css('border-top-width')))];},_checkOffset:function(a){var b=(a.elem.is(':hidden')&&a.trigger?a.trigger:a.elem);var c=b.offset();var d=$(window).width();var e=$(window).height();if(d===0){return c;}var f=false;$(a.elem).parents().each(function(){f|=$(this).css('position')==='fixed';return!f;});var g=document.documentElement.scrollLeft||document.body.scrollLeft;var h=document.documentElement.scrollTop||document.body.scrollTop;var i=c.top-(f?h:0)-a.div.outerHeight();var j=c.top-(f?h:0)+b.outerHeight();var k=c.left-(f?g:0);var l=c.left-(f?g:0)+b.outerWidth()-a.div.outerWidth();var m=(c.left-g+a.div.outerWidth())>d;var n=(c.top-h+a.elem.outerHeight()+a.div.outerHeight())>e;a.div.css('position',f?'fixed':'absolute');var o=a.options.alignment;if(o==='topLeft'){c={left:k,top:i};}else if(o==='topRight'){c={left:l,top:i};}else if(o==='bottomLeft'){c={left:k,top:j};}else if(o==='bottomRight'){c={left:l,top:j};}else if(o==='top'){c={left:(a.options.isRTL||m?l:k),top:i};}else{c={left:(a.options.isRTL||m?l:k),top:(n?i:j)};}c.left=Math.max((f?0:g),c.left);c.top=Math.max((f?0:h),c.top);return c;},_checkExternalClick:function(a){if(!F.curInst){return;}var b=$(a.target);if(b.closest('.'+F._popupClass+',.'+F._triggerClass).length===0&&!b.hasClass(F._getMarker())){F.hide(F.curInst);}},hide:function(a,b){if(!a){return;}var c=this._getInst(a);if($.isEmptyObject(c)){c=a;}if(c&&c===F.curInst){var d=(b?'':c.options.showAnim);var e=c.options.showSpeed;e=(e==='normal'&&$.ui&&parseInt($.ui.version.substring(2))>=8?'_default':e);var f=function(){if(!c.div){return;}c.div.remove();c.div=null;F.curInst=null;if($.isFunction(c.options.onClose)){c.options.onClose.apply(a,[c.selectedDates]);}};c.div.stop();if($.effects&&($.effects[d]||($.effects.effect&&$.effects.effect[d]))){c.div.hide(d,c.options.showOptions,e,f);}else{var g=(d==='slideDown'?'slideUp':(d==='fadeIn'?'fadeOut':'hide'));c.div[g]((d?e:''),f);}if(!d){f();}}},_keyDown:function(a){var b=a.target;var c=F._getInst(b);var d=false;if(c.div){if(a.keyCode===9){F.hide(b);}else if(a.keyCode===13){F.selectDate(b,$('a.'+c.options.renderer.highlightedClass,c.div)[0]);d=true;}else{var e=c.options.commands;for(var f in e){var g=e[f];if(g.keystroke.keyCode===a.keyCode&&!!g.keystroke.ctrlKey===!!(a.ctrlKey||a.metaKey)&&!!g.keystroke.altKey===a.altKey&&!!g.keystroke.shiftKey===a.shiftKey){F.performAction(b,f);d=true;break;}}}}else{var g=c.options.commands.current;if(g.keystroke.keyCode===a.keyCode&&!!g.keystroke.ctrlKey===!!(a.ctrlKey||a.metaKey)&&!!g.keystroke.altKey===a.altKey&&!!g.keystroke.shiftKey===a.shiftKey){F.show(b);d=true;}}c.ctrlKey=((a.keyCode<48&&a.keyCode!==32)||a.ctrlKey||a.metaKey);if(d){a.preventDefault();a.stopPropagation();}return!d;},_keyPress:function(a){var b=F._getInst(a.target);if(!$.isEmptyObject(b)&&b.options.constrainInput){var c=String.fromCharCode(a.keyCode||a.charCode);var d=F._allowedChars(b);return(a.metaKey||b.ctrlKey||c<' '||!d||d.indexOf(c)>-1);}return true;},_allowedChars:function(a){var b=(a.options.multiSelect?a.options.multiSeparator:(a.options.rangeSelect?a.options.rangeSeparator:''));var c=false;var d=false;var e=a.options.dateFormat;for(var i=0;i<e.length;i++){var f=e.charAt(i);if(c){if(f==="'"&&e.charAt(i+1)!=="'"){c=false;}else{b+=f;}}else{switch(f){case'd':case'm':case'o':case'w':b+=(d?'':'0123456789');d=true;break;case'y':case'@':case'!':b+=(d?'':'0123456789')+'-';d=true;break;case'J':b+=(d?'':'0123456789')+'-.';d=true;break;case'D':case'M':case'Y':return null;case"'":if(e.charAt(i+1)==="'"){b+="'";}else{c=true;}break;default:b+=f;}}}return b;},_keyUp:function(a){var b=a.target;var c=F._getInst(b);if(!$.isEmptyObject(c)&&!c.ctrlKey&&c.lastVal!==c.elem.val()){try{var d=F._extractDates(c,c.elem.val());if(d.length>0){F.setDate(b,d,null,true);}}catch(a){}}return true;},_doMouseWheel:function(a,b){var c=(F.curInst&&F.curInst.elem[0])||$(a.target).closest('.'+F._getMarker())[0];if(F.isDisabled(c)){return;}var d=F._getInst(c);if(d.options.useMouseWheel){b=(b<0?-1:+1);F.changeMonth(c,-d.options[a.ctrlKey?'monthsToJump':'monthsToStep']*b);}a.preventDefault();},clear:function(a){var b=this._getInst(a);if(!$.isEmptyObject(b)){b.selectedDates=[];this.hide(a);var c=b.get('defaultDate');if(b.options.selectDefaultDate&&c){this.setDate(a,F.newDate(c||F.today()));}else{this._updateInput(a);}}},getDate:function(a){var b=this._getInst(a);return(!$.isEmptyObject(b)?b.selectedDates:[]);},setDate:function(a,b,c,d,e){var f=this._getInst(a);if(!$.isEmptyObject(f)){if(!$.isArray(b)){b=[b];if(c){b.push(c);}}var g=f.get('minDate');var h=f.get('maxDate');var k=f.selectedDates[0];f.selectedDates=[];for(var i=0;i<b.length;i++){var l=F.determineDate(b[i],null,k,f.options.dateFormat,f.getConfig());if(l){if((!g||l.getTime()>=g.getTime())&&(!h||l.getTime()<=h.getTime())){var m=false;for(var j=0;j<f.selectedDates.length;j++){if(f.selectedDates[j].getTime()===l.getTime()){m=true;break;}}if(!m){f.selectedDates.push(l);}}}}f.selectedDates.splice(f.options.multiSelect||(f.options.rangeSelect?2:1),f.selectedDates.length);if(f.options.rangeSelect){switch(f.selectedDates.length){case 1:f.selectedDates[1]=f.selectedDates[0];break;case 2:f.selectedDates[1]=(f.selectedDates[0].getTime()>f.selectedDates[1].getTime()?f.selectedDates[0]:f.selectedDates[1]);break;}f.pickingRange=false;}f.prevDate=(f.drawDate?F.newDate(f.drawDate):null);f.drawDate=this._checkMinMax(F.newDate(f.selectedDates[0]||f.get('defaultDate')||F.today()),f);if(!e){this._update(a);this._updateInput(a,d);}}},isSelectable:function(a,b){var c=this._getInst(a);if($.isEmptyObject(c)){return false;}b=F.determineDate(b,c.selectedDates[0]||this.today(),null,c.options.dateFormat,c.getConfig());return this._isSelectable(a,b,c.options.onDate,c.get('minDate'),c.get('maxDate'));},_isSelectable:function(a,b,c,d,e){var f=(typeof c==='boolean'?{selectable:c}:(!$.isFunction(c)?{}:c.apply(a,[b,true])));return(f.selectable!==false)&&(!d||b.getTime()>=d.getTime())&&(!e||b.getTime()<=e.getTime());},performAction:function(a,b){var c=this._getInst(a);if(!$.isEmptyObject(c)&&!this.isDisabled(a)){var d=c.options.commands;if(d[b]&&d[b].enabled.apply(a,[c])){d[b].action.apply(a,[c]);}}},showMonth:function(a,b,c,d){var e=this._getInst(a);if(!$.isEmptyObject(e)&&(d!=null||(e.drawDate.getFullYear()!==b||e.drawDate.getMonth()+1!==c))){e.prevDate=F.newDate(e.drawDate);var f=this._checkMinMax((b!=null?F.newDate(b,c,1):F.today()),e);e.drawDate=F.newDate(f.getFullYear(),f.getMonth()+1,(d!=null?d:Math.min(e.drawDate.getDate(),F.daysInMonth(f.getFullYear(),f.getMonth()+1))));this._update(a);}},changeMonth:function(a,b){var c=this._getInst(a);if(!$.isEmptyObject(c)){var d=F.add(F.newDate(c.drawDate),b,'m');this.showMonth(a,d.getFullYear(),d.getMonth()+1);}},changeDay:function(a,b){var c=this._getInst(a);if(!$.isEmptyObject(c)){var d=F.add(F.newDate(c.drawDate),b,'d');this.showMonth(a,d.getFullYear(),d.getMonth()+1,d.getDate());}},_checkMinMax:function(a,b){var c=b.get('minDate');var d=b.get('maxDate');a=(c&&a.getTime()<c.getTime()?F.newDate(c):a);a=(d&&a.getTime()>d.getTime()?F.newDate(d):a);return a;},retrieveDate:function(a,b){var c=this._getInst(a);return($.isEmptyObject(c)?null:this._normaliseDate(new Date(parseInt(b.className.replace(/^.*dp(-?\d+).*$/,'$1'),10))));},selectDate:function(a,b){var c=this._getInst(a);if(!$.isEmptyObject(c)&&!this.isDisabled(a)){var d=this.retrieveDate(a,b);if(c.options.multiSelect){var e=false;for(var i=0;i<c.selectedDates.length;i++){if(d.getTime()===c.selectedDates[i].getTime()){c.selectedDates.splice(i,1);e=true;break;}}if(!e&&c.selectedDates.length<c.options.multiSelect){c.selectedDates.push(d);}}else if(c.options.rangeSelect){if(c.pickingRange){c.selectedDates[1]=d;}else{c.selectedDates=[d,d];}c.pickingRange=!c.pickingRange;}else{c.selectedDates=[d];}c.prevDate=F.newDate(d);this._updateInput(a);if(c.inline||c.pickingRange||c.selectedDates.length<(c.options.multiSelect||(c.options.rangeSelect?2:1))){this._update(a);}else{this.hide(a);}}},_generateContent:function(h,i){var j=i.options.monthsToShow;j=($.isArray(j)?j:[1,j]);i.drawDate=this._checkMinMax(i.drawDate||i.get('defaultDate')||F.today(),i);var k=F._applyMonthsOffset(F.newDate(i.drawDate),i);var l='';for(var m=0;m<j[0];m++){var n='';for(var o=0;o<j[1];o++){n+=this._generateMonth(h,i,k.getFullYear(),k.getMonth()+1,i.options.renderer,(m===0&&o===0));F.add(k,1,'m');}l+=this._prepare(i.options.renderer.monthRow,i).replace(/\{months\}/,n);}var p=this._prepare(i.options.renderer.picker,i).replace(/\{months\}/,l).replace(/\{weekHeader\}/g,this._generateDayHeaders(i,i.options.renderer));var q=function(a,b,c,d,e){if(p.indexOf('{'+a+':'+d+'}')===-1){return;}var f=i.options.commands[d];var g=(i.options.commandsAsDateFormat?f.date.apply(h,[i]):null);p=p.replace(new RegExp('\\{'+a+':'+d+'\\}','g'),'<'+b+(f.status?' title="'+i.options[f.status]+'"':'')+' class="'+i.options.renderer.commandClass+' '+i.options.renderer.commandClass+'-'+d+' '+e+(f.enabled(i)?'':' '+i.options.renderer.disabledClass)+'">'+(g?F.formatDate(i.options[f.text],g,i.getConfig()):i.options[f.text])+'</'+c+'>');};for(var r in i.options.commands){q('button','button type="button"','button',r,i.options.renderer.commandButtonClass);q('link','a href="javascript:void(0)"','a',r,i.options.renderer.commandLinkClass);}p=$(p);if(j[1]>1){var s=0;$(i.options.renderer.monthSelector,p).each(function(){var a=++s%j[1];$(this).addClass(a===1?'first':(a===0?'last':''));});}var t=this;p.find(i.options.renderer.daySelector+' a').hover(function(){$(this).addClass(i.options.renderer.highlightedClass);},function(){(i.inline?$(this).closest('.'+t._getMarker()):i.div).find(i.options.renderer.daySelector+' a').removeClass(i.options.renderer.highlightedClass);}).click(function(){t.selectDate(h,this);}).end().find('select.'+this._monthYearClass+':not(.'+this._anyYearClass+')').change(function(){var a=$(this).val().split('/');t.showMonth(h,parseInt(a[1],10),parseInt(a[0],10));}).end().find('select.'+this._anyYearClass).click(function(){$(this).css('visibility','hidden').next('input').css({left:this.offsetLeft,top:this.offsetTop,width:this.offsetWidth,height:this.offsetHeight}).show().focus();}).end().find('input.'+t._monthYearClass).change(function(){try{var a=parseInt($(this).val(),10);a=(isNaN(a)?i.drawDate.getFullYear():a);t.showMonth(h,a,i.drawDate.getMonth()+1,i.drawDate.getDate());}catch(e){alert(e);}}).keydown(function(a){if(a.keyCode===13){$(a.elem).change();}else if(a.keyCode===27){$(a.elem).hide().prev('select').css('visibility','visible');i.elem.focus();}});p.find('.'+i.options.renderer.commandClass).click(function(){if(!$(this).hasClass(i.options.renderer.disabledClass)){var a=this.className.replace(new RegExp('^.*'+i.options.renderer.commandClass+'-([^ ]+).*$'),'$1');F.performAction(h,a);}});if(i.options.isRTL){p.addClass(i.options.renderer.rtlClass);}if(j[0]*j[1]>1){p.addClass(i.options.renderer.multiClass);}if(i.options.pickerClass){p.addClass(i.options.pickerClass);}$('body').append(p);var u=0;p.find(i.options.renderer.monthSelector).each(function(){u+=$(this).outerWidth();});p.width(u/j[0]);if($.isFunction(i.options.onShow)){i.options.onShow.apply(h,[p,i]);}return p;},_generateMonth:function(a,b,c,d,e,f){var g=F.daysInMonth(c,d);var h=b.options.monthsToShow;h=($.isArray(h)?h:[1,h]);var j=b.options.fixedWeeks||(h[0]*h[1]>1);var k=b.options.firstDay;var l=(F.newDate(c,d,1).getDay()-k+7)%7;var m=(j?6:Math.ceil((l+g)/7));var n=b.options.selectOtherMonths&&b.options.showOtherMonths;var o=(b.pickingRange?b.selectedDates[0]:b.get('minDate'));var p=b.get('maxDate');var q=e.week.indexOf('{weekOfYear}')>-1;var r=F.today();var s=F.newDate(c,d,1);F.add(s,-l-(j&&(s.getDay()===k)?7:0),'d');var t=s.getTime();var u='';for(var v=0;v<m;v++){var w=(!q?'':'<span class="dp'+t+'">'+($.isFunction(b.options.calculateWeek)?b.options.calculateWeek(s):0)+'</span>');var x='';for(var y=0;y<7;y++){var z=false;if(b.options.rangeSelect&&b.selectedDates.length>0){z=(s.getTime()>=b.selectedDates[0]&&s.getTime()<=b.selectedDates[1]);}else{for(var i=0;i<b.selectedDates.length;i++){if(b.selectedDates[i].getTime()===s.getTime()){z=true;break;}}}var A=(!$.isFunction(b.options.onDate)?{}:b.options.onDate.apply(a,[s,s.getMonth()+1===d]));var B=(n||s.getMonth()+1===d)&&this._isSelectable(a,s,A.selectable,o,p);x+=this._prepare(e.day,b).replace(/\{day\}/g,(B?'<a href="javascript:void(0)"':'<span')+' class="dp'+t+' '+(A.dateClass||'')+(z&&(n||s.getMonth()+1===d)?' '+e.selectedClass:'')+(B?' '+e.defaultClass:'')+((s.getDay()||7)<6?'':' '+e.weekendClass)+(s.getMonth()+1===d?'':' '+e.otherMonthClass)+(s.getTime()===r.getTime()&&(s.getMonth()+1)===d?' '+e.todayClass:'')+(s.getTime()===b.drawDate.getTime()&&(s.getMonth()+1)===d?' '+e.highlightedClass:'')+'"'+(A.title||(b.options.dayStatus&&B)?' title="'+(A.title||F.formatDate(b.options.dayStatus,s,b.getConfig()))+'"':'')+'>'+(b.options.showOtherMonths||(s.getMonth()+1)===d?A.content||s.getDate():'&nbsp;')+(B?'</a>':'</span>'));F.add(s,1,'d');t=s.getTime();}u+=this._prepare(e.week,b).replace(/\{days\}/g,x).replace(/\{weekOfYear\}/g,w);}var C=this._prepare(e.month,b).match(/\{monthHeader(:[^\}]+)?\}/);C=(C[0].length<=13?'MM yyyy':C[0].substring(13,C[0].length-1));C=(f?this._generateMonthSelection(b,c,d,o,p,C,e):F.formatDate(C,F.newDate(c,d,1),b.getConfig()));var D=this._prepare(e.weekHeader,b).replace(/\{days\}/g,this._generateDayHeaders(b,e));return this._prepare(e.month,b).replace(/\{monthHeader(:[^\}]+)?\}/g,C).replace(/\{weekHeader\}/g,D).replace(/\{weeks\}/g,u);},_generateDayHeaders:function(a,b){var c='';for(var d=0;d<7;d++){var e=(d+a.options.firstDay)%7;c+=this._prepare(b.dayHeader,a).replace(/\{day\}/g,'<span class="'+this._curDoWClass+e+'" title="'+a.options.dayNames[e]+'">'+a.options.dayNamesMin[e]+'</span>');}return c;},_generateMonthSelection:function(a,b,c,d,e,f){if(!a.options.changeMonth){return F.formatDate(f,F.newDate(b,c,1),a.getConfig());}var g=a.options['monthNames'+(f.match(/mm/i)?'':'Short')];var h=f.replace(/m+/i,'\\x2E').replace(/y+/i,'\\x2F');var i='<select class="'+this._monthYearClass+'" title="'+a.options.monthStatus+'">';for(var m=1;m<=12;m++){if((!d||F.newDate(b,m,F.daysInMonth(b,m)).getTime()>=d.getTime())&&(!e||F.newDate(b,m,1).getTime()<=e.getTime())){i+='<option value="'+m+'/'+b+'"'+(c===m?' selected="selected"':'')+'>'+g[m-1]+'</option>';}}i+='</select>';h=h.replace(/\\x2E/,i);var j=a.options.yearRange;if(j==='any'){i='<select class="'+this._monthYearClass+' '+this._anyYearClass+'" title="'+a.options.yearStatus+'">'+'<option>'+b+'</option></select>'+'<input class="'+this._monthYearClass+' '+this._curMonthClass+c+'" value="'+b+'">';}else{j=j.split(':');var k=F.today().getFullYear();var l=(j[0].match('c[+-].*')?b+parseInt(j[0].substring(1),10):((j[0].match('[+-].*')?k:0)+parseInt(j[0],10)));var n=(j[1].match('c[+-].*')?b+parseInt(j[1].substring(1),10):((j[1].match('[+-].*')?k:0)+parseInt(j[1],10)));i='<select class="'+this._monthYearClass+'" title="'+a.options.yearStatus+'">';l=F.add(F.newDate(l+1,1,1),-1,'d');n=F.newDate(n,1,1);var o=function(y){if(y!==0){i+='<option value="'+c+'/'+y+'"'+(b===y?' selected="selected"':'')+'>'+y+'</option>';}};if(l.getTime()<n.getTime()){l=(d&&d.getTime()>l.getTime()?d:l).getFullYear();n=(e&&e.getTime()<n.getTime()?e:n).getFullYear();for(var y=l;y<=n;y++){o(y);}}else{l=(e&&e.getTime()<l.getTime()?e:l).getFullYear();n=(d&&d.getTime()>n.getTime()?d:n).getFullYear();for(var y=l;y>=n;y--){o(y);}}i+='</select>';}h=h.replace(/\\x2F/,i);return h;},_prepare:function(e,f){var g=function(a,b){while(true){var c=e.indexOf('{'+a+':start}');if(c===-1){return;}var d=e.substring(c).indexOf('{'+a+':end}');if(d>-1){e=e.substring(0,c)+(b?e.substr(c+a.length+8,d-a.length-8):'')+e.substring(c+d+a.length+6);}}};g('inline',f.inline);g('popup',!f.inline);var h=/\{l10n:([^\}]+)\}/;var i=null;while(i=h.exec(e)){e=e.replace(i[0],f.options[i[1]]);}return e;}});var F=$.datepick;$(function(){$(document).on('mousedown.'+E,F._checkExternalClick).on('resize.'+E,function(){F.hide(F.curInst)})})})(jQuery);
\ Pas de fin de ligne à la fin du fichier
diff -urN cahier-de-prepa3.2.0/js/jquery.js cahier-de-prepa4.0.0/js/jquery.js
--- cahier-de-prepa3.2.0/js/jquery.js	2012-08-15 11:52:56.000000000 +0200
+++ cahier-de-prepa4.0.0/js/jquery.js	2014-05-01 19:59:34.000000000 +0200
@@ -1,18 +1,4 @@
-/*!
- * jQuery JavaScript Library v1.6.1
- * http://jquery.com/
- *
- * Copyright 2011, John Resig
- * Dual licensed under the MIT or GPL Version 2 licenses.
- * http://jquery.org/license
- *
- * Includes Sizzle.js
- * http://sizzlejs.com/
- * Copyright 2011, The Dojo Foundation
- * Released under the MIT, BSD, and GPL Licenses.
- *
- * Date: Thu May 12 15:04:36 2011 -0400
- */
-(function(a,b){function cy(a){return f.isWindow(a)?a:a.nodeType===9?a.defaultView||a.parentWindow:!1}function cv(a){if(!cj[a]){var b=f("<"+a+">").appendTo("body"),d=b.css("display");b.remove();if(d==="none"||d===""){ck||(ck=c.createElement("iframe"),ck.frameBorder=ck.width=ck.height=0),c.body.appendChild(ck);if(!cl||!ck.createElement)cl=(ck.contentWindow||ck.contentDocument).document,cl.write("<!doctype><html><body></body></html>");b=cl.createElement(a),cl.body.appendChild(b),d=f.css(b,"display"),c.body.removeChild(ck)}cj[a]=d}return cj[a]}function cu(a,b){var c={};f.each(cp.concat.apply([],cp.slice(0,b)),function(){c[this]=a});return c}function ct(){cq=b}function cs(){setTimeout(ct,0);return cq=f.now()}function ci(){try{return new a.ActiveXObject("Microsoft.XMLHTTP")}catch(b){}}function ch(){try{return new a.XMLHttpRequest}catch(b){}}function cb(a,c){a.dataFilter&&(c=a.dataFilter(c,a.dataType));var d=a.dataTypes,e={},g,h,i=d.length,j,k=d[0],l,m,n,o,p;for(g=1;g<i;g++){if(g===1)for(h in a.converters)typeof h=="string"&&(e[h.toLowerCase()]=a.converters[h]);l=k,k=d[g];if(k==="*")k=l;else if(l!=="*"&&l!==k){m=l+" "+k,n=e[m]||e["* "+k];if(!n){p=b;for(o in e){j=o.split(" ");if(j[0]===l||j[0]==="*"){p=e[j[1]+" "+k];if(p){o=e[o],o===!0?n=p:p===!0&&(n=o);break}}}}!n&&!p&&f.error("No conversion from "+m.replace(" "," to ")),n!==!0&&(c=n?n(c):p(o(c)))}}return c}function ca(a,c,d){var e=a.contents,f=a.dataTypes,g=a.responseFields,h,i,j,k;for(i in g)i in d&&(c[g[i]]=d[i]);while(f[0]==="*")f.shift(),h===b&&(h=a.mimeType||c.getResponseHeader("content-type"));if(h)for(i in e)if(e[i]&&e[i].test(h)){f.unshift(i);break}if(f[0]in d)j=f[0];else{for(i in d){if(!f[0]||a.converters[i+" "+f[0]]){j=i;break}k||(k=i)}j=j||k}if(j){j!==f[0]&&f.unshift(j);return d[j]}}function b_(a,b,c,d){if(f.isArray(b))f.each(b,function(b,e){c||bF.test(a)?d(a,e):b_(a+"["+(typeof e=="object"||f.isArray(e)?b:"")+"]",e,c,d)});else if(!c&&b!=null&&typeof b=="object")for(var e in b)b_(a+"["+e+"]",b[e],c,d);else d(a,b)}function b$(a,c,d,e,f,g){f=f||c.dataTypes[0],g=g||{},g[f]=!0;var h=a[f],i=0,j=h?h.length:0,k=a===bU,l;for(;i<j&&(k||!l);i++)l=h[i](c,d,e),typeof l=="string"&&(!k||g[l]?l=b:(c.dataTypes.unshift(l),l=b$(a,c,d,e,l,g)));(k||!l)&&!g["*"]&&(l=b$(a,c,d,e,"*",g));return l}function bZ(a){return function(b,c){typeof b!="string"&&(c=b,b="*");if(f.isFunction(c)){var d=b.toLowerCase().split(bQ),e=0,g=d.length,h,i,j;for(;e<g;e++)h=d[e],j=/^\+/.test(h),j&&(h=h.substr(1)||"*"),i=a[h]=a[h]||[],i[j?"unshift":"push"](c)}}}function bD(a,b,c){var d=b==="width"?bx:by,e=b==="width"?a.offsetWidth:a.offsetHeight;if(c==="border")return e;f.each(d,function(){c||(e-=parseFloat(f.css(a,"padding"+this))||0),c==="margin"?e+=parseFloat(f.css(a,"margin"+this))||0:e-=parseFloat(f.css(a,"border"+this+"Width"))||0});return e}function bn(a,b){b.src?f.ajax({url:b.src,async:!1,dataType:"script"}):f.globalEval((b.text||b.textContent||b.innerHTML||"").replace(bf,"/*$0*/")),b.parentNode&&b.parentNode.removeChild(b)}function bm(a){f.nodeName(a,"input")?bl(a):a.getElementsByTagName&&f.grep(a.getElementsByTagName("input"),bl)}function bl(a){if(a.type==="checkbox"||a.type==="radio")a.defaultChecked=a.checked}function bk(a){return"getElementsByTagName"in a?a.getElementsByTagName("*"):"querySelectorAll"in a?a.querySelectorAll("*"):[]}function bj(a,b){var c;if(b.nodeType===1){b.clearAttributes&&b.clearAttributes(),b.mergeAttributes&&b.mergeAttributes(a),c=b.nodeName.toLowerCase();if(c==="object")b.outerHTML=a.outerHTML;else if(c!=="input"||a.type!=="checkbox"&&a.type!=="radio"){if(c==="option")b.selected=a.defaultSelected;else if(c==="input"||c==="textarea")b.defaultValue=a.defaultValue}else a.checked&&(b.defaultChecked=b.checked=a.checked),b.value!==a.value&&(b.value=a.value);b.removeAttribute(f.expando)}}function bi(a,b){if(b.nodeType===1&&!!f.hasData(a)){var c=f.expando,d=f.data(a),e=f.data(b,d);if(d=d[c]){var g=d.events;e=e[c]=f.extend({},d);if(g){delete e.handle,e.events={};for(var h in g)for(var i=0,j=g[h].length;i<j;i++)f.event.add(b,h+(g[h][i].namespace?".":"")+g[h][i].namespace,g[h][i],g[h][i].data)}}}}function bh(a,b){return f.nodeName(a,"table")?a.getElementsByTagName("tbody")[0]||a.appendChild(a.ownerDocument.createElement("tbody")):a}function X(a,b,c){b=b||0;if(f.isFunction(b))return f.grep(a,function(a,d){var e=!!b.call(a,d,a);return e===c});if(b.nodeType)return f.grep(a,function(a,d){return a===b===c});if(typeof b=="string"){var d=f.grep(a,function(a){return a.nodeType===1});if(S.test(b))return f.filter(b,d,!c);b=f.filter(b,d)}return f.grep(a,function(a,d){return f.inArray(a,b)>=0===c})}function W(a){return!a||!a.parentNode||a.parentNode.nodeType===11}function O(a,b){return(a&&a!=="*"?a+".":"")+b.replace(A,"`").replace(B,"&")}function N(a){var b,c,d,e,g,h,i,j,k,l,m,n,o,p=[],q=[],r=f._data(this,"events");if(!(a.liveFired===this||!r||!r.live||a.target.disabled||a.button&&a.type==="click")){a.namespace&&(n=new RegExp("(^|\\.)"+a.namespace.split(".").join("\\.(?:.*\\.)?")+"(\\.|$)")),a.liveFired=this;var s=r.live.slice(0);for(i=0;i<s.length;i++)g=s[i],g.origType.replace(y,"")===a.type?q.push(g.selector):s.splice(i--,1);e=f(a.target).closest(q,a.currentTarget);for(j=0,k=e.length;j<k;j++){m=e[j];for(i=0;i<s.length;i++){g=s[i];if(m.selector===g.selector&&(!n||n.test(g.namespace))&&!m.elem.disabled){h=m.elem,d=null;if(g.preType==="mouseenter"||g.preType==="mouseleave")a.type=g.preType,d=f(a.relatedTarget).closest(g.selector)[0],d&&f.contains(h,d)&&(d=h);(!d||d!==h)&&p.push({elem:h,handleObj:g,level:m.level})}}}for(j=0,k=p.length;j<k;j++){e=p[j];if(c&&e.level>c)break;a.currentTarget=e.elem,a.data=e.handleObj.data,a.handleObj=e.handleObj,o=e.handleObj.origHandler.apply(e.elem,arguments);if(o===!1||a.isPropagationStopped()){c=e.level,o===!1&&(b=!1);if(a.isImmediatePropagationStopped())break}}return b}}function L(a,c,d){var e=f.extend({},d[0]);e.type=a,e.originalEvent={},e.liveFired=b,f.event.handle.call(c,e),e.isDefaultPrevented()&&d[0].preventDefault()}function F(){return!0}function E(){return!1}function m(a,c,d){var e=c+"defer",g=c+"queue",h=c+"mark",i=f.data(a,e,b,!0);i&&(d==="queue"||!f.data(a,g,b,!0))&&(d==="mark"||!f.data(a,h,b,!0))&&setTimeout(function(){!f.data(a,g,b,!0)&&!f.data(a,h,b,!0)&&(f.removeData(a,e,!0),i.resolve())},0)}function l(a){for(var b in a)if(b!=="toJSON")return!1;return!0}function k(a,c,d){if(d===b&&a.nodeType===1){var e="data-"+c.replace(j,"$1-$2").toLowerCase();d=a.getAttribute(e);if(typeof d=="string"){try{d=d==="true"?!0:d==="false"?!1:d==="null"?null:f.isNaN(d)?i.test(d)?f.parseJSON(d):d:parseFloat(d)}catch(g){}f.data(a,c,d)}else d=b}return d}var c=a.document,d=a.navigator,e=a.location,f=function(){function H(){if(!e.isReady){try{c.documentElement.doScroll("left")}catch(a){setTimeout(H,1);return}e.ready()}}var e=function(a,b){return new e.fn.init(a,b,h)},f=a.jQuery,g=a.$,h,i=/^(?:[^<]*(<[\w\W]+>)[^>]*$|#([\w\-]*)$)/,j=/\S/,k=/^\s+/,l=/\s+$/,m=/\d/,n=/^<(\w+)\s*\/?>(?:<\/\1>)?$/,o=/^[\],:{}\s]*$/,p=/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,q=/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,r=/(?:^|:|,)(?:\s*\[)+/g,s=/(webkit)[ \/]([\w.]+)/,t=/(opera)(?:.*version)?[ \/]([\w.]+)/,u=/(msie) ([\w.]+)/,v=/(mozilla)(?:.*? rv:([\w.]+))?/,w=d.userAgent,x,y,z,A=Object.prototype.toString,B=Object.prototype.hasOwnProperty,C=Array.prototype.push,D=Array.prototype.slice,E=String.prototype.trim,F=Array.prototype.indexOf,G={};e.fn=e.prototype={constructor:e,init:function(a,d,f){var g,h,j,k;if(!a)return this;if(a.nodeType){this.context=this[0]=a,this.length=1;return this}if(a==="body"&&!d&&c.body){this.context=c,this[0]=c.body,this.selector=a,this.length=1;return this}if(typeof a=="string"){a.charAt(0)!=="<"||a.charAt(a.length-1)!==">"||a.length<3?g=i.exec(a):g=[null,a,null];if(g&&(g[1]||!d)){if(g[1]){d=d instanceof e?d[0]:d,k=d?d.ownerDocument||d:c,j=n.exec(a),j?e.isPlainObject(d)?(a=[c.createElement(j[1])],e.fn.attr.call(a,d,!0)):a=[k.createElement(j[1])]:(j=e.buildFragment([g[1]],[k]),a=(j.cacheable?e.clone(j.fragment):j.fragment).childNodes);return e.merge(this,a)}h=c.getElementById(g[2]);if(h&&h.parentNode){if(h.id!==g[2])return f.find(a);this.length=1,this[0]=h}this.context=c,this.selector=a;return this}return!d||d.jquery?(d||f).find(a):this.constructor(d).find(a)}if(e.isFunction(a))return f.ready(a);a.selector!==b&&(this.selector=a.selector,this.context=a.context);return e.makeArray(a,this)},selector:"",jquery:"1.6.1",length:0,size:function(){return this.length},toArray:function(){return D.call(this,0)},get:function(a){return a==null?this.toArray():a<0?this[this.length+a]:this[a]},pushStack:function(a,b,c){var d=this.constructor();e.isArray(a)?C.apply(d,a):e.merge(d,a),d.prevObject=this,d.context=this.context,b==="find"?d.selector=this.selector+(this.selector?" ":"")+c:b&&(d.selector=this.selector+"."+b+"("+c+")");return d},each:function(a,b){return e.each(this,a,b)},ready:function(a){e.bindReady(),y.done(a);return this},eq:function(a){return a===-1?this.slice(a):this.slice(a,+a+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(D.apply(this,arguments),"slice",D.call(arguments).join(","))},map:function(a){return this.pushStack(e.map(this,function(b,c){return a.call(b,c,b)}))},end:function(){return this.prevObject||this.constructor(null)},push:C,sort:[].sort,splice:[].splice},e.fn.init.prototype=e.fn,e.extend=e.fn.extend=function(){var a,c,d,f,g,h,i=arguments[0]||{},j=1,k=arguments.length,l=!1;typeof i=="boolean"&&(l=i,i=arguments[1]||{},j=2),typeof i!="object"&&!e.isFunction(i)&&(i={}),k===j&&(i=this,--j);for(;j<k;j++)if((a=arguments[j])!=null)for(c in a){d=i[c],f=a[c];if(i===f)continue;l&&f&&(e.isPlainObject(f)||(g=e.isArray(f)))?(g?(g=!1,h=d&&e.isArray(d)?d:[]):h=d&&e.isPlainObject(d)?d:{},i[c]=e.extend(l,h,f)):f!==b&&(i[c]=f)}return i},e.extend({noConflict:function(b){a.$===e&&(a.$=g),b&&a.jQuery===e&&(a.jQuery=f);return e},isReady:!1,readyWait:1,holdReady:function(a){a?e.readyWait++:e.ready(!0)},ready:function(a){if(a===!0&&!--e.readyWait||a!==!0&&!e.isReady){if(!c.body)return setTimeout(e.ready,1);e.isReady=!0;if(a!==!0&&--e.readyWait>0)return;y.resolveWith(c,[e]),e.fn.trigger&&e(c).trigger("ready").unbind("ready")}},bindReady:function(){if(!y){y=e._Deferred();if(c.readyState==="complete")return setTimeout(e.ready,1);if(c.addEventListener)c.addEventListener("DOMContentLoaded",z,!1),a.addEventListener("load",e.ready,!1);else if(c.attachEvent){c.attachEvent("onreadystatechange",z),a.attachEvent("onload",e.ready);var b=!1;try{b=a.frameElement==null}catch(d){}c.documentElement.doScroll&&b&&H()}}},isFunction:function(a){return e.type(a)==="function"},isArray:Array.isArray||function(a){return e.type(a)==="array"},isWindow:function(a){return a&&typeof a=="object"&&"setInterval"in a},isNaN:function(a){return a==null||!m.test(a)||isNaN(a)},type:function(a){return a==null?String(a):G[A.call(a)]||"object"},isPlainObject:function(a){if(!a||e.type(a)!=="object"||a.nodeType||e.isWindow(a))return!1;if(a.constructor&&!B.call(a,"constructor")&&!B.call(a.constructor.prototype,"isPrototypeOf"))return!1;var c;for(c in a);return c===b||B.call(a,c)},isEmptyObject:function(a){for(var b in a)return!1;return!0},error:function(a){throw a},parseJSON:function(b){if(typeof b!="string"||!b)return null;b=e.trim(b);if(a.JSON&&a.JSON.parse)return a.JSON.parse(b);if(o.test(b.replace(p,"@").replace(q,"]").replace(r,"")))return(new Function("return "+b))();e.error("Invalid JSON: "+b)},parseXML:function(b,c,d){a.DOMParser?(d=new DOMParser,c=d.parseFromString(b,"text/xml")):(c=new ActiveXObject("Microsoft.XMLDOM"),c.async="false",c.loadXML(b)),d=c.documentElement,(!d||!d.nodeName||d.nodeName==="parsererror")&&e.error("Invalid XML: "+b);return c},noop:function(){},globalEval:function(b){b&&j.test(b)&&(a.execScript||function(b){a.eval.call(a,b)})(b)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toUpperCase()===b.toUpperCase()},each:function(a,c,d){var f,g=0,h=a.length,i=h===b||e.isFunction(a);if(d){if(i){for(f in a)if(c.apply(a[f],d)===!1)break}else for(;g<h;)if(c.apply(a[g++],d)===!1)break}else if(i){for(f in a)if(c.call(a[f],f,a[f])===!1)break}else for(;g<h;)if(c.call(a[g],g,a[g++])===!1)break;return a},trim:E?function(a){return a==null?"":E.call(a)}:function(a){return a==null?"":(a+"").replace(k,"").replace(l,"")},makeArray:function(a,b){var c=b||[];if(a!=null){var d=e.type(a);a.length==null||d==="string"||d==="function"||d==="regexp"||e.isWindow(a)?C.call(c,a):e.merge(c,a)}return c},inArray:function(a,b){if(F)return F.call(b,a);for(var c=0,d=b.length;c<d;c++)if(b[c]===a)return c;return-1},merge:function(a,c){var d=a.length,e=0;if(typeof c.length=="number")for(var f=c.length;e<f;e++)a[d++]=c[e];else while(c[e]!==b)a[d++]=c[e++];a.length=d;return a},grep:function(a,b,c){var d=[],e;c=!!c;for(var f=0,g=a.length;f<g;f++)e=!!b(a[f],f),c!==e&&d.push(a[f]);return d},map:function(a,c,d){var f,g,h=[],i=0,j=a.length,k=a instanceof e||j!==b&&typeof j=="number"&&(j>0&&a[0]&&a[j-1]||j===0||e.isArray(a));if(k)for(;i<j;i++)f=c(a[i],i,d),f!=null&&(h[h.length]=f);else for(g in a)f=c(a[g],g,d),f!=null&&(h[h.length]=f);return h.concat.apply([],h)},guid:1,proxy:function(a,c){if(typeof c=="string"){var d=a[c];c=a,a=d}if(!e.isFunction(a))return b;var f=D.call(arguments,2),g=function(){return a.apply(c,f.concat(D.call(arguments)))};g.guid=a.guid=a.guid||g.guid||e.guid++;return g},access:function(a,c,d,f,g,h){var i=a.length;if(typeof c=="object"){for(var j in c)e.access(a,j,c[j],f,g,d);return a}if(d!==b){f=!h&&f&&e.isFunction(d);for(var k=0;k<i;k++)g(a[k],c,f?d.call(a[k],k,g(a[k],c)):d,h);return a}return i?g(a[0],c):b},now:function(){return(new Date).getTime()},uaMatch:function(a){a=a.toLowerCase();var b=s.exec(a)||t.exec(a)||u.exec(a)||a.indexOf("compatible")<0&&v.exec(a)||[];return{browser:b[1]||"",version:b[2]||"0"}},sub:function(){function a(b,c){return new a.fn.init(b,c)}e.extend(!0,a,this),a.superclass=this,a.fn=a.prototype=this(),a.fn.constructor=a,a.sub=this.sub,a.fn.init=function(d,f){f&&f instanceof e&&!(f instanceof a)&&(f=a(f));return e.fn.init.call(this,d,f,b)},a.fn.init.prototype=a.fn;var b=a(c);return a},browser:{}}),e.each("Boolean Number String Function Array Date RegExp Object".split(" "),function(a,b){G["[object "+b+"]"]=b.toLowerCase()}),x=e.uaMatch(w),x.browser&&(e.browser[x.browser]=!0,e.browser.version=x.version),e.browser.webkit&&(e.browser.safari=!0),j.test(" ")&&(k=/^[\s\xA0]+/,l=/[\s\xA0]+$/),h=e(c),c.addEventListener?z=function(){c.removeEventListener("DOMContentLoaded",z,!1),e.ready()}:c.attachEvent&&(z=function(){c.readyState==="complete"&&(c.detachEvent("onreadystatechange",z),e.ready())});return e}(),g="done fail isResolved isRejected promise then always pipe".split(" "),h=[].slice;f.extend({_Deferred:function(){var a=[],b,c,d,e={done:function(){if(!d){var c=arguments,g,h,i,j,k;b&&(k=b,b=0);for(g=0,h=c.length;g<h;g++)i=c[g],j=f.type(i),j==="array"?e.done.apply(e,i):j==="function"&&a.push(i);k&&e.resolveWith(k[0],k[1])}return this},resolveWith:function(e,f){if(!d&&!b&&!c){f=f||[],c=1;try{while(a[0])a.shift().apply(e,f)}finally{b=[e,f],c=0}}return this},resolve:function(){e.resolveWith(this,arguments);return this},isResolved:function(){return!!c||!!b},cancel:function(){d=1,a=[];return this}};return e},Deferred:function(a){var b=f._Deferred(),c=f._Deferred(),d;f.extend(b,{then:function(a,c){b.done(a).fail(c);return this},always:function(){return b.done.apply(b,arguments).fail.apply(this,arguments)},fail:c.done,rejectWith:c.resolveWith,reject:c.resolve,isRejected:c.isResolved,pipe:function(a,c){return f.Deferred(function(d){f.each({done:[a,"resolve"],fail:[c,"reject"]},function(a,c){var e=c[0],g=c[1],h;f.isFunction(e)?b[a](function(){h=e.apply(this,arguments),h&&f.isFunction(h.promise)?h.promise().then(d.resolve,d.reject):d[g](h)}):b[a](d[g])})}).promise()},promise:function(a){if(a==null){if(d)return d;d=a={}}var c=g.length;while(c--)a[g[c]]=b[g[c]];return a}}),b.done(c.cancel).fail(b.cancel),delete b.cancel,a&&a.call(b,b);return b},when:function(a){function i(a){return function(c){b[a]=arguments.length>1?h.call(arguments,0):c,--e||g.resolveWith(g,h.call(b,0))}}var b=arguments,c=0,d=b.length,e=d,g=d<=1&&a&&f.isFunction(a.promise)?a:f.Deferred();if(d>1){for(;c<d;c++)b[c]&&f.isFunction(b[c].promise)?b[c].promise().then(i(c),g.reject):--e;e||g.resolveWith(g,b)}else g!==a&&g.resolveWith(g,d?[a]:[]);return g.promise()}}),f.support=function(){var a=c.createElement("div"),b=c.documentElement,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r;a.setAttribute("className","t"),a.innerHTML="   <link/><table></table><a href='/a' style='top:1px;float:left;opacity:.55;'>a</a><input type='checkbox'/>",d=a.getElementsByTagName("*"),e=a.getElementsByTagName("a")[0];if(!d||!d.length||!e)return{};f=c.createElement("select"),g=f.appendChild(c.createElement("option")),h=a.getElementsByTagName("input")[0],j={leadingWhitespace:a.firstChild.nodeType===3,tbody:!a.getElementsByTagName("tbody").length,htmlSerialize:!!a.getElementsByTagName("link").length,style:/top/.test(e.getAttribute("style")),hrefNormalized:e.getAttribute("href")==="/a",opacity:/^0.55$/.test(e.style.opacity),cssFloat:!!e.style.cssFloat,checkOn:h.value==="on",optSelected:g.selected,getSetAttribute:a.className!=="t",submitBubbles:!0,changeBubbles:!0,focusinBubbles:!1,deleteExpando:!0,noCloneEvent:!0,inlineBlockNeedsLayout:!1,shrinkWrapBlocks:!1,reliableMarginRight:!0},h.checked=!0,j.noCloneChecked=h.cloneNode(!0).checked,f.disabled=!0,j.optDisabled=!g.disabled;try{delete a.test}catch(s){j.deleteExpando=!1}!a.addEventListener&&a.attachEvent&&a.fireEvent&&(a.attachEvent("onclick",function b(){j.noCloneEvent=!1,a.detachEvent("onclick",b)}),a.cloneNode(!0).fireEvent("onclick")),h=c.createElement("input"),h.value="t",h.setAttribute("type","radio"),j.radioValue=h.value==="t",h.setAttribute("checked","checked"),a.appendChild(h),k=c.createDocumentFragment(),k.appendChild(a.firstChild),j.checkClone=k.cloneNode(!0).cloneNode(!0).lastChild.checked,a.innerHTML="",a.style.width=a.style.paddingLeft="1px",l=c.createElement("body"),m={visibility:"hidden",width:0,height:0,border:0,margin:0,background:"none"};for(q in m)l.style[q]=m[q];l.appendChild(a),b.insertBefore(l,b.firstChild),j.appendChecked=h.checked,j.boxModel=a.offsetWidth===2,"zoom"in a.style&&(a.style.display="inline",a.style.zoom=1,j.inlineBlockNeedsLayout=a.offsetWidth===2,a.style.display="",a.innerHTML="<div style='width:4px;'></div>",j.shrinkWrapBlocks=a.offsetWidth!==2),a.innerHTML="<table><tr><td style='padding:0;border:0;display:none'></td><td>t</td></tr></table>",n=a.getElementsByTagName("td"),r=n[0].offsetHeight===0,n[0].style.display="",n[1].style.display="none",j.reliableHiddenOffsets=r&&n[0].offsetHeight===0,a.innerHTML="",c.defaultView&&c.defaultView.getComputedStyle&&(i=c.createElement("div"),i.style.width="0",i.style.marginRight="0",a.appendChild(i),j.reliableMarginRight=(parseInt((c.defaultView.getComputedStyle(i,null)||{marginRight:0}).marginRight,10)||0)===0),l.innerHTML="",b.removeChild(l);if(a.attachEvent)for(q in{submit:1,change:1,focusin:1})p="on"+q,r=p in a,r||(a.setAttribute(p,"return;"),r=typeof a[p]=="function"),j[q+"Bubbles"]=r;return j}(),f.boxModel=f.support.boxModel;var i=/^(?:\{.*\}|\[.*\])$/,j=/([a-z])([A-Z])/g;f.extend({cache:{},uuid:0,expando:"jQuery"+(f.fn.jquery+Math.random()).replace(/\D/g,""),noData:{embed:!0,object:"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",applet:!0},hasData:function(a){a=a.nodeType?f.cache[a[f.expando]]:a[f.expando];return!!a&&!l(a)},data:function(a,c,d,e){if(!!f.acceptData(a)){var g=f.expando,h=typeof c=="string",i,j=a.nodeType,k=j?f.cache:a,l=j?a[f.expando]:a[f.expando]&&f.expando;if((!l||e&&l&&!k[l][g])&&h&&d===b)return;l||(j?a[f.expando]=l=++f.uuid:l=f.expando),k[l]||(k[l]={},j||(k[l].toJSON=f.noop));if(typeof c=="object"||typeof c=="function")e?k[l][g]=f.extend(k[l][g],c):k[l]=f.extend(k[l],c);i=k[l],e&&(i[g]||(i[g]={}),i=i[g]),d!==b&&(i[f.camelCase(c)]=d);if(c==="events"&&!i[c])return i[g]&&i[g].events;return h?i[f.camelCase(c)]:i}},removeData:function(b,c,d){if(!!f.acceptData(b)){var e=f.expando,g=b.nodeType,h=g?f.cache:b,i=g?b[f.expando]:f.expando;if(!h[i])return;if(c){var j=d?h[i][e]:h[i];if(j){delete j[c];if(!l(j))return}}if(d){delete h[i][e];if(!l(h[i]))return}var k=h[i][e];f.support.deleteExpando||h!=a?delete h[i]:h[i]=null,k?(h[i]={},g||(h[i].toJSON=f.noop),h[i][e]=k):g&&(f.support.deleteExpando?delete b[f.expando]:b.removeAttribute?b.removeAttribute(f.expando):b[f.expando]=null)}},_data:function(a,b,c){return f.data(a,b,c,!0)},acceptData:function(a){if(a.nodeName){var b=f.noData[a.nodeName.toLowerCase()];if(b)return b!==!0&&a.getAttribute("classid")===b}return!0}}),f.fn.extend({data:function(a,c){var d=null;if(typeof a=="undefined"){if(this.length){d=f.data(this[0]);if(this[0].nodeType===1){var e=this[0].attributes,g;for(var h=0,i=e.length;h<i;h++)g=e[h].name,g.indexOf("data-")===0&&(g=f.camelCase(g.substring(5)),k(this[0],g,d[g]))}}return d}if(typeof a=="object")return this.each(function(){f.data(this,a)});var j=a.split(".");j[1]=j[1]?"."+j[1]:"";if(c===b){d=this.triggerHandler("getData"+j[1]+"!",[j[0]]),d===b&&this.length&&(d=f.data(this[0],a),d=k(this[0],a,d));return d===b&&j[1]?this.data(j[0]):d}return this.each(function(){var b=f(this),d=[j[0],c];b.triggerHandler("setData"+j[1]+"!",d),f.data(this,a,c),b.triggerHandler("changeData"+j[1]+"!",d)})},removeData:function(a){return this.each(function(){f.removeData(this,a)})}}),f.extend({_mark:function(a,c){a&&(c=(c||"fx")+"mark",f.data(a,c,(f.data(a,c,b,!0)||0)+1,!0))},_unmark:function(a,c,d){a!==!0&&(d=c,c=a,a=!1);if(c){d=d||"fx";var e=d+"mark",g=a?0:(f.data(c,e,b,!0)||1)-1;g?f.data(c,e,g,!0):(f.removeData(c,e,!0),m(c,d,"mark"))}},queue:function(a,c,d){if(a){c=(c||"fx")+"queue";var e=f.data(a,c,b,!0);d&&(!e||f.isArray(d)?e=f.data(a,c,f.makeArray(d),!0):e.push(d));return e||[]}},dequeue:function(a,b){b=b||"fx";var c=f.queue(a,b),d=c.shift(),e;d==="inprogress"&&(d=c.shift()),d&&(b==="fx"&&c.unshift("inprogress"),d.call(a,function(){f.dequeue(a,b)})),c.length||(f.removeData(a,b+"queue",!0),m(a,b,"queue"))}}),f.fn.extend({queue:function(a,c){typeof a!="string"&&(c=a,a="fx");if(c===b)return f.queue(this[0],a);return this.each(function(){var b=f.queue(this,a,c);a==="fx"&&b[0]!=="inprogress"&&f.dequeue(this,a)})},dequeue:function(a){return this.each(function(){f.dequeue(this,a)})},delay:function(a,b){a=f.fx?f.fx.speeds[a]||a:a,b=b||"fx";return this.queue(b,function(){var c=this;setTimeout(function(){f.dequeue(c,b)},a)})},clearQueue:function(a){return this.queue(a||"fx",[])},promise:function(a,c){function m(){--h||d.resolveWith(e,[e])}typeof a!="string"&&(c=a,a=b),a=a||"fx";var d=f.Deferred(),e=this,g=e.length,h=1,i=a+"defer",j=a+"queue",k=a+"mark",l;while(g--)if(l=f.data(e[g],i,b,!0)||(f.data(e[g],j,b,!0)||f.data(e[g],k,b,!0))&&f.data(e[g],i,f._Deferred(),!0))h++,l.done(m);m();return d.promise()}});var n=/[\n\t\r]/g,o=/\s+/,p=/\r/g,q=/^(?:button|input)$/i,r=/^(?:button|input|object|select|textarea)$/i,s=/^a(?:rea)?$/i,t=/^(?:autofocus|autoplay|async|checked|controls|defer|disabled|hidden|loop|multiple|open|readonly|required|scoped|selected)$/i,u=/\:/,v,w;f.fn.extend({attr:function(a,b){return f.access(this,a,b,!0,f.attr)},removeAttr:function(a){return this.each(function(){f.removeAttr(this,a)})},prop:function(a,b){return f.access(this,a,b,!0,f.prop)},removeProp:function(a){a=f.propFix[a]||a;return this.each(function(){try{this[a]=b,delete this[a]}catch(c){}})},addClass:function(a){if(f.isFunction(a))return this.each(function(b){var c=f(this);c.addClass(a.call(this,b,c.attr("class")||""))});if(a&&typeof a=="string"){var b=(a||"").split(o);for(var c=0,d=this.length;c<d;c++){var e=this[c];if(e.nodeType===1)if(!e.className)e.className=a;else{var g=" "+e.className+" ",h=e.className;for(var i=0,j=b.length;i<j;i++)g.indexOf(" "+b[i]+" ")<0&&(h+=" "+b[i]);e.className=f.trim(h)}}}return this},removeClass:function(a){if(f.isFunction(a))return this.each(function(b){var c=f(this);c.removeClass(a.call(this,b,c.attr("class")))});if(a&&typeof a=="string"||a===b){var c=(a||"").split(o);for(var d=0,e=this.length;d<e;d++){var g=this[d];if(g.nodeType===1&&g.className)if(a){var h=(" "+g.className+" ").replace(n," ");for(var i=0,j=c.length;i<j;i++)h=h.replace(" "+c[i]+" "," ");g.className=f.trim(h)}else g.className=""}}return this},toggleClass:function(a,b){var c=typeof a,d=typeof b=="boolean";if(f.isFunction(a))return this.each(function(c){var d=f(this);d.toggleClass(a.call(this,c,d.attr("class"),b),b)});return this.each(function(){if(c==="string"){var e,g=0,h=f(this),i=b,j=a.split(o);while(e=j[g++])i=d?i:!h.hasClass(e),h[i?"addClass":"removeClass"](e)}else if(c==="undefined"||c==="boolean")this.className&&f._data(this,"__className__",this.className),this.className=this.className||a===!1?"":f._data(this,"__className__")||""})},hasClass:function(a){var b=" "+a+" ";for(var c=0,d=this.length;c<d;c++)if((" "+this[c].className+" ").replace(n," ").indexOf(b)>-1)return!0;return!1},val:function(a){var c,d,e=this[0];if(!arguments.length){if(e){c=f.valHooks[e.nodeName.toLowerCase()]||f.valHooks[e.type];if(c&&"get"in c&&(d=c.get(e,"value"))!==b)return d;return(e.value||"").replace(p,"")}return b}var g=f.isFunction(a);return this.each(function(d){var e=f(this),h;if(this.nodeType===1){g?h=a.call(this,d,e.val()):h=a,h==null?h="":typeof h=="number"?h+="":f.isArray(h)&&(h=f.map(h,function(a){return a==null?"":a+""})),c=f.valHooks[this.nodeName.toLowerCase()]||f.valHooks[this.type];if(!c||!("set"in c)||c.set(this,h,"value")===b)this.value=h}})}}),f.extend({valHooks:{option:{get:function(a){var b=a.attributes.value;return!b||b.specified?a.value:a.text}},select:{get:function(a){var b,c=a.selectedIndex,d=[],e=a.options,g=a.type==="select-one";if(c<0)return null;for(var h=g?c:0,i=g?c+1:e.length;h<i;h++){var j=e[h];if(j.selected&&(f.support.optDisabled?!j.disabled:j.getAttribute("disabled")===null)&&(!j.parentNode.disabled||!f.nodeName(j.parentNode,"optgroup"))){b=f(j).val();if(g)return b;d.push(b)}}if(g&&!d.length&&e.length)return f(e[c]).val();return d},set:function(a,b){var c=f.makeArray(b);f(a).find("option").each(function(){this.selected=f.inArray(f(this).val(),c)>=0}),c.length||(a.selectedIndex=-1);return c}}},attrFn:{val:!0,css:!0,html:!0,text:!0,data:!0,width:!0,height:!0,offset:!0},attrFix:{tabindex:"tabIndex"},attr:function(a,c,d,e){var g=a.nodeType;if(!a||g===3||g===8||g===2)return b;if(e&&c in f.attrFn)return f(a)[c](d);if(!("getAttribute"in a))return f.prop(a,c,d);var h,i,j=g!==1||!f.isXMLDoc(a);c=j&&f.attrFix[c]||c,i=f.attrHooks[c],i||(!t.test(c)||typeof d!="boolean"&&d!==b&&d.toLowerCase()!==c.toLowerCase()?v&&(f.nodeName(a,"form")||u.test(c))&&(i=v):i=w);if(d!==b){if(d===null){f.removeAttr(a,c);return b}if(i&&"set"in i&&j&&(h=i.set(a,d,c))!==b)return h;a.setAttribute(c,""+d);return d}if(i&&"get"in i&&j)return i.get(a,c);h=a.getAttribute(c);return h===null?b:h},removeAttr:function(a,b){var c;a.nodeType===1&&(b=f.attrFix[b]||b,f.support.getSetAttribute?a.removeAttribute(b):(f.attr(a,b,""),a.removeAttributeNode(a.getAttributeNode(b))),t.test(b)&&(c=f.propFix[b]||b)in a&&(a[c]=!1))},attrHooks:{type:{set:function(a,b){if(q.test(a.nodeName)&&a.parentNode)f.error("type property can't be changed");else if(!f.support.radioValue&&b==="radio"&&f.nodeName(a,"input")){var c=a.value;a.setAttribute("type",b),c&&(a.value=c);return b}}},tabIndex:{get:function(a){var c=a.getAttributeNode("tabIndex");return c&&c.specified?parseInt(c.value,10):r.test(a.nodeName)||s.test(a.nodeName)&&a.href?0:b}}},propFix:{tabindex:"tabIndex",readonly:"readOnly","for":"htmlFor","class":"className",maxlength:"maxLength",cellspacing:"cellSpacing",cellpadding:"cellPadding",rowspan:"rowSpan",colspan:"colSpan",usemap:"useMap",frameborder:"frameBorder",contenteditable:"contentEditable"},prop:function(a,c,d){var e=a.nodeType;if(!a||e===3||e===8||e===2)return b;var g,h,i=e!==1||!f.isXMLDoc(a);c=i&&f.propFix[c]||c,h=f.propHooks[c];return d!==b?h&&"set"in h&&(g=h.set(a,d,c))!==b?g:a[c]=d:h&&"get"in h&&(g=h.get(a,c))!==b?g:a[c]},propHooks:{}}),w={get:function(a,c){return a[f.propFix[c]||c]?c.toLowerCase():b},set:function(a,b,c){var d;b===!1?f.removeAttr(a,c):(d=f.propFix[c]||c,d in a&&(a[d]=b),a.setAttribute(c,c.toLowerCase()));return c}},f.attrHooks.value={get:function(a,b){if(v&&f.nodeName(a,"button"))return v.get(a,b);return a.value},set:function(a,b,c){if(v&&f.nodeName(a,"button"))return v.set(a,b,c);a.value=b}},f.support.getSetAttribute||(f.attrFix=f.propFix,v=f.attrHooks.name=f.valHooks.button={get:function(a,c){var d;d=a.getAttributeNode(c);return d&&d.nodeValue!==""?d.nodeValue:b},set:function(a,b,c){var d=a.getAttributeNode(c);if(d){d.nodeValue=b;return b}}},f.each(["width","height"],function(a,b){f.attrHooks[b]=f.extend(f.attrHooks[b],{set:function(a,c){if(c===""){a.setAttribute(b,"auto");return c}}})})),f.support.hrefNormalized||f.each(["href","src","width","height"],function(a,c){f.attrHooks[c]=f.extend(f.attrHooks[c],{get:function(a){var d=a.getAttribute(c,2);return d===null?b:d}})}),f.support.style||(f.attrHooks.style={get:function(a){return a.style.cssText.toLowerCase()||b},set:function(a,b){return a.style.cssText=""+b}}),f.support.optSelected||(f.propHooks.selected=f.extend(f.propHooks.selected,{get:function(a){var b=a.parentNode;b&&(b.selectedIndex,b.parentNode&&b.parentNode.selectedIndex)}})),f.support.checkOn||f.each(["radio","checkbox"],function(){f.valHooks[this]={get:function(a){return a.getAttribute("value")===null?"on":a.value}}}),f.each(["radio","checkbox"],function(){f.valHooks[this]=f.extend(f.valHooks[this],{set:function(a,b){if(f.isArray(b))return a.checked=f.inArray(f(a).val(),b)>=0}})});var x=Object.prototype.hasOwnProperty,y=/\.(.*)$/,z=/^(?:textarea|input|select)$/i,A=/\./g,B=/ /g,C=/[^\w\s.|`]/g,D=function(a){return a.replace(C,"\\$&")};f.event={add:function(a,c,d,e){if(a.nodeType!==3&&a.nodeType!==8){if(d===!1)d=E;else if(!d)return;var g,h;d.handler&&(g=d,d=g.handler),d.guid||(d.guid=f.guid++);var i=f._data(a);if(!i)return;var j=i.events,k=i.handle;j||(i.events=j={}),k||(i.handle=k=function(a){return typeof f!="undefined"&&(!a||f.event.triggered!==a.type)?f.event.handle.apply(k.elem,arguments):b}),k.elem=a,c=c.split(" ");var l,m=0,n;while(l=c[m++]){h=g?f.extend({},g):{handler:d,data:e},l.indexOf(".")>-1?(n=l.split("."),l=n.shift(),h.namespace=n.slice(0).sort().join(".")):(n=[],h.namespace=""),h.type=l,h.guid||(h.guid=d.guid);var o=j[l],p=f.event.special[l]||{};if(!o){o=j[l]=[];if(!p.setup||p.setup.call(a,e,n,k)===!1)a.addEventListener?a.addEventListener(l,k,!1):a.attachEvent&&a.attachEvent("on"+l,k)}p.add&&(p.add.call(a,h),h.handler.guid||(h.handler.guid=d.guid)),o.push(h),f.event.global[l]=!0}a=null}},global:{},remove:function(a,c,d,e){if(a.nodeType!==3&&a.nodeType!==8){d===!1&&(d=E);var g,h,i,j,k=0,l,m,n,o,p,q,r,s=f.hasData(a)&&f._data(a),t=s&&s.events;if(!s||!t)return;c&&c.type&&(d=c.handler,c=c.type);if(!c||typeof c=="string"&&c.charAt(0)==="."){c=c||"";for(h in t)f.event.remove(a,h+c);return}c=c.split(" ");while(h=c[k++]){r=h,q=null,l=h.indexOf(".")<0,m=[],l||(m=h.split("."),h=m.shift(),n=new RegExp("(^|\\.)"+f.map(m.slice(0).sort(),D).join("\\.(?:.*\\.)?")+"(\\.|$)")),p=t[h];if(!p)continue;if(!d){for(j=0;j<p.length;j++){q=p[j];if(l||n.test(q.namespace))f.event.remove(a,r,q.handler,j),p.splice(j--,1)}continue}o=f.event.special[h]||{};for(j=e||0;j<p.length;j++){q=p[j];if(d.guid===q.guid){if(l||n.test(q.namespace))e==null&&p.splice(j--,1),o.remove&&o.remove.call(a,q);if(e!=null)break}}if(p.length===0||e!=null&&p.length===1)(!o.teardown||o.teardown.call(a,m)===!1)&&f.removeEvent(a,h,s.handle),g=null,delete t[h]}if(f.isEmptyObject(t)){var u=s.handle;u&&(u.elem=null),delete s.events,delete s.handle,f.isEmptyObject(s)&&f.removeData(a,b,!0)}}},customEvent:{getData:!0,setData:!0,changeData:!0},trigger:function(c,d,e,g){var h=c.type||c,i=[],j;h.indexOf("!")>=0&&(h=h.slice(0,-1),j=!0),h.indexOf(".")>=0&&(i=h.split("."),h=i.shift(),i.sort());if(!!e&&!f.event.customEvent[h]||!!f.event.global[h]){c=typeof c=="object"?c[f.expando]?c:new f.Event(h,c):new f.Event(h),c.type=h,c.exclusive=j,c.namespace=i.join("."),c.namespace_re=new RegExp("(^|\\.)"+i.join("\\.(?:.*\\.)?")+"(\\.|$)");if(g||!e)c.preventDefault(),c.stopPropagation();if(!e){f.each(f.cache,function(){var a=f.expando,b=this[a];b&&b.events&&b.events[h]&&f.event.trigger(c,d,b.handle.elem
-)});return}if(e.nodeType===3||e.nodeType===8)return;c.result=b,c.target=e,d=d?f.makeArray(d):[],d.unshift(c);var k=e,l=h.indexOf(":")<0?"on"+h:"";do{var m=f._data(k,"handle");c.currentTarget=k,m&&m.apply(k,d),l&&f.acceptData(k)&&k[l]&&k[l].apply(k,d)===!1&&(c.result=!1,c.preventDefault()),k=k.parentNode||k.ownerDocument||k===c.target.ownerDocument&&a}while(k&&!c.isPropagationStopped());if(!c.isDefaultPrevented()){var n,o=f.event.special[h]||{};if((!o._default||o._default.call(e.ownerDocument,c)===!1)&&(h!=="click"||!f.nodeName(e,"a"))&&f.acceptData(e)){try{l&&e[h]&&(n=e[l],n&&(e[l]=null),f.event.triggered=h,e[h]())}catch(p){}n&&(e[l]=n),f.event.triggered=b}}return c.result}},handle:function(c){c=f.event.fix(c||a.event);var d=((f._data(this,"events")||{})[c.type]||[]).slice(0),e=!c.exclusive&&!c.namespace,g=Array.prototype.slice.call(arguments,0);g[0]=c,c.currentTarget=this;for(var h=0,i=d.length;h<i;h++){var j=d[h];if(e||c.namespace_re.test(j.namespace)){c.handler=j.handler,c.data=j.data,c.handleObj=j;var k=j.handler.apply(this,g);k!==b&&(c.result=k,k===!1&&(c.preventDefault(),c.stopPropagation()));if(c.isImmediatePropagationStopped())break}}return c.result},props:"altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode layerX layerY metaKey newValue offsetX offsetY pageX pageY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target toElement view wheelDelta which".split(" "),fix:function(a){if(a[f.expando])return a;var d=a;a=f.Event(d);for(var e=this.props.length,g;e;)g=this.props[--e],a[g]=d[g];a.target||(a.target=a.srcElement||c),a.target.nodeType===3&&(a.target=a.target.parentNode),!a.relatedTarget&&a.fromElement&&(a.relatedTarget=a.fromElement===a.target?a.toElement:a.fromElement);if(a.pageX==null&&a.clientX!=null){var h=a.target.ownerDocument||c,i=h.documentElement,j=h.body;a.pageX=a.clientX+(i&&i.scrollLeft||j&&j.scrollLeft||0)-(i&&i.clientLeft||j&&j.clientLeft||0),a.pageY=a.clientY+(i&&i.scrollTop||j&&j.scrollTop||0)-(i&&i.clientTop||j&&j.clientTop||0)}a.which==null&&(a.charCode!=null||a.keyCode!=null)&&(a.which=a.charCode!=null?a.charCode:a.keyCode),!a.metaKey&&a.ctrlKey&&(a.metaKey=a.ctrlKey),!a.which&&a.button!==b&&(a.which=a.button&1?1:a.button&2?3:a.button&4?2:0);return a},guid:1e8,proxy:f.proxy,special:{ready:{setup:f.bindReady,teardown:f.noop},live:{add:function(a){f.event.add(this,O(a.origType,a.selector),f.extend({},a,{handler:N,guid:a.handler.guid}))},remove:function(a){f.event.remove(this,O(a.origType,a.selector),a)}},beforeunload:{setup:function(a,b,c){f.isWindow(this)&&(this.onbeforeunload=c)},teardown:function(a,b){this.onbeforeunload===b&&(this.onbeforeunload=null)}}}},f.removeEvent=c.removeEventListener?function(a,b,c){a.removeEventListener&&a.removeEventListener(b,c,!1)}:function(a,b,c){a.detachEvent&&a.detachEvent("on"+b,c)},f.Event=function(a,b){if(!this.preventDefault)return new f.Event(a,b);a&&a.type?(this.originalEvent=a,this.type=a.type,this.isDefaultPrevented=a.defaultPrevented||a.returnValue===!1||a.getPreventDefault&&a.getPreventDefault()?F:E):this.type=a,b&&f.extend(this,b),this.timeStamp=f.now(),this[f.expando]=!0},f.Event.prototype={preventDefault:function(){this.isDefaultPrevented=F;var a=this.originalEvent;!a||(a.preventDefault?a.preventDefault():a.returnValue=!1)},stopPropagation:function(){this.isPropagationStopped=F;var a=this.originalEvent;!a||(a.stopPropagation&&a.stopPropagation(),a.cancelBubble=!0)},stopImmediatePropagation:function(){this.isImmediatePropagationStopped=F,this.stopPropagation()},isDefaultPrevented:E,isPropagationStopped:E,isImmediatePropagationStopped:E};var G=function(a){var b=a.relatedTarget;a.type=a.data;try{if(b&&b!==c&&!b.parentNode)return;while(b&&b!==this)b=b.parentNode;b!==this&&f.event.handle.apply(this,arguments)}catch(d){}},H=function(a){a.type=a.data,f.event.handle.apply(this,arguments)};f.each({mouseenter:"mouseover",mouseleave:"mouseout"},function(a,b){f.event.special[a]={setup:function(c){f.event.add(this,b,c&&c.selector?H:G,a)},teardown:function(a){f.event.remove(this,b,a&&a.selector?H:G)}}}),f.support.submitBubbles||(f.event.special.submit={setup:function(a,b){if(!f.nodeName(this,"form"))f.event.add(this,"click.specialSubmit",function(a){var b=a.target,c=b.type;(c==="submit"||c==="image")&&f(b).closest("form").length&&L("submit",this,arguments)}),f.event.add(this,"keypress.specialSubmit",function(a){var b=a.target,c=b.type;(c==="text"||c==="password")&&f(b).closest("form").length&&a.keyCode===13&&L("submit",this,arguments)});else return!1},teardown:function(a){f.event.remove(this,".specialSubmit")}});if(!f.support.changeBubbles){var I,J=function(a){var b=a.type,c=a.value;b==="radio"||b==="checkbox"?c=a.checked:b==="select-multiple"?c=a.selectedIndex>-1?f.map(a.options,function(a){return a.selected}).join("-"):"":f.nodeName(a,"select")&&(c=a.selectedIndex);return c},K=function(c){var d=c.target,e,g;if(!!z.test(d.nodeName)&&!d.readOnly){e=f._data(d,"_change_data"),g=J(d),(c.type!=="focusout"||d.type!=="radio")&&f._data(d,"_change_data",g);if(e===b||g===e)return;if(e!=null||g)c.type="change",c.liveFired=b,f.event.trigger(c,arguments[1],d)}};f.event.special.change={filters:{focusout:K,beforedeactivate:K,click:function(a){var b=a.target,c=f.nodeName(b,"input")?b.type:"";(c==="radio"||c==="checkbox"||f.nodeName(b,"select"))&&K.call(this,a)},keydown:function(a){var b=a.target,c=f.nodeName(b,"input")?b.type:"";(a.keyCode===13&&!f.nodeName(b,"textarea")||a.keyCode===32&&(c==="checkbox"||c==="radio")||c==="select-multiple")&&K.call(this,a)},beforeactivate:function(a){var b=a.target;f._data(b,"_change_data",J(b))}},setup:function(a,b){if(this.type==="file")return!1;for(var c in I)f.event.add(this,c+".specialChange",I[c]);return z.test(this.nodeName)},teardown:function(a){f.event.remove(this,".specialChange");return z.test(this.nodeName)}},I=f.event.special.change.filters,I.focus=I.beforeactivate}f.support.focusinBubbles||f.each({focus:"focusin",blur:"focusout"},function(a,b){function e(a){var c=f.event.fix(a);c.type=b,c.originalEvent={},f.event.trigger(c,null,c.target),c.isDefaultPrevented()&&a.preventDefault()}var d=0;f.event.special[b]={setup:function(){d++===0&&c.addEventListener(a,e,!0)},teardown:function(){--d===0&&c.removeEventListener(a,e,!0)}}}),f.each(["bind","one"],function(a,c){f.fn[c]=function(a,d,e){var g;if(typeof a=="object"){for(var h in a)this[c](h,d,a[h],e);return this}if(arguments.length===2||d===!1)e=d,d=b;c==="one"?(g=function(a){f(this).unbind(a,g);return e.apply(this,arguments)},g.guid=e.guid||f.guid++):g=e;if(a==="unload"&&c!=="one")this.one(a,d,e);else for(var i=0,j=this.length;i<j;i++)f.event.add(this[i],a,g,d);return this}}),f.fn.extend({unbind:function(a,b){if(typeof a=="object"&&!a.preventDefault)for(var c in a)this.unbind(c,a[c]);else for(var d=0,e=this.length;d<e;d++)f.event.remove(this[d],a,b);return this},delegate:function(a,b,c,d){return this.live(b,c,d,a)},undelegate:function(a,b,c){return arguments.length===0?this.unbind("live"):this.die(b,null,c,a)},trigger:function(a,b){return this.each(function(){f.event.trigger(a,b,this)})},triggerHandler:function(a,b){if(this[0])return f.event.trigger(a,b,this[0],!0)},toggle:function(a){var b=arguments,c=a.guid||f.guid++,d=0,e=function(c){var e=(f.data(this,"lastToggle"+a.guid)||0)%d;f.data(this,"lastToggle"+a.guid,e+1),c.preventDefault();return b[e].apply(this,arguments)||!1};e.guid=c;while(d<b.length)b[d++].guid=c;return this.click(e)},hover:function(a,b){return this.mouseenter(a).mouseleave(b||a)}});var M={focus:"focusin",blur:"focusout",mouseenter:"mouseover",mouseleave:"mouseout"};f.each(["live","die"],function(a,c){f.fn[c]=function(a,d,e,g){var h,i=0,j,k,l,m=g||this.selector,n=g?this:f(this.context);if(typeof a=="object"&&!a.preventDefault){for(var o in a)n[c](o,d,a[o],m);return this}if(c==="die"&&!a&&g&&g.charAt(0)==="."){n.unbind(g);return this}if(d===!1||f.isFunction(d))e=d||E,d=b;a=(a||"").split(" ");while((h=a[i++])!=null){j=y.exec(h),k="",j&&(k=j[0],h=h.replace(y,""));if(h==="hover"){a.push("mouseenter"+k,"mouseleave"+k);continue}l=h,M[h]?(a.push(M[h]+k),h=h+k):h=(M[h]||h)+k;if(c==="live")for(var p=0,q=n.length;p<q;p++)f.event.add(n[p],"live."+O(h,m),{data:d,selector:m,handler:e,origType:h,origHandler:e,preType:l});else n.unbind("live."+O(h,m),e)}return this}}),f.each("blur focus focusin focusout load resize scroll unload click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup error".split(" "),function(a,b){f.fn[b]=function(a,c){c==null&&(c=a,a=null);return arguments.length>0?this.bind(b,a,c):this.trigger(b)},f.attrFn&&(f.attrFn[b]=!0)}),function(){function u(a,b,c,d,e,f){for(var g=0,h=d.length;g<h;g++){var i=d[g];if(i){var j=!1;i=i[a];while(i){if(i.sizcache===c){j=d[i.sizset];break}if(i.nodeType===1){f||(i.sizcache=c,i.sizset=g);if(typeof b!="string"){if(i===b){j=!0;break}}else if(k.filter(b,[i]).length>0){j=i;break}}i=i[a]}d[g]=j}}}function t(a,b,c,d,e,f){for(var g=0,h=d.length;g<h;g++){var i=d[g];if(i){var j=!1;i=i[a];while(i){if(i.sizcache===c){j=d[i.sizset];break}i.nodeType===1&&!f&&(i.sizcache=c,i.sizset=g);if(i.nodeName.toLowerCase()===b){j=i;break}i=i[a]}d[g]=j}}}var a=/((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,d=0,e=Object.prototype.toString,g=!1,h=!0,i=/\\/g,j=/\W/;[0,0].sort(function(){h=!1;return 0});var k=function(b,d,f,g){f=f||[],d=d||c;var h=d;if(d.nodeType!==1&&d.nodeType!==9)return[];if(!b||typeof b!="string")return f;var i,j,n,o,q,r,s,t,u=!0,w=k.isXML(d),x=[],y=b;do{a.exec(""),i=a.exec(y);if(i){y=i[3],x.push(i[1]);if(i[2]){o=i[3];break}}}while(i);if(x.length>1&&m.exec(b))if(x.length===2&&l.relative[x[0]])j=v(x[0]+x[1],d);else{j=l.relative[x[0]]?[d]:k(x.shift(),d);while(x.length)b=x.shift(),l.relative[b]&&(b+=x.shift()),j=v(b,j)}else{!g&&x.length>1&&d.nodeType===9&&!w&&l.match.ID.test(x[0])&&!l.match.ID.test(x[x.length-1])&&(q=k.find(x.shift(),d,w),d=q.expr?k.filter(q.expr,q.set)[0]:q.set[0]);if(d){q=g?{expr:x.pop(),set:p(g)}:k.find(x.pop(),x.length===1&&(x[0]==="~"||x[0]==="+")&&d.parentNode?d.parentNode:d,w),j=q.expr?k.filter(q.expr,q.set):q.set,x.length>0?n=p(j):u=!1;while(x.length)r=x.pop(),s=r,l.relative[r]?s=x.pop():r="",s==null&&(s=d),l.relative[r](n,s,w)}else n=x=[]}n||(n=j),n||k.error(r||b);if(e.call(n)==="[object Array]")if(!u)f.push.apply(f,n);else if(d&&d.nodeType===1)for(t=0;n[t]!=null;t++)n[t]&&(n[t]===!0||n[t].nodeType===1&&k.contains(d,n[t]))&&f.push(j[t]);else for(t=0;n[t]!=null;t++)n[t]&&n[t].nodeType===1&&f.push(j[t]);else p(n,f);o&&(k(o,h,f,g),k.uniqueSort(f));return f};k.uniqueSort=function(a){if(r){g=h,a.sort(r);if(g)for(var b=1;b<a.length;b++)a[b]===a[b-1]&&a.splice(b--,1)}return a},k.matches=function(a,b){return k(a,null,null,b)},k.matchesSelector=function(a,b){return k(b,null,null,[a]).length>0},k.find=function(a,b,c){var d;if(!a)return[];for(var e=0,f=l.order.length;e<f;e++){var g,h=l.order[e];if(g=l.leftMatch[h].exec(a)){var j=g[1];g.splice(1,1);if(j.substr(j.length-1)!=="\\"){g[1]=(g[1]||"").replace(i,""),d=l.find[h](g,b,c);if(d!=null){a=a.replace(l.match[h],"");break}}}}d||(d=typeof b.getElementsByTagName!="undefined"?b.getElementsByTagName("*"):[]);return{set:d,expr:a}},k.filter=function(a,c,d,e){var f,g,h=a,i=[],j=c,m=c&&c[0]&&k.isXML(c[0]);while(a&&c.length){for(var n in l.filter)if((f=l.leftMatch[n].exec(a))!=null&&f[2]){var o,p,q=l.filter[n],r=f[1];g=!1,f.splice(1,1);if(r.substr(r.length-1)==="\\")continue;j===i&&(i=[]);if(l.preFilter[n]){f=l.preFilter[n](f,j,d,i,e,m);if(!f)g=o=!0;else if(f===!0)continue}if(f)for(var s=0;(p=j[s])!=null;s++)if(p){o=q(p,f,s,j);var t=e^!!o;d&&o!=null?t?g=!0:j[s]=!1:t&&(i.push(p),g=!0)}if(o!==b){d||(j=i),a=a.replace(l.match[n],"");if(!g)return[];break}}if(a===h)if(g==null)k.error(a);else break;h=a}return j},k.error=function(a){throw"Syntax error, unrecognized expression: "+a};var l=k.selectors={order:["ID","NAME","TAG"],match:{ID:/#((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,CLASS:/\.((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,NAME:/\[name=['"]*((?:[\w\u00c0-\uFFFF\-]|\\.)+)['"]*\]/,ATTR:/\[\s*((?:[\w\u00c0-\uFFFF\-]|\\.)+)\s*(?:(\S?=)\s*(?:(['"])(.*?)\3|(#?(?:[\w\u00c0-\uFFFF\-]|\\.)*)|)|)\s*\]/,TAG:/^((?:[\w\u00c0-\uFFFF\*\-]|\\.)+)/,CHILD:/:(only|nth|last|first)-child(?:\(\s*(even|odd|(?:[+\-]?\d+|(?:[+\-]?\d*)?n\s*(?:[+\-]\s*\d+)?))\s*\))?/,POS:/:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^\-]|$)/,PSEUDO:/:((?:[\w\u00c0-\uFFFF\-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/},leftMatch:{},attrMap:{"class":"className","for":"htmlFor"},attrHandle:{href:function(a){return a.getAttribute("href")},type:function(a){return a.getAttribute("type")}},relative:{"+":function(a,b){var c=typeof b=="string",d=c&&!j.test(b),e=c&&!d;d&&(b=b.toLowerCase());for(var f=0,g=a.length,h;f<g;f++)if(h=a[f]){while((h=h.previousSibling)&&h.nodeType!==1);a[f]=e||h&&h.nodeName.toLowerCase()===b?h||!1:h===b}e&&k.filter(b,a,!0)},">":function(a,b){var c,d=typeof b=="string",e=0,f=a.length;if(d&&!j.test(b)){b=b.toLowerCase();for(;e<f;e++){c=a[e];if(c){var g=c.parentNode;a[e]=g.nodeName.toLowerCase()===b?g:!1}}}else{for(;e<f;e++)c=a[e],c&&(a[e]=d?c.parentNode:c.parentNode===b);d&&k.filter(b,a,!0)}},"":function(a,b,c){var e,f=d++,g=u;typeof b=="string"&&!j.test(b)&&(b=b.toLowerCase(),e=b,g=t),g("parentNode",b,f,a,e,c)},"~":function(a,b,c){var e,f=d++,g=u;typeof b=="string"&&!j.test(b)&&(b=b.toLowerCase(),e=b,g=t),g("previousSibling",b,f,a,e,c)}},find:{ID:function(a,b,c){if(typeof b.getElementById!="undefined"&&!c){var d=b.getElementById(a[1]);return d&&d.parentNode?[d]:[]}},NAME:function(a,b){if(typeof b.getElementsByName!="undefined"){var c=[],d=b.getElementsByName(a[1]);for(var e=0,f=d.length;e<f;e++)d[e].getAttribute("name")===a[1]&&c.push(d[e]);return c.length===0?null:c}},TAG:function(a,b){if(typeof b.getElementsByTagName!="undefined")return b.getElementsByTagName(a[1])}},preFilter:{CLASS:function(a,b,c,d,e,f){a=" "+a[1].replace(i,"")+" ";if(f)return a;for(var g=0,h;(h=b[g])!=null;g++)h&&(e^(h.className&&(" "+h.className+" ").replace(/[\t\n\r]/g," ").indexOf(a)>=0)?c||d.push(h):c&&(b[g]=!1));return!1},ID:function(a){return a[1].replace(i,"")},TAG:function(a,b){return a[1].replace(i,"").toLowerCase()},CHILD:function(a){if(a[1]==="nth"){a[2]||k.error(a[0]),a[2]=a[2].replace(/^\+|\s*/g,"");var b=/(-?)(\d*)(?:n([+\-]?\d*))?/.exec(a[2]==="even"&&"2n"||a[2]==="odd"&&"2n+1"||!/\D/.test(a[2])&&"0n+"+a[2]||a[2]);a[2]=b[1]+(b[2]||1)-0,a[3]=b[3]-0}else a[2]&&k.error(a[0]);a[0]=d++;return a},ATTR:function(a,b,c,d,e,f){var g=a[1]=a[1].replace(i,"");!f&&l.attrMap[g]&&(a[1]=l.attrMap[g]),a[4]=(a[4]||a[5]||"").replace(i,""),a[2]==="~="&&(a[4]=" "+a[4]+" ");return a},PSEUDO:function(b,c,d,e,f){if(b[1]==="not")if((a.exec(b[3])||"").length>1||/^\w/.test(b[3]))b[3]=k(b[3],null,null,c);else{var g=k.filter(b[3],c,d,!0^f);d||e.push.apply(e,g);return!1}else if(l.match.POS.test(b[0])||l.match.CHILD.test(b[0]))return!0;return b},POS:function(a){a.unshift(!0);return a}},filters:{enabled:function(a){return a.disabled===!1&&a.type!=="hidden"},disabled:function(a){return a.disabled===!0},checked:function(a){return a.checked===!0},selected:function(a){a.parentNode&&a.parentNode.selectedIndex;return a.selected===!0},parent:function(a){return!!a.firstChild},empty:function(a){return!a.firstChild},has:function(a,b,c){return!!k(c[3],a).length},header:function(a){return/h\d/i.test(a.nodeName)},text:function(a){var b=a.getAttribute("type"),c=a.type;return a.nodeName.toLowerCase()==="input"&&"text"===c&&(b===c||b===null)},radio:function(a){return a.nodeName.toLowerCase()==="input"&&"radio"===a.type},checkbox:function(a){return a.nodeName.toLowerCase()==="input"&&"checkbox"===a.type},file:function(a){return a.nodeName.toLowerCase()==="input"&&"file"===a.type},password:function(a){return a.nodeName.toLowerCase()==="input"&&"password"===a.type},submit:function(a){var b=a.nodeName.toLowerCase();return(b==="input"||b==="button")&&"submit"===a.type},image:function(a){return a.nodeName.toLowerCase()==="input"&&"image"===a.type},reset:function(a){var b=a.nodeName.toLowerCase();return(b==="input"||b==="button")&&"reset"===a.type},button:function(a){var b=a.nodeName.toLowerCase();return b==="input"&&"button"===a.type||b==="button"},input:function(a){return/input|select|textarea|button/i.test(a.nodeName)},focus:function(a){return a===a.ownerDocument.activeElement}},setFilters:{first:function(a,b){return b===0},last:function(a,b,c,d){return b===d.length-1},even:function(a,b){return b%2===0},odd:function(a,b){return b%2===1},lt:function(a,b,c){return b<c[3]-0},gt:function(a,b,c){return b>c[3]-0},nth:function(a,b,c){return c[3]-0===b},eq:function(a,b,c){return c[3]-0===b}},filter:{PSEUDO:function(a,b,c,d){var e=b[1],f=l.filters[e];if(f)return f(a,c,b,d);if(e==="contains")return(a.textContent||a.innerText||k.getText([a])||"").indexOf(b[3])>=0;if(e==="not"){var g=b[3];for(var h=0,i=g.length;h<i;h++)if(g[h]===a)return!1;return!0}k.error(e)},CHILD:function(a,b){var c=b[1],d=a;switch(c){case"only":case"first":while(d=d.previousSibling)if(d.nodeType===1)return!1;if(c==="first")return!0;d=a;case"last":while(d=d.nextSibling)if(d.nodeType===1)return!1;return!0;case"nth":var e=b[2],f=b[3];if(e===1&&f===0)return!0;var g=b[0],h=a.parentNode;if(h&&(h.sizcache!==g||!a.nodeIndex)){var i=0;for(d=h.firstChild;d;d=d.nextSibling)d.nodeType===1&&(d.nodeIndex=++i);h.sizcache=g}var j=a.nodeIndex-f;return e===0?j===0:j%e===0&&j/e>=0}},ID:function(a,b){return a.nodeType===1&&a.getAttribute("id")===b},TAG:function(a,b){return b==="*"&&a.nodeType===1||a.nodeName.toLowerCase()===b},CLASS:function(a,b){return(" "+(a.className||a.getAttribute("class"))+" ").indexOf(b)>-1},ATTR:function(a,b){var c=b[1],d=l.attrHandle[c]?l.attrHandle[c](a):a[c]!=null?a[c]:a.getAttribute(c),e=d+"",f=b[2],g=b[4];return d==null?f==="!=":f==="="?e===g:f==="*="?e.indexOf(g)>=0:f==="~="?(" "+e+" ").indexOf(g)>=0:g?f==="!="?e!==g:f==="^="?e.indexOf(g)===0:f==="$="?e.substr(e.length-g.length)===g:f==="|="?e===g||e.substr(0,g.length+1)===g+"-":!1:e&&d!==!1},POS:function(a,b,c,d){var e=b[2],f=l.setFilters[e];if(f)return f(a,c,b,d)}}},m=l.match.POS,n=function(a,b){return"\\"+(b-0+1)};for(var o in l.match)l.match[o]=new RegExp(l.match[o].source+/(?![^\[]*\])(?![^\(]*\))/.source),l.leftMatch[o]=new RegExp(/(^(?:.|\r|\n)*?)/.source+l.match[o].source.replace(/\\(\d+)/g,n));var p=function(a,b){a=Array.prototype.slice.call(a,0);if(b){b.push.apply(b,a);return b}return a};try{Array.prototype.slice.call(c.documentElement.childNodes,0)[0].nodeType}catch(q){p=function(a,b){var c=0,d=b||[];if(e.call(a)==="[object Array]")Array.prototype.push.apply(d,a);else if(typeof a.length=="number")for(var f=a.length;c<f;c++)d.push(a[c]);else for(;a[c];c++)d.push(a[c]);return d}}var r,s;c.documentElement.compareDocumentPosition?r=function(a,b){if(a===b){g=!0;return 0}if(!a.compareDocumentPosition||!b.compareDocumentPosition)return a.compareDocumentPosition?-1:1;return a.compareDocumentPosition(b)&4?-1:1}:(r=function(a,b){if(a===b){g=!0;return 0}if(a.sourceIndex&&b.sourceIndex)return a.sourceIndex-b.sourceIndex;var c,d,e=[],f=[],h=a.parentNode,i=b.parentNode,j=h;if(h===i)return s(a,b);if(!h)return-1;if(!i)return 1;while(j)e.unshift(j),j=j.parentNode;j=i;while(j)f.unshift(j),j=j.parentNode;c=e.length,d=f.length;for(var k=0;k<c&&k<d;k++)if(e[k]!==f[k])return s(e[k],f[k]);return k===c?s(a,f[k],-1):s(e[k],b,1)},s=function(a,b,c){if(a===b)return c;var d=a.nextSibling;while(d){if(d===b)return-1;d=d.nextSibling}return 1}),k.getText=function(a){var b="",c;for(var d=0;a[d];d++)c=a[d],c.nodeType===3||c.nodeType===4?b+=c.nodeValue:c.nodeType!==8&&(b+=k.getText(c.childNodes));return b},function(){var a=c.createElement("div"),d="script"+(new Date).getTime(),e=c.documentElement;a.innerHTML="<a name='"+d+"'/>",e.insertBefore(a,e.firstChild),c.getElementById(d)&&(l.find.ID=function(a,c,d){if(typeof c.getElementById!="undefined"&&!d){var e=c.getElementById(a[1]);return e?e.id===a[1]||typeof e.getAttributeNode!="undefined"&&e.getAttributeNode("id").nodeValue===a[1]?[e]:b:[]}},l.filter.ID=function(a,b){var c=typeof a.getAttributeNode!="undefined"&&a.getAttributeNode("id");return a.nodeType===1&&c&&c.nodeValue===b}),e.removeChild(a),e=a=null}(),function(){var a=c.createElement("div");a.appendChild(c.createComment("")),a.getElementsByTagName("*").length>0&&(l.find.TAG=function(a,b){var c=b.getElementsByTagName(a[1]);if(a[1]==="*"){var d=[];for(var e=0;c[e];e++)c[e].nodeType===1&&d.push(c[e]);c=d}return c}),a.innerHTML="<a href='#'></a>",a.firstChild&&typeof a.firstChild.getAttribute!="undefined"&&a.firstChild.getAttribute("href")!=="#"&&(l.attrHandle.href=function(a){return a.getAttribute("href",2)}),a=null}(),c.querySelectorAll&&function(){var a=k,b=c.createElement("div"),d="__sizzle__";b.innerHTML="<p class='TEST'></p>";if(!b.querySelectorAll||b.querySelectorAll(".TEST").length!==0){k=function(b,e,f,g){e=e||c;if(!g&&!k.isXML(e)){var h=/^(\w+$)|^\.([\w\-]+$)|^#([\w\-]+$)/.exec(b);if(h&&(e.nodeType===1||e.nodeType===9)){if(h[1])return p(e.getElementsByTagName(b),f);if(h[2]&&l.find.CLASS&&e.getElementsByClassName)return p(e.getElementsByClassName(h[2]),f)}if(e.nodeType===9){if(b==="body"&&e.body)return p([e.body],f);if(h&&h[3]){var i=e.getElementById(h[3]);if(!i||!i.parentNode)return p([],f);if(i.id===h[3])return p([i],f)}try{return p(e.querySelectorAll(b),f)}catch(j){}}else if(e.nodeType===1&&e.nodeName.toLowerCase()!=="object"){var m=e,n=e.getAttribute("id"),o=n||d,q=e.parentNode,r=/^\s*[+~]/.test(b);n?o=o.replace(/'/g,"\\$&"):e.setAttribute("id",o),r&&q&&(e=e.parentNode);try{if(!r||q)return p(e.querySelectorAll("[id='"+o+"'] "+b),f)}catch(s){}finally{n||m.removeAttribute("id")}}}return a(b,e,f,g)};for(var e in a)k[e]=a[e];b=null}}(),function(){var a=c.documentElement,b=a.matchesSelector||a.mozMatchesSelector||a.webkitMatchesSelector||a.msMatchesSelector;if(b){var d=!b.call(c.createElement("div"),"div"),e=!1;try{b.call(c.documentElement,"[test!='']:sizzle")}catch(f){e=!0}k.matchesSelector=function(a,c){c=c.replace(/\=\s*([^'"\]]*)\s*\]/g,"='$1']");if(!k.isXML(a))try{if(e||!l.match.PSEUDO.test(c)&&!/!=/.test(c)){var f=b.call(a,c);if(f||!d||a.document&&a.document.nodeType!==11)return f}}catch(g){}return k(c,null,null,[a]).length>0}}}(),function(){var a=c.createElement("div");a.innerHTML="<div class='test e'></div><div class='test'></div>";if(!!a.getElementsByClassName&&a.getElementsByClassName("e").length!==0){a.lastChild.className="e";if(a.getElementsByClassName("e").length===1)return;l.order.splice(1,0,"CLASS"),l.find.CLASS=function(a,b,c){if(typeof b.getElementsByClassName!="undefined"&&!c)return b.getElementsByClassName(a[1])},a=null}}(),c.documentElement.contains?k.contains=function(a,b){return a!==b&&(a.contains?a.contains(b):!0)}:c.documentElement.compareDocumentPosition?k.contains=function(a,b){return!!(a.compareDocumentPosition(b)&16)}:k.contains=function(){return!1},k.isXML=function(a){var b=(a?a.ownerDocument||a:0).documentElement;return b?b.nodeName!=="HTML":!1};var v=function(a,b){var c,d=[],e="",f=b.nodeType?[b]:b;while(c=l.match.PSEUDO.exec(a))e+=c[0],a=a.replace(l.match.PSEUDO,"");a=l.relative[a]?a+"*":a;for(var g=0,h=f.length;g<h;g++)k(a,f[g],d);return k.filter(e,d)};f.find=k,f.expr=k.selectors,f.expr[":"]=f.expr.filters,f.unique=k.uniqueSort,f.text=k.getText,f.isXMLDoc=k.isXML,f.contains=k.contains}();var P=/Until$/,Q=/^(?:parents|prevUntil|prevAll)/,R=/,/,S=/^.[^:#\[\.,]*$/,T=Array.prototype.slice,U=f.expr.match.POS,V={children:!0,contents:!0,next:!0,prev:!0};f.fn.extend({find:function(a){var b=this,c,d;if(typeof a!="string")return f(a).filter(function(){for(c=0,d=b.length;c<d;c++)if(f.contains(b[c],this))return!0});var e=this.pushStack("","find",a),g,h,i;for(c=0,d=this.length;c<d;c++){g=e.length,f.find(a,this[c],e);if(c>0)for(h=g;h<e.length;h++)for(i=0;i<g;i++)if(e[i]===e[h]){e.splice(h--,1);break}}return e},has:function(a){var b=f(a);return this.filter(function(){for(var a=0,c=b.length;a<c;a++)if(f.contains(this,b[a]))return!0})},not:function(a){return this.pushStack(X(this,a,!1),"not",a)},filter:function(a){return this.pushStack(X(this,a,!0),"filter",a)},is:function(a){return!!a&&(typeof a=="string"?f.filter(a,this).length>0:this.filter(a).length>0)},closest:function(a,b){var c=[],d,e,g=this[0];if(f.isArray(a)){var h,i,j={},k=1;if(g&&a.length){for(d=0,e=a.length;d<e;d++)i=a[d],j[i]||(j[i]=U.test(i)?f(i,b||this.context):i);while(g&&g.ownerDocument&&g!==b){for(i in j)h=j[i],(h.jquery?h.index(g)>-1:f(g).is(h))&&c.push({selector:i,elem:g,level:k});g=g.parentNode,k++}}return c}var l=U.test(a)||typeof a!="string"?f(a,b||this.context):0;for(d=0,e=this.length;d<e;d++){g=this[d];while(g){if(l?l.index(g)>-1:f.find.matchesSelector(g,a)){c.push(g);break}g=g.parentNode;if(!g||!g.ownerDocument||g===b||g.nodeType===11)break}}c=c.length>1?f.unique(c):c;return this.pushStack(c,"closest",a)},index:function(a){if(!a||typeof a=="string")return f.inArray(this[0],a?f(a):this.parent().children());return f.inArray(a.jquery?a[0]:a,this)},add:function(a,b){var c=typeof a=="string"?f(a,b):f.makeArray(a&&a.nodeType?[a]:a),d=f.merge(this.get(),c);return this.pushStack(W(c[0])||W(d[0])?d:f.unique(d))},andSelf:function(){return this.add(this.prevObject)}}),f.each({parent:function(a){var b=a.parentNode;return b&&b.nodeType!==11?b:null},parents:function(a){return f.dir(a,"parentNode")},parentsUntil:function(a,b,c){return f.dir(a,"parentNode",c)},next:function(a){return f.nth(a,2,"nextSibling")},prev:function(a){return f.nth(a,2,"previousSibling")},nextAll:function(a){return f.dir(a,"nextSibling")},prevAll:function(a){return f.dir(a,"previousSibling")},nextUntil:function(a,b,c){return f.dir(a,"nextSibling",c)},prevUntil:function(a,b,c){return f.dir(a,"previousSibling",c)},siblings:function(a){return f.sibling(a.parentNode.firstChild,a)},children:function(a){return f.sibling(a.firstChild)},contents:function(a){return f.nodeName(a,"iframe")?a.contentDocument||a.contentWindow.document:f.makeArray(a.childNodes)}},function(a,b){f.fn[a]=function(c,d){var e=f.map(this,b,c),g=T.call(arguments);P.test(a)||(d=c),d&&typeof d=="string"&&(e=f.filter(d,e)),e=this.length>1&&!V[a]?f.unique(e):e,(this.length>1||R.test(d))&&Q.test(a)&&(e=e.reverse());return this.pushStack(e,a,g.join(","))}}),f.extend({filter:function(a,b,c){c&&(a=":not("+a+")");return b.length===1?f.find.matchesSelector(b[0],a)?[b[0]]:[]:f.find.matches(a,b)},dir:function(a,c,d){var e=[],g=a[c];while(g&&g.nodeType!==9&&(d===b||g.nodeType!==1||!f(g).is(d)))g.nodeType===1&&e.push(g),g=g[c];return e},nth:function(a,b,c,d){b=b||1;var e=0;for(;a;a=a[c])if(a.nodeType===1&&++e===b)break;return a},sibling:function(a,b){var c=[];for(;a;a=a.nextSibling)a.nodeType===1&&a!==b&&c.push(a);return c}});var Y=/ jQuery\d+="(?:\d+|null)"/g,Z=/^\s+/,$=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig,_=/<([\w:]+)/,ba=/<tbody/i,bb=/<|&#?\w+;/,bc=/<(?:script|object|embed|option|style)/i,bd=/checked\s*(?:[^=]|=\s*.checked.)/i,be=/\/(java|ecma)script/i,bf=/^\s*<!(?:\[CDATA\[|\-\-)/,bg={option:[1,"<select multiple='multiple'>","</select>"],legend:[1,"<fieldset>","</fieldset>"],thead:[1,"<table>","</table>"],tr:[2,"<table><tbody>","</tbody></table>"],td:[3,"<table><tbody><tr>","</tr></tbody></table>"],col:[2,"<table><tbody></tbody><colgroup>","</colgroup></table>"],area:[1,"<map>","</map>"],_default:[0,"",""]};bg.optgroup=bg.option,bg.tbody=bg.tfoot=bg.colgroup=bg.caption=bg.thead,bg.th=bg.td,f.support.htmlSerialize||(bg._default=[1,"div<div>","</div>"]),f.fn.extend({text:function(a){if(f.isFunction(a))return this.each(function(b){var c=f(this);c.text(a.call(this,b,c.text()))});if(typeof a!="object"&&a!==b)return this.empty().append((this[0]&&this[0].ownerDocument||c).createTextNode(a));return f.text(this)},wrapAll:function(a){if(f.isFunction(a))return this.each(function(b){f(this).wrapAll(a.call(this,b))});if(this[0]){var b=f(a,this[0].ownerDocument).eq(0).clone(!0);this[0].parentNode&&b.insertBefore(this[0]),b.map(function(){var a=this;while(a.firstChild&&a.firstChild.nodeType===1)a=a.firstChild;return a}).append(this)}return this},wrapInner:function(a){if(f.isFunction(a))return this.each(function(b){f(this).wrapInner(a.call(this,b))});return this.each(function(){var b=f(this),c=b.contents();c.length?c.wrapAll(a):b.append(a)})},wrap:function(a){return this.each(function(){f(this).wrapAll(a)})},unwrap:function(){return this.parent().each(function(){f.nodeName(this,"body")||f(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,!0,function(a){this.nodeType===1&&this.appendChild(a)})},prepend:function(){return this.domManip(arguments,!0,function(a){this.nodeType===1&&this.insertBefore(a,this.firstChild)})},before:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this)});if(arguments.length){var a=f(arguments[0]);a.push.apply(a,this.toArray());return this.pushStack(a,"before",arguments)}},after:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this.nextSibling)});if(arguments.length){var a=this.pushStack(this,"after",arguments);a.push.apply(a,f(arguments[0]).toArray());return a}},remove:function(a,b){for(var c=0,d;(d=this[c])!=null;c++)if(!a||f.filter(a,[d]).length)!b&&d.nodeType===1&&(f.cleanData(d.getElementsByTagName("*")),f.cleanData([d])),d.parentNode&&d.parentNode.removeChild(d);return this},empty:function(){for(var a=0,b;(b=this[a])!=null;a++){b.nodeType===1&&f.cleanData(b.getElementsByTagName("*"));while(b.firstChild)b.removeChild(b.firstChild)}return this},clone:function(a,b){a=a==null?!1:a,b=b==null?a:b;return this.map(function(){return f.clone(this,a,b)})},html:function(a){if(a===b)return this[0]&&this[0].nodeType===1?this[0].innerHTML.replace(Y,""):null;if(typeof a=="string"&&!bc.test(a)&&(f.support.leadingWhitespace||!Z.test(a))&&!bg[(_.exec(a)||["",""])[1].toLowerCase()]){a=a.replace($,"<$1></$2>");try{for(var c=0,d=this.length;c<d;c++)this[c].nodeType===1&&(f.cleanData(this[c].getElementsByTagName("*")),this[c].innerHTML=a)}catch(e){this.empty().append(a)}}else f.isFunction(a)?this.each(function(b){var c=f(this);c.html(a.call(this,b,c.html()))}):this.empty().append(a);return this},replaceWith:function(a){if(this[0]&&this[0].parentNode){if(f.isFunction(a))return this.each(function(b){var c=f(this),d=c.html();c.replaceWith(a.call(this,b,d))});typeof a!="string"&&(a=f(a).detach());return this.each(function(){var b=this.nextSibling,c=this.parentNode;f(this).remove(),b?f(b).before(a):f(c).append(a)})}return this.length?this.pushStack(f(f.isFunction(a)?a():a),"replaceWith",a):this},detach:function(a){return this.remove(a,!0)},domManip:function(a,c,d){var e,g,h,i,j=a[0],k=[];if(!f.support.checkClone&&arguments.length===3&&typeof j=="string"&&bd.test(j))return this.each(function(){f(this).domManip(a,c,d,!0)});if(f.isFunction(j))return this.each(function(e){var g=f(this);a[0]=j.call(this,e,c?g.html():b),g.domManip(a,c,d)});if(this[0]){i=j&&j.parentNode,f.support.parentNode&&i&&i.nodeType===11&&i.childNodes.length===this.length?e={fragment:i}:e=f.buildFragment(a,this,k),h=e.fragment,h.childNodes.length===1?g=h=h.firstChild:g=h.firstChild;if(g){c=c&&f.nodeName(g,"tr");for(var l=0,m=this.length,n=m-1;l<m;l++)d.call(c?bh(this[l],g):this[l],e.cacheable||m>1&&l<n?f.clone(h,!0,!0):h)}k.length&&f.each(k,bn)}return this}}),f.buildFragment=function(a,b,d){var e,g,h,i=b&&b[0]?b[0].ownerDocument||b[0]:c;a.length===1&&typeof a[0]=="string"&&a[0].length<512&&i===c&&a[0].charAt(0)==="<"&&!bc.test(a[0])&&(f.support.checkClone||!bd.test(a[0]))&&(g=!0,h=f.fragments[a[0]],h&&h!==1&&(e=h)),e||(e=i.createDocumentFragment(),f.clean(a,i,e,d)),g&&(f.fragments[a[0]]=h?e:1);return{fragment:e,cacheable:g}},f.fragments={},f.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){f.fn[a]=function(c){var d=[],e=f(c),g=this.length===1&&this[0].parentNode;if(g&&g.nodeType===11&&g.childNodes.length===1&&e.length===1){e[b](this[0]);return this}for(var h=0,i=e.length;h<i;h++){var j=(h>0?this.clone(!0):this).get();f(e[h])[b](j),d=d.concat(j)}return this.pushStack(d,a,e.selector)}}),f.extend({clone:function(a,b,c){var d=a.cloneNode(!0),e,g,h;if((!f.support.noCloneEvent||!f.support.noCloneChecked)&&(a.nodeType===1||a.nodeType===11)&&!f.isXMLDoc(a)){bj(a,d),e=bk(a),g=bk(d);for(h=0;e[h];++h)bj(e[h],g[h])}if(b){bi(a,d);if(c){e=bk(a),g=bk(d);for(h=0;e[h];++h)bi(e[h],g[h])}}return d},clean:function(a,b,d,e){var g;b=b||c,typeof b.createElement=="undefined"&&(b=b.ownerDocument||
-b[0]&&b[0].ownerDocument||c);var h=[],i;for(var j=0,k;(k=a[j])!=null;j++){typeof k=="number"&&(k+="");if(!k)continue;if(typeof k=="string")if(!bb.test(k))k=b.createTextNode(k);else{k=k.replace($,"<$1></$2>");var l=(_.exec(k)||["",""])[1].toLowerCase(),m=bg[l]||bg._default,n=m[0],o=b.createElement("div");o.innerHTML=m[1]+k+m[2];while(n--)o=o.lastChild;if(!f.support.tbody){var p=ba.test(k),q=l==="table"&&!p?o.firstChild&&o.firstChild.childNodes:m[1]==="<table>"&&!p?o.childNodes:[];for(i=q.length-1;i>=0;--i)f.nodeName(q[i],"tbody")&&!q[i].childNodes.length&&q[i].parentNode.removeChild(q[i])}!f.support.leadingWhitespace&&Z.test(k)&&o.insertBefore(b.createTextNode(Z.exec(k)[0]),o.firstChild),k=o.childNodes}var r;if(!f.support.appendChecked)if(k[0]&&typeof (r=k.length)=="number")for(i=0;i<r;i++)bm(k[i]);else bm(k);k.nodeType?h.push(k):h=f.merge(h,k)}if(d){g=function(a){return!a.type||be.test(a.type)};for(j=0;h[j];j++)if(e&&f.nodeName(h[j],"script")&&(!h[j].type||h[j].type.toLowerCase()==="text/javascript"))e.push(h[j].parentNode?h[j].parentNode.removeChild(h[j]):h[j]);else{if(h[j].nodeType===1){var s=f.grep(h[j].getElementsByTagName("script"),g);h.splice.apply(h,[j+1,0].concat(s))}d.appendChild(h[j])}}return h},cleanData:function(a){var b,c,d=f.cache,e=f.expando,g=f.event.special,h=f.support.deleteExpando;for(var i=0,j;(j=a[i])!=null;i++){if(j.nodeName&&f.noData[j.nodeName.toLowerCase()])continue;c=j[f.expando];if(c){b=d[c]&&d[c][e];if(b&&b.events){for(var k in b.events)g[k]?f.event.remove(j,k):f.removeEvent(j,k,b.handle);b.handle&&(b.handle.elem=null)}h?delete j[f.expando]:j.removeAttribute&&j.removeAttribute(f.expando),delete d[c]}}}});var bo=/alpha\([^)]*\)/i,bp=/opacity=([^)]*)/,bq=/-([a-z])/ig,br=/([A-Z]|^ms)/g,bs=/^-?\d+(?:px)?$/i,bt=/^-?\d/,bu=/^[+\-]=/,bv=/[^+\-\.\de]+/g,bw={position:"absolute",visibility:"hidden",display:"block"},bx=["Left","Right"],by=["Top","Bottom"],bz,bA,bB,bC=function(a,b){return b.toUpperCase()};f.fn.css=function(a,c){if(arguments.length===2&&c===b)return this;return f.access(this,a,c,!0,function(a,c,d){return d!==b?f.style(a,c,d):f.css(a,c)})},f.extend({cssHooks:{opacity:{get:function(a,b){if(b){var c=bz(a,"opacity","opacity");return c===""?"1":c}return a.style.opacity}}},cssNumber:{zIndex:!0,fontWeight:!0,opacity:!0,zoom:!0,lineHeight:!0,widows:!0,orphans:!0},cssProps:{"float":f.support.cssFloat?"cssFloat":"styleFloat"},style:function(a,c,d,e){if(!!a&&a.nodeType!==3&&a.nodeType!==8&&!!a.style){var g,h,i=f.camelCase(c),j=a.style,k=f.cssHooks[i];c=f.cssProps[i]||i;if(d===b){if(k&&"get"in k&&(g=k.get(a,!1,e))!==b)return g;return j[c]}h=typeof d;if(h==="number"&&isNaN(d)||d==null)return;h==="string"&&bu.test(d)&&(d=+d.replace(bv,"")+parseFloat(f.css(a,c))),h==="number"&&!f.cssNumber[i]&&(d+="px");if(!k||!("set"in k)||(d=k.set(a,d))!==b)try{j[c]=d}catch(l){}}},css:function(a,c,d){var e,g;c=f.camelCase(c),g=f.cssHooks[c],c=f.cssProps[c]||c,c==="cssFloat"&&(c="float");if(g&&"get"in g&&(e=g.get(a,!0,d))!==b)return e;if(bz)return bz(a,c)},swap:function(a,b,c){var d={};for(var e in b)d[e]=a.style[e],a.style[e]=b[e];c.call(a);for(e in b)a.style[e]=d[e]},camelCase:function(a){return a.replace(bq,bC)}}),f.curCSS=f.css,f.each(["height","width"],function(a,b){f.cssHooks[b]={get:function(a,c,d){var e;if(c){a.offsetWidth!==0?e=bD(a,b,d):f.swap(a,bw,function(){e=bD(a,b,d)});if(e<=0){e=bz(a,b,b),e==="0px"&&bB&&(e=bB(a,b,b));if(e!=null)return e===""||e==="auto"?"0px":e}if(e<0||e==null){e=a.style[b];return e===""||e==="auto"?"0px":e}return typeof e=="string"?e:e+"px"}},set:function(a,b){if(!bs.test(b))return b;b=parseFloat(b);if(b>=0)return b+"px"}}}),f.support.opacity||(f.cssHooks.opacity={get:function(a,b){return bp.test((b&&a.currentStyle?a.currentStyle.filter:a.style.filter)||"")?parseFloat(RegExp.$1)/100+"":b?"1":""},set:function(a,b){var c=a.style,d=a.currentStyle;c.zoom=1;var e=f.isNaN(b)?"":"alpha(opacity="+b*100+")",g=d&&d.filter||c.filter||"";c.filter=bo.test(g)?g.replace(bo,e):g+" "+e}}),f(function(){f.support.reliableMarginRight||(f.cssHooks.marginRight={get:function(a,b){var c;f.swap(a,{display:"inline-block"},function(){b?c=bz(a,"margin-right","marginRight"):c=a.style.marginRight});return c}})}),c.defaultView&&c.defaultView.getComputedStyle&&(bA=function(a,c){var d,e,g;c=c.replace(br,"-$1").toLowerCase();if(!(e=a.ownerDocument.defaultView))return b;if(g=e.getComputedStyle(a,null))d=g.getPropertyValue(c),d===""&&!f.contains(a.ownerDocument.documentElement,a)&&(d=f.style(a,c));return d}),c.documentElement.currentStyle&&(bB=function(a,b){var c,d=a.currentStyle&&a.currentStyle[b],e=a.runtimeStyle&&a.runtimeStyle[b],f=a.style;!bs.test(d)&&bt.test(d)&&(c=f.left,e&&(a.runtimeStyle.left=a.currentStyle.left),f.left=b==="fontSize"?"1em":d||0,d=f.pixelLeft+"px",f.left=c,e&&(a.runtimeStyle.left=e));return d===""?"auto":d}),bz=bA||bB,f.expr&&f.expr.filters&&(f.expr.filters.hidden=function(a){var b=a.offsetWidth,c=a.offsetHeight;return b===0&&c===0||!f.support.reliableHiddenOffsets&&(a.style.display||f.css(a,"display"))==="none"},f.expr.filters.visible=function(a){return!f.expr.filters.hidden(a)});var bE=/%20/g,bF=/\[\]$/,bG=/\r?\n/g,bH=/#.*$/,bI=/^(.*?):[ \t]*([^\r\n]*)\r?$/mg,bJ=/^(?:color|date|datetime|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i,bK=/^(?:about|app|app\-storage|.+\-extension|file|widget):$/,bL=/^(?:GET|HEAD)$/,bM=/^\/\//,bN=/\?/,bO=/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi,bP=/^(?:select|textarea)/i,bQ=/\s+/,bR=/([?&])_=[^&]*/,bS=/^([\w\+\.\-]+:)(?:\/\/([^\/?#:]*)(?::(\d+))?)?/,bT=f.fn.load,bU={},bV={},bW,bX;try{bW=e.href}catch(bY){bW=c.createElement("a"),bW.href="",bW=bW.href}bX=bS.exec(bW.toLowerCase())||[],f.fn.extend({load:function(a,c,d){if(typeof a!="string"&&bT)return bT.apply(this,arguments);if(!this.length)return this;var e=a.indexOf(" ");if(e>=0){var g=a.slice(e,a.length);a=a.slice(0,e)}var h="GET";c&&(f.isFunction(c)?(d=c,c=b):typeof c=="object"&&(c=f.param(c,f.ajaxSettings.traditional),h="POST"));var i=this;f.ajax({url:a,type:h,dataType:"html",data:c,complete:function(a,b,c){c=a.responseText,a.isResolved()&&(a.done(function(a){c=a}),i.html(g?f("<div>").append(c.replace(bO,"")).find(g):c)),d&&i.each(d,[c,b,a])}});return this},serialize:function(){return f.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?f.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||bP.test(this.nodeName)||bJ.test(this.type))}).map(function(a,b){var c=f(this).val();return c==null?null:f.isArray(c)?f.map(c,function(a,c){return{name:b.name,value:a.replace(bG,"\r\n")}}):{name:b.name,value:c.replace(bG,"\r\n")}}).get()}}),f.each("ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "),function(a,b){f.fn[b]=function(a){return this.bind(b,a)}}),f.each(["get","post"],function(a,c){f[c]=function(a,d,e,g){f.isFunction(d)&&(g=g||e,e=d,d=b);return f.ajax({type:c,url:a,data:d,success:e,dataType:g})}}),f.extend({getScript:function(a,c){return f.get(a,b,c,"script")},getJSON:function(a,b,c){return f.get(a,b,c,"json")},ajaxSetup:function(a,b){b?f.extend(!0,a,f.ajaxSettings,b):(b=a,a=f.extend(!0,f.ajaxSettings,b));for(var c in{context:1,url:1})c in b?a[c]=b[c]:c in f.ajaxSettings&&(a[c]=f.ajaxSettings[c]);return a},ajaxSettings:{url:bW,isLocal:bK.test(bX[1]),global:!0,type:"GET",contentType:"application/x-www-form-urlencoded",processData:!0,async:!0,accepts:{xml:"application/xml, text/xml",html:"text/html",text:"text/plain",json:"application/json, text/javascript","*":"*/*"},contents:{xml:/xml/,html:/html/,json:/json/},responseFields:{xml:"responseXML",text:"responseText"},converters:{"* text":a.String,"text html":!0,"text json":f.parseJSON,"text xml":f.parseXML}},ajaxPrefilter:bZ(bU),ajaxTransport:bZ(bV),ajax:function(a,c){function w(a,c,l,m){if(s!==2){s=2,q&&clearTimeout(q),p=b,n=m||"",v.readyState=a?4:0;var o,r,u,w=l?ca(d,v,l):b,x,y;if(a>=200&&a<300||a===304){if(d.ifModified){if(x=v.getResponseHeader("Last-Modified"))f.lastModified[k]=x;if(y=v.getResponseHeader("Etag"))f.etag[k]=y}if(a===304)c="notmodified",o=!0;else try{r=cb(d,w),c="success",o=!0}catch(z){c="parsererror",u=z}}else{u=c;if(!c||a)c="error",a<0&&(a=0)}v.status=a,v.statusText=c,o?h.resolveWith(e,[r,c,v]):h.rejectWith(e,[v,c,u]),v.statusCode(j),j=b,t&&g.trigger("ajax"+(o?"Success":"Error"),[v,d,o?r:u]),i.resolveWith(e,[v,c]),t&&(g.trigger("ajaxComplete",[v,d]),--f.active||f.event.trigger("ajaxStop"))}}typeof a=="object"&&(c=a,a=b),c=c||{};var d=f.ajaxSetup({},c),e=d.context||d,g=e!==d&&(e.nodeType||e instanceof f)?f(e):f.event,h=f.Deferred(),i=f._Deferred(),j=d.statusCode||{},k,l={},m={},n,o,p,q,r,s=0,t,u,v={readyState:0,setRequestHeader:function(a,b){if(!s){var c=a.toLowerCase();a=m[c]=m[c]||a,l[a]=b}return this},getAllResponseHeaders:function(){return s===2?n:null},getResponseHeader:function(a){var c;if(s===2){if(!o){o={};while(c=bI.exec(n))o[c[1].toLowerCase()]=c[2]}c=o[a.toLowerCase()]}return c===b?null:c},overrideMimeType:function(a){s||(d.mimeType=a);return this},abort:function(a){a=a||"abort",p&&p.abort(a),w(0,a);return this}};h.promise(v),v.success=v.done,v.error=v.fail,v.complete=i.done,v.statusCode=function(a){if(a){var b;if(s<2)for(b in a)j[b]=[j[b],a[b]];else b=a[v.status],v.then(b,b)}return this},d.url=((a||d.url)+"").replace(bH,"").replace(bM,bX[1]+"//"),d.dataTypes=f.trim(d.dataType||"*").toLowerCase().split(bQ),d.crossDomain==null&&(r=bS.exec(d.url.toLowerCase()),d.crossDomain=!(!r||r[1]==bX[1]&&r[2]==bX[2]&&(r[3]||(r[1]==="http:"?80:443))==(bX[3]||(bX[1]==="http:"?80:443)))),d.data&&d.processData&&typeof d.data!="string"&&(d.data=f.param(d.data,d.traditional)),b$(bU,d,c,v);if(s===2)return!1;t=d.global,d.type=d.type.toUpperCase(),d.hasContent=!bL.test(d.type),t&&f.active++===0&&f.event.trigger("ajaxStart");if(!d.hasContent){d.data&&(d.url+=(bN.test(d.url)?"&":"?")+d.data),k=d.url;if(d.cache===!1){var x=f.now(),y=d.url.replace(bR,"$1_="+x);d.url=y+(y===d.url?(bN.test(d.url)?"&":"?")+"_="+x:"")}}(d.data&&d.hasContent&&d.contentType!==!1||c.contentType)&&v.setRequestHeader("Content-Type",d.contentType),d.ifModified&&(k=k||d.url,f.lastModified[k]&&v.setRequestHeader("If-Modified-Since",f.lastModified[k]),f.etag[k]&&v.setRequestHeader("If-None-Match",f.etag[k])),v.setRequestHeader("Accept",d.dataTypes[0]&&d.accepts[d.dataTypes[0]]?d.accepts[d.dataTypes[0]]+(d.dataTypes[0]!=="*"?", */*; q=0.01":""):d.accepts["*"]);for(u in d.headers)v.setRequestHeader(u,d.headers[u]);if(d.beforeSend&&(d.beforeSend.call(e,v,d)===!1||s===2)){v.abort();return!1}for(u in{success:1,error:1,complete:1})v[u](d[u]);p=b$(bV,d,c,v);if(!p)w(-1,"No Transport");else{v.readyState=1,t&&g.trigger("ajaxSend",[v,d]),d.async&&d.timeout>0&&(q=setTimeout(function(){v.abort("timeout")},d.timeout));try{s=1,p.send(l,w)}catch(z){status<2?w(-1,z):f.error(z)}}return v},param:function(a,c){var d=[],e=function(a,b){b=f.isFunction(b)?b():b,d[d.length]=encodeURIComponent(a)+"="+encodeURIComponent(b)};c===b&&(c=f.ajaxSettings.traditional);if(f.isArray(a)||a.jquery&&!f.isPlainObject(a))f.each(a,function(){e(this.name,this.value)});else for(var g in a)b_(g,a[g],c,e);return d.join("&").replace(bE,"+")}}),f.extend({active:0,lastModified:{},etag:{}});var cc=f.now(),cd=/(\=)\?(&|$)|\?\?/i;f.ajaxSetup({jsonp:"callback",jsonpCallback:function(){return f.expando+"_"+cc++}}),f.ajaxPrefilter("json jsonp",function(b,c,d){var e=b.contentType==="application/x-www-form-urlencoded"&&typeof b.data=="string";if(b.dataTypes[0]==="jsonp"||b.jsonp!==!1&&(cd.test(b.url)||e&&cd.test(b.data))){var g,h=b.jsonpCallback=f.isFunction(b.jsonpCallback)?b.jsonpCallback():b.jsonpCallback,i=a[h],j=b.url,k=b.data,l="$1"+h+"$2";b.jsonp!==!1&&(j=j.replace(cd,l),b.url===j&&(e&&(k=k.replace(cd,l)),b.data===k&&(j+=(/\?/.test(j)?"&":"?")+b.jsonp+"="+h))),b.url=j,b.data=k,a[h]=function(a){g=[a]},d.always(function(){a[h]=i,g&&f.isFunction(i)&&a[h](g[0])}),b.converters["script json"]=function(){g||f.error(h+" was not called");return g[0]},b.dataTypes[0]="json";return"script"}}),f.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/javascript|ecmascript/},converters:{"text script":function(a){f.globalEval(a);return a}}}),f.ajaxPrefilter("script",function(a){a.cache===b&&(a.cache=!1),a.crossDomain&&(a.type="GET",a.global=!1)}),f.ajaxTransport("script",function(a){if(a.crossDomain){var d,e=c.head||c.getElementsByTagName("head")[0]||c.documentElement;return{send:function(f,g){d=c.createElement("script"),d.async="async",a.scriptCharset&&(d.charset=a.scriptCharset),d.src=a.url,d.onload=d.onreadystatechange=function(a,c){if(c||!d.readyState||/loaded|complete/.test(d.readyState))d.onload=d.onreadystatechange=null,e&&d.parentNode&&e.removeChild(d),d=b,c||g(200,"success")},e.insertBefore(d,e.firstChild)},abort:function(){d&&d.onload(0,1)}}}});var ce=a.ActiveXObject?function(){for(var a in cg)cg[a](0,1)}:!1,cf=0,cg;f.ajaxSettings.xhr=a.ActiveXObject?function(){return!this.isLocal&&ch()||ci()}:ch,function(a){f.extend(f.support,{ajax:!!a,cors:!!a&&"withCredentials"in a})}(f.ajaxSettings.xhr()),f.support.ajax&&f.ajaxTransport(function(c){if(!c.crossDomain||f.support.cors){var d;return{send:function(e,g){var h=c.xhr(),i,j;c.username?h.open(c.type,c.url,c.async,c.username,c.password):h.open(c.type,c.url,c.async);if(c.xhrFields)for(j in c.xhrFields)h[j]=c.xhrFields[j];c.mimeType&&h.overrideMimeType&&h.overrideMimeType(c.mimeType),!c.crossDomain&&!e["X-Requested-With"]&&(e["X-Requested-With"]="XMLHttpRequest");try{for(j in e)h.setRequestHeader(j,e[j])}catch(k){}h.send(c.hasContent&&c.data||null),d=function(a,e){var j,k,l,m,n;try{if(d&&(e||h.readyState===4)){d=b,i&&(h.onreadystatechange=f.noop,ce&&delete cg[i]);if(e)h.readyState!==4&&h.abort();else{j=h.status,l=h.getAllResponseHeaders(),m={},n=h.responseXML,n&&n.documentElement&&(m.xml=n),m.text=h.responseText;try{k=h.statusText}catch(o){k=""}!j&&c.isLocal&&!c.crossDomain?j=m.text?200:404:j===1223&&(j=204)}}}catch(p){e||g(-1,p)}m&&g(j,k,m,l)},!c.async||h.readyState===4?d():(i=++cf,ce&&(cg||(cg={},f(a).unload(ce)),cg[i]=d),h.onreadystatechange=d)},abort:function(){d&&d(0,1)}}}});var cj={},ck,cl,cm=/^(?:toggle|show|hide)$/,cn=/^([+\-]=)?([\d+.\-]+)([a-z%]*)$/i,co,cp=[["height","marginTop","marginBottom","paddingTop","paddingBottom"],["width","marginLeft","marginRight","paddingLeft","paddingRight"],["opacity"]],cq,cr=a.webkitRequestAnimationFrame||a.mozRequestAnimationFrame||a.oRequestAnimationFrame;f.fn.extend({show:function(a,b,c){var d,e;if(a||a===0)return this.animate(cu("show",3),a,b,c);for(var g=0,h=this.length;g<h;g++)d=this[g],d.style&&(e=d.style.display,!f._data(d,"olddisplay")&&e==="none"&&(e=d.style.display=""),e===""&&f.css(d,"display")==="none"&&f._data(d,"olddisplay",cv(d.nodeName)));for(g=0;g<h;g++){d=this[g];if(d.style){e=d.style.display;if(e===""||e==="none")d.style.display=f._data(d,"olddisplay")||""}}return this},hide:function(a,b,c){if(a||a===0)return this.animate(cu("hide",3),a,b,c);for(var d=0,e=this.length;d<e;d++)if(this[d].style){var g=f.css(this[d],"display");g!=="none"&&!f._data(this[d],"olddisplay")&&f._data(this[d],"olddisplay",g)}for(d=0;d<e;d++)this[d].style&&(this[d].style.display="none");return this},_toggle:f.fn.toggle,toggle:function(a,b,c){var d=typeof a=="boolean";f.isFunction(a)&&f.isFunction(b)?this._toggle.apply(this,arguments):a==null||d?this.each(function(){var b=d?a:f(this).is(":hidden");f(this)[b?"show":"hide"]()}):this.animate(cu("toggle",3),a,b,c);return this},fadeTo:function(a,b,c,d){return this.filter(":hidden").css("opacity",0).show().end().animate({opacity:b},a,c,d)},animate:function(a,b,c,d){var e=f.speed(b,c,d);if(f.isEmptyObject(a))return this.each(e.complete,[!1]);a=f.extend({},a);return this[e.queue===!1?"each":"queue"](function(){e.queue===!1&&f._mark(this);var b=f.extend({},e),c=this.nodeType===1,d=c&&f(this).is(":hidden"),g,h,i,j,k,l,m,n,o;b.animatedProperties={};for(i in a){g=f.camelCase(i),i!==g&&(a[g]=a[i],delete a[i]),h=a[g],f.isArray(h)?(b.animatedProperties[g]=h[1],h=a[g]=h[0]):b.animatedProperties[g]=b.specialEasing&&b.specialEasing[g]||b.easing||"swing";if(h==="hide"&&d||h==="show"&&!d)return b.complete.call(this);c&&(g==="height"||g==="width")&&(b.overflow=[this.style.overflow,this.style.overflowX,this.style.overflowY],f.css(this,"display")==="inline"&&f.css(this,"float")==="none"&&(f.support.inlineBlockNeedsLayout?(j=cv(this.nodeName),j==="inline"?this.style.display="inline-block":(this.style.display="inline",this.style.zoom=1)):this.style.display="inline-block"))}b.overflow!=null&&(this.style.overflow="hidden");for(i in a)k=new f.fx(this,b,i),h=a[i],cm.test(h)?k[h==="toggle"?d?"show":"hide":h]():(l=cn.exec(h),m=k.cur(),l?(n=parseFloat(l[2]),o=l[3]||(f.cssNumber[i]?"":"px"),o!=="px"&&(f.style(this,i,(n||1)+o),m=(n||1)/k.cur()*m,f.style(this,i,m+o)),l[1]&&(n=(l[1]==="-="?-1:1)*n+m),k.custom(m,n,o)):k.custom(m,h,""));return!0})},stop:function(a,b){a&&this.queue([]),this.each(function(){var a=f.timers,c=a.length;b||f._unmark(!0,this);while(c--)a[c].elem===this&&(b&&a[c](!0),a.splice(c,1))}),b||this.dequeue();return this}}),f.each({slideDown:cu("show",1),slideUp:cu("hide",1),slideToggle:cu("toggle",1),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"},fadeToggle:{opacity:"toggle"}},function(a,b){f.fn[a]=function(a,c,d){return this.animate(b,a,c,d)}}),f.extend({speed:function(a,b,c){var d=a&&typeof a=="object"?f.extend({},a):{complete:c||!c&&b||f.isFunction(a)&&a,duration:a,easing:c&&b||b&&!f.isFunction(b)&&b};d.duration=f.fx.off?0:typeof d.duration=="number"?d.duration:d.duration in f.fx.speeds?f.fx.speeds[d.duration]:f.fx.speeds._default,d.old=d.complete,d.complete=function(a){d.queue!==!1?f.dequeue(this):a!==!1&&f._unmark(this),f.isFunction(d.old)&&d.old.call(this)};return d},easing:{linear:function(a,b,c,d){return c+d*a},swing:function(a,b,c,d){return(-Math.cos(a*Math.PI)/2+.5)*d+c}},timers:[],fx:function(a,b,c){this.options=b,this.elem=a,this.prop=c,b.orig=b.orig||{}}}),f.fx.prototype={update:function(){this.options.step&&this.options.step.call(this.elem,this.now,this),(f.fx.step[this.prop]||f.fx.step._default)(this)},cur:function(){if(this.elem[this.prop]!=null&&(!this.elem.style||this.elem.style[this.prop]==null))return this.elem[this.prop];var a,b=f.css(this.elem,this.prop);return isNaN(a=parseFloat(b))?!b||b==="auto"?0:b:a},custom:function(a,b,c){function h(a){return d.step(a)}var d=this,e=f.fx,g;this.startTime=cq||cs(),this.start=a,this.end=b,this.unit=c||this.unit||(f.cssNumber[this.prop]?"":"px"),this.now=this.start,this.pos=this.state=0,h.elem=this.elem,h()&&f.timers.push(h)&&!co&&(cr?(co=1,g=function(){co&&(cr(g),e.tick())},cr(g)):co=setInterval(e.tick,e.interval))},show:function(){this.options.orig[this.prop]=f.style(this.elem,this.prop),this.options.show=!0,this.custom(this.prop==="width"||this.prop==="height"?1:0,this.cur()),f(this.elem).show()},hide:function(){this.options.orig[this.prop]=f.style(this.elem,this.prop),this.options.hide=!0,this.custom(this.cur(),0)},step:function(a){var b=cq||cs(),c=!0,d=this.elem,e=this.options,g,h;if(a||b>=e.duration+this.startTime){this.now=this.end,this.pos=this.state=1,this.update(),e.animatedProperties[this.prop]=!0;for(g in e.animatedProperties)e.animatedProperties[g]!==!0&&(c=!1);if(c){e.overflow!=null&&!f.support.shrinkWrapBlocks&&f.each(["","X","Y"],function(a,b){d.style["overflow"+b]=e.overflow[a]}),e.hide&&f(d).hide();if(e.hide||e.show)for(var i in e.animatedProperties)f.style(d,i,e.orig[i]);e.complete.call(d)}return!1}e.duration==Infinity?this.now=b:(h=b-this.startTime,this.state=h/e.duration,this.pos=f.easing[e.animatedProperties[this.prop]](this.state,h,0,1,e.duration),this.now=this.start+(this.end-this.start)*this.pos),this.update();return!0}},f.extend(f.fx,{tick:function(){for(var a=f.timers,b=0;b<a.length;++b)a[b]()||a.splice(b--,1);a.length||f.fx.stop()},interval:13,stop:function(){clearInterval(co),co=null},speeds:{slow:600,fast:200,_default:400},step:{opacity:function(a){f.style(a.elem,"opacity",a.now)},_default:function(a){a.elem.style&&a.elem.style[a.prop]!=null?a.elem.style[a.prop]=(a.prop==="width"||a.prop==="height"?Math.max(0,a.now):a.now)+a.unit:a.elem[a.prop]=a.now}}}),f.expr&&f.expr.filters&&(f.expr.filters.animated=function(a){return f.grep(f.timers,function(b){return a===b.elem}).length});var cw=/^t(?:able|d|h)$/i,cx=/^(?:body|html)$/i;"getBoundingClientRect"in c.documentElement?f.fn.offset=function(a){var b=this[0],c;if(a)return this.each(function(b){f.offset.setOffset(this,a,b)});if(!b||!b.ownerDocument)return null;if(b===b.ownerDocument.body)return f.offset.bodyOffset(b);try{c=b.getBoundingClientRect()}catch(d){}var e=b.ownerDocument,g=e.documentElement;if(!c||!f.contains(g,b))return c?{top:c.top,left:c.left}:{top:0,left:0};var h=e.body,i=cy(e),j=g.clientTop||h.clientTop||0,k=g.clientLeft||h.clientLeft||0,l=i.pageYOffset||f.support.boxModel&&g.scrollTop||h.scrollTop,m=i.pageXOffset||f.support.boxModel&&g.scrollLeft||h.scrollLeft,n=c.top+l-j,o=c.left+m-k;return{top:n,left:o}}:f.fn.offset=function(a){var b=this[0];if(a)return this.each(function(b){f.offset.setOffset(this,a,b)});if(!b||!b.ownerDocument)return null;if(b===b.ownerDocument.body)return f.offset.bodyOffset(b);f.offset.initialize();var c,d=b.offsetParent,e=b,g=b.ownerDocument,h=g.documentElement,i=g.body,j=g.defaultView,k=j?j.getComputedStyle(b,null):b.currentStyle,l=b.offsetTop,m=b.offsetLeft;while((b=b.parentNode)&&b!==i&&b!==h){if(f.offset.supportsFixedPosition&&k.position==="fixed")break;c=j?j.getComputedStyle(b,null):b.currentStyle,l-=b.scrollTop,m-=b.scrollLeft,b===d&&(l+=b.offsetTop,m+=b.offsetLeft,f.offset.doesNotAddBorder&&(!f.offset.doesAddBorderForTableAndCells||!cw.test(b.nodeName))&&(l+=parseFloat(c.borderTopWidth)||0,m+=parseFloat(c.borderLeftWidth)||0),e=d,d=b.offsetParent),f.offset.subtractsBorderForOverflowNotVisible&&c.overflow!=="visible"&&(l+=parseFloat(c.borderTopWidth)||0,m+=parseFloat(c.borderLeftWidth)||0),k=c}if(k.position==="relative"||k.position==="static")l+=i.offsetTop,m+=i.offsetLeft;f.offset.supportsFixedPosition&&k.position==="fixed"&&(l+=Math.max(h.scrollTop,i.scrollTop),m+=Math.max(h.scrollLeft,i.scrollLeft));return{top:l,left:m}},f.offset={initialize:function(){var a=c.body,b=c.createElement("div"),d,e,g,h,i=parseFloat(f.css(a,"marginTop"))||0,j="<div style='position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;'><div></div></div><table style='position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;' cellpadding='0' cellspacing='0'><tr><td></td></tr></table>";f.extend(b.style,{position:"absolute",top:0,left:0,margin:0,border:0,width:"1px",height:"1px",visibility:"hidden"}),b.innerHTML=j,a.insertBefore(b,a.firstChild),d=b.firstChild,e=d.firstChild,h=d.nextSibling.firstChild.firstChild,this.doesNotAddBorder=e.offsetTop!==5,this.doesAddBorderForTableAndCells=h.offsetTop===5,e.style.position="fixed",e.style.top="20px",this.supportsFixedPosition=e.offsetTop===20||e.offsetTop===15,e.style.position=e.style.top="",d.style.overflow="hidden",d.style.position="relative",this.subtractsBorderForOverflowNotVisible=e.offsetTop===-5,this.doesNotIncludeMarginInBodyOffset=a.offsetTop!==i,a.removeChild(b),f.offset.initialize=f.noop},bodyOffset:function(a){var b=a.offsetTop,c=a.offsetLeft;f.offset.initialize(),f.offset.doesNotIncludeMarginInBodyOffset&&(b+=parseFloat(f.css(a,"marginTop"))||0,c+=parseFloat(f.css(a,"marginLeft"))||0);return{top:b,left:c}},setOffset:function(a,b,c){var d=f.css(a,"position");d==="static"&&(a.style.position="relative");var e=f(a),g=e.offset(),h=f.css(a,"top"),i=f.css(a,"left"),j=(d==="absolute"||d==="fixed")&&f.inArray("auto",[h,i])>-1,k={},l={},m,n;j?(l=e.position(),m=l.top,n=l.left):(m=parseFloat(h)||0,n=parseFloat(i)||0),f.isFunction(b)&&(b=b.call(a,c,g)),b.top!=null&&(k.top=b.top-g.top+m),b.left!=null&&(k.left=b.left-g.left+n),"using"in b?b.using.call(a,k):e.css(k)}},f.fn.extend({position:function(){if(!this[0])return null;var a=this[0],b=this.offsetParent(),c=this.offset(),d=cx.test(b[0].nodeName)?{top:0,left:0}:b.offset();c.top-=parseFloat(f.css(a,"marginTop"))||0,c.left-=parseFloat(f.css(a,"marginLeft"))||0,d.top+=parseFloat(f.css(b[0],"borderTopWidth"))||0,d.left+=parseFloat(f.css(b[0],"borderLeftWidth"))||0;return{top:c.top-d.top,left:c.left-d.left}},offsetParent:function(){return this.map(function(){var a=this.offsetParent||c.body;while(a&&!cx.test(a.nodeName)&&f.css(a,"position")==="static")a=a.offsetParent;return a})}}),f.each(["Left","Top"],function(a,c){var d="scroll"+c;f.fn[d]=function(c){var e,g;if(c===b){e=this[0];if(!e)return null;g=cy(e);return g?"pageXOffset"in g?g[a?"pageYOffset":"pageXOffset"]:f.support.boxModel&&g.document.documentElement[d]||g.document.body[d]:e[d]}return this.each(function(){g=cy(this),g?g.scrollTo(a?f(g).scrollLeft():c,a?c:f(g).scrollTop()):this[d]=c})}}),f.each(["Height","Width"],function(a,c){var d=c.toLowerCase();f.fn["inner"+c]=function(){return this[0]?parseFloat(f.css(this[0],d,"padding")):null},f.fn["outer"+c]=function(a){return this[0]?parseFloat(f.css(this[0],d,a?"margin":"border")):null},f.fn[d]=function(a){var e=this[0];if(!e)return a==null?null:this;if(f.isFunction(a))return this.each(function(b){var c=f(this);c[d](a.call(this,b,c[d]()))});if(f.isWindow(e)){var g=e.document.documentElement["client"+c];return e.document.compatMode==="CSS1Compat"&&g||e.document.body["client"+c]||g}if(e.nodeType===9)return Math.max(e.documentElement["client"+c],e.body["scroll"+c],e.documentElement["scroll"+c],e.body["offset"+c],e.documentElement["offset"+c]);if(a===b){var h=f.css(e,d),i=parseFloat(h);return f.isNaN(i)?h:i}return this.css(d,typeof a=="string"?a:a+"px")}}),a.jQuery=a.$=f})(window);
\ Pas de fin de ligne à la fin du fichier
+/*! jQuery v1.11.1 | (c) 2005, 2014 jQuery Foundation, Inc. | jquery.org/license */
+!function(a,b){"object"==typeof module&&"object"==typeof module.exports?module.exports=a.document?b(a,!0):function(a){if(!a.document)throw new Error("jQuery requires a window with a document");return b(a)}:b(a)}("undefined"!=typeof window?window:this,function(a,b){var c=[],d=c.slice,e=c.concat,f=c.push,g=c.indexOf,h={},i=h.toString,j=h.hasOwnProperty,k={},l="1.11.1",m=function(a,b){return new m.fn.init(a,b)},n=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,o=/^-ms-/,p=/-([\da-z])/gi,q=function(a,b){return b.toUpperCase()};m.fn=m.prototype={jquery:l,constructor:m,selector:"",length:0,toArray:function(){return d.call(this)},get:function(a){return null!=a?0>a?this[a+this.length]:this[a]:d.call(this)},pushStack:function(a){var b=m.merge(this.constructor(),a);return b.prevObject=this,b.context=this.context,b},each:function(a,b){return m.each(this,a,b)},map:function(a){return this.pushStack(m.map(this,function(b,c){return a.call(b,c,b)}))},slice:function(){return this.pushStack(d.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(a){var b=this.length,c=+a+(0>a?b:0);return this.pushStack(c>=0&&b>c?[this[c]]:[])},end:function(){return this.prevObject||this.constructor(null)},push:f,sort:c.sort,splice:c.splice},m.extend=m.fn.extend=function(){var a,b,c,d,e,f,g=arguments[0]||{},h=1,i=arguments.length,j=!1;for("boolean"==typeof g&&(j=g,g=arguments[h]||{},h++),"object"==typeof g||m.isFunction(g)||(g={}),h===i&&(g=this,h--);i>h;h++)if(null!=(e=arguments[h]))for(d in e)a=g[d],c=e[d],g!==c&&(j&&c&&(m.isPlainObject(c)||(b=m.isArray(c)))?(b?(b=!1,f=a&&m.isArray(a)?a:[]):f=a&&m.isPlainObject(a)?a:{},g[d]=m.extend(j,f,c)):void 0!==c&&(g[d]=c));return g},m.extend({expando:"jQuery"+(l+Math.random()).replace(/\D/g,""),isReady:!0,error:function(a){throw new Error(a)},noop:function(){},isFunction:function(a){return"function"===m.type(a)},isArray:Array.isArray||function(a){return"array"===m.type(a)},isWindow:function(a){return null!=a&&a==a.window},isNumeric:function(a){return!m.isArray(a)&&a-parseFloat(a)>=0},isEmptyObject:function(a){var b;for(b in a)return!1;return!0},isPlainObject:function(a){var b;if(!a||"object"!==m.type(a)||a.nodeType||m.isWindow(a))return!1;try{if(a.constructor&&!j.call(a,"constructor")&&!j.call(a.constructor.prototype,"isPrototypeOf"))return!1}catch(c){return!1}if(k.ownLast)for(b in a)return j.call(a,b);for(b in a);return void 0===b||j.call(a,b)},type:function(a){return null==a?a+"":"object"==typeof a||"function"==typeof a?h[i.call(a)]||"object":typeof a},globalEval:function(b){b&&m.trim(b)&&(a.execScript||function(b){a.eval.call(a,b)})(b)},camelCase:function(a){return a.replace(o,"ms-").replace(p,q)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toLowerCase()===b.toLowerCase()},each:function(a,b,c){var d,e=0,f=a.length,g=r(a);if(c){if(g){for(;f>e;e++)if(d=b.apply(a[e],c),d===!1)break}else for(e in a)if(d=b.apply(a[e],c),d===!1)break}else if(g){for(;f>e;e++)if(d=b.call(a[e],e,a[e]),d===!1)break}else for(e in a)if(d=b.call(a[e],e,a[e]),d===!1)break;return a},trim:function(a){return null==a?"":(a+"").replace(n,"")},makeArray:function(a,b){var c=b||[];return null!=a&&(r(Object(a))?m.merge(c,"string"==typeof a?[a]:a):f.call(c,a)),c},inArray:function(a,b,c){var d;if(b){if(g)return g.call(b,a,c);for(d=b.length,c=c?0>c?Math.max(0,d+c):c:0;d>c;c++)if(c in b&&b[c]===a)return c}return-1},merge:function(a,b){var c=+b.length,d=0,e=a.length;while(c>d)a[e++]=b[d++];if(c!==c)while(void 0!==b[d])a[e++]=b[d++];return a.length=e,a},grep:function(a,b,c){for(var d,e=[],f=0,g=a.length,h=!c;g>f;f++)d=!b(a[f],f),d!==h&&e.push(a[f]);return e},map:function(a,b,c){var d,f=0,g=a.length,h=r(a),i=[];if(h)for(;g>f;f++)d=b(a[f],f,c),null!=d&&i.push(d);else for(f in a)d=b(a[f],f,c),null!=d&&i.push(d);return e.apply([],i)},guid:1,proxy:function(a,b){var c,e,f;return"string"==typeof b&&(f=a[b],b=a,a=f),m.isFunction(a)?(c=d.call(arguments,2),e=function(){return a.apply(b||this,c.concat(d.call(arguments)))},e.guid=a.guid=a.guid||m.guid++,e):void 0},now:function(){return+new Date},support:k}),m.each("Boolean Number String Function Array Date RegExp Object Error".split(" "),function(a,b){h["[object "+b+"]"]=b.toLowerCase()});function r(a){var b=a.length,c=m.type(a);return"function"===c||m.isWindow(a)?!1:1===a.nodeType&&b?!0:"array"===c||0===b||"number"==typeof b&&b>0&&b-1 in a}var s=function(a){var b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u="sizzle"+-new Date,v=a.document,w=0,x=0,y=gb(),z=gb(),A=gb(),B=function(a,b){return a===b&&(l=!0),0},C="undefined",D=1<<31,E={}.hasOwnProperty,F=[],G=F.pop,H=F.push,I=F.push,J=F.slice,K=F.indexOf||function(a){for(var b=0,c=this.length;c>b;b++)if(this[b]===a)return b;return-1},L="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",M="[\\x20\\t\\r\\n\\f]",N="(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",O=N.replace("w","w#"),P="\\["+M+"*("+N+")(?:"+M+"*([*^$|!~]?=)"+M+"*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|("+O+"))|)"+M+"*\\]",Q=":("+N+")(?:\\((('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|((?:\\\\.|[^\\\\()[\\]]|"+P+")*)|.*)\\)|)",R=new RegExp("^"+M+"+|((?:^|[^\\\\])(?:\\\\.)*)"+M+"+$","g"),S=new RegExp("^"+M+"*,"+M+"*"),T=new RegExp("^"+M+"*([>+~]|"+M+")"+M+"*"),U=new RegExp("="+M+"*([^\\]'\"]*?)"+M+"*\\]","g"),V=new RegExp(Q),W=new RegExp("^"+O+"$"),X={ID:new RegExp("^#("+N+")"),CLASS:new RegExp("^\\.("+N+")"),TAG:new RegExp("^("+N.replace("w","w*")+")"),ATTR:new RegExp("^"+P),PSEUDO:new RegExp("^"+Q),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+M+"*(even|odd|(([+-]|)(\\d*)n|)"+M+"*(?:([+-]|)"+M+"*(\\d+)|))"+M+"*\\)|)","i"),bool:new RegExp("^(?:"+L+")$","i"),needsContext:new RegExp("^"+M+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+M+"*((?:-\\d)?\\d*)"+M+"*\\)|)(?=[^-]|$)","i")},Y=/^(?:input|select|textarea|button)$/i,Z=/^h\d$/i,$=/^[^{]+\{\s*\[native \w/,_=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ab=/[+~]/,bb=/'|\\/g,cb=new RegExp("\\\\([\\da-f]{1,6}"+M+"?|("+M+")|.)","ig"),db=function(a,b,c){var d="0x"+b-65536;return d!==d||c?b:0>d?String.fromCharCode(d+65536):String.fromCharCode(d>>10|55296,1023&d|56320)};try{I.apply(F=J.call(v.childNodes),v.childNodes),F[v.childNodes.length].nodeType}catch(eb){I={apply:F.length?function(a,b){H.apply(a,J.call(b))}:function(a,b){var c=a.length,d=0;while(a[c++]=b[d++]);a.length=c-1}}}function fb(a,b,d,e){var f,h,j,k,l,o,r,s,w,x;if((b?b.ownerDocument||b:v)!==n&&m(b),b=b||n,d=d||[],!a||"string"!=typeof a)return d;if(1!==(k=b.nodeType)&&9!==k)return[];if(p&&!e){if(f=_.exec(a))if(j=f[1]){if(9===k){if(h=b.getElementById(j),!h||!h.parentNode)return d;if(h.id===j)return d.push(h),d}else if(b.ownerDocument&&(h=b.ownerDocument.getElementById(j))&&t(b,h)&&h.id===j)return d.push(h),d}else{if(f[2])return I.apply(d,b.getElementsByTagName(a)),d;if((j=f[3])&&c.getElementsByClassName&&b.getElementsByClassName)return I.apply(d,b.getElementsByClassName(j)),d}if(c.qsa&&(!q||!q.test(a))){if(s=r=u,w=b,x=9===k&&a,1===k&&"object"!==b.nodeName.toLowerCase()){o=g(a),(r=b.getAttribute("id"))?s=r.replace(bb,"\\$&"):b.setAttribute("id",s),s="[id='"+s+"'] ",l=o.length;while(l--)o[l]=s+qb(o[l]);w=ab.test(a)&&ob(b.parentNode)||b,x=o.join(",")}if(x)try{return I.apply(d,w.querySelectorAll(x)),d}catch(y){}finally{r||b.removeAttribute("id")}}}return i(a.replace(R,"$1"),b,d,e)}function gb(){var a=[];function b(c,e){return a.push(c+" ")>d.cacheLength&&delete b[a.shift()],b[c+" "]=e}return b}function hb(a){return a[u]=!0,a}function ib(a){var b=n.createElement("div");try{return!!a(b)}catch(c){return!1}finally{b.parentNode&&b.parentNode.removeChild(b),b=null}}function jb(a,b){var c=a.split("|"),e=a.length;while(e--)d.attrHandle[c[e]]=b}function kb(a,b){var c=b&&a,d=c&&1===a.nodeType&&1===b.nodeType&&(~b.sourceIndex||D)-(~a.sourceIndex||D);if(d)return d;if(c)while(c=c.nextSibling)if(c===b)return-1;return a?1:-1}function lb(a){return function(b){var c=b.nodeName.toLowerCase();return"input"===c&&b.type===a}}function mb(a){return function(b){var c=b.nodeName.toLowerCase();return("input"===c||"button"===c)&&b.type===a}}function nb(a){return hb(function(b){return b=+b,hb(function(c,d){var e,f=a([],c.length,b),g=f.length;while(g--)c[e=f[g]]&&(c[e]=!(d[e]=c[e]))})})}function ob(a){return a&&typeof a.getElementsByTagName!==C&&a}c=fb.support={},f=fb.isXML=function(a){var b=a&&(a.ownerDocument||a).documentElement;return b?"HTML"!==b.nodeName:!1},m=fb.setDocument=function(a){var b,e=a?a.ownerDocument||a:v,g=e.defaultView;return e!==n&&9===e.nodeType&&e.documentElement?(n=e,o=e.documentElement,p=!f(e),g&&g!==g.top&&(g.addEventListener?g.addEventListener("unload",function(){m()},!1):g.attachEvent&&g.attachEvent("onunload",function(){m()})),c.attributes=ib(function(a){return a.className="i",!a.getAttribute("className")}),c.getElementsByTagName=ib(function(a){return a.appendChild(e.createComment("")),!a.getElementsByTagName("*").length}),c.getElementsByClassName=$.test(e.getElementsByClassName)&&ib(function(a){return a.innerHTML="<div class='a'></div><div class='a i'></div>",a.firstChild.className="i",2===a.getElementsByClassName("i").length}),c.getById=ib(function(a){return o.appendChild(a).id=u,!e.getElementsByName||!e.getElementsByName(u).length}),c.getById?(d.find.ID=function(a,b){if(typeof b.getElementById!==C&&p){var c=b.getElementById(a);return c&&c.parentNode?[c]:[]}},d.filter.ID=function(a){var b=a.replace(cb,db);return function(a){return a.getAttribute("id")===b}}):(delete d.find.ID,d.filter.ID=function(a){var b=a.replace(cb,db);return function(a){var c=typeof a.getAttributeNode!==C&&a.getAttributeNode("id");return c&&c.value===b}}),d.find.TAG=c.getElementsByTagName?function(a,b){return typeof b.getElementsByTagName!==C?b.getElementsByTagName(a):void 0}:function(a,b){var c,d=[],e=0,f=b.getElementsByTagName(a);if("*"===a){while(c=f[e++])1===c.nodeType&&d.push(c);return d}return f},d.find.CLASS=c.getElementsByClassName&&function(a,b){return typeof b.getElementsByClassName!==C&&p?b.getElementsByClassName(a):void 0},r=[],q=[],(c.qsa=$.test(e.querySelectorAll))&&(ib(function(a){a.innerHTML="<select msallowclip=''><option selected=''></option></select>",a.querySelectorAll("[msallowclip^='']").length&&q.push("[*^$]="+M+"*(?:''|\"\")"),a.querySelectorAll("[selected]").length||q.push("\\["+M+"*(?:value|"+L+")"),a.querySelectorAll(":checked").length||q.push(":checked")}),ib(function(a){var b=e.createElement("input");b.setAttribute("type","hidden"),a.appendChild(b).setAttribute("name","D"),a.querySelectorAll("[name=d]").length&&q.push("name"+M+"*[*^$|!~]?="),a.querySelectorAll(":enabled").length||q.push(":enabled",":disabled"),a.querySelectorAll("*,:x"),q.push(",.*:")})),(c.matchesSelector=$.test(s=o.matches||o.webkitMatchesSelector||o.mozMatchesSelector||o.oMatchesSelector||o.msMatchesSelector))&&ib(function(a){c.disconnectedMatch=s.call(a,"div"),s.call(a,"[s!='']:x"),r.push("!=",Q)}),q=q.length&&new RegExp(q.join("|")),r=r.length&&new RegExp(r.join("|")),b=$.test(o.compareDocumentPosition),t=b||$.test(o.contains)?function(a,b){var c=9===a.nodeType?a.documentElement:a,d=b&&b.parentNode;return a===d||!(!d||1!==d.nodeType||!(c.contains?c.contains(d):a.compareDocumentPosition&&16&a.compareDocumentPosition(d)))}:function(a,b){if(b)while(b=b.parentNode)if(b===a)return!0;return!1},B=b?function(a,b){if(a===b)return l=!0,0;var d=!a.compareDocumentPosition-!b.compareDocumentPosition;return d?d:(d=(a.ownerDocument||a)===(b.ownerDocument||b)?a.compareDocumentPosition(b):1,1&d||!c.sortDetached&&b.compareDocumentPosition(a)===d?a===e||a.ownerDocument===v&&t(v,a)?-1:b===e||b.ownerDocument===v&&t(v,b)?1:k?K.call(k,a)-K.call(k,b):0:4&d?-1:1)}:function(a,b){if(a===b)return l=!0,0;var c,d=0,f=a.parentNode,g=b.parentNode,h=[a],i=[b];if(!f||!g)return a===e?-1:b===e?1:f?-1:g?1:k?K.call(k,a)-K.call(k,b):0;if(f===g)return kb(a,b);c=a;while(c=c.parentNode)h.unshift(c);c=b;while(c=c.parentNode)i.unshift(c);while(h[d]===i[d])d++;return d?kb(h[d],i[d]):h[d]===v?-1:i[d]===v?1:0},e):n},fb.matches=function(a,b){return fb(a,null,null,b)},fb.matchesSelector=function(a,b){if((a.ownerDocument||a)!==n&&m(a),b=b.replace(U,"='$1']"),!(!c.matchesSelector||!p||r&&r.test(b)||q&&q.test(b)))try{var d=s.call(a,b);if(d||c.disconnectedMatch||a.document&&11!==a.document.nodeType)return d}catch(e){}return fb(b,n,null,[a]).length>0},fb.contains=function(a,b){return(a.ownerDocument||a)!==n&&m(a),t(a,b)},fb.attr=function(a,b){(a.ownerDocument||a)!==n&&m(a);var e=d.attrHandle[b.toLowerCase()],f=e&&E.call(d.attrHandle,b.toLowerCase())?e(a,b,!p):void 0;return void 0!==f?f:c.attributes||!p?a.getAttribute(b):(f=a.getAttributeNode(b))&&f.specified?f.value:null},fb.error=function(a){throw new Error("Syntax error, unrecognized expression: "+a)},fb.uniqueSort=function(a){var b,d=[],e=0,f=0;if(l=!c.detectDuplicates,k=!c.sortStable&&a.slice(0),a.sort(B),l){while(b=a[f++])b===a[f]&&(e=d.push(f));while(e--)a.splice(d[e],1)}return k=null,a},e=fb.getText=function(a){var b,c="",d=0,f=a.nodeType;if(f){if(1===f||9===f||11===f){if("string"==typeof a.textContent)return a.textContent;for(a=a.firstChild;a;a=a.nextSibling)c+=e(a)}else if(3===f||4===f)return a.nodeValue}else while(b=a[d++])c+=e(b);return c},d=fb.selectors={cacheLength:50,createPseudo:hb,match:X,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(a){return a[1]=a[1].replace(cb,db),a[3]=(a[3]||a[4]||a[5]||"").replace(cb,db),"~="===a[2]&&(a[3]=" "+a[3]+" "),a.slice(0,4)},CHILD:function(a){return a[1]=a[1].toLowerCase(),"nth"===a[1].slice(0,3)?(a[3]||fb.error(a[0]),a[4]=+(a[4]?a[5]+(a[6]||1):2*("even"===a[3]||"odd"===a[3])),a[5]=+(a[7]+a[8]||"odd"===a[3])):a[3]&&fb.error(a[0]),a},PSEUDO:function(a){var b,c=!a[6]&&a[2];return X.CHILD.test(a[0])?null:(a[3]?a[2]=a[4]||a[5]||"":c&&V.test(c)&&(b=g(c,!0))&&(b=c.indexOf(")",c.length-b)-c.length)&&(a[0]=a[0].slice(0,b),a[2]=c.slice(0,b)),a.slice(0,3))}},filter:{TAG:function(a){var b=a.replace(cb,db).toLowerCase();return"*"===a?function(){return!0}:function(a){return a.nodeName&&a.nodeName.toLowerCase()===b}},CLASS:function(a){var b=y[a+" "];return b||(b=new RegExp("(^|"+M+")"+a+"("+M+"|$)"))&&y(a,function(a){return b.test("string"==typeof a.className&&a.className||typeof a.getAttribute!==C&&a.getAttribute("class")||"")})},ATTR:function(a,b,c){return function(d){var e=fb.attr(d,a);return null==e?"!="===b:b?(e+="","="===b?e===c:"!="===b?e!==c:"^="===b?c&&0===e.indexOf(c):"*="===b?c&&e.indexOf(c)>-1:"$="===b?c&&e.slice(-c.length)===c:"~="===b?(" "+e+" ").indexOf(c)>-1:"|="===b?e===c||e.slice(0,c.length+1)===c+"-":!1):!0}},CHILD:function(a,b,c,d,e){var f="nth"!==a.slice(0,3),g="last"!==a.slice(-4),h="of-type"===b;return 1===d&&0===e?function(a){return!!a.parentNode}:function(b,c,i){var j,k,l,m,n,o,p=f!==g?"nextSibling":"previousSibling",q=b.parentNode,r=h&&b.nodeName.toLowerCase(),s=!i&&!h;if(q){if(f){while(p){l=b;while(l=l[p])if(h?l.nodeName.toLowerCase()===r:1===l.nodeType)return!1;o=p="only"===a&&!o&&"nextSibling"}return!0}if(o=[g?q.firstChild:q.lastChild],g&&s){k=q[u]||(q[u]={}),j=k[a]||[],n=j[0]===w&&j[1],m=j[0]===w&&j[2],l=n&&q.childNodes[n];while(l=++n&&l&&l[p]||(m=n=0)||o.pop())if(1===l.nodeType&&++m&&l===b){k[a]=[w,n,m];break}}else if(s&&(j=(b[u]||(b[u]={}))[a])&&j[0]===w)m=j[1];else while(l=++n&&l&&l[p]||(m=n=0)||o.pop())if((h?l.nodeName.toLowerCase()===r:1===l.nodeType)&&++m&&(s&&((l[u]||(l[u]={}))[a]=[w,m]),l===b))break;return m-=e,m===d||m%d===0&&m/d>=0}}},PSEUDO:function(a,b){var c,e=d.pseudos[a]||d.setFilters[a.toLowerCase()]||fb.error("unsupported pseudo: "+a);return e[u]?e(b):e.length>1?(c=[a,a,"",b],d.setFilters.hasOwnProperty(a.toLowerCase())?hb(function(a,c){var d,f=e(a,b),g=f.length;while(g--)d=K.call(a,f[g]),a[d]=!(c[d]=f[g])}):function(a){return e(a,0,c)}):e}},pseudos:{not:hb(function(a){var b=[],c=[],d=h(a.replace(R,"$1"));return d[u]?hb(function(a,b,c,e){var f,g=d(a,null,e,[]),h=a.length;while(h--)(f=g[h])&&(a[h]=!(b[h]=f))}):function(a,e,f){return b[0]=a,d(b,null,f,c),!c.pop()}}),has:hb(function(a){return function(b){return fb(a,b).length>0}}),contains:hb(function(a){return function(b){return(b.textContent||b.innerText||e(b)).indexOf(a)>-1}}),lang:hb(function(a){return W.test(a||"")||fb.error("unsupported lang: "+a),a=a.replace(cb,db).toLowerCase(),function(b){var c;do if(c=p?b.lang:b.getAttribute("xml:lang")||b.getAttribute("lang"))return c=c.toLowerCase(),c===a||0===c.indexOf(a+"-");while((b=b.parentNode)&&1===b.nodeType);return!1}}),target:function(b){var c=a.location&&a.location.hash;return c&&c.slice(1)===b.id},root:function(a){return a===o},focus:function(a){return a===n.activeElement&&(!n.hasFocus||n.hasFocus())&&!!(a.type||a.href||~a.tabIndex)},enabled:function(a){return a.disabled===!1},disabled:function(a){return a.disabled===!0},checked:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&!!a.checked||"option"===b&&!!a.selected},selected:function(a){return a.parentNode&&a.parentNode.selectedIndex,a.selected===!0},empty:function(a){for(a=a.firstChild;a;a=a.nextSibling)if(a.nodeType<6)return!1;return!0},parent:function(a){return!d.pseudos.empty(a)},header:function(a){return Z.test(a.nodeName)},input:function(a){return Y.test(a.nodeName)},button:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&"button"===a.type||"button"===b},text:function(a){var b;return"input"===a.nodeName.toLowerCase()&&"text"===a.type&&(null==(b=a.getAttribute("type"))||"text"===b.toLowerCase())},first:nb(function(){return[0]}),last:nb(function(a,b){return[b-1]}),eq:nb(function(a,b,c){return[0>c?c+b:c]}),even:nb(function(a,b){for(var c=0;b>c;c+=2)a.push(c);return a}),odd:nb(function(a,b){for(var c=1;b>c;c+=2)a.push(c);return a}),lt:nb(function(a,b,c){for(var d=0>c?c+b:c;--d>=0;)a.push(d);return a}),gt:nb(function(a,b,c){for(var d=0>c?c+b:c;++d<b;)a.push(d);return a})}},d.pseudos.nth=d.pseudos.eq;for(b in{radio:!0,checkbox:!0,file:!0,password:!0,image:!0})d.pseudos[b]=lb(b);for(b in{submit:!0,reset:!0})d.pseudos[b]=mb(b);function pb(){}pb.prototype=d.filters=d.pseudos,d.setFilters=new pb,g=fb.tokenize=function(a,b){var c,e,f,g,h,i,j,k=z[a+" "];if(k)return b?0:k.slice(0);h=a,i=[],j=d.preFilter;while(h){(!c||(e=S.exec(h)))&&(e&&(h=h.slice(e[0].length)||h),i.push(f=[])),c=!1,(e=T.exec(h))&&(c=e.shift(),f.push({value:c,type:e[0].replace(R," ")}),h=h.slice(c.length));for(g in d.filter)!(e=X[g].exec(h))||j[g]&&!(e=j[g](e))||(c=e.shift(),f.push({value:c,type:g,matches:e}),h=h.slice(c.length));if(!c)break}return b?h.length:h?fb.error(a):z(a,i).slice(0)};function qb(a){for(var b=0,c=a.length,d="";c>b;b++)d+=a[b].value;return d}function rb(a,b,c){var d=b.dir,e=c&&"parentNode"===d,f=x++;return b.first?function(b,c,f){while(b=b[d])if(1===b.nodeType||e)return a(b,c,f)}:function(b,c,g){var h,i,j=[w,f];if(g){while(b=b[d])if((1===b.nodeType||e)&&a(b,c,g))return!0}else while(b=b[d])if(1===b.nodeType||e){if(i=b[u]||(b[u]={}),(h=i[d])&&h[0]===w&&h[1]===f)return j[2]=h[2];if(i[d]=j,j[2]=a(b,c,g))return!0}}}function sb(a){return a.length>1?function(b,c,d){var e=a.length;while(e--)if(!a[e](b,c,d))return!1;return!0}:a[0]}function tb(a,b,c){for(var d=0,e=b.length;e>d;d++)fb(a,b[d],c);return c}function ub(a,b,c,d,e){for(var f,g=[],h=0,i=a.length,j=null!=b;i>h;h++)(f=a[h])&&(!c||c(f,d,e))&&(g.push(f),j&&b.push(h));return g}function vb(a,b,c,d,e,f){return d&&!d[u]&&(d=vb(d)),e&&!e[u]&&(e=vb(e,f)),hb(function(f,g,h,i){var j,k,l,m=[],n=[],o=g.length,p=f||tb(b||"*",h.nodeType?[h]:h,[]),q=!a||!f&&b?p:ub(p,m,a,h,i),r=c?e||(f?a:o||d)?[]:g:q;if(c&&c(q,r,h,i),d){j=ub(r,n),d(j,[],h,i),k=j.length;while(k--)(l=j[k])&&(r[n[k]]=!(q[n[k]]=l))}if(f){if(e||a){if(e){j=[],k=r.length;while(k--)(l=r[k])&&j.push(q[k]=l);e(null,r=[],j,i)}k=r.length;while(k--)(l=r[k])&&(j=e?K.call(f,l):m[k])>-1&&(f[j]=!(g[j]=l))}}else r=ub(r===g?r.splice(o,r.length):r),e?e(null,g,r,i):I.apply(g,r)})}function wb(a){for(var b,c,e,f=a.length,g=d.relative[a[0].type],h=g||d.relative[" "],i=g?1:0,k=rb(function(a){return a===b},h,!0),l=rb(function(a){return K.call(b,a)>-1},h,!0),m=[function(a,c,d){return!g&&(d||c!==j)||((b=c).nodeType?k(a,c,d):l(a,c,d))}];f>i;i++)if(c=d.relative[a[i].type])m=[rb(sb(m),c)];else{if(c=d.filter[a[i].type].apply(null,a[i].matches),c[u]){for(e=++i;f>e;e++)if(d.relative[a[e].type])break;return vb(i>1&&sb(m),i>1&&qb(a.slice(0,i-1).concat({value:" "===a[i-2].type?"*":""})).replace(R,"$1"),c,e>i&&wb(a.slice(i,e)),f>e&&wb(a=a.slice(e)),f>e&&qb(a))}m.push(c)}return sb(m)}function xb(a,b){var c=b.length>0,e=a.length>0,f=function(f,g,h,i,k){var l,m,o,p=0,q="0",r=f&&[],s=[],t=j,u=f||e&&d.find.TAG("*",k),v=w+=null==t?1:Math.random()||.1,x=u.length;for(k&&(j=g!==n&&g);q!==x&&null!=(l=u[q]);q++){if(e&&l){m=0;while(o=a[m++])if(o(l,g,h)){i.push(l);break}k&&(w=v)}c&&((l=!o&&l)&&p--,f&&r.push(l))}if(p+=q,c&&q!==p){m=0;while(o=b[m++])o(r,s,g,h);if(f){if(p>0)while(q--)r[q]||s[q]||(s[q]=G.call(i));s=ub(s)}I.apply(i,s),k&&!f&&s.length>0&&p+b.length>1&&fb.uniqueSort(i)}return k&&(w=v,j=t),r};return c?hb(f):f}return h=fb.compile=function(a,b){var c,d=[],e=[],f=A[a+" "];if(!f){b||(b=g(a)),c=b.length;while(c--)f=wb(b[c]),f[u]?d.push(f):e.push(f);f=A(a,xb(e,d)),f.selector=a}return f},i=fb.select=function(a,b,e,f){var i,j,k,l,m,n="function"==typeof a&&a,o=!f&&g(a=n.selector||a);if(e=e||[],1===o.length){if(j=o[0]=o[0].slice(0),j.length>2&&"ID"===(k=j[0]).type&&c.getById&&9===b.nodeType&&p&&d.relative[j[1].type]){if(b=(d.find.ID(k.matches[0].replace(cb,db),b)||[])[0],!b)return e;n&&(b=b.parentNode),a=a.slice(j.shift().value.length)}i=X.needsContext.test(a)?0:j.length;while(i--){if(k=j[i],d.relative[l=k.type])break;if((m=d.find[l])&&(f=m(k.matches[0].replace(cb,db),ab.test(j[0].type)&&ob(b.parentNode)||b))){if(j.splice(i,1),a=f.length&&qb(j),!a)return I.apply(e,f),e;break}}}return(n||h(a,o))(f,b,!p,e,ab.test(a)&&ob(b.parentNode)||b),e},c.sortStable=u.split("").sort(B).join("")===u,c.detectDuplicates=!!l,m(),c.sortDetached=ib(function(a){return 1&a.compareDocumentPosition(n.createElement("div"))}),ib(function(a){return a.innerHTML="<a href='#'></a>","#"===a.firstChild.getAttribute("href")})||jb("type|href|height|width",function(a,b,c){return c?void 0:a.getAttribute(b,"type"===b.toLowerCase()?1:2)}),c.attributes&&ib(function(a){return a.innerHTML="<input/>",a.firstChild.setAttribute("value",""),""===a.firstChild.getAttribute("value")})||jb("value",function(a,b,c){return c||"input"!==a.nodeName.toLowerCase()?void 0:a.defaultValue}),ib(function(a){return null==a.getAttribute("disabled")})||jb(L,function(a,b,c){var d;return c?void 0:a[b]===!0?b.toLowerCase():(d=a.getAttributeNode(b))&&d.specified?d.value:null}),fb}(a);m.find=s,m.expr=s.selectors,m.expr[":"]=m.expr.pseudos,m.unique=s.uniqueSort,m.text=s.getText,m.isXMLDoc=s.isXML,m.contains=s.contains;var t=m.expr.match.needsContext,u=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,v=/^.[^:#\[\.,]*$/;function w(a,b,c){if(m.isFunction(b))return m.grep(a,function(a,d){return!!b.call(a,d,a)!==c});if(b.nodeType)return m.grep(a,function(a){return a===b!==c});if("string"==typeof b){if(v.test(b))return m.filter(b,a,c);b=m.filter(b,a)}return m.grep(a,function(a){return m.inArray(a,b)>=0!==c})}m.filter=function(a,b,c){var d=b[0];return c&&(a=":not("+a+")"),1===b.length&&1===d.nodeType?m.find.matchesSelector(d,a)?[d]:[]:m.find.matches(a,m.grep(b,function(a){return 1===a.nodeType}))},m.fn.extend({find:function(a){var b,c=[],d=this,e=d.length;if("string"!=typeof a)return this.pushStack(m(a).filter(function(){for(b=0;e>b;b++)if(m.contains(d[b],this))return!0}));for(b=0;e>b;b++)m.find(a,d[b],c);return c=this.pushStack(e>1?m.unique(c):c),c.selector=this.selector?this.selector+" "+a:a,c},filter:function(a){return this.pushStack(w(this,a||[],!1))},not:function(a){return this.pushStack(w(this,a||[],!0))},is:function(a){return!!w(this,"string"==typeof a&&t.test(a)?m(a):a||[],!1).length}});var x,y=a.document,z=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,A=m.fn.init=function(a,b){var c,d;if(!a)return this;if("string"==typeof a){if(c="<"===a.charAt(0)&&">"===a.charAt(a.length-1)&&a.length>=3?[null,a,null]:z.exec(a),!c||!c[1]&&b)return!b||b.jquery?(b||x).find(a):this.constructor(b).find(a);if(c[1]){if(b=b instanceof m?b[0]:b,m.merge(this,m.parseHTML(c[1],b&&b.nodeType?b.ownerDocument||b:y,!0)),u.test(c[1])&&m.isPlainObject(b))for(c in b)m.isFunction(this[c])?this[c](b[c]):this.attr(c,b[c]);return this}if(d=y.getElementById(c[2]),d&&d.parentNode){if(d.id!==c[2])return x.find(a);this.length=1,this[0]=d}return this.context=y,this.selector=a,this}return a.nodeType?(this.context=this[0]=a,this.length=1,this):m.isFunction(a)?"undefined"!=typeof x.ready?x.ready(a):a(m):(void 0!==a.selector&&(this.selector=a.selector,this.context=a.context),m.makeArray(a,this))};A.prototype=m.fn,x=m(y);var B=/^(?:parents|prev(?:Until|All))/,C={children:!0,contents:!0,next:!0,prev:!0};m.extend({dir:function(a,b,c){var d=[],e=a[b];while(e&&9!==e.nodeType&&(void 0===c||1!==e.nodeType||!m(e).is(c)))1===e.nodeType&&d.push(e),e=e[b];return d},sibling:function(a,b){for(var c=[];a;a=a.nextSibling)1===a.nodeType&&a!==b&&c.push(a);return c}}),m.fn.extend({has:function(a){var b,c=m(a,this),d=c.length;return this.filter(function(){for(b=0;d>b;b++)if(m.contains(this,c[b]))return!0})},closest:function(a,b){for(var c,d=0,e=this.length,f=[],g=t.test(a)||"string"!=typeof a?m(a,b||this.context):0;e>d;d++)for(c=this[d];c&&c!==b;c=c.parentNode)if(c.nodeType<11&&(g?g.index(c)>-1:1===c.nodeType&&m.find.matchesSelector(c,a))){f.push(c);break}return this.pushStack(f.length>1?m.unique(f):f)},index:function(a){return a?"string"==typeof a?m.inArray(this[0],m(a)):m.inArray(a.jquery?a[0]:a,this):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(a,b){return this.pushStack(m.unique(m.merge(this.get(),m(a,b))))},addBack:function(a){return this.add(null==a?this.prevObject:this.prevObject.filter(a))}});function D(a,b){do a=a[b];while(a&&1!==a.nodeType);return a}m.each({parent:function(a){var b=a.parentNode;return b&&11!==b.nodeType?b:null},parents:function(a){return m.dir(a,"parentNode")},parentsUntil:function(a,b,c){return m.dir(a,"parentNode",c)},next:function(a){return D(a,"nextSibling")},prev:function(a){return D(a,"previousSibling")},nextAll:function(a){return m.dir(a,"nextSibling")},prevAll:function(a){return m.dir(a,"previousSibling")},nextUntil:function(a,b,c){return m.dir(a,"nextSibling",c)},prevUntil:function(a,b,c){return m.dir(a,"previousSibling",c)},siblings:function(a){return m.sibling((a.parentNode||{}).firstChild,a)},children:function(a){return m.sibling(a.firstChild)},contents:function(a){return m.nodeName(a,"iframe")?a.contentDocument||a.contentWindow.document:m.merge([],a.childNodes)}},function(a,b){m.fn[a]=function(c,d){var e=m.map(this,b,c);return"Until"!==a.slice(-5)&&(d=c),d&&"string"==typeof d&&(e=m.filter(d,e)),this.length>1&&(C[a]||(e=m.unique(e)),B.test(a)&&(e=e.reverse())),this.pushStack(e)}});var E=/\S+/g,F={};function G(a){var b=F[a]={};return m.each(a.match(E)||[],function(a,c){b[c]=!0}),b}m.Callbacks=function(a){a="string"==typeof a?F[a]||G(a):m.extend({},a);var b,c,d,e,f,g,h=[],i=!a.once&&[],j=function(l){for(c=a.memory&&l,d=!0,f=g||0,g=0,e=h.length,b=!0;h&&e>f;f++)if(h[f].apply(l[0],l[1])===!1&&a.stopOnFalse){c=!1;break}b=!1,h&&(i?i.length&&j(i.shift()):c?h=[]:k.disable())},k={add:function(){if(h){var d=h.length;!function f(b){m.each(b,function(b,c){var d=m.type(c);"function"===d?a.unique&&k.has(c)||h.push(c):c&&c.length&&"string"!==d&&f(c)})}(arguments),b?e=h.length:c&&(g=d,j(c))}return this},remove:function(){return h&&m.each(arguments,function(a,c){var d;while((d=m.inArray(c,h,d))>-1)h.splice(d,1),b&&(e>=d&&e--,f>=d&&f--)}),this},has:function(a){return a?m.inArray(a,h)>-1:!(!h||!h.length)},empty:function(){return h=[],e=0,this},disable:function(){return h=i=c=void 0,this},disabled:function(){return!h},lock:function(){return i=void 0,c||k.disable(),this},locked:function(){return!i},fireWith:function(a,c){return!h||d&&!i||(c=c||[],c=[a,c.slice?c.slice():c],b?i.push(c):j(c)),this},fire:function(){return k.fireWith(this,arguments),this},fired:function(){return!!d}};return k},m.extend({Deferred:function(a){var b=[["resolve","done",m.Callbacks("once memory"),"resolved"],["reject","fail",m.Callbacks("once memory"),"rejected"],["notify","progress",m.Callbacks("memory")]],c="pending",d={state:function(){return c},always:function(){return e.done(arguments).fail(arguments),this},then:function(){var a=arguments;return m.Deferred(function(c){m.each(b,function(b,f){var g=m.isFunction(a[b])&&a[b];e[f[1]](function(){var a=g&&g.apply(this,arguments);a&&m.isFunction(a.promise)?a.promise().done(c.resolve).fail(c.reject).progress(c.notify):c[f[0]+"With"](this===d?c.promise():this,g?[a]:arguments)})}),a=null}).promise()},promise:function(a){return null!=a?m.extend(a,d):d}},e={};return d.pipe=d.then,m.each(b,function(a,f){var g=f[2],h=f[3];d[f[1]]=g.add,h&&g.add(function(){c=h},b[1^a][2].disable,b[2][2].lock),e[f[0]]=function(){return e[f[0]+"With"](this===e?d:this,arguments),this},e[f[0]+"With"]=g.fireWith}),d.promise(e),a&&a.call(e,e),e},when:function(a){var b=0,c=d.call(arguments),e=c.length,f=1!==e||a&&m.isFunction(a.promise)?e:0,g=1===f?a:m.Deferred(),h=function(a,b,c){return function(e){b[a]=this,c[a]=arguments.length>1?d.call(arguments):e,c===i?g.notifyWith(b,c):--f||g.resolveWith(b,c)}},i,j,k;if(e>1)for(i=new Array(e),j=new Array(e),k=new Array(e);e>b;b++)c[b]&&m.isFunction(c[b].promise)?c[b].promise().done(h(b,k,c)).fail(g.reject).progress(h(b,j,i)):--f;return f||g.resolveWith(k,c),g.promise()}});var H;m.fn.ready=function(a){return m.ready.promise().done(a),this},m.extend({isReady:!1,readyWait:1,holdReady:function(a){a?m.readyWait++:m.ready(!0)},ready:function(a){if(a===!0?!--m.readyWait:!m.isReady){if(!y.body)return setTimeout(m.ready);m.isReady=!0,a!==!0&&--m.readyWait>0||(H.resolveWith(y,[m]),m.fn.triggerHandler&&(m(y).triggerHandler("ready"),m(y).off("ready")))}}});function I(){y.addEventListener?(y.removeEventListener("DOMContentLoaded",J,!1),a.removeEventListener("load",J,!1)):(y.detachEvent("onreadystatechange",J),a.detachEvent("onload",J))}function J(){(y.addEventListener||"load"===event.type||"complete"===y.readyState)&&(I(),m.ready())}m.ready.promise=function(b){if(!H)if(H=m.Deferred(),"complete"===y.readyState)setTimeout(m.ready);else if(y.addEventListener)y.addEventListener("DOMContentLoaded",J,!1),a.addEventListener("load",J,!1);else{y.attachEvent("onreadystatechange",J),a.attachEvent("onload",J);var c=!1;try{c=null==a.frameElement&&y.documentElement}catch(d){}c&&c.doScroll&&!function e(){if(!m.isReady){try{c.doScroll("left")}catch(a){return setTimeout(e,50)}I(),m.ready()}}()}return H.promise(b)};var K="undefined",L;for(L in m(k))break;k.ownLast="0"!==L,k.inlineBlockNeedsLayout=!1,m(function(){var a,b,c,d;c=y.getElementsByTagName("body")[0],c&&c.style&&(b=y.createElement("div"),d=y.createElement("div"),d.style.cssText="position:absolute;border:0;width:0;height:0;top:0;left:-9999px",c.appendChild(d).appendChild(b),typeof b.style.zoom!==K&&(b.style.cssText="display:inline;margin:0;border:0;padding:1px;width:1px;zoom:1",k.inlineBlockNeedsLayout=a=3===b.offsetWidth,a&&(c.style.zoom=1)),c.removeChild(d))}),function(){var a=y.createElement("div");if(null==k.deleteExpando){k.deleteExpando=!0;try{delete a.test}catch(b){k.deleteExpando=!1}}a=null}(),m.acceptData=function(a){var b=m.noData[(a.nodeName+" ").toLowerCase()],c=+a.nodeType||1;return 1!==c&&9!==c?!1:!b||b!==!0&&a.getAttribute("classid")===b};var M=/^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,N=/([A-Z])/g;function O(a,b,c){if(void 0===c&&1===a.nodeType){var d="data-"+b.replace(N,"-$1").toLowerCase();if(c=a.getAttribute(d),"string"==typeof c){try{c="true"===c?!0:"false"===c?!1:"null"===c?null:+c+""===c?+c:M.test(c)?m.parseJSON(c):c}catch(e){}m.data(a,b,c)}else c=void 0}return c}function P(a){var b;for(b in a)if(("data"!==b||!m.isEmptyObject(a[b]))&&"toJSON"!==b)return!1;return!0}function Q(a,b,d,e){if(m.acceptData(a)){var f,g,h=m.expando,i=a.nodeType,j=i?m.cache:a,k=i?a[h]:a[h]&&h;
+if(k&&j[k]&&(e||j[k].data)||void 0!==d||"string"!=typeof b)return k||(k=i?a[h]=c.pop()||m.guid++:h),j[k]||(j[k]=i?{}:{toJSON:m.noop}),("object"==typeof b||"function"==typeof b)&&(e?j[k]=m.extend(j[k],b):j[k].data=m.extend(j[k].data,b)),g=j[k],e||(g.data||(g.data={}),g=g.data),void 0!==d&&(g[m.camelCase(b)]=d),"string"==typeof b?(f=g[b],null==f&&(f=g[m.camelCase(b)])):f=g,f}}function R(a,b,c){if(m.acceptData(a)){var d,e,f=a.nodeType,g=f?m.cache:a,h=f?a[m.expando]:m.expando;if(g[h]){if(b&&(d=c?g[h]:g[h].data)){m.isArray(b)?b=b.concat(m.map(b,m.camelCase)):b in d?b=[b]:(b=m.camelCase(b),b=b in d?[b]:b.split(" ")),e=b.length;while(e--)delete d[b[e]];if(c?!P(d):!m.isEmptyObject(d))return}(c||(delete g[h].data,P(g[h])))&&(f?m.cleanData([a],!0):k.deleteExpando||g!=g.window?delete g[h]:g[h]=null)}}}m.extend({cache:{},noData:{"applet ":!0,"embed ":!0,"object ":"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"},hasData:function(a){return a=a.nodeType?m.cache[a[m.expando]]:a[m.expando],!!a&&!P(a)},data:function(a,b,c){return Q(a,b,c)},removeData:function(a,b){return R(a,b)},_data:function(a,b,c){return Q(a,b,c,!0)},_removeData:function(a,b){return R(a,b,!0)}}),m.fn.extend({data:function(a,b){var c,d,e,f=this[0],g=f&&f.attributes;if(void 0===a){if(this.length&&(e=m.data(f),1===f.nodeType&&!m._data(f,"parsedAttrs"))){c=g.length;while(c--)g[c]&&(d=g[c].name,0===d.indexOf("data-")&&(d=m.camelCase(d.slice(5)),O(f,d,e[d])));m._data(f,"parsedAttrs",!0)}return e}return"object"==typeof a?this.each(function(){m.data(this,a)}):arguments.length>1?this.each(function(){m.data(this,a,b)}):f?O(f,a,m.data(f,a)):void 0},removeData:function(a){return this.each(function(){m.removeData(this,a)})}}),m.extend({queue:function(a,b,c){var d;return a?(b=(b||"fx")+"queue",d=m._data(a,b),c&&(!d||m.isArray(c)?d=m._data(a,b,m.makeArray(c)):d.push(c)),d||[]):void 0},dequeue:function(a,b){b=b||"fx";var c=m.queue(a,b),d=c.length,e=c.shift(),f=m._queueHooks(a,b),g=function(){m.dequeue(a,b)};"inprogress"===e&&(e=c.shift(),d--),e&&("fx"===b&&c.unshift("inprogress"),delete f.stop,e.call(a,g,f)),!d&&f&&f.empty.fire()},_queueHooks:function(a,b){var c=b+"queueHooks";return m._data(a,c)||m._data(a,c,{empty:m.Callbacks("once memory").add(function(){m._removeData(a,b+"queue"),m._removeData(a,c)})})}}),m.fn.extend({queue:function(a,b){var c=2;return"string"!=typeof a&&(b=a,a="fx",c--),arguments.length<c?m.queue(this[0],a):void 0===b?this:this.each(function(){var c=m.queue(this,a,b);m._queueHooks(this,a),"fx"===a&&"inprogress"!==c[0]&&m.dequeue(this,a)})},dequeue:function(a){return this.each(function(){m.dequeue(this,a)})},clearQueue:function(a){return this.queue(a||"fx",[])},promise:function(a,b){var c,d=1,e=m.Deferred(),f=this,g=this.length,h=function(){--d||e.resolveWith(f,[f])};"string"!=typeof a&&(b=a,a=void 0),a=a||"fx";while(g--)c=m._data(f[g],a+"queueHooks"),c&&c.empty&&(d++,c.empty.add(h));return h(),e.promise(b)}});var S=/[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/.source,T=["Top","Right","Bottom","Left"],U=function(a,b){return a=b||a,"none"===m.css(a,"display")||!m.contains(a.ownerDocument,a)},V=m.access=function(a,b,c,d,e,f,g){var h=0,i=a.length,j=null==c;if("object"===m.type(c)){e=!0;for(h in c)m.access(a,b,h,c[h],!0,f,g)}else if(void 0!==d&&(e=!0,m.isFunction(d)||(g=!0),j&&(g?(b.call(a,d),b=null):(j=b,b=function(a,b,c){return j.call(m(a),c)})),b))for(;i>h;h++)b(a[h],c,g?d:d.call(a[h],h,b(a[h],c)));return e?a:j?b.call(a):i?b(a[0],c):f},W=/^(?:checkbox|radio)$/i;!function(){var a=y.createElement("input"),b=y.createElement("div"),c=y.createDocumentFragment();if(b.innerHTML="  <link/><table></table><a href='/a'>a</a><input type='checkbox'/>",k.leadingWhitespace=3===b.firstChild.nodeType,k.tbody=!b.getElementsByTagName("tbody").length,k.htmlSerialize=!!b.getElementsByTagName("link").length,k.html5Clone="<:nav></:nav>"!==y.createElement("nav").cloneNode(!0).outerHTML,a.type="checkbox",a.checked=!0,c.appendChild(a),k.appendChecked=a.checked,b.innerHTML="<textarea>x</textarea>",k.noCloneChecked=!!b.cloneNode(!0).lastChild.defaultValue,c.appendChild(b),b.innerHTML="<input type='radio' checked='checked' name='t'/>",k.checkClone=b.cloneNode(!0).cloneNode(!0).lastChild.checked,k.noCloneEvent=!0,b.attachEvent&&(b.attachEvent("onclick",function(){k.noCloneEvent=!1}),b.cloneNode(!0).click()),null==k.deleteExpando){k.deleteExpando=!0;try{delete b.test}catch(d){k.deleteExpando=!1}}}(),function(){var b,c,d=y.createElement("div");for(b in{submit:!0,change:!0,focusin:!0})c="on"+b,(k[b+"Bubbles"]=c in a)||(d.setAttribute(c,"t"),k[b+"Bubbles"]=d.attributes[c].expando===!1);d=null}();var X=/^(?:input|select|textarea)$/i,Y=/^key/,Z=/^(?:mouse|pointer|contextmenu)|click/,$=/^(?:focusinfocus|focusoutblur)$/,_=/^([^.]*)(?:\.(.+)|)$/;function ab(){return!0}function bb(){return!1}function cb(){try{return y.activeElement}catch(a){}}m.event={global:{},add:function(a,b,c,d,e){var f,g,h,i,j,k,l,n,o,p,q,r=m._data(a);if(r){c.handler&&(i=c,c=i.handler,e=i.selector),c.guid||(c.guid=m.guid++),(g=r.events)||(g=r.events={}),(k=r.handle)||(k=r.handle=function(a){return typeof m===K||a&&m.event.triggered===a.type?void 0:m.event.dispatch.apply(k.elem,arguments)},k.elem=a),b=(b||"").match(E)||[""],h=b.length;while(h--)f=_.exec(b[h])||[],o=q=f[1],p=(f[2]||"").split(".").sort(),o&&(j=m.event.special[o]||{},o=(e?j.delegateType:j.bindType)||o,j=m.event.special[o]||{},l=m.extend({type:o,origType:q,data:d,handler:c,guid:c.guid,selector:e,needsContext:e&&m.expr.match.needsContext.test(e),namespace:p.join(".")},i),(n=g[o])||(n=g[o]=[],n.delegateCount=0,j.setup&&j.setup.call(a,d,p,k)!==!1||(a.addEventListener?a.addEventListener(o,k,!1):a.attachEvent&&a.attachEvent("on"+o,k))),j.add&&(j.add.call(a,l),l.handler.guid||(l.handler.guid=c.guid)),e?n.splice(n.delegateCount++,0,l):n.push(l),m.event.global[o]=!0);a=null}},remove:function(a,b,c,d,e){var f,g,h,i,j,k,l,n,o,p,q,r=m.hasData(a)&&m._data(a);if(r&&(k=r.events)){b=(b||"").match(E)||[""],j=b.length;while(j--)if(h=_.exec(b[j])||[],o=q=h[1],p=(h[2]||"").split(".").sort(),o){l=m.event.special[o]||{},o=(d?l.delegateType:l.bindType)||o,n=k[o]||[],h=h[2]&&new RegExp("(^|\\.)"+p.join("\\.(?:.*\\.|)")+"(\\.|$)"),i=f=n.length;while(f--)g=n[f],!e&&q!==g.origType||c&&c.guid!==g.guid||h&&!h.test(g.namespace)||d&&d!==g.selector&&("**"!==d||!g.selector)||(n.splice(f,1),g.selector&&n.delegateCount--,l.remove&&l.remove.call(a,g));i&&!n.length&&(l.teardown&&l.teardown.call(a,p,r.handle)!==!1||m.removeEvent(a,o,r.handle),delete k[o])}else for(o in k)m.event.remove(a,o+b[j],c,d,!0);m.isEmptyObject(k)&&(delete r.handle,m._removeData(a,"events"))}},trigger:function(b,c,d,e){var f,g,h,i,k,l,n,o=[d||y],p=j.call(b,"type")?b.type:b,q=j.call(b,"namespace")?b.namespace.split("."):[];if(h=l=d=d||y,3!==d.nodeType&&8!==d.nodeType&&!$.test(p+m.event.triggered)&&(p.indexOf(".")>=0&&(q=p.split("."),p=q.shift(),q.sort()),g=p.indexOf(":")<0&&"on"+p,b=b[m.expando]?b:new m.Event(p,"object"==typeof b&&b),b.isTrigger=e?2:3,b.namespace=q.join("."),b.namespace_re=b.namespace?new RegExp("(^|\\.)"+q.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,b.result=void 0,b.target||(b.target=d),c=null==c?[b]:m.makeArray(c,[b]),k=m.event.special[p]||{},e||!k.trigger||k.trigger.apply(d,c)!==!1)){if(!e&&!k.noBubble&&!m.isWindow(d)){for(i=k.delegateType||p,$.test(i+p)||(h=h.parentNode);h;h=h.parentNode)o.push(h),l=h;l===(d.ownerDocument||y)&&o.push(l.defaultView||l.parentWindow||a)}n=0;while((h=o[n++])&&!b.isPropagationStopped())b.type=n>1?i:k.bindType||p,f=(m._data(h,"events")||{})[b.type]&&m._data(h,"handle"),f&&f.apply(h,c),f=g&&h[g],f&&f.apply&&m.acceptData(h)&&(b.result=f.apply(h,c),b.result===!1&&b.preventDefault());if(b.type=p,!e&&!b.isDefaultPrevented()&&(!k._default||k._default.apply(o.pop(),c)===!1)&&m.acceptData(d)&&g&&d[p]&&!m.isWindow(d)){l=d[g],l&&(d[g]=null),m.event.triggered=p;try{d[p]()}catch(r){}m.event.triggered=void 0,l&&(d[g]=l)}return b.result}},dispatch:function(a){a=m.event.fix(a);var b,c,e,f,g,h=[],i=d.call(arguments),j=(m._data(this,"events")||{})[a.type]||[],k=m.event.special[a.type]||{};if(i[0]=a,a.delegateTarget=this,!k.preDispatch||k.preDispatch.call(this,a)!==!1){h=m.event.handlers.call(this,a,j),b=0;while((f=h[b++])&&!a.isPropagationStopped()){a.currentTarget=f.elem,g=0;while((e=f.handlers[g++])&&!a.isImmediatePropagationStopped())(!a.namespace_re||a.namespace_re.test(e.namespace))&&(a.handleObj=e,a.data=e.data,c=((m.event.special[e.origType]||{}).handle||e.handler).apply(f.elem,i),void 0!==c&&(a.result=c)===!1&&(a.preventDefault(),a.stopPropagation()))}return k.postDispatch&&k.postDispatch.call(this,a),a.result}},handlers:function(a,b){var c,d,e,f,g=[],h=b.delegateCount,i=a.target;if(h&&i.nodeType&&(!a.button||"click"!==a.type))for(;i!=this;i=i.parentNode||this)if(1===i.nodeType&&(i.disabled!==!0||"click"!==a.type)){for(e=[],f=0;h>f;f++)d=b[f],c=d.selector+" ",void 0===e[c]&&(e[c]=d.needsContext?m(c,this).index(i)>=0:m.find(c,this,null,[i]).length),e[c]&&e.push(d);e.length&&g.push({elem:i,handlers:e})}return h<b.length&&g.push({elem:this,handlers:b.slice(h)}),g},fix:function(a){if(a[m.expando])return a;var b,c,d,e=a.type,f=a,g=this.fixHooks[e];g||(this.fixHooks[e]=g=Z.test(e)?this.mouseHooks:Y.test(e)?this.keyHooks:{}),d=g.props?this.props.concat(g.props):this.props,a=new m.Event(f),b=d.length;while(b--)c=d[b],a[c]=f[c];return a.target||(a.target=f.srcElement||y),3===a.target.nodeType&&(a.target=a.target.parentNode),a.metaKey=!!a.metaKey,g.filter?g.filter(a,f):a},props:"altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "),fixHooks:{},keyHooks:{props:"char charCode key keyCode".split(" "),filter:function(a,b){return null==a.which&&(a.which=null!=b.charCode?b.charCode:b.keyCode),a}},mouseHooks:{props:"button buttons clientX clientY fromElement offsetX offsetY pageX pageY screenX screenY toElement".split(" "),filter:function(a,b){var c,d,e,f=b.button,g=b.fromElement;return null==a.pageX&&null!=b.clientX&&(d=a.target.ownerDocument||y,e=d.documentElement,c=d.body,a.pageX=b.clientX+(e&&e.scrollLeft||c&&c.scrollLeft||0)-(e&&e.clientLeft||c&&c.clientLeft||0),a.pageY=b.clientY+(e&&e.scrollTop||c&&c.scrollTop||0)-(e&&e.clientTop||c&&c.clientTop||0)),!a.relatedTarget&&g&&(a.relatedTarget=g===a.target?b.toElement:g),a.which||void 0===f||(a.which=1&f?1:2&f?3:4&f?2:0),a}},special:{load:{noBubble:!0},focus:{trigger:function(){if(this!==cb()&&this.focus)try{return this.focus(),!1}catch(a){}},delegateType:"focusin"},blur:{trigger:function(){return this===cb()&&this.blur?(this.blur(),!1):void 0},delegateType:"focusout"},click:{trigger:function(){return m.nodeName(this,"input")&&"checkbox"===this.type&&this.click?(this.click(),!1):void 0},_default:function(a){return m.nodeName(a.target,"a")}},beforeunload:{postDispatch:function(a){void 0!==a.result&&a.originalEvent&&(a.originalEvent.returnValue=a.result)}}},simulate:function(a,b,c,d){var e=m.extend(new m.Event,c,{type:a,isSimulated:!0,originalEvent:{}});d?m.event.trigger(e,null,b):m.event.dispatch.call(b,e),e.isDefaultPrevented()&&c.preventDefault()}},m.removeEvent=y.removeEventListener?function(a,b,c){a.removeEventListener&&a.removeEventListener(b,c,!1)}:function(a,b,c){var d="on"+b;a.detachEvent&&(typeof a[d]===K&&(a[d]=null),a.detachEvent(d,c))},m.Event=function(a,b){return this instanceof m.Event?(a&&a.type?(this.originalEvent=a,this.type=a.type,this.isDefaultPrevented=a.defaultPrevented||void 0===a.defaultPrevented&&a.returnValue===!1?ab:bb):this.type=a,b&&m.extend(this,b),this.timeStamp=a&&a.timeStamp||m.now(),void(this[m.expando]=!0)):new m.Event(a,b)},m.Event.prototype={isDefaultPrevented:bb,isPropagationStopped:bb,isImmediatePropagationStopped:bb,preventDefault:function(){var a=this.originalEvent;this.isDefaultPrevented=ab,a&&(a.preventDefault?a.preventDefault():a.returnValue=!1)},stopPropagation:function(){var a=this.originalEvent;this.isPropagationStopped=ab,a&&(a.stopPropagation&&a.stopPropagation(),a.cancelBubble=!0)},stopImmediatePropagation:function(){var a=this.originalEvent;this.isImmediatePropagationStopped=ab,a&&a.stopImmediatePropagation&&a.stopImmediatePropagation(),this.stopPropagation()}},m.each({mouseenter:"mouseover",mouseleave:"mouseout",pointerenter:"pointerover",pointerleave:"pointerout"},function(a,b){m.event.special[a]={delegateType:b,bindType:b,handle:function(a){var c,d=this,e=a.relatedTarget,f=a.handleObj;return(!e||e!==d&&!m.contains(d,e))&&(a.type=f.origType,c=f.handler.apply(this,arguments),a.type=b),c}}}),k.submitBubbles||(m.event.special.submit={setup:function(){return m.nodeName(this,"form")?!1:void m.event.add(this,"click._submit keypress._submit",function(a){var b=a.target,c=m.nodeName(b,"input")||m.nodeName(b,"button")?b.form:void 0;c&&!m._data(c,"submitBubbles")&&(m.event.add(c,"submit._submit",function(a){a._submit_bubble=!0}),m._data(c,"submitBubbles",!0))})},postDispatch:function(a){a._submit_bubble&&(delete a._submit_bubble,this.parentNode&&!a.isTrigger&&m.event.simulate("submit",this.parentNode,a,!0))},teardown:function(){return m.nodeName(this,"form")?!1:void m.event.remove(this,"._submit")}}),k.changeBubbles||(m.event.special.change={setup:function(){return X.test(this.nodeName)?(("checkbox"===this.type||"radio"===this.type)&&(m.event.add(this,"propertychange._change",function(a){"checked"===a.originalEvent.propertyName&&(this._just_changed=!0)}),m.event.add(this,"click._change",function(a){this._just_changed&&!a.isTrigger&&(this._just_changed=!1),m.event.simulate("change",this,a,!0)})),!1):void m.event.add(this,"beforeactivate._change",function(a){var b=a.target;X.test(b.nodeName)&&!m._data(b,"changeBubbles")&&(m.event.add(b,"change._change",function(a){!this.parentNode||a.isSimulated||a.isTrigger||m.event.simulate("change",this.parentNode,a,!0)}),m._data(b,"changeBubbles",!0))})},handle:function(a){var b=a.target;return this!==b||a.isSimulated||a.isTrigger||"radio"!==b.type&&"checkbox"!==b.type?a.handleObj.handler.apply(this,arguments):void 0},teardown:function(){return m.event.remove(this,"._change"),!X.test(this.nodeName)}}),k.focusinBubbles||m.each({focus:"focusin",blur:"focusout"},function(a,b){var c=function(a){m.event.simulate(b,a.target,m.event.fix(a),!0)};m.event.special[b]={setup:function(){var d=this.ownerDocument||this,e=m._data(d,b);e||d.addEventListener(a,c,!0),m._data(d,b,(e||0)+1)},teardown:function(){var d=this.ownerDocument||this,e=m._data(d,b)-1;e?m._data(d,b,e):(d.removeEventListener(a,c,!0),m._removeData(d,b))}}}),m.fn.extend({on:function(a,b,c,d,e){var f,g;if("object"==typeof a){"string"!=typeof b&&(c=c||b,b=void 0);for(f in a)this.on(f,b,c,a[f],e);return this}if(null==c&&null==d?(d=b,c=b=void 0):null==d&&("string"==typeof b?(d=c,c=void 0):(d=c,c=b,b=void 0)),d===!1)d=bb;else if(!d)return this;return 1===e&&(g=d,d=function(a){return m().off(a),g.apply(this,arguments)},d.guid=g.guid||(g.guid=m.guid++)),this.each(function(){m.event.add(this,a,d,c,b)})},one:function(a,b,c,d){return this.on(a,b,c,d,1)},off:function(a,b,c){var d,e;if(a&&a.preventDefault&&a.handleObj)return d=a.handleObj,m(a.delegateTarget).off(d.namespace?d.origType+"."+d.namespace:d.origType,d.selector,d.handler),this;if("object"==typeof a){for(e in a)this.off(e,b,a[e]);return this}return(b===!1||"function"==typeof b)&&(c=b,b=void 0),c===!1&&(c=bb),this.each(function(){m.event.remove(this,a,c,b)})},trigger:function(a,b){return this.each(function(){m.event.trigger(a,b,this)})},triggerHandler:function(a,b){var c=this[0];return c?m.event.trigger(a,b,c,!0):void 0}});function db(a){var b=eb.split("|"),c=a.createDocumentFragment();if(c.createElement)while(b.length)c.createElement(b.pop());return c}var eb="abbr|article|aside|audio|bdi|canvas|data|datalist|details|figcaption|figure|footer|header|hgroup|mark|meter|nav|output|progress|section|summary|time|video",fb=/ jQuery\d+="(?:null|\d+)"/g,gb=new RegExp("<(?:"+eb+")[\\s/>]","i"),hb=/^\s+/,ib=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,jb=/<([\w:]+)/,kb=/<tbody/i,lb=/<|&#?\w+;/,mb=/<(?:script|style|link)/i,nb=/checked\s*(?:[^=]|=\s*.checked.)/i,ob=/^$|\/(?:java|ecma)script/i,pb=/^true\/(.*)/,qb=/^\s*<!(?:\[CDATA\[|--)|(?:\]\]|--)>\s*$/g,rb={option:[1,"<select multiple='multiple'>","</select>"],legend:[1,"<fieldset>","</fieldset>"],area:[1,"<map>","</map>"],param:[1,"<object>","</object>"],thead:[1,"<table>","</table>"],tr:[2,"<table><tbody>","</tbody></table>"],col:[2,"<table><tbody></tbody><colgroup>","</colgroup></table>"],td:[3,"<table><tbody><tr>","</tr></tbody></table>"],_default:k.htmlSerialize?[0,"",""]:[1,"X<div>","</div>"]},sb=db(y),tb=sb.appendChild(y.createElement("div"));rb.optgroup=rb.option,rb.tbody=rb.tfoot=rb.colgroup=rb.caption=rb.thead,rb.th=rb.td;function ub(a,b){var c,d,e=0,f=typeof a.getElementsByTagName!==K?a.getElementsByTagName(b||"*"):typeof a.querySelectorAll!==K?a.querySelectorAll(b||"*"):void 0;if(!f)for(f=[],c=a.childNodes||a;null!=(d=c[e]);e++)!b||m.nodeName(d,b)?f.push(d):m.merge(f,ub(d,b));return void 0===b||b&&m.nodeName(a,b)?m.merge([a],f):f}function vb(a){W.test(a.type)&&(a.defaultChecked=a.checked)}function wb(a,b){return m.nodeName(a,"table")&&m.nodeName(11!==b.nodeType?b:b.firstChild,"tr")?a.getElementsByTagName("tbody")[0]||a.appendChild(a.ownerDocument.createElement("tbody")):a}function xb(a){return a.type=(null!==m.find.attr(a,"type"))+"/"+a.type,a}function yb(a){var b=pb.exec(a.type);return b?a.type=b[1]:a.removeAttribute("type"),a}function zb(a,b){for(var c,d=0;null!=(c=a[d]);d++)m._data(c,"globalEval",!b||m._data(b[d],"globalEval"))}function Ab(a,b){if(1===b.nodeType&&m.hasData(a)){var c,d,e,f=m._data(a),g=m._data(b,f),h=f.events;if(h){delete g.handle,g.events={};for(c in h)for(d=0,e=h[c].length;e>d;d++)m.event.add(b,c,h[c][d])}g.data&&(g.data=m.extend({},g.data))}}function Bb(a,b){var c,d,e;if(1===b.nodeType){if(c=b.nodeName.toLowerCase(),!k.noCloneEvent&&b[m.expando]){e=m._data(b);for(d in e.events)m.removeEvent(b,d,e.handle);b.removeAttribute(m.expando)}"script"===c&&b.text!==a.text?(xb(b).text=a.text,yb(b)):"object"===c?(b.parentNode&&(b.outerHTML=a.outerHTML),k.html5Clone&&a.innerHTML&&!m.trim(b.innerHTML)&&(b.innerHTML=a.innerHTML)):"input"===c&&W.test(a.type)?(b.defaultChecked=b.checked=a.checked,b.value!==a.value&&(b.value=a.value)):"option"===c?b.defaultSelected=b.selected=a.defaultSelected:("input"===c||"textarea"===c)&&(b.defaultValue=a.defaultValue)}}m.extend({clone:function(a,b,c){var d,e,f,g,h,i=m.contains(a.ownerDocument,a);if(k.html5Clone||m.isXMLDoc(a)||!gb.test("<"+a.nodeName+">")?f=a.cloneNode(!0):(tb.innerHTML=a.outerHTML,tb.removeChild(f=tb.firstChild)),!(k.noCloneEvent&&k.noCloneChecked||1!==a.nodeType&&11!==a.nodeType||m.isXMLDoc(a)))for(d=ub(f),h=ub(a),g=0;null!=(e=h[g]);++g)d[g]&&Bb(e,d[g]);if(b)if(c)for(h=h||ub(a),d=d||ub(f),g=0;null!=(e=h[g]);g++)Ab(e,d[g]);else Ab(a,f);return d=ub(f,"script"),d.length>0&&zb(d,!i&&ub(a,"script")),d=h=e=null,f},buildFragment:function(a,b,c,d){for(var e,f,g,h,i,j,l,n=a.length,o=db(b),p=[],q=0;n>q;q++)if(f=a[q],f||0===f)if("object"===m.type(f))m.merge(p,f.nodeType?[f]:f);else if(lb.test(f)){h=h||o.appendChild(b.createElement("div")),i=(jb.exec(f)||["",""])[1].toLowerCase(),l=rb[i]||rb._default,h.innerHTML=l[1]+f.replace(ib,"<$1></$2>")+l[2],e=l[0];while(e--)h=h.lastChild;if(!k.leadingWhitespace&&hb.test(f)&&p.push(b.createTextNode(hb.exec(f)[0])),!k.tbody){f="table"!==i||kb.test(f)?"<table>"!==l[1]||kb.test(f)?0:h:h.firstChild,e=f&&f.childNodes.length;while(e--)m.nodeName(j=f.childNodes[e],"tbody")&&!j.childNodes.length&&f.removeChild(j)}m.merge(p,h.childNodes),h.textContent="";while(h.firstChild)h.removeChild(h.firstChild);h=o.lastChild}else p.push(b.createTextNode(f));h&&o.removeChild(h),k.appendChecked||m.grep(ub(p,"input"),vb),q=0;while(f=p[q++])if((!d||-1===m.inArray(f,d))&&(g=m.contains(f.ownerDocument,f),h=ub(o.appendChild(f),"script"),g&&zb(h),c)){e=0;while(f=h[e++])ob.test(f.type||"")&&c.push(f)}return h=null,o},cleanData:function(a,b){for(var d,e,f,g,h=0,i=m.expando,j=m.cache,l=k.deleteExpando,n=m.event.special;null!=(d=a[h]);h++)if((b||m.acceptData(d))&&(f=d[i],g=f&&j[f])){if(g.events)for(e in g.events)n[e]?m.event.remove(d,e):m.removeEvent(d,e,g.handle);j[f]&&(delete j[f],l?delete d[i]:typeof d.removeAttribute!==K?d.removeAttribute(i):d[i]=null,c.push(f))}}}),m.fn.extend({text:function(a){return V(this,function(a){return void 0===a?m.text(this):this.empty().append((this[0]&&this[0].ownerDocument||y).createTextNode(a))},null,a,arguments.length)},append:function(){return this.domManip(arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=wb(this,a);b.appendChild(a)}})},prepend:function(){return this.domManip(arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=wb(this,a);b.insertBefore(a,b.firstChild)}})},before:function(){return this.domManip(arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this)})},after:function(){return this.domManip(arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this.nextSibling)})},remove:function(a,b){for(var c,d=a?m.filter(a,this):this,e=0;null!=(c=d[e]);e++)b||1!==c.nodeType||m.cleanData(ub(c)),c.parentNode&&(b&&m.contains(c.ownerDocument,c)&&zb(ub(c,"script")),c.parentNode.removeChild(c));return this},empty:function(){for(var a,b=0;null!=(a=this[b]);b++){1===a.nodeType&&m.cleanData(ub(a,!1));while(a.firstChild)a.removeChild(a.firstChild);a.options&&m.nodeName(a,"select")&&(a.options.length=0)}return this},clone:function(a,b){return a=null==a?!1:a,b=null==b?a:b,this.map(function(){return m.clone(this,a,b)})},html:function(a){return V(this,function(a){var b=this[0]||{},c=0,d=this.length;if(void 0===a)return 1===b.nodeType?b.innerHTML.replace(fb,""):void 0;if(!("string"!=typeof a||mb.test(a)||!k.htmlSerialize&&gb.test(a)||!k.leadingWhitespace&&hb.test(a)||rb[(jb.exec(a)||["",""])[1].toLowerCase()])){a=a.replace(ib,"<$1></$2>");try{for(;d>c;c++)b=this[c]||{},1===b.nodeType&&(m.cleanData(ub(b,!1)),b.innerHTML=a);b=0}catch(e){}}b&&this.empty().append(a)},null,a,arguments.length)},replaceWith:function(){var a=arguments[0];return this.domManip(arguments,function(b){a=this.parentNode,m.cleanData(ub(this)),a&&a.replaceChild(b,this)}),a&&(a.length||a.nodeType)?this:this.remove()},detach:function(a){return this.remove(a,!0)},domManip:function(a,b){a=e.apply([],a);var c,d,f,g,h,i,j=0,l=this.length,n=this,o=l-1,p=a[0],q=m.isFunction(p);if(q||l>1&&"string"==typeof p&&!k.checkClone&&nb.test(p))return this.each(function(c){var d=n.eq(c);q&&(a[0]=p.call(this,c,d.html())),d.domManip(a,b)});if(l&&(i=m.buildFragment(a,this[0].ownerDocument,!1,this),c=i.firstChild,1===i.childNodes.length&&(i=c),c)){for(g=m.map(ub(i,"script"),xb),f=g.length;l>j;j++)d=i,j!==o&&(d=m.clone(d,!0,!0),f&&m.merge(g,ub(d,"script"))),b.call(this[j],d,j);if(f)for(h=g[g.length-1].ownerDocument,m.map(g,yb),j=0;f>j;j++)d=g[j],ob.test(d.type||"")&&!m._data(d,"globalEval")&&m.contains(h,d)&&(d.src?m._evalUrl&&m._evalUrl(d.src):m.globalEval((d.text||d.textContent||d.innerHTML||"").replace(qb,"")));i=c=null}return this}}),m.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){m.fn[a]=function(a){for(var c,d=0,e=[],g=m(a),h=g.length-1;h>=d;d++)c=d===h?this:this.clone(!0),m(g[d])[b](c),f.apply(e,c.get());return this.pushStack(e)}});var Cb,Db={};function Eb(b,c){var d,e=m(c.createElement(b)).appendTo(c.body),f=a.getDefaultComputedStyle&&(d=a.getDefaultComputedStyle(e[0]))?d.display:m.css(e[0],"display");return e.detach(),f}function Fb(a){var b=y,c=Db[a];return c||(c=Eb(a,b),"none"!==c&&c||(Cb=(Cb||m("<iframe frameborder='0' width='0' height='0'/>")).appendTo(b.documentElement),b=(Cb[0].contentWindow||Cb[0].contentDocument).document,b.write(),b.close(),c=Eb(a,b),Cb.detach()),Db[a]=c),c}!function(){var a;k.shrinkWrapBlocks=function(){if(null!=a)return a;a=!1;var b,c,d;return c=y.getElementsByTagName("body")[0],c&&c.style?(b=y.createElement("div"),d=y.createElement("div"),d.style.cssText="position:absolute;border:0;width:0;height:0;top:0;left:-9999px",c.appendChild(d).appendChild(b),typeof b.style.zoom!==K&&(b.style.cssText="-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;display:block;margin:0;border:0;padding:1px;width:1px;zoom:1",b.appendChild(y.createElement("div")).style.width="5px",a=3!==b.offsetWidth),c.removeChild(d),a):void 0}}();var Gb=/^margin/,Hb=new RegExp("^("+S+")(?!px)[a-z%]+$","i"),Ib,Jb,Kb=/^(top|right|bottom|left)$/;a.getComputedStyle?(Ib=function(a){return a.ownerDocument.defaultView.getComputedStyle(a,null)},Jb=function(a,b,c){var d,e,f,g,h=a.style;return c=c||Ib(a),g=c?c.getPropertyValue(b)||c[b]:void 0,c&&(""!==g||m.contains(a.ownerDocument,a)||(g=m.style(a,b)),Hb.test(g)&&Gb.test(b)&&(d=h.width,e=h.minWidth,f=h.maxWidth,h.minWidth=h.maxWidth=h.width=g,g=c.width,h.width=d,h.minWidth=e,h.maxWidth=f)),void 0===g?g:g+""}):y.documentElement.currentStyle&&(Ib=function(a){return a.currentStyle},Jb=function(a,b,c){var d,e,f,g,h=a.style;return c=c||Ib(a),g=c?c[b]:void 0,null==g&&h&&h[b]&&(g=h[b]),Hb.test(g)&&!Kb.test(b)&&(d=h.left,e=a.runtimeStyle,f=e&&e.left,f&&(e.left=a.currentStyle.left),h.left="fontSize"===b?"1em":g,g=h.pixelLeft+"px",h.left=d,f&&(e.left=f)),void 0===g?g:g+""||"auto"});function Lb(a,b){return{get:function(){var c=a();if(null!=c)return c?void delete this.get:(this.get=b).apply(this,arguments)}}}!function(){var b,c,d,e,f,g,h;if(b=y.createElement("div"),b.innerHTML="  <link/><table></table><a href='/a'>a</a><input type='checkbox'/>",d=b.getElementsByTagName("a")[0],c=d&&d.style){c.cssText="float:left;opacity:.5",k.opacity="0.5"===c.opacity,k.cssFloat=!!c.cssFloat,b.style.backgroundClip="content-box",b.cloneNode(!0).style.backgroundClip="",k.clearCloneStyle="content-box"===b.style.backgroundClip,k.boxSizing=""===c.boxSizing||""===c.MozBoxSizing||""===c.WebkitBoxSizing,m.extend(k,{reliableHiddenOffsets:function(){return null==g&&i(),g},boxSizingReliable:function(){return null==f&&i(),f},pixelPosition:function(){return null==e&&i(),e},reliableMarginRight:function(){return null==h&&i(),h}});function i(){var b,c,d,i;c=y.getElementsByTagName("body")[0],c&&c.style&&(b=y.createElement("div"),d=y.createElement("div"),d.style.cssText="position:absolute;border:0;width:0;height:0;top:0;left:-9999px",c.appendChild(d).appendChild(b),b.style.cssText="-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;display:block;margin-top:1%;top:1%;border:1px;padding:1px;width:4px;position:absolute",e=f=!1,h=!0,a.getComputedStyle&&(e="1%"!==(a.getComputedStyle(b,null)||{}).top,f="4px"===(a.getComputedStyle(b,null)||{width:"4px"}).width,i=b.appendChild(y.createElement("div")),i.style.cssText=b.style.cssText="-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;display:block;margin:0;border:0;padding:0",i.style.marginRight=i.style.width="0",b.style.width="1px",h=!parseFloat((a.getComputedStyle(i,null)||{}).marginRight)),b.innerHTML="<table><tr><td></td><td>t</td></tr></table>",i=b.getElementsByTagName("td"),i[0].style.cssText="margin:0;border:0;padding:0;display:none",g=0===i[0].offsetHeight,g&&(i[0].style.display="",i[1].style.display="none",g=0===i[0].offsetHeight),c.removeChild(d))}}}(),m.swap=function(a,b,c,d){var e,f,g={};for(f in b)g[f]=a.style[f],a.style[f]=b[f];e=c.apply(a,d||[]);for(f in b)a.style[f]=g[f];return e};var Mb=/alpha\([^)]*\)/i,Nb=/opacity\s*=\s*([^)]*)/,Ob=/^(none|table(?!-c[ea]).+)/,Pb=new RegExp("^("+S+")(.*)$","i"),Qb=new RegExp("^([+-])=("+S+")","i"),Rb={position:"absolute",visibility:"hidden",display:"block"},Sb={letterSpacing:"0",fontWeight:"400"},Tb=["Webkit","O","Moz","ms"];function Ub(a,b){if(b in a)return b;var c=b.charAt(0).toUpperCase()+b.slice(1),d=b,e=Tb.length;while(e--)if(b=Tb[e]+c,b in a)return b;return d}function Vb(a,b){for(var c,d,e,f=[],g=0,h=a.length;h>g;g++)d=a[g],d.style&&(f[g]=m._data(d,"olddisplay"),c=d.style.display,b?(f[g]||"none"!==c||(d.style.display=""),""===d.style.display&&U(d)&&(f[g]=m._data(d,"olddisplay",Fb(d.nodeName)))):(e=U(d),(c&&"none"!==c||!e)&&m._data(d,"olddisplay",e?c:m.css(d,"display"))));for(g=0;h>g;g++)d=a[g],d.style&&(b&&"none"!==d.style.display&&""!==d.style.display||(d.style.display=b?f[g]||"":"none"));return a}function Wb(a,b,c){var d=Pb.exec(b);return d?Math.max(0,d[1]-(c||0))+(d[2]||"px"):b}function Xb(a,b,c,d,e){for(var f=c===(d?"border":"content")?4:"width"===b?1:0,g=0;4>f;f+=2)"margin"===c&&(g+=m.css(a,c+T[f],!0,e)),d?("content"===c&&(g-=m.css(a,"padding"+T[f],!0,e)),"margin"!==c&&(g-=m.css(a,"border"+T[f]+"Width",!0,e))):(g+=m.css(a,"padding"+T[f],!0,e),"padding"!==c&&(g+=m.css(a,"border"+T[f]+"Width",!0,e)));return g}function Yb(a,b,c){var d=!0,e="width"===b?a.offsetWidth:a.offsetHeight,f=Ib(a),g=k.boxSizing&&"border-box"===m.css(a,"boxSizing",!1,f);if(0>=e||null==e){if(e=Jb(a,b,f),(0>e||null==e)&&(e=a.style[b]),Hb.test(e))return e;d=g&&(k.boxSizingReliable()||e===a.style[b]),e=parseFloat(e)||0}return e+Xb(a,b,c||(g?"border":"content"),d,f)+"px"}m.extend({cssHooks:{opacity:{get:function(a,b){if(b){var c=Jb(a,"opacity");return""===c?"1":c}}}},cssNumber:{columnCount:!0,fillOpacity:!0,flexGrow:!0,flexShrink:!0,fontWeight:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{"float":k.cssFloat?"cssFloat":"styleFloat"},style:function(a,b,c,d){if(a&&3!==a.nodeType&&8!==a.nodeType&&a.style){var e,f,g,h=m.camelCase(b),i=a.style;if(b=m.cssProps[h]||(m.cssProps[h]=Ub(i,h)),g=m.cssHooks[b]||m.cssHooks[h],void 0===c)return g&&"get"in g&&void 0!==(e=g.get(a,!1,d))?e:i[b];if(f=typeof c,"string"===f&&(e=Qb.exec(c))&&(c=(e[1]+1)*e[2]+parseFloat(m.css(a,b)),f="number"),null!=c&&c===c&&("number"!==f||m.cssNumber[h]||(c+="px"),k.clearCloneStyle||""!==c||0!==b.indexOf("background")||(i[b]="inherit"),!(g&&"set"in g&&void 0===(c=g.set(a,c,d)))))try{i[b]=c}catch(j){}}},css:function(a,b,c,d){var e,f,g,h=m.camelCase(b);return b=m.cssProps[h]||(m.cssProps[h]=Ub(a.style,h)),g=m.cssHooks[b]||m.cssHooks[h],g&&"get"in g&&(f=g.get(a,!0,c)),void 0===f&&(f=Jb(a,b,d)),"normal"===f&&b in Sb&&(f=Sb[b]),""===c||c?(e=parseFloat(f),c===!0||m.isNumeric(e)?e||0:f):f}}),m.each(["height","width"],function(a,b){m.cssHooks[b]={get:function(a,c,d){return c?Ob.test(m.css(a,"display"))&&0===a.offsetWidth?m.swap(a,Rb,function(){return Yb(a,b,d)}):Yb(a,b,d):void 0},set:function(a,c,d){var e=d&&Ib(a);return Wb(a,c,d?Xb(a,b,d,k.boxSizing&&"border-box"===m.css(a,"boxSizing",!1,e),e):0)}}}),k.opacity||(m.cssHooks.opacity={get:function(a,b){return Nb.test((b&&a.currentStyle?a.currentStyle.filter:a.style.filter)||"")?.01*parseFloat(RegExp.$1)+"":b?"1":""},set:function(a,b){var c=a.style,d=a.currentStyle,e=m.isNumeric(b)?"alpha(opacity="+100*b+")":"",f=d&&d.filter||c.filter||"";c.zoom=1,(b>=1||""===b)&&""===m.trim(f.replace(Mb,""))&&c.removeAttribute&&(c.removeAttribute("filter"),""===b||d&&!d.filter)||(c.filter=Mb.test(f)?f.replace(Mb,e):f+" "+e)}}),m.cssHooks.marginRight=Lb(k.reliableMarginRight,function(a,b){return b?m.swap(a,{display:"inline-block"},Jb,[a,"marginRight"]):void 0}),m.each({margin:"",padding:"",border:"Width"},function(a,b){m.cssHooks[a+b]={expand:function(c){for(var d=0,e={},f="string"==typeof c?c.split(" "):[c];4>d;d++)e[a+T[d]+b]=f[d]||f[d-2]||f[0];return e}},Gb.test(a)||(m.cssHooks[a+b].set=Wb)}),m.fn.extend({css:function(a,b){return V(this,function(a,b,c){var d,e,f={},g=0;if(m.isArray(b)){for(d=Ib(a),e=b.length;e>g;g++)f[b[g]]=m.css(a,b[g],!1,d);return f}return void 0!==c?m.style(a,b,c):m.css(a,b)},a,b,arguments.length>1)},show:function(){return Vb(this,!0)},hide:function(){return Vb(this)},toggle:function(a){return"boolean"==typeof a?a?this.show():this.hide():this.each(function(){U(this)?m(this).show():m(this).hide()})}});function Zb(a,b,c,d,e){return new Zb.prototype.init(a,b,c,d,e)}m.Tween=Zb,Zb.prototype={constructor:Zb,init:function(a,b,c,d,e,f){this.elem=a,this.prop=c,this.easing=e||"swing",this.options=b,this.start=this.now=this.cur(),this.end=d,this.unit=f||(m.cssNumber[c]?"":"px")
+},cur:function(){var a=Zb.propHooks[this.prop];return a&&a.get?a.get(this):Zb.propHooks._default.get(this)},run:function(a){var b,c=Zb.propHooks[this.prop];return this.pos=b=this.options.duration?m.easing[this.easing](a,this.options.duration*a,0,1,this.options.duration):a,this.now=(this.end-this.start)*b+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),c&&c.set?c.set(this):Zb.propHooks._default.set(this),this}},Zb.prototype.init.prototype=Zb.prototype,Zb.propHooks={_default:{get:function(a){var b;return null==a.elem[a.prop]||a.elem.style&&null!=a.elem.style[a.prop]?(b=m.css(a.elem,a.prop,""),b&&"auto"!==b?b:0):a.elem[a.prop]},set:function(a){m.fx.step[a.prop]?m.fx.step[a.prop](a):a.elem.style&&(null!=a.elem.style[m.cssProps[a.prop]]||m.cssHooks[a.prop])?m.style(a.elem,a.prop,a.now+a.unit):a.elem[a.prop]=a.now}}},Zb.propHooks.scrollTop=Zb.propHooks.scrollLeft={set:function(a){a.elem.nodeType&&a.elem.parentNode&&(a.elem[a.prop]=a.now)}},m.easing={linear:function(a){return a},swing:function(a){return.5-Math.cos(a*Math.PI)/2}},m.fx=Zb.prototype.init,m.fx.step={};var $b,_b,ac=/^(?:toggle|show|hide)$/,bc=new RegExp("^(?:([+-])=|)("+S+")([a-z%]*)$","i"),cc=/queueHooks$/,dc=[ic],ec={"*":[function(a,b){var c=this.createTween(a,b),d=c.cur(),e=bc.exec(b),f=e&&e[3]||(m.cssNumber[a]?"":"px"),g=(m.cssNumber[a]||"px"!==f&&+d)&&bc.exec(m.css(c.elem,a)),h=1,i=20;if(g&&g[3]!==f){f=f||g[3],e=e||[],g=+d||1;do h=h||".5",g/=h,m.style(c.elem,a,g+f);while(h!==(h=c.cur()/d)&&1!==h&&--i)}return e&&(g=c.start=+g||+d||0,c.unit=f,c.end=e[1]?g+(e[1]+1)*e[2]:+e[2]),c}]};function fc(){return setTimeout(function(){$b=void 0}),$b=m.now()}function gc(a,b){var c,d={height:a},e=0;for(b=b?1:0;4>e;e+=2-b)c=T[e],d["margin"+c]=d["padding"+c]=a;return b&&(d.opacity=d.width=a),d}function hc(a,b,c){for(var d,e=(ec[b]||[]).concat(ec["*"]),f=0,g=e.length;g>f;f++)if(d=e[f].call(c,b,a))return d}function ic(a,b,c){var d,e,f,g,h,i,j,l,n=this,o={},p=a.style,q=a.nodeType&&U(a),r=m._data(a,"fxshow");c.queue||(h=m._queueHooks(a,"fx"),null==h.unqueued&&(h.unqueued=0,i=h.empty.fire,h.empty.fire=function(){h.unqueued||i()}),h.unqueued++,n.always(function(){n.always(function(){h.unqueued--,m.queue(a,"fx").length||h.empty.fire()})})),1===a.nodeType&&("height"in b||"width"in b)&&(c.overflow=[p.overflow,p.overflowX,p.overflowY],j=m.css(a,"display"),l="none"===j?m._data(a,"olddisplay")||Fb(a.nodeName):j,"inline"===l&&"none"===m.css(a,"float")&&(k.inlineBlockNeedsLayout&&"inline"!==Fb(a.nodeName)?p.zoom=1:p.display="inline-block")),c.overflow&&(p.overflow="hidden",k.shrinkWrapBlocks()||n.always(function(){p.overflow=c.overflow[0],p.overflowX=c.overflow[1],p.overflowY=c.overflow[2]}));for(d in b)if(e=b[d],ac.exec(e)){if(delete b[d],f=f||"toggle"===e,e===(q?"hide":"show")){if("show"!==e||!r||void 0===r[d])continue;q=!0}o[d]=r&&r[d]||m.style(a,d)}else j=void 0;if(m.isEmptyObject(o))"inline"===("none"===j?Fb(a.nodeName):j)&&(p.display=j);else{r?"hidden"in r&&(q=r.hidden):r=m._data(a,"fxshow",{}),f&&(r.hidden=!q),q?m(a).show():n.done(function(){m(a).hide()}),n.done(function(){var b;m._removeData(a,"fxshow");for(b in o)m.style(a,b,o[b])});for(d in o)g=hc(q?r[d]:0,d,n),d in r||(r[d]=g.start,q&&(g.end=g.start,g.start="width"===d||"height"===d?1:0))}}function jc(a,b){var c,d,e,f,g;for(c in a)if(d=m.camelCase(c),e=b[d],f=a[c],m.isArray(f)&&(e=f[1],f=a[c]=f[0]),c!==d&&(a[d]=f,delete a[c]),g=m.cssHooks[d],g&&"expand"in g){f=g.expand(f),delete a[d];for(c in f)c in a||(a[c]=f[c],b[c]=e)}else b[d]=e}function kc(a,b,c){var d,e,f=0,g=dc.length,h=m.Deferred().always(function(){delete i.elem}),i=function(){if(e)return!1;for(var b=$b||fc(),c=Math.max(0,j.startTime+j.duration-b),d=c/j.duration||0,f=1-d,g=0,i=j.tweens.length;i>g;g++)j.tweens[g].run(f);return h.notifyWith(a,[j,f,c]),1>f&&i?c:(h.resolveWith(a,[j]),!1)},j=h.promise({elem:a,props:m.extend({},b),opts:m.extend(!0,{specialEasing:{}},c),originalProperties:b,originalOptions:c,startTime:$b||fc(),duration:c.duration,tweens:[],createTween:function(b,c){var d=m.Tween(a,j.opts,b,c,j.opts.specialEasing[b]||j.opts.easing);return j.tweens.push(d),d},stop:function(b){var c=0,d=b?j.tweens.length:0;if(e)return this;for(e=!0;d>c;c++)j.tweens[c].run(1);return b?h.resolveWith(a,[j,b]):h.rejectWith(a,[j,b]),this}}),k=j.props;for(jc(k,j.opts.specialEasing);g>f;f++)if(d=dc[f].call(j,a,k,j.opts))return d;return m.map(k,hc,j),m.isFunction(j.opts.start)&&j.opts.start.call(a,j),m.fx.timer(m.extend(i,{elem:a,anim:j,queue:j.opts.queue})),j.progress(j.opts.progress).done(j.opts.done,j.opts.complete).fail(j.opts.fail).always(j.opts.always)}m.Animation=m.extend(kc,{tweener:function(a,b){m.isFunction(a)?(b=a,a=["*"]):a=a.split(" ");for(var c,d=0,e=a.length;e>d;d++)c=a[d],ec[c]=ec[c]||[],ec[c].unshift(b)},prefilter:function(a,b){b?dc.unshift(a):dc.push(a)}}),m.speed=function(a,b,c){var d=a&&"object"==typeof a?m.extend({},a):{complete:c||!c&&b||m.isFunction(a)&&a,duration:a,easing:c&&b||b&&!m.isFunction(b)&&b};return d.duration=m.fx.off?0:"number"==typeof d.duration?d.duration:d.duration in m.fx.speeds?m.fx.speeds[d.duration]:m.fx.speeds._default,(null==d.queue||d.queue===!0)&&(d.queue="fx"),d.old=d.complete,d.complete=function(){m.isFunction(d.old)&&d.old.call(this),d.queue&&m.dequeue(this,d.queue)},d},m.fn.extend({fadeTo:function(a,b,c,d){return this.filter(U).css("opacity",0).show().end().animate({opacity:b},a,c,d)},animate:function(a,b,c,d){var e=m.isEmptyObject(a),f=m.speed(b,c,d),g=function(){var b=kc(this,m.extend({},a),f);(e||m._data(this,"finish"))&&b.stop(!0)};return g.finish=g,e||f.queue===!1?this.each(g):this.queue(f.queue,g)},stop:function(a,b,c){var d=function(a){var b=a.stop;delete a.stop,b(c)};return"string"!=typeof a&&(c=b,b=a,a=void 0),b&&a!==!1&&this.queue(a||"fx",[]),this.each(function(){var b=!0,e=null!=a&&a+"queueHooks",f=m.timers,g=m._data(this);if(e)g[e]&&g[e].stop&&d(g[e]);else for(e in g)g[e]&&g[e].stop&&cc.test(e)&&d(g[e]);for(e=f.length;e--;)f[e].elem!==this||null!=a&&f[e].queue!==a||(f[e].anim.stop(c),b=!1,f.splice(e,1));(b||!c)&&m.dequeue(this,a)})},finish:function(a){return a!==!1&&(a=a||"fx"),this.each(function(){var b,c=m._data(this),d=c[a+"queue"],e=c[a+"queueHooks"],f=m.timers,g=d?d.length:0;for(c.finish=!0,m.queue(this,a,[]),e&&e.stop&&e.stop.call(this,!0),b=f.length;b--;)f[b].elem===this&&f[b].queue===a&&(f[b].anim.stop(!0),f.splice(b,1));for(b=0;g>b;b++)d[b]&&d[b].finish&&d[b].finish.call(this);delete c.finish})}}),m.each(["toggle","show","hide"],function(a,b){var c=m.fn[b];m.fn[b]=function(a,d,e){return null==a||"boolean"==typeof a?c.apply(this,arguments):this.animate(gc(b,!0),a,d,e)}}),m.each({slideDown:gc("show"),slideUp:gc("hide"),slideToggle:gc("toggle"),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"},fadeToggle:{opacity:"toggle"}},function(a,b){m.fn[a]=function(a,c,d){return this.animate(b,a,c,d)}}),m.timers=[],m.fx.tick=function(){var a,b=m.timers,c=0;for($b=m.now();c<b.length;c++)a=b[c],a()||b[c]!==a||b.splice(c--,1);b.length||m.fx.stop(),$b=void 0},m.fx.timer=function(a){m.timers.push(a),a()?m.fx.start():m.timers.pop()},m.fx.interval=13,m.fx.start=function(){_b||(_b=setInterval(m.fx.tick,m.fx.interval))},m.fx.stop=function(){clearInterval(_b),_b=null},m.fx.speeds={slow:600,fast:200,_default:400},m.fn.delay=function(a,b){return a=m.fx?m.fx.speeds[a]||a:a,b=b||"fx",this.queue(b,function(b,c){var d=setTimeout(b,a);c.stop=function(){clearTimeout(d)}})},function(){var a,b,c,d,e;b=y.createElement("div"),b.setAttribute("className","t"),b.innerHTML="  <link/><table></table><a href='/a'>a</a><input type='checkbox'/>",d=b.getElementsByTagName("a")[0],c=y.createElement("select"),e=c.appendChild(y.createElement("option")),a=b.getElementsByTagName("input")[0],d.style.cssText="top:1px",k.getSetAttribute="t"!==b.className,k.style=/top/.test(d.getAttribute("style")),k.hrefNormalized="/a"===d.getAttribute("href"),k.checkOn=!!a.value,k.optSelected=e.selected,k.enctype=!!y.createElement("form").enctype,c.disabled=!0,k.optDisabled=!e.disabled,a=y.createElement("input"),a.setAttribute("value",""),k.input=""===a.getAttribute("value"),a.value="t",a.setAttribute("type","radio"),k.radioValue="t"===a.value}();var lc=/\r/g;m.fn.extend({val:function(a){var b,c,d,e=this[0];{if(arguments.length)return d=m.isFunction(a),this.each(function(c){var e;1===this.nodeType&&(e=d?a.call(this,c,m(this).val()):a,null==e?e="":"number"==typeof e?e+="":m.isArray(e)&&(e=m.map(e,function(a){return null==a?"":a+""})),b=m.valHooks[this.type]||m.valHooks[this.nodeName.toLowerCase()],b&&"set"in b&&void 0!==b.set(this,e,"value")||(this.value=e))});if(e)return b=m.valHooks[e.type]||m.valHooks[e.nodeName.toLowerCase()],b&&"get"in b&&void 0!==(c=b.get(e,"value"))?c:(c=e.value,"string"==typeof c?c.replace(lc,""):null==c?"":c)}}}),m.extend({valHooks:{option:{get:function(a){var b=m.find.attr(a,"value");return null!=b?b:m.trim(m.text(a))}},select:{get:function(a){for(var b,c,d=a.options,e=a.selectedIndex,f="select-one"===a.type||0>e,g=f?null:[],h=f?e+1:d.length,i=0>e?h:f?e:0;h>i;i++)if(c=d[i],!(!c.selected&&i!==e||(k.optDisabled?c.disabled:null!==c.getAttribute("disabled"))||c.parentNode.disabled&&m.nodeName(c.parentNode,"optgroup"))){if(b=m(c).val(),f)return b;g.push(b)}return g},set:function(a,b){var c,d,e=a.options,f=m.makeArray(b),g=e.length;while(g--)if(d=e[g],m.inArray(m.valHooks.option.get(d),f)>=0)try{d.selected=c=!0}catch(h){d.scrollHeight}else d.selected=!1;return c||(a.selectedIndex=-1),e}}}}),m.each(["radio","checkbox"],function(){m.valHooks[this]={set:function(a,b){return m.isArray(b)?a.checked=m.inArray(m(a).val(),b)>=0:void 0}},k.checkOn||(m.valHooks[this].get=function(a){return null===a.getAttribute("value")?"on":a.value})});var mc,nc,oc=m.expr.attrHandle,pc=/^(?:checked|selected)$/i,qc=k.getSetAttribute,rc=k.input;m.fn.extend({attr:function(a,b){return V(this,m.attr,a,b,arguments.length>1)},removeAttr:function(a){return this.each(function(){m.removeAttr(this,a)})}}),m.extend({attr:function(a,b,c){var d,e,f=a.nodeType;if(a&&3!==f&&8!==f&&2!==f)return typeof a.getAttribute===K?m.prop(a,b,c):(1===f&&m.isXMLDoc(a)||(b=b.toLowerCase(),d=m.attrHooks[b]||(m.expr.match.bool.test(b)?nc:mc)),void 0===c?d&&"get"in d&&null!==(e=d.get(a,b))?e:(e=m.find.attr(a,b),null==e?void 0:e):null!==c?d&&"set"in d&&void 0!==(e=d.set(a,c,b))?e:(a.setAttribute(b,c+""),c):void m.removeAttr(a,b))},removeAttr:function(a,b){var c,d,e=0,f=b&&b.match(E);if(f&&1===a.nodeType)while(c=f[e++])d=m.propFix[c]||c,m.expr.match.bool.test(c)?rc&&qc||!pc.test(c)?a[d]=!1:a[m.camelCase("default-"+c)]=a[d]=!1:m.attr(a,c,""),a.removeAttribute(qc?c:d)},attrHooks:{type:{set:function(a,b){if(!k.radioValue&&"radio"===b&&m.nodeName(a,"input")){var c=a.value;return a.setAttribute("type",b),c&&(a.value=c),b}}}}}),nc={set:function(a,b,c){return b===!1?m.removeAttr(a,c):rc&&qc||!pc.test(c)?a.setAttribute(!qc&&m.propFix[c]||c,c):a[m.camelCase("default-"+c)]=a[c]=!0,c}},m.each(m.expr.match.bool.source.match(/\w+/g),function(a,b){var c=oc[b]||m.find.attr;oc[b]=rc&&qc||!pc.test(b)?function(a,b,d){var e,f;return d||(f=oc[b],oc[b]=e,e=null!=c(a,b,d)?b.toLowerCase():null,oc[b]=f),e}:function(a,b,c){return c?void 0:a[m.camelCase("default-"+b)]?b.toLowerCase():null}}),rc&&qc||(m.attrHooks.value={set:function(a,b,c){return m.nodeName(a,"input")?void(a.defaultValue=b):mc&&mc.set(a,b,c)}}),qc||(mc={set:function(a,b,c){var d=a.getAttributeNode(c);return d||a.setAttributeNode(d=a.ownerDocument.createAttribute(c)),d.value=b+="","value"===c||b===a.getAttribute(c)?b:void 0}},oc.id=oc.name=oc.coords=function(a,b,c){var d;return c?void 0:(d=a.getAttributeNode(b))&&""!==d.value?d.value:null},m.valHooks.button={get:function(a,b){var c=a.getAttributeNode(b);return c&&c.specified?c.value:void 0},set:mc.set},m.attrHooks.contenteditable={set:function(a,b,c){mc.set(a,""===b?!1:b,c)}},m.each(["width","height"],function(a,b){m.attrHooks[b]={set:function(a,c){return""===c?(a.setAttribute(b,"auto"),c):void 0}}})),k.style||(m.attrHooks.style={get:function(a){return a.style.cssText||void 0},set:function(a,b){return a.style.cssText=b+""}});var sc=/^(?:input|select|textarea|button|object)$/i,tc=/^(?:a|area)$/i;m.fn.extend({prop:function(a,b){return V(this,m.prop,a,b,arguments.length>1)},removeProp:function(a){return a=m.propFix[a]||a,this.each(function(){try{this[a]=void 0,delete this[a]}catch(b){}})}}),m.extend({propFix:{"for":"htmlFor","class":"className"},prop:function(a,b,c){var d,e,f,g=a.nodeType;if(a&&3!==g&&8!==g&&2!==g)return f=1!==g||!m.isXMLDoc(a),f&&(b=m.propFix[b]||b,e=m.propHooks[b]),void 0!==c?e&&"set"in e&&void 0!==(d=e.set(a,c,b))?d:a[b]=c:e&&"get"in e&&null!==(d=e.get(a,b))?d:a[b]},propHooks:{tabIndex:{get:function(a){var b=m.find.attr(a,"tabindex");return b?parseInt(b,10):sc.test(a.nodeName)||tc.test(a.nodeName)&&a.href?0:-1}}}}),k.hrefNormalized||m.each(["href","src"],function(a,b){m.propHooks[b]={get:function(a){return a.getAttribute(b,4)}}}),k.optSelected||(m.propHooks.selected={get:function(a){var b=a.parentNode;return b&&(b.selectedIndex,b.parentNode&&b.parentNode.selectedIndex),null}}),m.each(["tabIndex","readOnly","maxLength","cellSpacing","cellPadding","rowSpan","colSpan","useMap","frameBorder","contentEditable"],function(){m.propFix[this.toLowerCase()]=this}),k.enctype||(m.propFix.enctype="encoding");var uc=/[\t\r\n\f]/g;m.fn.extend({addClass:function(a){var b,c,d,e,f,g,h=0,i=this.length,j="string"==typeof a&&a;if(m.isFunction(a))return this.each(function(b){m(this).addClass(a.call(this,b,this.className))});if(j)for(b=(a||"").match(E)||[];i>h;h++)if(c=this[h],d=1===c.nodeType&&(c.className?(" "+c.className+" ").replace(uc," "):" ")){f=0;while(e=b[f++])d.indexOf(" "+e+" ")<0&&(d+=e+" ");g=m.trim(d),c.className!==g&&(c.className=g)}return this},removeClass:function(a){var b,c,d,e,f,g,h=0,i=this.length,j=0===arguments.length||"string"==typeof a&&a;if(m.isFunction(a))return this.each(function(b){m(this).removeClass(a.call(this,b,this.className))});if(j)for(b=(a||"").match(E)||[];i>h;h++)if(c=this[h],d=1===c.nodeType&&(c.className?(" "+c.className+" ").replace(uc," "):"")){f=0;while(e=b[f++])while(d.indexOf(" "+e+" ")>=0)d=d.replace(" "+e+" "," ");g=a?m.trim(d):"",c.className!==g&&(c.className=g)}return this},toggleClass:function(a,b){var c=typeof a;return"boolean"==typeof b&&"string"===c?b?this.addClass(a):this.removeClass(a):this.each(m.isFunction(a)?function(c){m(this).toggleClass(a.call(this,c,this.className,b),b)}:function(){if("string"===c){var b,d=0,e=m(this),f=a.match(E)||[];while(b=f[d++])e.hasClass(b)?e.removeClass(b):e.addClass(b)}else(c===K||"boolean"===c)&&(this.className&&m._data(this,"__className__",this.className),this.className=this.className||a===!1?"":m._data(this,"__className__")||"")})},hasClass:function(a){for(var b=" "+a+" ",c=0,d=this.length;d>c;c++)if(1===this[c].nodeType&&(" "+this[c].className+" ").replace(uc," ").indexOf(b)>=0)return!0;return!1}}),m.each("blur focus focusin focusout load resize scroll unload click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup error contextmenu".split(" "),function(a,b){m.fn[b]=function(a,c){return arguments.length>0?this.on(b,null,a,c):this.trigger(b)}}),m.fn.extend({hover:function(a,b){return this.mouseenter(a).mouseleave(b||a)},bind:function(a,b,c){return this.on(a,null,b,c)},unbind:function(a,b){return this.off(a,null,b)},delegate:function(a,b,c,d){return this.on(b,a,c,d)},undelegate:function(a,b,c){return 1===arguments.length?this.off(a,"**"):this.off(b,a||"**",c)}});var vc=m.now(),wc=/\?/,xc=/(,)|(\[|{)|(}|])|"(?:[^"\\\r\n]|\\["\\\/bfnrt]|\\u[\da-fA-F]{4})*"\s*:?|true|false|null|-?(?!0\d)\d+(?:\.\d+|)(?:[eE][+-]?\d+|)/g;m.parseJSON=function(b){if(a.JSON&&a.JSON.parse)return a.JSON.parse(b+"");var c,d=null,e=m.trim(b+"");return e&&!m.trim(e.replace(xc,function(a,b,e,f){return c&&b&&(d=0),0===d?a:(c=e||b,d+=!f-!e,"")}))?Function("return "+e)():m.error("Invalid JSON: "+b)},m.parseXML=function(b){var c,d;if(!b||"string"!=typeof b)return null;try{a.DOMParser?(d=new DOMParser,c=d.parseFromString(b,"text/xml")):(c=new ActiveXObject("Microsoft.XMLDOM"),c.async="false",c.loadXML(b))}catch(e){c=void 0}return c&&c.documentElement&&!c.getElementsByTagName("parsererror").length||m.error("Invalid XML: "+b),c};var yc,zc,Ac=/#.*$/,Bc=/([?&])_=[^&]*/,Cc=/^(.*?):[ \t]*([^\r\n]*)\r?$/gm,Dc=/^(?:about|app|app-storage|.+-extension|file|res|widget):$/,Ec=/^(?:GET|HEAD)$/,Fc=/^\/\//,Gc=/^([\w.+-]+:)(?:\/\/(?:[^\/?#]*@|)([^\/?#:]*)(?::(\d+)|)|)/,Hc={},Ic={},Jc="*/".concat("*");try{zc=location.href}catch(Kc){zc=y.createElement("a"),zc.href="",zc=zc.href}yc=Gc.exec(zc.toLowerCase())||[];function Lc(a){return function(b,c){"string"!=typeof b&&(c=b,b="*");var d,e=0,f=b.toLowerCase().match(E)||[];if(m.isFunction(c))while(d=f[e++])"+"===d.charAt(0)?(d=d.slice(1)||"*",(a[d]=a[d]||[]).unshift(c)):(a[d]=a[d]||[]).push(c)}}function Mc(a,b,c,d){var e={},f=a===Ic;function g(h){var i;return e[h]=!0,m.each(a[h]||[],function(a,h){var j=h(b,c,d);return"string"!=typeof j||f||e[j]?f?!(i=j):void 0:(b.dataTypes.unshift(j),g(j),!1)}),i}return g(b.dataTypes[0])||!e["*"]&&g("*")}function Nc(a,b){var c,d,e=m.ajaxSettings.flatOptions||{};for(d in b)void 0!==b[d]&&((e[d]?a:c||(c={}))[d]=b[d]);return c&&m.extend(!0,a,c),a}function Oc(a,b,c){var d,e,f,g,h=a.contents,i=a.dataTypes;while("*"===i[0])i.shift(),void 0===e&&(e=a.mimeType||b.getResponseHeader("Content-Type"));if(e)for(g in h)if(h[g]&&h[g].test(e)){i.unshift(g);break}if(i[0]in c)f=i[0];else{for(g in c){if(!i[0]||a.converters[g+" "+i[0]]){f=g;break}d||(d=g)}f=f||d}return f?(f!==i[0]&&i.unshift(f),c[f]):void 0}function Pc(a,b,c,d){var e,f,g,h,i,j={},k=a.dataTypes.slice();if(k[1])for(g in a.converters)j[g.toLowerCase()]=a.converters[g];f=k.shift();while(f)if(a.responseFields[f]&&(c[a.responseFields[f]]=b),!i&&d&&a.dataFilter&&(b=a.dataFilter(b,a.dataType)),i=f,f=k.shift())if("*"===f)f=i;else if("*"!==i&&i!==f){if(g=j[i+" "+f]||j["* "+f],!g)for(e in j)if(h=e.split(" "),h[1]===f&&(g=j[i+" "+h[0]]||j["* "+h[0]])){g===!0?g=j[e]:j[e]!==!0&&(f=h[0],k.unshift(h[1]));break}if(g!==!0)if(g&&a["throws"])b=g(b);else try{b=g(b)}catch(l){return{state:"parsererror",error:g?l:"No conversion from "+i+" to "+f}}}return{state:"success",data:b}}m.extend({active:0,lastModified:{},etag:{},ajaxSettings:{url:zc,type:"GET",isLocal:Dc.test(yc[1]),global:!0,processData:!0,async:!0,contentType:"application/x-www-form-urlencoded; charset=UTF-8",accepts:{"*":Jc,text:"text/plain",html:"text/html",xml:"application/xml, text/xml",json:"application/json, text/javascript"},contents:{xml:/xml/,html:/html/,json:/json/},responseFields:{xml:"responseXML",text:"responseText",json:"responseJSON"},converters:{"* text":String,"text html":!0,"text json":m.parseJSON,"text xml":m.parseXML},flatOptions:{url:!0,context:!0}},ajaxSetup:function(a,b){return b?Nc(Nc(a,m.ajaxSettings),b):Nc(m.ajaxSettings,a)},ajaxPrefilter:Lc(Hc),ajaxTransport:Lc(Ic),ajax:function(a,b){"object"==typeof a&&(b=a,a=void 0),b=b||{};var c,d,e,f,g,h,i,j,k=m.ajaxSetup({},b),l=k.context||k,n=k.context&&(l.nodeType||l.jquery)?m(l):m.event,o=m.Deferred(),p=m.Callbacks("once memory"),q=k.statusCode||{},r={},s={},t=0,u="canceled",v={readyState:0,getResponseHeader:function(a){var b;if(2===t){if(!j){j={};while(b=Cc.exec(f))j[b[1].toLowerCase()]=b[2]}b=j[a.toLowerCase()]}return null==b?null:b},getAllResponseHeaders:function(){return 2===t?f:null},setRequestHeader:function(a,b){var c=a.toLowerCase();return t||(a=s[c]=s[c]||a,r[a]=b),this},overrideMimeType:function(a){return t||(k.mimeType=a),this},statusCode:function(a){var b;if(a)if(2>t)for(b in a)q[b]=[q[b],a[b]];else v.always(a[v.status]);return this},abort:function(a){var b=a||u;return i&&i.abort(b),x(0,b),this}};if(o.promise(v).complete=p.add,v.success=v.done,v.error=v.fail,k.url=((a||k.url||zc)+"").replace(Ac,"").replace(Fc,yc[1]+"//"),k.type=b.method||b.type||k.method||k.type,k.dataTypes=m.trim(k.dataType||"*").toLowerCase().match(E)||[""],null==k.crossDomain&&(c=Gc.exec(k.url.toLowerCase()),k.crossDomain=!(!c||c[1]===yc[1]&&c[2]===yc[2]&&(c[3]||("http:"===c[1]?"80":"443"))===(yc[3]||("http:"===yc[1]?"80":"443")))),k.data&&k.processData&&"string"!=typeof k.data&&(k.data=m.param(k.data,k.traditional)),Mc(Hc,k,b,v),2===t)return v;h=k.global,h&&0===m.active++&&m.event.trigger("ajaxStart"),k.type=k.type.toUpperCase(),k.hasContent=!Ec.test(k.type),e=k.url,k.hasContent||(k.data&&(e=k.url+=(wc.test(e)?"&":"?")+k.data,delete k.data),k.cache===!1&&(k.url=Bc.test(e)?e.replace(Bc,"$1_="+vc++):e+(wc.test(e)?"&":"?")+"_="+vc++)),k.ifModified&&(m.lastModified[e]&&v.setRequestHeader("If-Modified-Since",m.lastModified[e]),m.etag[e]&&v.setRequestHeader("If-None-Match",m.etag[e])),(k.data&&k.hasContent&&k.contentType!==!1||b.contentType)&&v.setRequestHeader("Content-Type",k.contentType),v.setRequestHeader("Accept",k.dataTypes[0]&&k.accepts[k.dataTypes[0]]?k.accepts[k.dataTypes[0]]+("*"!==k.dataTypes[0]?", "+Jc+"; q=0.01":""):k.accepts["*"]);for(d in k.headers)v.setRequestHeader(d,k.headers[d]);if(k.beforeSend&&(k.beforeSend.call(l,v,k)===!1||2===t))return v.abort();u="abort";for(d in{success:1,error:1,complete:1})v[d](k[d]);if(i=Mc(Ic,k,b,v)){v.readyState=1,h&&n.trigger("ajaxSend",[v,k]),k.async&&k.timeout>0&&(g=setTimeout(function(){v.abort("timeout")},k.timeout));try{t=1,i.send(r,x)}catch(w){if(!(2>t))throw w;x(-1,w)}}else x(-1,"No Transport");function x(a,b,c,d){var j,r,s,u,w,x=b;2!==t&&(t=2,g&&clearTimeout(g),i=void 0,f=d||"",v.readyState=a>0?4:0,j=a>=200&&300>a||304===a,c&&(u=Oc(k,v,c)),u=Pc(k,u,v,j),j?(k.ifModified&&(w=v.getResponseHeader("Last-Modified"),w&&(m.lastModified[e]=w),w=v.getResponseHeader("etag"),w&&(m.etag[e]=w)),204===a||"HEAD"===k.type?x="nocontent":304===a?x="notmodified":(x=u.state,r=u.data,s=u.error,j=!s)):(s=x,(a||!x)&&(x="error",0>a&&(a=0))),v.status=a,v.statusText=(b||x)+"",j?o.resolveWith(l,[r,x,v]):o.rejectWith(l,[v,x,s]),v.statusCode(q),q=void 0,h&&n.trigger(j?"ajaxSuccess":"ajaxError",[v,k,j?r:s]),p.fireWith(l,[v,x]),h&&(n.trigger("ajaxComplete",[v,k]),--m.active||m.event.trigger("ajaxStop")))}return v},getJSON:function(a,b,c){return m.get(a,b,c,"json")},getScript:function(a,b){return m.get(a,void 0,b,"script")}}),m.each(["get","post"],function(a,b){m[b]=function(a,c,d,e){return m.isFunction(c)&&(e=e||d,d=c,c=void 0),m.ajax({url:a,type:b,dataType:e,data:c,success:d})}}),m.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(a,b){m.fn[b]=function(a){return this.on(b,a)}}),m._evalUrl=function(a){return m.ajax({url:a,type:"GET",dataType:"script",async:!1,global:!1,"throws":!0})},m.fn.extend({wrapAll:function(a){if(m.isFunction(a))return this.each(function(b){m(this).wrapAll(a.call(this,b))});if(this[0]){var b=m(a,this[0].ownerDocument).eq(0).clone(!0);this[0].parentNode&&b.insertBefore(this[0]),b.map(function(){var a=this;while(a.firstChild&&1===a.firstChild.nodeType)a=a.firstChild;return a}).append(this)}return this},wrapInner:function(a){return this.each(m.isFunction(a)?function(b){m(this).wrapInner(a.call(this,b))}:function(){var b=m(this),c=b.contents();c.length?c.wrapAll(a):b.append(a)})},wrap:function(a){var b=m.isFunction(a);return this.each(function(c){m(this).wrapAll(b?a.call(this,c):a)})},unwrap:function(){return this.parent().each(function(){m.nodeName(this,"body")||m(this).replaceWith(this.childNodes)}).end()}}),m.expr.filters.hidden=function(a){return a.offsetWidth<=0&&a.offsetHeight<=0||!k.reliableHiddenOffsets()&&"none"===(a.style&&a.style.display||m.css(a,"display"))},m.expr.filters.visible=function(a){return!m.expr.filters.hidden(a)};var Qc=/%20/g,Rc=/\[\]$/,Sc=/\r?\n/g,Tc=/^(?:submit|button|image|reset|file)$/i,Uc=/^(?:input|select|textarea|keygen)/i;function Vc(a,b,c,d){var e;if(m.isArray(b))m.each(b,function(b,e){c||Rc.test(a)?d(a,e):Vc(a+"["+("object"==typeof e?b:"")+"]",e,c,d)});else if(c||"object"!==m.type(b))d(a,b);else for(e in b)Vc(a+"["+e+"]",b[e],c,d)}m.param=function(a,b){var c,d=[],e=function(a,b){b=m.isFunction(b)?b():null==b?"":b,d[d.length]=encodeURIComponent(a)+"="+encodeURIComponent(b)};if(void 0===b&&(b=m.ajaxSettings&&m.ajaxSettings.traditional),m.isArray(a)||a.jquery&&!m.isPlainObject(a))m.each(a,function(){e(this.name,this.value)});else for(c in a)Vc(c,a[c],b,e);return d.join("&").replace(Qc,"+")},m.fn.extend({serialize:function(){return m.param(this.serializeArray())},serializeArray:function(){return this.map(function(){var a=m.prop(this,"elements");return a?m.makeArray(a):this}).filter(function(){var a=this.type;return this.name&&!m(this).is(":disabled")&&Uc.test(this.nodeName)&&!Tc.test(a)&&(this.checked||!W.test(a))}).map(function(a,b){var c=m(this).val();return null==c?null:m.isArray(c)?m.map(c,function(a){return{name:b.name,value:a.replace(Sc,"\r\n")}}):{name:b.name,value:c.replace(Sc,"\r\n")}}).get()}}),m.ajaxSettings.xhr=void 0!==a.ActiveXObject?function(){return!this.isLocal&&/^(get|post|head|put|delete|options)$/i.test(this.type)&&Zc()||$c()}:Zc;var Wc=0,Xc={},Yc=m.ajaxSettings.xhr();a.ActiveXObject&&m(a).on("unload",function(){for(var a in Xc)Xc[a](void 0,!0)}),k.cors=!!Yc&&"withCredentials"in Yc,Yc=k.ajax=!!Yc,Yc&&m.ajaxTransport(function(a){if(!a.crossDomain||k.cors){var b;return{send:function(c,d){var e,f=a.xhr(),g=++Wc;if(f.open(a.type,a.url,a.async,a.username,a.password),a.xhrFields)for(e in a.xhrFields)f[e]=a.xhrFields[e];a.mimeType&&f.overrideMimeType&&f.overrideMimeType(a.mimeType),a.crossDomain||c["X-Requested-With"]||(c["X-Requested-With"]="XMLHttpRequest");for(e in c)void 0!==c[e]&&f.setRequestHeader(e,c[e]+"");f.send(a.hasContent&&a.data||null),b=function(c,e){var h,i,j;if(b&&(e||4===f.readyState))if(delete Xc[g],b=void 0,f.onreadystatechange=m.noop,e)4!==f.readyState&&f.abort();else{j={},h=f.status,"string"==typeof f.responseText&&(j.text=f.responseText);try{i=f.statusText}catch(k){i=""}h||!a.isLocal||a.crossDomain?1223===h&&(h=204):h=j.text?200:404}j&&d(h,i,j,f.getAllResponseHeaders())},a.async?4===f.readyState?setTimeout(b):f.onreadystatechange=Xc[g]=b:b()},abort:function(){b&&b(void 0,!0)}}}});function Zc(){try{return new a.XMLHttpRequest}catch(b){}}function $c(){try{return new a.ActiveXObject("Microsoft.XMLHTTP")}catch(b){}}m.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/(?:java|ecma)script/},converters:{"text script":function(a){return m.globalEval(a),a}}}),m.ajaxPrefilter("script",function(a){void 0===a.cache&&(a.cache=!1),a.crossDomain&&(a.type="GET",a.global=!1)}),m.ajaxTransport("script",function(a){if(a.crossDomain){var b,c=y.head||m("head")[0]||y.documentElement;return{send:function(d,e){b=y.createElement("script"),b.async=!0,a.scriptCharset&&(b.charset=a.scriptCharset),b.src=a.url,b.onload=b.onreadystatechange=function(a,c){(c||!b.readyState||/loaded|complete/.test(b.readyState))&&(b.onload=b.onreadystatechange=null,b.parentNode&&b.parentNode.removeChild(b),b=null,c||e(200,"success"))},c.insertBefore(b,c.firstChild)},abort:function(){b&&b.onload(void 0,!0)}}}});var _c=[],ad=/(=)\?(?=&|$)|\?\?/;m.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var a=_c.pop()||m.expando+"_"+vc++;return this[a]=!0,a}}),m.ajaxPrefilter("json jsonp",function(b,c,d){var e,f,g,h=b.jsonp!==!1&&(ad.test(b.url)?"url":"string"==typeof b.data&&!(b.contentType||"").indexOf("application/x-www-form-urlencoded")&&ad.test(b.data)&&"data");return h||"jsonp"===b.dataTypes[0]?(e=b.jsonpCallback=m.isFunction(b.jsonpCallback)?b.jsonpCallback():b.jsonpCallback,h?b[h]=b[h].replace(ad,"$1"+e):b.jsonp!==!1&&(b.url+=(wc.test(b.url)?"&":"?")+b.jsonp+"="+e),b.converters["script json"]=function(){return g||m.error(e+" was not called"),g[0]},b.dataTypes[0]="json",f=a[e],a[e]=function(){g=arguments},d.always(function(){a[e]=f,b[e]&&(b.jsonpCallback=c.jsonpCallback,_c.push(e)),g&&m.isFunction(f)&&f(g[0]),g=f=void 0}),"script"):void 0}),m.parseHTML=function(a,b,c){if(!a||"string"!=typeof a)return null;"boolean"==typeof b&&(c=b,b=!1),b=b||y;var d=u.exec(a),e=!c&&[];return d?[b.createElement(d[1])]:(d=m.buildFragment([a],b,e),e&&e.length&&m(e).remove(),m.merge([],d.childNodes))};var bd=m.fn.load;m.fn.load=function(a,b,c){if("string"!=typeof a&&bd)return bd.apply(this,arguments);var d,e,f,g=this,h=a.indexOf(" ");return h>=0&&(d=m.trim(a.slice(h,a.length)),a=a.slice(0,h)),m.isFunction(b)?(c=b,b=void 0):b&&"object"==typeof b&&(f="POST"),g.length>0&&m.ajax({url:a,type:f,dataType:"html",data:b}).done(function(a){e=arguments,g.html(d?m("<div>").append(m.parseHTML(a)).find(d):a)}).complete(c&&function(a,b){g.each(c,e||[a.responseText,b,a])}),this},m.expr.filters.animated=function(a){return m.grep(m.timers,function(b){return a===b.elem}).length};var cd=a.document.documentElement;function dd(a){return m.isWindow(a)?a:9===a.nodeType?a.defaultView||a.parentWindow:!1}m.offset={setOffset:function(a,b,c){var d,e,f,g,h,i,j,k=m.css(a,"position"),l=m(a),n={};"static"===k&&(a.style.position="relative"),h=l.offset(),f=m.css(a,"top"),i=m.css(a,"left"),j=("absolute"===k||"fixed"===k)&&m.inArray("auto",[f,i])>-1,j?(d=l.position(),g=d.top,e=d.left):(g=parseFloat(f)||0,e=parseFloat(i)||0),m.isFunction(b)&&(b=b.call(a,c,h)),null!=b.top&&(n.top=b.top-h.top+g),null!=b.left&&(n.left=b.left-h.left+e),"using"in b?b.using.call(a,n):l.css(n)}},m.fn.extend({offset:function(a){if(arguments.length)return void 0===a?this:this.each(function(b){m.offset.setOffset(this,a,b)});var b,c,d={top:0,left:0},e=this[0],f=e&&e.ownerDocument;if(f)return b=f.documentElement,m.contains(b,e)?(typeof e.getBoundingClientRect!==K&&(d=e.getBoundingClientRect()),c=dd(f),{top:d.top+(c.pageYOffset||b.scrollTop)-(b.clientTop||0),left:d.left+(c.pageXOffset||b.scrollLeft)-(b.clientLeft||0)}):d},position:function(){if(this[0]){var a,b,c={top:0,left:0},d=this[0];return"fixed"===m.css(d,"position")?b=d.getBoundingClientRect():(a=this.offsetParent(),b=this.offset(),m.nodeName(a[0],"html")||(c=a.offset()),c.top+=m.css(a[0],"borderTopWidth",!0),c.left+=m.css(a[0],"borderLeftWidth",!0)),{top:b.top-c.top-m.css(d,"marginTop",!0),left:b.left-c.left-m.css(d,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var a=this.offsetParent||cd;while(a&&!m.nodeName(a,"html")&&"static"===m.css(a,"position"))a=a.offsetParent;return a||cd})}}),m.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(a,b){var c=/Y/.test(b);m.fn[a]=function(d){return V(this,function(a,d,e){var f=dd(a);return void 0===e?f?b in f?f[b]:f.document.documentElement[d]:a[d]:void(f?f.scrollTo(c?m(f).scrollLeft():e,c?e:m(f).scrollTop()):a[d]=e)},a,d,arguments.length,null)}}),m.each(["top","left"],function(a,b){m.cssHooks[b]=Lb(k.pixelPosition,function(a,c){return c?(c=Jb(a,b),Hb.test(c)?m(a).position()[b]+"px":c):void 0})}),m.each({Height:"height",Width:"width"},function(a,b){m.each({padding:"inner"+a,content:b,"":"outer"+a},function(c,d){m.fn[d]=function(d,e){var f=arguments.length&&(c||"boolean"!=typeof d),g=c||(d===!0||e===!0?"margin":"border");return V(this,function(b,c,d){var e;return m.isWindow(b)?b.document.documentElement["client"+a]:9===b.nodeType?(e=b.documentElement,Math.max(b.body["scroll"+a],e["scroll"+a],b.body["offset"+a],e["offset"+a],e["client"+a])):void 0===d?m.css(b,c,g):m.style(b,c,d,g)},b,f?d:void 0,f,null)}})}),m.fn.size=function(){return this.length},m.fn.andSelf=m.fn.addBack,"function"==typeof define&&define.amd&&define("jquery",[],function(){return m});var ed=a.jQuery,fd=a.$;return m.noConflict=function(b){return a.$===m&&(a.$=fd),b&&a.jQuery===m&&(a.jQuery=ed),m},typeof b===K&&(a.jQuery=a.$=m),m});
diff -urN cahier-de-prepa3.2.0/js/jquery.plugin.js cahier-de-prepa4.0.0/js/jquery.plugin.js
--- cahier-de-prepa3.2.0/js/jquery.plugin.js	1970-01-01 01:00:00.000000000 +0100
+++ cahier-de-prepa4.0.0/js/jquery.plugin.js	2014-08-02 00:24:29.672120654 +0200
@@ -0,0 +1,4 @@
+﻿/** Abstract base class for collection plugins v1.0.1.
+	Written by Keith Wood (kbwood{at}iinet.com.au) December 2013.
+	Licensed under the MIT (https://github.com/jquery/jquery/blob/master/MIT-LICENSE.txt) license. */
+(function(){var j=false;window.JQClass=function(){};JQClass.classes={};JQClass.extend=function extender(f){var g=this.prototype;j=true;var h=new this();j=false;for(var i in f){h[i]=typeof f[i]=='function'&&typeof g[i]=='function'?(function(d,e){return function(){var b=this._super;this._super=function(a){return g[d].apply(this,a||[])};var c=e.apply(this,arguments);this._super=b;return c}})(i,f[i]):f[i]}function JQClass(){if(!j&&this._init){this._init.apply(this,arguments)}}JQClass.prototype=h;JQClass.prototype.constructor=JQClass;JQClass.extend=extender;return JQClass}})();(function($){JQClass.classes.JQPlugin=JQClass.extend({name:'plugin',defaultOptions:{},regionalOptions:{},_getters:[],_getMarker:function(){return'is-'+this.name},_init:function(){$.extend(this.defaultOptions,(this.regionalOptions&&this.regionalOptions[''])||{});var c=camelCase(this.name);$[c]=this;$.fn[c]=function(a){var b=Array.prototype.slice.call(arguments,1);if($[c]._isNotChained(a,b)){return $[c][a].apply($[c],[this[0]].concat(b))}return this.each(function(){if(typeof a==='string'){if(a[0]==='_'||!$[c][a]){throw'Unknown method: '+a;}$[c][a].apply($[c],[this].concat(b))}else{$[c]._attach(this,a)}})}},setDefaults:function(a){$.extend(this.defaultOptions,a||{})},_isNotChained:function(a,b){if(a==='option'&&(b.length===0||(b.length===1&&typeof b[0]==='string'))){return true}return $.inArray(a,this._getters)>-1},_attach:function(a,b){a=$(a);if(a.hasClass(this._getMarker())){return}a.addClass(this._getMarker());b=$.extend({},this.defaultOptions,this._getMetadata(a),b||{});var c=$.extend({name:this.name,elem:a,options:b},this._instSettings(a,b));a.data(this.name,c);this._postAttach(a,c);this.option(a,b)},_instSettings:function(a,b){return{}},_postAttach:function(a,b){},_getMetadata:function(d){try{var f=d.data(this.name.toLowerCase())||'';f=f.replace(/'/g,'"');f=f.replace(/([a-zA-Z0-9]+):/g,function(a,b,i){var c=f.substring(0,i).match(/"/g);return(!c||c.length%2===0?'"'+b+'":':b+':')});f=$.parseJSON('{'+f+'}');for(var g in f){var h=f[g];if(typeof h==='string'&&h.match(/^new Date\((.*)\)$/)){f[g]=eval(h)}}return f}catch(e){return{}}},_getInst:function(a){return $(a).data(this.name)||{}},option:function(a,b,c){a=$(a);var d=a.data(this.name);if(!b||(typeof b==='string'&&c==null)){var e=(d||{}).options;return(e&&b?e[b]:e)}if(!a.hasClass(this._getMarker())){return}var e=b||{};if(typeof b==='string'){e={};e[b]=c}this._optionsChanged(a,d,e);$.extend(d.options,e)},_optionsChanged:function(a,b,c){},destroy:function(a){a=$(a);if(!a.hasClass(this._getMarker())){return}this._preDestroy(a,this._getInst(a));a.removeData(this.name).removeClass(this._getMarker())},_preDestroy:function(a,b){}});function camelCase(c){return c.replace(/-([a-z])/g,function(a,b){return b.toUpperCase()})}$.JQPlugin={createPlugin:function(a,b){if(typeof a==='object'){b=a;a='JQPlugin'}a=camelCase(a);var c=camelCase(b.name);JQClass.classes[c]=JQClass.classes[a].extend(b);new JQClass.classes[c]()}}})(jQuery);
\ Pas de fin de ligne à la fin du fichier
diff -urN cahier-de-prepa3.2.0/js/jquery.timeentry.css cahier-de-prepa4.0.0/js/jquery.timeentry.css
--- cahier-de-prepa3.2.0/js/jquery.timeentry.css	2012-08-15 11:52:55.000000000 +0200
+++ cahier-de-prepa4.0.0/js/jquery.timeentry.css	2014-08-02 00:18:04.652108333 +0200
@@ -1,8 +1,5 @@
-/* TimeEntry styles */
-.timeEntry_control {
+/* TimeEntry styles v2.0.0 */
+.timeEntry-control {
 	vertical-align: middle;
 	margin-left: 2px;
 }
-* html .timeEntry_control { /* IE only */
-	margin-top: -4px;
-}
diff -urN cahier-de-prepa3.2.0/js/jquery.timeentry-fr.js cahier-de-prepa4.0.0/js/jquery.timeentry-fr.js
--- cahier-de-prepa3.2.0/js/jquery.timeentry-fr.js	2012-08-15 11:52:55.000000000 +0200
+++ cahier-de-prepa4.0.0/js/jquery.timeentry-fr.js	2014-08-02 00:18:20.644108845 +0200
@@ -2,8 +2,8 @@
    French initialisation for the jQuery time entry extension
    Written by Keith Wood (kbwood@iprimus.com.au) June 2007. */
 (function($) {
-	$.timeEntry.regional['fr'] = {show24Hours: true, separator: ':',
+	$.timeEntry.regionalOptions['fr'] = {show24Hours: true, separator: ':',
 		ampmPrefix: '', ampmNames: ['AM', 'PM'],
-		spinnerTexts: ['Maintenant', 'Précédent', 'Suivant', 'Augmentez', 'Amoindrissez']};
-	$.timeEntry.setDefaults($.timeEntry.regional['fr']);
+		spinnerTexts: ['Maintenant', 'Précédent', 'Suivant', 'Augmenter', 'Diminuer']};
+	$.timeEntry.setDefaults($.timeEntry.regionalOptions['fr']);
 })(jQuery);
diff -urN cahier-de-prepa3.2.0/js/jquery.timeentry.js cahier-de-prepa4.0.0/js/jquery.timeentry.js
--- cahier-de-prepa3.2.0/js/jquery.timeentry.js	2012-08-15 11:52:54.000000000 +0200
+++ cahier-de-prepa4.0.0/js/jquery.timeentry.js	2014-08-02 00:18:11.312108546 +0200
@@ -1,7 +1,6 @@
 /* http://keith-wood.name/timeEntry.html
-   Time entry for jQuery v1.4.6.
+   Time entry for jQuery v2.0.1.
    Written by Keith Wood (kbwood{at}iinet.com.au) June 2007.
-   Dual licensed under the GPL (http://dev.jquery.com/browser/trunk/jquery/GPL-LICENSE.txt) and 
-   MIT (http://dev.jquery.com/browser/trunk/jquery/MIT-LICENSE.txt) licenses. 
+   Available under the MIT (https://github.com/jquery/jquery/blob/master/MIT-LICENSE.txt) license.
    Please attribute the author if you use it. */
-(function($){function TimeEntry(){this._disabledInputs=[];this.regional=[];this.regional['']={show24Hours:false,separator:':',ampmPrefix:'',ampmNames:['AM','PM'],spinnerTexts:['Now','Previous field','Next field','Increment','Decrement']};this._defaults={appendText:'',showSeconds:false,timeSteps:[1,1,1],initialField:0,useMouseWheel:true,defaultTime:null,minTime:null,maxTime:null,spinnerImage:'js/spinnerDefault.png',spinnerSize:[20,20,8],spinnerBigImage:'',spinnerBigSize:[40,40,16],spinnerIncDecOnly:false,spinnerRepeat:[500,250],beforeShow:null,beforeSetTime:null};$.extend(this._defaults,this.regional[''])}var m='timeEntry';$.extend(TimeEntry.prototype,{markerClassName:'hasTimeEntry',setDefaults:function(a){extendRemove(this._defaults,a||{});return this},_connectTimeEntry:function(b,c){var d=$(b);if(d.hasClass(this.markerClassName)){return}var e={};e.options=$.extend({},c);e._selectedHour=0;e._selectedMinute=0;e._selectedSecond=0;e._field=0;e.input=$(b);$.data(b,m,e);var f=this._get(e,'spinnerImage');var g=this._get(e,'spinnerText');var h=this._get(e,'spinnerSize');var i=this._get(e,'appendText');var j=(!f?null:$('<span class="timeEntry_control" style="display: inline-block; '+'background: url(\''+f+'\') 0 0 no-repeat; '+'width: '+h[0]+'px; height: '+h[1]+'px;'+($.browser.mozilla&&$.browser.version<'1.9'?' padding-left: '+h[0]+'px; padding-bottom: '+(h[1]-18)+'px;':'')+'"></span>'));d.wrap('<span class="timeEntry_wrap"></span>').after(i?'<span class="timeEntry_append">'+i+'</span>':'').after(j||'');d.addClass(this.markerClassName).bind('focus.timeEntry',this._doFocus).bind('blur.timeEntry',this._doBlur).bind('click.timeEntry',this._doClick).bind('keydown.timeEntry',this._doKeyDown).bind('keypress.timeEntry',this._doKeyPress);if($.browser.mozilla){d.bind('input.timeEntry',function(a){$.timeentry._parseTime(e)})}if($.browser.msie){d.bind('paste.timeEntry',function(a){setTimeout(function(){$.timeentry._parseTime(e)},1)})}if(this._get(e,'useMouseWheel')&&$.fn.mousewheel){d.mousewheel(this._doMouseWheel)}if(j){j.mousedown(this._handleSpinner).mouseup(this._endSpinner).mouseover(this._expandSpinner).mouseout(this._endSpinner).mousemove(this._describeSpinner)}},_enableTimeEntry:function(a){this._enableDisable(a,false)},_disableTimeEntry:function(a){this._enableDisable(a,true)},_enableDisable:function(b,c){var d=$.data(b,m);if(!d){return}b.disabled=c;if(b.nextSibling&&b.nextSibling.nodeName.toLowerCase()=='span'){$.timeEntry._changeSpinner(d,b.nextSibling,(c?5:-1))}$.timeEntry._disabledInputs=$.map($.timeEntry._disabledInputs,function(a){return(a==b?null:a)});if(c){$.timeEntry._disabledInputs.push(b)}},_isDisabledTimeEntry:function(a){return $.inArray(a,this._disabledInputs)>-1},_changeTimeEntry:function(a,b){var c=$.data(a,m);if(c){var d=this._extractTime(c);extendRemove(c.options,b||{});if(d){this._setTime(c,new Date(0,0,0,d[0],d[1],d[2]))}}$.data(a,m,c)},_destroyTimeEntry:function(b){$input=$(b);if(!$input.hasClass(this.markerClassName)){return}$input.removeClass(this.markerClassName).unbind('.timeEntry');if($.fn.mousewheel){$input.unmousewheel()}this._disabledInputs=$.map(this._disabledInputs,function(a){return(a==b?null:a)});$input.parent().replaceWith($input);$.removeData(b,m)},_setTimeTimeEntry:function(a,b){var c=$.data(a,m);if(c){this._setTime(c,b?(typeof b=='object'?new Date(b.getTime()):b):null)}},_getTimeTimeEntry:function(a){var b=$.data(a,m);var c=(b?this._extractTime(b):null);return(!c?null:new Date(0,0,0,c[0],c[1],c[2]))},_doFocus:function(a){var b=(a.nodeName&&a.nodeName.toLowerCase()=='input'?a:this);if($.timeEntry._lastInput==b||$.timeEntry._isDisabledTimeEntry(b)){$.timeEntry._focussed=false;return}var c=$.data(b,m);$.timeEntry._focussed=true;$.timeEntry._lastInput=b;$.timeEntry._blurredInput=null;var d=$.timeEntry._get(c,'beforeShow');extendRemove(c.options,(d?d.apply(b,[b]):{}));$.data(b,m,c);$.timeEntry._parseTime(c);setTimeout(function(){$.timeEntry._showField(c)},10)},_doBlur:function(a){$.timeEntry._blurredInput=$.timeEntry._lastInput;$.timeEntry._lastInput=null},_doClick:function(b){var c=b.target;var d=$.data(c,m);if(!$.timeEntry._focussed){var e=$.timeEntry._get(d,'separator').length+2;d._field=0;if(c.selectionStart!=null){for(var f=0;f<=Math.max(1,d._secondField,d._ampmField);f++){var g=(f!=d._ampmField?(f*e)+2:(d._ampmField*e)+$.timeEntry._get(d,'ampmPrefix').length+$.timeEntry._get(d,'ampmNames')[0].length);d._field=f;if(c.selectionStart<g){break}}}else if(c.createTextRange){var h=$(b.srcElement);var i=c.createTextRange();var j=function(a){return{thin:2,medium:4,thick:6}[a]||a};var k=b.clientX+document.documentElement.scrollLeft-(h.offset().left+parseInt(j(h.css('border-left-width')),10))-i.offsetLeft;for(var f=0;f<=Math.max(1,d._secondField,d._ampmField);f++){var g=(f!=d._ampmField?(f*e)+2:(d._ampmField*e)+$.timeEntry._get(d,'ampmPrefix').length+$.timeEntry._get(d,'ampmNames')[0].length);i.collapse();i.moveEnd('character',g);d._field=f;if(k<i.boundingWidth){break}}}}$.data(c,m,d);$.timeEntry._showField(d);$.timeEntry._focussed=false},_doKeyDown:function(a){if(a.keyCode>=48){return true}var b=$.data(a.target,m);switch(a.keyCode){case 9:return(a.shiftKey?$.timeEntry._changeField(b,-1,true):$.timeEntry._changeField(b,+1,true));case 35:if(a.ctrlKey){$.timeEntry._setValue(b,'')}else{b._field=Math.max(1,b._secondField,b._ampmField);$.timeEntry._adjustField(b,0)}break;case 36:if(a.ctrlKey){$.timeEntry._setTime(b)}else{b._field=0;$.timeEntry._adjustField(b,0)}break;case 37:$.timeEntry._changeField(b,-1,false);break;case 38:$.timeEntry._adjustField(b,+1);break;case 39:$.timeEntry._changeField(b,+1,false);break;case 40:$.timeEntry._adjustField(b,-1);break;case 46:$.timeEntry._setValue(b,'');break}return false},_doKeyPress:function(a){var b=String.fromCharCode(a.charCode==undefined?a.keyCode:a.charCode);if(b<' '){return true}var c=$.data(a.target,m);$.timeEntry._handleKeyPress(c,b);return false},_doMouseWheel:function(a,b){if($.timeEntry._isDisabledTimeEntry(a.target)){return}b=($.browser.opera?-b/Math.abs(b):($.browser.safari?b/Math.abs(b):b));var c=$.data(a.target,m);c.input.focus();if(!c.input.val()){$.timeEntry._parseTime(c)}$.timeEntry._adjustField(c,b);a.preventDefault()},_expandSpinner:function(b){var c=$.timeEntry._getSpinnerTarget(b);var d=$.data($.timeEntry._getInput(c),m);var e=$.timeEntry._get(d,'spinnerBigImage');if(e){d._expanded=true;var f=$(c).offset();var g=null;$(c).parents().each(function(){var a=$(this);if(a.css('position')=='relative'||a.css('position')=='absolute'){g=a.offset()}return!g});var h=$.timeEntry._get(d,'spinnerSize');var i=$.timeEntry._get(d,'spinnerBigSize');$('<div class="timeEntry_expand" style="position: absolute; left: '+(f.left-(i[0]-h[0])/2-(g?g.left:0))+'px; top: '+(f.top-(i[1]-h[1])/2-(g?g.top:0))+'px; width: '+i[0]+'px; height: '+i[1]+'px; background: transparent url('+e+') no-repeat 0px 0px; z-index: 10;"></div>').mousedown($.timeEntry._handleSpinner).mouseup($.timeEntry._endSpinner).mouseout($.timeEntry._endExpand).mousemove($.timeEntry._describeSpinner).insertAfter(c)}},_getInput:function(a){return $(a).siblings('.'+$.timeEntry.markerClassName)[0]},_describeSpinner:function(a){var b=$.timeEntry._getSpinnerTarget(a);var c=$.data($.timeEntry._getInput(b),m);b.title=$.timeEntry._get(c,'spinnerTexts')[$.timeEntry._getSpinnerRegion(c,a)]},_handleSpinner:function(a){var b=$.timeEntry._getSpinnerTarget(a);var c=$.timeEntry._getInput(b);if($.timeEntry._isDisabledTimeEntry(c)){return}if(c==$.timeEntry._blurredInput){$.timeEntry._lastInput=c;$.timeEntry._blurredInput=null}var d=$.data(c,m);$.timeEntry._doFocus(c);var e=$.timeEntry._getSpinnerRegion(d,a);$.timeEntry._changeSpinner(d,b,e);$.timeEntry._actionSpinner(d,e);$.timeEntry._timer=null;$.timeEntry._handlingSpinner=true;var f=$.timeEntry._get(d,'spinnerRepeat');if(e>=3&&f[0]){$.timeEntry._timer=setTimeout(function(){$.timeEntry._repeatSpinner(d,e)},f[0]);$(b).one('mouseout',$.timeEntry._releaseSpinner).one('mouseup',$.timeEntry._releaseSpinner)}},_actionSpinner:function(a,b){if(!a.input.val()){$.timeEntry._parseTime(a)}switch(b){case 0:this._setTime(a);break;case 1:this._changeField(a,-1,false);break;case 2:this._changeField(a,+1,false);break;case 3:this._adjustField(a,+1);break;case 4:this._adjustField(a,-1);break}},_repeatSpinner:function(a,b){if(!$.timeEntry._timer){return}$.timeEntry._lastInput=$.timeEntry._blurredInput;this._actionSpinner(a,b);this._timer=setTimeout(function(){$.timeEntry._repeatSpinner(a,b)},this._get(a,'spinnerRepeat')[1])},_releaseSpinner:function(a){clearTimeout($.timeEntry._timer);$.timeEntry._timer=null},_endExpand:function(a){$.timeEntry._timer=null;var b=$.timeEntry._getSpinnerTarget(a);var c=$.timeEntry._getInput(b);var d=$.data(c,m);$(b).remove();d._expanded=false},_endSpinner:function(a){$.timeEntry._timer=null;var b=$.timeEntry._getSpinnerTarget(a);var c=$.timeEntry._getInput(b);var d=$.data(c,m);if(!$.timeEntry._isDisabledTimeEntry(c)){$.timeEntry._changeSpinner(d,b,-1)}if($.timeEntry._handlingSpinner){$.timeEntry._lastInput=$.timeEntry._blurredInput}if($.timeEntry._lastInput&&$.timeEntry._handlingSpinner){$.timeEntry._showField(d)}$.timeEntry._handlingSpinner=false},_getSpinnerTarget:function(a){return a.target||a.srcElement},_getSpinnerRegion:function(a,b){var c=this._getSpinnerTarget(b);var d=($.browser.opera||$.browser.safari?$.timeEntry._findPos(c):$(c).offset());var e=($.browser.safari?$.timeEntry._findScroll(c):[document.documentElement.scrollLeft||document.body.scrollLeft,document.documentElement.scrollTop||document.body.scrollTop]);var f=this._get(a,'spinnerIncDecOnly');var g=(f?99:b.clientX+e[0]-d.left-($.browser.msie?2:0));var h=b.clientY+e[1]-d.top-($.browser.msie?2:0);var i=this._get(a,(a._expanded?'spinnerBigSize':'spinnerSize'));var j=(f?99:i[0]-1-g);var k=i[1]-1-h;if(i[2]>0&&Math.abs(g-j)<=i[2]&&Math.abs(h-k)<=i[2]){return 0}var l=Math.min(g,h,j,k);return(l==g?1:(l==j?2:(l==h?3:4)))},_changeSpinner:function(a,b,c){$(b).css('background-position','-'+((c+1)*this._get(a,(a._expanded?'spinnerBigSize':'spinnerSize'))[0])+'px 0px')},_findPos:function(a){var b=curTop=0;if(a.offsetParent){b=a.offsetLeft;curTop=a.offsetTop;while(a=a.offsetParent){var c=b;b+=a.offsetLeft;if(b<0){b=c}curTop+=a.offsetTop}}return{left:b,top:curTop}},_findScroll:function(a){var b=false;$(a).parents().each(function(){b|=$(this).css('position')=='fixed'});if(b){return[0,0]}var c=a.scrollLeft;var d=a.scrollTop;while(a=a.parentNode){c+=a.scrollLeft||0;d+=a.scrollTop||0}return[c,d]},_get:function(a,b){return(a.options[b]!=null?a.options[b]:$.timeEntry._defaults[b])},_parseTime:function(a){var b=this._extractTime(a);var c=this._get(a,'showSeconds');if(b){a._selectedHour=b[0];a._selectedMinute=b[1];a._selectedSecond=b[2]}else{var d=this._constrainTime(a);a._selectedHour=d[0];a._selectedMinute=d[1];a._selectedSecond=(c?d[2]:0)}a._secondField=(c?2:-1);a._ampmField=(this._get(a,'show24Hours')?-1:(c?3:2));a._lastChr='';a._field=Math.max(0,Math.min(Math.max(1,a._secondField,a._ampmField),this._get(a,'initialField')));if(a.input.val()!=''){this._showTime(a)}},_extractTime:function(a){var b=a.input.val();var c=this._get(a,'separator');var d=b.split(c);if(c==''&&b!=''){d[0]=b.substring(0,2);d[1]=b.substring(2,4);d[2]=b.substring(4,6)}var e=this._get(a,'ampmNames');var f=this._get(a,'show24Hours');if(d.length>=2){var g=!f&&(b.indexOf(e[0])>-1);var h=!f&&(b.indexOf(e[1])>-1);var i=parseInt(d[0],10);i=(isNaN(i)?0:i);i=((g||h)&&i==12?0:i)+(h?12:0);var j=parseInt(d[1],10);j=(isNaN(j)?0:j);var k=(d.length>=3?parseInt(d[2],10):0);k=(isNaN(k)||!this._get(a,'showSeconds')?0:k);return this._constrainTime(a,[i,j,k])}return null},_constrainTime:function(a,b){var c=(b!=null);if(!c){var d=this._determineTime(this._get(a,'defaultTime'))||new Date();b=[d.getHours(),d.getMinutes(),d.getSeconds()]}var e=false;var f=this._get(a,'timeSteps');for(var i=0;i<f.length;i++){if(e){b[i]=0}else if(f[i]>1){b[i]=Math.round(b[i]/f[i])*f[i];e=true}}return b},_showTime:function(a){var b=this._get(a,'show24Hours');var c=this._get(a,'separator');var d=(this._formatNumber(b?a._selectedHour:((a._selectedHour+11)%12)+1)+c+this._formatNumber(a._selectedMinute)+(this._get(a,'showSeconds')?c+this._formatNumber(a._selectedSecond):'')+(b?'':this._get(a,'ampmPrefix')+this._get(a,'ampmNames')[(a._selectedHour<12?0:1)]));this._setValue(a,d);this._showField(a)},_showField:function(a){var b=a.input[0];if(a.input.is(':hidden')||$.timeEntry._lastInput!=b){return}var c=this._get(a,'separator');var d=c.length+2;var e=(a._field!=a._ampmField?(a._field*d):(a._ampmField*d)-c.length+this._get(a,'ampmPrefix').length);var f=e+(a._field!=a._ampmField?2:this._get(a,'ampmNames')[0].length);if(b.setSelectionRange){b.setSelectionRange(e,f)}else if(b.createTextRange){var g=b.createTextRange();g.moveStart('character',e);g.moveEnd('character',f-a.input.val().length);g.select()}if(!b.disabled){b.focus()}},_formatNumber:function(a){return(a<10?'0':'')+a},_setValue:function(a,b){if(b!=a.input.val()){a.input.val(b).trigger('change')}},_changeField:function(a,b,c){var d=(a.input.val()==''||a._field==(b==-1?0:Math.max(1,a._secondField,a._ampmField)));if(!d){a._field+=b}this._showField(a);a._lastChr='';$.data(a.input[0],m,a);return(d&&c)},_adjustField:function(a,b){if(a.input.val()==''){b=0}var c=this._get(a,'timeSteps');this._setTime(a,new Date(0,0,0,a._selectedHour+(a._field==0?b*c[0]:0)+(a._field==a._ampmField?b*12:0),a._selectedMinute+(a._field==1?b*c[1]:0),a._selectedSecond+(a._field==a._secondField?b*c[2]:0)))},_setTime:function(a,b){b=this._determineTime(b);var c=this._constrainTime(a,b?[b.getHours(),b.getMinutes(),b.getSeconds()]:null);b=new Date(0,0,0,c[0],c[1],c[2]);var b=this._normaliseTime(b);var d=this._normaliseTime(this._determineTime(this._get(a,'minTime')));var e=this._normaliseTime(this._determineTime(this._get(a,'maxTime')));b=(d&&b<d?d:(e&&b>e?e:b));var f=this._get(a,'beforeSetTime');if(f){b=f.apply(a.input[0],[this._getTimeTimeEntry(a.input[0]),b,d,e])}a._selectedHour=b.getHours();a._selectedMinute=b.getMinutes();a._selectedSecond=b.getSeconds();this._showTime(a);$.data(a.input[0],m,a)},_determineTime:function(h){var i=function(a){var b=new Date();b.setTime(b.getTime()+a*1000);return b};var j=function(a){var b=new Date();var c=b.getHours();var d=b.getMinutes();var e=b.getSeconds();var f=/([+-]?[0-9]+)\s*(s|S|m|M|h|H)?/g;var g=f.exec(a);while(g){switch(g[2]||'s'){case's':case'S':e+=parseInt(g[1],10);break;case'm':case'M':d+=parseInt(g[1],10);break;case'h':case'H':c+=parseInt(g[1],10);break}g=f.exec(a)}b=new Date(0,0,10,c,d,e,0);if(/^!/.test(a)){if(b.getDate()>10){b=new Date(0,0,10,23,59,59)}else if(b.getDate()<10){b=new Date(0,0,10,0,0,0)}}return b};return(h?(typeof h=='string'?j(h):(typeof h=='number'?i(h):h)):null)},_normaliseTime:function(a){if(!a){return null}a.setFullYear(1900);a.setMonth(0);a.setDate(0);return a},_handleKeyPress:function(a,b){if(b==this._get(a,'separator')){this._changeField(a,+1,false)}else if(b>='0'&&b<='9'){var c=parseInt(b,10);var d=parseInt(a._lastChr+b,10);var e=this._get(a,'show24Hours');var f=(a._field!=0?a._selectedHour:(e?(d<24?d:c):(d>=1&&d<=12?d:(c>0?c:a._selectedHour))%12+(a._selectedHour>=12?12:0)));var g=(a._field!=1?a._selectedMinute:(d<60?d:c));var h=(a._field!=a._secondField?a._selectedSecond:(d<60?d:c));var i=this._constrainTime(a,[f,g,h]);this._setTime(a,new Date(0,0,0,i[0],i[1],i[2]));a._lastChr=b}else if(!this._get(a,'show24Hours')){var j=this._get(a,'ampmNames');if((b==j[0].substring(0,1).toLowerCase()&&a._selectedHour>=12)||(b==j[1].substring(0,1).toLowerCase()&&a._selectedHour<12)){var k=a._field;a._field=a._ampmField;this._adjustField(a,+1);a._field=k;this._showField(a)}}}});function extendRemove(a,b){$.extend(a,b);for(var c in b){if(b[c]==null){a[c]=null}}return a}$.fn.timeEntry=function(c){var d=Array.prototype.slice.call(arguments,1);if(typeof c=='string'&&(c=='isDisabled'||c=='getTime')){return $.timeEntry['_'+c+'TimeEntry'].apply($.timeEntry,[this[0]].concat(d))}return this.each(function(){var a=this.nodeName.toLowerCase();if(a=='input'){if(typeof c=='string'){$.timeEntry['_'+c+'TimeEntry'].apply($.timeEntry,[this].concat(d))}else{var b=($.fn.metadata?$(this).metadata():{});$.timeEntry._connectTimeEntry(this,$.extend(b,c))}}})};$.timeEntry=new TimeEntry()})(jQuery);
+(function($){var n='timeEntry';$.JQPlugin.createPlugin({name:n,defaultOptions:{appendText:'',showSeconds:false,unlimitedHours:false,timeSteps:[1,1,1],initialField:null,noSeparatorEntry:false,tabToExit:false,useMouseWheel:true,defaultTime:null,minTime:null,maxTime:null,spinnerImage:'spinnerDefault.png',spinnerSize:[20,20,8],spinnerBigImage:'',spinnerBigSize:[40,40,16],spinnerIncDecOnly:false,spinnerRepeat:[500,250],beforeShow:null,beforeSetTime:null},regionalOptions:{'':{show24Hours:false,separator:':',ampmPrefix:'',ampmNames:['AM','PM'],spinnerTexts:['Now','Previous field','Next field','Increment','Decrement']}},_getters:['getOffset','getTime','isDisabled'],_appendClass:n+'-append',_controlClass:n+'-control',_expandClass:n+'-expand',_disabledInputs:[],_instSettings:function(a,b){return{_field:0,_selectedHour:0,_selectedMinute:0,_selectedSecond:0}},_postAttach:function(b,c){b.on('focus.'+c.name,this._doFocus).on('blur.'+c.name,this._doBlur).on('click.'+c.name,this._doClick).on('keydown.'+c.name,this._doKeyDown).on('keypress.'+c.name,this._doKeyPress).on('paste.'+c.name,function(a){setTimeout(function(){o._parseTime(c)},1)})},_optionsChanged:function(a,b,c){var d=this._extractTime(b);$.extend(b.options,c);b.options.show24Hours=b.options.show24Hours||b.options.unlimitedHours;b._field=0;if(d){this._setTime(b,new Date(0,0,0,d[0],d[1],d[2]))}a.next('span.'+this._appendClass).remove();a.parent().find('span.'+this._controlClass).remove();if($.fn.mousewheel){a.unmousewheel()}var e=(!b.options.spinnerImage?null:$('<span class="'+this._controlClass+'" style="display: inline-block; '+'background: url(\''+b.options.spinnerImage+'\') 0 0 no-repeat; width: '+b.options.spinnerSize[0]+'px; height: '+b.options.spinnerSize[1]+'px;"></span>'));a.after(b.options.appendText?'<span class="'+this._appendClass+'">'+b.options.appendText+'</span>':'').after(e||'');if(b.options.useMouseWheel&&$.fn.mousewheel){a.mousewheel(this._doMouseWheel)}if(e){e.mousedown(this._handleSpinner).mouseup(this._endSpinner).mouseover(this._expandSpinner).mouseout(this._endSpinner).mousemove(this._describeSpinner)}},enable:function(a){this._enableDisable(a,false)},disable:function(a){this._enableDisable(a,true)},_enableDisable:function(b,c){var d=this._getInst(b);if(!d){return}b.disabled=c;if(b.nextSibling&&b.nextSibling.nodeName.toLowerCase()==='span'){this._changeSpinner(d,b.nextSibling,(c?5:-1))}this._disabledInputs=$.map(this._disabledInputs,function(a){return(a===b?null:a)});if(c){this._disabledInputs.push(b)}},isDisabled:function(a){return $.inArray(a,this._disabledInputs)>-1},_preDestroy:function(b,c){b=$(b).off('.'+n);if($.fn.mousewheel){b.unmousewheel()}this._disabledInputs=$.map(this._disabledInputs,function(a){return(a===b[0]?null:a)});b.siblings('.'+this._appendClass+',.'+this._controlClass).remove()},setTime:function(a,b){var c=this._getInst(a);if(c){if(b===null||b===''){$(a).val('')}else{this._setTime(c,b?($.isArray(b)?b:(typeof b==='object'?new Date(b.getTime()):b)):null)}}},getTime:function(a){var b=this._getInst(a);var c=(b?this._extractTime(b):null);return(!c?null:new Date(0,0,0,c[0],c[1],c[2]))},getOffset:function(a){var b=this._getInst(a);var c=(b?this._extractTime(b):null);return(!c?0:(c[0]*3600+c[1]*60+c[2])*1000)},_doFocus:function(a){var b=(a.nodeName&&a.nodeName.toLowerCase()==='input'?a:this);if(o._lastInput===b||o.isDisabled(b)){o._focussed=false;return}var c=o._getInst(b);o._focussed=true;o._lastInput=b;o._blurredInput=null;$.extend(c.options,($.isFunction(c.options.beforeShow)?c.options.beforeShow.apply(b,[b]):{}));o._parseTime(c,a.nodeName?null:a);setTimeout(function(){o._showField(c)},10)},_doBlur:function(a){o._blurredInput=o._lastInput;o._lastInput=null},_doClick:function(a){var b=a.target;var c=o._getInst(b);var d=c._field;if(!o._focussed){c._field=o._getSelection(c,b,a)}if(d!==c._field){c._lastChr=''}o._showField(c);o._focussed=false},_getSelection:function(b,c,d){var e=0;var f=[b.elem.val().split(b.options.separator)[0].length,2,2];if(c.selectionStart!==null){var g=0;for(var h=0;h<=Math.max(1,b._secondField,b._ampmField);h++){g+=(h!==b._ampmField?f[h]+b.options.separator.length:b.options.ampmPrefix.length+b.options.ampmNames[0].length);e=h;if(c.selectionStart<g){break}}}else if(c.createTextRange&&d!=null){var i=$(d.srcElement);var j=c.createTextRange();var k=function(a){return{thin:2,medium:4,thick:6}[a]||a};var l=d.clientX+document.documentElement.scrollLeft-(i.offset().left+parseInt(k(i.css('border-left-width')),10))-j.offsetLeft;for(var h=0;h<=Math.max(1,b._secondField,b._ampmField);h++){var g=(h!==b._ampmField?(h*fieldSize)+2:(b._ampmField*fieldSize)+b.options.ampmPrefix.length+b.options.ampmNames[0].length);j.collapse();j.moveEnd('character',g);e=h;if(l<j.boundingWidth){break}}}return e},_doKeyDown:function(a){if(a.keyCode>=48){return true}var b=o._getInst(a.target);switch(a.keyCode){case 9:return(b.options.tabToExit?true:(a.shiftKey?o._changeField(b,-1,true):o._changeField(b,+1,true)));case 35:if(a.ctrlKey){o._setValue(b,'')}else{b._field=Math.max(1,b._secondField,b._ampmField);o._adjustField(b,0)}break;case 36:if(a.ctrlKey){o._setTime(b)}else{b._field=0;o._adjustField(b,0)}break;case 37:o._changeField(b,-1,false);break;case 38:o._adjustField(b,+1);break;case 39:o._changeField(b,+1,false);break;case 40:o._adjustField(b,-1);break;case 46:o._setValue(b,'');break;case 8:b._lastChr='';default:return true}return false},_doKeyPress:function(a){var b=String.fromCharCode(a.charCode===undefined?a.keyCode:a.charCode);if(b<' '){return true}var c=o._getInst(a.target);o._handleKeyPress(c,b);return false},_handleKeyPress:function(a,b){if(b===a.options.separator){this._changeField(a,+1,false)}else if(b>='0'&&b<='9'){var c=parseInt(b,10);var d=parseInt(a._lastChr+b,10);var e=(a._field!==0?a._selectedHour:(a.options.unlimitedHours?d:(a.options.show24Hours?(d<24?d:c):(d>=1&&d<=12?d:(c>0?c:a._selectedHour))%12+(a._selectedHour>=12?12:0))));var f=(a._field!==1?a._selectedMinute:(d<60?d:c));var g=(a._field!==a._secondField?a._selectedSecond:(d<60?d:c));var h=this._constrainTime(a,[e,f,g]);this._setTime(a,(a.options.unlimitedHours?h:new Date(0,0,0,h[0],h[1],h[2])));if(a.options.noSeparatorEntry&&a._lastChr){this._changeField(a,+1,false)}else{a._lastChr=(a.options.unlimitedHours&&a._field===0?a._lastChr+b:b)}}else if(!a.options.show24Hours){b=b.toLowerCase();if((b===a.options.ampmNames[0].substring(0,1).toLowerCase()&&a._selectedHour>=12)||(b===a.options.ampmNames[1].substring(0,1).toLowerCase()&&a._selectedHour<12)){var i=a._field;a._field=a._ampmField;this._adjustField(a,+1);a._field=i;this._showField(a)}}},_doMouseWheel:function(a,b){if(o.isDisabled(a.target)){return}var c=o._getInst(a.target);c.elem.focus();if(!c.elem.val()){o._parseTime(c)}o._adjustField(c,b);a.preventDefault()},_expandSpinner:function(b){var c=o._getSpinnerTarget(b);var d=o._getInst(o._getInput(c));if(o.isDisabled(d.elem[0])){return}if(d.options.spinnerBigImage){d._expanded=true;var e=$(c).offset();var f=null;$(c).parents().each(function(){var a=$(this);if(a.css('position')==='relative'||a.css('position')==='absolute'){f=a.offset()}return!f});$('<div class="'+o._expandClass+'" style="position: absolute; left: '+(e.left-(d.options.spinnerBigSize[0]-d.options.spinnerSize[0])/2-(f?f.left:0))+'px; top: '+(e.top-(d.options.spinnerBigSize[1]-d.options.spinnerSize[1])/2-(f?f.top:0))+'px; width: '+d.options.spinnerBigSize[0]+'px; height: '+d.options.spinnerBigSize[1]+'px; background: transparent url('+d.options.spinnerBigImage+') no-repeat 0px 0px; z-index: 10;"></div>').mousedown(o._handleSpinner).mouseup(o._endSpinner).mouseout(o._endExpand).mousemove(o._describeSpinner).insertAfter(c)}},_getInput:function(a){return $(a).siblings('.'+this._getMarker())[0]},_describeSpinner:function(a){var b=o._getSpinnerTarget(a);var c=o._getInst(o._getInput(b));b.title=c.options.spinnerTexts[o._getSpinnerRegion(c,a)]},_handleSpinner:function(a){var b=o._getSpinnerTarget(a);var c=o._getInput(b);if(o.isDisabled(c)){return}if(c===o._blurredInput){o._lastInput=c;o._blurredInput=null}var d=o._getInst(c);o._doFocus(c);var e=o._getSpinnerRegion(d,a);o._changeSpinner(d,b,e);o._actionSpinner(d,e);o._timer=null;o._handlingSpinner=true;if(e>=3&&d.options.spinnerRepeat[0]){o._timer=setTimeout(function(){o._repeatSpinner(d,e)},d.options.spinnerRepeat[0]);$(b).one('mouseout',o._releaseSpinner).one('mouseup',o._releaseSpinner)}},_actionSpinner:function(a,b){if(!a.elem.val()){o._parseTime(a)}switch(b){case 0:this._setTime(a);break;case 1:this._changeField(a,-1,false);break;case 2:this._changeField(a,+1,false);break;case 3:this._adjustField(a,+1);break;case 4:this._adjustField(a,-1);break}},_repeatSpinner:function(a,b){if(!o._timer){return}o._lastInput=o._blurredInput;this._actionSpinner(a,b);this._timer=setTimeout(function(){o._repeatSpinner(a,b)},a.options.spinnerRepeat[1])},_releaseSpinner:function(a){clearTimeout(o._timer);o._timer=null},_endExpand:function(a){o._timer=null;var b=o._getSpinnerTarget(a);var c=o._getInput(b);var d=o._getInst(c);$(b).remove();d._expanded=false},_endSpinner:function(a){o._timer=null;var b=o._getSpinnerTarget(a);var c=o._getInput(b);var d=o._getInst(c);if(!o.isDisabled(c)){o._changeSpinner(d,b,-1)}if(o._handlingSpinner){o._lastInput=o._blurredInput}if(o._lastInput&&o._handlingSpinner){o._showField(d)}o._handlingSpinner=false},_getSpinnerTarget:function(a){return a.target||a.srcElement},_getSpinnerRegion:function(a,b){var c=this._getSpinnerTarget(b);var d=$(c).offset();var e=[document.documentElement.scrollLeft||document.body.scrollLeft,document.documentElement.scrollTop||document.body.scrollTop];var f=(a.options.spinnerIncDecOnly?99:b.clientX+e[0]-d.left);var g=b.clientY+e[1]-d.top;var h=a.options[a._expanded?'spinnerBigSize':'spinnerSize'];var i=(a.options.spinnerIncDecOnly?99:h[0]-1-f);var j=h[1]-1-g;if(h[2]>0&&Math.abs(f-i)<=h[2]&&Math.abs(g-j)<=h[2]){return 0}var k=Math.min(f,g,i,j);return(k===f?1:(k===i?2:(k===g?3:4)))},_changeSpinner:function(a,b,c){$(b).css('background-position','-'+((c+1)*a.options[a._expanded?'spinnerBigSize':'spinnerSize'][0])+'px 0px')},_parseTime:function(a,b){var c=this._extractTime(a);if(c){a._selectedHour=c[0];a._selectedMinute=c[1];a._selectedSecond=c[2]}else{var d=this._constrainTime(a);a._selectedHour=d[0];a._selectedMinute=d[1];a._selectedSecond=(a.options.showSeconds?d[2]:0)}a._secondField=(a.options.showSeconds?2:-1);a._ampmField=(a.options.show24Hours?-1:(a.options.showSeconds?3:2));a._lastChr='';var e=function(){if(a.elem.val()!==''){o._showTime(a)}};if(typeof a.options.initialField==='number'){a._field=Math.max(0,Math.min(Math.max(1,a._secondField,a._ampmField),a.options.initialField));e()}else{setTimeout(function(){a._field=o._getSelection(a,a.elem[0],b);e()},0)}},_extractTime:function(a,b){b=b||a.elem.val();var c=b.split(a.options.separator);if(a.options.separator===''&&b!==''){c[0]=b.substring(0,2);c[1]=b.substring(2,4);c[2]=b.substring(4,6)}if(c.length>=2){var d=!a.options.show24Hours&&(b.indexOf(a.options.ampmNames[0])>-1);var e=!a.options.show24Hours&&(b.indexOf(a.options.ampmNames[1])>-1);var f=parseInt(c[0],10);f=(isNaN(f)?0:f);f=((d||e)&&f===12?0:f)+(e?12:0);var g=parseInt(c[1],10);g=(isNaN(g)?0:g);var h=(c.length>=3?parseInt(c[2],10):0);h=(isNaN(h)||!a.options.showSeconds?0:h);return this._constrainTime(a,[f,g,h])}return null},_constrainTime:function(a,b){var c=(b!==null&&b!==undefined);if(!c){var d=this._determineTime(a.options.defaultTime,a)||new Date();b=[d.getHours(),d.getMinutes(),d.getSeconds()]}var e=false;for(var i=0;i<a.options.timeSteps.length;i++){if(e){b[i]=0}else if(a.options.timeSteps[i]>1){b[i]=Math.round(b[i]/a.options.timeSteps[i])*a.options.timeSteps[i];e=true}}return b},_showTime:function(a){var b=(a.options.unlimitedHours?a._selectedHour:this._formatNumber(a.options.show24Hours?a._selectedHour:((a._selectedHour+11)%12)+1))+a.options.separator+this._formatNumber(a._selectedMinute)+(a.options.showSeconds?a.options.separator+this._formatNumber(a._selectedSecond):'')+(a.options.show24Hours?'':a.options.ampmPrefix+a.options.ampmNames[(a._selectedHour<12?0:1)]);this._setValue(a,b);this._showField(a)},_showField:function(a){var b=a.elem[0];if(a.elem.is(':hidden')||o._lastInput!==b){return}var c=[a.elem.val().split(a.options.separator)[0].length,2,2];var d=0;var e=0;while(e<a._field){d+=c[e]+(e===Math.max(1,a._secondField)?0:a.options.separator.length);e++}var f=d+(a._field!==a._ampmField?c[e]:a.options.ampmPrefix.length+a.options.ampmNames[0].length);if(b.setSelectionRange){b.setSelectionRange(d,f)}else if(b.createTextRange){var g=b.createTextRange();g.moveStart('character',d);g.moveEnd('character',f-a.elem.val().length);g.select()}if(!b.disabled){b.focus()}},_formatNumber:function(a){return(a<10?'0':'')+a},_setValue:function(a,b){if(b!==a.elem.val()){a.elem.val(b).trigger('change')}},_changeField:function(a,b,c){var d=(a.elem.val()===''||a._field===(b===-1?0:Math.max(1,a._secondField,a._ampmField)));if(!d){a._field+=b}this._showField(a);a._lastChr='';return(d&&c)},_adjustField:function(a,b){if(a.elem.val()===''){b=0}if(a.options.unlimitedHours){this._setTime(a,[a._selectedHour+(a._field===0?b*a.options.timeSteps[0]:0),a._selectedMinute+(a._field===1?b*a.options.timeSteps[1]:0),a._selectedSecond+(a._field===a._secondField?b*a.options.timeSteps[2]:0)])}else{this._setTime(a,new Date(0,0,0,a._selectedHour+(a._field===0?b*a.options.timeSteps[0]:0)+(a._field===a._ampmField?b*12:0),a._selectedMinute+(a._field===1?b*a.options.timeSteps[1]:0),a._selectedSecond+(a._field===a._secondField?b*a.options.timeSteps[2]:0)))}},_setTime:function(a,b){if(a.options.unlimitedHours&&$.isArray(b)){var c=b}else{b=this._determineTime(b,a);var c=(b?[b.getHours(),b.getMinutes(),b.getSeconds()]:null)}c=this._constrainTime(a,c);b=new Date(0,0,0,c[0],c[1],c[2]);var b=this._normaliseTime(b);var d=this._normaliseTime(this._determineTime(a.options.minTime,a));var e=this._normaliseTime(this._determineTime(a.options.maxTime,a));if(a.options.unlimitedHours){while(c[2]<0){c[2]+=60;c[1]--}while(c[2]>59){c[2]-=60;c[1]++}while(c[1]<0){c[1]+=60;c[0]--}while(c[1]>59){c[1]-=60;c[0]++}d=(a.options.minTime!=null&&$.isArray(a.options.minTime))?a.options.minTime:[0,0,0];if(c[0]<d[0]){c=d.slice(0,3)}else if(c[0]===d[0]){if(c[1]<d[1]){c[1]=d[1];c[2]=d[2]}else if(c[1]===d[1]){if(c[2]<d[2]){c[2]=d[2]}}}if(a.options.maxTime!=null&&$.isArray(a.options.maxTime)){if(c[0]>a.options.maxTime[0]){c=a.options.maxTime.slice(0,3)}else if(c[0]===a.options.maxTime[0]){if(c[1]>a.options.maxTime[1]){c[1]=a.options.maxTime[1];c[2]=a.options.maxTime[2]}else if(c[1]===a.options.maxTime[1]){if(c[2]>a.options.maxTime[2]){c[2]=a.options.maxTime[2]}}}}}else{if(d&&e&&d>e){if(b<d&&b>e){b=(Math.abs(b-d)<Math.abs(b-e)?d:e)}}else{b=(d&&b<d?d:(e&&b>e?e:b))}c[0]=b.getHours();c[1]=b.getMinutes();c[2]=b.getSeconds()}if($.isFunction(a.options.beforeSetTime)){b=a.options.beforeSetTime.apply(a.elem[0],[this.getTime(a.elem[0]),b,d,e]);c[0]=b.getHours();c[1]=b.getMinutes();c[2]=b.getSeconds()}a._selectedHour=c[0];a._selectedMinute=c[1];a._selectedSecond=c[2];this._showTime(a)},_determineTime:function(i,j){var k=function(a){var b=new Date();b.setTime(b.getTime()+a*1000);return b};var l=function(a){var b=o._extractTime(j,a);var c=new Date();var d=(b?b[0]:c.getHours());var e=(b?b[1]:c.getMinutes());var f=(b?b[2]:c.getSeconds());if(!b){var g=/([+-]?[0-9]+)\s*(s|S|m|M|h|H)?/g;var h=g.exec(a);while(h){switch(h[2]||'s'){case's':case'S':f+=parseInt(h[1],10);break;case'm':case'M':e+=parseInt(h[1],10);break;case'h':case'H':d+=parseInt(h[1],10);break}h=g.exec(a)}}c=new Date(0,0,10,d,e,f,0);if(/^!/.test(a)){if(c.getDate()>10){c=new Date(0,0,10,23,59,59)}else if(c.getDate()<10){c=new Date(0,0,10,0,0,0)}}return c};var m=function(a){return new Date(0,0,0,a[0],a[1]||0,a[2]||0,0)};return(i?(typeof i==='string'?l(i):(typeof i==='number'?k(i):($.isArray(i)?m(i):i))):null)},_normaliseTime:function(a){if(!a){return null}a.setFullYear(1900);a.setMonth(0);a.setDate(0);return a}});var o=$.timeEntry})(jQuery);
\ Pas de fin de ligne à la fin du fichier
diff -urN cahier-de-prepa3.2.0/js/timeout.js cahier-de-prepa4.0.0/js/timeout.js
--- cahier-de-prepa3.2.0/js/timeout.js	2013-10-23 16:34:28.399129024 +0200
+++ cahier-de-prepa4.0.0/js/timeout.js	1970-01-01 01:00:00.000000000 +0100
@@ -1,51 +0,0 @@
-
-// Messages d'annonce de la future déconnexion
-var html1 = 'Vous allez être déconnecté dans moins de 5 minutes. Toute modification non validée dans 5 minutes sera perdue...<br><a href="#" id="connect">Cliquez ici</a> pour rester connecté.';
-var html2 = 'Vous avez été déconnecté automatiquement. Si vous validez cette page, vos modifications seront perdues. Il suffit avant cela de taper à nouveau votre nom et votre mot de passe&nbsp;;\
-<form id="formlogin">\
-  <p class="ligne"><label for="login">Nom d\'utilisateur&nbsp;: </label><input type="text" name="login" id="logintmp"></p>\
-  <p class="ligne"><label for="motdepasse">Mot de passe&nbsp;: </label><input type="password" name="motdepasse" id="motdepassetmp"></p>\
-  <p><input type="submit" value="Envoyer"></p>\
-</form>';
-
-// Affichage des messages
-function afficher_timeout() {
-  $('#message').hide(200);
-  // Suppression de l'éventuelle attente
-  try{ window.clearTimeout(timeout); } catch(e){}
-  timeout = window.setTimeout( function() {
-    $('#message').html(html1).show(200);
-    $("#connect").click( function() { connect(); return false; });
-    timeout = window.setTimeout( function() {
-      $('#message').html(html2);
-      $('#formlogin').submit( function() { connect(); return false; });
-    } , 300000);
-  } , 600000);
-}
-
-// Vérification de la connexion et reconnexion
-function connect() {
-  var login = '';
-  var motdepasse = '';
-  if ( $('#formlogin').length > 0 )  {
-    login = $('#logintmp').val();
-    motdepasse = $('#motdepassetmp').val();
-  }
-  $.post('connect',
-        { login : login , motdepasse : motdepasse },
-        function(data) {
-          if ( data.etat == 1 )  { afficher_timeout(); }
-          else  {
-            $('#message').html(html2);
-            $('#formlogin').submit( function() { connect(); return false; });
-          }
-        },
-        "json");
-}
-
-// Initialisation
-var timeout;
-$( function() {
-  $('<div id="message" class="warning"></div>').prependTo('body').css('display', 'none');
-  afficher_timeout();
-});
diff -urN cahier-de-prepa3.2.0/login_admin.php cahier-de-prepa4.0.0/login_admin.php
--- cahier-de-prepa3.2.0/login_admin.php	2013-08-15 16:39:01.506713173 +0200
+++ cahier-de-prepa4.0.0/login_admin.php	1970-01-01 01:00:00.000000000 +0100
@@ -1,99 +0,0 @@
-<?php
-// Sécurité : ne doit pas être exécuté s'il n'est pas lancé par un script autorisé
-if ( !defined('OK') )  exit();
-
-// Gestion de la connexion pour l'interface d'administration
-// $admin est faux par défaut. Si l'utilisateur a bien rempli le formulaire
-//  précédemment, $admin devient vrai et on retourne au script appelant (debut.php)
-//  Sinon, on affiche un formulaire spécifique (sans menu, donc pas d'appel à
-//  haut.php).
-$admin = false;
-if ( isset($_REQUEST['login']) && isset($_REQUEST['motdepasse']) )  {
-
-  // Récupération des logins/mdp dans la base MySQL et comparaison
-  // Seuls les logins/mdp correspondant à une/des matières sont acceptés
-  // (si matieres=0, utilisateurs en lecture seulement)
-  $mysqli = premiere_connexion();
-  $resultat = $mysqli->query('SELECT id, nom, mdp, matieres, protection FROM utilisateurs WHERE matieres');
-  $mysqli->close();
-  if ( $resultat->num_rows )  {
-    while ( $r = $resultat->fetch_assoc() )
-      if ( ( $r['nom'] == $_REQUEST['login'] ) && ( $r['mdp'] == sha1($_REQUEST['motdepasse']) ) )  {
-        $admin = true;
-        break;
-      }
-    $resultat->free();
-  }
-  // Si pas d'utilisateur enregistré : retour au script d'installation
-  else
-    include('installation.php');
-
-  // Si connexion, génération de $_SESSION pour reconnaissance ultérieure
-  if ( $admin )  {
-    // Interdiction de garder son identifiant de session
-    session_regenerate_id(true);
-    $_SESSION = array();
-    // Interdiction de pouvoir se connecter aux autres site sur le même serveur
-    $_SESSION[md5($site)] = true;
-    // Pour vérification aux connexions ultérieures
-    $_SESSION['client'] = $_SERVER['HTTP_USER_AGENT'];
-    $_SESSION['ip'] = $_SERVER['REMOTE_ADDR'];
-    $_SESSION['id'] = $r['id'];
-    $_SESSION['nom'] = $r['nom'];
-    $_SESSION['mdp'] = $r['mdp'];
-    $_SESSION['matieres'] = $r['matieres'];
-    $_SESSION['protection'] = $r['protection'];
-    $_SESSION['time'] = time()+900;
-    // Remarque : pas de session_write_close() pour mettre à jour par prefs.php
-    
-    // Vérification du répertoire de sauvegarde des données MySQL
-    if ( !is_dir('sauvegarde') || !is_executable('sauvegarde') || !is_writable('sauvegarde') )
-      $message = 'Attention, le répertoire de sauvegarde n\'est pas accessible&nbsp;: cela signifie que les données ne pourront pas être sauvegardées périodiquement comme prévu, et qu\'aucune protection n\'est en fonctionnement pour prévenir un accident.';
-
-    // Retour au script appelant
-    return;
-  }
-  else
-    $message = 'Ce login/mot de passe n\'est pas correct&nbsp;!';
-}
-
-// Retour au script appelé par AJAX si c'est le cas (pas d'HTML)
-if ( defined('AJAX') )
-  exit(json_encode(array('etat'=>0)));
-
-// Message par défaut
-if ( !isset($message) )
-  $message = "Vous devez vous identifier pour accéder à cette partie du site.";
-
-////////////
-/// HTML ///
-////////////
-
-$t = "La $classe du lycée $lycee - Interface d'administration";
-?>
-<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
-<html>
-<head>
-  <title><?php echo $t; ?></title>
-  <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
-  <link rel="stylesheet" href="css/style.css" type="text/css" media="screen">
-  <link rel="stylesheet" href="css/couleurs.css" type="text/css" media="screen">
-  <link rel="stylesheet" href="css/mobile.css" type="text/css" media="handheld, only screen and (max-device-width:480px)">
-</head>
-<body>
-
-<h1><?php echo $t; ?></h1>
-
-<p class="warning"><?php echo $message; ?></p>
-
-<form class="warning" action="" method="post">
-  <p class="ligne"><label for="login">Identifiant&nbsp;: </label><input type="text" name="login" id="login"></p>
-  <p class="ligne"><label for="motdepasse">Mot de passe&nbsp;: </label><input type="password" name="motdepasse" id="motdepasse"></p>
-  <p><input type="submit" name="Ok" value="Envoyer"></p>
-</form>
-
-<h3 class="warning"><a href="http://<?php echo $site; ?>">Revenir au site public</a></h3>
-
-</body>
-</html>
-<?php exit(); ?>
diff -urN cahier-de-prepa3.2.0/login_lecture.php cahier-de-prepa4.0.0/login_lecture.php
--- cahier-de-prepa3.2.0/login_lecture.php	2013-08-15 15:32:09.426584786 +0200
+++ cahier-de-prepa4.0.0/login_lecture.php	1970-01-01 01:00:00.000000000 +0100
@@ -1,72 +0,0 @@
-<?php
-// Sécurité : ne doit pas être exécuté s'il n'est pas lancé par un script autorisé
-if ( !defined('OK') )  exit();
-
-// Gestion de la connexion
-// $lecteur est faux par défaut. Si l'utilisateur a bien rempli le formulaire
-//  précédemment, $lecteur devient vrai et on retourne au script appelant.
-//  Sinon, on affiche un formulaire à la place du contenu prévu.
-if ( isset($_REQUEST['login']) && isset($_REQUEST['motdepasse']) )  {
-
-  // Récupération des logins/mdp dans la base MySQL et comparaison
-  // Tous les logins/mdp sont autorisés
-  // Attention : la connexion MySQL doit être établie par le script appelant
-  $resultat = $mysqli->query('SELECT nom, mdp FROM utilisateurs');
-  while ( $r = $resultat->fetch_assoc() )
-    if ( ( $r['nom'] == $_REQUEST['login'] ) && ( $r['mdp'] == sha1($_REQUEST['motdepasse']) ) )  {
-      $lecteur = true;
-      break;
-    }
-  $resultat->free();
-
-  // Si connexion, génération de $_SESSION pour reconnaissance ultérieure
-  if ( $lecteur )  {
-    // Interdiction de garder son identifiant de session
-    session_regenerate_id(true);
-    $_SESSION = array();
-    // Interdiction de pouvoir se connecter aux autres site sur le même serveur
-    $_SESSION[md5("$site-lecture")] = true;
-    // Pour vérification aux connexions ultérieures
-    $_SESSION['client'] = $_SERVER['HTTP_USER_AGENT'];
-    $_SESSION['ip'] = $_SERVER['REMOTE_ADDR'];
-    $_SESSION['login'] = $_POST['login'];
-    $_SESSION['time'] = time()+3600;
-    session_write_close();
-    
-    // Retour au script appelant
-    return;
-  }
-  else
-    $message2 = 'Ce login/mot de passe n\'est pas correct&nbsp;!';
-}
-
-// Message par défaut
-if ( !isset($message2) )
-  $message2 = "Ce contenu est protégé. Vous devez vous connecter pour l'afficher.";
-
-////////////
-/// HTML ///
-////////////
-
-// Haut de page, menu et message ($p et $t doivent avoir été définis dans le script appelant)
-include('haut.php');
-$mysqli->close();
-
-// Spécifique pour docs.php : répertoires parents
-if ( isset($parents) )
-  echo $parents;
-
-echo "\n<p class=\"warning\">$message2</p>\n";
-?>
-
-<form class="warning" action="" method="post">
-  <p class="ligne"><label for="login">Identifiant&nbsp;: </label><input type="text" name="login" id="login"></p>
-  <p class="ligne"><label for="motdepasse">Mot de passe&nbsp;: </label><input type="password" name="motdepasse" id="motdepasse"></p>
-  <p><input type="submit" name="Ok" value="Envoyer"></p>
-</form>
-
-<?php
-// Bas de page
-include('bas.php');
-exit();
-?>
diff -urN cahier-de-prepa3.2.0/login.php cahier-de-prepa4.0.0/login.php
--- cahier-de-prepa3.2.0/login.php	1970-01-01 01:00:00.000000000 +0100
+++ cahier-de-prepa4.0.0/login.php	2014-08-21 17:23:32.338608629 +0200
@@ -0,0 +1,92 @@
+<?php
+// Sécurité : ne doit pas être exécuté s'il n'est pas lancé par un script autorisé
+if ( !defined('OK') )  exit();
+
+// Mode https obligatoire si connecté
+if ( $https && ( !isset($_SERVER['HTTPS']) || $_SERVER['HTTPS'] == 'off' ) )  {
+  header("Location: https://{$_SERVER['HTTP_HOST']}{$_SERVER['REQUEST_URI']}");
+  $mysqli->close();
+  exit();
+}
+
+// Si une demande de connexion est faite
+if ( isset($_REQUEST['login']) && isset($_REQUEST['motdepasse']) )  {
+
+  // Récupération des logins/mdp dans la base MySQL et comparaison
+  // Attention : la connexion MySQL doit être établie par le script appelant
+  $resultat = $mysqli->query('SELECT id, login, genre, mdp, autorisation, matieres, timeout FROM utilisateurs');
+  while ( $r = $resultat->fetch_assoc() )
+    if ( ( $r['login'] == $_REQUEST['login'] ) && ( $r['mdp'] == sha1($_REQUEST['motdepasse']) ) )  {
+      // Interdiction de garder son identifiant de session
+      session_regenerate_id(true);
+      $_SESSION = array();
+      // Interdiction de pouvoir se connecter aux autres site sur le même serveur
+      $_SESSION[md5("$site")] = true;
+      // Pour vérification/utilisation aux connexions ultérieures
+      $_SESSION['client'] = $_SERVER['HTTP_USER_AGENT'];
+      $_SESSION['ip'] = $_SERVER['REMOTE_ADDR'];
+      $_SESSION['login'] = $_POST['login'];
+      $_SESSION['id'] = $r['id'];
+      $_SESSION['autorisation'] = $autorisation = $r['autorisation'];
+      $_SESSION['matieres'] = $r['matieres'];
+      $_SESSION['timeout'] = $r['timeout'];
+      $_SESSION['time'] = time()+$_SESSION['timeout'];
+      $resultat->free();
+      
+      // Si genre >= 5, cela signifie qu'on a demandé une régénération de mot de passe (connect.php)
+      if ( $r['genre'] >= 5 )  {
+        include('admin/fonctions.php');
+        $mysqli->close();
+        $mysqli = mysql_ecriture();
+        requete('utilisateurs',"UPDATE utilisateurs SET mdp='pas de mot de passe', genre=MOD(genre,5) WHERE id = ${r['id']}");
+        $mysqli->close();
+        $mysqli = mysql_lecture();
+      }
+      // Retour au script appelant
+      unset($_REQUEST['motdepasse']);
+      return;
+    }
+  $resultat->free();
+  // Utilisateur inconnu ou mot de passe faux
+  $message_login = 'Cet identifiant/mot de passe n\'est pas correct&nbsp;!';
+}
+// Message par défaut
+else
+  $message_login = "Ce contenu est protégé. Vous devez vous connecter pour l'afficher.";
+
+////////////
+/// HTML ///
+////////////
+
+// Haut de page, menu et message ($p et $t doivent avoir été définis dans le script appelant)
+include('haut.php');
+$mysqli->close();
+
+// Spécifique pour docs.php : répertoires parents
+if ( isset($parents) )
+  echo $parents;
+
+// Si utilisateur déjà connecté, message d'erreur simple
+if ( $autorisation )
+  echo "\n<p class=\"warning\">Ce contenu est protégé. Vous n'avez pas les droits d'accès suffisants pour afficher ce contenu.</p>\n";
+// Si utilisateur non déjà connecté, formulaire de connexion
+else  {
+  if ( $p != 'connect' || isset($_REQUEST['login']) )
+    echo "\n<div class=\"warning\">$message_login</div>\n";
+?>
+
+<form class="warning" action="" method="post">
+  <p class="ligne"><label for="login">Identifiant&nbsp;: </label><input type="text" name="login" id="login"></p>
+  <p class="ligne"><label for="motdepasse">Mot de passe&nbsp;: </label><input type="password" name="motdepasse" id="motdepasse"></p>
+  <p><input type="submit" name="Ok" value="Envoyer"></p>
+  <p id="oubli"><a href="connect?oubli">Identifiant ou mot de passe oublié&nbsp;?</a></p>
+  <p id="oubli"><a href="connect?creation">Créer un compte</a></p>
+</form>
+
+<?php
+}
+
+// Bas de page
+include('bas.php');
+exit();
+?>
diff -urN cahier-de-prepa3.2.0/MAJSQL.sql cahier-de-prepa4.0.0/MAJSQL.sql
--- cahier-de-prepa3.2.0/MAJSQL.sql	2013-12-26 22:35:06.148884518 +0100
+++ cahier-de-prepa4.0.0/MAJSQL.sql	2014-08-28 13:46:05.577544806 +0200
@@ -117,3 +117,64 @@
 ALTER TABLE pages ADD mat TINYINT( 2 ) UNSIGNED NOT NULL AFTER cle;
 ALTER TABLE reps ADD menu TINYINT( 1 ) UNSIGNED NOT NULL;
 
+-- 
+-- Voilà les modifications à effectuer sur chaque base pour passer de Cahier de
+-- Prépa 3.2.0 à Cahier de Prépa 4.0.0.
+-- 
+
+ALTER TABLE utilisateurs
+  DROP protection,
+  CHANGE nom login VARCHAR( 50 ) NOT NULL,
+  ADD UNIQUE (login),
+  ADD nom VARCHAR( 50 ) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL AFTER login,
+  ADD prenom VARCHAR( 50 ) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL AFTER nom,
+  ADD genre TINYINT( 1 ) UNSIGNED NOT NULL AFTER prenom,
+  ADD mail VARCHAR( 50 ) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL AFTER genre,
+  ADD autorisation TINYINT( 1 ) UNSIGNED NOT NULL AFTER mail,
+  ADD timeout SMALLINT( 4 ) UNSIGNED NOT NULL,
+  ADD mailexp TINYINT( 1 ) UNSIGNED NOT NULL,
+  ADD mailcopy TINYINT( 1 ) UNSIGNED NOT NULL;
+UPDATE utilisateurs SET autorisation = IF(matieres,3,1), matieres = IF(matieres,matieres,''), timeout = 900, mailexp = 1, mailcopy = 1;
+
+ALTER TABLE matieres
+  ADD notes TINYINT( 1 ) UNSIGNED NOT NULL,
+  ADD colles_protection TINYINT( 1 ) UNSIGNED NOT NULL,
+  ADD cdt_protection TINYINT( 1 ) UNSIGNED NOT NULL,
+  ADD INDEX(notes);
+UPDATE matieres SET colles_protection = colles DIV 2, cdt_protection = cdt DIV 2,
+  colles = MOD( colles, 2 ), cdt = MOD( cdt, 2 ), notes = 0;
+
+ALTER TABLE reps
+  ADD nbrep_v TINYINT( 2 ) UNSIGNED NOT NULL AFTER nbrep,
+  CHANGE nbdoc_nv nbdoc TINYINT( 2 ) UNSIGNED NOT NULL AFTER nbrep_v,
+  CHANGE nbdoc_v nbdoc_v TINYINT( 2 ) UNSIGNED NOT NULL AFTER nbdoc;
+UPDATE reps SET nbrep_v = nbrep, nbdoc = nbdoc + nbdoc_v;
+
+UPDATE docs SET protection = 4 WHERE protection = 2; -- aucun répertoire ne pouvait avoir protection = 2
+
+ALTER TABLE `cdt-types`
+  ADD nb TINYINT( 3 ) UNSIGNED NOT NULL,
+  ADD nb_v TINYINT( 3 ) UNSIGNED NOT NULL;
+
+-- Semaines 2014-2015 -> Suppression des cahiers de texte et programmes de colles
+TRUNCATE TABLE cdt;
+TRUNCATE TABLE colles;
+UPDATE matieres SET colles = 0, cdt = 0
+TRUNCATE TABLE semaines;
+INSERT INTO semaines (debut) VALUES ('2014-09-02'),('2014-09-08'),('2014-09-15'),('2014-09-22'),('2014-09-29'),
+  ('2014-10-06'),('2014-10-13'),('2014-10-20'),('2014-10-27'),('2014-11-03'),('2014-11-10'),('2014-11-17'),('2014-11-24'),
+  ('2014-12-01'),('2014-12-08'),('2014-12-15'),('2014-12-22'),('2014-12-29'),('2015-01-05'),('2015-01-12'),('2015-01-19'),('2015-01-26'),
+  ('2015-02-02'),('2015-02-09'),('2015-02-16'),('2015-02-23'),('2015-03-02'),('2015-03-09'),('2015-03-16'),('2015-03-23'),('2015-03-30'),
+  ('2015-04-07'),('2015-04-13'),('2015-04-20'),('2015-04-27'),('2015-05-04'),('2015-05-11'),('2015-05-18'),('2015-05-26'),
+  ('2015-06-01'),('2015-06-08'),('2015-06-15'),('2015-06-22'),('2015-06-29');
+UPDATE semaines SET colle = 1;
+
+CREATE TABLE notes (
+  id SMALLINT( 5 ) UNSIGNED NOT NULL PRIMARY KEY,
+  semaine TINYINT( 2 ) UNSIGNED NOT NULL,
+  eleve TINYINT( 2 ) UNSIGNED NOT NULL,
+  colleur TINYINT( 2 ) UNSIGNED NOT NULL,
+  matiere TINYINT( 2 ) UNSIGNED NOT NULL,
+  note VARCHAR( 2 ) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL
+) ENGINE=MyISAM  DEFAULT CHARSET=utf8;
+
diff -urN cahier-de-prepa3.2.0/matieres.php cahier-de-prepa4.0.0/matieres.php
--- cahier-de-prepa3.2.0/matieres.php	2013-12-27 11:04:18.894323011 +0100
+++ cahier-de-prepa4.0.0/matieres.php	1970-01-01 01:00:00.000000000 +0100
@@ -1,190 +0,0 @@
-<?php
-// Sécurité et récupération des données de configuration
-// Mode admin obligatoire (OK=2)
-define('OK',2);
-include('debut.php');
-
-///////////////////
-// Modifications //
-///////////////////
-if ( isset($_REQUEST['id']) && is_numeric($id = $_REQUEST['id']) )  {
-  // Connexion à la base de données
-  $mysqli = new mysqli($serveur,"$base-adm",$mdp,$base);
-  $mysqli->set_charset('utf8');
-
-  // Vérification que l'identifiant est valide. Défaut : id=0 (nouvelle page)
-  if ( $id )  {
-    $resultat = $mysqli->query("SELECT id, nom, ordre, MOD(colles,2)+MOD(cdt,2)+docs AS nonvide, (SELECT MAX(m.ordre) FROM matieres AS m) AS max FROM matieres WHERE id = $id");
-    if ( $resultat->num_rows )  {
-      $r = $resultat->fetch_assoc();
-      $resultat->free();
-      $nom = $r['nom'];
-      $ordre = $r['ordre'];
-    }
-    else
-      $id = 0;
-  }
-
-  // Ajout ou modification d'une matière
-  if ( isset($_REQUEST['modifie']) )  {
-    // Vérification des données envoyées
-    if ( !strlen($_REQUEST['cle']) || !strlen($_REQUEST['nom']) )
-      $message = 'Il n\'est pas possible de valider une matière sans clé ou sans nom. Pour supprimer une matière, il faut cliquer sur Supprimer.';
-    else  {
-      // Sauvegarde des tables contenant les données
-      sauvegarde_mysql('matieres');
-      sauvegarde_mysql('reps');
-      // Validation des données envoyées
-      $cle = $mysqli->real_escape_string($_REQUEST['cle']);
-      $nom = $mysqli->real_escape_string($_REQUEST['nom']);
-      $colles = ", colles = IF((SELECT id FROM colles WHERE matiere = $id AND cache = 0 LIMIT 1),".( ( isset($_REQUEST['colles']) ) ? '3,2)' : '1,0)');
-      $cdt = ", cdt = IF((SELECT id FROM cdt WHERE matiere = $id AND cache = 0 LIMIT 1),".( ( isset($_REQUEST['cdt']) ) ? '3,2)' : '1,0)');
-      // Si identifiant numérique non nul, modification d'une matière existante
-      if ( $id )
-        $message = ( $mysqli->query("UPDATE matieres SET cle = '$cle', nom = '$nom'$colles$cdt WHERE id = $id")
-                  && $mysqli->query("UPDATE reps SET nom = '$nom' WHERE matiere = $id AND parent = 0")
-        ) ? 'La matière <em>'.stripslashes($nom).'</em> a bien été modifiée.' : "La matière <em>${r['nom']}</em> n'a pas pu être modifiée. Erreur MySQL n°".$mysqli->errno.', «'.$mysqli->error.'».';
-      // Si id = 0, ajout d'une nouvelle matière
-      else  {
-        $colles = ( isset($_REQUEST['colles']) ) ? 2 : 0;
-        $cdt = ( isset($_REQUEST['cdt']) ) ? 2 : 0;
-        $message = ( $mysqli->query("INSERT INTO matieres SET cle = '$cle', nom = '$nom', colles = '$colles', cdt = '$cdt', docs = 0, ordre = ((SELECT MAX(m.ordre) FROM matieres AS m)+1)")
-                  && ( $id = $mysqli->insert_id )
-                  && $mysqli->query("INSERT INTO reps SET parent = 0, parents = 0, nom = '$nom', matiere = $id, nbrep = 0, nbdoc_v = 0, nbdoc_nv = 0, protection = 0")
-                  && $mysqli->query("INSERT INTO `cdt-types` (matiere, ordre, cle, titre, deb_fin_pour) VALUES
-                                                             ($id, 1, 'cours', 'Cours', 1),
-                                                             ($id, 2, 'TD', 'Séance de travaux dirigés', 1),
-                                                             ($id, 3, 'TP', 'Séance de travaux pratiques', 1),
-                                                             ($id, 4, 'DS', 'Devoir surveillé', 1),
-                                                             ($id, 5, 'interros', 'Interrogation de cours', 0),
-                                                             ($id, 6, 'distributions', 'Distribution de document', 0),
-                                                             ($id, 7, 'DM', 'Devoir maison', 2)")
-        ) ? 'La matière <em>'.stripslashes($nom).'</em> a bien été ajoutée.' : 'La matière <em>'.stripslashes($nom).'</em> n\'a pas pu être ajoutée. Erreur MySQL n°'.$mysqli->errno.', «'.$mysqli->error.'».';
-      }
-    }
-  }
-
-  elseif ( $id )  {
-    
-    // Sauvegarde de la table contenant les données
-    sauvegarde_mysql('matieres');
-
-    // Déplacement vers le haut
-    if ( isset($_REQUEST['monte']) && ( $ordre > 1 ) )
-      $message = ( $mysqli->query("UPDATE matieres SET ordre = (2*$ordre-1-ordre) WHERE ordre = $ordre OR ordre = ($ordre-1)")
-                && $mysqli->query('ALTER TABLE matieres ORDER BY ordre')
-      ) ? "La matière <em>$nom</em> a bien été montée d'une place." : "La matière <em>$nom</em> n'a pas pu être déplacée. Erreur MySQL n°".$mysqli->errno.', «'.$mysqli->error.'».';
-
-    // Déplacement vers le bas
-    elseif ( isset($_REQUEST['descend']) && ( $ordre < $r['max'] ) )
-      $message = ( $mysqli->query("UPDATE matieres SET ordre = (2*$ordre+1-ordre) WHERE ordre = $ordre OR ordre = ($ordre+1)")
-                && $mysqli->query('ALTER TABLE matieres ORDER BY ordre')
-      ) ? "La matière <em>$nom</em> a bien été descendue d'une place." : "La matière <em>$nom</em> n'a pas pu être déplacée. Erreur MySQL n°".$mysqli->errno.', «'.$mysqli->error.'».';
-
-    // Suppression
-    elseif ( isset($_REQUEST['supprime']) && ( $r['max'] > 1 ) )  {
-      // La suppression est impossible si la matière n'est pas vide
-      if ( $r['nonvide'] )
-        $message = "La matière <em>$nom</em> n'a pas pu être supprimée&nbsp;: ses programmes de colles, son cahier de texte ou ses documents ne sont pas vides. Il faut supprimer l'ensemble de ces données pour pouvoir supprimer une matière.";
-      // La suppression est impossible si la matière est associée à qqun
-      else  {
-        $resultat = $mysqli->query("SELECT id FROM utilisateurs WHERE FIND_IN_SET($id,matieres)");
-        if ( $resultat->num_rows )  {
-          $message = "La matière <em>$nom</em> n'a pas pu être supprimée&nbsp;: elle est associée à au moins un utilisateur. Il faut que chaque utilisateur concerné supprime lui-même son association à cette matière.";
-          $resultat->free();
-        }
-        else
-          $message = ( $mysqli->query("DELETE FROM matieres WHERE id = $id")
-                    && $mysqli->query("UPDATE matieres SET ordre = (ordre-1) WHERE ordre > $ordre")
-                    && $mysqli->query("DELETE FROM `cdt-types` WHERE matiere = $id")
-                    && $mysqli->query("DELETE FROM `cdt-seances` WHERE matiere = $id")
-                    && $mysqli->query("DELETE FROM reps WHERE matiere = $id")
-                    && $mysqli->query("UPDATE pages SET mat = 0 WHERE mat = $id")
-          ) ? "La matière <em>$nom</em> a bien été supprimée." : "La matière <em>$nom</em> n'a pas pu être supprimée. Erreur MySQL n°".$mysqli->errno.', «'.$mysqli->error.'».';
-      }
-    }
-
-  }
-  $mysqli->close();
-}
-
-//////////
-// HTML //
-//////////
-// Haut de page, menu et message
-$p = "matieres$urladmin2";
-$t = 'Modifications des matieres';
-$mysqli = new mysqli($serveur,$base,$mdp,$base);
-$mysqli->set_charset('utf8');
-include('haut.php');
-?>
-
-  <div class="item aide">
-    <h3>Aide et explications</h3>
-    <p>Vous pouvez ci-dessous ajouter une matière ou modifier les matières existantes.</p>
-    <p>L'ordre d'apparition des matières dans le menu est modifiable grâce aux flèches. Il s'agit du menu de la partie publique uniquement&nbsp;: si vous avez plusieurs matières dans votre interface d'administration, vous pouvez gérer leur ordre d'apparition dans <a href="prefs">vos préférences</a>.</p>
-    <p>La suppression d'une matière n'est possible, afin d'éviter les erreurs de manipulation lourdes de conséquences, que si cela est sans conséquences. Il faut donc qu'aucun programme de colles/cahier de texte/document ne soit enregistré. Il faut aussi que cette matière n'apparaissent pas dans l'interface d'administration, pour tous les utilisateurs&nbsp;: chaque utilisateur peut faire ce choix dans ses préférences.</p>
-    <h4>Préférences de chaque matière</h4>
-    <p>Le <em>nom complet</em> s'affiche dans le menu et dans les titres des pages.</p>
-    <p>La <em>clé dans l'adresse</em> est un mot-clé utilisé uniquement dans l'adresse des pages associées à la matière. Il vaut mieux que ce soit un mot unique, court et sans majuscule. Par exemple, «&nbsp;maths&nbsp;», «&nbsp;phys&nbsp;»...</p>
-    <p>Les deux cases à cocher <em>Demande d'identification pour l'affichage...</em> permettent de restreindre la visibilité du programme de colles ou du cahier de texte aux visiteurs qui se sont identifiés. Dans ce cas, après <a href="utilisateurs">avoir créé un compte pour les élèves ici</a>, ces informations seront accessibles aux élèves auxquels vous aurez donné l'identifiant et le mot de passe choisis.</p>
-    <p>Les liens vers le cahier de texte, les programmes de colles et les documents sont affichés automatiquement dans le menu de la partie publique, uniquement lorsqu'il y a derrière quelque chose à afficher.</p>
-  </div>
-
-<?php
-// Fonction d'affichage
-function affichage($r)  {
-  if ( $id = $r['id'] )  {
-    $debut = "Matière n°${r['ordre']}&nbsp;: ${r['nom']}";
-    $valide1 = '';
-    $valide = "\n      <input type=\"submit\" name=\"modifie\" value=\"Valider\" title=\"Valider les modifications\">";
-    $monte = ( $r['ordre'] > 1 ) ? "\n      <input type=\"submit\" name=\"monte\" value=\"&uarr;\" title=\"Remonter la matière dans l'ordre d'apparition\">" : '';
-    $descend = ( $r['ordre'] < $GLOBALS['max'] ) ? "\n      <input type=\"submit\" name=\"descend\" value=\"&darr;\" title=\"Descendre la matière dans l'ordre d'apparition\">" : '';
-    if ( $r['nonvide'] )  {
-      $suppr = ( $GLOBALS['max'] > 1 ) ? "\n      <input type=\"submit\" name=\"supprime\" value=\"Supprimer\" title=\"Supprimer la matière\" disabled>" : '';
-      $indication = ( $GLOBALS['max'] > 1 ) ? "\n    <p>Cette matière ne peut pas être supprimée car ses programmes de colles, son cahier de texte ou des documents ne sont pas vides.</p>" : '';
-    }
-    else  {
-      $suppr = ( $GLOBALS['max'] > 1 ) ? "\n      <input type=\"submit\" name=\"supprime\" value=\"Supprimer\" title=\"Supprimer la matière\">" : '';
-      $indication = '';
-    }
-  }
-  else  {
-    $debut = 'Nouvelle matière';
-    $valide1 = "\n    <input class=\"bouton\" type=\"submit\" name=\"modifie\" value=\"Valider\" title=\"Valider les modifications\">";
-    $valide = $monte = $descend = $suppr = $indication = '';
-  }
-  echo <<<FIN
-  <div class="item admin">
-  <form action="" method="post">$valide1
-    <h3>$debut</h3>
-    <p class="boutons">$valide$monte$descend$suppr
-    </p>
-    <p class="ligne"><label for="nom$id">Nom complet&nbsp;: </label><input type="text" id="nom$id" name="nom" value="${r['nom']}" size="50"></p>
-    <p class="ligne"><label for="cle$id">Clé dans l'adresse&nbsp;: </label><input type="text" id="cle$id" name="cle" value="${r['cle']}" size="30"></p>
-    <p class="ligne"><label for="colles$id">Demande d'identification pour l'affichage du programme de colle</label><input type="checkbox" id="colles$id" name="colles" value="1"${r['colles']}></p>
-    <p class="ligne"><label for="cdt$id">Demande d'identification pour l'affichage du cahier de texte</label><input type="checkbox" id="cdt$id" name="cdt" value="1"${r['cdt']}></p>
-    <input type="hidden" name="id" value="$id">$indication
-  </form>
-  </div>
-
-
-FIN;
-}
-
-// Formulaire vide pour une nouvelle matière et aide générale
-affichage(array('id' => 0, 'ordre' => 0, 'cle' => 'cle_de_la_matiere', 'nom' => 'Nom de la matière', 'colles' => '', 'cdt' => ''));
-
-// Affichage des matières existantes
-$resultat = $mysqli->query('SELECT id, ordre, cle, nom, MOD(colles,2)+MOD(cdt,2)+docs AS nonvide,
-                            IF(colles>1,\' checked\',\'\') AS colles, IF(cdt>1,\' checked\',\'\') AS cdt
-                            FROM matieres');
-$max = $resultat->num_rows;
-$mysqli->close();
-while ( $r = $resultat->fetch_assoc() )
-  affichage($r);
-$resultat->free();
-
-// Bas de page
-include('bas.php');
-?>
diff -urN cahier-de-prepa3.2.0/notes.php cahier-de-prepa4.0.0/notes.php
--- cahier-de-prepa3.2.0/notes.php	1970-01-01 01:00:00.000000000 +0100
+++ cahier-de-prepa4.0.0/notes.php	2014-08-27 02:36:39.217494667 +0200
@@ -0,0 +1,118 @@
+<?php
+// Sécurité et récupération des données de configuration
+define('OK',1);
+include('debut.php');
+
+// L'accès est différent selon le type d'utilisateur, mais la connexion est obligatoire
+$mysqli = premiere_connexion();
+if ( !$autorisation )  {
+  $p = 'notes';
+  $t = 'Notes de colles';
+  include('login.php');
+}
+
+////////////////////////////////////////
+// Validation de la requête : matière //
+////////////////////////////////////////
+
+// Recherche de la matière concernée (affichage seulement si des notes existent)
+$mysqli = premiere_connexion();
+$requete = ( $autorisation > 1 ) ? "SELECT id, cle, nom FROM matieres WHERE FIND_IN_SET(id,'${_SESSION['matieres']}')" : 'SELECT id, cle, nom FROM matieres WHERE notes';
+$resultat = $mysqli->query($requete);
+if ( $resultat->num_rows )  {
+  if ( !empty($_REQUEST) )
+    while ( $r = $resultat->fetch_assoc() )
+      if ( isset($_REQUEST[$r['cle']]) )  {
+        $matiere = $r;
+        break;
+      }
+  // Matière par défaut : la première
+  if ( !isset($matiere) )  {
+    $resultat->data_seek(0);
+    $matiere = $resultat->fetch_assoc();
+  }
+  $resultat->free();
+}
+// Si aucune note n'est enregistrée pour aucune matière
+else  {
+  $mysqli->close();
+  exit('Cette page ne contient aucune information.');
+}
+
+// Si colleurs/profs : ajout de notes de colles -> admin/notes-saisie.php
+// Si élèves : on continue ici
+if ( $autorisation > 1 )  {
+  $mid = $matiere['id'];
+  include('admin/fonctions.php');
+  include('admin/notes-saisie.php');
+  $mysqli->close();
+  // Aide et javascript pour le pliage et les boutons
+?>
+
+  <div class="item aide">
+    <h3>Aide et explications</h3>
+    <p>Vous pouvez ajouter ou modifier des notes. Attention, un élève ne peut avoir plus d'une note dans la même matière une même semaine&nbsp;: si vous ajouter une telle note, elle ne sera pas validée.</p>
+    <p>Pour ajouter des notes sur une nouvelle semaine, vous devez obligatoirement sélectionner une semaine où vous n'avez pas encore mis de note. Il n'y a pas de limite de nombre d'élèves collés par semaine (toutes les notes d'une semaine sont à entrer en une seule fois).</p>
+    <p>Les notes déjà mises peuvent être modifiées&nbsp;: soit déplacées à une autre semaine, soit modifiées sur place, soit les deux simultanément.</p>
+    <p>Il est aussi possible de <em>supprimer</em> l'ensemble des notes d'une semaine.</p>
+  </div>
+<?php
+  echo $aff;
+?>
+
+<script type="text/javascript">
+$( function() {
+  // Pliage des items
+  $('.item h3').append(' <span>déplier</span>');
+  $('span').click( function () {
+    $(this).parent().parent().children(':not(h3)').toggle();
+    $(this).text($(this).text() == 'déplier' ? 'replier' : 'déplier');
+  });
+  // Premier pliage
+  $('.item form, .aide').children(':not(h3)').hide();
+  
+});
+</script>
+
+<?php
+  // Bas de page
+  include('bas.php');
+  exit();
+}
+else  {
+  // Fonction d'affichage des semaines
+  function format_date($date)  {
+    $semaine = array('dimanche','lundi','mardi','mercredi','jeudi','vendredi','samedi');
+    $mois = array('','janvier','février','mars','avril','mai','juin','juillet','août','septembre','octobre','novembre','décembre');
+    return $semaine[substr($date,0,1)].' '.substr($date,7).' '.$mois[intval(substr($date,5,2))].' '.substr($date,1,4);
+  }
+}
+
+////////////
+/// HTML ///
+////////////
+$p = "notes?${matiere['cle']}";
+$t = "Mes notes en ${matiere['nom']}";
+
+// Haut de page, menu et message
+include('haut.php');
+
+// Récupération de l'ensemble des notes, semaines, colleurs
+$resultat = $mysqli->query("SELECT DATE_FORMAT(s.debut,'%w%Y%m%e') AS d, n.note,
+                            CONCAT( CASE MOD(u.genre,5) WHEN 1 THEN 'M. ' WHEN 2 THEN 'Mme ' WHEN 3 THEN 'Melle ' END, u.nom ) AS c
+                            FROM notes AS n LEFT JOIN semaines AS s ON n.semaine=s.id LEFT JOIN utilisateurs AS u ON n.colleur=u.id
+                            WHERE n.eleve = ${_SESSION['id']} AND n.matiere = ${matiere['id']} ORDER BY s.id");
+
+// Affichage des notes concernées
+if ( $resultat->num_rows )  {
+  while ( $r = $resultat->fetch_assoc() )
+    echo '  <div class="item"><h3>Semaine du '.format_date($r['d'])."</h3><p>${r['note']} (${r['c']})</p></div>\n\n";
+  $resultat->free();
+}
+else
+  echo "  <h2>Vous n'avez encore aucune note cette année en ${matiere['nom']}.</h2>\n\n";
+$mysqli->close();
+
+// Bas de page
+include('bas.php');
+?>
diff -urN cahier-de-prepa3.2.0/pages.php cahier-de-prepa4.0.0/pages.php
--- cahier-de-prepa3.2.0/pages.php	2013-12-29 00:06:42.026590008 +0100
+++ cahier-de-prepa4.0.0/pages.php	1970-01-01 01:00:00.000000000 +0100
@@ -1,194 +0,0 @@
-<?php
-// Sécurité et récupération des données de configuration
-// Mode admin obligatoire (OK=2)
-define('OK',2);
-include('debut.php');
-
-///////////////////
-// Modifications //
-///////////////////
-if ( isset($_REQUEST['id']) && is_numeric($id = $_REQUEST['id']) )  {
-  // Connexion à la base de données
-  $mysqli = new mysqli($serveur,"$base-adm",$mdp,$base);
-  $mysqli->set_charset('utf8');
-  
-  // Vérification que l'identifiant est valide. Défaut : id=0 (nouvelle page)
-  if ( $id )  {
-    $resultat = $mysqli->query("SELECT id, nom, ordre, mat, (SELECT MAX(p.ordre) FROM pages AS p WHERE p.mat = pages.mat) AS max FROM pages WHERE id = $id");
-    if ( $resultat->num_rows )  {
-      $r = $resultat->fetch_assoc();
-      $resultat->free();
-    }
-    else
-      $id = 0;
-  }
-
-  // Ajout ou modification d'une page
-  if ( isset($_REQUEST['modifie']) )  {
-    // Vérification des données envoyées
-    if ( !strlen($_REQUEST['titre']) || !strlen($_REQUEST['cle']) || !strlen($_REQUEST['nom']) )
-      $message = 'Il n\'est pas possible de valider une page avec une clé, un nom ou un titre vide. Pour supprimer une page, il faut cliquer sur Supprimer.';
-    else  {
-      // Sauvegarde de la table contenant les données
-      sauvegarde_mysql('pages');
-      // Validation des données envoyées
-      $titre = $mysqli->real_escape_string($_REQUEST['titre']);
-      $bandeau = $mysqli->real_escape_string($_REQUEST['bandeau']);
-      $cle = $mysqli->real_escape_string($_REQUEST['cle']);
-      $nom = $mysqli->real_escape_string($_REQUEST['nom']);
-      $protection = ( isset($_REQUEST['protection']) ) ? 1 : 0;
-      $mat = ( is_numeric($_REQUEST['mat']) ) ? $_REQUEST['mat'] : 0;
-      // Si identifiant numérique non nul, modification d'une page existante
-      if ( $id )  {
-        // Si matière identique
-        if ( $mat == $r['mat'] )
-          $message = ( $mysqli->query("UPDATE pages SET cle = '$cle', nom = '$nom', titre = '$titre',
-                                       bandeau = '$bandeau', protection = $protection WHERE id = $id")
-          ) ? 'La page <em>'.stripslashes($nom).'</em> a bien été modifiée.' : "La page <em>${r['nom']}</em> n'a pas pu être modifiée. Erreur MySQL n°".$mysqli->errno.', «'.$mysqli->error.'».';
-        else  {
-          $resultat = $mysqli->query("SELECT IFNULL(MAX(ordre)+1,1) AS max FROM pages WHERE mat = $mat");
-          $m = $resultat->fetch_assoc();
-          $resultat->free();
-          $message = ( $mysqli->query("UPDATE pages SET cle = '$cle', nom = '$nom', mat = $mat, titre = '$titre',
-                                       ordre = ${m['max']}, bandeau = '$bandeau', protection = $protection WHERE id = $id")
-                    && $mysqli->query("UPDATE pages SET ordre = (ordre-1) WHERE mat = ${r['mat']} AND ordre > ${r['ordre']}")
-                    && $mysqli->query('ALTER TABLE pages ORDER BY mat,ordre')
-          ) ? 'La page <em>'.stripslashes($nom).'</em> a bien été modifiée.' : "La page <em>${r['nom']}</em> n'a pas pu être modifiée. Erreur MySQL n°".$mysqli->errno.', «'.$mysqli->error.'».';
-        }
-      }
-      // Si id = 0, ajout d'une nouvelle page
-      else
-        $message = ( $mysqli->query("INSERT INTO pages SET cle = '$cle', nom = '$nom', mat = $mat, titre = '$titre',
-                                     bandeau = '$bandeau', protection = $protection, ordre = (SELECT IFNULL(MAX(ordre)+1,1) FROM pages AS p WHERE p.mat = $mat)")
-                  && $mysqli->query('ALTER TABLE pages ORDER BY mat,ordre')
-        ) ? 'La page «&nbsp'.stripslashes($nom).'</em> a bien été ajoutée.' : 'La page <em>'.stripslashes($nom).'</em> n\'a pas pu être ajoutée. Erreur MySQL n°'.$mysqli->errno.', «'.$mysqli->error.'».';
-    }
-  }
-
-  elseif ( $id )  {
-    $ordre = $r['ordre'];
-
-    // Sauvegarde de la table contenant les données
-    sauvegarde_mysql('pages');
-
-    // Déplacement vers le haut
-    if ( isset($_REQUEST['monte']) && ( $ordre > 1 ) )
-      $message = ( $mysqli->query("UPDATE pages SET ordre = (2*$ordre-1-ordre) WHERE mat = ${r['mat']} AND ( ordre = $ordre OR ordre = ($ordre-1) )")
-                && $mysqli->query('ALTER TABLE pages ORDER BY mat,ordre')
-      ) ? "La page <em>${r['nom']}</em> a bien été montée d'une place." : "La page <em>${r['nom']}</em> n'a pas pu être déplacée. Erreur MySQL n°".$mysqli->errno.', «'.$mysqli->error.'».';
-
-    // Déplacement vers le bas
-    elseif ( isset($_REQUEST['descend']) && ( $ordre < $r['max'] ) )
-      $message = ( $mysqli->query("UPDATE pages SET ordre = (2*$ordre+1-ordre) WHERE mat = ${r['mat']} AND ( ordre = $ordre OR ordre = ($ordre+1) )")
-                && $mysqli->query('ALTER TABLE pages ORDER BY mat,ordre')
-      ) ? "La page <em>${r['nom']}</em> a bien été descendue d'une place." : "La page <em>${r['nom']}</em> n'a pas pu être déplacée. Erreur MySQL n°".$mysqli->errno.', «'.$mysqli->error.'».';
-
-    // Suppression : possible uniquement hors page d'accueil, page n°1 sans matière associée (mat=0)
-    elseif ( isset($_REQUEST['supprime']) && ( $r['max']+$r['mat'] > 1 ) )
-      $message = ( $mysqli->query("DELETE FROM pages WHERE id = $id")
-                && $mysqli->query("DELETE FROM infos WHERE page = $id")
-                && $mysqli->query("UPDATE pages SET ordre = (ordre-1) WHERE mat = ${r['mat']} AND ordre > $ordre")
-      ) ? "La page <em>${r['nom']}</em> a bien été supprimée, ainsi que les informations qu'elle comportait." : "La page <em>${r['nom']}</em> n'a pas pu être supprimée. Erreur MySQL n°".$mysqli->errno.', «'.$mysqli->error.'».';
-
-  }
-  $mysqli->close();
-}
-
-//////////
-// HTML //
-//////////
-// Haut de page, menu et message
-$p = "pages$urladmin2";
-$t = 'Modifications des pages d\'informations';
-$mysqli = new mysqli($serveur,$base,$mdp,$base);
-$mysqli->set_charset('utf8');
-include('haut.php');
-
-?>
-
-  <div class="item aide">
-    <h3>Aide et explications</h3>
-    <p>Vous pouvez ci-dessous ajouter une page d'information ou modifier les pages existantes ou leur ordre d'apparition dans le menu. Les pages non associées à une matière apparaîssent en début de menu (sur la partie publique), une page associée à une matière donnée apparaît au sein de cette matière.</p>
-    <p>Supprimer une page supprime automatiquement les informations qui y sont.</p>
-    <h4>Préférences associées à chaque page</h4>
-    <ul>
-      <li>Le <em>titre</em> sera affiché en haut de page et dans la barre de titre du navigateur. Par exemple, «&nbsp;À propos de l'ADS et du TIPE&nbsp;».</li>
-      <li>Le <em>nom dans le menu</em> est affiché dans le menu en tant que lien vers la page. Il est préférable qu'il rentre sur une ligne, il faut donc le choisir assez court. Par exemple, «&nbsp;Informations ADS/TIPE&nbsp;».</li>
-      <li>La <em>clé</em> est un mot-clé qui est utilisé uniquement dans l'adresse de la page (<code>http://<?php echo $site; ?>/?[clé]</code>). Il vaut mieux que ce soit un mot unique, court et sans majuscule. Par exemple, «&nbsp;ads-tipe&nbsp;».</li>
-      <li>Le <em>texte de début</em> sera affiché au-dessus des informations de la page. Il s'agit d'une ou deux phrases maximum.</li>
-    </ul>
-    <p>La case à cocher <em>Demande d'identification pour l'affichage de la page </em> permet de restreindre la visibilité de la page aux visiteurs qui se sont identifiés. Dans ce cas, après <a href="utilisateurs">avoir créé un compte pour les élèves ici</a>, ces informations seront accessibles aux élèves auxquels vous aurez donné l'identifiant et le mot de passe choisis.</p>
-  </div>
-
-<?php
-// Select sur les matières
-// La variable $matieres est définie dans haut.php
-$select_matieres = '<option value="O">Pas de matière associée</option>';
-foreach ( $matieres as $m )
-  $select_matieres .= "<option value=\"${m['id']}\">${m['nom']}</option>";
-
-// Fonction d'affichage
-function affichage($r)  {
-  // Si l'identifiant est 0, c'est une nouvelle page 
-  if ( $id  = $r['id'] )  {
-    $debut = "Page n°${r['ordre']}&nbsp;: ${r['nom']}";
-    $valide1 = '';
-    $valide = "\n      <input type=\"submit\" name=\"modifie\" value=\"Valider\" title=\"Valider les modifications\">";
-    $monte = ( $r['ordre'] > 1 ) ? "\n      <input type=\"submit\" name=\"monte\" value=\"&uarr;\" title=\"Remonter la page dans l'ordre d'apparition\">" : '';
-    $descend = ( $r['ordre'] < $GLOBALS['max'] ) ? "\n      <input type=\"submit\" name=\"descend\" value=\"&darr;\" title=\"Descendre la page dans l'ordre d'apparition\">" : '';
-    $suppr = ( $GLOBALS['max']+$r['mat'] > 1 ) ? "\n      <input type=\"submit\" name=\"supprime\" value=\"Supprimer\" title=\"Supprimer la page\">" : '';
-    $disabled = ( $r['ordre']+$r['mat'] == 1 ) ? ' disabled' : '';
-  }
-  else  {
-    $debut = 'Nouvelle page';
-    $valide1 = "\n    <input class=\"bouton\" type=\"submit\" name=\"modifie\" value=\"Valider\" title=\"Valider les modifications\">";
-    $valide = $monte = $descend = $suppr = $disabled = '';
-  }
-  // Identifiant unique pour les champs du formulaire
-  $fid = "${r['mat']}$id";
-  // Affichage du formulaire
-  echo <<<FIN
-  <div class="item admin">
-  <form action="" method="post">$valide1
-    <h3>$debut</h3>
-    <p class="boutons">$valide$monte$descend$suppr
-    </p>
-    <p class="ligne"><label for="titre$fid">Titre&nbsp;: </label><input type="text" id="titre$fid" name="titre" value="${r['titre']}" size="80"></p>
-    <p class="ligne"><label for="nom$fid">Nom dans le menu&nbsp;: </label><input type="text" id="nom$fid" name="nom" value="${r['nom']}" size="50"></p>
-    <p class="ligne"><label for="cle$fid">Clé dans l'adresse&nbsp;: </label><input type="text" id="cle$fid" name="cle" value="${r['cle']}" size="30"></p>
-    <p class="ligne"><label for="mat$fid">Matière associée&nbsp;: </label>
-      <select id="mat$fid" name="mat"$disabled>${GLOBALS['select_mat']}</select>
-    </p>
-    <p class="ligne"><label for="bandeau$fid">Texte de début&nbsp;:</label></p>
-    <textarea id="bandeau$fid" name="bandeau" rows="2" cols="100">${r['bandeau']}</textarea>
-    <p class="ligne"><label for="protection">Demande d'identification pour l'affichage de la page&nbsp;: </label><input type="checkbox" id="protection" name="protection" value="1"${r['protection']}></p>
-    <input type="hidden" name="id" value="$id">
-  </form>
-  </div>
-
-
-FIN;
-}
-
-// Pour chaque matière
-$matieres = array(-1=>array('id'=>0,'nom'=>'Menu général'))+$matieres;
-foreach ( $matieres as $m )  {
-  echo "<h2>${m['nom']}</h2>\n";
-  $select_mat = str_replace("\"${m['id']}\"","\"${m['id']}\" selected",$select_matieres);  
-
-  // Formulaire vide pour une nouvelle page et aide générale
-  affichage(array('id' => 0, 'cle' => 'cle_de_la_page', 'mat' => $m['id'], 'nom' => 'Nom de la page', 'titre' => 'Titre de la page', 'bandeau' => 'Texte (éventuel) marqué en haut de la page', 'protection' => ''));
-
-  // Affichage des pages existantes
-  $resultat = $mysqli->query("SELECT id,ordre,cle,nom,mat,titre,bandeau,IF(protection=1,' checked','') AS protection FROM pages WHERE mat = ${m['id']}");
-  if ( $max = $resultat->num_rows )  {
-    while ( $r = $resultat->fetch_assoc() )
-      affichage($r);
-    $resultat->free();
-  }
-}
-$mysqli->close();
-
-// Bas de page
-include('bas.php');
-?>
diff -urN cahier-de-prepa3.2.0/prefs.php cahier-de-prepa4.0.0/prefs.php
--- cahier-de-prepa3.2.0/prefs.php	2013-12-25 23:25:26.174216348 +0100
+++ cahier-de-prepa4.0.0/prefs.php	2014-08-25 12:23:24.341091614 +0200
@@ -1,258 +1,144 @@
 <?php
 // Sécurité et récupération des données de configuration
-// Mode admin obligatoire (OK=2)
-define('OK',2);
+define('OK',1);
 include('debut.php');
 
+$p = 'prefs';
+$t = 'Cahier de Prépa - Mon compte';
+$mysqli = premiere_connexion();
+// Vérification que l'on est bien connecté
+if ( !$autorisation )
+  include('login.php');
+
+// Récupération des données de l'utilisateur
+$resultat = $mysqli->query("SELECT login, nom, prenom, mdp, genre, mail, timeout
+                            FROM utilisateurs WHERE id = '${_SESSION['id']}'");
+$u = $resultat->fetch_assoc();
+$resultat->free();
+
 ///////////////////
 // Modifications //
 ///////////////////
 if ( isset($_REQUEST['modifie']) )  {
-  // Connexion à la base de données
-  $mysqli = new mysqli($serveur,"$base-adm",$mdp,$base);
-  $mysqli->set_charset('utf8');
-
-  // Traitement de la modification du nom d'utilisateur
-  if ( isset($_REQUEST['modifie_nom']) )  {
-    // Vérification des données envoyées
-    if ( $_SESSION['mdp'] != SHA1($mysqli->real_escape_string($_REQUEST['mdp'])) )
-      $message = 'Votre identifiant n\'a pas pu être modifié&nbsp;: le mot de passe renseigné est incorrect.';
-    elseif ( !strlen($nom = $mysqli->real_escape_string($_REQUEST['nom'])) )
-      $message = 'Votre identifiant n\'a pas pu être modifié&nbsp;: il ne peut pas être vide.';
-    else  {
-      // Sauvegarde de la table contenant les données
-      sauvegarde_mysql('utilisateurs');
-      $message = ( $mysqli->query("UPDATE utilisateurs SET nom = '$nom' WHERE id = ${_SESSION['id']}") ) ? 'Votre identifiant a bien été modifié.' : 'Votre identifiant n\'a pas pu être modifié. Erreur MySQL n°'.$mysqli->errno.', «'.$mysqli->error.'».';
-      $_SESSION['nom'] = $nom;
-    }
-  }
 
-  // Traitement de la modification du mot de passe
-  elseif ( isset($_REQUEST['modifie_mdp']) )  {
-    // Vérification des données envoyées
-    if ( $_SESSION['mdp'] != SHA1($mysqli->real_escape_string($_REQUEST['mdp0'])) )
-      $message = 'Votre mot de passe n\'a pas pu être modifié&nbsp;: le mot de passe actuel est incorrect.';
-    elseif ( !strlen($mdp1 = $mysqli->real_escape_string($_REQUEST['mdp1'])) )
-      $message = 'Votre mot de passe n\'a pas pu être modifié&nbsp;: il ne peut pas être vide.';
-    elseif ( $_REQUEST['mdp1'] != $_REQUEST['mdp2'] )
-      $message = 'Votre mot de passe n\'a pas pu être modifié&nbsp;: vous avez tapé deux mots de passe différents.';
-    else  {
-      // Sauvegarde de la table contenant les données
-      sauvegarde_mysql('utilisateurs');
-      $message = ( $mysqli->query("UPDATE utilisateurs SET mdp = SHA1('$mdp1') WHERE id = ${_SESSION['id']}") ) ? 'Votre mot de passe a bien été modifié.' : 'Votre mot de passe n\'a pas pu être modifié. Erreur MySQL n°'.$mysqli->errno.', «'.$mysqli->error.'».';
-      $_SESSION['mdp'] = SHA1($mdp1);
-    }
-  }
+  // Vérification du mot de passe
+  if ( ( sha1($_REQUEST['mdp0']) != $u['mdp'] ) && ( $u['mdp'] != 'pas de mot de passe' ) )
+    $message = 'Le mot de passe fourni est incorrect. Les modifications n\'ont pas été effectuées.';
+  else  {
 
-  // Traitement de la modification des matières
-  elseif ( isset($_REQUEST['modifie_matieres']) )  {
-    // Validation des données envoyées
-    $m = array_filter(array_unique($_REQUEST['matieres'],SORT_NUMERIC),'is_numeric');
-    if ( empty($m) )
-      $message = 'Vos matières associées n\'ont pas été modifiées&nbsp;: vous devez avoir au moins une matière associée.';
-    else
-      // Sauvegarde de la table contenant les données
-      sauvegarde_mysql('utilisateurs');
-      $m = implode(',',$m);
-      $message = ( $mysqli->query("UPDATE utilisateurs SET matieres = '$m' WHERE id = ${_SESSION['id']}") ) ? 'Vos matières associées ont bien été modifiées.' : 'Vos matières associées n\'ont pas pu être modifiées. Erreur MySQL n°'.$mysqli->errno.', «'.$mysqli->error.'».';
-      $_SESSION['matieres'] = $m;
-  }
+    // Fonctions de connexion/enregistrement
+    include('admin/fonctions.php');
 
-  // Traitement de la modification de protection par défaut des documents
-  elseif ( isset($_REQUEST['modifie_protection']) )  {
+    $requete = array();
+    $modif = array();
     // Validation des données envoyées
-    $protection = ( isset($_REQUEST['protection']) ) ? 1 : 0;
-    // Sauvegarde de la table contenant les données
-    sauvegarde_mysql('utilisateurs');
-    $message = ( $mysqli->query("UPDATE utilisateurs SET protection = $protection WHERE id = ${_SESSION['id']}") ) ? 'Votre choix par défaut de protection des documents a bien été modifié.' : 'Votre choix par défaut de protection des documents n\'a pas pu être modifié. Erreur MySQL n°'.$mysqli->errno.', «'.$mysqli->error.'».';
-    $_SESSION['protection'] = $protection;
-  }
-
-  // Traitement de la modification d'une matière
-  elseif ( isset($_REQUEST['modifie_matiere']) && is_numeric($id = $_REQUEST['id']) )  {
-    // Vérification des données envoyées
-    if ( !strlen($_REQUEST['cle']) || !strlen($_REQUEST['nom']) )
-      $message = 'Il n\'est pas possible de valider une matière sans clé ou sans nom.';
-    else  {
-      // Validation des données envoyées
-      $cle = $mysqli->real_escape_string($_REQUEST['cle']);
-      $nom = $mysqli->real_escape_string($_REQUEST['nom']);
-      $colles = ", colles = IF((SELECT id FROM colles WHERE matiere = $id AND cache = 0 LIMIT 1),".( ( isset($_REQUEST['colles']) ) ? '3,2)' : '1,0)');
-      $cdt = ", cdt = IF((SELECT id FROM cdt WHERE matiere = $id AND cache = 0 LIMIT 1),".( ( isset($_REQUEST['cdt']) ) ? '3,2)' : '1,0)');
-      // Sauvegarde des tables contenant les données
-      sauvegarde_mysql('matieres');
-      sauvegarde_mysql('reps');
-      $message = ( $mysqli->query("UPDATE matieres SET cle = '$cle', nom = '$nom'$colles$cdt WHERE id = $id")
-                && $mysqli->query("UPDATE reps SET nom = '$nom' WHERE matiere = $id AND parent = 0")
-      ) ? 'La matière <em>'.stripslashes($nom).'</em> a bien été modifiée.' : 'La matière <em>'.stripslashes($nom).'</em> n\'a pas pu être modifiée. Erreur MySQL n°'.$mysqli->errno.', «'.$mysqli->error.'».';
+    $login = $mysqli->real_escape_string($_REQUEST['login']);
+    $prenom = mb_convert_case($mysqli->real_escape_string($_REQUEST['prenom']),MB_CASE_TITLE,'UTF-8');
+    $nom = mb_convert_case($mysqli->real_escape_string($_REQUEST['nom']),MB_CASE_TITLE,'UTF-8');
+    $mail = strtolower($mysqli->real_escape_string($_REQUEST['mail']));
+    if ( in_array($genre = $_REQUEST['genre'],array(1,2,3)) && ( $genre != $u['genre'] ) )  {
+      $u['genre'] = $genre;
+      $requete[] = "genre = $genre";
+      $modif[] = 'genre';
+    }
+    if ( strlen($login) && ( $login != $u['login'] ) )  {
+      // Vérification que le login n'existe pas déjà
+      $resultat = $mysqli->query("SELECT id FROM utilisateurs WHERE login = '$login'");
+      if ( $resultat->num_rows )
+        $resultat->free();
+      else  {
+        $u['login'] = $login;
+        $requete[] = "login = '$login'";
+        $modif[] = 'identifiant';
+        $_SESSION['login'] = $login;
+      }
+    }
+    if ( strlen($nom) && ( $nom != $u['nom'] ) )  {
+      $u['nom'] = $nom;
+      $requete[] = "nom = '$nom'";
+      $modif[] = 'nom';
+    }
+    if ( strlen($prenom) && ( $prenom != $u['prenom'] ) )  {
+      $u['prenom'] = $prenom;
+      $requete[] = "prenom = '$prenom'";
+      $modif[] = 'prénom';
+    }
+    if ( ( $mail != $u['mail'] ) && ( filter_var($mail,FILTER_VALIDATE_EMAIL) || !strlen($mail) ) )  {
+      $u['mail'] = $mail;
+      $requete[] = "mail = '$mail'";
+      $modif[] = 'mail';
+    }
+    if ( strlen($mdp1 = $_REQUEST['mdp1']) && ( $mdp1 == $_REQUEST['mdp2'] ) && ( ( $mdp1 = sha1($mdp1) ) != $u['mdp'] ) )  {
+      $u['mdp'] = $mdp1;
+      $requete[] = "mdp = '$mdp1'";
+      $modif[] = 'mot de passe';
+    }
+    if ( is_numeric( $timeout = $_REQUEST['timeout'] ) && ( $timeout != $u['timeout'] ) )  {
+      if ( $timeout < 60 )
+        $timeout = 60;
+      $u['timeout'] = $timeout;
+      $requete[] = "timeout = $timeout";
+      $modif[] = 'temps de connexion';
+      $_SESSION['timeout'] = $timeout;
+    }
+    if ( !empty($requete) )  {
+      $requete = implode(', ',$requete);
+      $modif = implode(', ',$modif);
+      // Connexion à la base de données avec les droits d'écriture
+      $mysqli->close();
+      $mysqli = mysql_ecriture();
+      if ( requete('utilisateurs',"UPDATE utilisateurs SET $requete WHERE id = ${_SESSION['id']}") )
+        $message = "Les modifications ($modif) ont bien été réalisées.";
     }
   }
-  $mysqli->close();
 }
 
 //////////////
 //// HTML ////
 //////////////
-// Haut de page, menu et message
-$p = "prefs$urladmin2";
-$t = 'Préférences';
-$mysqli = new mysqli($serveur,$base,$mdp,$base);
-$mysqli->set_charset('utf8');
-include('haut.php');
 
-/////////////////
-// Identifiant //
-/////////////////
-?>
-
-  <div class="item aide">
-    <h3>Aide et explications</h3>
-    <p>Vous pouvez ci-dessous modifier les réglages qui vous concernent personnellement&nbsp;: votre identifiant et votre mot de passe, les matières apparaissant dans le menu à gauche, le choix par défaut de la protection des documents, ainsi que les réglages de chaque matière précédemment choisies.</p>
-    <h4>Identifiant</h4>
-    <p>Vous pouvez changer d'identifiant. Celui-ci ne sert que pour vous connecter, il n'est pas accessible aux élèves. Les accents ne posent pas de problème. Vous devez renseigner votre mot de passe pour valider cette modification.</p>
-    <h4>Mot de passe</h4>
-    <p>Vous pouvez modifier votre mot de passe. Il faut bien entendu renseigner le mot de passe actuel et taper deux fois le nouveau mot de passe pour éviter les erreurs de frappe.</p>
-    <p>Les mots de passe sont chiffrés avant d'être stockés dans la base de données&nbsp;: il n'y a donc aucun souci sur leur confidentialité. Même l'administrateur du site, l'hébergeur ou d'éventuels pirates ne pourront jamais y avoir accès. Lors de la connexion, le mot de passe entré est chiffré à son tour et ce sont les deux chiffrements qui sont comparés. Attention&nbsp;: il est très dangereux que ce mot de passe soit évident. Un bon mot de passe est un mot de passe d'au moins 8 caractères contenant des lettres, des chiffres et au moins un symbole parmi «&nbsp;?&nbsp;;&nbsp;:&nbsp;!&nbsp;.&nbsp;,-&nbsp;_&nbsp;». Mais vous faîtes ce que vous voulez...</p>
-    <h4>Les matières vous concernant</h4>
-    <p>Vous pouvez choisir quelles matières apparaissent dans le menu de gauche. Ce choix ne modifie pas les matières apparaissant dans le menu de la partie publique, ni leur ordre d'apparition. Plusieurs collègues peuvent afficher la même matière (et donc y ajouter du contenu).</p>
-    <h4>Protection par défaut des documents</h4>
-    <p>Pour chaque document, vous pouvez spécifier s'il est nécessaire au visiteur de s'identifier pour le voir (accès restreint aux élèves, <a href="utilisateurs">compte à créer ici</a>) ou non. Ce choix vous permet simplement de pré-cocher la case si vous le souhaiter. Vous pourrez bien sûr, pour chaque document, restreindre ou non l'accès lors de l'envoi.</p>
-    <h4>Préférences de matière</h4>
-    <p>Pour chaque matière vous concernant (choix fait juste au-dessus), vous pouvez modifier le <em>nom complet</em>, qui s'affiche dans le menu et dans les titres des pages.</p>
-    <p>Vous pouvez aussi modifier la <em>clé dans l'adresse</em>, qui est un mot-clé utilisé uniquement dans l'adresse des pages associées à la matière. Il vaut mieux que ce soit un mot unique, court et sans majuscule. Par exemple, «&nbsp;maths&nbsp;», «&nbsp;phys&nbsp;»...</p>
-    <p>Les deux cases à cocher <em>Demande d'identification pour l'affichage...</em> permettent de restreindre la visibilité du programme de colles ou du cahier de texte aux visiteurs qui se sont identifiés. Dans ce cas, après <a href="utilisateurs">avoir créé un compte pour les élèves ici</a>, ces informations seront accessibles aux élèves auxquels vous aurez donné l'identifiant et le mot de passe choisis.</p>
-    <p>Les liens vers le cahier de texte, les programmes de colles et les documents sont affichés automatiquement dans le menu de la partie publique, uniquement lorsqu'il y a derrière quelque chose à afficher.</p>
-  </div>
-
-  <div class="item admin">
-  <form action="" method="post">
-    <input class="bouton" type="submit" name="modifie_nom" value="Valider" title="Valider les modifications">
-    <h3>Modifier mon identifiant</h3>
-    <p class="ligne"><label for="nom">Identifiant&nbsp;: </label><input type="text" id="nom" name="nom" value="<?php echo $_SESSION['nom']; ?>"></p>
-    <p class="ligne"><label for="mdp">Mot de passe&nbsp;: </label><input type="password" id="mdp" name="mdp" value=""></p>
-    <input type="hidden" name="modifie" value="">
-  </form>
-  </div>
-<?php
-
-//////////////////
-// Mot de passe //
-//////////////////
-?>
+// Plus de mot de passe : remise à zéro suite à une demande de mot de passe oublié
+if ( $u['mdp'] == 'pas de mot de passe' )  {
+  $message = 'IMPORTANT : votre mot de passe a été réinitialisé. Vous devez absolument le redéfinir ci-dessous.';
+  $mdp0 = '    <input type="hidden" name="mdp0" value="réinitialisé">';
+}
+else
+  $mdp0 = '    <p class="ligne"><label for="mdp0">Mot de passe actuel&nbsp;: </label><input type="password" id="mdp0" name="mdp0" value=""></p>';
 
-  <div class="item admin">
-  <form action="" method="post">
-    <input class="bouton" type="submit" name="modifie_mdp" value="Valider" title="Valider les modifications">
-    <h3>Modifier mon mot de passe</h3>
-    <p class="ligne"><label for="mdp0">Mot de passe actuel&nbsp;: </label><input type="password" id="mdp0" name="mdp0" value=""></p>
-    <p class="ligne"><label for="mdp1">Nouveau&nbsp;: </label><input type="password" id="mdp1" name="mdp1" value=""></p>
-    <p class="ligne"><label for="mdp2">Confirmation&nbsp;: </label><input type="password" id="mdp2" name="mdp2" value=""></p>
-    <input type="hidden" name="modifie" value="">
-  </form>
-  </div>
-<?php
+// Haut de page, menu et message
+include('haut.php');
+$mysqli->close();
 
-///////////////////////////////////////////
-// Détermination des matières concernées //
-///////////////////////////////////////////
-// Récupération de toutes les matières
-$resultat = $mysqli->query("SELECT id, nom, ordre FROM matieres ORDER BY ordre");
-$select = "    <p id=\"ligneXX\" class=\"ligne\">Matière n°XX&nbsp;:\n      <select name=matieres[XX]>\n";
-$max = $resultat->num_rows;
-while ( $r = $resultat->fetch_assoc() )
-  $select .= "        <option value=\"${r['id']}\">${r['nom']}</option>\n";
-$resultat->free();
-$select .= "      </select>\n    </p>\n";
-?>
+// Formulaire des données
+$select_genre = str_replace("\"${u['genre']}\"","\"${u['genre']}\" selected",'
+        <option value="1">M.</option>
+        <option value="2">Mme</option>
+        <option value="3">Melle</option>');
 
+echo <<<FIN
   <div class="item admin">
   <form action="" method="post">
-    <input class="bouton" type="submit" name="modifie_matieres" value="Valider" title="Valider les modifications">
-    <h3>Modifier mes matières</h3>
-<?php
-$n = 1;
-foreach ( $matieres as $m )
-  echo str_replace("\"${m['id']}\"","\"${m['id']}\" selected",str_replace('XX',$n++,$select));
-?>
-    <p class="boutons">
-      <input type="button" id="ajouter" value="Ajouter une matière">
-      <input type="button" id="retirer" value="Retirer la dernière matière">
+    <input class="bouton" type="submit" name="modifie" value="Valider" title="Valider les modifications">
+    <h3>Modifier mon compte</h3>
+    <p class="ligne"><label for="genre">M-Mme-Melle&nbsp;:</label>
+      <select name="genre" id="genre">$select_genre
+      </select>
     </p>
-    <input type="hidden" name="modifie" value="">
-  </form>
-  </div>
-<?php
-
-//////////////////////////////////////////////////
-// Choix de protection par défaut des documents //
-//////////////////////////////////////////////////
-$protection = ( $_SESSION['protection'] ) ? ' checked' : '';
-?>
-
-  <div class="item admin">
-  <form action="" method="post">
-    <input class="bouton" type="submit" name="modifie_protection" value="Valider" title="Valider les modifications">
-    <h3>Modifier la protection par défaut de mes documents</h3>
-    <p class="ligne"><label for="protection">Cocher automatiquement la case de protection pour les nouveaux documents&nbsp;: </label><input type="checkbox" id="protection" name="protection" value="1"<?php echo $protection; ?>></p>
-    <input type="hidden" name="modifie" value="">
+    <p class="ligne"><label for="login">Identifiant&nbsp;: </label><input type="text" id="login" name="login" value="${u['login']}" size="50"></p>
+    <p class="ligne"><label for="prenom">Prénom&nbsp;: </label><input type="text" id="prenom" name="prenom" value="${u['prenom']}" size="50"></p>
+    <p class="ligne"><label for="nom">Nom&nbsp;: </label><input type="text" id="nom" name="nom" value="${u['nom']}" size="50"></p>
+    <p class="ligne"><label for="mail">Adresse mail&nbsp;: </label><input type="text" id="mail" name="mail" value="${u['mail']}" size="50"></p>
+$mdp0
+    <p class="ligne"><label for="mdp1">Nouveau mot de passe&nbsp;: </label><input type="password" id="mdp1" name="mdp1" value=""></p>
+    <p class="ligne"><label for="mdp2">Confirmation&nbsp;: </label><input type="password" id="mdp2" name="mdp2" value=""></p>
+    <p class="ligne"><label for="timeout">Temps de déconnexion&nbsp;: </label><input type="text" id="timeout" name="timeout" value="${u['timeout']}" size="5"></p>
   </form>
+  <p>Le mot de passe actuel doit être obligatoirement fourni.</p>
   </div>
-<?php
 
-/////////////////////////////////////////
-// Préférences des matières concernées //
-/////////////////////////////////////////
-foreach ( $matieres as $m )  {
-  $colles = ( $m['colles'] > 1 ) ? ' checked': '';
-  $cdt = ( $m['cdt'] > 1 ) ? ' checked': '';
-  $id = $m['id'];
-  echo <<<FIN
-
-  <div class="item admin">
-  <form action="" method="post">
-    <input class="bouton" type="submit" name="modifie_matiere" value="Valider" title="Valider les modifications">
-    <h3>Modifier la matière <em>${m['nom']}</em></h3>
-    <p class="ligne"><label for="nom$id">Nom complet&nbsp;: </label><input type="text" id="nom$id" name="nom" value="${m['nom']}" size="50"></p>
-    <p class="ligne"><label for="cle$id">Clé dans l'adresse&nbsp;: </label><input type="text" id="cle$id" name="cle" value="${m['cle']}" size="30"></p>
-    <p class="ligne"><label for="colles$id">Demande d'identification pour l'affichage du programme de colle</label><input type="checkbox" id="colles$id" name="colles" value="1"$colles></p>
-    <p class="ligne"><label for="cdt$id">Demande d'identification pour l'affichage du cahier de texte</label><input type="checkbox" id="cdt$id" name="cdt" value="1"$cdt></p>
-    <input type="hidden" name="id" value="$id">
-    <input type="hidden" name="modifie" value="">
-  </form>
-  </div>
 
 FIN;
-}
-?>
-
-  <script type="text/javascript">
-$( function() {
-  
-  var n = <?php echo $n; ?>;
-  $('#ajouter').click(function () {
-    if ( n <= <?php echo $max; ?> )  {
-      $('#ligne'+(n-1)).clone(true).attr('id','ligne'+n).insertAfter($('#ligne'+(n-1)));
-      $('#ligne'+n).find('select[name="matieres['+(n-1)+']"]').attr('name','matieres['+n+']');
-      $('#ligne'+n).html($('#ligne'+n).html().replace('n°'+(n-1),'n°'+n))
-      $('select[name="matieres['+n+']"]').val([1]);
-      n++;
-    }
-  });
-  $('#retirer').click(function () {
-    if ( n > 2 )  {
-      $('#ligne'+(n-1)).remove();
-      n--;
-    }
-  });
 
-});
-  </script>
-
-  <script type="text/javascript" src="textarea.js.php"></script>
-
-<?php
-$mysqli->close();
 // Bas de page
 include('bas.php');
-exit();
 ?>
diff -urN cahier-de-prepa3.2.0/rss.php cahier-de-prepa4.0.0/rss.php
--- cahier-de-prepa3.2.0/rss.php	2013-12-25 00:05:26.639528353 +0100
+++ cahier-de-prepa4.0.0/rss.php	2014-08-01 20:45:09.219699517 +0200
@@ -3,23 +3,16 @@
 define('OK',1);
 include('debut.php');
 
-// Ce script sert simplement à donner le lien du flux RSS.
-// S'il existe un utilisateur de type élève, qui sert à demander une identification
-// avant d'afficher certains contenus, on suppose qu'il est nécessaire de se
-// connecter pour voir le lien.
-
 $p = '';
-$t = "La $classe du lycée $lycee - Flux RSS";
-
+// Titre : on récupère celui de la première page
 $mysqli = premiere_connexion();
-if ( !$lecteur )  {
-  $resultat = $mysqli->query('SELECT id FROM utilisateurs WHERE matieres = \'0\'');
-  if ( $resultat->num_rows )  {
-    $resultat->free();
-    // Demander l'identification
-    include('login_lecture.php');
-  }
-}
+$resultat = $mysqli->query('SELECT titre FROM pages WHERE id = 1');
+$r = $resultat->fetch_assoc();
+$resultat->free();
+$t = "${r['titre']} - Flux RSS";
+
+// Contenu accessible uniquement aux connectés
+include('login.php');
 
 // Haut de page, menu et message
 include('haut.php');
@@ -28,7 +21,7 @@
 
   <div class="item">
     <h3>Le flux RSS est disponible à l'adresse</h3>
-    <p><a href="documents/<?php echo sha1($GLOBALS['base']); ?>/rss.xml">http://<?php echo "$site/documents/".sha1($GLOBALS['base']).'/rss.xml'; ?></a></p>
+    <p><a href="documents/<?php echo sha1($base); ?>/rss.xml">http://<?php echo "$site/documents/".sha1($base).'/rss.xml'; ?></a></p>
   </div>
 
 <?php
diff -urN cahier-de-prepa3.2.0/semaines.php cahier-de-prepa4.0.0/semaines.php
--- cahier-de-prepa3.2.0/semaines.php	2013-12-25 23:25:09.274215807 +0100
+++ cahier-de-prepa4.0.0/semaines.php	1970-01-01 01:00:00.000000000 +0100
@@ -1,203 +0,0 @@
-<?php
-// Sécurité et récupération des données de configuration
-// Mode admin obligatoire (OK=2)
-define('OK',2);
-include('debut.php');
-
-///////////////////
-// Modifications //
-///////////////////
-if ( isset($_REQUEST['initialise']) && in_array($_REQUEST['zone'],array('A','B','C')) )  {
-  // Sauvegarde de la table contenant les données
-  sauvegarde_mysql('semaines');
-  // Récupération des semaines
-  include('definition_semaines.php');
-  // Écriture MySQL
-  $mysqli = new mysqli($serveur,"$base-adm",$mdp,$base);
-  $mysqli->set_charset('utf8');
-  $mysqli->query('TRUNCATE semaines');
-  $message = ( $mysqli->query($semaines) ) ? 'Les semaines ont bien été réinitialisées' : 'Les semaines n\'ont pas pu être réinitialisées. Erreur MySQL n°'.$mysqli->errno.', «'.$mysqli->error.'».';
-  $mysqli->close();
-}
-
-if ( isset($_REQUEST['modifie']) && !empty($_REQUEST['debut']) )  {
-  // Sauvegarde de la table contenant les données
-  sauvegarde_mysql('semaines');
-
-  // Récupération et validation de l'ensemble des paramètres
-  $debuts = $_REQUEST['debut'];
-  $colles = $_REQUEST['colle'];
-  $vacances = $_REQUEST['vacances'];
-  $n = count($debuts);
-  $valeurs = '';
-  for ( $i = 0; $i <$n; $i++ )  {
-    // La date doit être non vide et du bon format
-    if ( strlen($debuts[$i]) )  {
-      $debut = preg_replace('/(\d{2})\/(\d{2})\/(\d{4})/','$3-$2-$1',$debuts[$i]);
-      if ( strlen($debut) == 10 )  {
-        if ( !is_numeric($v = $vacances[$i]) || ( $v < 0 ) || ( $v > 4 ) )
-          $v = 0;
-        $colle = ( isset($colles[$i]) && !$v ) ? 1 : 0;
-        $valeurs .= " ('$debut',$colle,$v),";
-      }
-    }
-  }
-
-  // Remplacement de l'ensemble dans la base
-  // Remarque : on réordonne les id pour qu'il soient dans le même ordre que celui
-  //  du tableau. Cela est nécessaire pour que la requête "WHERE s.id BETWEEN $n
-  //  AND ${sid[$m+$nb-1]}" de colles.php, l231, fonctionne correctement.
-  //  Il serait aussi possible de changer cette requête par une beaucoup plus
-  //  complexe, faisant intervenir le champ 'debut' uniquement. 
-  if ( strlen($valeurs) )  {
-    $mysqli = new mysqli($serveur,"$base-adm",$mdp,$base);
-    $mysqli->set_charset('utf8');
-    $message = ( $mysqli->query('DELETE FROM semaines')
-              && $mysqli->query('ALTER TABLE semaines AUTO_INCREMENT = 50')
-              && $mysqli->query('INSERT INTO semaines (debut, colle, vacances) VALUES'.substr($valeurs, 0, -1))
-              && $mysqli->query('ALTER TABLE semaines ORDER BY debut')
-              && $mysqli->query('SET @count = 0;')
-              && $mysqli->query('UPDATE semaines SET id = @count:= @count + 1;')
-              && $mysqli->query('ALTER TABLE semaines AUTO_INCREMENT = 1')
-    ) ? 'Les semaines ont bien été toutes remises à jour.' : 'Les semaines n\'ont pas pu être modifiées. Erreur MySQL n°'.$mysqli->errno.', «'.$mysqli->error.'».';
-    $mysqli->close();
-  }
-}
-
-//////////
-// HTML //
-//////////
-// Haut de page, menu et message
-$p = "semaines$urladmin2";
-$t = 'Modifications des semaines du planning annuel';
-$mysqli = new mysqli($serveur,$base,$mdp,$base);
-$mysqli->set_charset('utf8');
-include('haut.php');
-
-// Fonction d'affichage du formulaire
-function affiche_formulaire($n,$r)  {
-  $select = str_replace("\"${r['vacances']}\"","\"${r['vacances']}\" selected",'<option value="0">Période scolaire</option><option value="1">Vacances de Toussaint</option><option value="2">Vacances de Noël</option><option value="3">Vacances d\'hiver</option><option value="4">Vacances de printemps</option>');
-  if ( strlen($r['jour']) )  {
-    $semaine = array('Dimanche','Lundi','Mardi','Mercredi','Jeudi','Vendredi','Samedi');
-    $r['jour'] = $semaine[$r['jour']];
-  }
-  echo <<<FIN
-          <tr id="ligne$n">
-            <td><label for="debut$n">${r['jour']}</label>&nbsp;&nbsp;<input type="text" class="date" id="debut$n" name="debut[$n]" value="${r['debut']}" size="8"></td>
-            <td><input type="checkbox" name="colle[$n]" value="1"${r['colle']}></td>
-            <td><select name="vacances[$n]">$select</select></td>
-          </tr>
-
-FIN;
-}
-
-// Affichage de l'aide générale et du haut du tableau
-?>
-  
-  <div class="item aide">
-    <h3>Aide et explications</h3>
-    <p>Vous pouvez ci-dessous modifier le planning annuel, c'est-à-dire l'ensemble des semaines et vacances ainsi que s'il des colles sont prévues ou non cette semaine-là. Ce planning annuel est utilisé pour les programmes de colles et pour les cahiers de texte.</p>
-    <p>Une réinitialisation complète est possible, en fonction de la zone scolaire. Il s'agit d'un planning allant jusqu'à juillet.</p>
-    <p>La colonne <em>Colles</em> est à cocher si des colles sont prévues cette semaine-là.</p>
-    <p>La validation des données se fait de façon globale, par les boutons de validation en haut ou en bas du formulaire&nbsp;: toutes les modifications sont prises en compte en une seule fois. De plus, les semaines sont automatiquement réorganisées par ordre chronologique&nbsp;: pas besoin de tout effacer et tout réécrire pour insérer une semaine oubliée, il suffit de l'ajouter en bas.</p>
-    <p>Pour supprimer une semaine, il suffit de vider la date et de valider le tableau.</p>
-  </div>
-
-  <div class="item admin">
-  <form action="" method="post">
-    <input class="bouton" type="submit" name="initialise" value="Valider" title="Valider les modifications">
-    <h3>Réinitialiser avec les valeurs de 2013-2014</h3>
-    <p class="ligne"><label for="zone">Zone&nbsp;:</label>
-      <select id="zone" name="zone"><option value="A">A</option><option value="B">B</option><option value="C">C</option></select>
-    </p>
-    <p>Attention, si des données du programme de colles sont entrées, le résultat peut être aléatoire...</p>
-  </form>
-  </div>
-
-  <div class="item">
-    <form action="" method="post">
-      <p class="boutons">
-        <input type="button" class="ajouter" value="Ajouter une ligne au tableau">
-        <input type="button" class="retirer" value="Retirer la dernière ligne du tableau">
-        <input type="submit" name="modifie" value="Valider">
-      </p>
-      <table id="semaines" class="admin">
-        <thead>
-          <tr>
-            <th>Début de semaine</th>
-            <th>Colle ou non</th>
-            <th>Vacances</th>
-          </tr>
-        </thead>
-        <tbody>
-<?php
-// Récupération et affichage des matières
-$resultat = $mysqli->query('SELECT DATE_FORMAT(debut,\'%w\') AS jour, DATE_FORMAT(debut,\'%d/%m/%Y\') AS debut, IF(colle=1,\' checked\',\'\') AS colle, vacances FROM semaines');
-$mysqli->close();
-$n = 0;
-if ( $resultat->num_rows )  {
-  while ( $r = $resultat->fetch_assoc() )
-    affiche_formulaire($n++,$r);
-  $resultat->free();
-}
-
-// Affichage d'un formulaire vide pour ajout
-affiche_formulaire($n++,array('jour'=>'','debut'=>'','colle'=>'','vacances'=>'0'));
-
-// Fin du formulaire
-?>
-        </tbody>
-      </table>
-
-      <p class="boutons">
-        <input type="button" class="ajouter" value="Ajouter une ligne au tableau">
-        <input type="button" class="retirer" value="Retirer la dernière ligne du tableau">
-        <input type="submit" name="modifie" value="Valider">
-      </p>
-    </form>
-  </div>
-
-  <script type="text/javascript">
-$( function() {
-  $('.date').datepick({
-    dateFormat: 'dd/mm/yyyy',
-    onSelect: function () {
-      var s = $(this).val();
-      var d = new Date(s.substring(6,10)+'/'+s.substring(3,5)+'/'+s.substring(0,2));
-      var semaine = new Array('Dimanche','Lundi','Mardi','Mercredi','Jeudi','Vendredi','Samedi');
-      $(this).prev('label').text(semaine[d.getDay()]);
-    }
-  });
-  $('select').change(function () {
-    if ( $(this).val() == 0 )  {
-      $(this).parent().prev().children('input').prop('checked',true);
-    }
-    else  {
-      $(this).parent().prev().children('input').prop('checked',false);
-    }
-  });
-  $('input:checkbox').change(function () {
-    if ( $(this).is(':checked') )  {
-      $(this).parent().next().children('select').val(0);
-    }
-  });
-
-  var n = <?php echo $n; ?>;
-  $('.ajouter').click(function () {
-    $('#ligne'+(n-1)).clone(true).attr('id','ligne'+n).insertAfter($('#ligne'+(n-1)));
-    $('#ligne'+n).find('input[name="debut['+(n-1)+']"]').attr('name','debut['+n+']');
-    $('#ligne'+n).find('input[name="colle['+(n-1)+']"]').attr('name','colle['+n+']');
-    $('#ligne'+n).find('select[name="vacances['+(n-1)+']"]').attr('name','vacances['+n+']');
-    n++;
-  });
-  $('.retirer').click(function () {
-    $('#ligne'+(n-1)).remove();
-    n--;
-  });
-});
-  </script>
-
-<?php
-// Bas de page
-include('bas.php');
-?>
diff -urN cahier-de-prepa3.2.0/textarea.js.php cahier-de-prepa4.0.0/textarea.js.php
--- cahier-de-prepa3.2.0/textarea.js.php	2013-10-22 12:10:50.711858045 +0200
+++ cahier-de-prepa4.0.0/textarea.js.php	1970-01-01 01:00:00.000000000 +0100
@@ -1,178 +0,0 @@
-<?php
-// Mode admin obligatoire (OK=2)
-define('OK',2);
-include('debut.php');
-header('Content-Type: application/javascript; charset=utf-8');
-
-// Récupération de l'identifiant de répertoire demandé
-$mysqli = premiere_connexion();
-
-// Si une matière est demandée (colles.php, cdt.php)
-$m = ( isset($_REQUEST['m']) && is_numeric($_REQUEST['m']) ) ? " AND matiere = ${_REQUEST['m']}" : '';
-// Récupération des répertoires, avec le chemin complet
-$resultat = $mysqli->query('SELECT id, CONCAT(
-            ( SELECT GROUP_CONCAT(nom SEPARATOR \'/\') FROM reps AS r WHERE FIND_IN_SET(r.id,reps.parents) )
-            ,\'/\') AS parents, nom FROM `reps` WHERE nbdoc_v'.$m);
-$reps = '';
-$docs = array();
-if ( $resultat->num_rows )  {
-  $reps = '
-    <p class="boutons">Insérer un document&nbsp;:\\
-      <select id="reps">\\';
-  while ( $r = $resultat->fetch_assoc() )  {
-    $reps .= "\n        <option value=\"${r['id']}\">".addslashes($r['parents'].$r['nom']).'</option>\\';
-    // Récupération des documents
-    $docs[$r['id']] = '';
-    $res = $mysqli->query("SELECT id, nom FROM docs WHERE parent = ${r['id']} AND protection < 2");
-    while ( $d = $res->fetch_assoc() )  {
-      $docs[$r['id']] .= "<option value=\"${d['id']}\">".addslashes($d['nom']).'</option>';
-    }
-    $res->free();
-  }
-  $resultat->free();
-  $reps .= '
-      </select>\\
-      <select id="doc">'.current($docs).'</select>\\
-      <input type="button" id="js_doc" value="Insérer">\\
-    </p>\\
-';
-}
-$mysqli->close();
-?>
-
-$( function() {
-
-  // Boutons supplémentaires
-  $('\
-  <div id="js_controls">\
-    <div id="aide_js">\
-      <h4>Aide et explications <span></span> <input type="button" id="js_preview" value="Prévisualiser le texte"></h4>\
-      <p>Le texte doit être écrit en HTML&nbsp;: toute mise en forme est indiquée par deux balises, de début comme &lt;h3&gt; pour un gros titre, de fin comme &lt;/h3&gt;. Les boutons ci-dessus aident à «&nbsp;encadrer&nbsp;» un texte sélectionné des balises correctes.</p>\
-      <p>Comme en HTML, les retours à la ligne et les espaces multiples ne comptent pas. Un retour à la ligne dans un paragraphe (&lt;p&gt;Bla bla&lt;/p&gt;) ne crée pas un retour à la ligne visible au final. Pour ce faire, on peut utiliser la balise unique &lt;br&gt;, mais il vaut mieux mettre chaque paragraphe entre balises &lt;p&gt; et &lt;/p&gt;. Les espaces en début de ligne, de même, n\'apparaissent pas sur le site public et ne servent qu\'à la lisibilité ici.</p>\
-      <p class="tex2jax_ignore">Il est possible d\'insérer du code en LaTeX, sur une ligne séparée (balises \\[...\\] ou balises $$...$$) ou au sein d\'une phrase (balises $...$). Il faut ensuite taper du code en LaTeX à l\'intérieur. La prévisualisation est impossible&nbsp;: il faut valider le texte et aller voir le site public pour vérifier ce que l\'on a tapé.</p>\
-    </div>\<?php echo $reps; ?>
-    <p class="boutons">\
-      <input type="button" id="js_ligne" value="Paragraphe" title="Transformer la sélection en paragraphe">\
-      <input type="button" id="js_note" value="Par. important" title="Transformer la sélection en paragraphe important, qui s\'affichera en rouge. Seul le texte entre les balises est affiché, soit &lt;div class=&rdquo;note&rdquo;&gt;TEXTE&lt;/div&gt;">\
-      <input type="button" id="js_annonce" value="Par. très important" title="Transformer la sélection en paragraphe très important, qui s\'affichera en rouge et encadré. Seul le texte entre les balises est affiché, soit &lt;div class=&rdquo;annonce&rdquo;&gt;TEXTE&lt;/div&gt;">\
-      <input type="button" id="js_lien" value="Lien" title="Créer un lien : le titre est entre les balises, l\'adresse entre les guillemets après « href », soit &lt;a href=&rdquo;ADRESSE&rdquo;>TITRE</a>">\
-      <input type="button" id="js_gras" value="Gras" title="Mettre la sélection en gras">\
-      <input type="button" id="js_it" value="Italique" title="Mettre la sélection en italique">\
-      <input type="button" id="js_exp" value="Exposant" title="Mettre la sélection en exposant">\
-      <input type="button" id="js_ind" value="Indice" title="Mettre la sélection en indice">\
-    </p>\
-    <p class="boutons">\
-      <input type="button" id="js_h3" value="Titre de type chapitre (h3)" title="Insérer un titre « grand »">\
-      <input type="button" id="js_h4" value="Titre de type «&nbsp;I.&nbsp;» (h4)" title="Insérer un titre « premier niveau »">\
-      <input type="button" id="js_h5" value="Titre de type «&nbsp;1.&nbsp;» (h5)" title="Insérer un titre « deuxième niveau »">\
-      <input type="button" id="js_h6" value="Titre de type «&nbsp;a.&nbsp;» (h6)" title="Insérer un titre « troisième niveau »">\
-    </p>\
-    <p class="boutons">\
-      <input type="button" id="js_colle" value="Titre de prog. de colles (h4)" title="Insérer le titre d\'une sous-partie d\'un programme de colles.">\
-      <input type="button" id="js_ul" value="Liste à puces" title="Insérer une liste à puces (chaque &lt;li&gt; est une puce)">\
-      <input type="button" id="js_latex" value="Phrase en LaTeX" title="Insérer une ligne d\'équation en LaTeX - voir l\'aide pour les détails">\
-    </p>\
-    <p class="boutons">\
-      <input type="button" id="js_s1" value="(HTML) Insérer le caractère">&nbsp;<select id="select1"></select>\
-      <input type="button" id="js_s2" value="(HTML) Insérer la lettre">&nbsp;<select id="select2"></select></p>\
-  </div>').appendTo('body').toggle();
-
-  // Symboles
-  var symboles = [ '&forall;', '&exist;', '&part;', '&nabla;', '&prod;', '&sum;', '&plusmn;', '&radic;', '&infin;', '&int;', '&prop;', '&sim;', '&cong;', '&asymp;', '&ne;', '&equiv;', '&le;', '&ge;', '&sub;', '&sup;', '&nsub;', '&sube;', '&supe;', '&isin;', '&notin;', '&ni;', '&oplus;', '&otimes;', '&sdot;', '&and;', '&or;', '&cap;', '&cup;', '&real;', '&image;', '&empty;', '&deg;', '&prime;', '&micro;', '&larr;', '&uarr;', '&rarr;', '&darr;', '&harr;', '&lArr;', '&uArr;', '&rArr;', '&dArr;', '&hArr;' ];
-  for (var i=0, n=symboles.length, s=''; i<n; i++) {
-     s = symboles[i];
-     $('#select1').append('<option value="'+s+'">'+s+'</option>');
-  }
-  symboles = [ '&alpha;', '&beta;', '&gamma;', '&Delta;', '&delta;', '&epsilon;', '&eta;', '&Theta;', '&theta;', '&Lambda;', '&lambda;', '&mu;', '&nu;', '&xi;', '&Pi;', '&pi;', '&rho;', '&Sigma;', '&sigma;', '&tau;', '&upsilon;', '&Phi;', '&phi;', '&Psi;', '&psi;', '&Omega;', '&omega;' ];
-  for (i=0, n=symboles.length; i<n; i++) {
-     s = symboles[i];
-     $('#select2').append('<option value="'+s+'">'+s+'</option>');
-  }
-
-  // Aide
-  $('#aide_js h4 span').css('cursor','pointer').click( function () {
-    $(this).parent().parent().find('p').toggle();
-    $(this).text($(this).text() == '[déplier]' ? '[replier]' : '[déplier]');
-  });
-  $('#aide_js').find('p').hide();
-
-  // Le focus sur un textarea fait apparaître les boutons au bon endroit
-  $('textarea:not(#bandeau)').focus( function() {
-    $('#active').prop('id','');
-    $(this).prop('id','active');
-    $('#js_controls').detach().appendTo($(this).parent()).show();
-    $('#aide_js').find('p').hide();
-    $('#aide_js h4 span').text('[déplier]');
-  });
-
-  // Prévisualisation
-  $('<div id="preview_fond"></div>').appendTo('body');
-  $('<div class="item admin" id="preview"><h2>Prévisualisation</h2><div></div><p>Cliquer n\'importe où pour revenir à l\'édition</p></div>').appendTo('body');
-  $('#preview_fond,#preview').hide().click(function() { $('#preview_fond,#preview').hide(); });
-  $('#js_preview').click(function() {
-    $('#preview>div').html($('#active').val());
-    $('#preview_fond,#preview').show();
-    MathJax.Hub.Queue(["Typeset",MathJax.Hub,"preview"]);
-    
-  });
-  
-<?php
-  if ( !empty($docs) )  {
-    echo '
-  // Modification des documents en fonction du répertoire choisi
-  $(\'#reps\').change( function() {
-    switch ($(this).val())  {';
-    foreach ( $docs as $rep => $select )  {
-      echo "\n      case '$rep': $('#doc').html('$select'); break;";
-    }
-    echo '
-    }
-  });';
-}
-?>
-  
-  // Fonction d'insertion dans la textarea
-  function insert(debut,fin) {
-    var ta = $('#active')[0];
-    if (document.selection) {
-      ta.focus();
-      sel = document.selection.createRange();
-      sel.text = debut+sel.text+fin;
-      ta.focus();
-    }
-    else if (ta.selectionStart || ta.selectionStart == '0') {
-      var d = ta.selectionStart;
-      var f = ta.selectionEnd;
-      var scrollTop = ta.scrollTop;
-      ta.value = ta.value.substring(0,d)+debut+ta.value.substring(d,f)+fin+ta.value.substring(f,ta.value.length);
-      ta.focus();
-      ta.selectionStart = d + debut.length;
-      ta.selectionEnd = f + debut.length;
-      ta.scrollTop = scrollTop;
-    } else {
-      ta.value += debut+fin;
-      ta.focus();
-    }
-  }
-  
-  // Actions d'insertion de balises
-  $('#js_doc').click(function() { insert('\u000A<p><a href="download?id='+$('#doc').val()+'">'+$('#doc option:selected').text()+'</a></p>',''); });
-  $('#js_ligne').click(function() { insert('<p>','</p>'); });
-  $('#js_lien').click(function() { insert('<a href="">','</a>'); });
-  $('#js_note').click(function() { insert('\u000A<div class="note">','</div>'); });
-  $('#js_annonce').click(function() { insert('\u000A<div class="annonce">','</div>'); });
-  $('#js_gras').click(function() { insert('<strong>','</strong>'); });
-  $('#js_it').click(function() { insert('<em>','</em>'); });
-  $('#js_exp').click(function() { insert('<sup>','</sup>'); });
-  $('#js_ind').click(function() { insert('<sub>','</sub>'); });
-  $('#js_h3').click(function() { insert('\u000A<h3>Chapitre ','</h3>'); });
-  $('#js_h4').click(function() { insert('\u000A<h4>I. ','</h4>'); });
-  $('#js_h5').click(function() { insert('\u000A<h5>1. ','</h5>'); });
-  $('#js_h6').click(function() { insert('\u000A<h6>a. ','</h6>'); });
-  $('#js_colle').click(function() { insert('\u000A<h4>','</h4>'); });
-  $('#js_ul').click(function() { insert('\u000A<ul>\u000A  <li>','</li>\u000A  <li></li>\u000A</ul>'); });
-  $('#js_latex').click(function() { insert('\u000A\\[','\\]'); });
-  $('#js_s1').click(function() { insert($('#select1').val(),''); });
-  $('#js_s2').click(function() { insert($('#select2').val(),''); });
-
-});
diff -urN cahier-de-prepa3.2.0/utilisateurs.php cahier-de-prepa4.0.0/utilisateurs.php
--- cahier-de-prepa3.2.0/utilisateurs.php	2013-12-27 14:44:39.506746072 +0100
+++ cahier-de-prepa4.0.0/utilisateurs.php	1970-01-01 01:00:00.000000000 +0100
@@ -1,184 +0,0 @@
-<?php
-// Sécurité et récupération des données de configuration
-// Mode admin obligatoire (OK=2)
-define('OK',2);
-include('debut.php');
-
-///////////////////
-// Modifications //
-///////////////////
-if ( isset($_REQUEST['nom']) )  {
-  // Connexion à la base de données
-  $mysqli = new mysqli($serveur,"$base-adm",$mdp,$base);
-  $mysqli->set_charset('utf8');
-
-  // Sauvegarde de la table contenant les données
-  sauvegarde_mysql('utilisateurs');
-
-  // Validation de la matière et de l'identifiant éventuels
-  $matiere = $id = 0;
-  if ( isset($_REQUEST['matiere']) && is_numeric($_REQUEST['matiere']) )  {
-    $resultat = $mysqli->query("SELECT id FROM matieres WHERE id = ${_REQUEST['matiere']}");
-    if ( $resultat->num_rows )  {
-      $matiere = $_REQUEST['matiere'];
-      $resultat->free();
-    }
-  }
-  if ( isset($_REQUEST['id']) && is_numeric($_REQUEST['id']) )  {
-    // type = 0 si professeur, 1 si élève
-    $resultat = $mysqli->query("SELECT nom, mdp, !matieres AS type FROM utilisateurs WHERE id = ${_REQUEST['id']}");
-    if ( $resultat->num_rows )  {
-      $id = $_REQUEST['id'];
-      $r = $resultat->fetch_assoc();
-      $resultat->free();
-    }
-  }
-  
-  // Suppression
-  if ( ( $id ) && isset($_REQUEST['supprime']) )
-    $message = ( $mysqli->query("DELETE FROM utilisateurs WHERE id = $id")
-    ) ? "L'utilisateur <em>${r['nom']}</em> a bien été supprimé." : "L'utilisateur <em>${r['nom']}</em> n'a pas pu être supprimé. Erreur MySQL n°".$mysqli->errno.', «'.$mysqli->error.'».';
-  // Modification ou ajout
-  elseif ( strlen($nom = $_REQUEST['nom']) && strlen($mdp1 = $_REQUEST['mdp1']) )  {
-    $n = ( $id ) ? $r['nom'] : $nom;
-    // Vérification que le nouveau nom n'existe pas
-    $nom = $mysqli->real_escape_string($nom);
-    $resultat = $mysqli->query("SELECT id FROM utilisateurs WHERE nom = '$nom' AND id != $id");
-    if ( $resultat->num_rows )  {
-      $message = "L'utilisateur <em>$n</em> n'a pas pu être enregistré&nbsp;: un autre utilisateur porte déjà le nom demandé.";
-      $resultat->free();
-    }
-    // Vérification de la concordance des mots de passe
-    elseif ( $mdp1 != $_REQUEST['mdp2'] )
-      $message = "L'utilisateur <em>$n</em> n'a pas pu être enregistré&nbsp;: les deux mots de passe donnés sont différents.";
-    else  {
-      $mdp1 = $mysqli->real_escape_string($mdp1);
-      // Modification
-      if ( $id )  {
-        // Vérification du mot de passe actuel (seulement pour les utilisateurs de type professeur)
-        if ( ( !$r['type'] ) && ( sha1($_REQUEST['mdp0']) != $r['mdp'] ) )
-          $message = "L'utilisateur <em>$n</em> n'a pas pu être enregistré&nbsp;: le mot de passe actuel est incorrect.";
-        else
-          $message = ( $mysqli->query("UPDATE utilisateurs SET nom = '$nom', mdp = SHA1('$mdp1') WHERE id = $id")
-          ) ? "Les données de l'utilisateur <em>$n</em> ont bien été modifiées." : "Les données de l\'utilisateur <em>$n</em> n'ont pas pu être modifiées. Erreur MySQL n°".$mysqli->errno.', «'.$mysqli->error.'».';
-      }
-      // Ajout
-      else
-        $message = ( $mysqli->query("INSERT INTO utilisateurs SET nom = '$nom', mdp = SHA1('$mdp1'), matieres = '$matiere', protection = 0")
-        ) ? "L'utilisateur <em>$n</em> a bien été enregistré." : "L'utilisateur <em>$n</em> n'a pas pu être enregistré. Erreur MySQL n°".$mysqli->errno.', «'.$mysqli->error.'».';
-    }
-  }
-  
-  $mysqli->close();
-}
-
-//////////
-// HTML //
-//////////
-// Haut de page, menu et message
-$p = "utilisateurs$urladmin2";
-$t = 'Modifications des utilisateurs';
-$mysqli = new mysqli($serveur,$base,$mdp,$base);
-$mysqli->set_charset('utf8');
-include('haut.php');
-
-// Récupération des matières
-$resultat = $mysqli->query('SELECT id, nom FROM matieres');
-$select_matieres = '';
-while ( $r = $resultat->fetch_assoc() )
-  $select_matieres .= "        <option value=\"${r['id']}\">${r['nom']}</option>\n";
-$resultat->free();
-
-// Aide générale et formulaires
-?>
-
-  <div class="item aide">
-    <h3>Aide et explications</h3>
-    <p>Vous pouvez ci-dessous ajouter un utilisateur, et modifier les utilisateurs existants. Les utilisateurs sont séparés en deux types&nbsp;:</p>
-    <ul>
-      <li><em>professeur</em>, qui est associé à une matière et permet d'accéder à l'interface d'administration</li>
-      <li><em>élève</em>, qui permet uniquement de visualiser les pages et documents qui seraient protégés par une <em>demande d'identification pour visualiser...</em>. Ce choix est à régler individuellement sur chaque page d'informations, individuellement pour chaque répertoire/documents, et dans vos <a href="prefs">préférences</a> pour les programmes de colles/cahier de texte de vos matières associées.</li>
-    </ul>
-    <p>Une même matière peut être associée à plusieurs utilisateurs de type professeur et chaque professeur peut avoir éventuellement plusieurs matières associées. Ce choix s'effectue dans vos <a href="prefs">préférences</a>.</p>
-    <p>L'identifiant ne sert que pour se connecter, il n'est pas accessible aux élèves. Les accents ne posent pas de problème. Un prénom est une bonne idée d'identifiant pour les utilisateurs de type professeur.</p>
-    <p>Les mots de passe sont chiffrés avant d'être stockés dans la base de données&nbsp;: il n'y a donc aucun souci sur leur confidentialité. Même l'administrateur du site, l'hébergeur ou d'éventuels pirates ne pourront jamais y avoir accès. Lors de la connexion, le mot de passe entré est chiffré à son tour et ce sont les deux chiffrements qui sont comparés. Attention&nbsp;: il est très dangereux que ce mot de passe soit évident. Un bon mot de passe est un mot de passe d'au moins 8 caractères contenant des lettres, des chiffres et au moins un symbole parmi «&nbsp;?&nbsp;;&nbsp;:&nbsp;!&nbsp;.&nbsp;,-&nbsp;_&nbsp;». Mais vous faîtes ce que vous voulez...</p>
-    <p>Pour toutes les modifications d'identifiant ou de mot de passe d'utilisateurs de type professeur, le mot de passe actuel est nécessaire. Il n'est pas nécessaire par contre pour la suppression, afin que le nettoyage des utilisateurs inactifs reste possible depuis l'interface d'administration.</p>
-    <p>Ajouter ou supprimer des utilisateurs ne modifie jamais la liste des matières. Pour ajouter ou supprimer des matières, il faut aller sur <a href="matieres">la page correspondante</a>.</p>
-  </div>
-
-  <div class="item admin">
-  <form action="" method="post" id="ajoute_professeur">
-    <input class="bouton" type="submit" name="ajoute" value="Valider" title="Valider les modifications">
-    <h3>Créer un nouvel utilisateur (professeur)</h3>
-    <p class="ligne"><label for="nom">Nom&nbsp;: </label><input type="text" id="nom" name="nom" value="" size="50"></p>
-    <p class="ligne"><label for="mdp1_p">Mot de passe&nbsp;: </label><input type="password" id="mdp1_p" name="mdp1" value=""></p>
-    <p class="ligne"><label for="mdp2_p">Confirmation&nbsp;: </label><input type="password" id="mdp2_p" name="mdp2" value=""></p>
-    <p class="ligne"><label for="matiere">Matière&nbsp;: </label>
-      <select  id="matiere" name="matiere">
-<?php echo $select_matieres; ?>
-      </select>
-    </p>
-    <p>La(les) matière(s) associée(s) à cet utilisateur seront modifiables par lui-même une fois qu'il se sera connecté, dans ses <em>préférences</em>. Il est possible de créer une matière <a href="matieres">ici</a>.</p>
-  </form>
-  </div>
-
-  <script type="text/javascript">
-$( function() {
-  // Ajout d'un générateur de mot de passe
-  $('#ajoute_professeur').append('<p><input type="button" value="Générer un mot de passe" id="generer_mdp">&nbsp;<span></span></p>');
-  $('#generer_mdp').click( function () {
-    var c = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789,;:!?.';
-    var p = '';
-    for ( var i = 0 ; i < 8 ; i++ )
-      p += c.charAt(1 + Math.floor(Math.random()*68));
-    $(this).next('span').html("Valeur recopiée&nbsp;:&nbsp;<code>"+p+"</code>");
-    $('#mdp1_p,#mdp2_p').val(p);
-  });
-});
-  </script>
-
-  <div class="item admin">
-  <form action="" method="post">
-    <input class="bouton" type="submit" name="ajoute" value="Valider" title="Valider les modifications">
-    <h3>Créer un nouvel utilisateur (élèves)</h3>
-    <p class="ligne"><label for="nom">Nom&nbsp;: </label><input type="text" id="nom" name="nom" value="" size="50"></p>
-    <p class="ligne"><label for="mdp1_e">Mot de passe&nbsp;: </label><input type="password" id="mdp1_e" name="mdp1" value=""></p>
-    <p class="ligne"><label for="mdp2_e">Confirmation&nbsp;: </label><input type="password" id="mdp2_e" name="mdp2" value=""></p>
-  </form>
-  </div>
-
-<?php
-// Récupération des utilisateurs
-$resultat = $mysqli->query('SELECT u.id, u.nom, GROUP_CONCAT(m.nom SEPARATOR \'</em>, <em>\') AS mats FROM utilisateurs AS u LEFT JOIN matieres AS m ON FIND_IN_SET(m.id,u.matieres) GROUP BY u.nom ORDER BY !u.matieres,u.nom,m.id');
-while ( $r = $resultat->fetch_assoc() )  {
-  $id = $r['id'];
-  if ( $r['mats'] )  {
-    $r['mats'] = ( strpos($r['mats'],'<em>') ) ? "\n    <p>Utilisateur de type professeur, associé aux matières <em>${r['mats']}</em></p>" : "\n    <p>Utilisateur de type professeur, associé à la matière <em>${r['mats']}</em></p>" ;
-    $mdp = "\n    <p class=\"ligne\"><label for=\"mdp0_$id\">Mot de passe actuel&nbsp;: </label><input type=\"password\" id=\"mdp0_$id\" name=\"mdp0\" value=\"\"></p>";
-  }
-  else  {
-    $r['mats'] = "\n    <p>Utilisateur de type élève</p>";
-    $mdp = '';
-  }
-  echo <<<FIN
-  <div class="item admin">
-  <form action="" method="post">
-    <input class="bouton" type="submit" name="modifie" value="Valider" title="Valider les modifications">
-    <input class="bouton" type="submit" name="supprime" value="Supprimer" title="Supprimer l'utilisateur">
-    <h3>Modifier l'utilisateur <em>${r['nom']}</em></h3>${r['mats']}
-    <p class="ligne"><label for="nom">Nom&nbsp;: </label><input type="text" id="nom" name="nom" value="${r['nom']}" size="50"></p>$mdp
-    <p class="ligne"><label for="mdp1_$id">Mot de passe&nbsp;: </label><input type="password" id="mdp1_$id" name="mdp1" value=""></p>
-    <p class="ligne"><label for="mdp2_$id">Confirmation&nbsp;: </label><input type="password" id="mdp2_$id" name="mdp2" value=""></p>
-    <input type="hidden" name="id" value="$id">
-  </form>
-  </div>
-
-
-FIN;
-}
-$resultat->free();
-$mysqli->close();
-
-// Bas de page
-include('bas.php');
-?>
