diff -urN cahier-de-prepa4.0.5/admin/docs.php cahier-de-prepa4.1.0/admin/docs.php
--- cahier-de-prepa4.0.5/admin/docs.php	2014-09-20 15:05:44.805288387 +0200
+++ cahier-de-prepa4.1.0/admin/docs.php	2014-10-23 22:08:14.109338320 +0200
@@ -103,7 +103,7 @@
       $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;
+    $protection = ( in_array($_REQUEST['protection'],array(0,1,2,3,4,5)) ) ? $_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.";
@@ -144,7 +144,7 @@
   // 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;
+    $protection = ( in_array($_REQUEST['protection'],array(0,1,2,3,4,5)) ) ? $_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),
@@ -154,10 +154,10 @@
 
   // 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),
+    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<5) 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)');
+    nbdoc_v = (SELECT COUNT(id) FROM docs AS d WHERE r.id = d.parent AND d.protection<5)');
   $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)');
@@ -216,7 +216,7 @@
       }
 
       // Modification de l'accès
-      $protection = ( in_array($_REQUEST['protection'],array(0,1,2,3,4)) ) ? $_REQUEST['protection'] : 0;
+      $protection = ( in_array($_REQUEST['protection'],array(0,1,2,3,4,5)) ) ? $_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.'».';
@@ -235,7 +235,7 @@
       
       // Si modification(s), on met à jour les informations récentes éventuelles
       if ( strlen($message) )  {
-        if ( $protection < 4 )  {
+        if ( $protection < 5 )  {
           $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();
@@ -243,7 +243,7 @@
           $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 )
+          if ( $doc['protection'] == 5 )
             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 lors d'une nouvelle information récente
@@ -251,7 +251,7 @@
           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.
+        // Si $protection = 5, on cherche à supprimer.
         else
           recent($mysqli,3,$id);
       }
@@ -267,7 +267,7 @@
       // Suppression physique
       exec("rm -rf ../documents/${doc['lien']}");
       // Mise à jour des informations récentes
-      if ( $doc['protection'] < 4 )
+      if ( $doc['protection'] < 5 )
         recent($mysqli,3,$id);
     }
     else
@@ -289,7 +289,7 @@
         // 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 )  {
+        if ( $doc['protection'] < 5 )  {
           $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();
@@ -313,7 +313,7 @@
     $ext = ( strpos($nom,'.') ) ? substr(strrchr($nom,'.'),0,4) : '';
     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;
+    $protection = ( in_array($_REQUEST['protection'],array(0,1,2,3,4,5)) ) ? $_REQUEST['protection'] : 0;
     // Création du répertoire particulier
     $lien = substr(sha1(mt_rand()),0,15);
     while ( is_dir("../documents/$lien") )
@@ -337,7 +337,7 @@
         $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 )  {
+        if ( $protection < 5 )  {
           $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();
@@ -360,9 +360,9 @@
   // Mise à jour des champs nbrep/nbdoc dans la table 'reps'
   $mysqli->query('UPDATE reps AS r SET
     nbrep = IFNULL((SELECT n FROM (SELECT COUNT(id) AS n, parent FROM reps GROUP BY parent) AS r1 WHERE r.id = r1.parent),0),
-    nbrep_v =IFNULL((SELECT n FROM (SELECT COUNT(id) AS n, parent FROM reps WHERE reps.protection<4 GROUP BY parent) AS r1 WHERE r.id =r1.parent),0),
+    nbrep_v =IFNULL((SELECT n FROM (SELECT COUNT(id) AS n, parent FROM reps WHERE reps.protection<5 GROUP BY parent) AS r1 WHERE r.id =r1.parent),0),
     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)');
+    nbdoc_v = (SELECT COUNT(id) FROM docs AS d WHERE r.id = d.parent AND d.protection<5)');
   $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)');
@@ -436,7 +436,7 @@
           $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)' : ')';
+        $contenu .= ( $r['protection'] == 5 ) ? '&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
 
@@ -472,16 +472,17 @@
       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 ) )
+        if ( $r['protection'] && ( $r['protection'] < 5 ) )
           $icone .= '-lock';
-        $cache = ( $r['protection'] == 4 ) ? ' (non visible)' : '';
+        $cache = ( $r['protection'] == 5 ) ? ' (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>");
+$indent        <option value=\"1\">Visible pour les connectés</option>
+$indent        <option value=\"2\">Visible pour les élèves, colleurs, profs</option>
+$indent        <option value=\"3\">Visible pour les colleurs et les profs</option>
+$indent        <option value=\"4\">Visible pour les profs uniquement</option>
+$indent        <option value=\"5\">Non visible</option>");
         echo <<<FIN
 
 $indent<div class="fic admin">
@@ -494,7 +495,7 @@
 $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="nomdoc$id">Nom à afficher&nbsp;: </label><input type="text" id="nomdoc$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>
@@ -544,10 +545,11 @@
 // 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>');
+        <option value="1">Visible pour les connectés</option>
+        <option value="2">Visible pour les élèves, colleurs, profs</option>
+        <option value="3">Visible pour les colleurs et les profs</option>
+        <option value="4">Visible pour les profs uniquement</option>
+        <option value="5">Non visible</option>');
 
   // HTML
 ?>
@@ -566,10 +568,11 @@
     <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>
+    <p>Vous pouvez modifier l'<em>accès</em> du document parmi six 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 connectés</em>&nbsp;: document accessible de tout utilisateur mais uniquement après identification (compte invité, compte élève, compte colleur ou compte professeur).</li>
+      <li><em>Visible pour les élèves, colleurs, professeurs</em>&nbsp;: document accessible uniquement par les utilisateurs de types élève, colleur ou professeur. Il faut donc avoir un compte personnel 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>
@@ -580,10 +583,11 @@
     <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>
+    <p>Vous pouvez modifier l'<em>accès</em> du répertoire parmi six 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 de tous</em>&nbsp;: répertoire accessible de tout visiteur, sans identification.</li>
+      <li><em>Visible pour les connectés</em>&nbsp;: répertoire accessible de tout utilisateur mais uniquement après identification (compte invité, compte élève, compte colleur ou compte professeur).</li>
+      <li><em>Visible pour les élèves, colleurs, professeurs</em>&nbsp;: répertoire accessible uniquement par les utilisateurs de types élève, colleur ou professeur. Il faut donc avoir un compte personnel 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>
@@ -594,10 +598,11 @@
     <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>
+    <p>Vous pouvez choisir l'<em>accès</em> du document parmi six 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 connectés</em>&nbsp;: document accessible de tout utilisateur mais uniquement après identification (compte invité, compte élève, compte colleur ou compte professeur).</li>
+      <li><em>Visible pour les élèves, colleurs, professeurs</em>&nbsp;: document accessible uniquement par les utilisateurs de types élève, colleur ou professeur. Il faut donc avoir un compte personnel 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>
@@ -606,10 +611,11 @@
     <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>
+    <p>Vous pouvez définir l'<em>accès</em> du sous-répertoire parmi six 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 connectés</em>&nbsp;: répertoire accessible de tout utilisateur mais uniquement après identification (compte invité, compte élève, compte colleur ou compte professeur).</li>
+      <li><em>Visible pour les élèves, colleurs, professeurs</em>&nbsp;: répertoire accessible uniquement par les utilisateurs de types élève, colleur ou professeur. Il faut donc avoir un compte personnel 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>
diff -urN cahier-de-prepa4.0.5/admin/groupes.php cahier-de-prepa4.1.0/admin/groupes.php
--- cahier-de-prepa4.0.5/admin/groupes.php	1970-01-01 01:00:00.000000000 +0100
+++ cahier-de-prepa4.1.0/admin/groupes.php	2014-10-23 23:30:59.541497214 +0200
@@ -0,0 +1,200 @@
+<?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 élèves
+$resultat = $mysqli->query('SELECT id, CONCAT(nom,\' \',prenom) AS nom_complet, nom FROM utilisateurs WHERE autorisation = 2 ORDER BY nom_complet');
+// Impossible d'aller plus loin si aucun élève existe
+if ( !$resultat->num_rows )  {
+  $p = "groupes";
+  $t = 'Modification des groupes de colles';
+  $message = 'Aucun élève n\'existe dans la base de données. Vous devez commencer par renseigner leur existence, soit vous-même via la page <a href="utilisateurs">Utilisateurs</a>, soit en leur disant de se connecter individuellement sur la partie publique (<em>Se connecter</em> puis <em>Créer un compte</em>).';
+  include('haut.php');
+  include('bas.php');
+  exit();
+}
+$eid = array();
+$select_eleves = <<<FIN
+    <p id="ligneXX_YY" class="ligne"><label for="eleveXX_YY">Élève n°XX&nbsp;:</label>
+      <select id="eleveXX_YY" name=eleves[]>
+        <option value="0">[Choisir un élève]</option>
+
+FIN;
+while ( $r = $resultat->fetch_assoc() )  {
+  $eid[$r['nom']] = $r['id'];
+  $select_eleves .= "        <option value=\"${r['id']}\">${r['nom_complet']}</option>\n";
+}
+$resultat->free();
+$select_eleves .=  "      </select>\n    </p>\n";
+
+////////////////////////////////////
+// Modifications des informations //
+////////////////////////////////////
+if ( isset($_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 information)
+  if ( is_numeric($id = $_REQUEST['id']) && $id )  {
+    $resultat = $mysqli->query("SELECT nom, eleves FROM groupes WHERE id = $id");
+    if ( $resultat->num_rows )  {
+      $r = $resultat->fetch_assoc();
+      $resultat->free();
+    }
+    else
+      $id = 0;
+  }
+  else
+    $id = 0;
+
+  // Traitement d'un ajout/modification
+  if ( isset($_REQUEST['modifie']) )  {
+    if ( !strlen($_REQUEST['nom']) )
+      $message = 'Il n\'est pas possible de valider un nom de groupe vide. Pour supprimer un groupe, il faut cliquer sur Supprimer.';
+    else  {
+      // Validation des données envoyées
+      $nom = $mysqli->real_escape_string($_REQUEST['nom']);
+      $eleves = implode(',',array_intersect($eid,$_REQUEST['eleves']));
+      $colle = ( isset($_REQUEST['colle']) ) ? 1 : 0;
+      // Modification de $nom : on remplace tous les nombres par leur écriture
+      // sur 6 chiffres, complété par des zéros à gauche.
+      // Cf fonction zpad() dans docs.php
+      $nom_nat = preg_replace_callback('/(\d+)/', function($m)  { return(str_pad($m[1],6,'0',STR_PAD_LEFT)); }, $nom);
+
+      // Si $id > 0 : modification d'un groupe existant
+      if ( $id )
+        $message = ( requete('groupes',"UPDATE groupes SET nom = '$nom', nom_nat = '$nom_nat', colle = $colle, eleves = '$eleves' WHERE id = $id") ) ? "Le groupe $nom a bien été modifié." : "Le groupe $nom n'a pas pu être modifié. Erreur MySQL n°".$mysqli->errno.', «'.$mysqli->error.'».';
+      // Sinon : ajout d'un nouveau groupe
+      else
+        $message = ( requete('groupes',"INSERT INTO groupes SET nom = '$nom', nom_nat = '$nom_nat', eleves = '$eleves'") ) ? "Le groupe $nom a bien été ajouté." : "Le groupe $nom n'a pas pu être ajouté. Erreur MySQL n°".$mysqli->errno.', «'.$mysqli->error.'».';
+
+      // Mise en ordre alphabétique naturel
+      $mysqli->query('ALTER TABLE groupes ORDER BY nom_nat');
+    }
+  }
+
+  // Suppression d'un groupe
+  elseif ( $id && isset($_REQUEST['supprime']) )
+    $message = ( requete('groupes',"DELETE FROM groupes WHERE id = $id") ) ? "Le groupe ${r['nom']} a bien été supprimé." : "Le groupe ${r['nom']} 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 //
+//////////
+// Haut de page, menu et message
+$p = "groupes";
+$t = 'Modification des groupes d\'élèves';
+include('haut.php');
+?>
+
+  <div class="item aide">
+    <h3>Aide et explications</h3>
+    <p>Vous pouvez ci-dessous ajouter et modifier des groupes d'élèves. Ces groupes sont utilisés à deux endroits&nbsp;:</p>
+    <ul>
+      <li>l'envoi de mails (professeurs et colleurs)</li>
+      <li>la saisie de notes de colles (professeurs et colleurs)</li>
+    </ul>
+    <p>Les groupes d'élèves ne se limitent pas obligatoirement qu'aux groupes de colles&nbsp;: il peut être notamment intéressant de créer les deux demi-groupes de la classe ou les groupes de LV2 par exemple, pour envoyer des mails à la seule partie concernée de la classe.</p>
+    <h4>Préférences associées à chaque groupe</h4>
+    <p>Le <em>nom</em> est ce qui sera affiché lors de l'utilisation de ce groupe. Il peut s'agir d'un simple numéro (1,2,3...) pour des groupes de colles, d'une lettre ou d'un mot pour des demi-groupes par exemple (A et B, impairs et pairs...), ou encore d'un nom plus long (&laquo;&nbsp;LV2 Espagnol&nbsp;&raquo;).</p>
+    <p>Si la case à cocher <em>Ce groupe est un groupe de colle</em> est cochée, le groupe apparaîtra à la page d'<a href="mail">Envoi de mails</a> et à la page de <a href="notes">saisie de notes</a>. Si elle n'est pas cochée, il n'apparaîtra qu'à la page d'<a href="mail">Envoi de mails</a>.</p>
+    <p>Il est possible d'inscrire autant d'élève que souhaité dans chaque groupe. Le choix <em>[Choisir un élève]</em> ne correspond à aucun élève et ne compte donc pas (il n'y a pas de &laquo;&nbsp;case vide&nbsp;&raquo;). Les élèves sont automatiquement classés par ordre alphabétique au sein du groupe. Les doublons sont automatiquement éliminés.</p>
+    <h4>Ordre des groupes</h4>
+    <p>Les groupes sont automatiquement classés par ordre alphanumérique naturel&nbsp;: les nombres sont avant les mots, et les nombres sont comptés de façon globale (&laquo;&nbsp;2&nbsp;&raquo; est plus petit que &laquo;&nbsp;10&nbsp;&raquo;).</p>
+  </div>
+
+<?php
+
+// Fonction d'affichage des infos
+function affichage_groupe($r)  {
+  if ( $id = $r['id'] )  {
+    $eid = explode(',',$r['eleves']);
+    if ( count($eid) > 5 )
+      $titre = "Groupe ${r['nom']} (".count($eid).' élèves)';
+    else
+      $titre = "Groupe ${r['nom']} (". implode(', ',array_keys(array_intersect($GLOBALS['eid'],$eid))) .')';
+    $valide1 = '';
+    $valide = "\n      <input type=\"submit\" name=\"modifie\" value=\"Valider\" title=\"Valider les modifications sur ce groupe\">";
+    $suppr = "\n      <input type=\"submit\" name=\"supprime\" value=\"Supprimer\" title=\"Supprimer ce groupe\">";
+  }
+  else  {
+    $titre = 'Ajouter un nouveau groupe';
+    $valide1 = "\n    <input class=\"bouton\" type=\"submit\" name=\"modifie\" value=\"Valider\" title=\"Valider les modifications\">";
+    $valide = $suppr = '';
+    $eid = array(0);
+  }
+  echo <<<FIN
+  <div class="item admin">
+  <form action="" method="post">$valide1
+    <h3>$titre</h3>
+    <p class="boutons">$valide$suppr
+    </p>
+    <p class="ligne"><label for="nom$id">Nom du groupe&nbsp;: </label><input type="text" id="nom$id" name="nom" value="${r['nom']}" size="50"></p>
+    <p class="ligne"><label for="colle$id">Ce groupe est un groupe de colle&nbsp;: </label><input type="checkbox" id="colle$id" name="colle" value="1"${r['colle']}></p>
+
+FIN;
+  $n = 0;
+  foreach ( $eid as $e )
+    echo str_replace(array("\"$e\"",'XX','YY'),array("\"$e\" selected",++$n,$id),$GLOBALS['select_eleves']);
+  echo <<<FIN
+    <p class="boutons">
+      <input type="button" class="ajouter" value="Ajouter un élève">
+      <input type="hidden" name="n" value="$n">
+      <input type="button" class="retirer" value="Retirer le dernier élève">
+    </p>
+    <input type="hidden" name="id" value="$id">
+  </form>
+  </div>
+
+
+FIN;
+}
+
+// Formulaire de nouveau groupe
+affichage_groupe(array('id' => 0, 'nom' => '', 'colle' => ' checked'));
+
+// Formulaires de modification des groupes
+$resultat = $mysqli->query('SELECT id, nom, IF(colle,\' checked\',\'\') AS colle, eleves FROM groupes');
+if ( $resultat->num_rows )  {
+  while ( $r = $resultat->fetch_assoc() )
+    affichage_groupe($r);
+  $resultat->free();
+}
+else
+  echo "  <h2>Il n'y a actuellement aucun groupe d'élèves défini.</h2>\n\n";
+
+$mysqli->close();
+
+?>
+
+  <script type="text/javascript">
+$( function() {
+  var select = '<?php echo str_replace(array("'","\n",'  '),array("\'",'',''),$select_eleves); ?>';
+  $('.ajouter').click(function () {
+    id = $(this).parent().next().val();
+    n = $(this).next().val();
+    $(this).next().val(++n);
+    $( select.replace(/XX/g,n).replace(/YY/g,id) ).insertBefore($(this).parent());
+  });
+  $('.retirer').click(function () {
+    id = $(this).parent().next().val();
+    n = $(this).prev().val();
+    if ( n > 1 )  {
+      $('#ligne'+n+'_'+id).remove();
+      $(this).prev().val(--n);
+    }
+  });
+
+});
+  </script>
+
+<?php
+// Bas de page
+include('bas.php');
+?>
diff -urN cahier-de-prepa4.0.5/admin/haut.php cahier-de-prepa4.1.0/admin/haut.php
--- cahier-de-prepa4.0.5/admin/haut.php	2014-08-25 00:02:35.203669236 +0200
+++ cahier-de-prepa4.1.0/admin/haut.php	2014-10-22 11:23:40.693335955 +0200
@@ -108,7 +108,7 @@
       <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="groupes">Les groupes d'élèves</a>
       <a href="matieres">Les matières</a>
       <a href="planning">Le planning annuel</a>
     </div>
diff -urN cahier-de-prepa4.0.5/admin/index.php cahier-de-prepa4.1.0/admin/index.php
--- cahier-de-prepa4.0.5/admin/index.php	2014-08-25 18:26:33.585788872 +0200
+++ cahier-de-prepa4.1.0/admin/index.php	2014-10-23 22:07:45.029337389 +0200
@@ -57,10 +57,11 @@
     <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>
+    <p>Vous pouvez choisir l'<em>accès</em> du document parmi six 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 connectés</em>&nbsp;: document accessible de tout utilisateur mais uniquement après identification (compte invité, compte élève, compte colleur ou compte professeur).</li>
+      <li><em>Visible pour les élèves, colleurs, professeurs</em>&nbsp;: document accessible uniquement par les utilisateurs de types élève, colleur ou professeur. Il faut donc avoir un compte personnel 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>
@@ -166,10 +167,11 @@
     <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>
+        <option value="1">Visible pour les connectés</option>
+        <option value="2">Visible pour les élèves, colleurs, profs</option>
+        <option value="3">Visible pour les colleurs et les profs</option>
+        <option value="4">Visible pour les profs uniquement</option>
+        <option value="5">Non visible</option>
       </select>
     </p>
     <input type="hidden" name="id" value="0">
diff -urN cahier-de-prepa4.0.5/admin/js.php cahier-de-prepa4.1.0/admin/js.php
--- cahier-de-prepa4.0.5/admin/js.php	2014-08-26 13:59:18.760040566 +0200
+++ cahier-de-prepa4.1.0/admin/js.php	2014-10-23 12:17:02.512203224 +0200
@@ -20,7 +20,7 @@
       $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");
+    $res = $mysqli->query("SELECT id, nom FROM docs WHERE parent = ${r['id']} AND protection < 5");
     while ( $d = $res->fetch_assoc() )  {
       $docs[$r['id']] .= "<option value=\"${d['id']}\">".addslashes($d['nom']).'</option>';
     }
@@ -49,8 +49,9 @@
 
   // Pliage des items et aides de l'interface d'administration
   // .fic => fichiers dans docs.php ; .user => nouvel utilisateur dans utilisateurs.php
+  // .notes => notes de colles dans notes-saisies.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 () {
+  $('.aide h3 span,.admin:not(.fic, .user, .nojs, .notes) h3 span').click( function () {
     $(this).parent().parent().children(':not(h3,.nonvisible)').toggle();
     $(this).text($(this).text() == 'déplier' ? 'replier' : 'déplier');
   });
diff -urN cahier-de-prepa4.0.5/admin/login.php cahier-de-prepa4.1.0/admin/login.php
--- cahier-de-prepa4.0.5/admin/login.php	2014-09-02 16:02:47.339631316 +0200
+++ cahier-de-prepa4.1.0/admin/login.php	2014-10-19 03:14:01.604101388 +0200
@@ -5,7 +5,7 @@
 // Connexion à la base MySQL et vérification de la connexion
 $mysqli = new mysqli($serveur,$base,$mdp,$base);
 if ( $mysqli->connect_errno )
-  include('installation.php');
+  include('../installation.php');
 $mysqli->set_charset('utf8');
 
 // Oubli d'identifiant/mot de passe -- copie dans /connect.php
@@ -14,7 +14,7 @@
   // Traitement du formulaire
   if ( isset($_REQUEST['mail']) && strlen($_REQUEST['mail']) )  {
     // Recherche du mail dans la base de données
-    $resultat = $mysqli->query('SELECT id, login, mail FROM utilisateurs WHERE autorisation = 3');
+    $resultat = $mysqli->query('SELECT id, login, mail FROM utilisateurs WHERE autorisation = 4');
     while ( $r = $resultat->fetch_assoc() )
       if ( $r['mail'] == $_REQUEST['mail'] )  {
         $utilisateur = $r;
@@ -88,7 +88,7 @@
 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');
+  $resultat = $mysqli->query('SELECT id, login, mdp, matieres, timeout, genre FROM utilisateurs WHERE autorisation = 4');
   if ( $resultat->num_rows )  {
     while ( $r = $resultat->fetch_assoc() )
       if ( ( $r['login'] == $_REQUEST['login'] ) && ( $r['mdp'] == sha1($_REQUEST['motdepasse']) ) )  {
@@ -123,7 +123,7 @@
   // Si pas d'utilisateur enregistré : retour au script d'installation
   else  {
     $mysqli->close();
-    include('installation.php');
+    include('../installation.php');
   }
   
   // Retour au script appelé par AJAX si c'est le cas (pas d'HTML)
diff -urN cahier-de-prepa4.0.5/admin/mail.php cahier-de-prepa4.1.0/admin/mail.php
--- cahier-de-prepa4.0.5/admin/mail.php	2014-09-19 23:44:59.135520519 +0200
+++ cahier-de-prepa4.1.0/admin/mail.php	2014-10-23 23:12:24.053461518 +0200
@@ -11,17 +11,13 @@
 
 // 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();
+  $p = "mail";
+  $t = 'Envoi de mail';
+  $message = '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é.';
+  include('haut.php');
+  include('bas.php');
+  exit();
 }
-
 // Champ expéditeur
 switch ( $u['genre'] )  {
   case 1 : $g = 'M. '; break;
@@ -41,37 +37,42 @@
 ///////////////////
 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 )
+  $resultat = $mysqli->query("SELECT id, mail, CASE autorisation WHEN autorisation = 2 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 )
+  $ids = explode(',',implode(',',$_REQUEST['dest']));
+  foreach ( $ids as $i )
     if ( isset($utilisateurs[$i]) )  {
       $dests .= $utilisateurs[$i]['nom_complet'].' <'.$utilisateurs[$i]['mail'].'>, ';
       unset($utilisateurs[$i]);
     }
-  if ( strlen($dests) )  {
-    $dests = substr($dests,0,-2);
-    $bcc = ( $u['mailcopy'] ) ? "Bcc: $exp <${u['mail']}>, " : 'Bcc: ';
-  }
+  if ( !strlen($dests) )
+    $message = 'Le mail n\'a pas été envoyé car aucun destinataire n\'a été renseigné.';
   else  {
-    $dests = "$exp <${u['mail']}>";
-    $bcc = 'Bcc: ';
+    $dests = substr($dests,0,-2);
+    $bcc = ( $u['mailcopy'] ) ? "$exp <${u['mail']}>, " : '';
+    if ( isset($_REQUEST['dest_bcc']) )
+      $ids = explode(',',implode(',',$_REQUEST['dest_bcc']));
+      foreach ( $ids as $i )
+        if ( isset($utilisateurs[$i]) )  {
+          $bcc .= $utilisateurs[$i]['nom_complet'].' <'.$utilisateurs[$i]['mail'].'>, ';
+          unset($utilisateurs[$i]);
+        }
+    $bcc = ( strlen($bcc) ) ? 'Bcc: '.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 de confirmation d'envoi
+    $n1 = substr_count($dests,'<');
+    $n2 = substr_count($bcc,'<') - $u['mailcopy'];
+    if ( $n2 )
+      $message = 'Le mail a été envoyé à '.($n1+$n2).' destinataires (dont '.$n2.' en copie cachée).';
+    else
+      $message = ( $n1 > 1 ) ? "Le mail a été envoyé à $n1 destinataires." : 'Le mail a été envoyé à un destinataire.';
   }
-  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 //
 //////////
@@ -86,25 +87,34 @@
   <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>
+    <h4>Destinataires</h4>
+    <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 destinataire.</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>Un utilisateur ne peut pas être à la fois en copie et 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>
+    <h4>Groupes d'élèves</h4>
+    <p>Il est possible de créer et de modifier des groupes d'élèves sur la page <a href="groupes">Groupes d'élèves</a>.</p>
+    <p>Une fois les groupes d'élèves créés, cocher un groupe permet d'envoyer le mail à tous les élèves du groupe ayant renseigné leur adresse mail (les élèves correspondants au groupe sont automatiquement cochés).</p>
+    <p>Le décochage d'un groupe ne décoche pas automatiquement les élèves du groupe.</p>
+    <p>Un élève appartenant à deux groupes cochés ne recevra qu'une seule fois le mail.</p>
+    <h4>Sujet et contenu du mail</h4>
+    <p>Le <em>sujet</em> est le sujet du mail. Tous les caractères sont autorisés. Pensez à envoyer des mails avec un sujet correspondant explicitement au contenu...</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 convention 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>
+    <h4>Autres réglages</h4>
+    <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, accessible dans 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, accessible dans 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");
+                            CONCAT( CASE MOD(genre,5) WHEN 1 THEN 'M. ' WHEN 2 THEN 'Mme ' WHEN 3 THEN 'Melle ' ELSE '' END, prenom, ' ', nom ) AS nom_complet
+                            FROM utilisateurs WHERE login NOT RLIKE '^tmp[0-9]{5}' AND id != ${_SESSION['id']} AND autorisation > 1 ORDER BY autorisation DESC, nom");
 ?>
   <div class="item admin nojs">
   <form action="" method="post">
@@ -119,25 +129,36 @@
   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;
+      case 2 : $t = 'Élèves'; break;
+      case 3 : $t = 'Colleurs'; break;
+      case 4 : $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><th>$t</th><td></td><td></td></tr>\n";
+  }
+  $nom = ( strlen($r['nom_complet']) > 1 ) ? $r['nom_complet'] : "<em>${r['login']}</em>";
+  if ( strlen($r['mail']) )
+    $nom .= ' (pas de mail)';
   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();
+
+// Affichage des groupes d'élèves
+$resultat = $mysqli->query('SELECT g.id, g.nom, g.eleves AS eid,
+                            GROUP_CONCAT( CONCAT(e.prenom,\' \',e.nom) ORDER BY FIND_IN_SET(e.id,g.eleves) SEPARATOR \', \') AS eleves
+                            FROM groupes AS g JOIN utilisateurs AS e ON FIND_IN_SET(e.id,g.eleves) GROUP BY g.id ORDER BY g.nom_nat');
+if ( $resultat->num_rows )  {
+  echo "        <tr><th id=\"th_groupes\">Groupes d'élèves</th><td></td><td></td></tr>\n";
+  while ( $r = $resultat->fetch_assoc() )  {
+    echo "        <tr><td>Groupe ${r['nom']}&nbsp;: ${r['eleves']}</td><td><input type=\"checkbox\" class=\"dest_c5\" name=\"dest[]\" value=\"${r['eid']}\"></td><td><input type=\"checkbox\" class=\"dest_bcc5\" name=\"dest_bcc[]\" value=\"${r['eid']}\"></td></tr>\n";
+  }
+  $resultat->free();
+}
 $mysqli->close();
 ?>
       </tbody>
     </table>
 
-  <script type="text/javascript">
+    <script type="text/javascript">
 $( function() {
   // Pliage du tableau des destinataires
   $('#dests').hide().prev('h3').append(' <span id="deplie">déplier</span>');
@@ -147,46 +168,74 @@
   });
 
   // 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">');
+  $('#dests th:not(#th_groupes)').next().append('<input type="button" class="button_c" value="Tout cocher">');
+  $('#dests th:not(#th_groupes)').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);
+      $('.dest_'+t1+a+':not(:disabled)').prop("checked",true);
+      $('.dest_'+t2+a+':not(:disabled)').prop("checked",false);
       $(this).closest('tr').find('.button_'+t2).val('Tout cocher');
     }
     else  {
       $(this).val('Tout cocher');
       $('.dest_'+t1+a).prop("checked",false);
     }
+    verif_dest();
   });
   $('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);
+    verif_dest();
+  });
+  
+  // Sélection d'un groupe
+  $('.dest_c5,.dest_bcc5').click( function() {
+    if ( $(this).is(':checked') )  {
+      var t1 = $(this).attr('class').slice(5,-1);
+      var t2 = ( t1 == 'c' ? 'bcc' : 'c' );
+      $(this).val().split(',').forEach( function(i) {
+        $('.dest_'+t1+'2[value='+i+']:not(:disabled)').prop('checked',true);
+        $('.dest_'+t2+'2[value='+i+']:not(:disabled)').prop('checked',false);
+      });
+      verif_dest();
+    }
   });
+  
+  // Vérification qu'au moins un destinataire a été sélectionné
+  function verif_dest() {
+    var a = $('#avertissement_destinataires').length;
+    var i = $('input[name=dest\\[\\]]:checked').length;
+    if ( !a && !i )  {
+      $('#dests').after('<p id="avertissement_destinataires" class="warning">Aucun destinataire n\'a été sélectionné. Il en faut un au minimum.</p>');
+      $('input[name=valide]').prop('disabled',true);
+    }
+    if ( a && i )  {
+      $('#avertissement_destinataires').remove();
+      $('input[name=valide]').prop('disabled',false);
+    }
+  }
+  verif_dest();
 });
-  </script>
+    </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>
+    <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-prepa4.0.5/admin/matieres.php cahier-de-prepa4.1.0/admin/matieres.php
--- cahier-de-prepa4.0.5/admin/matieres.php	2014-08-26 22:20:54.261003626 +0200
+++ cahier-de-prepa4.1.0/admin/matieres.php	2014-10-23 22:09:58.325341655 +0200
@@ -44,11 +44,11 @@
       $modif[] = 'clé';
     }
   }
-  if ( in_array($colles_protection = $_REQUEST['colles_protection'],array(0,1,2,3)) && ( $colles_protection != $r['colles_protection'] ) )  {
+  if ( in_array($colles_protection = $_REQUEST['colles_protection'],array(0,1,2,3,4)) && ( $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'] ) )  {
+  if ( in_array($cdt_protection = $_REQUEST['cdt_protection'],array(0,1,2,3,4)) && ( $cdt_protection != $r['cdt_protection'] ) )  {
     $requete[] = "cdt_protection = $cdt_protection";
     $modif[] = 'accès au cahier de texte';
   }
@@ -157,7 +157,7 @@
 //////////
 // Haut de page, menu et message
 $p = "matieres";
-$t = 'Modifications des matieres';
+$t = 'Modification des matieres';
 include('haut.php');
 ?>
 
@@ -171,10 +171,11 @@
       <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>
+    <p>L'<em>accès au programmes de colles</em> et l'<em>accès au cahier de texte</em> peuvent être choisis parmi cinq 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 connectés</em>&nbsp;: accessible de tout utilisateur mais uniquement après identification (compte invité, compte élève, compte colleur ou compte professeur).</li>
+      <li><em>Visible pour les élèves, colleurs, professeurs</em>&nbsp;: accessible uniquement par les utilisateurs de types élève, colleur ou professeur. Il faut donc avoir un compte personnel 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>
@@ -192,9 +193,10 @@
 // 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>';
+        <option value="1">Visible pour les connectés</option>
+        <option value="2">Visible pour les élèves, colleurs, profs</option>
+        <option value="3">Visible pour les colleurs et les profs</option>
+        <option value="4">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']);
diff -urN cahier-de-prepa4.0.5/admin/notes.php cahier-de-prepa4.1.0/admin/notes.php
--- cahier-de-prepa4.0.5/admin/notes.php	2014-08-31 09:25:28.881338862 +0200
+++ cahier-de-prepa4.1.0/admin/notes.php	2014-10-23 21:33:50.453272282 +0200
@@ -26,10 +26,9 @@
 //////////////////////////////
 // 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
+// * fournit le contenu à afficher pour les formulaires de modifications $aff
 include('notes-saisie.php');
 
 ////////////////////////////////////
@@ -37,7 +36,7 @@
 ////////////////////////////////////
 
 // 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");
+$resultat = $mysqli->query("SELECT id, CONCAT(prenom,' ',nom) AS nom FROM utilisateurs WHERE autorisation > 2 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>";
@@ -88,6 +87,60 @@
 else
   $nb = 1;
 
+//////////////////////////////////
+// Exportation des notes en xls //
+//////////////////////////////////
+// Exportation uniquement si aucun header déjà envoyé
+if ( isset($_REQUEST['action']) && ( $_REQUEST['action'] == 'xls' ) && !headers_sent() )  {
+  // Recherche des notes concernées
+  $resultat = $mysqli->query("SELECT nom, GROUP_CONCAT( IF(ISNULL(note) OR note = 'x' $colleur,'',note) ORDER BY sid SEPARATOR '|') AS notes
+                              FROM ( SELECT s.id AS sid, u.id AS eid, CONCAT(nom,' ',prenom) 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=2 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 )  {
+    // Fonctions de saisie
+    function saisie_nombre($l, $c, $v)  {
+      echo pack("sssss", 0x203, 14, $l, $c, 0).pack("d", $v);
+      return;
+    }
+    function saisie_chaine($l, $c, $v)  {
+      echo pack("ssssss", 0x204, 8 + strlen($v), $l, $c, 0, strlen($v)).$v;
+      return;
+    }
+    // Envoi des headers
+    header("Content-Type: application/vnd.ms-excel");
+    header("Content-Disposition: attachment; filename=notes.xls");
+    header("Content-Transfer-Encoding: binary");
+    // Début du fichier xls
+    echo pack("sssss", 0x809, 6, 0, 0x10, 0);
+    // Remplissage
+    foreach ( $sid as $j => $i)
+      saisie_chaine(0, $j+1, str_pad(preg_replace('/.{5}(.{2})(.*)/','\2/\1',$semaines[$i]),5,'0',STR_PAD_LEFT));
+    $l = 0;
+    while ( $r = $resultat->fetch_assoc() )  {
+      saisie_chaine(++$l, 0, utf8_decode($r['nom']));
+      $notes = explode('|',$r['notes']);
+      foreach ( $notes as $j => $n)
+        if ( is_numeric($n) )
+          saisie_nombre($l, $j+1, $n);
+        elseif ( strlen($n) )
+          saisie_chaine($l, $j+1, $n);
+    }
+    // Fin du fichier xls
+    echo pack("ss", 0x0A, 0x00);
+    exit();
+  }
+}
+
+////////////
+/// HTML ///
+////////////
+$p = "notes?${matiere['cle']}";
+$t = "Notes de colles en ${matiere['nom']}";
+
+// Haut de page, menu et message
+include('haut.php');
+
 // Aide et formulaire de recherche
 ?>
 
@@ -96,29 +149,40 @@
     <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>Pour consulter les notes, vous devez valider le premier formulaire ci-dessous, vous permettant de choisir le format de récupération, é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>Il est possible de récupérer un fichier de type <code>xls</code>, lisible par tous les tableurs modernes. L'autre possibilité est d'afficher le tableau ici, tableau éventuellement récupérable dans un tableur par simple copié-collé (il faut répondre &laquo;&nbsp;automatique&nbsp;&raquo; lorsque le tableur demande comment 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 ajoutez 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>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 deuxième note (à un élève qui aurait déjà eu une note d'un autre colleur), 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 peuvent être entrées 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>Le caractère &laquo;&nbsp;a&nbsp;&raquo; signifie que l'élève a été marqué absent par le colleur.</p>
     <p>Il est aussi possible de <em>supprimer</em> l'ensemble des notes d'une semaine.</p>
+    <h4>Gestion des groupes de colles</h4>
+    <p>Il est possible de créer et de modifier des groupes de colles sur la page <a href="groupes">Groupes d'élèves</a>.</p>
+    <p>Une fois les groupes de colles créés, cocher un groupe permet de n'afficher que les élèves de ce groupe. Lorsque deux ou plusieurs groupes sont cochés, tous les élèves de ces groupes sont affichés. Si aucun groupe n'est coché, tous les élèves de la classe sont affichés.</p>
+    <p>Seules les notes des élèves affichés sont envoyées au serveur.</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;
+    <p>
+      <select name="action">
+        <option value="aff">Afficher</option>
+        <option value="xls">Récupérer en fichier xls</option>
+      </select>
+      les notes de
       <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;
+      ou pendant <input type="text" name="nb" value="<?php echo $nb; ?>" size="2"> semaine(s) à partir du 
       <select name="n"><?php echo $select_semaines; ?>
+
       </select>
-      <input type="submit" name="" value="OK">
     </p>
   </form>
   </div>
@@ -127,9 +191,9 @@
 // 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
+  $resultat = $mysqli->query("SELECT nom, GROUP_CONCAT( IF(ISNULL(note) OR note = 'x' $colleur,'',note) ORDER BY sid SEPARATOR '</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
+                                     FROM semaines AS s LEFT JOIN utilisateurs AS u ON 1 WHERE colle AND s.id>=$n1 AND s.id<=$n2 AND u.autorisation=2 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>";
@@ -137,8 +201,8 @@
       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";
+      if ( strlen(str_replace('</td><td>','',$r['notes'])) )
+        echo "        <tr>${r['nom']}<td>${r['notes']}</td></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();
@@ -148,7 +212,7 @@
     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
+// Affichage des formulaires de modification (provenant de notes.saisie.php)
 echo "  <h2>Ajouter des notes</h2>\n$aff";
 
 $mysqli->close();
diff -urN cahier-de-prepa4.0.5/admin/notes-saisie.php cahier-de-prepa4.1.0/admin/notes-saisie.php
--- cahier-de-prepa4.0.5/admin/notes-saisie.php	2014-09-19 11:14:46.734080116 +0200
+++ cahier-de-prepa4.1.0/admin/notes-saisie.php	2014-10-23 22:45:02.969409004 +0200
@@ -9,8 +9,8 @@
 // * $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');
-$eleves = array();
+$resultat = $mysqli->query('SELECT id, CONCAT(nom,\' \',prenom) AS nom FROM utilisateurs WHERE autorisation = 2 ORDER BY nom');
+$eleves = $eid = array();
 while ( $r = $resultat->fetch_assoc() )  {
   $eid[] = $r['id'];
   $eleves[] = $r;
@@ -107,12 +107,6 @@
 ////////////
 /// 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();
 
@@ -147,6 +141,28 @@
 }
 $resultat->free();
 
+// Récupération des groupes de colles et préparation à l'affichage
+$resultat = $mysqli->query('SELECT g.id, g.nom, g.eleves AS eid,
+                            GROUP_CONCAT( CONCAT(e.prenom,\' \',e.nom) ORDER BY FIND_IN_SET(e.id,g.eleves) SEPARATOR \', \') AS eleves
+                            FROM groupes AS g JOIN utilisateurs AS e ON FIND_IN_SET(e.id,g.eleves) WHERE g.colle GROUP BY g.id ORDER BY g.nom_nat');
+if ( $resultat->num_rows )  {
+  $affichage_groupes = <<<FIN
+    <h4>Sélectionner les groupes de colles à afficher</h4>
+    <table class="admin groupes">
+      <tbody>
+        <tr><th>Groupes de colles</th><td><input type="button" class="affbouton" value="Tout cocher"></td></tr>
+
+FIN;
+  while ( $r = $resultat->fetch_assoc() )  {
+    $affichage_groupes .= "        <tr><td>Groupe ${r['nom']}&nbsp;: ${r['eleves']}</td><td><input type=\"checkbox\" class=\"affbox\" name=\"groupes[]\" value=\"${r['eid']}\"></td></tr>\n";
+  }
+  $affichage_groupes .= "      </tbody>\n    </table>\n";
+  $resultat->free();
+  $classe_item = ' notes';
+}
+else
+  $affichage_groupes = $classe_item = '';
+
 // Fonction d'affichage
 function affichage($r,$d)  {
   $s = $r['semaine'];
@@ -166,7 +182,7 @@
   }
   echo <<<FIN
 
-  <div class="item admin">
+  <div class="item admin${GLOBALS['classe_item']}">
   <form action="" method="post">
     <input class="bouton" type="submit" name="modifie" value="Valider" title="Valider ces notes">$suppr
     <h3>$titre</h3>
@@ -174,7 +190,9 @@
       <select id="sem$s" name="semaine">$select_semaines
       </select>
     </p>
+${GLOBALS['affichage_groupes']}
 FIN;
+
   // Affichage des élèves
   $select_notes = '<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'];
@@ -213,6 +231,64 @@
 else
   echo "  <h2>Vous n'avez encore donné aucune note cette année.</h2>\n\n";
 
+// Javascript pour la sélection des groupes de colles
+if (strlen($affichage_groupes) )  {
+?>
+
+  <script type="text/javascript">
+$( function() {
+  // Remplacement de la fonction de pliage de js.php
+  $('.admin h3 span').click( function () {
+    if ( $(this).text() == 'déplier' )  {
+      $(this).text('replier').parent().parent().children(':not(h3,table)').show();
+      if ( $(this).parent().parent().children('h4').children('span').text() == 'replier' )
+        $(this).parent().parent().children('table').show();
+    }
+    else
+      $(this).text('déplier').parent().parent().children(':not(h3)').hide();
+  });
+  // Pliage du tableau des groupes
+  $('.groupes').prev('h4').append(' <span>déplier</span>');
+  $('h4 span').click( function () {
+    $(this).parent().next().toggle();
+    $(this).text($(this).text() == 'déplier' ? 'replier' : 'déplier');
+  });
+
+  // Action à la sélection d'un groupe
+  $('.affbox').click( function() {
+    affichage_eleves($(this).closest('form'));
+  });
+
+  // Action au clic du bouton Tout cocher/Tout décocher
+  $('.affbouton').click( function() {
+    var f = $(this).closest('form');
+    f.find('input:checkbox').prop('checked', $(this).val() == 'Tout cocher' );
+    affichage_eleves(f);
+  });
+
+  function affichage_eleves(f) {
+    var id = f.children('input[name=id]').val();
+    if ( f.find('input:checkbox:checked').length )  {
+      f.find('select[name!=semaine]').prop('disabled',true).parent().show();
+      f.find('input:checkbox:checked').each( function() {
+        $(this).val().split(',').forEach( function(i) {
+          $('#eleve'+i+'_'+id).prop('disabled',false);
+        });
+      });
+      f.find('select:disabled').parent().hide();
+      f.find('.affbouton').val('Tout décocher');
+    }
+    else  {
+      f.find('select[name!=semaine]').prop('disabled',false).parent().show();
+      f.find('.affbouton').val('Tout cocher');
+    }
+  }
+});
+  </script>
+
+<?php
+}
+
 // Le buffer est stocké dans $aff, à afficher dans le script appelant
 $aff = ob_get_clean();
 ?>
diff -urN cahier-de-prepa4.0.5/admin/pages.php cahier-de-prepa4.1.0/admin/pages.php
--- cahier-de-prepa4.0.5/admin/pages.php	2014-09-15 23:14:40.936403093 +0200
+++ cahier-de-prepa4.1.0/admin/pages.php	2014-10-23 22:12:05.765345733 +0200
@@ -52,7 +52,7 @@
       $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;
+      $protection = ( in_array($_REQUEST['protection'],array(0,1,2,3,4)) ) ? $_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
@@ -262,7 +262,8 @@
     <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 connectés</em>&nbsp;: accessible de tout utilisateur mais uniquement après identification (compte invité, compte élève, compte colleur ou compte professeur).</li>
+      <li><em>Visible pour les élèves, colleurs, professeurs</em>&nbsp;: accessible uniquement par les utilisateurs de types élève, colleur ou professeur. Il faut donc avoir un compte personnel 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>
@@ -279,7 +280,7 @@
 }
 else  {
   $p = "pages";
-  $t = 'Modifications des pages d\'informations';
+  $t = 'Modification des pages d\'informations';
   include('haut.php');
 ?>
 
@@ -321,9 +322,10 @@
 // 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>';
+        <option value="1">Visible pour les connectés</option>
+        <option value="2">Visible pour les élèves, colleurs, profs</option>
+        <option value="3">Visible pour les colleurs et les profs</option>
+        <option value="4">Visible pour les profs uniquement</option>';
 
 // Fonction d'affichage des pages
 function affichage_page($r)  {
@@ -428,7 +430,7 @@
     </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="id" value="$id">
     <input type="hidden" name="action" value="info">
   </form>
   </div>
@@ -469,7 +471,7 @@
   // Formulaire de nouvelle information
   affichage_info(array('id' => 0, 'cache' => 0, 'titre' => 'Titre de l\'information', 'texte' => ''));
   
-  // Formulaire de modification des informations
+  // Formulaires 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() )
diff -urN cahier-de-prepa4.0.5/admin/planning.php cahier-de-prepa4.1.0/admin/planning.php
--- cahier-de-prepa4.0.5/admin/planning.php	2014-08-26 22:21:24.409004591 +0200
+++ cahier-de-prepa4.1.0/admin/planning.php	2014-10-22 10:55:24.697281683 +0200
@@ -45,7 +45,7 @@
 //////////
 // Haut de page, menu et message
 $p = "semaines";
-$t = 'Modifications des semaines du planning annuel';
+$t = 'Modification des semaines du planning annuel';
 include('haut.php');
 
 // Affichage de l'aide générale et du haut du tableau
diff -urN cahier-de-prepa4.0.5/admin/prefs.php cahier-de-prepa4.1.0/admin/prefs.php
--- cahier-de-prepa4.0.5/admin/prefs.php	2014-09-19 23:31:33.387494735 +0200
+++ cahier-de-prepa4.1.0/admin/prefs.php	2014-10-23 22:12:45.365347000 +0200
@@ -4,7 +4,7 @@
 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
+$resultat = $mysqli->query("SELECT login, nom, prenom, genre, mail, mdp, matieres, timeout, mailexp, mailcopy
                             FROM utilisateurs WHERE id = ${_SESSION['id']}");
 $u = $resultat->fetch_assoc();
 $resultat->free();
@@ -25,39 +25,39 @@
     // 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');
+        $login = strip_tags($mysqli->real_escape_string($_REQUEST['login']));
+        $prenom = mb_convert_case(strip_tags($mysqli->real_escape_string($_REQUEST['prenom'])),MB_CASE_TITLE,'UTF-8');
+        $nom = mb_convert_case(strip_tags($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'] ) )  {
+        if ( strlen($login) && ( stripslashes($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;
+            $u['login'] = stripslashes($login);
             $requete[] = "login = '$login'";
             $modif[] = 'identifiant';
-            $_SESSION['login'] = $login;
+            $_SESSION['login'] = stripslashes($login);
           }
         }
-        if ( strlen($nom) && ( $nom != $u['nom'] ) )  {
-          $u['nom'] = $nom;
+        if ( strlen($nom) && ( stripslashes($nom) != $u['nom'] ) )  {
+          $u['nom'] = stripslashes($nom);
           $requete[] = "nom = '$nom'";
           $modif[] = 'nom';
         }
-        if ( strlen($prenom) && ( $prenom != $u['prenom'] ) )  {
-          $u['prenom'] = $prenom;
+        if ( strlen($prenom) && ( stripslashes($prenom) != $u['prenom'] ) )  {
+          $u['prenom'] = stripslashes($prenom);
           $requete[] = "prenom = '$prenom'";
           $modif[] = 'prénom';
         }
         if ( ( $mail != $u['mail'] ) && ( filter_var($mail,FILTER_VALIDATE_EMAIL) || !strlen($mail) ) )  {
-          $u['mail'] = $mail;
+          $u['mail'] = stripslashes($mail);
           $requete[] = "mail = '$mail'";
           $modif[] = 'mail';
         }
@@ -72,7 +72,6 @@
         break;
 
       case 'params':
-        $mailcopy = ( isset($_REQUEST['mailcopy']) ) ? 1 : 0;
         if ( is_numeric( $timeout = $_REQUEST['timeout'] ) && ( $timeout != $u['timeout'] ) )  {
           if ( $timeout < 60 )
             $timeout = 60;
@@ -86,6 +85,7 @@
           $requete[] = "mailexp = $mailexp";
           $modif[] = 'champ expéditeur des mails';
         }
+        $mailcopy = ( isset($_REQUEST['mailcopy']) ) ? 1 : 0;
         if ( $mailcopy != $u['mailcopy'] )  {
           $u['mailcopy'] = $mailcopy;
           $requete[] = "mailcopy = '$mailcopy'";
@@ -119,7 +119,7 @@
       // 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']}") )
+      if ( requete('utilisateurs',"UPDATE utilisateurs SET $requete WHERE id = ${_SESSION['id']}") )
         $message = "Les modifications ($modif) ont bien été réalisées.";
     }
   }
@@ -164,11 +164,11 @@
         $requete[] = "cle = '$cle'";
         $modif[] = 'clé';
       }
-      if ( in_array($colles_protection = $_REQUEST['colles_protection'],array(0,1,2,3)) && ( $colles_protection != $r['colles_protection'] ) )  {
+      if ( in_array($colles_protection = $_REQUEST['colles_protection'],array(0,1,2,3,4)) && ( $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'] ) )  {
+      if ( in_array($cdt_protection = $_REQUEST['cdt_protection'],array(0,1,2,3,4)) && ( $cdt_protection != $r['cdt_protection'] ) )  {
         $requete[] = "cdt_protection = $cdt_protection";
         $modif[] = 'accès au cahier de texte';
       }
@@ -290,7 +290,8 @@
     <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 connectés</em>&nbsp;: accessible de tout utilisateur mais uniquement après identification (compte invité, compte élève, compte colleur ou compte professeur).</li>
+      <li><em>Visible pour les élèves, colleurs, professeurs</em>&nbsp;: accessible uniquement par les utilisateurs de types élève, colleur ou professeur. Il faut donc avoir un compte personnel 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>
@@ -352,7 +353,7 @@
         <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>");
+        <option value=\"4\">${u['nom']}</option>");
 }
 else
   $select_mailexp = '
@@ -379,7 +380,7 @@
 
 // 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";
+$select = "    <p id=\"ligneXX\" class=\"ligne\"><label for=\"matiereXX\">Matière n°XX&nbsp;:</label>\n      <select id=\"matiereXX\" name=matieres[XX]>\n";
 $max = $resultat->num_rows;
 while ( $r = $resultat->fetch_assoc() )
   $select .= "        <option value=\"${r['id']}\">${r['nom']}</option>\n";
@@ -409,9 +410,10 @@
 // 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>';
+        <option value="1">Visible pour les connectés</option>
+        <option value="2">Visible pour les élèves, colleurs, profs</option>
+        <option value="3">Visible pour les colleurs et les profs</option>
+        <option value="4">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() )  {
diff -urN cahier-de-prepa4.0.5/admin/utilisateurs.php cahier-de-prepa4.1.0/admin/utilisateurs.php
--- cahier-de-prepa4.0.5/admin/utilisateurs.php	2014-09-03 00:20:39.856587241 +0200
+++ cahier-de-prepa4.1.0/admin/utilisateurs.php	2014-10-23 22:28:28.241377172 +0200
@@ -61,13 +61,29 @@
 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']));
+  $login = strip_tags($mysqli->real_escape_string($_REQUEST['login']));
+  $nom = mb_convert_case(strip_tags($mysqli->real_escape_string($_REQUEST['nom'])),MB_CASE_TITLE,'UTF-8');
   $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'] : '';
+  $autor = ( in_array($_REQUEST['autorisation'],array(1,2,3,4)) ) ? $_REQUEST['autorisation'] : 2;
+  if ( $autor > 1 )  {
+    $prenom = mb_convert_case(strip_tags($mysqli->real_escape_string($_REQUEST['prenom'])),MB_CASE_TITLE,'UTF-8');
+    $genre = ( in_array($_REQUEST['genre'],array(1,2,3)) ) ? $_REQUEST['genre']+5 : 5;
+    $mail = strtolower($mysqli->real_escape_string($_REQUEST['mail']));
+    if ( $autor > 2 )  {
+      $mailexp = $mailcopy = 1;
+      $matiere = ( isset($_REQUEST['matiere']) && is_numeric($_REQUEST['matiere']) ) ? $_REQUEST['matiere'] : '';
+    }
+    else  {
+      $matiere = '';
+      $mailexp = $mailcopy = 0;
+    }
+  }
+  else  {
+    $genre = 0;
+    $prenom = $mail = $matiere = '';
+    $mailexp = $mailcopy = 0;
+  }
+  
   // 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
@@ -81,14 +97,14 @@
     $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) )  {
+  elseif ( 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']) )  {
+                                 mdp = '$mdp1', autorisation = $autor, matieres = '$matiere', timeout = 900, mailexp = $mailexp, mailcopy = $mailcopy") )  {
+      $message = ( $autor > 1 ) ? 'Le compte de '.stripslashes("$prenom $nom").' a bien été créé.' : 'Le compte invité &laquo;&nbsp;'.stripslashes($nom).'&nbsp;&raquo; a bien été créé.';
+      if ( isset($_REQUEST['envoimail']) && strlen($mail) )  {
         envoi_mail(1,$autor,$mail,stripslashes($prenom),stripslashes($nom),stripslashes($login),$_REQUEST['mdp1']);
         $message .= " Un mail lui a été envoyé.";
       }
@@ -168,7 +184,7 @@
 //////////
 // Haut de page, menu et message
 $p = "utilisateurs";
-$t = 'Modifications des utilisateurs';
+$t = 'Modification des utilisateurs';
 include('haut.php');
 
 // Récupération des matières
@@ -185,14 +201,16 @@
     <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>
+      <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>). Chaque professeur a les mêmes droits que tous les autres, il n'y a pas d'&laquo;&nbsp;administrateur&nbsp;&raquo;.</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 ou plusieurs matières. Ils peuvent modifier leur identité, mot de passe, matières, par la page de Préférences de la partie publique.</li>
+      <li>Les <em>élèves</em> peuvent voir leurs notes s'il y en a ou simplement les contenus dont l'accès est protégé, en se connectant sur la partie publique. Ils peuvent modifier leur identité, mot de passe, matières, par la page de Préférences de la partie publique.</li>
+      <li>Les <em>invités</em> peuvent uniquement accéder aux contenus dont l'accès est protégé, en se connectant sur la partie publique. Il n'est pas possible de modifier l'identifiant ou le mot de passe d'un compte invité&nbsp;: il est possible de fournir ce compte pour plusieurs personnes, comme un compte collectif, non personnel.</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 de tout visiteur, après identification, avec un compte personnel (élève, colleur, professeur) ou non personnel (invité)</li>
+      <li>visible uniquement pour les utilisateurs ayant un compte personnel, 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>
@@ -200,10 +218,10 @@
     <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>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 et les colleurs, 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>
+    <p>Pour les colleurs et les professeurs, vous pouvez associer une matière. L'utilisateur pourra ensuite modifier ce choix dans ses Préférences et éventuellement ajouter d'autres matières.</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>
@@ -211,6 +229,8 @@
     <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>
+    <h4>Gestion des groupes d'élèves</h4>
+    <p>Des groupes d'élèves peuvent être créés, dans la page <a href="groupes">Groupes</a>.</p>
   </div>
 
   <div class="item admin user">
@@ -231,9 +251,10 @@
     <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>
+        <option value="1">Invité</option>
+        <option value="2" selected>Élève</option>
+        <option value="3">Colleur</option>
+        <option value="4">Professeur</option>
       </select>
     </p>
     <p class="ligne"><label for="matiere">Matière&nbsp;: </label>
@@ -261,12 +282,17 @@
   });
   $('#generer_mdp').parent().toggle();
 
-  // Affichage du choix de matière uniquement si colleur ou professeur
-  $('#autorisation').change( function() { 
+  // Modification du formulaire en fonction du type d'utilisateur
+  $('#autorisation').change( function() {
+    switch ( $(this).val() )  {
+      case '1' : $('#genre').parent().hide(); $('#prenom').parent().hide(); $('#mail').parent().hide(); $('#envoimail').parent().hide(); break;
+      default  : $('#genre').parent().show(); $('#prenom').parent().show(); $('#mail').parent().show(); $('#envoimail').parent().show(); break;
+    }
     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;
+      case '1' : 
+      case '2' : $('#matiere').parent().hide(); break;
+      case '3' : $('#matiere').parent().show().find('option:last').prop('disabled',true); break;
+      case '4' : $('#matiere').parent().show().find('option:last').prop('disabled',false); break;
     }
   });
   $('#matiere').parent().hide();
@@ -277,9 +303,10 @@
     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;
+        case '1' :
+        case '2' : $('#matiere').parent().hide(); break;
+        case '3' :
+        case '4' : $('#matiere').parent().show(); break;
       }
     }
     else  {
@@ -293,8 +320,8 @@
 
 <?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
+$resultat = $mysqli->query('SELECT id, mail, CASE autorisation WHEN 2 THEN \'Élève\' WHEN 3 THEN \'Colleur\' END AS autorisation,
+                            CONCAT( CASE MOD(genre,5) WHEN 1 THEN \'M. \' WHEN 2 THEN \'Mme \' WHEN 3 THEN \'Melle \' ELSE \'\' 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';
@@ -327,9 +354,16 @@
   $resultat->free();
 }
 
+// Décompte total des utilisateurs "réels" en fonction de leur type
+$resultat = $mysqli->query('SELECT autorisation, COUNT(id) AS n FROM utilisateurs WHERE login NOT RLIKE \'^tmp[0-9]{5}\' GROUP BY autorisation ORDER BY autorisation DESC');
+$n = array();
+while ( $r = $resultat->fetch_assoc() )
+  $n[$r['autorisation']] = ( $r['n'] > 1 ) ? "${r['n']} comptes" : "${r['n']} compte";
+$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
+                            CONCAT( CASE MOD(genre,5) WHEN 1 THEN \'M. \' WHEN 2 THEN \'Mme \' WHEN 3 THEN \'Melle \' ELSE \'\' END, prenom, \' \', nom ) AS nom_complet
                             FROM utilisateurs WHERE login NOT RLIKE \'^tmp[0-9]{5}\' ORDER BY autorisation DESC, nom');
 ?>
 
@@ -346,12 +380,13 @@
   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;
+      case 1 : $t = 'Invités'; break;
+      case 2 : $t = 'Élèves'; break;
+      case 3 : $t = 'Colleurs'; break;
+      case 4 : $t = 'Professeurs'; break;
     }
     echo <<<FIN
-        <tr><th colspan="4">$t</th></tr>
+        <tr><th colspan="4">$t (${n[$a]})</th></tr>
         <tr><th>Nom</th><th>Identifiant</th><th>Mail</th><th class="checks">Supprimer</th></tr>
       
 FIN;
diff -urN cahier-de-prepa4.0.5/CHANGELOG.php cahier-de-prepa4.1.0/CHANGELOG.php
--- cahier-de-prepa4.0.5/CHANGELOG.php	2014-10-18 21:26:49.023434743 +0200
+++ cahier-de-prepa4.1.0/CHANGELOG.php	2014-10-23 23:37:27.585509632 +0200
@@ -1,4 +1,4 @@
-Version actuelle : 4.0.5 (18/10/14)
+Version actuelle : 4.1.0 (24/10/14)
 ===================
 Changements :
 1.0   31/08/11 Première version
@@ -131,31 +131,39 @@
 4.0.3 02/09/14 Correction de bug (réinitialisation des mots de passe)
 4.0.4 20/09/14 Correction de bugs multiples (merci O. Bouverot, E. Saudrais)
 4.0.5 18/10/14 Correction de bug (redéfinition occasionnelle d'une fonction)
+4.1.0 24/10/14 Nouvelles fonctionnalités :
+  * Compte "invité" au mot de passe non modifiable (similaire aux comptes élève
+  de la version 3)
+  * Récupération des notes de colles en .xls
+  * Impossibilité d'envoyer un mail sans destinataire
+  * Envoi de mail possible aussi pour les colleurs
+  * Possibilité de changer de matière pour les colleurs, plusieurs possibles
+  * Gestion des groupes d'élèves/groupes de colles
+    * Ajout, modification, suppression
+    * Utilisation pour l'envoi de mail
+    * Utilisation pour la saisie des notes de colles
+  * Ajout du nombre de comptes dans la gestion des utilisateurs
+  Correction de bug :
+  * Modification du nom du répertoire lors de la modification d'une matière
+  * Correction d'une faille de sécurité XSS dans le nom/prénom/login
 ===================
 
 Todo :
 
-[ 4.1 ] Novembre 2014
- * Envoi de mail pour les colleurs
- * Gestion des groupes de colles
- * Gestion des informations récentes : suppression/modification
- * Récupération des notes de colles en .xls
- * Retour d'un compte invité au mot de passe non modifiable
- * Bug : modification de matière -> modification du nom de répertoire principal
- * Bug : gestion des informations récentes lors des suppressions/modifications
+[ 4.2 ] Avril 2015
+  * Gestion simplifiée des programmes de colles/cahiers de texte en pdf
+  * Récupération des documents en .zip
+  * Récupération des données de la base (via la sauvegarde)
+  * Version mobile
+  * Bug : gestion des informations récentes lors des suppressions/modifications
   massives (nom de répertoire...)
- * Bug : supprimer les notes, les entrées de cahier de textes et les programmes
+  * Bug : supprimer les notes, les entrées de cahier de textes et les programmes
   de colles lors de la modification du planning
 
-[ 4.2 ] Avril 2015
- * Suppression multiple de documents
- * 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
-  * Identité visuelle : favicon, logo
+  * Agenda
+  * Suppression multiple de documents
+  * Gestion des informations récentes : suppression/modification
   * 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...
diff -urN cahier-de-prepa4.0.5/connect.php cahier-de-prepa4.1.0/connect.php
--- cahier-de-prepa4.0.5/connect.php	2014-09-02 16:00:24.563626748 +0200
+++ cahier-de-prepa4.1.0/connect.php	2014-10-23 00:09:51.750807034 +0200
@@ -3,6 +3,7 @@
 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');
@@ -10,7 +11,6 @@
 $resultat->free();
 $t = "${r['titre']}";
 
-
 // Oubli d'identifiant/mot de passe -- copie dans /admin/login.php
 if ( isset($_REQUEST['oubli']) )  {
 
@@ -81,12 +81,21 @@
   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');
+    $prenom = mb_convert_case(strip_tags($mysqli->real_escape_string($_REQUEST['prenom'])),MB_CASE_TITLE,'UTF-8');
+    $nom = mb_convert_case(strip_tags($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'] : '';
+    // Si "colleur" choisi, autorisation vaut 3. Sinon, 2 (élève)
+    if ( $_REQUEST['autorisation'] == 2 )  {
+      $autor = 3;
+      $matiere = ( isset($_REQUEST['matiere']) && is_numeric($_REQUEST['matiere']) ) ? $_REQUEST['matiere'] : '';
+      $mailexp = $mailcopy = 1;
+    }
+    else  {
+      $autor = 2;
+      $matiere = '';
+      $mailexp = $mailcopy = 0;
+    }
     // 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');
@@ -100,7 +109,7 @@
       $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") )
+                                   mail = '$mail', mdp = '$mdp1', autorisation = $autor, matieres = '$matiere', timeout = 900, mailexp = $mailexp, mailcopy = $mailcopy") )
         $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();
diff -urN cahier-de-prepa4.0.5/css/style.css cahier-de-prepa4.1.0/css/style.css
--- cahier-de-prepa4.0.5/css/style.css	2014-09-19 11:10:15.886071449 +0200
+++ cahier-de-prepa4.1.0/css/style.css	2014-10-23 12:11:16.684192158 +0200
@@ -99,7 +99,7 @@
 .bouton { margin: 0 4% 0 10px; float: right; }
 .bouton + .bouton { margin-right: 0; }
 .admin + h2 { margin-top: 1em; }
-.admin h3 span, .aide h3 span { cursor: pointer; font-size: 0.9em; padding: 0 0.2em; border: 1px solid black; margin-left: 1em; }
+.admin h3 span, .aide h3 span, .admin h4 span { cursor: pointer; font-size: 0.9em; padding: 0 0.2em; border: 1px solid black; margin-left: 1em; }
 #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: middle; }
diff -urN cahier-de-prepa4.0.5/def_sql.php cahier-de-prepa4.1.0/def_sql.php
--- cahier-de-prepa4.0.5/def_sql.php	2014-08-30 23:06:29.152150386 +0200
+++ cahier-de-prepa4.1.0/def_sql.php	2014-10-23 10:29:33.627996859 +0200
@@ -190,8 +190,16 @@
   `note` varchar(2) NOT NULL
 ) ENGINE=MyISAM  DEFAULT CHARSET=utf8;
 
+CREATE TABLE groupes (
+  `id` tinyint(2) UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
+  `nom` varchar(10) NOT NULL,
+  `nom_nat` varchar(30) NOT NULL,
+  `colle` tinyint(1) UNSIGNED NOT NULL,
+  `eleves` varchar(100) 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);
+  VALUES (1, '$login', $genre, '$prenom', '$nom', '$mail', '$mot_de_passe', 4, '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);
diff -urN cahier-de-prepa4.0.5/docs.php cahier-de-prepa4.1.0/docs.php
--- cahier-de-prepa4.0.5/docs.php	2014-08-26 01:07:30.838558707 +0200
+++ cahier-de-prepa4.1.0/docs.php	2014-10-18 22:07:19.687512525 +0200
@@ -13,14 +13,14 @@
             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("$requete WHERE r.protection < 4 AND r.id = $rid");
+  $resultat = $mysqli->query("$requete WHERE r.protection < 5 AND r.id = $rid");
   if ( $resultat->num_rows )  {
     $rep = $resultat->fetch_assoc();
     $resultat->free();
   }
 }
 elseif ( !empty($_REQUEST) )  {
-  $resultat = $mysqli->query("$requete WHERE r.protection < 4 AND r.parent = 0");
+  $resultat = $mysqli->query("$requete WHERE r.protection < 5 AND r.parent = 0");
   if ( $resultat->num_rows )  {
     while ( $r = $resultat->fetch_assoc() )
       if ( isset($_REQUEST[$r['cle']]) )  {
@@ -113,7 +113,7 @@
     // 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");
+                                FROM reps WHERE parent = $rid AND protection < 5");
     if ( $resultat->num_rows )  {
       while ( $r = $resultat->fetch_assoc() )  {
         // Affichage du contenu si autorisé
@@ -144,7 +144,7 @@
     // 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']}");
+                                FROM docs WHERE parent = $rid AND protection < 5 ${GLOBALS['ordre']}");
     if ( $resultat->num_rows )  {
       $icones = $GLOBALS['icones'];
       while ( $r = $resultat->fetch_assoc() )  {
diff -urN cahier-de-prepa4.0.5/download.php cahier-de-prepa4.1.0/download.php
--- cahier-de-prepa4.0.5/download.php	2014-09-20 00:37:24.679621176 +0200
+++ cahier-de-prepa4.1.0/download.php	2014-10-18 22:07:46.771513392 +0200
@@ -12,7 +12,7 @@
   $mysqli = premiere_connexion();
   $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");
+                              WHERE d.id = $id AND protection < 5");
   if ( $resultat->num_rows )  {
     $f = $resultat->fetch_assoc();
     $resultat->free();
diff -urN cahier-de-prepa4.0.5/haut.php cahier-de-prepa4.1.0/haut.php
--- cahier-de-prepa4.0.5/haut.php	2014-09-05 14:46:33.435779383 +0200
+++ cahier-de-prepa4.1.0/haut.php	2014-10-23 12:11:33.440192694 +0200
@@ -10,9 +10,9 @@
 <head>
   <title><?php echo $t; ?></title>
   <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
-  <link rel="stylesheet" href="css/style.css?v=4" type="text/css" media="screen">
+  <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?v=4" type="text/css" media="screen">
+  <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="alternate" type="application/rss+xml" title="Flux RSS" href="http://cahier-de-prepa.fr/$site/documents/<?php echo sha1($base); ?>/rss.xml">
@@ -69,7 +69,7 @@
       $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 AND protection < 4");
+      $resultat_doc = $mysqli->query("SELECT id, nom FROM reps WHERE matiere = ${r['id']} AND menu = 1 AND protection < 5");
       if ( $resultat_doc->num_rows )  {
         while ( $d = $resultat_doc->fetch_assoc() )
           $menu .= "\n        <a class=\"menurep\" href=\"docs?rep=${d['id']}\">${d['nom']}</a>";        
@@ -91,8 +91,16 @@
       <a href=\"connect\">Se connecter</a>
     </div>";
     break;
-  // Si compte élève
+  // Si compte invité : pas de modification possible
   case 1:
+    $menu .= "
+    <div>
+      <h3>Espace utilisateur</h3>
+      <a href=\"connect?deconnexion\">Se déconnecter</a>
+    </div>";
+    break;
+  // Si compte élève
+  case 2:
     $notes = '';
     $resultat = $mysqli->query("SELECT cle, nom FROM matieres WHERE notes ORDER BY ordre");
     while ( $r = $resultat->fetch_assoc() )
@@ -125,7 +133,8 @@
     $menu .= "
     <div>
       <h3>Espace utilisateur</h3>
-      <a href=\"prefs\">Mes préférences</a>$notes
+      <a href=\"prefs\">Mes préférences</a>
+      <a href=\"mail\">Envoyer un mail</a>$notes
       <a href=\"connect?deconnexion\">Se déconnecter</a>
       
     </div>";
diff -urN cahier-de-prepa4.0.5/installation.php cahier-de-prepa4.1.0/installation.php
--- cahier-de-prepa4.0.5/installation.php	2014-08-28 11:52:44.385327167 +0200
+++ cahier-de-prepa4.1.0/installation.php	2014-10-18 22:46:03.835586898 +0200
@@ -262,8 +262,8 @@
 }
 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');
+// La base de données doit contenir au moins un professeur
+$resultat = $mysqli->query('SELECT id FROM utilisateurs WHERE autorisation = 4 LIMIT 1');
 affiche('Présence d\'au moins un utilisateur de type professeur',$resultat->num_rows);
 $resultat->free();
 
diff -urN cahier-de-prepa4.0.5/mail.php cahier-de-prepa4.1.0/mail.php
--- cahier-de-prepa4.0.5/mail.php	1970-01-01 01:00:00.000000000 +0100
+++ cahier-de-prepa4.1.0/mail.php	2014-10-23 23:13:30.301463638 +0200
@@ -0,0 +1,249 @@
+<?php
+// Sécurité et récupération des données de configuration
+define('OK',1);
+include('debut.php');
+
+// L'accès est autorisé uniquement aux colleurs et professeurs
+$mysqli = premiere_connexion();
+if ( !$autorisation )  {
+  $p = 'mail';
+  $t = 'Envoi de mail';
+  include('login.php');
+}
+if ( $autorisation < 2 )
+  exit('Cette page ne contient aucune information.');
+
+// 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é ou si mailexp est
+// nul : possibilité de désactivation
+if ( !strlen($u['mail']) || !$u['mailexp'] )  {
+  $p = "mail";
+  $t = 'Envoi de mail';
+  $message = ( strlen($u['mail']) ) ? 'Vous n\'êtes pas autorisé à envoyer de mails. Il faut demander au professeur de votre matière l\'autorisation.' : '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é.';
+  include('haut.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;
+  default : $g = '';
+}
+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 = 2 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 = '';
+  $ids = explode(',',implode(',',$_REQUEST['dest']));
+  foreach ( $ids as $i )
+    if ( isset($utilisateurs[$i]) )  {
+      $dests .= $utilisateurs[$i]['nom_complet'].' <'.$utilisateurs[$i]['mail'].'>, ';
+      unset($utilisateurs[$i]);
+    }
+  if ( !strlen($dests) )
+    $message = 'Le mail n\'a pas été envoyé car aucun destinataire n\'a été renseigné.';
+  else  {
+    $dests = substr($dests,0,-2);
+    $bcc = ( $u['mailcopy'] ) ? "$exp <${u['mail']}>, " : '';
+    if ( isset($_REQUEST['dest_bcc']) )
+      $ids = explode(',',implode(',',$_REQUEST['dest_bcc']));
+      foreach ( $ids as $i )
+        if ( isset($utilisateurs[$i]) )  {
+          $bcc .= $utilisateurs[$i]['nom_complet'].' <'.$utilisateurs[$i]['mail'].'>, ';
+          unset($utilisateurs[$i]);
+        }
+    $bcc = ( strlen($bcc) ) ? 'Bcc: '.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 de confirmation d'envoi
+    $n1 = substr_count($dests,'<');
+    $n2 = substr_count($bcc,'<') - $u['mailcopy'];
+    if ( $n2 )
+      $message = 'Le mail a été envoyé à '.($n1+$n2).' destinataires (dont '.$n2.' en copie cachée).';
+    else
+      $message = ( $n1 > 1 ) ? "Le mail a été envoyé à $n1 destinataires." : 'Le mail a été envoyé à un destinataire.';
+  }
+}
+
+//////////
+// 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>
+    <h4>Destinataires</h4>
+    <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 destinataire.</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>Un utilisateur ne peut pas être à la fois en copie et en copie cachée.</p>
+    <p>Les case à cocher sont grisées si l'utilisateur n'a pas renseigné son adresse mail.</p>
+    <h4>Groupes d'élèves</h4>
+    <p>Il est possible de créer et de modifier des groupes d'élèves sur la page <a href="groupes">Groupes d'élèves</a>.</p>
+    <p>Une fois les groupes d'élèves créés, cocher un groupe permet d'envoyer le mail à tous les élèves du groupe ayant renseigné leur adresse mail (les élèves correspondants au groupe sont automatiquement cochés).</p>
+    <p>Le décochage d'un groupe ne décoche pas automatiquement les élèves du groupe.</p>
+    <p>Un élève appartenant à deux groupes cochés ne recevra qu'une seule fois le mail.</p>
+    <h4>Sujet et contenu du mail</h4>
+    <p>Le <em>sujet</em> est le sujet du mail. Tous les caractères sont autorisés. Pensez à envoyer des mails avec un sujet correspondant explicitement au contenu...</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 convention 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>
+    <h4>Autres réglages</h4>
+    <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, accessible dans 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, accessible dans 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 ' ELSE '' END, prenom, ' ', nom ) AS nom_complet
+                            FROM utilisateurs WHERE login NOT RLIKE '^tmp[0-9]{5}' AND id != ${_SESSION['id']} AND autorisation > 1 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 2 : $t = 'Élèves'; break;
+      case 3 : $t = 'Colleurs'; break;
+      case 4 : $t = 'Professeurs'; break;
+    }
+    echo "        <tr><th>$t</th><td></td><td></td></tr>\n";
+  }
+  $nom = ( strlen($r['nom_complet']) > 1 ) ? $r['nom_complet'] : "<em>${r['login']}</em>";
+  if ( strlen($r['mail']) )
+    $nom .= ' (pas de mail)';
+  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();
+
+// Affichage des groupes d'élèves
+$resultat = $mysqli->query('SELECT g.id, g.nom, g.eleves AS eid,
+                            GROUP_CONCAT( CONCAT(e.prenom,\' \',e.nom) ORDER BY FIND_IN_SET(e.id,g.eleves) SEPARATOR \', \') AS eleves
+                            FROM groupes AS g JOIN utilisateurs AS e ON FIND_IN_SET(e.id,g.eleves) GROUP BY g.id ORDER BY g.nom_nat');
+if ( $resultat->num_rows )  {
+  echo "        <tr><th>Groupes d'élèves</th><td></td><td></td></tr>\n";
+  while ( $r = $resultat->fetch_assoc() )  {
+    echo "        <tr><td>Groupe ${r['nom']}&nbsp;: ${r['eleves']}</td><td><input type=\"checkbox\" class=\"dest_c5\" name=\"dest[]\" value=\"${r['eid']}\"></td><td><input type=\"checkbox\" class=\"dest_bcc5\" name=\"dest_bcc[]\" value=\"${r['eid']}\"></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');
+  });
+  
+  // Pliage de l'aide
+  $('.aide h3').append(' <span>déplier</span>');
+  $('.aide h3 span').click( function () {
+    $('.aide').children(':not(h3)').toggle();
+    $(this).text($(this).text() == 'déplier' ? 'replier' : 'déplier');
+  });
+  $('.aide').children(':not(h3)').hide();
+
+  // 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+':not(:disabled)').prop("checked",true);
+      $('.dest_'+t2+a+':not(:disabled)').prop("checked",false);
+      $(this).closest('tr').find('.button_'+t2).val('Tout cocher');
+    }
+    else  {
+      $(this).val('Tout cocher');
+      $('.dest_'+t1+a).prop("checked",false);
+    }
+    verif_dest();
+  });
+  $('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);
+    verif_dest();
+  });
+
+  // Vérification qu'au moins un destinataire a été sélectionné
+  function verif_dest() {
+    var a = $('#avertissement_destinataires').length;
+    var i = $('input[name=dest\\[\\]]:checked').length;
+    if ( !a && !i )  {
+      $('#dests').after('<p id="avertissement_destinataires" class="warning">Aucun destinataire n\'a été sélectionné. Il en faut un au minimum.</p>');
+      $('input[name=valide]').prop('disabled',true);
+    }
+    if ( a && i )  {
+      $('#avertissement_destinataires').remove();
+      $('input[name=valide]').prop('disabled',false);
+    }
+  }
+  verif_dest();
+  
+});
+    </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-prepa4.0.5/MAJSQL.sql cahier-de-prepa4.1.0/MAJSQL.sql
--- cahier-de-prepa4.0.5/MAJSQL.sql	2014-08-28 13:46:05.577544806 +0200
+++ cahier-de-prepa4.1.0/MAJSQL.sql	2014-10-23 10:29:07.583996026 +0200
@@ -178,3 +178,24 @@
   note VARCHAR( 2 ) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL
 ) ENGINE=MyISAM  DEFAULT CHARSET=utf8;
 
+-- 
+-- Voilà les modifications à effectuer sur chaque base pour passer de Cahier de
+-- Prépa 4.0.5 à Cahier de Prépa 4.1.0.
+-- 
+
+UPDATE utilisateurs SET autorisation = autorisation+1;
+UPDATE pages SET protection = protection+1 WHERE protection;
+UPDATE reps SET protection = protection+1 WHERE protection;
+UPDATE docs SET protection = protection+1 WHERE protection;
+UPDATE matieres SET cdt_protection = cdt_protection+1 WHERE cdt_protection;
+UPDATE matieres SET colles_protection = colles_protection+1 WHERE colles_protection;
+UPDATE utilisateurs SET mailexp = 1, mailcopy = 1 WHERE autorisation = 3;
+UPDATE utilisateurs SET mailexp = 0, mailcopy = 0 WHERE autorisation = 2;
+
+CREATE TABLE groupes (
+  `id` TINYINT( 2 ) UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
+  `nom` VARCHAR( 10 ) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
+  `nom_nat` VARCHAR( 30 ) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
+  `colle` TINYINT( 1 ) UNSIGNED NOT NULL,
+  `eleves` VARCHAR( 100 ) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL
+) ENGINE=MyISAM  DEFAULT CHARSET=utf8;
diff -urN cahier-de-prepa4.0.5/notes.php cahier-de-prepa4.1.0/notes.php
--- cahier-de-prepa4.0.5/notes.php	2014-08-27 02:36:39.217494667 +0200
+++ cahier-de-prepa4.1.0/notes.php	2014-10-23 21:33:18.809271270 +0200
@@ -10,6 +10,9 @@
   $t = 'Notes de colles';
   include('login.php');
 }
+// Aucun affichage pour les comptes invités
+if ( $autorisation == 1 )
+  exit('Cette page ne contient aucune information.');
 
 ////////////////////////////////////////
 // Validation de la requête : matière //
@@ -17,7 +20,7 @@
 
 // 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';
+$requete = ( $autorisation > 2 ) ? "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) )
@@ -39,9 +42,17 @@
   exit('Cette page ne contient aucune information.');
 }
 
+////////////
+/// HTML ///
+////////////
+$p = "notes?${matiere['cle']}";
+$t = "Notes de colles en ${matiere['nom']}";
+
+// Haut de page, menu et message
+include('haut.php');
+
 // Si colleurs/profs : ajout de notes de colles -> admin/notes-saisie.php
-// Si élèves : on continue ici
-if ( $autorisation > 1 )  {
+if ( $autorisation > 2 )  {
   $mid = $matiere['id'];
   include('admin/fonctions.php');
   include('admin/notes-saisie.php');
@@ -51,10 +62,18 @@
 
   <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>Vous pouvez ci-dessous consulter les notes que vous avez mises 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>Gestion des notes</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 deuxième note (à un élève qui aurait déjà eu une note d'un autre colleur), 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 peuvent être entrées 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>Le caractère &laquo;&nbsp;a&nbsp;&raquo; signifie que l'élève a été marqué absent par le colleur.</p>
     <p>Il est aussi possible de <em>supprimer</em> l'ensemble des notes d'une semaine.</p>
+    <h4>Gestion des groupes de colles</h4>
+    <p>Il est possible (par les professeurs, dans l'interface d'administration) de créer des groupes de colles.</p>
+    <p>Une fois les groupes de colles créés, cocher un groupe permet de n'afficher que les élèves de ce groupe. Lorsque deux ou plusieurs groupes sont cochés, tous les élèves de ces groupes sont affichés. Si aucun groupe n'est coché, tous les élèves de la classe sont affichés.</p>
+    <p>Seules les notes des élèves affichés sont envoyées au serveur.</p>
   </div>
 <?php
   echo $aff;
@@ -75,10 +94,9 @@
 </script>
 
 <?php
-  // Bas de page
-  include('bas.php');
-  exit();
 }
+
+// Si élèves : on continue ici
 else  {
   // Fonction d'affichage des semaines
   function format_date($date)  {
@@ -86,32 +104,23 @@
     $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");
 
-// 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();
+  // 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();
 }
-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-prepa4.0.5/prefs.php cahier-de-prepa4.1.0/prefs.php
--- cahier-de-prepa4.0.5/prefs.php	2014-09-21 00:09:06.238331637 +0200
+++ cahier-de-prepa4.1.0/prefs.php	2014-10-23 00:15:15.854817406 +0200
@@ -7,12 +7,19 @@
 $t = 'Cahier de Prépa - Mon compte';
 $mysqli = premiere_connexion();
 // Vérification que l'on est bien connecté
-if ( !$autorisation )
+if ( $autorisation < 1 )
   include('login.php');
+// Modification impossible pour les comptes invités
+if ( $autorisation == 1 )  {
+  $message = 'Il est impossible de modifier les préférences de ce compte.';
+  include('haut.php');
+  include('bas.php');
+  exit();
+}
 
 // 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']}'");
+$resultat = $mysqli->query("SELECT login, nom, prenom, genre, mail, mdp, matieres, timeout, mailexp, mailcopy
+                            FROM utilisateurs WHERE id = ${_SESSION['id']}");
 $u = $resultat->fetch_assoc();
 $resultat->free();
 
@@ -32,34 +39,34 @@
     $requete = array();
     $modif = array();
     // Validation des données envoyées
-    $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');
+    $login = strip_tags($mysqli->real_escape_string($_REQUEST['login']));
+    $prenom = mb_convert_case(strip_tags($mysqli->real_escape_string($_REQUEST['prenom'])),MB_CASE_TITLE,'UTF-8');
+    $nom = mb_convert_case(strip_tags($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'] ) )  {
+    if ( strlen($login) && ( stripslashes($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;
+        $u['login'] = stripslashes($login);
         $requete[] = "login = '$login'";
         $modif[] = 'identifiant';
-        $_SESSION['login'] = $login;
+        $_SESSION['login'] = stripslashes($login);
       }
     }
-    if ( strlen($nom) && ( $nom != $u['nom'] ) )  {
-      $u['nom'] = $nom;
+    if ( strlen($nom) && ( stripslashes($nom) != $u['nom'] ) )  {
+      $u['nom'] = stripslashes($nom);
       $requete[] = "nom = '$nom'";
       $modif[] = 'nom';
     }
-    if ( strlen($prenom) && ( $prenom != $u['prenom'] ) )  {
-      $u['prenom'] = $prenom;
+    if ( strlen($prenom) && ( stripslashes($prenom) != $u['prenom'] ) )  {
+      $u['prenom'] = stripslashes($prenom);
       $requete[] = "prenom = '$prenom'";
       $modif[] = 'prénom';
     }
@@ -81,6 +88,25 @@
       $modif[] = 'temps de connexion';
       $_SESSION['timeout'] = $timeout;
     }
+    // Modification de mailexp et mailcopy uniquement si mailexp est non nul
+    if ( $u['mailexp'] && 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';
+    }
+    $mailcopy = ( isset($_REQUEST['mailcopy']) ) ? 1 : 0;
+    if ( $u['mailexp'] && ( $mailcopy != $u['mailcopy'] ) )  {
+      $u['mailcopy'] = $mailcopy;
+      $requete[] = "mailcopy = '$mailcopy'";
+      $modif[] = 'mise en copie des mails';
+    }
+    $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;
+    }
+        
     if ( !empty($requete) )  {
       $requete = implode(', ',$requete);
       $modif = implode(', ',$modif);
@@ -110,14 +136,44 @@
 
 // Haut de page, menu et message
 include('haut.php');
-$mysqli->close();
+
+// Pas de gestion des préférences de mail si mailexp est nul
+if ( $u['mailexp'] )  {
+  if ( strlen($u['mail']) )  {
+    switch ( $u['genre'] )  {
+      case 1 : $g = 'M. '; break;
+      case 2 : $g = 'Mme '; break;
+      case 3 : $g = 'Melle '; break;
+      default : $g = '';
+    }
+    $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=\"4\">${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' : '';
+  $prefs_mail = <<<FIN
+    <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>
+
+FIN;
+}
+else  {
+  $prefs_mail = '';
+}
 
 // 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">
@@ -134,11 +190,64 @@
 $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>
+    <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>
+$prefs_mail
+    <h3>Modifier mes matières</h3>
+
+FIN;
+
+// Modification des matières
+$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();
+}
+$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 id=\"matiereXX\" 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";
+$mysqli->close();
+
+$n = 0;
+foreach ( $matieres as $m )
+  echo str_replace("\"${m['id']}\"","\"${m['id']}\" selected",str_replace('XX',++$n,$select));
+$select = str_replace(array("'","\n",'  '),array("\'",'',''),$select);
+
+echo <<<FIN
+    <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>
   </form>
 $mdp_message
   </div>
 
+  <script type="text/javascript">
+\$( function() {
+  var n = $n;
+  var select = '$select';
+  \$('#ajouter').click(function () {
+    if ( n < $max )  {
+      n++;
+      \$( select.replace(/XX/g,n) ).insertBefore(\$(this).parent());
+    }
+  });
+  \$('#retirer').click(function () {
+    if ( n )  {
+      \$('#ligne'+n).remove();
+      n--;
+    }
+  });
+
+});
+  </script>
+
 
 FIN;
 
