| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517 |
- <?php
- /**
- * Classe MapModel
- *
- * Modèle pour gérer les cartes dans la base de données.
- * Fournit les opérations CRUD complètes pour les cartes.
- */
- if (!class_exists('MapModel')) {
- class MapModel
- {
- protected static $table = 'maps';
- private static $pdo = null;
- /**
- * Obtient une connexion PDO à la base de données.
- *
- * @return PDO
- */
- private static function getPDO() {
- if (self::$pdo === null) {
- $config = require __DIR__ . '/../../config/config.php';
-
- // Utiliser le socket Unix pour MAMP
- $dsn = sprintf(
- 'mysql:dbname=%s;unix_socket=%s;charset=utf8mb4',
- $config['db_name'],
- $config['db_socket']
- );
-
- self::$pdo = new PDO($dsn, $config['db_user'], $config['db_password']);
- self::$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
- }
- return self::$pdo;
- }
- /**
- * Crée la table maps si elle n'existe pas
- *
- * @return bool True si la table a été créée ou existe déjà
- */
- public static function createTable(): bool
- {
- $pdo = self::getPDO();
- $sql = "
- CREATE TABLE IF NOT EXISTS maps (
- id INT AUTO_INCREMENT PRIMARY KEY,
- name VARCHAR(255) NOT NULL,
- description TEXT,
- width INT NOT NULL DEFAULT 10,
- height INT NOT NULL DEFAULT 10,
- data JSON,
- file_path VARCHAR(500),
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
- updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
- INDEX idx_name (name),
- INDEX idx_created_at (created_at)
- ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
- ";
- try {
- $pdo->exec($sql);
- AppDebugger::log('Table maps créée avec succès', 'INFO');
- return true;
- } catch (Exception $e) {
- AppDebugger::log('Erreur lors de la création de la table maps: ' . $e->getMessage(), 'ERROR');
- return false;
- }
- }
- /**
- * Crée une nouvelle carte dans la base de données
- *
- * @param Map $map L'objet Map à sauvegarder
- * @param string|null $description Description optionnelle
- * @return int|false L'ID de la carte créée ou false en cas d'erreur
- */
- public static function create(Map $map, ?string $description = null, ?string $template = null, ?int $radius = null): int|false
- {
- $pdo = self::getPDO();
- // Générer le chemin du fichier
- $fileName = 'map_' . time() . '_' . uniqid() . '.json';
- $filePath = 'storage/maps/' . $fileName;
- // Sauvegarder le fichier JSON
- if (!$map->saveToFile($filePath)) {
- AppDebugger::log('Erreur lors de la sauvegarde du fichier JSON', 'ERROR');
- return false;
- }
- // Convertir les données pour la base
- $data = [
- 'name' => $map->getName(),
- 'width' => $map->getWidth(),
- 'height' => $map->getHeight(),
- 'created_at' => $map->getCreatedAt(),
- 'updated_at' => $map->getUpdatedAt(),
- 'statistics' => $map->getStatistics()
- ];
- // Ajouter métadonnées template et radius pour traçabilité
- if ($template !== null) $data['template'] = $template;
- if ($radius !== null) $data['radius'] = (int)$radius;
- $sql = "
- INSERT INTO maps (name, description, width, height, data, file_path, created_at, updated_at)
- VALUES (:name, :description, :width, :height, :data, :file_path, :created_at, :updated_at)
- ";
- try {
- $stmt = $pdo->prepare($sql);
- $stmt->execute([
- 'name' => $map->getName(),
- 'description' => $description,
- 'width' => $map->getWidth(),
- 'height' => $map->getHeight(),
- 'data' => json_encode($data),
- 'file_path' => $filePath,
- 'created_at' => $map->getCreatedAt(),
- 'updated_at' => $map->getUpdatedAt()
- ]);
- $mapId = $pdo->lastInsertId();
- AppDebugger::log('Carte créée avec succès, ID: ' . $mapId, 'INFO');
- return (int) $mapId;
- } catch (Exception $e) {
- AppDebugger::log('Erreur lors de la création de la carte: ' . $e->getMessage(), 'ERROR');
- return false;
- }
- }
- /**
- * Récupère les données brutes d'une carte par son ID (pour usage interne)
- *
- * @param int $id L'ID de la carte
- * @return array|null Les données de la carte ou null si non trouvée
- */
- private static function findById(int $id): ?array
- {
- try {
- $pdo = self::getPDO();
- $stmt = $pdo->prepare('SELECT * FROM maps WHERE id = :id');
- $stmt->execute(['id' => $id]);
- $row = $stmt->fetch(PDO::FETCH_ASSOC);
-
- return $row ?: null;
- } catch (Exception $e) {
- AppDebugger::log('Erreur dans findById: ' . $e->getMessage(), 'ERROR');
- return null;
- }
- }
- /**
- * Retourne la row brute (publique) pour un id
- * @param int $id
- * @return array|null
- */
- public static function getRowById(int $id): ?array
- {
- return self::findById($id);
- }
- /**
- * Récupère une carte par son ID
- *
- * @param int $id L'ID de la carte
- * @return Map|null La carte chargée ou null si non trouvée
- */
- public static function findMap(int $id): ?Map
- {
- $row = self::findById($id);
- if (!$row) {
- return null;
- }
- // Charger depuis le fichier JSON
- $map = Map::fromFile(__DIR__ . '/../../' . $row['file_path']);
- if (!$map) {
- AppDebugger::log('Erreur lors du chargement de la carte depuis le fichier: ' . __DIR__ . '/../../' . $row['file_path'], 'ERROR');
- return null;
- }
- return $map;
- }
- /**
- * Met à jour une carte existante
- *
- * @param int $id L'ID de la carte à mettre à jour
- * @param Map $map Les nouvelles données de la carte
- * @param string|null $description Nouvelle description optionnelle
- * @return bool True si la mise à jour a réussi
- */
- public static function update(int $id, Map $map, ?string $description = null, ?string $template = null, ?int $radius = null): bool
- {
- $pdo = self::getPDO();
- // Vérifier que la carte existe
- $existing = self::findById($id);
- if (!$existing) {
- AppDebugger::log('Carte non trouvée pour mise à jour, ID: ' . $id, 'ERROR');
- return false;
- }
- // Sauvegarder le fichier JSON mis à jour
- $filePath = $existing['file_path'];
- if (!$map->saveToFile($filePath)) {
- AppDebugger::log('Erreur lors de la sauvegarde du fichier JSON mis à jour', 'ERROR');
- return false;
- }
- // Mettre à jour les données
- $data = [
- 'name' => $map->getName(),
- 'width' => $map->getWidth(),
- 'height' => $map->getHeight(),
- 'created_at' => $existing['created_at'], // Garder la date de création originale
- 'updated_at' => $map->getUpdatedAt(),
- 'statistics' => $map->getStatistics()
- ];
- // Ajouter métadonnées template et radius si fournies
- if ($template !== null) $data['template'] = $template;
- if ($radius !== null) $data['radius'] = (int)$radius;
- $sql = "
- UPDATE maps
- SET name = :name, description = :description, width = :width, height = :height,
- data = :data, updated_at = :updated_at
- WHERE id = :id
- ";
- try {
- $stmt = $pdo->prepare($sql);
- $stmt->execute([
- 'id' => $id,
- 'name' => $map->getName(),
- 'description' => $description,
- 'width' => $map->getWidth(),
- 'height' => $map->getHeight(),
- 'data' => json_encode($data),
- 'updated_at' => $map->getUpdatedAt()
- ]);
- AppDebugger::log('Carte mise à jour avec succès, ID: ' . $id, 'INFO');
- return true;
- } catch (Exception $e) {
- AppDebugger::log('Erreur lors de la mise à jour de la carte: ' . $e->getMessage(), 'ERROR');
- return false;
- }
- }
- /**
- * Supprime une carte
- *
- * @param int $id L'ID de la carte à supprimer
- * @return bool True si la suppression a réussi
- */
- public static function delete(int $id): bool
- {
- $pdo = self::getPDO();
- // Récupérer les informations de la carte avant suppression
- $existing = self::findById($id);
- if (!$existing) {
- AppDebugger::log('Carte non trouvée pour suppression, ID: ' . $id, 'ERROR');
- return false;
- }
- // Supprimer le fichier JSON (résoudre chemin absolu depuis la racine du projet)
- $projectRootPath = __DIR__ . '/../../';
- $absoluteFilePath = realpath($projectRootPath . $existing['file_path']) ?: ($projectRootPath . $existing['file_path']);
- if (file_exists($absoluteFilePath)) {
- if (!@unlink($absoluteFilePath)) {
- AppDebugger::log('Erreur lors de la suppression du fichier: ' . $absoluteFilePath, 'ERROR');
- // Ne pas arrêter la suppression pour autant
- }
- } else {
- AppDebugger::log('Fichier à supprimer introuvable: ' . $absoluteFilePath, 'WARNING');
- }
- // Supprimer de la base de données
- $stmt = $pdo->prepare('DELETE FROM maps WHERE id = :id');
- try {
- $stmt->execute(['id' => $id]);
- $affectedRows = $stmt->rowCount();
- if ($affectedRows > 0) {
- AppDebugger::log('Carte supprimée avec succès, ID: ' . $id, 'INFO');
- return true;
- } else {
- AppDebugger::log('Aucune carte supprimée, ID: ' . $id, 'WARNING');
- return false;
- }
- } catch (Exception $e) {
- AppDebugger::log('Erreur lors de la suppression de la carte: ' . $e->getMessage(), 'ERROR');
- return false;
- }
- }
- /**
- * Récupère toutes les cartes avec pagination
- *
- * @param int $page Le numéro de page (commence à 1)
- * @param int $perPage Nombre d'éléments par page
- * @param string $orderBy Colonne de tri
- * @param string $orderDirection Direction du tri (ASC ou DESC)
- * @return array Tableau avec 'data' (cartes) et 'pagination' (infos de pagination)
- */
- public static function paginate(int $page = 1, int $perPage = 10, string $orderBy = 'created_at', string $orderDirection = 'DESC'): array
- {
- $pdo = self::getPDO();
- // Calculer l'offset
- $offset = ($page - 1) * $perPage;
- // Compter le total
- $totalStmt = $pdo->query('SELECT COUNT(*) as total FROM maps');
- $total = $totalStmt->fetch(PDO::FETCH_ASSOC)['total'];
- // Récupérer les cartes
- $sql = "SELECT * FROM maps ORDER BY {$orderBy} {$orderDirection} LIMIT :limit OFFSET :offset";
- $stmt = $pdo->prepare($sql);
- $stmt->bindValue(':limit', $perPage, PDO::PARAM_INT);
- $stmt->bindValue(':offset', $offset, PDO::PARAM_INT);
- $stmt->execute();
- $maps = $stmt->fetchAll(PDO::FETCH_ASSOC);
- // Calculer les informations de pagination
- $totalPages = ceil($total / $perPage);
- $hasNextPage = $page < $totalPages;
- $hasPrevPage = $page > 1;
- return [
- 'data' => $maps,
- 'pagination' => [
- 'current_page' => $page,
- 'per_page' => $perPage,
- 'total' => (int) $total,
- 'total_pages' => $totalPages,
- 'has_next_page' => $hasNextPage,
- 'has_prev_page' => $hasPrevPage,
- 'next_page' => $hasNextPage ? $page + 1 : null,
- 'prev_page' => $hasPrevPage ? $page - 1 : null
- ]
- ];
- }
- /**
- * Recherche des cartes par nom ou description
- *
- * @param string $query Le terme de recherche
- * @param int $limit Nombre maximum de résultats
- * @return array Les cartes trouvées
- */
- public static function search(string $query, int $limit = 20): array
- {
- $pdo = self::getPDO();
- // S'assurer que limit est un entier positif
- $limit = max(1, min(100, (int)$limit));
- // Recherche dans le nom et la description
- $sql = "SELECT * FROM maps
- WHERE name LIKE :query OR description LIKE :query
- ORDER BY name
- LIMIT $limit";
- $stmt = $pdo->prepare($sql);
- $searchTerm = '%' . $query . '%';
- $stmt->execute(['query' => $searchTerm]);
- return $stmt->fetchAll(PDO::FETCH_ASSOC);
- }
- /**
- * Récupère les statistiques globales des cartes
- *
- * @return array Statistiques générales
- */
- public static function getGlobalStats(): array
- {
- $pdo = self::getPDO();
- // Compter le nombre total de cartes
- $totalStmt = $pdo->query('SELECT COUNT(*) as total FROM maps');
- $total = $totalStmt->fetch(PDO::FETCH_ASSOC)['total'];
- // Dimensions moyennes
- $dimensionsStmt = $pdo->query('SELECT AVG(width) as avg_width, AVG(height) as avg_height FROM maps');
- $dimensions = $dimensionsStmt->fetch(PDO::FETCH_ASSOC);
- // Dernière carte créée
- $latestStmt = $pdo->query('SELECT name, created_at FROM maps ORDER BY created_at DESC LIMIT 1');
- $latest = $latestStmt->fetch(PDO::FETCH_ASSOC);
- return [
- 'total_maps' => (int) $total,
- 'average_width' => round((float) $dimensions['avg_width'], 1),
- 'average_height' => round((float) $dimensions['avg_height'], 1),
- 'latest_map' => $latest ?: null
- ];
- }
- /**
- * Vérifie si une carte existe
- *
- * @param int $id L'ID de la carte
- * @return bool True si la carte existe
- */
- public static function exists(int $id): bool
- {
- $pdo = self::getPDO();
- $stmt = $pdo->prepare('SELECT COUNT(*) as count FROM maps WHERE id = :id');
- $stmt->execute(['id' => $id]);
- return $stmt->fetch(PDO::FETCH_ASSOC)['count'] > 0;
- }
- /**
- * Duplique une carte existante
- *
- * @param int $id L'ID de la carte à dupliquer
- * @param string $newName Le nom de la nouvelle carte
- * @return int|false L'ID de la nouvelle carte ou false en cas d'erreur
- */
- public static function duplicate(int $id, string $newName): int|false
- {
- $originalMap = self::findMap($id);
- if (!$originalMap) {
- AppDebugger::log('Carte originale non trouvée pour duplication, ID: ' . $id, 'ERROR');
- return false;
- }
- // Créer une nouvelle carte avec le même contenu
- $newMap = new Map($newName, $originalMap->getWidth(), $originalMap->getHeight());
- // Copier toutes les tuiles
- for ($q = 0; $q < $originalMap->getWidth(); $q++) {
- for ($r = 0; $r < $originalMap->getHeight(); $r++) {
- $tile = $originalMap->getTile($q, $r);
- if ($tile) {
- $newMap->setTile($q, $r, $tile);
- }
- }
- }
- // Créer la nouvelle carte en base
- $description = "Copie de la carte #" . $id;
- return self::create($newMap, $description);
- }
- /**
- * Exporte une carte vers un format spécifique
- *
- * @param int $id L'ID de la carte
- * @param string $format Le format d'export ('json', 'csv', etc.)
- * @return string|false Les données exportées ou false en cas d'erreur
- */
- public static function export(int $id, string $format = 'json'): string|false
- {
- $map = self::findMap($id);
- if (!$map) {
- return false;
- }
- switch ($format) {
- case 'json':
- return $map->toJson(true);
- case 'csv':
- return self::exportToCSV($map);
- default:
- AppDebugger::log('Format d\'export non supporté: ' . $format, 'ERROR');
- return false;
- }
- }
- /**
- * Exporte une carte au format CSV
- *
- * @param Map $map La carte à exporter
- * @return string Les données CSV
- */
- private static function exportToCSV(Map $map): string
- {
- $csv = "q,r,type,resources,temperature,elevation\n";
- for ($q = 0; $q < $map->getWidth(); $q++) {
- for ($r = 0; $r < $map->getHeight(); $r++) {
- $tile = $map->getTile($q, $r);
- if ($tile && !$tile->isEmpty()) {
- $resources = json_encode($tile->getResources());
- $csv .= sprintf(
- "%d,%d,%s,%s,%s,%s\n",
- $q,
- $r,
- $tile->getType() ?? '',
- $resources,
- $tile->getTemperature() ?? '',
- $tile->getElevation() ?? ''
- );
- }
- }
- }
- return $csv;
- }
- }
- }
|