src/Controller/LivresController.php line 68

Open in your IDE?
  1. <?php
  2. namespace App\Controller;
  3. use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
  4. use Symfony\Component\Routing\Annotation\Route;
  5. use Symfony\Component\HttpFoundation\Response;
  6. use Symfony\Component\HttpFoundation\JsonResponse;
  7. use App\Entity\Livre;
  8. use App\Service\BookCoverService;
  9. use Symfony\Component\HttpFoundation\Request;
  10. use Knp\Component\Pager\PaginatorInterface;
  11. use Doctrine\ORM\EntityManagerInterface;
  12. class LivresController extends AbstractController
  13. {
  14. private BookCoverService $bookCoverService;
  15. private EntityManagerInterface $em;
  16. public function __construct(BookCoverService $bookCoverService, EntityManagerInterface $em)
  17. {
  18. $this->bookCoverService = $bookCoverService;
  19. $this->em = $em;
  20. }
  21. /**
  22. * @Route("/listelivre", name="listesLivres")
  23. */
  24. public function listesLivres(Request $request, PaginatorInterface $paginator)
  25. {
  26. $detect = new \Mobile_Detect;
  27. $em = $this->getDoctrine()->getManager();
  28. $listeLivreId = $em->getRepository(Livre::class)->getAllLivres($request->get('user'),$request->get('sort'), $request->get('order'));
  29. $images = array();
  30. $page = $paginator->paginate(
  31. $listeLivreId,
  32. $request->query->getInt('page', 1),
  33. 100
  34. );
  35. $page->setCustomParameters([
  36. 'align' => 'center', # center|right (for template: twitter_bootstrap_v4_pagination)
  37. 'style' => 'bottom',
  38. 'span_class' => 'whatever',
  39. ]);
  40. $listeLivre = $em->getRepository(Livre::class)->getLivresByID($page->getItems(), $request->get('sort'), $request->get('order'));
  41. foreach ($listeLivre as $livre) {
  42. if ($livre && $livre->getImage()) {
  43. $images[$livre->getId()] = base64_encode(stream_get_contents($livre->getImage()));
  44. }
  45. }
  46. return $this->render('pages/listelivre.html.twig', ['pagination' => $page, 'Listelivres' => $listeLivre, 'images'=> $images, 'mobile' => $detect->isMobile()]) ;
  47. }
  48. /**
  49. * @Route("/livre/{id}", name="livreDetail")
  50. */
  51. public function livreDetail(string $id, Request $request)
  52. {
  53. $detect = new \Mobile_Detect;
  54. $em = $this->getDoctrine()->getManager();
  55. $livre = $em->getRepository(Livre::class)->findOneById($id);
  56. return $this->render('pages/livreDetail.html.twig', ['livre' => $livre, 'mobile' => $detect->isMobile()]) ;
  57. }
  58. /**
  59. * @Route("/recherche", name="searchBook")
  60. */
  61. public function searchBook(Request $request, PaginatorInterface $paginator)
  62. {
  63. $em = $this->getDoctrine()->getManager();
  64. $detect = new \Mobile_Detect;
  65. $users = $em->getRepository(\App\Entity\User::class)->findAll();
  66. if($request->query->has('value')){
  67. $search = $request->get('value');
  68. $user = $request->get('user');
  69. $userName = null;
  70. $userId = null;
  71. if ($user == '0' || empty($user)){
  72. $user = null;
  73. } else {
  74. $userId = (int) $user;
  75. $userEntity = $em->getRepository(\App\Entity\User::class)->find($user);
  76. if ($userEntity) {
  77. $userName = $userEntity->getUsername();
  78. }
  79. }
  80. $listeLivreID = $em->getRepository(Livre::class)->getSearchLivre2($search, $request->get('sort'), $request->get('order'), $user);
  81. $images = array();
  82. // S'assurer que listeLivreID est un tableau
  83. if (!is_array($listeLivreID)) {
  84. $listeLivreID = [];
  85. }
  86. $totalResults = count($listeLivreID);
  87. if($listeLivreID && $totalResults > 0) {
  88. $page = $paginator->paginate(
  89. $listeLivreID,
  90. $request->query->getInt('page', 1),
  91. 100
  92. );
  93. $page->setCustomParameters([
  94. 'align' => 'center',
  95. 'style' => 'bottom',
  96. 'span_class' => 'whatever',
  97. ]);
  98. $listeLivre = $em->getRepository(Livre::class)->getLivresByID($page->getItems(), $request->get('sort'), $request->get('order'));
  99. foreach ($listeLivre as $livre) {
  100. if ($livre && $livre->getImage()) {
  101. $images[$livre->getId()] = base64_encode(stream_get_contents($livre->getImage()));
  102. }
  103. }
  104. return $this->render('pages/listelivre.html.twig', [
  105. 'pagination' => $page,
  106. 'Listelivres' => $listeLivre,
  107. 'images'=> $images,
  108. 'mobile' => $detect->isMobile(),
  109. 'searchQuery' => $search,
  110. 'searchUser' => $userName,
  111. 'searchUserId' => $userId,
  112. 'users' => $users,
  113. 'totalResults' => $totalResults,
  114. 'isSearchResult' => true
  115. ]);
  116. }
  117. return $this->render('pages/listelivre.html.twig', [
  118. 'pagination' => null,
  119. 'Listelivres' => [],
  120. 'images'=> [],
  121. 'mobile' => $detect->isMobile(),
  122. 'searchQuery' => $search,
  123. 'searchUser' => $userName,
  124. 'searchUserId' => $userId,
  125. 'users' => $users,
  126. 'totalResults' => 0,
  127. 'isSearchResult' => true
  128. ]);
  129. }
  130. $this->addFlash('warning', 'Veuillez entrer un terme de recherche');
  131. return $this->redirectToRoute('index');
  132. }
  133. /**
  134. * @Route("/listelivreUser/{id}", name="listelivreUser")
  135. */
  136. public function listesLivresbyUser(string $id, Request $request, PaginatorInterface $paginator)
  137. {
  138. $em = $this->getDoctrine()->getManager();
  139. $detect = new \Mobile_Detect;
  140. $listeLivreId = $em->getRepository(Livre::class)->getAllLivresByUser($id, $request->get('sort'), $request->get('order'));
  141. $images = array();
  142. $page = $paginator->paginate(
  143. $listeLivreId,
  144. $request->query->getInt('page', 1),
  145. 100
  146. );
  147. $page->setCustomParameters([
  148. 'align' => 'center', # center|right (for template: twitter_bootstrap_v4_pagination)
  149. 'style' => 'bottom',
  150. 'span_class' => 'whatever',
  151. ]);
  152. $listeLivre = $em->getRepository(Livre::class)->getLivresByID($page->getItems(), $request->get('sort'), $request->get('order'));
  153. foreach ($listeLivre as $livre) {
  154. if ($livre && $livre->getImage()) {
  155. $images[$livre->getId()] = base64_encode(stream_get_contents($livre->getImage()));
  156. }
  157. }
  158. return $this->render('pages/listelivre.html.twig', ['pagination' => $page, 'Listelivres' => $listeLivre, 'images'=> $images, 'mobile' => $detect->isMobile()]) ;
  159. }
  160. /**
  161. * @Route("/livre/{id}/rechercher-couverture", name="livre_search_cover", requirements={"id"="\d+"})
  162. */
  163. public function searchCover(int $id): JsonResponse
  164. {
  165. $livre = $this->em->getRepository(Livre::class)->find($id);
  166. if (!$livre) {
  167. return new JsonResponse(['success' => false, 'message' => 'Livre non trouvé'], 404);
  168. }
  169. $images = [];
  170. // 1. Essayer de scraper l'URL Amazon/externe du livre si elle existe
  171. $amazonUrl = $livre->getAmazon();
  172. if (!empty($amazonUrl)) {
  173. $scrapedImages = $this->bookCoverService->scrapeImagesFromUrl($amazonUrl);
  174. foreach ($scrapedImages as $url) {
  175. $images[] = ['url' => $url, 'source' => 'Page produit'];
  176. }
  177. }
  178. // 2. Essayer via ISBN si disponible
  179. $isbn = $livre->getIsbn();
  180. if (!empty($isbn)) {
  181. $result = $this->bookCoverService->findBestCover($isbn);
  182. if ($result['url']) {
  183. $images[] = ['url' => $result['url'], 'source' => $result['source']];
  184. }
  185. }
  186. if (!empty($images)) {
  187. return new JsonResponse([
  188. 'success' => true,
  189. 'images' => $images,
  190. 'message' => count($images) . ' image(s) trouvée(s)'
  191. ]);
  192. }
  193. return new JsonResponse([
  194. 'success' => false,
  195. 'message' => 'Aucune image trouvée'
  196. ]);
  197. }
  198. /**
  199. * @Route("/livre/{id}/mettre-a-jour-couverture", name="livre_update_cover", methods={"POST"}, requirements={"id"="\d+"})
  200. */
  201. public function updateCover(int $id, Request $request): JsonResponse
  202. {
  203. $this->denyAccessUnlessGranted('IS_AUTHENTICATED_FULLY');
  204. $livre = $this->em->getRepository(Livre::class)->find($id);
  205. if (!$livre) {
  206. return new JsonResponse(['success' => false, 'message' => 'Livre non trouvé'], 404);
  207. }
  208. $imageUrl = $request->request->get('image_url');
  209. if (empty($imageUrl)) {
  210. return new JsonResponse(['success' => false, 'message' => 'URL de l\'image manquante']);
  211. }
  212. // Stocker l'URL au lieu de télécharger l'image
  213. $livre->setImageUrl($imageUrl);
  214. $this->em->flush();
  215. return new JsonResponse([
  216. 'success' => true,
  217. 'message' => 'URL de l\'image enregistrée avec succès',
  218. 'imageUrl' => $imageUrl
  219. ]);
  220. }
  221. /**
  222. * @Route("/livre/{id}/supprimer-url-couverture", name="livre_remove_cover_url", methods={"POST"}, requirements={"id"="\d+"})
  223. */
  224. public function removeCoverUrl(int $id): JsonResponse
  225. {
  226. $this->denyAccessUnlessGranted('IS_AUTHENTICATED_FULLY');
  227. $livre = $this->em->getRepository(Livre::class)->find($id);
  228. if (!$livre) {
  229. return new JsonResponse(['success' => false, 'message' => 'Livre non trouvé'], 404);
  230. }
  231. $livre->setImageUrl(null);
  232. $this->em->flush();
  233. return new JsonResponse([
  234. 'success' => true,
  235. 'message' => 'URL de l\'image supprimée, l\'image originale sera utilisée'
  236. ]);
  237. }
  238. /**
  239. * @Route("/livres/mettre-a-jour-toutes-couvertures", name="livres_update_all_covers")
  240. */
  241. public function updateAllCovers(): Response
  242. {
  243. $this->denyAccessUnlessGranted('IS_AUTHENTICATED_FULLY');
  244. $livres = $this->em->getRepository(Livre::class)->findAll();
  245. $updated = 0;
  246. foreach ($livres as $livre) {
  247. $isbn = $livre->getIsbn();
  248. // Ne pas mettre à jour si déjà une URL ou pas d'ISBN
  249. if (empty($isbn) || !empty($livre->getImageUrl())) {
  250. continue;
  251. }
  252. $result = $this->bookCoverService->findBestCover($isbn);
  253. if ($result['url']) {
  254. $livre->setImageUrl($result['url']);
  255. $updated++;
  256. }
  257. }
  258. $this->em->flush();
  259. $this->addFlash('success', $updated . ' URL(s) de couverture(s) ajoutée(s)');
  260. return $this->redirectToRoute('listesLivres');
  261. }
  262. /**
  263. * @Route("/livre/{id}/scrape-covers", name="livre_scrape_covers")
  264. */
  265. public function scrapeCovers(string $id): JsonResponse
  266. {
  267. $livre = $this->em->getRepository(Livre::class)->findOneById($id);
  268. if (!$livre) {
  269. return new JsonResponse(['error' => 'Livre non trouvé'], 404);
  270. }
  271. $isbn = $livre->getIsbn();
  272. if (!$isbn) {
  273. return new JsonResponse(['error' => 'Ce livre n\'a pas d\'ISBN'], 400);
  274. }
  275. // Scraper toutes les sources via Puppeteer
  276. $images = $this->bookCoverService->findAllCovers($isbn);
  277. return new JsonResponse([
  278. 'success' => true,
  279. 'images' => $images,
  280. 'isbn' => $isbn,
  281. 'count' => count($images)
  282. ]);
  283. }
  284. /**
  285. * @Route("/livre/{id}/update-cover-url", name="livre_update_cover_url", methods={"POST"})
  286. */
  287. public function updateCoverUrl(string $id, Request $request): Response
  288. {
  289. $livre = $this->em->getRepository(Livre::class)->findOneById($id);
  290. if (!$livre) {
  291. return new JsonResponse(['error' => 'Livre non trouvé'], 404);
  292. }
  293. $imageUrl = $request->request->get('imageUrl');
  294. if (!$imageUrl) {
  295. return new JsonResponse(['error' => 'URL d\'image requise'], 400);
  296. }
  297. try {
  298. // Créer le répertoire si nécessaire
  299. $uploadDir = $this->getParameter('kernel.project_dir') . '/public/uploads/covers';
  300. if (!is_dir($uploadDir)) {
  301. if (!mkdir($uploadDir, 0755, true)) {
  302. return new JsonResponse(['error' => 'Impossible de créer le répertoire uploads/covers'], 500);
  303. }
  304. }
  305. // Vérifier que le répertoire est accessible en écriture
  306. if (!is_writable($uploadDir)) {
  307. return new JsonResponse(['error' => 'Le répertoire uploads/covers n\'est pas accessible en écriture'], 500);
  308. }
  309. // Télécharger l'image avec contexte pour gérer les redirections
  310. $context = stream_context_create([
  311. 'http' => [
  312. 'follow_location' => true,
  313. 'max_redirects' => 5,
  314. 'timeout' => 30,
  315. 'user_agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
  316. ],
  317. 'ssl' => [
  318. 'verify_peer' => false,
  319. 'verify_peer_name' => false
  320. ]
  321. ]);
  322. $imageContent = @file_get_contents($imageUrl, false, $context);
  323. if ($imageContent === false) {
  324. $error = error_get_last();
  325. return new JsonResponse([
  326. 'error' => 'Impossible de télécharger l\'image',
  327. 'details' => $error ? $error['message'] : 'Erreur inconnue',
  328. 'url' => $imageUrl
  329. ], 500);
  330. }
  331. // Vérifier que le contenu est bien une image
  332. if (strlen($imageContent) < 100) {
  333. return new JsonResponse(['error' => 'Le contenu téléchargé ne semble pas être une image valide'], 500);
  334. }
  335. // Générer un nom de fichier unique
  336. $extension = pathinfo(parse_url($imageUrl, PHP_URL_PATH), PATHINFO_EXTENSION);
  337. if (empty($extension) || !in_array(strtolower($extension), ['jpg', 'jpeg', 'png', 'gif', 'webp'])) {
  338. $extension = 'jpg';
  339. }
  340. $filename = $livre->getId() . '_' . uniqid() . '.' . $extension;
  341. $filepath = $uploadDir . '/' . $filename;
  342. // Sauvegarder l'image
  343. $bytesWritten = @file_put_contents($filepath, $imageContent);
  344. if ($bytesWritten === false) {
  345. return new JsonResponse(['error' => 'Impossible de sauvegarder l\'image sur le disque'], 500);
  346. }
  347. // Supprimer l'ancienne image si elle existe
  348. if ($livre->getImage2()) {
  349. $oldFile = $uploadDir . '/' . $livre->getImage2();
  350. if (file_exists($oldFile)) {
  351. @unlink($oldFile);
  352. }
  353. }
  354. // Mettre à jour le livre
  355. $livre->setImage2($filename);
  356. $this->em->flush();
  357. return new JsonResponse([
  358. 'success' => true,
  359. 'message' => 'Image de couverture mise à jour',
  360. 'filename' => $filename
  361. ]);
  362. } catch (\Exception $e) {
  363. return new JsonResponse(['error' => 'Erreur: ' . $e->getMessage()], 500);
  364. }
  365. }
  366. }