Pages /legal et /confidentialite réécrites d'après le système réel, purge des classes archivées depuis 90 jours (prouvée par Pest), archivage côté prof. Polices, xterm.js et CodeMirror ne sont plus chargés depuis Google ni jsDelivr. Pages d'aperçu signalées, sans vrais établissements ni faux chiffres. Journaux Laravel gardés 14 jours. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
159 lines
5 KiB
PHP
159 lines
5 KiB
PHP
<?php
|
||
|
||
namespace App\Http\Controllers;
|
||
|
||
use App\Models\Classe;
|
||
use App\Models\Eleve;
|
||
use App\Models\Seance;
|
||
use App\Services\GenerateurCompteEleve;
|
||
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'),
|
||
]);
|
||
}
|
||
|
||
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
|
||
{
|
||
$identifiant = $this->generateur->genererIdentifiant($prenom, $nom);
|
||
$codeSecret = $this->generateur->genererCodeSecret();
|
||
$jetonQr = Str::random(32);
|
||
|
||
$classe->eleves()->create([
|
||
'prenom' => $prenom,
|
||
'nom' => $nom,
|
||
'identifiant' => $identifiant,
|
||
'code_secret' => Hash::make($codeSecret),
|
||
'jeton_qr' => $jetonQr,
|
||
]);
|
||
|
||
return [
|
||
'identifiant' => $identifiant,
|
||
'prenom' => $prenom,
|
||
'nom' => $nom,
|
||
'code_secret' => $codeSecret,
|
||
'jeton_qr' => $jetonQr,
|
||
];
|
||
}
|
||
|
||
/**
|
||
* 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);
|
||
}
|
||
}
|