From fd2bd63a129bf90aabdf3a28ae67ff50592c75da Mon Sep 17 00:00:00 2001 From: Nathan Boiron Date: Wed, 20 May 2026 10:41:28 +0200 Subject: [PATCH] Refactor des articles avec Doctrine --- app/config/packages/doctrine.yaml | 2 + db/seeds/Articles.php | 4 - .../Admin/Site/Article/AddArticleAction.php | 8 +- .../Site/Article/DeleteArticleAction.php | 14 +- .../Admin/Site/Article/EditArticleAction.php | 12 +- .../Admin/Site/Article/ListArticlesAction.php | 6 +- .../Controller/Website/Cms/DisplayAction.php | 13 +- .../Controller/Website/Global/HomeAction.php | 2 +- .../Website/Global/HtmlSitemapAction.php | 4 +- .../Controller/Website/News/DisplayAction.php | 17 +- .../Controller/Website/News/ListAction.php | 6 +- .../Controller/Website/RssFeedController.php | 12 +- .../AppBundle/Doctrine/EntityRepository.php | 5 + sources/AppBundle/Site/Entity/Article.php | 88 ++++ .../Entity/Repository/ArticleRepository.php | 225 +++++++++ sources/AppBundle/Site/Entity/Rubrique.php | 1 + .../Site/Enum/ArticleContentType.php | 11 - sources/AppBundle/Site/Enum/ArticleEtat.php | 21 + sources/AppBundle/Site/Enum/ArticleTheme.php | 14 - sources/AppBundle/Site/Form/ArticleType.php | 66 +-- .../AppBundle/Site/Form/NewsFiltersType.php | 20 +- sources/AppBundle/Site/Model/Article.php | 358 --------------- .../Model/Repository/ArticleRepository.php | 433 ------------------ .../Subscriber/SitemapXmlSubscriber.php | 10 +- templates/admin/site/article_form.html.twig | 16 +- templates/site/cms_page/display.html.twig | 10 +- templates/site/home.html.twig | 8 +- templates/site/news/display.html.twig | 36 +- templates/site/news/list.html.twig | 8 +- .../Admin/Site/AdminSiteArticles.feature | 50 +- .../Admin/Site/AdminSiteRubriques.feature | 2 +- 31 files changed, 491 insertions(+), 991 deletions(-) create mode 100644 sources/AppBundle/Site/Entity/Article.php create mode 100644 sources/AppBundle/Site/Entity/Repository/ArticleRepository.php delete mode 100644 sources/AppBundle/Site/Enum/ArticleContentType.php create mode 100644 sources/AppBundle/Site/Enum/ArticleEtat.php delete mode 100644 sources/AppBundle/Site/Model/Article.php delete mode 100644 sources/AppBundle/Site/Model/Repository/ArticleRepository.php diff --git a/app/config/packages/doctrine.yaml b/app/config/packages/doctrine.yaml index 652e639e1..87c8359ff 100644 --- a/app/config/packages/doctrine.yaml +++ b/app/config/packages/doctrine.yaml @@ -23,6 +23,8 @@ doctrine: dql: datetime_functions: UNIX_TIMESTAMP: DoctrineExtensions\Query\Mysql\UnixTimestamp + YEAR: DoctrineExtensions\Query\Mysql\Year + FROM_UNIXTIME: DoctrineExtensions\Query\Mysql\FromUnixtime mappings: Accounting: type: attribute diff --git a/db/seeds/Articles.php b/db/seeds/Articles.php index fd276e8db..67f5d8564 100644 --- a/db/seeds/Articles.php +++ b/db/seeds/Articles.php @@ -2,7 +2,6 @@ declare(strict_types=1); -use AppBundle\Site\Enum\ArticleContentType; use AppBundle\Site\Model\Rubrique; use Cocur\Slugify\Slugify; use Faker\Factory; @@ -32,7 +31,6 @@ public function run(): void 'date' => 1542150000, 'id_forum' => Event::ID_FORUM, 'etat' => 1, - 'type_contenu' => ArticleContentType::Html->value, ], ]; @@ -50,7 +48,6 @@ public function run(): void 'id_site_rubrique' => Rubrique::ID_RUBRIQUE_ACTUALITES, 'date' => $faker->unixTime(new DateTime('2017-12-31T23:59:59')), 'etat' => 1, - 'type_contenu' => ArticleContentType::Html->value, ]; } @@ -88,7 +85,6 @@ private function createMarkdownArticle(): array 'date' => 1761859722, 'id_forum' => Event::ID_FORUM, 'etat' => 1, - 'type_contenu' => ArticleContentType::Markdown->value, ]; } } diff --git a/sources/AppBundle/Controller/Admin/Site/Article/AddArticleAction.php b/sources/AppBundle/Controller/Admin/Site/Article/AddArticleAction.php index 3443fae11..afeb2a658 100644 --- a/sources/AppBundle/Controller/Admin/Site/Article/AddArticleAction.php +++ b/sources/AppBundle/Controller/Admin/Site/Article/AddArticleAction.php @@ -5,9 +5,9 @@ namespace AppBundle\Controller\Admin\Site\Article; use AppBundle\AuditLog\Audit; +use AppBundle\Site\Entity\Article; +use AppBundle\Site\Entity\Repository\ArticleRepository; use AppBundle\Site\Form\ArticleType; -use AppBundle\Site\Model\Article; -use AppBundle\Site\Model\Repository\ArticleRepository; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; @@ -28,8 +28,8 @@ public function __invoke(Request $request): Response if ($form->isSubmitted() && $form->isValid()) { $this->articleRepository->save($article); - $this->audit->log('Ajout de l\'article ' . $article->getTitle()); - $this->addFlash('notice', 'L\'article ' . $article->getTitle() . ' a été ajouté'); + $this->audit->log('Ajout de l\'article ' . $article->titre); + $this->addFlash('notice', 'L\'article ' . $article->titre . ' a été ajouté'); return $this->redirectToRoute('admin_site_articles_list'); } diff --git a/sources/AppBundle/Controller/Admin/Site/Article/DeleteArticleAction.php b/sources/AppBundle/Controller/Admin/Site/Article/DeleteArticleAction.php index 0353e0e79..a7824aee8 100644 --- a/sources/AppBundle/Controller/Admin/Site/Article/DeleteArticleAction.php +++ b/sources/AppBundle/Controller/Admin/Site/Article/DeleteArticleAction.php @@ -5,7 +5,7 @@ namespace AppBundle\Controller\Admin\Site\Article; use AppBundle\AuditLog\Audit; -use AppBundle\Site\Model\Repository\ArticleRepository; +use AppBundle\Site\Entity\Repository\ArticleRepository; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\Security\Csrf\CsrfToken; @@ -25,11 +25,15 @@ public function __invoke(int $id, string $token): RedirectResponse $this->addFlash('error', 'Token invalide'); return $this->redirectToRoute('admin_site_articles_list'); } - $article = $this->articleRepository->get($id); - $name = $article->getTitle(); + + $article = $this->articleRepository->find($id); + if ($article === null) { + throw $this->createNotFoundException(); + } + $this->articleRepository->delete($article); - $this->audit->log('Suppression de l\'article ' . $name); - $this->addFlash('notice', 'L\'article ' . $name . ' a été supprimé'); + $this->audit->log('Suppression de l\'article ' . $article->titre); + $this->addFlash('notice', 'L\'article ' . $article->titre . ' a été supprimé'); return $this->redirectToRoute('admin_site_articles_list'); } } diff --git a/sources/AppBundle/Controller/Admin/Site/Article/EditArticleAction.php b/sources/AppBundle/Controller/Admin/Site/Article/EditArticleAction.php index 7edd443a5..3964ab7ea 100644 --- a/sources/AppBundle/Controller/Admin/Site/Article/EditArticleAction.php +++ b/sources/AppBundle/Controller/Admin/Site/Article/EditArticleAction.php @@ -5,8 +5,8 @@ namespace AppBundle\Controller\Admin\Site\Article; use AppBundle\AuditLog\Audit; +use AppBundle\Site\Entity\Repository\ArticleRepository; use AppBundle\Site\Form\ArticleType; -use AppBundle\Site\Model\Repository\ArticleRepository; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; @@ -20,13 +20,17 @@ public function __construct( public function __invoke(int $id, Request $request): Response { - $article = $this->articleRepository->get($id); + $article = $this->articleRepository->find($id); + if ($article === null) { + throw $this->createNotFoundException(); + } + $form = $this->createForm(ArticleType::class, $article); $form->handleRequest($request); if ($form->isSubmitted() && $form->isValid()) { $this->articleRepository->save($article); - $this->audit->log('Modification de l\'article ' . $article->getTitle()); - $this->addFlash('notice', 'L\'article ' . $article->getTitle() . ' a été modifié'); + $this->audit->log('Modification de l\'article ' . $article->titre); + $this->addFlash('notice', 'L\'article ' . $article->titre . ' a été modifié'); return $this->redirectToRoute('admin_site_articles_list'); } diff --git a/sources/AppBundle/Controller/Admin/Site/Article/ListArticlesAction.php b/sources/AppBundle/Controller/Admin/Site/Article/ListArticlesAction.php index 3226b1eaa..f8248cf8b 100644 --- a/sources/AppBundle/Controller/Admin/Site/Article/ListArticlesAction.php +++ b/sources/AppBundle/Controller/Admin/Site/Article/ListArticlesAction.php @@ -4,7 +4,7 @@ namespace AppBundle\Controller\Admin\Site\Article; -use AppBundle\Site\Model\Repository\ArticleRepository; +use AppBundle\Site\Entity\Repository\ArticleRepository; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Twig\Environment; @@ -18,7 +18,7 @@ public function __construct( public function __invoke(Request $request): Response { - $fields = ['date', 'titre', 'etat']; + $fields = ['date', 'titre', 'nom_rubrique', 'etat']; $sort = $request->query->get('sort', 'date'); if (in_array($sort, $fields) === false) { @@ -26,7 +26,7 @@ public function __invoke(Request $request): Response } $direction = $request->query->get('direction', 'desc'); $filter = $request->query->get('filter', ''); - $articles = $this->articleRepository->getAllArticlesWithCategoryAndTheme($sort, $direction, $filter); + $articles = $this->articleRepository->getAllArticlesWithRubriqueAndTheme($sort, $direction, $filter); return new Response($this->twig->render('admin/site/article_list.html.twig', [ 'articles' => $articles, diff --git a/sources/AppBundle/Controller/Website/Cms/DisplayAction.php b/sources/AppBundle/Controller/Website/Cms/DisplayAction.php index 620e42762..510fb3a76 100644 --- a/sources/AppBundle/Controller/Website/Cms/DisplayAction.php +++ b/sources/AppBundle/Controller/Website/Cms/DisplayAction.php @@ -4,10 +4,10 @@ namespace AppBundle\Controller\Website\Cms; -use AppBundle\Site\Model\Article; -use AppBundle\Site\Model\Repository\ArticleRepository; -use AppBundle\Site\Entity\Repository\RubriqueRepository; +use AppBundle\Site\Entity\Article; +use AppBundle\Site\Entity\Repository\ArticleRepository; use AppBundle\Site\Entity\Rubrique; +use AppBundle\Site\Enum\ArticleEtat; use AppBundle\Twig\ViewRenderer; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\Response; @@ -17,7 +17,6 @@ final class DisplayAction extends AbstractController public function __construct( private readonly ViewRenderer $view, private readonly ArticleRepository $articleRepository, - private readonly RubriqueRepository $rubriqueRepository, ) {} public function __invoke(string $code): Response @@ -28,13 +27,13 @@ public function __invoke(string $code): Response throw $this->createNotFoundException(); } - if (false === $this->isGranted('ROLE_ADMIN') && $article->getState() !== 1) { + if (false === $this->isGranted('ROLE_ADMIN') && $article->etat !== ArticleEtat::EnLigne) { throw $this->createAccessDeniedException(); } - $rubrique = $this->rubriqueRepository->find($article->getRubricId()); + $rubrique = $article->rubrique; - if (!$this->isRubriqueAllowed($rubrique)) { + if ($rubrique === null || !$this->isRubriqueAllowed($rubrique)) { throw $this->createNotFoundException(); } diff --git a/sources/AppBundle/Controller/Website/Global/HomeAction.php b/sources/AppBundle/Controller/Website/Global/HomeAction.php index 3e9c7408c..27d9628c7 100644 --- a/sources/AppBundle/Controller/Website/Global/HomeAction.php +++ b/sources/AppBundle/Controller/Website/Global/HomeAction.php @@ -10,7 +10,7 @@ use AppBundle\Event\Model\Meetup; use AppBundle\Event\Model\Repository\TalkRepository; use AppBundle\Event\Model\Talk; -use AppBundle\Site\Model\Repository\ArticleRepository; +use AppBundle\Site\Entity\Repository\ArticleRepository; use AppBundle\Site\Entity\Repository\FeuilleRepository; use AppBundle\Twig\ViewRenderer; use Psr\Cache\CacheItemPoolInterface; diff --git a/sources/AppBundle/Controller/Website/Global/HtmlSitemapAction.php b/sources/AppBundle/Controller/Website/Global/HtmlSitemapAction.php index 31abf67ac..953d47d13 100644 --- a/sources/AppBundle/Controller/Website/Global/HtmlSitemapAction.php +++ b/sources/AppBundle/Controller/Website/Global/HtmlSitemapAction.php @@ -8,7 +8,7 @@ use AppBundle\Association\Model\Repository\CompanyMemberRepository; use AppBundle\Event\Model\Repository\TalkRepository; use AppBundle\Event\Model\Talk; -use AppBundle\Site\Model\Repository\ArticleRepository; +use AppBundle\Site\Entity\Repository\ArticleRepository; use AppBundle\Site\Entity\Repository\FeuilleRepository; use AppBundle\Twig\ViewRenderer; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; @@ -75,7 +75,7 @@ private function members(): array private function news(): array { $news = []; - $newsList = $this->articleRepository->findAllPublishedNews(); + $newsList = $this->articleRepository->findAllPublishedArticles(); foreach ($newsList as $newsItem) { $url = $this->generateUrl('news_display', [ 'code' => $newsItem->getSlug(), diff --git a/sources/AppBundle/Controller/Website/News/DisplayAction.php b/sources/AppBundle/Controller/Website/News/DisplayAction.php index 593284688..ae082e753 100644 --- a/sources/AppBundle/Controller/Website/News/DisplayAction.php +++ b/sources/AppBundle/Controller/Website/News/DisplayAction.php @@ -6,8 +6,9 @@ use AppBundle\Event\Model\Event; use AppBundle\Event\Model\Repository\EventRepository; -use AppBundle\Site\Model\Article; -use AppBundle\Site\Model\Repository\ArticleRepository; +use AppBundle\Site\Entity\Article; +use AppBundle\Site\Entity\Repository\ArticleRepository; +use AppBundle\Site\Enum\ArticleEtat; use AppBundle\Twig\ViewRenderer; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\DependencyInjection\Attribute\Autowire; @@ -27,12 +28,12 @@ public function __construct( public function __invoke(string $code): Response { - $article = $this->articleRepository->findNewsBySlug($code); + $article = $this->articleRepository->findArticleBySlug($code); if (null === $article) { throw $this->createNotFoundException(); } - if (!$this->authorizationChecker->isGranted('ROLE_ADMIN') && ($article->getState() === 0 || $article->getPublishedAt() > new \DateTime())) { + if (!$this->authorizationChecker->isGranted('ROLE_ADMIN') && ($article->etat !== ArticleEtat::EnLigne || $article->datePublication > new \DateTime())) { throw $this->createNotFoundException(); } @@ -47,20 +48,20 @@ public function __invoke(string $code): Response private function getRelatedEvent(Article $article): ?Event { - if (null === ($eventId = $article->getEventId())) { + if (null === $article->idEvent) { return null; } - return $this->eventRepository->get($eventId); + return $this->eventRepository->get($article->idEvent); } private function getHeaderImageUrl(Article $article): ?string { - if (null === ($theme = $article->getTheme())) { + if (null === $article->theme) { return null; } - $image = '/images/news/' . $theme . '.png'; + $image = '/images/news/' . $article->theme->value . '.png'; $url = $this->projectDir . '/htdocs' . $image ; diff --git a/sources/AppBundle/Controller/Website/News/ListAction.php b/sources/AppBundle/Controller/Website/News/ListAction.php index 953276322..e5b915711 100644 --- a/sources/AppBundle/Controller/Website/News/ListAction.php +++ b/sources/AppBundle/Controller/Website/News/ListAction.php @@ -5,7 +5,7 @@ namespace AppBundle\Controller\Website\News; use AppBundle\Site\Form\NewsFiltersType; -use AppBundle\Site\Model\Repository\ArticleRepository; +use AppBundle\Site\Entity\Repository\ArticleRepository; use AppBundle\Twig\ViewRenderer; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\Request; @@ -32,8 +32,8 @@ public function __invoke(Request $request): Response return $this->view->render('site/news/list.html.twig', [ 'filters' => $filters, - 'articles' => $this->articleRepository->findPublishedNews($page, self::ARTICLES_PER_PAGE, $filters), - 'total_items' => $this->articleRepository->countPublishedNews($filters), + 'articles' => $this->articleRepository->findPublishedArticles($page, self::ARTICLES_PER_PAGE, $filters), + 'total_items' => $this->articleRepository->countPublishedArticles($filters), 'current_page' => $page, 'articles_per_page' => self::ARTICLES_PER_PAGE, 'form' => $form->createView(), diff --git a/sources/AppBundle/Controller/Website/RssFeedController.php b/sources/AppBundle/Controller/Website/RssFeedController.php index e62f7b12c..6bc6f8b78 100644 --- a/sources/AppBundle/Controller/Website/RssFeedController.php +++ b/sources/AppBundle/Controller/Website/RssFeedController.php @@ -4,8 +4,7 @@ namespace AppBundle\Controller\Website; -use AppBundle\Site\Model\Article; -use AppBundle\Site\Model\Repository\ArticleRepository; +use AppBundle\Site\Entity\Repository\ArticleRepository; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\Response; @@ -15,15 +14,14 @@ public function __construct(private readonly ArticleRepository $articleRepositor public function __invoke(): Response { - $articles = $this->articleRepository->findPublishedNews(1, 20, []); + $articles = $this->articleRepository->findPublishedArticles(1, 20, []); $derniersArticles = []; foreach ($articles as $article) { - /** @var Article $article */ $derniersArticles[] = [ - 'titre' => $article->getTitle(), - 'contenu' => $article->getFormatedContent(), + 'titre' => $article->titre, + 'contenu' => $article->getContenuFormate(), 'url' => $article->getSlug(), - 'maj' => $article->getPublishedAt()->format(DATE_RSS), + 'maj' => $article->datePublication->format(DATE_RSS), ]; } $datas = [ diff --git a/sources/AppBundle/Doctrine/EntityRepository.php b/sources/AppBundle/Doctrine/EntityRepository.php index f9fc8d190..cb7aea73f 100644 --- a/sources/AppBundle/Doctrine/EntityRepository.php +++ b/sources/AppBundle/Doctrine/EntityRepository.php @@ -29,4 +29,9 @@ final public function delete(object $entity): void $this->getEntityManager()->remove($entity); $this->getEntityManager()->flush(); } + + final protected function hasColumn(string $column): bool + { + return in_array($column, $this->getClassMetadata()->getColumnNames(), true); + } } diff --git a/sources/AppBundle/Site/Entity/Article.php b/sources/AppBundle/Site/Entity/Article.php new file mode 100644 index 000000000..a403b1d38 --- /dev/null +++ b/sources/AppBundle/Site/Entity/Article.php @@ -0,0 +1,88 @@ +datePublication = new \DateTime(); + } + + public function getSlug(): string + { + return $this->id . '-' . $this->raccourci; + } + + public function getContenuFormate(): string + { + return (new \Parsedown())->text((string) $this->contenu); + } + + public function getChapeauFormate(): string + { + return (new \Parsedown())->text((string) $this->chapeau); + } + + public function getResume(): string + { + if (strlen((string) $this->chapeau) !== 0) { + return strip_tags((string) $this->chapeau); + } + + return substr(strip_tags((string) $this->contenu), 0, 200); + } + + public function getResumeTexte(): string + { + return html_entity_decode($this->getResume()); + } +} diff --git a/sources/AppBundle/Site/Entity/Repository/ArticleRepository.php b/sources/AppBundle/Site/Entity/Repository/ArticleRepository.php new file mode 100644 index 000000000..f055281d5 --- /dev/null +++ b/sources/AppBundle/Site/Entity/Repository/ArticleRepository.php @@ -0,0 +1,225 @@ + + */ +final class ArticleRepository extends EntityRepository +{ + public function __construct(ManagerRegistry $registry) + { + parent::__construct($registry, Article::class); + } + + /** + * @return int[] + */ + public function getAllYears(): array + { + return $this->getEntityManager()->getConnection()->createQueryBuilder() + ->select('YEAR(FROM_UNIXTIME(a.date)) as annee') + ->from('afup_site_article', 'a') + ->where('a.id_site_rubrique = :rubriqueId') + ->andWhere('a.etat = :etat') + ->groupBy('YEAR(FROM_UNIXTIME(a.date))') + ->orderBy('YEAR(FROM_UNIXTIME(a.date))', 'DESC') + ->setParameter('rubriqueId', Rubrique::ID_RUBRIQUE_ACTUALITES) + ->setParameter('etat', ArticleEtat::EnLigne->value) + ->executeQuery() + ->fetchFirstColumn(); + } + + /** + * @return array + */ + public function getEventsLabelsById(): array + { + $rows = $this->getEntityManager()->getConnection()->createQueryBuilder() + ->select('e.id', 'e.titre') + ->from('afup_site_article', 'a') + ->innerJoin('a', 'afup_forum', 'e', 'a.id_forum = e.id') + ->groupBy('e.id') + ->orderBy('e.date_debut', 'DESC') + ->executeQuery() + ->fetchAllAssociative(); + + return array_column($rows, 'titre', 'id'); + } + + public function countPublishedArticles(array $filtres): int + { + return (int) $this->createQueryBuilderPublishedActualites($filtres) + ->select('COUNT(a.id)') + ->getQuery() + ->getSingleScalarResult(); + } + + /** + * @return Article[] + */ + public function findPublishedArticles(int $page, int $articlesParPage, array $filtres): array + { + return $this->createQueryBuilderPublishedActualites($filtres) + ->setFirstResult(($page - 1) * $articlesParPage) + ->setMaxResults($articlesParPage) + ->getQuery() + ->getResult(); + } + + /** + * @return Article[] + */ + public function findAllPublishedArticles(array $filtres = []): array + { + return $this->createQueryBuilderPublishedActualites($filtres) + ->getQuery() + ->getResult(); + } + + public function findPrevious(Article $article): ?Article + { + if (!($datePublication = $article->datePublication) instanceof \DateTime) { + return null; + } + + $filtres = [ + 'before_date' => $datePublication->getTimestamp(), + ]; + + return $this->createQueryBuilderPublishedActualites($filtres) + ->setMaxResults(1) + ->getQuery() + ->getOneOrNullResult(); + } + + public function findNext(Article $article): ?Article + { + if (!($datePublication = $article->datePublication) instanceof \DateTime) { + return null; + } + + $filtres = [ + 'after_date' => $datePublication->getTimestamp(), + ]; + + return $this->createQueryBuilderPublishedActualites($filtres, 'ASC') + ->setMaxResults(1) + ->getQuery() + ->getOneOrNullResult(); + } + + public function findArticleBySlug(string $slug): ?Article + { + return $this->createQueryBuilder('a') + ->where('a.rubrique = :rubriqueId') + ->andWhere("CONCAT(a.id, '-', a.raccourci) = :slug") + ->setParameter('rubriqueId', Rubrique::ID_RUBRIQUE_ACTUALITES) + ->setParameter('slug', $slug) + ->setMaxResults(1) + ->getQuery() + ->getOneOrNullResult(); + } + + public function findBySlug(string $slug): ?Article + { + return $this->createQueryBuilder('a') + ->andWhere("CONCAT(a.id, '-', a.raccourci) = :slug") + ->setParameter('slug', $slug) + ->setMaxResults(1) + ->getQuery() + ->getOneOrNullResult(); + } + + /** + * @return array> + */ + public function getAllArticlesWithRubriqueAndTheme(string $ordre = 'titre', string $direction = 'desc', string $filtre = ''): array + { + if ($direction !== 'desc' && $direction !== 'asc') { + $direction = 'desc'; + } + + if (!$this->hasColumn($ordre)) { + $ordre = 'titre'; + } + + return $this->getEntityManager()->getConnection()->createQueryBuilder() + ->select('a.*', 'r.nom as nom_rubrique', 'f.titre as nom_forum') + ->from('afup_site_article', 'a') + ->innerJoin('a', 'afup_site_rubrique', 'r', 'a.id_site_rubrique = r.id') + ->leftJoin('a', 'afup_forum', 'f', 'a.id_forum = f.id') + ->where('a.titre LIKE :filtre OR a.contenu LIKE :filtre') + ->orderBy($ordre, $direction) + ->setParameter('filtre', '%' . $filtre . '%') + ->executeQuery() + ->fetchAllAssociative(); + } + + /** + * @return Article[] + */ + public function findListForHome(): array + { + return $this->createQueryBuilder('a') + ->innerJoin('a.rubrique', 'r') + ->where('a.etat = :etat') + ->andWhere('a.datePublication <= UNIX_TIMESTAMP()') + ->setParameter('etat', ArticleEtat::EnLigne) + ->andWhere('r.idParent <> ' . Rubrique::ID_RUBRIQUE_FORUM) + ->andWhere('r.id <> ' . Rubrique::ID_RUBRIQUE_ASSOCIATION) + ->andWhere('r.id <> ' . Rubrique::ID_RUBRIQUE_ANTENNES) + ->andWhere('r.id <> ' . Rubrique::ID_RUBRIQUE_NOS_ACTIONS) + ->orderBy('a.datePublication', 'DESC') + ->setMaxResults(5) + ->getQuery() + ->getResult(); + } + + private function createQueryBuilderPublishedActualites(array $filtres, string $ordre = 'DESC'): QueryBuilder + { + $qb = $this->createQueryBuilder('a') + ->where('a.rubrique = :rubriqueId') + ->andWhere('a.etat = :etat') + ->andWhere('a.datePublication <= UNIX_TIMESTAMP()') + ->setParameter('etat', ArticleEtat::EnLigne) + ->setParameter('rubriqueId', Rubrique::ID_RUBRIQUE_ACTUALITES) + ->orderBy('a.datePublication', $ordre); + + if (!empty($filtres['year'])) { + $qb->andWhere($qb->expr()->in('YEAR(FROM_UNIXTIME(a.datePublication))', ':years')) + ->setParameter('years', array_map(fn($year) => (int) $year, $filtres['year'])); + } + + if (!empty($filtres['theme'])) { + $qb->andWhere($qb->expr()->in('a.theme', ':themes')) + ->setParameter('themes', $filtres['theme']); + } + + if (!empty($filtres['event'])) { + $qb->andWhere($qb->expr()->in('a.idEvent', ':event')) + ->setParameter('event', $filtres['event']); + } + + if (isset($filtres['after_date'])) { + $qb->andWhere('a.datePublication > :afterDate') + ->setParameter('afterDate', $filtres['after_date']); + } + + if (isset($filtres['before_date'])) { + $qb->andWhere('a.datePublication < :beforeDate') + ->setParameter('beforeDate', $filtres['before_date']); + } + + return $qb; + } +} diff --git a/sources/AppBundle/Site/Entity/Rubrique.php b/sources/AppBundle/Site/Entity/Rubrique.php index 5f3037c42..d78a507c3 100644 --- a/sources/AppBundle/Site/Entity/Rubrique.php +++ b/sources/AppBundle/Site/Entity/Rubrique.php @@ -12,6 +12,7 @@ class Rubrique { public const ID_RUBRIQUE_ACTUALITES = 9; + public const ID_RUBRIQUE_FORUM = 52; public const ID_RUBRIQUE_ASSOCIATION = 85; public const ID_RUBRIQUE_ANTENNES = 84; public const ID_RUBRIQUE_INFORMATIONS_PRATIQUES = 86; diff --git a/sources/AppBundle/Site/Enum/ArticleContentType.php b/sources/AppBundle/Site/Enum/ArticleContentType.php deleted file mode 100644 index c914076b8..000000000 --- a/sources/AppBundle/Site/Enum/ArticleContentType.php +++ /dev/null @@ -1,11 +0,0 @@ - 'Hors ligne', + self::EnAttente => 'En attente', + self::EnLigne => 'En ligne', + }; + } +} diff --git a/sources/AppBundle/Site/Enum/ArticleTheme.php b/sources/AppBundle/Site/Enum/ArticleTheme.php index 6ef2ca43d..ed070cf14 100644 --- a/sources/AppBundle/Site/Enum/ArticleTheme.php +++ b/sources/AppBundle/Site/Enum/ArticleTheme.php @@ -22,18 +22,4 @@ public function label(): string self::AfupSoutien => "L'AFUP soutient", }; } - - /** - * @return array - */ - public static function asChoicesMap(): array - { - $map = []; - - foreach (self::cases() as $case) { - $map[$case->label()] = $case->value; - } - - return $map; - } } diff --git a/sources/AppBundle/Site/Form/ArticleType.php b/sources/AppBundle/Site/Form/ArticleType.php index a0b7aeb76..aa06847ae 100644 --- a/sources/AppBundle/Site/Form/ArticleType.php +++ b/sources/AppBundle/Site/Form/ArticleType.php @@ -5,11 +5,14 @@ namespace AppBundle\Site\Form; use AppBundle\Event\Model\Repository\EventRepository; +use AppBundle\Site\Entity\Rubrique; +use AppBundle\Site\Enum\ArticleEtat; use AppBundle\Site\Enum\ArticleTheme; -use AppBundle\Site\Entity\Repository\RubriqueRepository; +use Symfony\Bridge\Doctrine\Form\Type\EntityType; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToTimestampTransformer; use Symfony\Component\Form\Extension\Core\Type\ChoiceType; +use Symfony\Component\Form\Extension\Core\Type\EnumType; use Symfony\Component\Form\Extension\Core\Type\DateTimeType; use Symfony\Component\Form\Extension\Core\Type\TextareaType; use Symfony\Component\Form\Extension\Core\Type\TextType; @@ -20,10 +23,7 @@ class ArticleType extends AbstractType { public const POSITIONS_RUBRIQUES = 9; - public function __construct( - private readonly RubriqueRepository $rubriqueRepository, - private readonly EventRepository $eventRepository, - ) {} + public function __construct(private readonly EventRepository $eventRepository) {} public function buildForm(FormBuilderInterface $builder, array $options): void { @@ -31,23 +31,14 @@ public function buildForm(FormBuilderInterface $builder, array $options): void for ($i = self::POSITIONS_RUBRIQUES; $i >= -(self::POSITIONS_RUBRIQUES); $i--) { $positions[$i] = $i; } - $rubriques = []; - foreach ($this->rubriqueRepository->findAll() as $rubrique) { - $rubriques[$rubrique->nom] = $rubrique->id; - } $events = []; foreach ($this->eventRepository->getAll() as $event) { $events[$event->getTitle()] = $event->getId(); } - /** @var \AppBundle\Site\Model\Article|null $article */ - $article = $builder->getData(); - $textareaCssClass = 'simplemde'; - - $builder - ->add('title', TextType::class, [ + ->add('titre', TextType::class, [ 'label' => 'Titre de l\'article', 'required' => true, 'attr' => [ @@ -60,19 +51,19 @@ public function buildForm(FormBuilderInterface $builder, array $options): void new Assert\Type('string'), ], ]) - ->add('leadParagraph', TextareaType::class, [ + ->add('chapeau', TextareaType::class, [ 'label' => 'Chapeau', 'required' => false, 'attr' => [ 'cols' => 42, 'rows' => 10, - 'class' => $textareaCssClass, + 'class' => 'simplemde', ], 'constraints' => [ new Assert\Type('string'), ], ]) - ->add('content', TextareaType::class, [ + ->add('contenu', TextareaType::class, [ 'label' => 'Contenu', // Désactive la validation HTML5, nécessaire à cause du wysiwyg qui masque l'input // tout en le mettant à required, ce qui bloque la soumission du formulaire. @@ -80,14 +71,14 @@ public function buildForm(FormBuilderInterface $builder, array $options): void 'attr' => [ 'cols' => 42, 'rows' => 20, - 'class' => $textareaCssClass, + 'class' => 'simplemde', ], 'constraints' => [ new Assert\NotBlank(message: 'Ce champ est obligatoire'), new Assert\Type('string'), ], ]) - ->add('path', TextType::class, [ + ->add('raccourci', TextType::class, [ 'required' => true, 'label' => 'Raccourci', 'attr' => [ @@ -101,15 +92,13 @@ public function buildForm(FormBuilderInterface $builder, array $options): void new Assert\Regex('/(\s)/', 'Ne doit pas contenir d\'espaces', null, false), ], ]) - ->add('rubricId', ChoiceType::class, [ + ->add('rubrique', EntityType::class, [ 'required' => true, 'label' => 'Rubrique', - 'choices' => $rubriques, - 'constraints' => [ - new Assert\Type("integer"), - ], + 'class' => Rubrique::class, + 'choice_label' => 'nom', ]) - ->add('publishedAt', DateTimeType::class, [ + ->add('datePublication', DateTimeType::class, [ 'required' => false, 'html5' => true, 'label' => 'Date', @@ -133,28 +122,19 @@ public function buildForm(FormBuilderInterface $builder, array $options): void new Assert\Type("integer"), ], ]) - ->add('state', ChoiceType::class, [ + ->add('etat', EnumType::class, [ 'label' => 'Etat', 'required' => false, - 'choices' => [ - 'Hors ligne' => -1, - 'En attente' => 0, - 'En ligne' => 1, - ], - 'placeholder' => false, - 'constraints' => [ - new Assert\Type("integer"), - ], + 'class' => ArticleEtat::class, + 'choice_label' => fn(ArticleEtat $etat) => $etat->label(), ]) - ->add('theme', ChoiceType::class, [ + ->add('theme', EnumType::class, [ 'label' => 'Thème', 'required' => false, - 'choices' => ArticleTheme::asChoicesMap(), - 'constraints' => [ - new Assert\Type("integer"), - ], + 'class' => ArticleTheme::class, + 'choice_label' => fn(ArticleTheme $theme) => $theme->label(), ]) - ->add('eventId', ChoiceType::class, [ + ->add('idEvent', ChoiceType::class, [ 'label' => 'Événement', 'required' => false, 'choices' => $events, @@ -163,6 +143,6 @@ public function buildForm(FormBuilderInterface $builder, array $options): void ], ]) ; - $builder->get('publishedAt')->addModelTransformer(new DateTimeToTimestampTransformer()); + $builder->get('datePublication')->addModelTransformer(new DateTimeToTimestampTransformer()); } } diff --git a/sources/AppBundle/Site/Form/NewsFiltersType.php b/sources/AppBundle/Site/Form/NewsFiltersType.php index 1be351532..5bfe0491a 100644 --- a/sources/AppBundle/Site/Form/NewsFiltersType.php +++ b/sources/AppBundle/Site/Form/NewsFiltersType.php @@ -5,9 +5,10 @@ namespace AppBundle\Site\Form; use AppBundle\Site\Enum\ArticleTheme; -use AppBundle\Site\Model\Repository\ArticleRepository; +use AppBundle\Site\Entity\Repository\ArticleRepository; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\Extension\Core\Type\ChoiceType; +use Symfony\Component\Form\Extension\Core\Type\EnumType; use Symfony\Component\Form\Extension\Core\Type\SubmitType; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\HttpFoundation\Request; @@ -36,16 +37,13 @@ public function buildForm(FormBuilderInterface $builder, array $options): void 'choices' => $yearValues, ], ) - ->add( - 'theme', - ChoiceType::class, - [ - 'label' => 'Thème', - 'multiple' => true, - 'expanded' => true, - 'choices' => ArticleTheme::asChoicesMap(), - ], - ) + ->add('theme', EnumType::class, [ + 'class' => ArticleTheme::class, + 'label' => 'Thème', + 'multiple' => true, + 'expanded' => true, + 'choice_label' => fn(ArticleTheme $theme) => $theme->label(), + ]) ->add( 'event', ChoiceType::class, diff --git a/sources/AppBundle/Site/Model/Article.php b/sources/AppBundle/Site/Model/Article.php deleted file mode 100644 index 95e7f70c2..000000000 --- a/sources/AppBundle/Site/Model/Article.php +++ /dev/null @@ -1,358 +0,0 @@ -position = 0; - $this->state = 0; - $this->contentType = ArticleContentType::Markdown->value; - $this->publishedAt = new \DateTime(); - } - - /** - * @return int - */ - public function getId(): ?int - { - return $this->id; - } - - /** - * @param int $id - * - * @return $this - */ - public function setId($id): self - { - $id = (int) $id; - $this->propertyChanged('id', $this->id, $id); - $this->id = $id; - return $this; - } - - /** - * @return mixed - */ - public function getRubricId() - { - return $this->rubricId; - } - - /** - * @param $rubricId $rubricId - * - * @return $this - */ - public function setRubricId($rubricId): self - { - $this->propertyChanged('rubricId', $this->rubricId, $rubricId); - $this->rubricId = $rubricId; - - return $this; - } - - /** - * @return string - */ - public function getTitle() - { - return $this->title; - } - - /** - * @param string $title - * - * @return $this - */ - public function setTitle($title): self - { - $this->propertyChanged('title', $this->title, $title); - $this->title = $title; - - return $this; - } - - - /** - * @return string - */ - public function getPath() - { - return $this->path; - } - - /** - * @param string $path - * - * @return $this - */ - public function setPath($path): self - { - $this->propertyChanged('path', $this->path, $path); - - $this->path = $path; - - return $this; - } - - /** - * @return string - */ - public function getFormatedLeadParagraph() - { - $parseDown = new \Parsedown(); - $leadParagraph = $parseDown->parse($this->leadParagraph); - - return $leadParagraph; - } - - public function getLeadParagraph() - { - return $this->leadParagraph; - } - - /** - * @param string $leadParagraph - * - * @return $this - */ - public function setLeadParagraph($leadParagraph): self - { - $this->propertyChanged('leadParagraph', $this->leadParagraph, $leadParagraph); - $this->leadParagraph = $leadParagraph; - - return $this; - } - - /** - * @return string - */ - public function getContent() - { - return $this->content; - } - - public function getFormatedContent() - { - $parseDown = new \Parsedown(); - $content = $parseDown->parse($this->content); - - return $content; - } - - /** - * @param string $content - * - * @return $this - */ - public function setContent($content): self - { - $this->propertyChanged('content', $this->content, $content); - $this->content = $content; - - return $this; - } - - /** - * @return string - */ - public function getContentType() - { - return $this->contentType; - } - - public function setContentType(string $contentType): self - { - $this->propertyChanged('contentType', $this->contentType, $contentType); - $this->contentType = $contentType; - - return $this; - } - - public function isContentTypeMarkdown(): bool - { - return $this->contentType === ArticleContentType::Markdown->value; - } - - /** - * @return \DateTime - */ - public function getPublishedAt(): ?\DateTime - { - return $this->publishedAt; - } - - /** - * @return $this - */ - public function setPublishedAt(\DateTime $publishedAt): self - { - $this->propertyChanged('publishedAt', $this->publishedAt, $publishedAt); - $this->publishedAt = $publishedAt; - - return $this; - } - - public function getTheme(): ?int - { - return $this->theme; - } - - public function getThemeEnum(): ?ArticleTheme - { - if ($this->theme === null) { - return null; - } - - return ArticleTheme::from($this->theme); - } - - public function setTheme(?int $theme): self - { - $this->propertyChanged('theme', $this->theme, $theme); - $this->theme = $theme; - - return $this; - } - - /** - * @return int|null - */ - public function getEventId() - { - return $this->eventId; - } - - /** - * @param int $eventId - * - * @return $this - */ - public function setEventId($eventId): self - { - $this->propertyChanged('eventId', $this->eventId, $eventId); - $this->eventId = $eventId; - - return $this; - } - - /** - * @return bool|string - */ - public function getTeaser() - { - if (strlen((string) $leadParagraph = $this->getLeadParagraph()) !== 0) { - return strip_tags((string) $leadParagraph); - } - - return substr(strip_tags($this->getContent()), 0, 200); - } - - public function getTextTeaser(): string - { - return html_entity_decode($this->getTeaser()); - } - - public function getSlug(): string - { - return $this->getId() . '-' . $this->getPath(); - } - /** - * @return int - */ - public function getState(): ?int - { - return $this->state; - } - - /** - * @param int $state - * - * @return $this - */ - public function setState($state): self - { - $state = (int) $state; - $this->propertyChanged('state', $this->state, $state); - $this->state = $state; - return $this; - } - - public function getPosition(): ?int - { - return $this->position; - } - - public function setPosition(?int $position): self - { - $this->propertyChanged('position', $this->position, $position); - $this->position = $position; - return $this; - } - - - public function getAuthorId(): ?int - { - return $this->authorId; - } - - public function setAuthorId(?int $authorId): self - { - $this->propertyChanged('authorId', $this->authorId, $authorId); - $this->authorId = $authorId; - return $this; - } -} diff --git a/sources/AppBundle/Site/Model/Repository/ArticleRepository.php b/sources/AppBundle/Site/Model/Repository/ArticleRepository.php deleted file mode 100644 index 3f6889649..000000000 --- a/sources/AppBundle/Site/Model/Repository/ArticleRepository.php +++ /dev/null @@ -1,433 +0,0 @@ - - */ -class ArticleRepository extends Repository implements MetadataInitializer -{ - /** - * @return mixed[] - */ - public function getAllYears(): array - { - $sql = " - SELECT YEAR(FROM_UNIXTIME(afup_site_article.date)) as year - FROM afup_site_article - WHERE afup_site_article.id_site_rubrique = :rubricId - AND etat = 1 - GROUP BY YEAR(FROM_UNIXTIME(afup_site_article.date)) - ORDER BY YEAR(FROM_UNIXTIME(afup_site_article.date)) DESC - "; - - $query = $this->getPreparedQuery($sql)->setParams(['rubricId' => Rubrique::ID_RUBRIQUE_ACTUALITES]); - - $years = []; - foreach ($query->query($this->getCollection(new HydratorArray()))->getIterator() as $row) { - $years[] = $row['year']; - } - - return $years; - } - - /** - * @return mixed[] - */ - public function getEventsLabelsById(): array - { - $sql = "SELECT afup_forum.id, afup_forum.titre - FROM afup_site_article - JOIN afup_forum ON (afup_site_article.id_forum = afup_forum.id) - GROUP BY afup_forum.id - ORDER BY afup_forum.date_debut DESC - "; - - $query = $this->getQuery($sql); - - $eventsLabelsById = []; - foreach ($query->query($this->getCollection(new HydratorArray()))->getIterator() as $row) { - $eventsLabelsById[$row['id']] = $row['titre']; - } - - return $eventsLabelsById; - } - - public function countPublishedNews(array $filters) - { - [$sql, $params] = $this->getSqlPublishedNews($filters); - - $sql = sprintf("SELECT COUNT(*) as cnt FROM (%s) as req", $sql); - - $query = $this->getPreparedQuery($sql)->setParams($params); - - $row = $query->query($this->getCollection(new HydratorArray()))->first(); - - return $row['cnt']; - } - - public function findPublishedNews(int $page, int $itemsPerPage, array $filters) - { - [$sql, $params] = $this->getSqlPublishedNews($filters); - - $sql .= ' LIMIT :offset, :limit '; - - $params = array_merge( - $params, - [ - 'offset' => ($page - 1) * $itemsPerPage, - 'limit' => $itemsPerPage, - ], - ); - - $query = $this->getPreparedQuery($sql)->setParams($params); - - return $query->query($this->getCollection(new HydratorSingleObject())); - } - - public function findAllPublishedNews(array $filters = []) - { - [$sql, $params] = $this->getSqlPublishedNews($filters); - - return $this->getPreparedQuery($sql) - ->setParams($params) - ->query($this->getCollection(new HydratorSingleObject())); - } - - public function findPrevious(Article $article) - { - if (!($publishedAt = $article->getPublishedAt()) instanceof \DateTime) { - return null; - } - - $filters = [ - 'before_date' => $publishedAt->getTimestamp(), - ]; - - [$sql, $params] = $this->getSqlPublishedNews($filters, 'DESC'); - - $sql .= ' LIMIT 1'; - - $query = $this->getPreparedQuery($sql)->setParams($params); - - $collection = $query->query($this->getCollection(new HydratorSingleObject())); - - return $collection->first(); - } - - public function findNext(Article $article) - { - if (!($publishedAt = $article->getPublishedAt()) instanceof \DateTime) { - return null; - } - - $filters = [ - 'after_date' => $publishedAt->getTimestamp(), - ]; - - [$sql, $params] = $this->getSqlPublishedNews($filters, 'ASC'); - - $sql .= ' LIMIT 1'; - - $query = $this->getPreparedQuery($sql)->setParams($params); - - $collection = $query->query($this->getCollection(new HydratorSingleObject())); - - return $collection->first(); - } - - private function getSqlPublishedNews(array $filters, string $order = 'DESC'): array - { - $yearParams = []; - $themeParams = []; - $eventParams = []; - $yearSqlFilter = ''; - $themeSqlFilter = ''; - $eventSqlFilter = ''; - $afterDateFilter = ''; - $beforeDateFilter = ''; - if (isset($filters['year']) && count($filters['year'])) { - $cpt = 1; - $yearPreaparedParams = []; - foreach ($filters['year'] as $year) { - $paramName = 'year_' . $cpt++; - $yearParams[$paramName] = $year; - $yearPreaparedParams[] = ':' . $paramName; - } - $yearSqlFilter = sprintf('AND YEAR(FROM_UNIXTIME(date)) IN (%s)', implode(',', $yearPreaparedParams)); - } - - if (isset($filters['theme']) && count($filters['theme'])) { - $cpt = 1; - $themesPreparedParams = []; - foreach ($filters['theme'] as $theme) { - $paramName = 'theme_' . $cpt++; - $themeParams[$paramName] = $theme; - $themesPreparedParams[] = ':' . $paramName; - } - $themeSqlFilter = sprintf('AND theme IN (%s)', implode(',', $themesPreparedParams)); - } - - if (isset($filters['event']) && count($filters['event'])) { - $cpt = 1; - $eventPreparedParams = []; - foreach ($filters['event'] as $event) { - $paramName = 'event_' . $cpt++; - $eventParams[$paramName] = $event; - $eventPreparedParams[] = ':' . $paramName; - } - $eventSqlFilter = sprintf('AND id_forum IN (%s)', implode(',', $eventPreparedParams)); - } - - if (isset($filters['after_date'])) { - $afterDateFilter = 'AND afup_site_article.date > :date_after'; - $eventParams['date_after'] = $filters['after_date']; - } - - if (isset($filters['before_date'])) { - $beforeDateFilter = 'AND afup_site_article.date < :date_before'; - $eventParams['date_before'] = $filters['before_date']; - } - - $sql = sprintf('SELECT afup_site_article.* - FROM afup_site_article - WHERE afup_site_article.id_site_rubrique = :rubricId - AND etat = 1 - AND afup_site_article.date <= UNIX_TIMESTAMP(NOW()) - %s %s %s %s %s - ORDER BY date %s - ', $yearSqlFilter, $themeSqlFilter, $eventSqlFilter, $afterDateFilter, $beforeDateFilter, $order); - - $params = [ - 'rubricId' => Rubrique::ID_RUBRIQUE_ACTUALITES, - ]; - - $params = array_merge($params, $yearParams, $themeParams, $eventParams); - - return [$sql, $params]; - } - - /** - * @param string $slug - * @return Article|null - */ - public function findNewsBySlug($slug) - { - $query = $this - ->getPreparedQuery( - 'SELECT * FROM afup_site_article - WHERE id_site_rubrique = :rubricId AND CONCAT(id, "-", raccourci) = :slug', - ) - ->setParams(['rubricId' => Rubrique::ID_RUBRIQUE_ACTUALITES, 'slug' => $slug]); - $articles = $query->query($this->getCollection(new HydratorSingleObject())); - - if ($articles->count() === 0) { - return null; - } - - return $articles->first(); - } - - public function findBySlug(string $slug): ?Article - { - $query = $this - ->getPreparedQuery('SELECT * FROM afup_site_article WHERE CONCAT(id, "-", raccourci) = :slug') - ->setParams(['slug' => $slug]); - - $articles = $query->query($this->getCollection(new HydratorSingleObject())); - - if ($articles->count() === 0) { - return null; - } - - return $articles->first(); - } - - public function getAllArticlesWithCategoryAndTheme(string $ordre = 'titre', string $direction = 'desc', string $filtre = '%'): CollectionInterface - { - if ($direction !== 'desc' && $direction !== 'asc') { - $direction = 'desc'; - } - $metadata = $this->getMetadata(); - $columnNameFound = false; - foreach ($metadata->getFields() as $field) { - if ($field['columnName'] === $ordre) { - $columnNameFound = true; - break; - } - } - if ($columnNameFound === false) { - $ordre = 'nom'; - } - - $requete = 'SELECT afup_site_article.*, afup_site_rubrique.nom as nom_rubrique, afup_forum.titre as nom_forum - FROM afup_site_article - INNER JOIN afup_site_rubrique on afup_site_article.id_site_rubrique = afup_site_rubrique.id - LEFT JOIN afup_forum on afup_site_article.id_forum = afup_forum.id - WHERE 1 = 1 '; - if ($filtre) { - $requete .= 'AND (afup_site_article.titre LIKE :filtre OR afup_site_article.contenu LIKE :filtre) '; - } - - $requete .= 'ORDER BY ' . $ordre . ' ' . $direction; - $query = $this->getQuery($requete); - $query->setParams(['filtre' => '%' . $filtre . '%']); - - return $query->query($this->getCollection(new HydratorArray())); - } - - /** - * @return CollectionInterface
- */ - public function findListForHome(): CollectionInterface - { - /** @var SelectInterface $builder */ - $builder = $this->getQueryBuilder(self::QUERY_SELECT); - $builder->cols(['*']) - ->from('afup_site_article') - ->innerJoin('afup_site_rubrique', 'ON afup_site_article.id_site_rubrique = afup_site_rubrique.id') - ->where('afup_site_article.etat = 1') - ->where('afup_site_article.date <= UNIX_TIMESTAMP(NOW())') - ->where('id_parent <> 52') // On n'affiche pas les articles des forums - ->where('afup_site_rubrique.id <> ' . Rubrique::ID_RUBRIQUE_ASSOCIATION) - ->where('afup_site_rubrique.id <> ' . Rubrique::ID_RUBRIQUE_ANTENNES) - ->where('afup_site_rubrique.id <> ' . Rubrique::ID_RUBRIQUE_NOS_ACTIONS) - ->orderBy(['afup_site_article.date DESC']) - ->offset(0) - ->limit(5) - ; - - return $this->getQuery($builder->getStatement())->query($this->getCollection(new HydratorSingleObject())); - } - - /** - * @return CollectionInterface
- */ - public function findAllHtmlArticles(int $limit = 0): CollectionInterface - { - $sql = 'SELECT * FROM afup_site_article WHERE type_contenu = :contentType ORDER BY id ASC'; - $params = ['contentType' => ArticleContentType::Html->value]; - if ($limit > 0) { - $sql .= ' LIMIT :limit'; - $params['limit'] = $limit; - } - - return $this->getPreparedQuery($sql) - ->setParams($params) - ->query($this->getCollection(new HydratorSingleObject())); - } - - public function findOneHtmlArticleById(int $id): ?Article - { - $collection = $this->getPreparedQuery( - 'SELECT * FROM afup_site_article WHERE id = :id AND type_contenu = :contentType', - ) - ->setParams(['id' => $id, 'contentType' => ArticleContentType::Html->value]) - ->query($this->getCollection(new HydratorSingleObject())); - - return $collection->count() === 0 ? null : $collection->first(); - } - - /** - * @inheritDoc - */ - public static function initMetadata(SerializerFactoryInterface $serializerFactory, array $options = []) - { - $metadata = new Metadata($serializerFactory); - - $metadata->setEntity(Article::class); - $metadata->setConnectionName('main'); - $metadata->setDatabase($options['database']); - $metadata->setTable('afup_site_article'); - - $metadata - ->addField([ - 'columnName' => 'id', - 'fieldName' => 'id', - 'primary' => true, - 'autoincrement' => true, - 'type' => 'int', - ]) - ->addField([ - 'columnName' => 'id_site_rubrique', - 'fieldName' => 'rubricId', - 'type' => 'int', - ]) - ->addField([ - 'columnName' => 'raccourci', - 'fieldName' => 'path', - 'type' => 'string', - ]) - ->addField([ - 'columnName' => 'titre', - 'fieldName' => 'title', - 'type' => 'string', - ]) - ->addField([ - 'columnName' => 'chapeau', - 'fieldName' => 'leadParagraph', - 'type' => 'string', - ]) - ->addField([ - 'columnName' => 'contenu', - 'fieldName' => 'content', - 'type' => 'string', - ]) - ->addField([ - 'columnName' => 'type_contenu', - 'fieldName' => 'contentType', - 'type' => 'string', - ]) - ->addField([ - 'columnName' => 'theme', - 'fieldName' => 'theme', - 'type' => 'int', - ]) - ->addField([ - 'columnName' => 'id_forum', - 'fieldName' => 'eventId', - 'type' => 'int', - ]) - ->addField([ - 'columnName' => 'date', - 'fieldName' => 'publishedAt', - 'type' => 'datetime', - 'serializer_options' => [ - 'unserialize' => ['unSerializeUseFormat' => true, 'format' => 'U'], - 'serialize' => [ 'format' => 'U'], - ], - ]) - ->addField([ - 'columnName' => 'etat', - 'fieldName' => 'state', - 'type' => 'int', - ]) - ->addField([ - 'columnName' => 'position', - 'fieldName' => 'position', - 'type' => 'int', - ]) - ->addField([ - 'columnName' => 'id_personne_physique', - 'fieldName' => 'authorId', - 'type' => 'int', - ]) - ; - - return $metadata; - } -} diff --git a/sources/AppBundle/Subscriber/SitemapXmlSubscriber.php b/sources/AppBundle/Subscriber/SitemapXmlSubscriber.php index 8276671e0..49bc304bc 100644 --- a/sources/AppBundle/Subscriber/SitemapXmlSubscriber.php +++ b/sources/AppBundle/Subscriber/SitemapXmlSubscriber.php @@ -15,8 +15,7 @@ use AppBundle\Event\Model\Speaker; use AppBundle\Event\Model\Talk; use AppBundle\Site\Entity\Repository\FeuilleRepository; -use AppBundle\Site\Model\Article; -use AppBundle\Site\Model\Repository\ArticleRepository; +use AppBundle\Site\Entity\Repository\ArticleRepository; use Presta\SitemapBundle\Event\SitemapPopulateEvent; use Presta\SitemapBundle\Service\UrlContainerInterface; use Presta\SitemapBundle\Sitemap\Url\GoogleVideo; @@ -125,8 +124,7 @@ public function registerEventsTalksUrls(UrlContainerInterface $urls): void public function registerNewsUrls(UrlContainerInterface $urls): void { - /** @var Article[] $news */ - $news = $this->articleRepository->findAllPublishedNews(); + $news = $this->articleRepository->findAllPublishedArticles(); foreach ($news as $article) { $urls->addUrl( @@ -136,7 +134,7 @@ public function registerNewsUrls(UrlContainerInterface $urls): void ['code' => $article->getSlug()], UrlGeneratorInterface::ABSOLUTE_URL, ), - $article->getPublishedAt(), + $article->datePublication, UrlConcrete::CHANGEFREQ_DAILY, 1, ), @@ -144,7 +142,7 @@ public function registerNewsUrls(UrlContainerInterface $urls): void ); } - $total = $this->articleRepository->countPublishedNews([]); + $total = $this->articleRepository->countPublishedArticles([]); $byPage = ListAction::ARTICLES_PER_PAGE; $lastPage = ceil($total / $byPage); diff --git a/templates/admin/site/article_form.html.twig b/templates/admin/site/article_form.html.twig index 04fbf2c2a..d35de261f 100644 --- a/templates/admin/site/article_form.html.twig +++ b/templates/admin/site/article_form.html.twig @@ -25,9 +25,9 @@
- {{ form_row(form.title) }} - {{ form_row(form.leadParagraph) }} - {{ form_row(form.content) }} + {{ form_row(form.titre) }} + {{ form_row(form.chapeau) }} + {{ form_row(form.contenu) }}
@@ -37,13 +37,13 @@
- {{ form_row(form.path) }} - {{ form_row(form.rubricId) }} - {{ form_row(form.publishedAt) }} + {{ form_row(form.raccourci) }} + {{ form_row(form.rubrique) }} + {{ form_row(form.datePublication) }} {{ form_row(form.position) }} - {{ form_row(form.state) }} + {{ form_row(form.etat) }} {{ form_row(form.theme) }} - {{ form_row(form.eventId) }} + {{ form_row(form.idEvent) }}
diff --git a/templates/site/cms_page/display.html.twig b/templates/site/cms_page/display.html.twig index c067d266b..641c664fd 100644 --- a/templates/site/cms_page/display.html.twig +++ b/templates/site/cms_page/display.html.twig @@ -1,6 +1,6 @@ {% extends 'site/base.html.twig' %} -{% block title %}{{ article.title }} - AFUP{% endblock %} +{% block title %}{{ article.titre }} - AFUP{% endblock %} {% block header %} {{ parent() }} @@ -15,13 +15,9 @@

{{ rubrique.nom }}

-

{{ article.title }}

+

{{ article.titre }}

- {% if article.contentTypeMarkdown %} - {{ article.content|markdown|raw }} - {% else %} - {{ article.content|raw }} - {% endif %} + {{ article.contenu|markdown|raw }}
{% endblock %} diff --git a/templates/site/home.html.twig b/templates/site/home.html.twig index 4b29276b7..f64e2ba07 100644 --- a/templates/site/home.html.twig +++ b/templates/site/home.html.twig @@ -58,17 +58,17 @@ {% for actualite in actualites %}
  • - {{ actualite.publishedAt|date('d/m') }} + {{ actualite.datePublication|date('d/m') }} {% if loop.first %} - {{ actualite.title }} + {{ actualite.titre }}

    - {{ actualite.leadParagraph|raw }} + {{ actualite.chapeau|raw }} Lire plus

    {% else %} - {{ actualite.title }} + {{ actualite.titre }} {% endif %}
  • diff --git a/templates/site/news/display.html.twig b/templates/site/news/display.html.twig index d1c4d309b..769b20a78 100644 --- a/templates/site/news/display.html.twig +++ b/templates/site/news/display.html.twig @@ -1,17 +1,17 @@ {% extends 'site/base.html.twig' %} -{% block title %}{{ article.getTitle }} - AFUP{% endblock %} +{% block title %}{{ article.titre }} - AFUP{% endblock %} {% block metas %} {{ parent() }} - - + + - - - + + + {% endblock %} @@ -32,47 +32,47 @@
    - {% if article.state == -1 %} + {% if article.etat == enum('AppBundle\\Site\\Enum\\ArticleEtat').HorsLigne %}
    Hors ligne : article supprimé
    - {% elseif article.state == 0 %} + {% elseif article.etat == enum('AppBundle\\Site\\Enum\\ArticleEtat').EnAttente %}
    En attente : article non publié
    {% endif %} -

    {{ article.getTitle }}

    +

    {{ article.titre }}

    -

    {{ article.publishedAt|format_date('long') }}

    +

    {{ article.datePublication|format_date('long') }}

    - {{ article.formatedLeadParagraph|raw }} + {{ article.chapeauFormate|raw }}
    - {{ article.formatedContent|raw }} + {{ article.contenuFormate|raw }}

    {% if article.theme %} - Thème : {{ article.themeEnum.label }} / + Thème : {{ article.theme.label }} / {% endif %} {% if related_event %} Cycle de conférences : {{ related_event.title }} / {% endif %} - {% set year = article.publishedAt|date('Y') %} + {% set year = article.datePublication|date('Y') %} Année : {{ year }}

    {% if previous or next %} diff --git a/templates/site/news/list.html.twig b/templates/site/news/list.html.twig index 059de21fb..c0782a6de 100644 --- a/templates/site/news/list.html.twig +++ b/templates/site/news/list.html.twig @@ -27,10 +27,10 @@ {% if articles|length %} {% for article in articles %} {% endfor %} {% else %} diff --git a/tests/behat/features/Admin/Site/AdminSiteArticles.feature b/tests/behat/features/Admin/Site/AdminSiteArticles.feature index ea459d87f..a41c52960 100644 --- a/tests/behat/features/Admin/Site/AdminSiteArticles.feature +++ b/tests/behat/features/Admin/Site/AdminSiteArticles.feature @@ -7,10 +7,10 @@ Feature: Administration - Partie Site And I should see "Actualités" When I follow "Ajouter" Then I should see "Ajouter un article" - And I fill in "article[title]" with "Le titre mini" - And I fill in "article[content]" with "Le contenu mini" - And I fill in "article[path]" with "url-article-mini" - And I select "Actualités" from "article[rubricId]" + And I fill in "article[titre]" with "Le titre mini" + And I fill in "article[contenu]" with "Le contenu mini" + And I fill in "article[raccourci]" with "url-article-mini" + And I select "Actualités" from "article[rubrique]" And I select "9" from "article[position]" And I press "Ajouter" When I should see "Liste des articles" @@ -26,15 +26,15 @@ Feature: Administration - Partie Site # ajout d'un article When I follow "Ajouter" Then I should see "Ajouter un article" - And I fill in "article[title]" with "Le titre de l'article" - And I fill in "article[leadParagraph]" with "Le chapeau de l'article" - And I fill in "article[content]" with "Le contenu de l'article" - And I fill in "article[path]" with "url-article" - And I select "Actualités" from "article[rubricId]" + And I fill in "article[titre]" with "Le titre de l'article" + And I fill in "article[chapeau]" with "Le chapeau de l'article" + And I fill in "article[contenu]" with "Le contenu de l'article" + And I fill in "article[raccourci]" with "url-article" + And I select "Actualités" from "article[rubrique]" And I select "9" from "article[position]" - And I select "En ligne" from "article[state]" + And I select "En ligne" from "article[etat]" And I select "Associatif" from "article[theme]" - And I select "forum" from "article[eventId]" + And I select "forum" from "article[idEvent]" And I press "Ajouter" When I should see "Liste des articles" Then the ".content table" element should contain "Le titre de l'article" @@ -49,7 +49,7 @@ Feature: Administration - Partie Site And I follow "Articles" Then I follow "modifier_2" And I should see "Modifier un article" - And I fill in "article[title]" with "Le titre de l'article modifié" + And I fill in "article[titre]" with "Le titre de l'article modifié" And I press "Modifier" And I should see "Liste des article" Then the ".content table" element should contain "Le titre de l'article modifié" @@ -65,7 +65,7 @@ Feature: Administration - Partie Site And I follow "Articles" Then I should see "Liste des articles" When I follow "Ajouter" - And I fill in "article[path]" with "un mauvais raccourci" + And I fill in "article[raccourci]" with "un mauvais raccourci" And I press "Ajouter" Then I should see "Ne doit pas contenir d'espace" @@ -73,10 +73,10 @@ Feature: Administration - Partie Site Given I am logged in as admin and on the Administration And I follow "Articles" When I follow "Ajouter" - And I fill in "article[title]" with "Le titre qui envoi 🚀" - And I fill in "article[content]" with "Pour un contenu rigolo 🤣" - And I fill in "article[path]" with "url-article-emojis" - And I select "Actualités" from "article[rubricId]" + And I fill in "article[titre]" with "Le titre qui envoi 🚀" + And I fill in "article[contenu]" with "Pour un contenu rigolo 🤣" + And I fill in "article[raccourci]" with "url-article-emojis" + And I select "Actualités" from "article[rubrique]" And I select "9" from "article[position]" And I press "Ajouter" When I should see "Liste des articles" @@ -92,11 +92,11 @@ Feature: Administration - Partie Site # Ajouter l'article When I follow "Ajouter" Then I should see "Ajouter un article" - And I fill in "article[title]" with "Le titre markdown" - And I fill in "article[leadParagraph]" with "Le *chapeau* markdown" - And I fill in "article[content]" with "Un peu de **contenu** avec de la *mise* en page et un [lien](https://afup.org)." - And I fill in "article[path]" with "url-article-markdown" - And I select "Actualités" from "article[rubricId]" + And I fill in "article[titre]" with "Le titre markdown" + And I fill in "article[chapeau]" with "Le *chapeau* markdown" + And I fill in "article[contenu]" with "Un peu de **contenu** avec de la *mise* en page et un [lien](https://afup.org)." + And I fill in "article[raccourci]" with "url-article-markdown" + And I select "Actualités" from "article[rubrique]" And I select "9" from "article[position]" And I press "Ajouter" @@ -108,6 +108,6 @@ Feature: Administration - Partie Site # Vérification du markdown inchangé dans le formulaire Then I follow "modifier_17" And I should see "Modifier un article" - And the "article[title]" field should contain "Le titre markdown" - And the "article[leadParagraph]" field should contain "Le *chapeau* markdown" - And the "article[content]" field should contain "Un peu de **contenu** avec de la *mise* en page et un [lien](https://afup.org)." + And the "article[titre]" field should contain "Le titre markdown" + And the "article[chapeau]" field should contain "Le *chapeau* markdown" + And the "article[contenu]" field should contain "Un peu de **contenu** avec de la *mise* en page et un [lien](https://afup.org)." diff --git a/tests/behat/features/Admin/Site/AdminSiteRubriques.feature b/tests/behat/features/Admin/Site/AdminSiteRubriques.feature index 06a52178e..9a53bc31a 100644 --- a/tests/behat/features/Admin/Site/AdminSiteRubriques.feature +++ b/tests/behat/features/Admin/Site/AdminSiteRubriques.feature @@ -35,7 +35,7 @@ Feature: Administration - Partie Site Then I should see "Liste des articles" When I follow "Ajouter" Then I should see "Ajouter un article" - Then The "article[rubricId]" field should only contain the follow values '["Actualités", "Évènements"]' + Then The "article[rubrique]" field should only contain the follow values '["Actualités", "Évènements"]' # suppression d'une rubrique When I follow "Rubriques"