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(__DIR__ . '/../../' . $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(__DIR__ . '/../../' . $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; } } }