Skip to content
Snippets Groups Projects
Commit 615bca68 authored by Pierre Fleutot's avatar Pierre Fleutot
Browse files

Labels: Création commande à exécuter en tâche CRON pour supprimer...

Labels: Création commande à exécuter en tâche CRON pour supprimer définitivement les labels en cours de suppression après N jours.
Fix: après succès d'un vote de suppression d'un label, le label n'est pas immédiatement supprimé mais passe à "suppression en cours" avec envoi de notification à tous les users qui peuvent le voir.
Copie vers labels persos d'un label en cours de suppression: Si le label créé entre en conflit avec un label existant, on le merge dans l'existant
Une mention "suppression en cours" + transparence est ajoutée sur les labels concernés.
Ajout dans .env du délai de suppression définitive et du nb de votes nécessaires pour valider une action, modif du code correspondant.
Utilisation des Voters pour sécuriser les actions des lexiques et des labels.
parent a73694b7
No related branches found
No related tags found
No related merge requests found
Showing
with 270 additions and 74 deletions
...@@ -49,8 +49,6 @@ class BalexInitializeCommand extends Command ...@@ -49,8 +49,6 @@ class BalexInitializeCommand extends Command
// $email = $input->getOption('email'); // $email = $input->getOption('email');
// $password = $input->getOption('password'); // $password = $input->getOption('password');
//TODO utiliser la liste de langues
// Création des lexiques zéro et new word // Création des lexiques zéro et new word
foreach (LanguagesIso::getCodes() as $language) { foreach (LanguagesIso::getCodes() as $language) {
// Création d'un lexique Zéro par langue // Création d'un lexique Zéro par langue
......
<?php
namespace App\Command;
use App\Entity\Label;
use App\Entity\Lexicon;
use App\Entity\Log;
use App\Entity\User;
use App\Languages\LanguagesIso;
use Doctrine\ORM\EntityManagerInterface;
use League\Bundle\OAuth2ServerBundle\Model\Client;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
use Symfony\Component\Uid\UuidV1;
class DeleteLabelsNDaysAfterRequestCommand extends Command
{
protected $daysNbBeforeRemoval;
protected static $defaultName = "app:label:completeDeletion";
protected static $defaultDescription = "On supprime les labels en cours de suppression depuis plus de N jours";
/**
* @var EntityManagerInterface
*/
private $em;
public function __construct(EntityManagerInterface $em)
{
parent::__construct();
$this->em = $em;
$this->daysNbBeforeRemoval = $_ENV['COMPLETE_DELETE_LABEL_DAYS_DELAY'];
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);
$labels = $this->em->getRepository(Label::class)->getLabelsToBeRemoved($this->daysNbBeforeRemoval);
$count = 0;
foreach ($labels as $label) {
// !!! L'updateRequest est supprimée en cascade, on garde un trace dans un log
$log = new Log();
$log->setCategory(Log::CATEGORY_DELETE_LABEL);
$log->setContent((string) $label);
$this->em->persist($log);
$this->em->remove($label);
$io->success(sprintf("label %s supprimé", $label));
$count++;
}
$this->em->flush();
$io->success(sprintf("%s labels définitivement supprimés", $count));
return Command::SUCCESS;
}
}
...@@ -199,6 +199,19 @@ class AppBaseController extends AbstractController ...@@ -199,6 +199,19 @@ class AppBaseController extends AbstractController
return $this->requestStack->getSession()->get('studied_language'); return $this->requestStack->getSession()->get('studied_language');
} }
// Retourne tous les utilisateurs partageant la langue de travail montrée actuellement
public function getAllUsersForCurrentLanguage()
{
$currentLanguage = $this->getLanguage();
$users = [];
foreach ($this->em->getRepository(User::class)->findAll() as $user) {
if ($user->hasStudiedLanguage($currentLanguage)) {
$users[] = $user;
}
}
return $users;
}
/** /**
* - Si on trouve un headword dont la valeur est word, on le retourne * - Si on trouve un headword dont la valeur est word, on le retourne
* - Sinon, si on trouve une graphie dont la valeur est word, on retourne le premier headword lié à cette graphie s'il existe. * - Sinon, si on trouve une graphie dont la valeur est word, on retourne le premier headword lié à cette graphie s'il existe.
......
...@@ -151,6 +151,7 @@ class LabelController extends AppBaseController ...@@ -151,6 +151,7 @@ class LabelController extends AppBaseController
/** /**
* @Route("/{id}/process-selected-entries", name="app_label_process_selected_headwords") * @Route("/{id}/process-selected-entries", name="app_label_process_selected_headwords")
* @IsGranted("LABEL_VIEW", subject="label")
*/ */
public function processSelectedEntries(Request $request, HeadwordRepository $headwordRepository, Label $label) public function processSelectedEntries(Request $request, HeadwordRepository $headwordRepository, Label $label)
{ {
...@@ -213,6 +214,7 @@ class LabelController extends AppBaseController ...@@ -213,6 +214,7 @@ class LabelController extends AppBaseController
/** /**
* @Route("/{id}/edit", name="app_label_edit", methods={"GET", "POST"}) * @Route("/{id}/edit", name="app_label_edit", methods={"GET", "POST"})
* @IsGranted("LABEL_EDIT", subject="label")
* *
* • Si renommage public => Vote => reload * • Si renommage public => Vote => reload
* • Si identiqueButMaster * • Si identiqueButMaster
...@@ -333,19 +335,25 @@ class LabelController extends AppBaseController ...@@ -333,19 +335,25 @@ class LabelController extends AppBaseController
/** /**
* @Route("/{id}/delete", name="app_label_delete") * @Route("/{id}/delete", name="app_label_delete")
* @IsGranted("LABEL_EDIT", subject="label")
*/ */
public function delete(Request $request, Label $label, LabelRepository $labelRepository): Response public function delete(Request $request, Label $label, LabelRepository $labelRepository): Response
{ {
$delete = false; $delete = false;
// Label de mon lexique perso => suppression directe
if ($label->getUser() === $this->getUser()) {
$delete = true;
} elseif ($label->hasDeleteUpdateRequest()) {
$this->addFlash('danger', "Un vote est déjà en cours pour valider la suppression de ce label.");
} elseif ($label->isDeleteInProgress()) {
$this->addFlash('danger', "Ce label est déjà en cours de suppression.");
// Label public // Label public
if ($label->isMasterPublic()) { } elseif ($label->isMasterPublic()) {
// Si le label n'est apposé à aucun mot-vedette => suppression directe // Si le label n'est apposé à aucun mot-vedette => suppression directe
if ($label->getHeadwords()->isEmpty()) { if ($label->getHeadwords()->isEmpty()) {
$delete = true; $delete = true;
// Sinon => on lance un vote // Sinon => on lance un vote
} elseif ($label->hasDeleteUpdateRequest()) {
$this->addFlash('danger', "Un vote est déjà en cours pour valider la suppression de ce label public.");
} else { } else {
$updateRequest = new UpdateRequest(UpdateRequest::CATEGORY_DELETE); $updateRequest = new UpdateRequest(UpdateRequest::CATEGORY_DELETE);
$updateRequest->setLabel($label); $updateRequest->setLabel($label);
...@@ -354,17 +362,12 @@ class LabelController extends AppBaseController ...@@ -354,17 +362,12 @@ class LabelController extends AppBaseController
$this->addFlash('success', "Un vote a été lancé pour valider la suppression de ce label public."); $this->addFlash('success', "Un vote a été lancé pour valider la suppression de ce label public.");
} }
// Label de groupe auquel j'appartiens => suppression en cours // Label de groupe auquel j'appartiens => suppression en cours
} elseif ($label->isMasterGroup() && $label->getGroup()->hasMember($this->getUser())) { } elseif ($label->isMasterGroup() && $label->getGroup()->hasMember($this->getUser())) {
$label->setGroupDeletionInProgress(true); $this->addFlash('success', sprintf("Le label est en cours de suppression. Il sera définitivement supprimé
$this->addFlash('success', "Le label est en cours de suppression. Il sera définitivement supprimé lorsque tous les membres du groupes l'auront copié ou supprimé, ou au terme de %s jours", $_ENV['COMPLETE_DELETE_LABEL_DAYS_DELAY']));
lorsque tous les membres du groupes l'auront copié ou supprimé, ou au terme de 45 jours");
$this->sendLabelDeletedNotificationToGroup($label); $this->sendLabelDeletedNotificationToGroup($label);
// Label de mon lexique perso => suppression directe
} elseif ($label->getUser() === $this->getUser()) {
$delete = true;
} }
if ($delete) { if ($delete) {
...@@ -379,8 +382,8 @@ class LabelController extends AppBaseController ...@@ -379,8 +382,8 @@ class LabelController extends AppBaseController
/** /**
* @Route("/{id}/edit-description", name="app_label_edit_description", methods={"GET", "POST"}) * @Route("/{id}/edit-description", name="app_label_edit_description", methods={"GET", "POST"})
* @IsGranted("LABEL_EDIT", subject="label")
*/ */
// * @IsGranted("LABEL_EDIT", subject="label")
public function editDescription(Request $request, Label $label, LabelRepository $labelRepository): Response public function editDescription(Request $request, Label $label, LabelRepository $labelRepository): Response
{ {
$form = $this->createForm(LabelDescriptionType::class, $label, [ $form = $this->createForm(LabelDescriptionType::class, $label, [
...@@ -402,6 +405,7 @@ class LabelController extends AppBaseController ...@@ -402,6 +405,7 @@ class LabelController extends AppBaseController
/** /**
* @Route("/{id}/add-word-from-searchbar", name="app_label_add_word_from_search_bar") * @Route("/{id}/add-word-from-searchbar", name="app_label_add_word_from_search_bar")
* @IsGranted("LABEL_EDIT", subject="label")
* *
* Barre de recherche : on cherche le mot en ajax. Si on le trouve dans le wiko, on l'importe et on recharge la page, sinon on ouvre la modale de confirmation d'ajout * Barre de recherche : on cherche le mot en ajax. Si on le trouve dans le wiko, on l'importe et on recharge la page, sinon on ouvre la modale de confirmation d'ajout
*/ */
...@@ -434,8 +438,8 @@ class LabelController extends AppBaseController ...@@ -434,8 +438,8 @@ class LabelController extends AppBaseController
/** /**
* @Route("/{id}/add-headword", name="app_label_add_headword", methods={"GET", "POST"}) * @Route("/{id}/add-headword", name="app_label_add_headword", methods={"GET", "POST"})
* @IsGranted("LABEL_EDIT", subject="label")
*/ */
// * @IsGranted("LABEL_EDIT", subject="label")
public function addHeadword(WiktionaryManager $wiktionaryManager, Request $request, Label $label): Response public function addHeadword(WiktionaryManager $wiktionaryManager, Request $request, Label $label): Response
{ {
$form = $this->createForm(EnterWordType::class, null, [ $form = $this->createForm(EnterWordType::class, null, [
...@@ -478,6 +482,7 @@ class LabelController extends AppBaseController ...@@ -478,6 +482,7 @@ class LabelController extends AppBaseController
* @Route("/{id}/toggle-visibility-in-lexicon/{lexiconId}", name="app_label_toggle_visibility", methods={"GET"}) * @Route("/{id}/toggle-visibility-in-lexicon/{lexiconId}", name="app_label_toggle_visibility", methods={"GET"})
* @ParamConverter("label", options={"id" = "id"}) * @ParamConverter("label", options={"id" = "id"})
* @ParamConverter("lexicon", options={"id" = "lexiconId"}) * @ParamConverter("lexicon", options={"id" = "lexiconId"})
* @IsGranted("LEXICON_EDIT", subject="lexicon")
* *
*/ */
public function toggleVisibility(ManagerRegistry $doctrine, Request $request, Label $label, Lexicon $lexicon): Response public function toggleVisibility(ManagerRegistry $doctrine, Request $request, Label $label, Lexicon $lexicon): Response
...@@ -509,12 +514,11 @@ class LabelController extends AppBaseController ...@@ -509,12 +514,11 @@ class LabelController extends AppBaseController
return $this->redirectToRoute('app_label_index', ['showVisibility' => $label->getCategory()]); return $this->redirectToRoute('app_label_index', ['showVisibility' => $label->getCategory()]);
} }
/** /**
* @Route("/{deleted_label_id}/merge-into/{merged_label_id}", name="app_label_merge") * @Route("/{deleted_label_id}/merge-into/{merged_label_id}", name="app_label_merge")
* @ParamConverter("labelDeleted", options={"id" = "deleted_label_id"}) * @ParamConverter("labelDeleted", options={"id" = "deleted_label_id"})
* @ParamConverter("labelMerged", options={"id" = "merged_label_id"}) * @ParamConverter("labelMerged", options={"id" = "merged_label_id"})
* @IsGranted("LABEL_EDIT", subject="labelDeleted")
*/ */
public function mergeSimilarLabels(Request $request, Label $labelDeleted, Label $labelMerged, LabelManager $labelManager): Response public function mergeSimilarLabels(Request $request, Label $labelDeleted, Label $labelMerged, LabelManager $labelManager): Response
{ {
...@@ -533,29 +537,48 @@ class LabelController extends AppBaseController ...@@ -533,29 +537,48 @@ class LabelController extends AppBaseController
/** /**
* @Route("/{id}/copy-as-personal", name="app_label_copy_as_personal") * @Route("/{id}/copy-as-personal", name="app_label_copy_as_personal")
*/ */
public function copyAsPersonal(Request $request, Notification $notification): Response public function copyAsPersonal(Request $request, Notification $notification, LabelManager $labelManager): Response
{ {
$label = $notification->getUpdateRequest()->getLabel(); $label = $notification->getUpdateRequest()->getLabel();
$personalLabel = clone($label); $personalLabel = new Label($label->getCategory());
$personalLabel->setName($label->getName());
$personalLabel->setLanguage($label->getLanguage());
foreach ($label->getHeadwords() as $headword) {
$headword->addLabel($personalLabel);
}
$personalLabel->setUser($this->getUser()); $personalLabel->setUser($this->getUser());
$personalLabel->setGroup(null); $personalLabel->setGroup(null);
$personalLabel->setMaster(Label::MASTER_PERSONAL); $personalLabel->setMaster(Label::MASTER_PERSONAL);
$this->em->persist($personalLabel); $this->em->persist($personalLabel);
$this->em->flush();
// Si le label créé entre en conflit avec un label existant, on le merge dans l'existant
$currentLabelWithSameName = $this->em->getRepository(Label::class)->findOneBy([
'name' => $personalLabel->getName(),
'user' => $personalLabel->getUser(),
]);
if ($currentLabelWithSameName) {
$labelManager->mergeLabels($personalLabel, $currentLabelWithSameName);
$this->addFlash('success', "Le label a été fusionné dans votre label personnel de même nom");
} else {
$this->addFlash('success', "Le label a été dupliqué dans vos labels personnels");
}
$notification->setStatus(Notification::STATUS_READ); $notification->setStatus(Notification::STATUS_READ);
$this->em->flush(); $this->em->flush();
$this->addFlash('success', "Le label a été dupliqué dans vos labels personnels");
// On regarde si tous les membres du groupe on pris une décision dupliquer ou ne rien faire (càd que toutes les notifs sont lues) // On regarde si tous les membres du groupe on pris une décision dupliquer ou ne rien faire (càd que toutes les notifs sont lues)
if ($notification->getUpdateRequest()->allNotificationsAreRead()) { if ($notification->getUpdateRequest()->allNotificationsAreRead()) {
$notification->getUpdateRequest()->setStatus(UpdateRequest::STATUS_ACCEPTED); $notification->getUpdateRequest()->setStatus(UpdateRequest::STATUS_ACCEPTED);
// !!! L'updateRequest est supprimée en cascade, on garde un trace dans un log
$this->addLabelLog($label, Log::CATEGORY_DELETE_LABEL);
$this->em->remove($label); $this->em->remove($label);
$this->em->flush(); $this->em->flush();
$this->addFlash('success', "Le label de groupe a été supprimé définitivement"); $this->addFlash('success', "Le label a été supprimé définitivement");
} }
return $this->render('reloadPage.js.twig'); return $this->render('redirect.js.twig', ['url' => $this->generateUrl('app_label_index')]);
} }
private function isMorePrivate(Label $label, Label $identicalLabel) private function isMorePrivate(Label $label, Label $identicalLabel)
...@@ -564,6 +587,8 @@ class LabelController extends AppBaseController ...@@ -564,6 +587,8 @@ class LabelController extends AppBaseController
|| ($label->isMasterGroup() && $identicalLabel->isMasterPublic()); || ($label->isMasterGroup() && $identicalLabel->isMasterPublic());
} }
// On crée une UpdateRequest "suppression en cours" et on envoie les notifications correspondantes à tous les utilisateurs qui peuvent voir le label
// Ils pourront alors choisir de faire uen copie perso du label avant suppression totale ou ne rien faire.
private function sendLabelDeletedNotificationToGroup(Label $label) private function sendLabelDeletedNotificationToGroup(Label $label)
{ {
$updateRequest = new UpdateRequest(UpdateRequest::CATEGORY_DELETE_IN_PROGRESS); $updateRequest = new UpdateRequest(UpdateRequest::CATEGORY_DELETE_IN_PROGRESS);
...@@ -577,6 +602,12 @@ class LabelController extends AppBaseController ...@@ -577,6 +602,12 @@ class LabelController extends AppBaseController
$notification->setUser($member); $notification->setUser($member);
$notification->setContent("Un label de groupe est en cours de suppression. Voulez-vous le dupliquer avant sa suppression définitive ?"); $notification->setContent("Un label de groupe est en cours de suppression. Voulez-vous le dupliquer avant sa suppression définitive ?");
$notification->setUpdateRequest($updateRequest); $notification->setUpdateRequest($updateRequest);
// Pour que l'user qui initie la suppression ne reçoive pas la notif de suggestion de copie du label supprimé dans les labels persos
// Servait aussi pour que le label n'apparaisse plus du tout immédiatement pour l'user qui initie la suppression, mais c'est mieux de continuer de l'afficher
if ($member === $this->getUser()) {
$notification->setStatus(Notification::STATUS_READ);
}
} }
} }
} }
...@@ -31,6 +31,7 @@ class LexiconController extends AppBaseController ...@@ -31,6 +31,7 @@ class LexiconController extends AppBaseController
{ {
/** /**
* @Route("/{id}", name="app_lexicon_show", methods={"GET"}) * @Route("/{id}", name="app_lexicon_show", methods={"GET"})
* @IsGranted("LEXICON_VIEW", subject="lexicon")
*/ */
public function show(ManagerRegistry $doctrine, Request $request, Lexicon $lexicon): Response public function show(ManagerRegistry $doctrine, Request $request, Lexicon $lexicon): Response
{ {
...@@ -94,8 +95,10 @@ class LexiconController extends AppBaseController ...@@ -94,8 +95,10 @@ class LexiconController extends AppBaseController
'searchString' => $form->get('searchString')->getData(), 'searchString' => $form->get('searchString')->getData(),
]); ]);
} }
/** /**
* @Route("/new-words/{id}", name="app_lexicon_new_words", methods={"GET"}) * @Route("/new-words/{id}", name="app_lexicon_new_words", methods={"GET"})
* @IsGranted("LEXICON_VIEW", subject="lexicon")
*/ */
public function showNewWordsLexicon(ManagerRegistry $doctrine, Request $request, Lexicon $lexicon): Response public function showNewWordsLexicon(ManagerRegistry $doctrine, Request $request, Lexicon $lexicon): Response
{ {
...@@ -123,6 +126,7 @@ class LexiconController extends AppBaseController ...@@ -123,6 +126,7 @@ class LexiconController extends AppBaseController
/** /**
* @Route("/{id}/choose-label-for-selection/{category}", name="app_lexicon_choose_label") * @Route("/{id}/choose-label-for-selection/{category}", name="app_lexicon_choose_label")
* @IsGranted("LEXICON_VIEW", subject="lexicon")
*/ */
public function chooseLabelForEntriesSelection(LabelManager $labelManager, Request $request, Lexicon $lexicon, $category) public function chooseLabelForEntriesSelection(LabelManager $labelManager, Request $request, Lexicon $lexicon, $category)
{ {
...@@ -134,6 +138,7 @@ class LexiconController extends AppBaseController ...@@ -134,6 +138,7 @@ class LexiconController extends AppBaseController
/** /**
* @Route("/{id}/process-selected-entries", name="app_lexicon_process_selected_entries") * @Route("/{id}/process-selected-entries", name="app_lexicon_process_selected_entries")
* @IsGranted("LEXICON_EDIT", subject="lexicon")
*/ */
public function processSelectedEntries(Request $request, EntryRepository $entryRepository, Lexicon $lexicon) public function processSelectedEntries(Request $request, EntryRepository $entryRepository, Lexicon $lexicon)
{ {
...@@ -177,7 +182,10 @@ class LexiconController extends AppBaseController ...@@ -177,7 +182,10 @@ class LexiconController extends AppBaseController
} }
/** /**
* Copie des entrées depuis lexicon vers le lexique choisi dans le formulaire
*
* @Route("/{id}/copy-entries", name="app_lexicon_copy_entries") * @Route("/{id}/copy-entries", name="app_lexicon_copy_entries")
* @IsGranted("LEXICON_VIEW", subject="lexicon")
*/ */
public function copySelection(Request $request, Lexicon $lexicon) public function copySelection(Request $request, Lexicon $lexicon)
{ {
...@@ -234,6 +242,7 @@ class LexiconController extends AppBaseController ...@@ -234,6 +242,7 @@ class LexiconController extends AppBaseController
/** /**
* @Route("/{id}/add-word-from-searchbar", name="app_lexicon_add_word_from_search_bar") * @Route("/{id}/add-word-from-searchbar", name="app_lexicon_add_word_from_search_bar")
* @IsGranted("LEXICON_EDIT", subject="lexicon")
* *
* Barre de recherche : on cherche le mot en ajax. Si on le trouve dans le wiko, on l'importe et on recharge la page, sinon on ouvre la modale de confirmation d'ajout * Barre de recherche : on cherche le mot en ajax. Si on le trouve dans le wiko, on l'importe et on recharge la page, sinon on ouvre la modale de confirmation d'ajout
*/ */
...@@ -266,6 +275,7 @@ class LexiconController extends AppBaseController ...@@ -266,6 +275,7 @@ class LexiconController extends AppBaseController
/** /**
* @Route("/{id}/add-headword", name="app_lexicon_add_headword", methods={"GET", "POST"}) * @Route("/{id}/add-headword", name="app_lexicon_add_headword", methods={"GET", "POST"})
* @IsGranted("LEXICON_EDIT", subject="lexicon")
*/ */
public function addHeadword(WiktionaryManager $wiktionaryManager, Request $request, Lexicon $lexicon): Response public function addHeadword(WiktionaryManager $wiktionaryManager, Request $request, Lexicon $lexicon): Response
{ {
...@@ -307,6 +317,7 @@ class LexiconController extends AppBaseController ...@@ -307,6 +317,7 @@ class LexiconController extends AppBaseController
/** /**
* @Route("/{id}/add-words-list", name="app_lexicon_add_words_list", methods={"GET", "POST"}) * @Route("/{id}/add-words-list", name="app_lexicon_add_words_list", methods={"GET", "POST"})
* @IsGranted("LEXICON_EDIT", subject="lexicon")
*/ */
public function addWordsList(WiktionaryManager $wiktionaryManager, Request $request, Lexicon $lexicon): Response public function addWordsList(WiktionaryManager $wiktionaryManager, Request $request, Lexicon $lexicon): Response
{ {
......
...@@ -46,14 +46,25 @@ class UpdateRequestController extends AppBaseController ...@@ -46,14 +46,25 @@ class UpdateRequestController extends AppBaseController
$label = $updateRequest->getLabel(); $label = $updateRequest->getLabel();
$this->em->flush(); $this->em->flush();
if (count($updateRequest->getVotesFor()) >= 7) { if (count($updateRequest->getVotesFor()) >= $_ENV['VOTE_APPROVAL_NB']) {
$updateRequest->setStatus(UpdateRequest::STATUS_ACCEPTED); $updateRequest->setStatus(UpdateRequest::STATUS_ACCEPTED);
if ($updateRequest->isDelete()) { if ($updateRequest->isDelete()) {
// !!! L'updateRequest et les votes associés sont supprimés en cascade, on garde un trace dans un log // Demande de suppression d'un label public: une requête avec vote est créée. Si elle est acceptée, une requête de suppression en cours est créée
$this->addLabelLog($label, Log::CATEGORY_DELETE_LABEL); // pour que tous les masters puissent choisir de copier le label vers un label perso ou de confirmer la suppression (ce qui a pour effet de masquer le label)
$this->em->remove($label); $updateRequest = new UpdateRequest(UpdateRequest::CATEGORY_DELETE_IN_PROGRESS);
$this->addFlash('success', "Le label a été supprimé"); $updateRequest->setLabel($label);
$this->em->persist($updateRequest);
foreach ($this->getAllUsersForCurrentLanguage() as $user) {
$notification = new Notification();
$this->em->persist($notification);
$notification->setUser($user);
$notification->setContent("Un label public est en cours de suppression. Voulez-vous le dupliquer avant sa suppression définitive ?");
$notification->setUpdateRequest($updateRequest);
}
$this->addFlash('success', "Le nombre de votes nécessaires a été atteint, le label est en cours de suppression");
$this->em->flush(); $this->em->flush();
return $this->redirectToRoute('app_label_index'); return $this->redirectToRoute('app_label_index');
...@@ -89,12 +100,12 @@ class UpdateRequestController extends AppBaseController ...@@ -89,12 +100,12 @@ class UpdateRequestController extends AppBaseController
$label = $updateRequest->getLabel(); $label = $updateRequest->getLabel();
$this->em->flush(); $this->em->flush();
if (count($updateRequest->getVotesAgainst()) >= 7) { if (count($updateRequest->getVotesAgainst()) >= $_ENV['VOTE_APPROVAL_NB']) {
$updateRequest->setStatus(UpdateRequest::STATUS_REJECTED); $updateRequest->setStatus(UpdateRequest::STATUS_REJECTED);
if ($updateRequest->isDelete()) { if ($updateRequest->isDelete()) {
$this->addFlash('success', "La demande de suppression du label a été rejetée"); $this->addFlash('success', "Le nombre de votes nécessaires a été atteint, la demande de suppression du label a été rejetée");
} elseif ($updateRequest->isRename()) { } elseif ($updateRequest->isRename()) {
$this->addFlash('success', "La demande de renommage du label a été rejetée"); $this->addFlash('success', "Le nombre de votes nécessaires a été atteint, la demande de renommage du label a été rejetée");
} }
$this->em->flush(); $this->em->flush();
......
...@@ -112,12 +112,6 @@ class Label ...@@ -112,12 +112,6 @@ class Label
*/ */
private $milestone; private $milestone;
/**
* @ORM\Column(type="boolean", nullable=true)
* @Groups({"label:read"})
*/
private $groupDeletionInProgress;
/** /**
* @ORM\ManyToMany(targetEntity=Headword::class, inversedBy="labels") * @ORM\ManyToMany(targetEntity=Headword::class, inversedBy="labels")
*/ */
...@@ -197,6 +191,7 @@ class Label ...@@ -197,6 +191,7 @@ class Label
return $result; return $result;
} }
// Retourne vrai si le label fait l'objet d'un vote de suppression
public function hasDeleteUpdateRequest() public function hasDeleteUpdateRequest()
{ {
foreach ($this->getUpdateRequests() as $updateRequest) { foreach ($this->getUpdateRequests() as $updateRequest) {
...@@ -207,6 +202,29 @@ class Label ...@@ -207,6 +202,29 @@ class Label
return false; return false;
} }
// Retourne vrai si le label est en cours de Suppression
public function isDeleteInProgress()
{
foreach ($this->getUpdateRequests() as $updateRequest) {
if ($updateRequest->getStatus() === UpdateRequest::STATUS_NEW && $updateRequest->isDeleteInProgress()) {
return true;
}
}
return false;
}
// Retourne vrai si le label est en cours de Suppression et que l'utilisateur a confirmé la suppression (il ne doit plus le voir dans l'interface)
public function isDeleteInProgressConfirmed(User $user)
{
foreach ($this->getUpdateRequests() as $updateRequest) {
if ($updateRequest->getStatus() === UpdateRequest::STATUS_NEW && $updateRequest->isDeleteInProgress()
&& $updateRequest->isConfirmedByUser($user)) {
return true;
}
}
return false;
}
public function isIdenticalTo(Label $label) public function isIdenticalTo(Label $label)
{ {
return empty($this->compareWith($label)); return empty($this->compareWith($label));
...@@ -440,8 +458,8 @@ class Label ...@@ -440,8 +458,8 @@ class Label
} }
/** /**
* @return Collection<int, UpdateRequest> * @return Collection|UpdateRequest[]
*/ */
public function getUpdateRequests(): Collection public function getUpdateRequests(): Collection
{ {
return $this->updateRequests; return $this->updateRequests;
...@@ -523,18 +541,6 @@ class Label ...@@ -523,18 +541,6 @@ class Label
return $this; return $this;
} }
public function isGroupDeletionInProgress(): ?bool
{
return $this->groupDeletionInProgress;
}
public function setGroupDeletionInProgress(?bool $groupDeletionInProgress): self
{
$this->groupDeletionInProgress = $groupDeletionInProgress;
return $this;
}
public function getLanguage(): ?string public function getLanguage(): ?string
{ {
return $this->language; return $this->language;
......
...@@ -96,6 +96,20 @@ class UpdateRequest ...@@ -96,6 +96,20 @@ class UpdateRequest
return true; return true;
} }
// Retourne vrai si la requête possède une notification adressée à $user et qu'elle a été lue
// Utilisé pour savoir si lors d'une suppression de label en cours, l'user a traité la notification (auquel cas on masque le label)
// en cliquant "sur ne rien faire" ou sur "copier comme label perso", ce qui dans les deux cas fait passer la notif à "lue"
// Si pas de notif trouvée, on renvoie false (on continue d'afficher le label)
public function isConfirmedByUser(User $user)
{
foreach ($this->getNotifications() as $notification) {
if ($notification->getUser() === $user && $notification->isRead()) {
return true;
}
}
return false;
}
public function isNew() public function isNew()
{ {
return $this->getStatus() === self::STATUS_NEW; return $this->getStatus() === self::STATUS_NEW;
...@@ -278,8 +292,8 @@ class UpdateRequest ...@@ -278,8 +292,8 @@ class UpdateRequest
} }
/** /**
* @return Collection<int, Notification> * @return Collection|Notification[]
*/ */
public function getNotifications(): Collection public function getNotifications(): Collection
{ {
return $this->notifications; return $this->notifications;
......
...@@ -286,6 +286,16 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface ...@@ -286,6 +286,16 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface
return $result; return $result;
} }
public function hasStudiedLanguage($language)
{
foreach ($this->getStudiedLanguagesValues() as $studiedLanguagesValue) {
if ($studiedLanguagesValue == $language) {
return true;
}
}
return false;
}
public function getMyFriends() public function getMyFriends()
{ {
$result = []; $result = [];
......
...@@ -5,6 +5,7 @@ namespace App\Repository; ...@@ -5,6 +5,7 @@ namespace App\Repository;
use App\Entity\Entry; use App\Entity\Entry;
use App\Entity\Label; use App\Entity\Label;
use App\Entity\Lexicon; use App\Entity\Lexicon;
use App\Entity\UpdateRequest;
use App\Entity\User; use App\Entity\User;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\ORM\QueryBuilder; use Doctrine\ORM\QueryBuilder;
...@@ -187,4 +188,19 @@ class LabelRepository extends ServiceEntityRepository ...@@ -187,4 +188,19 @@ class LabelRepository extends ServiceEntityRepository
; ;
return $qb->getQuery()->getResult(); return $qb->getQuery()->getResult();
} }
// Retourne les labels en cours de suppression depuis $daysNb jours
public function getLabelsToBeRemoved($daysNb)
{
$qb = $this->createQueryBuilder('l')
->innerJoin('l.updateRequests', 'ur')
->andWhere('ur.category = :deleteInProgress')
->setParameter('deleteInProgress', UpdateRequest::CATEGORY_DELETE_IN_PROGRESS)
->andWhere('ur.status = :statusNew')
->setParameter('statusNew', UpdateRequest::STATUS_NEW)
->andWhere('ur.createdAt < :daysAgo')
->setParameter('daysAgo', new \DateTimeImmutable($daysNb . ' days ago'))
;
return $qb->getQuery()->getResult();
}
} }
...@@ -55,19 +55,19 @@ class LabelVoter extends Voter ...@@ -55,19 +55,19 @@ class LabelVoter extends Voter
private function canView(Label $label, User $user): bool private function canView(Label $label, User $user): bool
{ {
// On ne voit que ses propres labels milestone ou ceux de ses groupes // On ne voit pas les labels persos des autres utilisateurs
if ($label->isMilestone() && if ($label->isMasterPersonal() && $label->getUser() !== $user) {
($label->getUser() === $user || ($label->getGroup() && $label->getGroup()->getGroupMembershipFor($user)) ) return false;
) {
return true;
} }
// On voit tous les autres labels // On ne voit pas les labels des groupes dont on n'est pas membre
if (!$label->isMilestone()) { if ($label->isMasterGroup() && !$label->getGroup()->getGroupMembershipFor($user)) {
return true; return false;
} }
return false; // On voit tous les autres labels
return true;
} }
private function canEdit(Label $label, User $user): bool private function canEdit(Label $label, User $user): bool
...@@ -82,6 +82,11 @@ class LabelVoter extends Voter ...@@ -82,6 +82,11 @@ class LabelVoter extends Voter
return true; return true;
} }
// On modifie les labels publics
if ($label->isMasterPublic()) {
return true;
}
// On modifie les labels de ses groupes // On modifie les labels de ses groupes
if ($label->getGroup() && $label->getGroup()->getGroupMembershipFor($user)) { if ($label->getGroup() && $label->getGroup()->getGroupMembershipFor($user)) {
return true; return true;
...@@ -97,11 +102,17 @@ class LabelVoter extends Voter ...@@ -97,11 +102,17 @@ class LabelVoter extends Voter
if ($label->isSystem()) { if ($label->isSystem()) {
return false; return false;
} }
// On supprime ses propres labels // On supprime ses propres labels
if ($label->getUser() === $user) { if ($label->getUser() === $user) {
return true; return true;
} }
// On supprime les labels publics
if ($label->isMasterPublic()) {
return true;
}
// On supprime les labels de ses groupes // On supprime les labels de ses groupes
if ($label->getGroup() && $label->getGroup()->getGroupMembershipFor($user)) { if ($label->getGroup() && $label->getGroup()->getGroupMembershipFor($user)) {
return true; return true;
......
...@@ -81,9 +81,9 @@ class LexiconVoter extends Voter ...@@ -81,9 +81,9 @@ class LexiconVoter extends Voter
return true; return true;
} }
if (in_array($lexicon->getLanguage(), $user->getStudiedLanguagesValues()) && $lexicon->isNewWords()) { // if (in_array($lexicon->getLanguage(), $user->getStudiedLanguagesValues()) && $lexicon->isNewWords()) {
return true; // return true;
} // }
return false; return false;
} }
......
<div class="badge rounded-pill {{ label.isMilestone ? 'badge-milestone'}} position-relative text-black {{ label|labelClass }} {#{% if not label.isDeleteInProgressConfirmed(app.user) %}#}
{{ label.isMilestone or label.isInstitutional or label.isMasterPublic ? 'border-auto'}}" style="margin-right: 5px;"
title="{{ label.description ? label.description~'.' }} Updated: {{ label.updatedAt|date('d/m/Y') }}"> <div class="badge rounded-pill {{ label.isMilestone ? 'badge-milestone'}} {{ label.isDeleteInProgress ? 'faded' }} position-relative text-black {{ label|labelClass }}
{{ label }} {{ label.isMilestone or label.isInstitutional or label.isMasterPublic ? 'border-auto'}}" style="margin-right: 5px;"
{% if label.isMilestone %}<br>{{ label.getMilestone|date('d/m/Y') }}{% endif %} title="{{ label.description ? label.description~'.' }} Updated: {{ label.updatedAt|date('d/m/Y') }}">
{% if showHeadwordsCounter|default(false) %} {{ label }}
<span class="position-absolute top-100 start-100 text-black">{{ label_manager.getHeadwordsNbWithLabel(label, app.user) }}/{{ label_manager.getHeadwordsNbWithLabel(label) }}</span>
{% endif %} {% if label.isMilestone %}<br>{{ label.getMilestone|date('d/m/Y') }}{% endif %}
</div>
{% if showHeadwordsCounter|default(false) %}
<span class="position-absolute top-100 start-100 text-black">{{ label_manager.getHeadwordsNbWithLabel(label, app.user) }}/{{ label_manager.getHeadwordsNbWithLabel(label) }}</span>
{% endif %}
{% if label.isDeleteInProgress %}<br><span class="text-danger">{{ "Suppression en cours"|trans }}</span>{% endif %}
</div>
{#{% endif %}#}
\ No newline at end of file
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
<h1 class=""> <h1 class="">
{{ lexicon|badgeXl }} {{ "Agora Des Néologismes"|trans }} {{ lexicon|badgeXl }} {{ "Agora Des Néologismes"|trans }}
</h1> </h1>
<p>{{ "Ce lexique permet à tous de proposer des mots n'existant ni dans le wiktionnaire ni dans la base de l'application BaLex. Si une entrée obtient 7 votes de alidation, elle sera ajoutée au lexique de BaLex et rendue disponible aux autres utilisateurs."|trans }}</p> <p>{{ "Ce lexique permet à tous de proposer des mots n'existant ni dans le wiktionnaire ni dans la base de l'application BaLex. Si une entrée obtient 7 votes de validation, elle sera ajoutée au lexique de BaLex et rendue disponible aux autres utilisateurs."|trans }}</p>
<div class="row mt-4"> <div class="row mt-4">
<div class="col-sm-3"> <div class="col-sm-3">
......
window.location.replace("{{ url|raw }}");
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment