Init project

This commit is contained in:
2026-01-11 16:19:42 +01:00
commit df59325836
380 changed files with 33805 additions and 0 deletions

0
src/Controller/.gitignore vendored Normal file
View File

View File

@@ -0,0 +1,38 @@
<?php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
final class BaseAdminController extends AbstractController
{
#[Route('/admin', name: 'admin_dashboard')]
public function admin(): Response
{
return $this->render('base-admin/index.html.twig', []);
}
#[Route('/sidebar', name: 'admin_sidebar')]
public function sidebar(string $path): Response
{
return $this->render('base-admin/sidebar.html.twig', [
'path' => $path,
'title' => $this->mapPathToFrenchTitle($path)
]);
}
private function mapPathToFrenchTitle(string $path): string
{
$mapping = [
'/admin' => 'Tableau de bord',
'/admin/module' => 'Module',
'/admin/news' => 'Actualité',
'/admin/image' => 'Image',
'/admin/news/add' => 'Ajouter une actualité',
];
return $mapping[$path] ?? 'Inconnu';
}
}

View File

@@ -0,0 +1,22 @@
<?php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
final class BaseController extends AbstractController
{
#[Route('/header', name: 'header')]
public function header(): Response
{
return $this->render('base/header.html.twig', []);
}
#[Route('/footer', name: 'footer')]
public function footer(): Response
{
return $this->render('base/footer.html.twig', []);
}
}

View File

@@ -0,0 +1,28 @@
<?php
namespace App\Controller;
use App\Entity\Feature;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
final class FeatureController extends AbstractController
{
private EntityManagerInterface $entityManager;
public function __construct(EntityManagerInterface $entityManager)
{
$this->entityManager = $entityManager;
}
#[Route('/admin/feature', name: 'admin_feature_index')]
public function index(): Response
{
$features = $this->entityManager->getRepository(Feature::class)->findAll();
return $this->render('feature/index.html.twig', [
'features' => $features,
]);
}
}

View File

@@ -0,0 +1,89 @@
<?php
namespace App\Controller;
use App\Entity\Image;
use App\Service\ImageService;
use Doctrine\ORM\EntityManagerInterface;
use Dom\Entity;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
final class ImageController extends AbstractController
{
private EntityManagerInterface $entityManager;
private ImageService $imageService;
public function __construct(EntityManagerInterface $entityManager, ImageService $imageService)
{
$this->entityManager = $entityManager;
$this->imageService = $imageService;
}
#[Route('/admin/image', name: 'admin_image_index')]
public function index(): Response
{
$images = $this->entityManager->getRepository(Image::class)->findAll();
return $this->render('image/index.html.twig', [
'images' => $images,
]);
}
#[Route('/admin/image/add', name: 'admin_image_add')]
public function add(): Response
{
return $this->render('image/add.html.twig');
}
#[Route('/admin/image/upload', name: 'admin_image_upload', methods: ['POST'])]
public function upload(Request $request): JsonResponse
{
$files = $request->files->get('file-upload', []);
foreach ($files as $file) {
if ($file && $file->isValid()) {
$originalName = pathinfo($file->getClientOriginalName(), PATHINFO_FILENAME);
$extension = $file->getClientOriginalExtension();
$filename = strtolower($originalName . '-' . uniqid() . '.' . $extension);
$stream = fopen($file->getPathname(), 'rb');
// Upload vers MinIO
$result = $this->imageService->upload($filename, $stream);
if ($result === true) {
$image = new Image();
$image->setName($filename);
$image->setUrl($this->getParameter('minio_endpoint') . '/' . $this->getParameter('minio_bucket') . '/' . $filename);
$image->setSize($file->getSize());
$this->entityManager->persist($image);
}
}
}
$this->entityManager->flush();
return new JsonResponse([
'success' => true,
]);
}
#[Route('/admin/image/delete/{id}', name: 'admin_image_delete', methods: ['GET', 'POST'])]
public function delete(Request $request, Image $image, EntityManagerInterface $em): Response
{
$submittedToken = $request->request->get('_token');
if ($this->isCsrfTokenValid('delete_image' . $image->getId(), $submittedToken)) {
$result = $this->imageService->delete($image);
if ($result === true) {
$em->remove($image);
$em->flush();
} else {
$this->addFlash('error', 'L\'image est utilisée et ne peut pas être supprimée.');
}
}
return $this->redirectToRoute('admin_image_index');
}
}

View File

@@ -0,0 +1,184 @@
<?php
namespace App\Controller;
use App\Entity\Image;
use App\Entity\News;
use App\Entity\NewsImage;
use App\Form\NewsType;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Form\FormError;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\String\Slugger\SluggerInterface;
final class NewsController extends AbstractController
{
private EntityManagerInterface $entityManager;
private SluggerInterface $slugger;
public function __construct(EntityManagerInterface $entityManager, SluggerInterface $slugger)
{
$this->entityManager = $entityManager;
$this->slugger = $slugger;
}
#[Route('/admin/news', name: 'admin_news_index')]
public function index(Request $request): Response
{
$news = $this->entityManager->getRepository(News::class)->findBy([], ['ordre' => 'ASC']);
return $this->render('news/index.html.twig', [
'news' => $news
]);
}
#[Route('/admin/news/add', name: 'admin_news_add')]
public function add(Request $request): Response
{
$form = $this->createForm(NewsType::class);
$images = $this->entityManager->getRepository(Image::class)->findAll();
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$news = $form->getData();
$slug = strtolower($this->slugger->slug($news->getTitle()));
if ($this->entityManager->getRepository(News::class)->findOneBy(['slug' => $slug])) {
$this->addFlash('error', 'Le titre est déjà utilisé.');
return $this->redirectToRoute('admin_news_add');
}
$news->setSlug($slug);
$lastestNews = $this->entityManager->getRepository(News::class)->findOneBy([], ['ordre' => 'DESC']);
$newsOrdre = $lastestNews ? $lastestNews->getOrdre() + 1 : 1;
$news->setOrdre($newsOrdre);
$news->setCreatedAt(new \DateTimeImmutable());
$news->setUpdatedAt(new \DateTimeImmutable());
$news->setIsActive(true);
$this->entityManager->persist($news);
$selectedImageIds = $request->request->all('selectedImages');
if (!empty($selectedImageIds)) {
$imageRepository = $this->entityManager->getRepository(Image::class);
foreach ($selectedImageIds as $key => $imageId) {
$image = $imageRepository->find($imageId);
$newsImage = new NewsImage();
$newsImage->setNews($news);
$newsImage->setImage($image);
$newsImage->setOrdre($key);
$this->entityManager->persist($newsImage);
}
}
$this->entityManager->flush();
return $this->redirectToRoute('admin_news_index');
}
return $this->render('news/add.html.twig', [
'form' => $form->createView(),
'images' => $images,
]);
}
#[Route('/admin/news/edit/{id}', name: 'admin_news_edit')]
public function edit(Request $request, News $news): Response
{
$form = $this->createForm(NewsType::class, $news);
$images = $this->entityManager->getRepository(Image::class)->findAll();
$selectedImages = [];
foreach ($news->getNewsImages() as $newsImage) {
$selectedImages[] = $newsImage->getImage();
}
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$news = $form->getData();
$news->setUpdatedAt(new \DateTimeImmutable());
foreach ($news->getNewsImages() as $newsImage) {
$this->entityManager->remove($newsImage);
}
$selectedImageIds = $request->request->all('selectedImages');
if (!empty($selectedImageIds)) {
$imageRepository = $this->entityManager->getRepository(Image::class);
foreach ($selectedImageIds as $key => $imageId) {
$image = $imageRepository->find($imageId);
$newsImage = new NewsImage();
$newsImage->setNews($news);
$newsImage->setImage($image);
$newsImage->setOrdre($key);
$this->entityManager->persist($newsImage);
}
}
$this->entityManager->flush();
return $this->redirectToRoute('admin_news_index');
}
return $this->render('news/edit.html.twig', [
'form' => $form->createView(),
'images' => $images,
'selectedImages' => $selectedImages,
]);
}
#[Route('/admin/news/delete/{id}', name: 'admin_news_delete', methods: ['POST'])]
public function delete(Request $request, News $news): Response
{
$submittedToken = $request->request->get('_token');
if ($this->isCsrfTokenValid('delete' . $news->getId(), $submittedToken)) {
$newsImages = $news->getNewsImages();
foreach ($newsImages as $newsImage) {
$this->entityManager->remove($newsImage);
}
$this->entityManager->remove($news);
$this->entityManager->flush();
$allNews = $this->entityManager->getRepository(News::class)->findBy([], ['ordre' => 'ASC']);
foreach ($allNews as $index => $item) {
$item->setOrdre($index + 1);
}
$this->entityManager->flush();
}
return $this->redirectToRoute('admin_news_index');
}
#[Route('/admin/news/reorder', name: 'admin_news_reorder', methods: ['POST'])]
public function reorder(Request $request, EntityManagerInterface $em): JsonResponse
{
$data = json_decode($request->getContent(), true);
foreach ($data['order'] as $item) {
$news = $em->getRepository(News::class)->find($item['id']);
if ($news) {
$news->setOrdre($item['position']);
}
}
$em->flush();
return new JsonResponse(['status' => 'ok']);
}
#[Route('/admin/news/toggle-active/{id}', name: 'admin_news_toggle_active', methods: ['POST'])]
public function toggleActive(Request $request, News $news, EntityManagerInterface $em): Response
{
$token = $request->request->get('_token');
if ($this->isCsrfTokenValid('toggle' . $news->getId(), $token)) {
$current = (bool) $request->request->get('active', 0);
$news->setIsActive(!$current);
$em->flush();
}
return $this->redirectToRoute('admin_news_index');
}
}

View File

@@ -0,0 +1,68 @@
<?php
namespace App\Controller;
use App\Entity\News;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
final class PageController extends AbstractController
{
private EntityManagerInterface $entityManager;
public function __construct(EntityManagerInterface $entityManager)
{
$this->entityManager = $entityManager;
}
#[Route('/', name: 'home')]
public function index(): Response
{
return $this->render('page/home.html.twig', []);
}
#[Route('/a-propos', name: 'about')]
public function about(): Response
{
return $this->render('page/about.html.twig', []);
}
#[Route('/projets-artistiques', name: 'project')]
public function project(): Response
{
return $this->render('page/project.html.twig', []);
}
#[Route('/projets-manuels-et-creatifs', name: 'manualproject')]
public function manualproject(): Response
{
return $this->render('page/manual-project.html.twig', []);
}
#[Route('/show', name: 'show')]
public function show(): Response
{
return $this->render('page/show.html.twig', []);
}
#[Route('/mentions-legales', name: 'legalmentions')]
public function legalmentions(): Response
{
return $this->render('page/legal-mentions.html.twig', []);
}
#[Route('/politique-de-confidentialite', name: 'confidentialitypolicy')]
public function confidentialitypolicy(): Response
{
return $this->render('page/confidentiality-policy.html.twig', []);
}
#[Route('/actualites/{slug}', name: 'actualites')]
public function actualites(string $slug): Response
{
$news = $this->entityManager->getRepository(News::class)->findOneBy(['slug' => $slug]);
return $this->render('page/news.html.twig', ['news' => $news]);
}
}

View File

@@ -0,0 +1,30 @@
<?php
namespace App\Controller;
use App\Entity\News;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
#[Route('/render/feature', name: 'render_feature_')]
final class RenderFeatureController extends AbstractController
{
private EntityManagerInterface $entityManager;
public function __construct(EntityManagerInterface $entityManager)
{
$this->entityManager = $entityManager;
}
#[Route('/news', name: 'news')]
public function news($isCarousel): Response
{
$news = $this->entityManager->getRepository(News::class)->findBy([], ['ordre' => 'ASC']);
return $this->render('render_feature/news.html.twig', [
'news' => $news,
'isCarousel' => $isCarousel
]);
}
}

View File

@@ -0,0 +1,28 @@
<?php
namespace App\Controller;
use App\Service\KeycloakClientService;
use KnpU\OAuth2ClientBundle\Client\ClientRegistry;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
class SecurityController extends AbstractController
{
private KeycloakClientService $keycloakClientService;
public function __construct(KeycloakClientService $keycloakClientService)
{
$this->keycloakClientService = $keycloakClientService;
}
#[Route('/connect/keycloak', name: 'connect_keycloak')]
public function connectKeycloak(ClientRegistry $clientRegistry): RedirectResponse
{
$client = $clientRegistry->getClient('keycloak');
return $client->redirect(['openid', 'profile', 'email'], []);
}
}

View File

@@ -0,0 +1,56 @@
<?php
namespace App\Controller;
use App\Entity\News;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
final class SitemapController extends AbstractController
{
#[Route('/sitemap.xml', name: 'sitemap', defaults: ['_format' => 'xml'])]
public function sitemap(EntityManagerInterface $entityManager): Response
{
// Routes statiques
$urls = [
[
'loc' => $this->generateUrl('home'),
'priority' => '1.0',
],
[
'loc' => $this->generateUrl('about'),
'priority' => '0.8',
],
[
'loc' => $this->generateUrl('project'),
'priority' => '0.8',
],
[
'loc' => $this->generateUrl('manualproject'),
'priority' => '0.8',
],
[
'loc' => $this->generateUrl('show'),
'priority' => '0.7',
],
];
// Routes dynamiques (actualités)
$newsList = $entityManager->getRepository(News::class)->findAll();
foreach ($newsList as $news) {
$urls[] = [
'loc' => $this->generateUrl('actualites', [
'slug' => $news->getSlug()
]),
'priority' => '0.6',
];
}
return $this->render('sitemap/sitemap.xml.twig', [
'urls' => $urls,
]);
}
}