Paillasse/portail/app/Http/Controllers/ClasseController.php
Adam Belkacemi dd4f9c3c55 permet aux élèves de rejoindre leur classe avec son code, et les fiches en lot
L'élève saisit le code de classe et son prénom, le prof accepte, l'élève
voit une seule fois son identifiant et son code. Aucun e-mail. Le prof
peut aussi régénérer les fiches de plusieurs élèves d'un coup. Procédure
pour prouver la copie de la clé des sauvegardes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 22:18:53 +02:00

201 lines
7 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
namespace App\Http\Controllers;
use App\Models\Classe;
use App\Models\DemandeInscription;
use App\Models\Eleve;
use App\Models\Seance;
use App\Services\GenerateurCompteEleve;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Str;
use Illuminate\View\View;
class ClasseController extends Controller
{
public function __construct(private readonly GenerateurCompteEleve $generateur) {}
public function index(): View
{
$classes = Auth::guard('web')->user()->classes()->withCount('eleves')->latest()->get();
return view('mes-classes.index', ['classes' => $classes]);
}
public function show(Classe $classe): View
{
$this->verifierProprietaire($classe);
return view('mes-classes.show', [
'classe' => $classe->load('eleves'),
'demandes' => $classe->demandesInscription()->where('statut', DemandeInscription::EN_ATTENTE)->oldest()->get(),
]);
}
public function storeEleve(Request $request, Classe $classe): RedirectResponse
{
$this->verifierProprietaire($classe);
$donnees = $request->validate([
'prenom' => ['required', 'string', 'max:100'],
'nom' => ['nullable', 'string', 'max:100'],
]);
$fiche = $this->creerEleve($classe, $donnees['prenom'], $donnees['nom'] ?? null);
return redirect()->route('mes-classes.show', $classe)
->with('fiches', [$fiche]);
}
public function importerCsv(Request $request, Classe $classe): RedirectResponse
{
$this->verifierProprietaire($classe);
$request->validate([
'fichier' => ['required', 'file', 'mimes:csv,txt'],
]);
$lignes = file($request->file('fichier')->getRealPath(), FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
$fiches = [];
foreach ($lignes as $ligne) {
$colonnes = str_getcsv($ligne);
$prenom = trim($colonnes[0] ?? '');
if ($prenom === '' || Str::lower($prenom) === 'prenom') {
continue; // ligne vide ou en-tête
}
$nom = trim($colonnes[1] ?? '') ?: null;
$fiches[] = $this->creerEleve($classe, $prenom, $nom);
}
return redirect()->route('mes-classes.show', $classe)
->with('fiches', $fiches)
->with('nombre_importes', count($fiches));
}
public function regenererCode(Classe $classe, Eleve $eleve): RedirectResponse
{
$this->verifierProprietaire($classe);
abort_if($eleve->classe_id !== $classe->id, 404);
$codeSecret = $this->generateur->genererCodeSecret();
$jetonQr = Str::random(32);
$eleve->forceFill([
'code_secret' => Hash::make($codeSecret),
'jeton_qr' => $jetonQr,
])->save();
return redirect()->route('mes-classes.show', $classe)
->with('fiches', [[
'identifiant' => $eleve->identifiant,
'prenom' => $eleve->prenom,
'nom' => $eleve->nom,
'code_secret' => $codeSecret,
'jeton_qr' => $jetonQr,
]]);
}
/**
* @return array{identifiant: string, prenom: string, nom: ?string, code_secret: string, jeton_qr: string}
*/
private function creerEleve(Classe $classe, string $prenom, ?string $nom): array
{
$fiche = $this->generateur->creerDans($classe, $prenom, $nom);
unset($fiche['eleve']);
return $fiche;
}
public function accepterDemande(Classe $classe, DemandeInscription $demande): RedirectResponse
{
$this->verifierProprietaire($classe);
abort_if($demande->classe_id !== $classe->id, 404);
if ($demande->statut === DemandeInscription::EN_ATTENTE && ! $classe->estArchivee()) {
$fiche = $this->generateur->creerDans($classe, $demande->prenom, $demande->nom);
$demande->forceFill([
'statut' => DemandeInscription::ACCEPTEE,
'eleve_id' => $fiche['eleve']->id,
'code_secret_chiffre' => $fiche['code_secret'],
])->save();
}
return redirect()->route('mes-classes.show', $classe);
}
public function refuserDemande(Classe $classe, DemandeInscription $demande): RedirectResponse
{
$this->verifierProprietaire($classe);
abort_if($demande->classe_id !== $classe->id, 404);
$demande->forceFill(['statut' => DemandeInscription::REFUSEE])->save();
return redirect()->route('mes-classes.show', $classe);
}
public function nombreDemandes(Classe $classe): JsonResponse
{
$this->verifierProprietaire($classe);
return response()->json(['en_attente' => $classe->demandesInscription()->where('statut', DemandeInscription::EN_ATTENTE)->count()]);
}
/**
* Nouvelles fiches (nouveau code, nouveau QR) pour les élèves cochés :
* la fiche n'est visible qu'une fois, ceci permet de la refaire en lot.
*/
public function nouvellesFiches(Request $request, Classe $classe): RedirectResponse
{
$this->verifierProprietaire($classe);
$ids = $request->validate(['eleves' => ['required', 'array'], 'eleves.*' => ['integer']])['eleves'];
$fiches = [];
foreach ($classe->eleves()->whereIn('id', $ids)->orderBy('prenom')->get() as $eleve) {
$codeSecret = $this->generateur->genererCodeSecret();
$jetonQr = Str::random(32);
$eleve->forceFill(['code_secret' => Hash::make($codeSecret), 'jeton_qr' => $jetonQr])->save();
$fiches[] = ['identifiant' => $eleve->identifiant, 'prenom' => $eleve->prenom, 'nom' => $eleve->nom, 'code_secret' => $codeSecret, 'jeton_qr' => $jetonQr];
}
return redirect()->route('mes-classes.show', $classe)->with('fiches', $fiches);
}
/**
* Fin d'année : plus de séance possible, et suppression définitive de la
* classe et des données de ses élèves 90 jours plus tard.
*/
public function archiver(Classe $classe): RedirectResponse
{
$this->verifierProprietaire($classe);
if ($classe->aDesLabsEnCours()) {
return back()->withErrors(['archivage' => 'Des élèves ont encore un lab en cours : termine la séance avant d’archiver.']);
}
$classe->forceFill(['archivee_a' => now()])->save();
$classe->seances()->whereIn('statut', [Seance::PLANIFIEE, Seance::SALLE_ATTENTE, Seance::EN_COURS])
->update(['statut' => Seance::TERMINEE, 'terminee_a' => now()]);
return redirect()->route('mes-classes.show', $classe);
}
public function desarchiver(Classe $classe): RedirectResponse
{
$this->verifierProprietaire($classe);
$classe->forceFill(['archivee_a' => null])->save();
return redirect()->route('mes-classes.show', $classe);
}
private function verifierProprietaire(Classe $classe): void
{
abort_if($classe->user_id !== Auth::guard('web')->id(), 403);
}
}