LTI authentifie l'élève et le fait rejoindre sa classe (même code que le code/QR existant), pas lancer un lab précis - relecture du CLAUDE.md qui liste LTI avec les autres façons d'ajouter des élèves. packbackbooks/lti-1p3-tool (Apache-2.0, recommandé par le CLAUDE.md), trois ponts vers Redis/cookies/MariaDB, élève retrouvé par identité Moodle stable pour ne jamais créer de doublon. Enregistrement d'une plateforme/d'un déploiement par commande Artisan (opération rare, faite à la main par Adam), jamais une page d'administration. Corrige au passage un vrai bug préexistant trouvé en vérifiant en réel sur le serveur : withoutMiddleware(ValidateCsrfToken::class) sur une route ne retire jamais le vrai middleware CSRF (ValidateCsrfToken est un alias déprécié de PreventRequestForgery, pas la classe réellement enregistrée) - /csp-rapport avait le même défaut depuis l'étape 14, invisible faute de surveillance. Corrigé pour les deux par la bonne méthode (validateCsrfTokens except, dans bootstrap/app.php). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
75 lines
2.2 KiB
PHP
75 lines
2.2 KiB
PHP
<?php
|
|
|
|
namespace App\Models;
|
|
|
|
use App\Models\Concerns\GerePreferences;
|
|
use App\Models\Concerns\GereSessionUnique;
|
|
use Database\Factories\EleveFactory;
|
|
use Illuminate\Auth\Authenticatable;
|
|
use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract;
|
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
|
use Illuminate\Database\Eloquent\Model;
|
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
|
use Illuminate\Database\Eloquent\Relations\HasMany;
|
|
use Illuminate\Database\Eloquent\Relations\HasOne;
|
|
|
|
class Eleve extends Model implements AuthenticatableContract
|
|
{
|
|
/** @use HasFactory<EleveFactory> */
|
|
use Authenticatable, GerePreferences, GereSessionUnique, HasFactory;
|
|
|
|
protected $fillable = ['classe_id', 'prenom', 'nom', 'identifiant', 'code_secret', 'jeton_qr', 'lti_emetteur', 'lti_sub'];
|
|
|
|
protected $hidden = ['code_secret', 'jeton_session_actif', 'jeton_qr'];
|
|
|
|
protected $casts = [
|
|
'derniere_connexion_a' => 'datetime',
|
|
'derniere_activite_a' => 'datetime',
|
|
'preferences' => 'array',
|
|
];
|
|
|
|
/**
|
|
* En ligne : une requête authentifiée dans les 3 dernières minutes
|
|
* (middleware MetAJourActivite) — pas une vraie présence temps réel.
|
|
*/
|
|
public function estEnLigne(): bool
|
|
{
|
|
return $this->derniere_activite_a !== null && $this->derniere_activite_a->gt(now()->subMinutes(3));
|
|
}
|
|
|
|
public function getAuthPasswordName(): string
|
|
{
|
|
return 'code_secret';
|
|
}
|
|
|
|
/** @return BelongsTo<Classe, $this> */
|
|
public function classe(): BelongsTo
|
|
{
|
|
return $this->belongsTo(Classe::class);
|
|
}
|
|
|
|
/** @return HasMany<LabSession, $this> */
|
|
public function labSessions(): HasMany
|
|
{
|
|
return $this->hasMany(LabSession::class);
|
|
}
|
|
|
|
/** @return HasMany<Raccourci, $this> */
|
|
public function raccourcis(): HasMany
|
|
{
|
|
return $this->hasMany(Raccourci::class)->orderBy('ordre');
|
|
}
|
|
|
|
/** @return HasOne<FilConversation, $this> */
|
|
public function filConversation(): HasOne
|
|
{
|
|
return $this->hasOne(FilConversation::class);
|
|
}
|
|
|
|
public function nomAffiche(): string
|
|
{
|
|
return $this->nom
|
|
? $this->prenom.' '.mb_substr($this->nom, 0, 1).'.'
|
|
: $this->prenom;
|
|
}
|
|
}
|