Ajout d'un easteregg

This commit is contained in:
Esteban
2026-07-03 10:56:54 +02:00
parent 542910a15d
commit 6164f7eaae
15 changed files with 2626 additions and 141 deletions
+37
View File
@@ -0,0 +1,37 @@
<?php
// Sert la photo de profil d'un joueur "Presse-Citron", stockée en base
// (voir migration Juillet 2026 (8)). Public : pas besoin d'être connecté pour voir
// les avatars affichés dans le classement.
require_once('../functions/databaseConnection.php');
$playerId = $_GET['player_id'] ?? '';
if (!is_numeric($playerId)) {
http_response_code(404);
exit;
}
try {
$stmt = $dbh->prepare('SELECT avatar_data, avatar_mime FROM lemon_avatars WHERE player_id = :id');
$stmt->bindValue(':id', (int) $playerId, PDO::PARAM_INT);
$stmt->execute();
$row = $stmt->fetch(PDO::FETCH_ASSOC);
} catch (PDOException $e) {
http_response_code(404);
exit;
}
if (!$row) {
http_response_code(404);
exit;
}
header('Content-Type: ' . $row['avatar_mime']);
header('X-Content-Type-Options: nosniff');
header('Content-Length: ' . strlen($row['avatar_data']));
header('Content-Disposition: inline');
// Les avatars changent rarement : cache navigateur d'une heure, comme les PDF (voir viewPdf.php)
header('Cache-Control: public, max-age=3600');
echo $row['avatar_data'];
exit;
+29
View File
@@ -0,0 +1,29 @@
<?php
// Suppression de la photo de profil du joueur "Presse-Citron" connecté (retour à
// l'avatar par défaut : cercle avec l'initiale du pseudo).
require_once(__DIR__ . '/../functions/security.php');
require_once('../functions/databaseConnection.php');
require_once('../functions/lemonSession.php');
header('Content-Type: application/json; charset=utf-8');
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
http_response_code(405);
echo json_encode(['error' => 'Méthode non autorisée.']);
exit;
}
requireLemonAuth();
requireCsrfToken();
try {
$stmt = $dbh->prepare('DELETE FROM lemon_avatars WHERE player_id = :id');
$stmt->bindValue(':id', (int) $_SESSION['lemon_player_id'], PDO::PARAM_INT);
$stmt->execute();
echo json_encode(['ok' => true]);
} catch (PDOException $e) {
http_response_code(500);
echo json_encode(['error' => 'Erreur serveur.']);
}
+51
View File
@@ -0,0 +1,51 @@
<?php
// Upload / remplacement de la photo de profil du joueur "Presse-Citron" connecté.
// Le fichier est déjà redimensionné et compressé côté navigateur (voir pages/citron.php),
// mais la validation ici ne fait aucune confiance à ça : elle revérifie tout elle-même.
require_once(__DIR__ . '/../functions/security.php');
require_once('../functions/databaseConnection.php');
require_once('../functions/lemonSession.php');
require_once('../functions/avatarUpload.php');
header('Content-Type: application/json; charset=utf-8');
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
http_response_code(405);
echo json_encode(['error' => 'Méthode non autorisée.']);
exit;
}
requireLemonAuth();
requireCsrfToken();
$resultat = lireUploadAvatar('avatar');
if ($resultat === null) {
http_response_code(400);
echo json_encode(['error' => 'Aucune image envoyée.']);
exit;
}
if (is_string($resultat)) {
http_response_code(400);
echo json_encode(['error' => $resultat]);
exit;
}
try {
$stmt = $dbh->prepare('
INSERT INTO lemon_avatars (player_id, avatar_data, avatar_mime, updated_at)
VALUES (:id, :data, :mime, NOW())
ON DUPLICATE KEY UPDATE avatar_data = VALUES(avatar_data), avatar_mime = VALUES(avatar_mime), updated_at = NOW()
');
$stmt->bindValue(':id', (int) $_SESSION['lemon_player_id'], PDO::PARAM_INT);
$stmt->bindValue(':data', $resultat['data'], PDO::PARAM_LOB);
$stmt->bindValue(':mime', $resultat['mime'], PDO::PARAM_STR);
$stmt->execute();
echo json_encode(['ok' => true]);
} catch (PDOException $e) {
http_response_code(500);
echo json_encode(['error' => 'Erreur serveur.']);
}
+51
View File
@@ -0,0 +1,51 @@
<?php
// Classement public du jeu "Presse-Citron" (les 15 meilleurs scores cumulés), avec un
// peu plus de détail que le simple score : nombre de bâtiments possédés (calculé depuis
// game_state), niveau de prestige, date de dernière partie. Lecture seule, sans connexion.
require_once(__DIR__ . '/../functions/security.php');
require_once('../functions/databaseConnection.php');
header('Content-Type: application/json; charset=utf-8');
try {
$stmt = $dbh->prepare('
SELECT id, username, score, lifetime_score, prestige_level, prestige_bonus, game_state, last_played, created_at
FROM lemon_players
ORDER BY lifetime_score DESC
LIMIT 15
');
$stmt->execute();
$lignes = $stmt->fetchAll(PDO::FETCH_ASSOC);
} catch (PDOException $e) {
$lignes = [];
}
// On calcule le nombre total de bâtiments possédés à partir du game_state (stocké en JSON) :
// une info que le classement n'affichait pas jusqu'ici, et qui donne un vrai aperçu de
// l'avancée d'un joueur au-delà du seul score
$classement = array_map(function ($ligne) {
$totalBatiments = 0;
if (!empty($ligne['game_state'])) {
$etat = json_decode($ligne['game_state'], true);
if (is_array($etat) && !empty($etat['buildings']) && is_array($etat['buildings'])) {
foreach ($etat['buildings'] as $possedes) {
$totalBatiments += (int) $possedes;
}
}
}
return [
'id' => (int) $ligne['id'],
'username' => $ligne['username'],
'score' => (float) $ligne['score'],
'lifetime_score' => (float) $ligne['lifetime_score'],
'prestige_level' => (int) $ligne['prestige_level'],
'prestige_bonus' => (float) $ligne['prestige_bonus'],
'total_buildings' => $totalBatiments,
'last_played' => $ligne['last_played'],
'created_at' => $ligne['created_at'],
];
}, $lignes);
echo json_encode($classement);
+61
View File
@@ -0,0 +1,61 @@
<?php
// Connexion au jeu "Presse-Citron" (easter egg). Comptes totalement séparés
// des comptes admin du portfolio (table lemon_players).
require_once(__DIR__ . '/../functions/security.php');
require_once('../functions/databaseConnection.php');
header('Content-Type: application/json; charset=utf-8');
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
http_response_code(405);
echo json_encode(['error' => 'Méthode non autorisée.']);
exit;
}
requireCsrfToken();
$username = trim($_POST['username'] ?? '');
$password = $_POST['password'] ?? '';
try {
$stmt = $dbh->prepare('
SELECT id, username, password, score, lifetime_score, prestige_level, prestige_bonus, game_state
FROM lemon_players WHERE username = :username LIMIT 1
');
$stmt->execute([':username' => $username]);
$player = $stmt->fetch(PDO::FETCH_ASSOC);
} catch (PDOException $e) {
http_response_code(500);
echo json_encode(['error' => 'Erreur serveur.']);
exit;
}
if (!$player || !password_verify($password, $player['password'])) {
http_response_code(401);
echo json_encode(['error' => 'Nom de pressier ou mot de passe incorrect.']);
exit;
}
// Régénère l'ID de session à la connexion : évite la fixation de session
session_regenerate_id(true);
$_SESSION['lemon_player_id'] = (int) $player['id'];
$_SESSION['lemon_username'] = $player['username'];
try {
$stmt = $dbh->prepare('UPDATE lemon_players SET last_played = NOW() WHERE id = :id');
$stmt->execute([':id' => $player['id']]);
} catch (PDOException $e) {
// Pas bloquant : la connexion reste valable même si cette mise à jour échoue
}
echo json_encode([
'ok' => true,
'id' => (int) $player['id'],
'username' => $player['username'],
'score' => (float) $player['score'],
'lifetime_score' => (float) $player['lifetime_score'],
'prestige_level' => (int) $player['prestige_level'],
'prestige_bonus' => (float) $player['prestige_bonus'],
'game_state' => $player['game_state'],
]);
+11
View File
@@ -0,0 +1,11 @@
<?php
// Déconnexion du jeu "Presse-Citron" uniquement : ne touche jamais à une éventuelle
// session admin du portfolio (clés de session complètement séparées)
require_once(__DIR__ . '/../functions/security.php');
header('Content-Type: application/json; charset=utf-8');
unset($_SESSION['lemon_player_id'], $_SESSION['lemon_username']);
echo json_encode(['ok' => true]);
+68
View File
@@ -0,0 +1,68 @@
<?php
// Création d'un compte pour le jeu "Presse-Citron" (easter egg).
// Comptes totalement séparés des comptes admin du portfolio (table lemon_players).
require_once(__DIR__ . '/../functions/security.php');
require_once('../functions/databaseConnection.php');
header('Content-Type: application/json; charset=utf-8');
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
http_response_code(405);
echo json_encode(['error' => 'Méthode non autorisée.']);
exit;
}
requireCsrfToken();
$username = trim($_POST['username'] ?? '');
$password = $_POST['password'] ?? '';
if ($username === '' || mb_strlen($username) > 50) {
http_response_code(400);
echo json_encode(['error' => "Nom de pressier invalide (1 à 50 caractères)."]);
exit;
}
if (mb_strlen($password) < 4) {
http_response_code(400);
echo json_encode(['error' => 'Le mot de passe doit faire au moins 4 caractères.']);
exit;
}
try {
// On vérifie que le nom n'est pas déjà pris
$stmt = $dbh->prepare('SELECT id FROM lemon_players WHERE username = :username LIMIT 1');
$stmt->execute([':username' => $username]);
if ($stmt->fetch()) {
http_response_code(409);
echo json_encode(['error' => 'Ce nom de pressier est déjà pris.']);
exit;
}
$hash = password_hash($password, PASSWORD_DEFAULT);
$stmt = $dbh->prepare('INSERT INTO lemon_players (username, password, created_at) VALUES (:username, :password, NOW())');
$stmt->execute([':username' => $username, ':password' => $hash]);
$playerId = (int) $dbh->lastInsertId();
// Régénère l'ID de session à la création de compte (même logique que checkLogin.php
// pour le portfolio : évite la fixation de session)
session_regenerate_id(true);
$_SESSION['lemon_player_id'] = $playerId;
$_SESSION['lemon_username'] = $username;
echo json_encode([
'ok' => true,
'id' => $playerId,
'username' => $username,
'score' => 0,
'lifetime_score' => 0,
'prestige_level' => 0,
'prestige_bonus' => 0,
'game_state' => null,
]);
} catch (PDOException $e) {
http_response_code(500);
echo json_encode(['error' => 'Erreur serveur.']);
}
+67
View File
@@ -0,0 +1,67 @@
<?php
// Sauvegarde de la partie en cours pour le joueur "Presse-Citron" connecté.
// Toute la logique du jeu vit côté JS : ce script se contente de valider
// grossièrement les données reçues (nombres positifs, JSON de taille raisonnable)
// et de les enregistrer telles quelles pour le joueur de la session en cours.
require_once(__DIR__ . '/../functions/security.php');
require_once('../functions/databaseConnection.php');
require_once('../functions/lemonSession.php');
header('Content-Type: application/json; charset=utf-8');
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
http_response_code(405);
echo json_encode(['error' => 'Méthode non autorisée.']);
exit;
}
requireLemonAuth();
requireCsrfToken();
$score = $_POST['score'] ?? null;
$lifetimeScore = $_POST['lifetime_score'] ?? null;
$prestigeLevel = $_POST['prestige_level'] ?? null;
$prestigeBonus = $_POST['prestige_bonus'] ?? null;
$gameState = $_POST['game_state'] ?? null;
// Validation basique : évite qu'une requête forgée n'enregistre n'importe quoi
if (
!is_numeric($score) || $score < 0 ||
!is_numeric($lifetimeScore) || $lifetimeScore < 0 ||
!is_numeric($prestigeLevel) || $prestigeLevel < 0 ||
!is_numeric($prestigeBonus) || $prestigeBonus < 0
) {
http_response_code(400);
echo json_encode(['error' => 'Données de sauvegarde invalides.']);
exit;
}
// game_state : JSON de taille raisonnable (20 Ko largement suffisant pour la liste
// des bâtiments/améliorations possédés), sinon on refuse plutôt que de tronquer
if ($gameState !== null && (strlen($gameState) > 20000 || json_decode($gameState) === null)) {
http_response_code(400);
echo json_encode(['error' => 'État de jeu invalide.']);
exit;
}
try {
$stmt = $dbh->prepare('
UPDATE lemon_players
SET score = :score, lifetime_score = :lifetime_score, prestige_level = :prestige_level,
prestige_bonus = :prestige_bonus, game_state = :game_state, last_played = NOW()
WHERE id = :id
');
$stmt->bindValue(':score', (float) $score);
$stmt->bindValue(':lifetime_score', (float) $lifetimeScore);
$stmt->bindValue(':prestige_level', (int) $prestigeLevel, PDO::PARAM_INT);
$stmt->bindValue(':prestige_bonus', (float) $prestigeBonus);
$stmt->bindValue(':game_state', $gameState, $gameState === null ? PDO::PARAM_NULL : PDO::PARAM_STR);
$stmt->bindValue(':id', (int) $_SESSION['lemon_player_id'], PDO::PARAM_INT);
$stmt->execute();
echo json_encode(['ok' => true]);
} catch (PDOException $e) {
http_response_code(500);
echo json_encode(['error' => 'Erreur serveur.']);
}