<?php
namespace App\Controller\Frontend;
use App\Controller\Frontend\Ranking\RankingPdfData;
use App\Controller\Shared\Club\ResolveClubLogoSvgUrl;
use App\Controller\Shared\Ranking\CalculateRankingCached;
use App\Entity\Backend\Club;
use App\Entity\Backend\Competicion;
use App\Entity\Backend\Jugador;
use App\Entity\Gestion\BonusRanking;
use App\Entity\Gestion\ClasificadoRanking;
use App\Entity\Gestion\PenalidadRanking;
use App\Entity\Gestion\Ranking;
use App\Util\PrintPdf;
use Doctrine\Persistence\ManagerRegistry;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\ResponseHeaderBag;
use Symfony\Component\Routing\Annotation\Route;
class RankingController extends AbstractController {
private $em;
public function __construct(ManagerRegistry $doctrine){
$this->em = $doctrine->getManager();
}
/**
* F-122 Visualización del ranking
*
* @Route("/ranking/{id}", name="ranking", options={"expose"=true})
*/
public function ranking($id, CalculateRankingCached $calculateRankingCached): RedirectResponse|Response
{
$id = intval($id);
$ranking = $this->em->getRepository(Ranking::class)->find($id);
if (!$ranking) {
$this->addFlash(
'error', "El ranking seleccionado no existe");
return $this->redirect($this->generateUrl('portada'));
}
$params = $calculateRankingCached->__invoke($id);
return $this->render("frontend/Ranking/ranking.html.twig", $params);
}
/**
* @Route("/ranking/{id}/pdf/{filename}", name="ranking_pdf", requirements={"id": "\d+", "filename": ".+\.pdf"}, defaults={"filename"=null})
*/
public function rankingPdf(
$id,
?string $filename,
CalculateRankingCached $calculateRankingCached,
RankingPdfData $rankingPdfData,
ResolveClubLogoSvgUrl $resolveClubLogoSvgUrl,
PrintPdf $printPdf,
): RedirectResponse|Response {
$id = intval($id);
$ranking = $this->em->getRepository(Ranking::class)->find($id);
if (!$ranking) {
$this->addFlash('error', "El ranking seleccionado no existe");
return $this->redirect($this->generateUrl('portada'));
}
if (!$ranking->getActivo() && !$this->isGranted('ROLE_ADMIN') && !$this->isGranted('ROLE_FEDE') && !$this->isGranted('ROLE_CLUB')) {
throw $this->createAccessDeniedException();
}
$pdfFilename = $this->pdfFilename($ranking);
if ($filename !== $pdfFilename) {
return $this->redirectToRoute('ranking_pdf', [
'id' => $ranking->getId(),
'filename' => $pdfFilename,
]);
}
$html = $this->renderRankingPdfHtml($id, $ranking, $calculateRankingCached, $rankingPdfData, $resolveClubLogoSvgUrl);
$pdf = $this->generateRankingPdfWithGotenberg($html, $printPdf);
$response = new Response($pdf);
$response->headers->set('Content-Type', 'application/pdf');
$response->headers->set(
'Content-Disposition',
$response->headers->makeDisposition(ResponseHeaderBag::DISPOSITION_INLINE, $pdfFilename)
);
$response->headers->set('X-Robots-Tag', 'noindex');
return $response;
}
private function generateRankingPdfWithGotenberg(string $html, PrintPdf $printPdf): string
{
$temporaryBaseFile = tempnam(sys_get_temp_dir(), 'ranking_pdf_');
if ($temporaryBaseFile === false) {
throw new \RuntimeException('No se pudo crear el archivo temporal del ranking');
}
$htmlFile = $temporaryBaseFile . '.html';
$pdfFile = $temporaryBaseFile . '.pdf';
try {
if (file_exists($temporaryBaseFile)) {
unlink($temporaryBaseFile);
}
file_put_contents($htmlFile, $html);
$printPdf->sendHtmlToGeneratePdf($temporaryBaseFile);
$pdf = file_get_contents($pdfFile);
if ($pdf === false) {
throw new \RuntimeException('No se pudo leer el PDF generado del ranking');
}
return $pdf;
} finally {
if (file_exists($temporaryBaseFile)) {
unlink($temporaryBaseFile);
}
if (file_exists($htmlFile)) {
unlink($htmlFile);
}
if (file_exists($pdfFile)) {
unlink($pdfFile);
}
}
}
private function renderRankingPdfHtml(
int $id,
Ranking $ranking,
CalculateRankingCached $calculateRankingCached,
RankingPdfData $rankingPdfData,
ResolveClubLogoSvgUrl $resolveClubLogoSvgUrl,
): string {
$params = $calculateRankingCached->__invoke($id);
$ownerCode = $rankingPdfData->ownerCode($ranking);
$pdfData = $rankingPdfData->__invoke($params);
return $this->renderView('frontend/Ranking/ranking-pdf.html.twig', array_merge($params, [
'rankingPdf' => $pdfData,
'rankingPdfColors' => $rankingPdfData->colors($ownerCode),
'rankingPdfLogoUrl' => $this->pdfLogoDataUri($resolveClubLogoSvgUrl((string)$ownerCode)),
]));
}
private function pdfLogoDataUri(string $logoUrl): string
{
$logoUrl = $this->preferPngLogoUrl($logoUrl);
$contents = @file_get_contents($logoUrl);
if ($contents === false || $contents === '') {
return $logoUrl;
}
$extension = strtolower((string)pathinfo(parse_url($logoUrl, PHP_URL_PATH) ?: '', PATHINFO_EXTENSION));
$mimeType = match ($extension) {
'png' => 'image/png',
'jpg', 'jpeg' => 'image/jpeg',
'webp' => 'image/webp',
'svg' => 'image/svg+xml',
default => 'image/png',
};
return 'data:' . $mimeType . ';base64,' . base64_encode($contents);
}
private function preferPngLogoUrl(string $logoUrl): string
{
$pngLogoUrl = str_replace(['/SVG/', '.svg'], ['/PNG/', '.png'], $logoUrl);
return $pngLogoUrl !== $logoUrl && $this->remoteFileExists($pngLogoUrl) ? $pngLogoUrl : $logoUrl;
}
private function remoteFileExists(string $url): bool
{
$headers = @get_headers($url);
$statusLine = $headers[0] ?? '';
return $statusLine !== '' && (
str_contains($statusLine, '200')
|| str_contains($statusLine, '301')
|| str_contains($statusLine, '302')
);
}
private function pdfFilename(Ranking $ranking): string
{
$filename = iconv('UTF-8', 'ASCII//TRANSLIT', (string)$ranking->getNombre());
$filename = preg_replace('/[^A-Za-z0-9._-]+/', '-', $filename ?: '');
$filename = trim((string)$filename, '-');
if ($filename === '') {
$filename = 'ranking-' . $ranking->getId();
}
return $filename . '.pdf';
}
/**
* F-123 Visualización de los rankings de un club
*
* @Route("/rankings-club/{id}", name="rankings_club")
*/
public function rankingsClub($id): RedirectResponse|Response
{
$club = $this->em->getRepository(Club::class)->find($id);
if (!$club) {
$this->addFlash('error', "No encontramos el Club que nos ha solicitado");
return $this->redirect($this->generateUrl('provincias'));
}
$isFederation = $club->isFederationClub();
$clientId = $isFederation ? $club->getCliente()->getId() : null;
$rankings = $this->em->getRepository(Ranking::class)->obtainRankingsByClubOrClient($club->getId(), $clientId, $isFederation, false);
return $this->render("frontend/Ranking/rankingsClub.html.twig", [
"rankings" => $rankings,
"club" => $club
]);
}
}