MapModel.php 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487
  1. <?php
  2. /**
  3. * Classe MapModel
  4. *
  5. * Modèle pour gérer les cartes dans la base de données.
  6. * Fournit les opérations CRUD complètes pour les cartes.
  7. */
  8. if (!class_exists('MapModel')) {
  9. class MapModel
  10. {
  11. protected static $table = 'maps';
  12. private static $pdo = null;
  13. /**
  14. * Obtient une connexion PDO à la base de données.
  15. *
  16. * @return PDO
  17. */
  18. private static function getPDO() {
  19. if (self::$pdo === null) {
  20. $config = require __DIR__ . '/../../config/config.php';
  21. // Utiliser le socket Unix pour MAMP
  22. $dsn = sprintf(
  23. 'mysql:dbname=%s;unix_socket=%s;charset=utf8mb4',
  24. $config['db_name'],
  25. $config['db_socket']
  26. );
  27. self::$pdo = new PDO($dsn, $config['db_user'], $config['db_password']);
  28. self::$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
  29. }
  30. return self::$pdo;
  31. }
  32. /**
  33. * Crée la table maps si elle n'existe pas
  34. *
  35. * @return bool True si la table a été créée ou existe déjà
  36. */
  37. public static function createTable(): bool
  38. {
  39. $pdo = self::getPDO();
  40. $sql = "
  41. CREATE TABLE IF NOT EXISTS maps (
  42. id INT AUTO_INCREMENT PRIMARY KEY,
  43. name VARCHAR(255) NOT NULL,
  44. description TEXT,
  45. width INT NOT NULL DEFAULT 10,
  46. height INT NOT NULL DEFAULT 10,
  47. data JSON,
  48. file_path VARCHAR(500),
  49. created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  50. updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  51. INDEX idx_name (name),
  52. INDEX idx_created_at (created_at)
  53. ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
  54. ";
  55. try {
  56. $pdo->exec($sql);
  57. AppDebugger::log('Table maps créée avec succès', 'INFO');
  58. return true;
  59. } catch (Exception $e) {
  60. AppDebugger::log('Erreur lors de la création de la table maps: ' . $e->getMessage(), 'ERROR');
  61. return false;
  62. }
  63. }
  64. /**
  65. * Crée une nouvelle carte dans la base de données
  66. *
  67. * @param Map $map L'objet Map à sauvegarder
  68. * @param string|null $description Description optionnelle
  69. * @return int|false L'ID de la carte créée ou false en cas d'erreur
  70. */
  71. public static function create(Map $map, ?string $description = null): int|false
  72. {
  73. $pdo = self::getPDO();
  74. // Générer le chemin du fichier
  75. $fileName = 'map_' . time() . '_' . uniqid() . '.json';
  76. $filePath = 'storage/maps/' . $fileName;
  77. // Sauvegarder le fichier JSON
  78. if (!$map->saveToFile($filePath)) {
  79. AppDebugger::log('Erreur lors de la sauvegarde du fichier JSON', 'ERROR');
  80. return false;
  81. }
  82. // Convertir les données pour la base
  83. $data = [
  84. 'name' => $map->getName(),
  85. 'width' => $map->getWidth(),
  86. 'height' => $map->getHeight(),
  87. 'created_at' => $map->getCreatedAt(),
  88. 'updated_at' => $map->getUpdatedAt(),
  89. 'statistics' => $map->getStatistics()
  90. ];
  91. $sql = "
  92. INSERT INTO maps (name, description, width, height, data, file_path, created_at, updated_at)
  93. VALUES (:name, :description, :width, :height, :data, :file_path, :created_at, :updated_at)
  94. ";
  95. try {
  96. $stmt = $pdo->prepare($sql);
  97. $stmt->execute([
  98. 'name' => $map->getName(),
  99. 'description' => $description,
  100. 'width' => $map->getWidth(),
  101. 'height' => $map->getHeight(),
  102. 'data' => json_encode($data),
  103. 'file_path' => $filePath,
  104. 'created_at' => $map->getCreatedAt(),
  105. 'updated_at' => $map->getUpdatedAt()
  106. ]);
  107. $mapId = $pdo->lastInsertId();
  108. AppDebugger::log('Carte créée avec succès, ID: ' . $mapId, 'INFO');
  109. return (int) $mapId;
  110. } catch (Exception $e) {
  111. AppDebugger::log('Erreur lors de la création de la carte: ' . $e->getMessage(), 'ERROR');
  112. return false;
  113. }
  114. }
  115. /**
  116. * Récupère les données brutes d'une carte par son ID (pour usage interne)
  117. *
  118. * @param int $id L'ID de la carte
  119. * @return array|null Les données de la carte ou null si non trouvée
  120. */
  121. private static function findById(int $id): ?array
  122. {
  123. $pdo = self::getPDO();
  124. $stmt = $pdo->prepare('SELECT * FROM maps WHERE id = :id');
  125. $stmt->execute(['id' => $id]);
  126. return $stmt->fetch(PDO::FETCH_ASSOC) ?: null;
  127. }
  128. /**
  129. * Récupère une carte par son ID
  130. *
  131. * @param int $id L'ID de la carte
  132. * @return Map|null La carte chargée ou null si non trouvée
  133. */
  134. public static function findMap(int $id): ?Map
  135. {
  136. $row = self::findById($id);
  137. if (!$row) {
  138. return null;
  139. }
  140. // Charger depuis le fichier JSON
  141. $map = Map::fromFile($row['file_path']);
  142. if (!$map) {
  143. AppDebugger::log('Erreur lors du chargement de la carte depuis le fichier: ' . $row['file_path'], 'ERROR');
  144. return null;
  145. }
  146. return $map;
  147. }
  148. /**
  149. * Met à jour une carte existante
  150. *
  151. * @param int $id L'ID de la carte à mettre à jour
  152. * @param Map $map Les nouvelles données de la carte
  153. * @param string|null $description Nouvelle description optionnelle
  154. * @return bool True si la mise à jour a réussi
  155. */
  156. public static function update(int $id, Map $map, ?string $description = null): bool
  157. {
  158. $pdo = self::getPDO();
  159. // Vérifier que la carte existe
  160. $existing = self::findById($id);
  161. if (!$existing) {
  162. AppDebugger::log('Carte non trouvée pour mise à jour, ID: ' . $id, 'ERROR');
  163. return false;
  164. }
  165. // Sauvegarder le fichier JSON mis à jour
  166. $filePath = $existing['file_path'];
  167. if (!$map->saveToFile($filePath)) {
  168. AppDebugger::log('Erreur lors de la sauvegarde du fichier JSON mis à jour', 'ERROR');
  169. return false;
  170. }
  171. // Mettre à jour les données
  172. $data = [
  173. 'name' => $map->getName(),
  174. 'width' => $map->getWidth(),
  175. 'height' => $map->getHeight(),
  176. 'created_at' => $existing['created_at'], // Garder la date de création originale
  177. 'updated_at' => $map->getUpdatedAt(),
  178. 'statistics' => $map->getStatistics()
  179. ];
  180. $sql = "
  181. UPDATE maps
  182. SET name = :name, description = :description, width = :width, height = :height,
  183. data = :data, updated_at = :updated_at
  184. WHERE id = :id
  185. ";
  186. try {
  187. $stmt = $pdo->prepare($sql);
  188. $stmt->execute([
  189. 'id' => $id,
  190. 'name' => $map->getName(),
  191. 'description' => $description,
  192. 'width' => $map->getWidth(),
  193. 'height' => $map->getHeight(),
  194. 'data' => json_encode($data),
  195. 'updated_at' => $map->getUpdatedAt()
  196. ]);
  197. AppDebugger::log('Carte mise à jour avec succès, ID: ' . $id, 'INFO');
  198. return true;
  199. } catch (Exception $e) {
  200. AppDebugger::log('Erreur lors de la mise à jour de la carte: ' . $e->getMessage(), 'ERROR');
  201. return false;
  202. }
  203. }
  204. /**
  205. * Supprime une carte
  206. *
  207. * @param int $id L'ID de la carte à supprimer
  208. * @return bool True si la suppression a réussi
  209. */
  210. public static function delete(int $id): bool
  211. {
  212. $pdo = self::getPDO();
  213. // Récupérer les informations de la carte avant suppression
  214. $existing = self::findById($id);
  215. if (!$existing) {
  216. AppDebugger::log('Carte non trouvée pour suppression, ID: ' . $id, 'ERROR');
  217. return false;
  218. }
  219. // Supprimer le fichier JSON
  220. if (file_exists($existing['file_path'])) {
  221. if (!unlink($existing['file_path'])) {
  222. AppDebugger::log('Erreur lors de la suppression du fichier: ' . $existing['file_path'], 'ERROR');
  223. // Ne pas arrêter la suppression pour autant
  224. }
  225. }
  226. // Supprimer de la base de données
  227. $stmt = $pdo->prepare('DELETE FROM maps WHERE id = :id');
  228. try {
  229. $stmt->execute(['id' => $id]);
  230. $affectedRows = $stmt->rowCount();
  231. if ($affectedRows > 0) {
  232. AppDebugger::log('Carte supprimée avec succès, ID: ' . $id, 'INFO');
  233. return true;
  234. } else {
  235. AppDebugger::log('Aucune carte supprimée, ID: ' . $id, 'WARNING');
  236. return false;
  237. }
  238. } catch (Exception $e) {
  239. AppDebugger::log('Erreur lors de la suppression de la carte: ' . $e->getMessage(), 'ERROR');
  240. return false;
  241. }
  242. }
  243. /**
  244. * Récupère toutes les cartes avec pagination
  245. *
  246. * @param int $page Le numéro de page (commence à 1)
  247. * @param int $perPage Nombre d'éléments par page
  248. * @param string $orderBy Colonne de tri
  249. * @param string $orderDirection Direction du tri (ASC ou DESC)
  250. * @return array Tableau avec 'data' (cartes) et 'pagination' (infos de pagination)
  251. */
  252. public static function paginate(int $page = 1, int $perPage = 10, string $orderBy = 'created_at', string $orderDirection = 'DESC'): array
  253. {
  254. $pdo = self::getPDO();
  255. // Calculer l'offset
  256. $offset = ($page - 1) * $perPage;
  257. // Compter le total
  258. $totalStmt = $pdo->query('SELECT COUNT(*) as total FROM maps');
  259. $total = $totalStmt->fetch(PDO::FETCH_ASSOC)['total'];
  260. // Récupérer les cartes
  261. $sql = "SELECT * FROM maps ORDER BY {$orderBy} {$orderDirection} LIMIT :limit OFFSET :offset";
  262. $stmt = $pdo->prepare($sql);
  263. $stmt->bindValue(':limit', $perPage, PDO::PARAM_INT);
  264. $stmt->bindValue(':offset', $offset, PDO::PARAM_INT);
  265. $stmt->execute();
  266. $maps = $stmt->fetchAll(PDO::FETCH_ASSOC);
  267. // Calculer les informations de pagination
  268. $totalPages = ceil($total / $perPage);
  269. $hasNextPage = $page < $totalPages;
  270. $hasPrevPage = $page > 1;
  271. return [
  272. 'data' => $maps,
  273. 'pagination' => [
  274. 'current_page' => $page,
  275. 'per_page' => $perPage,
  276. 'total' => (int) $total,
  277. 'total_pages' => $totalPages,
  278. 'has_next_page' => $hasNextPage,
  279. 'has_prev_page' => $hasPrevPage,
  280. 'next_page' => $hasNextPage ? $page + 1 : null,
  281. 'prev_page' => $hasPrevPage ? $page - 1 : null
  282. ]
  283. ];
  284. }
  285. /**
  286. * Recherche des cartes par nom ou description
  287. *
  288. * @param string $query Le terme de recherche
  289. * @param int $limit Nombre maximum de résultats
  290. * @return array Les cartes trouvées
  291. */
  292. public static function search(string $query, int $limit = 20): array
  293. {
  294. $pdo = self::getPDO();
  295. // S'assurer que limit est un entier positif
  296. $limit = max(1, min(100, (int)$limit));
  297. // Recherche dans le nom et la description
  298. $sql = "SELECT * FROM maps
  299. WHERE name LIKE :query OR description LIKE :query
  300. ORDER BY name
  301. LIMIT $limit";
  302. $stmt = $pdo->prepare($sql);
  303. $searchTerm = '%' . $query . '%';
  304. $stmt->execute(['query' => $searchTerm]);
  305. return $stmt->fetchAll(PDO::FETCH_ASSOC);
  306. }
  307. /**
  308. * Récupère les statistiques globales des cartes
  309. *
  310. * @return array Statistiques générales
  311. */
  312. public static function getGlobalStats(): array
  313. {
  314. $pdo = self::getPDO();
  315. // Compter le nombre total de cartes
  316. $totalStmt = $pdo->query('SELECT COUNT(*) as total FROM maps');
  317. $total = $totalStmt->fetch(PDO::FETCH_ASSOC)['total'];
  318. // Dimensions moyennes
  319. $dimensionsStmt = $pdo->query('SELECT AVG(width) as avg_width, AVG(height) as avg_height FROM maps');
  320. $dimensions = $dimensionsStmt->fetch(PDO::FETCH_ASSOC);
  321. // Dernière carte créée
  322. $latestStmt = $pdo->query('SELECT name, created_at FROM maps ORDER BY created_at DESC LIMIT 1');
  323. $latest = $latestStmt->fetch(PDO::FETCH_ASSOC);
  324. return [
  325. 'total_maps' => (int) $total,
  326. 'average_width' => round((float) $dimensions['avg_width'], 1),
  327. 'average_height' => round((float) $dimensions['avg_height'], 1),
  328. 'latest_map' => $latest ?: null
  329. ];
  330. }
  331. /**
  332. * Vérifie si une carte existe
  333. *
  334. * @param int $id L'ID de la carte
  335. * @return bool True si la carte existe
  336. */
  337. public static function exists(int $id): bool
  338. {
  339. $pdo = self::getPDO();
  340. $stmt = $pdo->prepare('SELECT COUNT(*) as count FROM maps WHERE id = :id');
  341. $stmt->execute(['id' => $id]);
  342. return $stmt->fetch(PDO::FETCH_ASSOC)['count'] > 0;
  343. }
  344. /**
  345. * Duplique une carte existante
  346. *
  347. * @param int $id L'ID de la carte à dupliquer
  348. * @param string $newName Le nom de la nouvelle carte
  349. * @return int|false L'ID de la nouvelle carte ou false en cas d'erreur
  350. */
  351. public static function duplicate(int $id, string $newName): int|false
  352. {
  353. $originalMap = self::findMap($id);
  354. if (!$originalMap) {
  355. AppDebugger::log('Carte originale non trouvée pour duplication, ID: ' . $id, 'ERROR');
  356. return false;
  357. }
  358. // Créer une nouvelle carte avec le même contenu
  359. $newMap = new Map($newName, $originalMap->getWidth(), $originalMap->getHeight());
  360. // Copier toutes les tuiles
  361. for ($q = 0; $q < $originalMap->getWidth(); $q++) {
  362. for ($r = 0; $r < $originalMap->getHeight(); $r++) {
  363. $tile = $originalMap->getTile($q, $r);
  364. if ($tile) {
  365. $newMap->setTile($q, $r, $tile);
  366. }
  367. }
  368. }
  369. // Créer la nouvelle carte en base
  370. $description = "Copie de la carte #" . $id;
  371. return self::create($newMap, $description);
  372. }
  373. /**
  374. * Exporte une carte vers un format spécifique
  375. *
  376. * @param int $id L'ID de la carte
  377. * @param string $format Le format d'export ('json', 'csv', etc.)
  378. * @return string|false Les données exportées ou false en cas d'erreur
  379. */
  380. public static function export(int $id, string $format = 'json'): string|false
  381. {
  382. $map = self::findMap($id);
  383. if (!$map) {
  384. return false;
  385. }
  386. switch ($format) {
  387. case 'json':
  388. return $map->toJson(true);
  389. case 'csv':
  390. return self::exportToCSV($map);
  391. default:
  392. AppDebugger::log('Format d\'export non supporté: ' . $format, 'ERROR');
  393. return false;
  394. }
  395. }
  396. /**
  397. * Exporte une carte au format CSV
  398. *
  399. * @param Map $map La carte à exporter
  400. * @return string Les données CSV
  401. */
  402. private static function exportToCSV(Map $map): string
  403. {
  404. $csv = "q,r,type,resources,temperature,elevation\n";
  405. for ($q = 0; $q < $map->getWidth(); $q++) {
  406. for ($r = 0; $r < $map->getHeight(); $r++) {
  407. $tile = $map->getTile($q, $r);
  408. if ($tile && !$tile->isEmpty()) {
  409. $resources = json_encode($tile->getResources());
  410. $csv .= sprintf(
  411. "%d,%d,%s,%s,%s,%s\n",
  412. $q,
  413. $r,
  414. $tile->getType() ?? '',
  415. $resources,
  416. $tile->getTemperature() ?? '',
  417. $tile->getElevation() ?? ''
  418. );
  419. }
  420. }
  421. }
  422. return $csv;
  423. }
  424. }
  425. }