diff --git a/app/config/packages/backoffice_menu.yaml b/app/config/packages/backoffice_menu.yaml
index a3d7edb6e..7a53bbcf9 100644
--- a/app/config/packages/backoffice_menu.yaml
+++ b/app/config/packages/backoffice_menu.yaml
@@ -157,12 +157,14 @@ parameters:
forum_facturation:
nom: "Factures d'évènement"
niveau: 'ROLE_ADMIN'
- forum_sessions:
+ talks:
nom: 'Conférences'
niveau: 'ROLE_FORUM'
url: '/admin/talk/'
extra_routes:
- admin_talk_list
+ - admin_talk_add
+ - admin_talk_edit
forum_vote_github:
nom: 'Votes visiteurs'
niveau: 'ROLE_FORUM'
diff --git a/app/config/routing/admin_talk.yml b/app/config/routing/admin_talk.yml
index 5658331eb..f76bb7915 100644
--- a/app/config/routing/admin_talk.yml
+++ b/app/config/routing/admin_talk.yml
@@ -2,6 +2,22 @@ admin_talk_list:
path: /
defaults: {_controller: AppBundle\Controller\Admin\Talk\IndexAction}
+admin_talk_add:
+ path: /add
+ defaults: {_controller: AppBundle\Controller\Admin\Talk\AddAction}
+
+admin_talk_edit:
+ path: /edit/{id}
+ defaults: {_controller: AppBundle\Controller\Admin\Talk\EditAction}
+ requirements:
+ id: \d+
+
+admin_talk_delete:
+ path: /delete/{id}
+ defaults: {_controller: AppBundle\Controller\Admin\Talk\DeleteAction}
+ requirements:
+ id: \d+
+
admin_talk_export_joind_in:
path: /export/joind-in/{eventId}/
defaults: {_controller: AppBundle\Controller\Admin\Talk\ExportJoindInAction}
diff --git a/sources/AppBundle/Controller/Admin/HomeAction.php b/sources/AppBundle/Controller/Admin/HomeAction.php
index f93172e74..69a43197d 100644
--- a/sources/AppBundle/Controller/Admin/HomeAction.php
+++ b/sources/AppBundle/Controller/Admin/HomeAction.php
@@ -15,6 +15,7 @@
use Psr\Clock\ClockInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
+use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
class HomeAction extends AbstractController
{
@@ -27,6 +28,7 @@ public function __construct(
private readonly StatisticsComputer $statisticsComputer,
private readonly ClockInterface $clock,
private readonly Authentication $authentication,
+ private readonly UrlGeneratorInterface $urlGenerator,
) {}
public function __invoke(): Response
@@ -37,7 +39,7 @@ public function __invoke(): Response
$cfp = [
'title' => 'CFP',
'subtitle' => 'Talks et speakers',
- 'url' => '/pages/administration/index.php?page=forum_sessions',
+ 'url' => $this->urlGenerator->generate('admin_talk_list'),
'statistics' => [],
];
diff --git a/sources/AppBundle/Controller/Admin/Talk/AddAction.php b/sources/AppBundle/Controller/Admin/Talk/AddAction.php
new file mode 100644
index 000000000..3c9b08f35
--- /dev/null
+++ b/sources/AppBundle/Controller/Admin/Talk/AddAction.php
@@ -0,0 +1,51 @@
+setForumId($eventSelection->event->getId());
+
+ $form = $this->createForm(TalkAdminType::class, $talk, [
+ 'event' => $eventSelection->event,
+ ]);
+
+ $form->handleRequest($request);
+ if ($form->isSubmitted() && $form->isValid()) {
+ $this->talkRepository->save($talk);
+ $this->talkToSpeakersRepository->replaceSpeakers($talk, $form->get('speakers')->getData());
+
+ $this->audit->log(sprintf('Ajout de la session de %s', $talk->getTitle()));
+ $this->addFlash('notice', 'La conférence a été ajoutée.');
+
+ return $this->redirectToRoute('admin_talk_list');
+ }
+
+ return $this->render('admin/talk/add.html.twig', [
+ 'event' => $eventSelection->event,
+ 'form' => $form,
+ ]);
+ }
+
+}
diff --git a/sources/AppBundle/Controller/Admin/Talk/DeleteAction.php b/sources/AppBundle/Controller/Admin/Talk/DeleteAction.php
new file mode 100644
index 000000000..be9e3279f
--- /dev/null
+++ b/sources/AppBundle/Controller/Admin/Talk/DeleteAction.php
@@ -0,0 +1,35 @@
+talkRepository->get($id);
+ if (!$talk instanceof Talk) {
+ throw $this->createNotFoundException(sprintf('Talk not found with id "%s"', $id));
+ }
+ $this->talkRepository->delete($talk);
+
+ $this->audit->log(sprintf('Suppression de la session de %s (%d)', $talk->getTitle(), $talk->getId()));
+ $this->addFlash('notice', 'La conférence a été supprimée.');
+
+ return $this->redirectToRoute('admin_talk_list');
+ }
+
+}
diff --git a/sources/AppBundle/Controller/Admin/Talk/EditAction.php b/sources/AppBundle/Controller/Admin/Talk/EditAction.php
new file mode 100644
index 000000000..d3ec7e459
--- /dev/null
+++ b/sources/AppBundle/Controller/Admin/Talk/EditAction.php
@@ -0,0 +1,62 @@
+talkRepository->get($id);
+ if (!$talk instanceof Talk) {
+ throw $this->createNotFoundException(sprintf('Talk not found with id "%s"', $id));
+ }
+ $event = $this->eventRepository->get($talk->getForumId());
+ if (!$event instanceof Event) {
+ throw $this->createNotFoundException(sprintf('Event not found with id "%s"', $talk->getForumId()));
+ }
+
+ $form = $this->createForm(TalkAdminType::class, $talk, [
+ 'event' => $event,
+ TalkType::OPT_COC_CHECKED => true,
+ ]);
+
+ $form->handleRequest($request);
+ if ($form->isSubmitted() && $form->isValid()) {
+ $this->talkRepository->save($talk);
+ $this->talkToSpeakersRepository->replaceSpeakers($talk, $form->get('speakers')->getData());
+
+ $this->audit->log(sprintf('Modification de la session de %s (%d)', $talk->getTitle(), $talk->getId()));
+ $this->addFlash('notice', 'La conférence a été modifiée.');
+
+ return $this->redirectToRoute('admin_talk_list');
+ }
+
+ return $this->render('admin/talk/edit.html.twig', [
+ 'event' => $event,
+ 'form' => $form,
+ 'talk' => $talk,
+ ]);
+ }
+
+}
diff --git a/sources/AppBundle/Controller/Admin/Talk/IndexAction.php b/sources/AppBundle/Controller/Admin/Talk/IndexAction.php
index 76996683d..3e7b22f2e 100644
--- a/sources/AppBundle/Controller/Admin/Talk/IndexAction.php
+++ b/sources/AppBundle/Controller/Admin/Talk/IndexAction.php
@@ -26,15 +26,6 @@ public function __construct(
public function __invoke(Request $request, AdminEventSelection $eventSelection): Response
{
- //TODO : à supprimer quand les actions via le formulaire auront été migrées
- if (isset($_SESSION['flash']['message'])) {
- $this->addFlash('notice', $_SESSION['flash']['message']);
- }
- if (isset($_SESSION['flash']['erreur'])) {
- $this->addFlash('error', $_SESSION['flash']['erreur']);
- }
- unset($_SESSION['flash']);
-
$event = $eventSelection->event;
$data = [
diff --git a/sources/AppBundle/Controller/Admin/Talk/UpdateIndexationAction.php b/sources/AppBundle/Controller/Admin/Talk/UpdateIndexationAction.php
index 7ea027a3c..d35ecb332 100644
--- a/sources/AppBundle/Controller/Admin/Talk/UpdateIndexationAction.php
+++ b/sources/AppBundle/Controller/Admin/Talk/UpdateIndexationAction.php
@@ -21,6 +21,6 @@ public function __invoke(Request $request): RedirectResponse
$this->addFlash('notice', 'Indexation effectuée');
- return $this->redirect($request->headers->get('referer', '/pages/administration/index.php?page=forum_sessions'));
+ return $this->redirect($request->headers->get('referer', $this->generateUrl('admin_talk_list')));
}
}
diff --git a/sources/AppBundle/Event/Form/TalkAdminType.php b/sources/AppBundle/Event/Form/TalkAdminType.php
new file mode 100644
index 000000000..caebdd809
--- /dev/null
+++ b/sources/AppBundle/Event/Form/TalkAdminType.php
@@ -0,0 +1,112 @@
+speakerRepository->searchSpeakers($options['event']);
+ $speakers = $this->speakerRepository->getSpeakersByTalk($builder->getData());
+ $speakers = iterator_to_array($speakers->getIterator());
+
+ $builder->remove('hasAllowedToSharingWithLocalOffices');
+ $builder
+ ->add('hasAllowedToSharingWithLocalOffices', CheckboxType::class, [
+ 'label' => 'Autoriser l’AFUP à transmettre ma proposition de conférence à ses antennes locales ?',
+ 'required' => false,
+ ])
+ ->add('joindinId', TextType::class, [
+ 'label' => 'joind.in ID',
+ 'required' => false,
+ 'attr' => ['placeholder' => '4639e'],
+ ])
+ ->add('youtubeId', TextType::class, [
+ 'label' => 'Youtube ID',
+ 'required' => false,
+ 'attr' => ['placeholder' => '9P7K3sdg6s4'],
+ ])
+ ->add('slidesUrl', UrlType::class, [
+ 'label' => 'URL des slides',
+ 'required' => false,
+ 'default_protocol' => 'https',
+ 'attr' => ['placeholder' => 'https://'],
+ ])
+ ->add('openfeedbackPath', TextType::class, [
+ 'label' => 'Open Feedback (path)',
+ 'required' => false,
+ 'attr' => ['placeholder' => 'forumphp2025/2025-10-09/5394'],
+ ])
+ ->add('blogPostUrl', UrlType::class, [
+ 'label' => 'URL du blog',
+ 'required' => false,
+ 'default_protocol' => 'https',
+ 'attr' => ['placeholder' => 'https://'],
+ ])
+ ->add('interviewUrl', UrlType::class, [
+ 'label' => 'URL de l\'interview',
+ 'required' => false,
+ 'default_protocol' => 'https',
+ 'attr' => ['placeholder' => 'https://'],
+ ])
+ ->add('languageCode', ChoiceType::class, [
+ 'label' => 'Langue',
+ 'required' => false,
+ 'choices' => array_flip(Talk::getLanguageLabelsByKey()),
+ ])
+ ->add('tweets', TextareaType::class, [
+ 'label' => 'Tweets',
+ 'required' => false,
+ ])
+ ->add('submittedOn', DateTimeType::class, [
+ 'label' => 'Date de soumission',
+ ])
+ ->add('publishedOn', DateTimeType::class, [
+ 'label' => 'Date de publication',
+ 'required' => false,
+ ])
+ ->add('speakers', ChoiceType::class, [
+ 'mapped' => false,
+ 'multiple' => true,
+ 'choices' => $allSpeakers,
+ 'required' => false,
+ 'data' => $speakers,
+ 'choice_label' => fn(Speaker $speaker) => $speaker->getLabel(),
+ 'choice_value' => fn(?Speaker $speaker) => $speaker?->getId(),
+ 'choice_name' => 'id',
+ ])
+ ->add('verbatim', TextareaType::class, [
+ 'label' => 'Verbatim',
+ 'required' => false,
+ ])
+ ->add('save', SubmitType::class)
+ ;
+ }
+
+ public function configureOptions(OptionsResolver $resolver): void
+ {
+ $resolver->setDefault('data_class', Talk::class);
+ $resolver->setDefault('event', EventType::class);
+
+ parent::configureOptions($resolver);
+ }
+}
diff --git a/sources/AppBundle/Event/Model/Repository/TalkRepository.php b/sources/AppBundle/Event/Model/Repository/TalkRepository.php
index da4b3c28e..513127fa3 100644
--- a/sources/AppBundle/Event/Model/Repository/TalkRepository.php
+++ b/sources/AppBundle/Event/Model/Repository/TalkRepository.php
@@ -459,6 +459,14 @@ public static function initMetadata(SerializerFactoryInterface $serializerFactor
'unserialize' => ['unSerializeUseFormat' => false],
],
])
+ ->addField([
+ 'columnName' => 'date_publication',
+ 'fieldName' => 'publishedOn',
+ 'type' => 'datetime',
+ 'serializer_options' => [
+ 'unserialize' => ['unSerializeUseFormat' => false],
+ ],
+ ])
->addField([
'columnName' => 'titre',
'fieldName' => 'title',
diff --git a/sources/AppBundle/Event/Model/Repository/TalkToSpeakersRepository.php b/sources/AppBundle/Event/Model/Repository/TalkToSpeakersRepository.php
index 8e91648ea..765cf351f 100644
--- a/sources/AppBundle/Event/Model/Repository/TalkToSpeakersRepository.php
+++ b/sources/AppBundle/Event/Model/Repository/TalkToSpeakersRepository.php
@@ -50,7 +50,7 @@ public function replaceSpeakers(Talk $talk, array $speakers): void
{
$this->startTransaction();
try {
- $delete = $this->getPreparedQuery('DELETE FROM afup_conferenciers_sessions WHERE talk_id = :talk');
+ $delete = $this->getPreparedQuery('DELETE FROM afup_conferenciers_sessions WHERE session_id = :talk');
$delete->setParams(['talk' => $talk->getId()])->execute();
$insert = $this->getPreparedQuery('INSERT INTO afup_conferenciers_sessions (conferencier_id, session_id) VALUES (:speaker, :talk)');
diff --git a/sources/AppBundle/Event/Model/Talk.php b/sources/AppBundle/Event/Model/Talk.php
index 1a21bc7aa..df57ca4ea 100644
--- a/sources/AppBundle/Event/Model/Talk.php
+++ b/sources/AppBundle/Event/Model/Talk.php
@@ -36,7 +36,8 @@ class Talk implements NotifyPropertyInterface
#[Assert\GreaterThan(0)]
private ?int $forumId = null;
- private ?\DateTime $submittedOn = null;
+ private \DateTime $submittedOn;
+ private ?\DateTime $publishedOn = null;
#[Assert\NotBlank]
private string $title = '';
@@ -96,6 +97,12 @@ class Talk implements NotifyPropertyInterface
*/
private array $votes = [];
+ public function __construct()
+ {
+ $this->submittedOn = new \DateTime();
+ $this->languageCode = self::LANGUAGE_CODE_FR;
+ }
+
public function getId(): ?int
{
return $this->id;
@@ -132,6 +139,18 @@ public function setSubmittedOn(\DateTime $submittedOn): self
return $this;
}
+ public function getPublishedOn(): ?\DateTime
+ {
+ return $this->publishedOn;
+ }
+
+ public function setPublishedOn(?\DateTime $publishedOn): self
+ {
+ $this->propertyChanged('publishedOn', $this->publishedOn, $publishedOn);
+ $this->publishedOn = $publishedOn;
+ return $this;
+ }
+
public function getTitle(): string
{
return $this->title;
@@ -355,6 +374,7 @@ public function getOpenfeedbackPath(): ?string
public function setOpenfeedbackPath(?string $openfeedbackPath): self
{
+ $this->propertyChanged('openfeedbackPath', $this->openfeedbackPath, $openfeedbackPath);
$this->openfeedbackPath = $openfeedbackPath;
return $this;
@@ -544,6 +564,7 @@ public function getTweets(): ?string
public function setTweets(?string $tweets): self
{
+ $this->propertyChanged('tweets', $this->tweets, $tweets);
$this->tweets = $tweets;
return $this;
diff --git a/sources/AppBundle/Slack/MessageFactory.php b/sources/AppBundle/Slack/MessageFactory.php
index 0183b334f..44676dd84 100644
--- a/sources/AppBundle/Slack/MessageFactory.php
+++ b/sources/AppBundle/Slack/MessageFactory.php
@@ -22,7 +22,10 @@ class MessageFactory
/**
* MessageFactory constructor.
*/
- public function __construct(private readonly TranslatorInterface $translator) {}
+ public function __construct(
+ private readonly TranslatorInterface $translator,
+ private readonly UrlGeneratorInterface $urlGenerator,
+ ) {}
public function createMessageForVote(Vote $vote): Message
{
@@ -76,10 +79,12 @@ public function createMessageForVote(Vote $vote): Message
*/
public function createMessageForTalk(Talk $talk, Event $event): Message
{
+ $link = $this->urlGenerator->generate('admin_talk_list', ['id' => $event->getId()], UrlGeneratorInterface::ABSOLUTE_URL);
+
$attachment = new Attachment();
$attachment
->setTitle('Nouvelle proposition sur le CFP - ' . $event->getTitle())
- ->setTitleLink('https://afup.org/pages/administration/index.php?' . http_build_query(['page' => 'forum_sessions', 'id_forum' => $event->getId()]))
+ ->setTitleLink($link)
->setFallback(sprintf(
'Nouvelle proposition intitulée "%s". Type %s - Public %s',
$talk->getTitle(),
@@ -260,10 +265,12 @@ public function createMessageForCfpStats(Event $event, TalkRepository $talkRepos
}
}
+ $link = $this->urlGenerator->generate(name: 'admin_talk_list', referenceType: UrlGeneratorInterface::ABSOLUTE_URL);
+
$attachment = new Attachment();
$attachment
->setTitle(sprintf('Total des réponses au CFP du %s', $event->getTitle()))
- ->setTitleLink('https://afup.org/pages/administration/index.php?page=forum_sessions')
+ ->setTitleLink($link)
;
foreach ($this->prepareCfpStatsFields($talkRepository, $talkToSpeakersRepository, $event) as $field) {
diff --git a/templates/admin/speaker/edit.html.twig b/templates/admin/speaker/edit.html.twig
index a027d3aa7..e2c5fa357 100644
--- a/templates/admin/speaker/edit.html.twig
+++ b/templates/admin/speaker/edit.html.twig
@@ -67,7 +67,7 @@