From c34b99243777440e1f95e43b1188c0c5a3771171 Mon Sep 17 00:00:00 2001 From: Mathieu Jaumotte Date: Sun, 21 Mar 2021 14:14:38 +0100 Subject: [PATCH] New permission for duplicate & export duplicate persons Signed-off-by: Mathieu Jaumotte --- .../Controller/PersonDuplicateController.php | 12 ++ .../Export/Export/ListPersonDuplicate.php | 202 ++++++++++++++++++ .../Search/SimilarPersonMatcher.php | 5 - .../Security/Authorization/PersonVoter.php | 7 +- .../config/services/exports.yaml | 12 +- 5 files changed, 229 insertions(+), 9 deletions(-) create mode 100644 src/Bundle/ChillPersonBundle/Export/Export/ListPersonDuplicate.php diff --git a/src/Bundle/ChillPersonBundle/Controller/PersonDuplicateController.php b/src/Bundle/ChillPersonBundle/Controller/PersonDuplicateController.php index a7b9a6523..6263cd13f 100644 --- a/src/Bundle/ChillPersonBundle/Controller/PersonDuplicateController.php +++ b/src/Bundle/ChillPersonBundle/Controller/PersonDuplicateController.php @@ -65,6 +65,9 @@ class PersonDuplicateController extends Controller . " found on this server"); } + $this->denyAccessUnlessGranted('CHILL_PERSON_DUPLICATE', $person, + "You are not allowed to see this person."); + $duplicatePersons = $this->similarPersonMatcher-> matchPerson($person, 0.5, SimilarPersonMatcher::SIMILAR_SEARCH_ORDER_BY_ALPHABETICAL); @@ -82,6 +85,9 @@ class PersonDuplicateController extends Controller { [$person1, $person2] = $this->_getPersonsByPriority($person1_id, $person2_id); + $this->denyAccessUnlessGranted('CHILL_PERSON_DUPLICATE', $person1, + "You are not allowed to see this person."); + $form = $this->createForm(PersonConfimDuplicateType::class); $form->handleRequest($request); @@ -118,6 +124,9 @@ class PersonDuplicateController extends Controller { [$person1, $person2] = $this->_getPersonsByPriority($person1_id, $person2_id); + $this->denyAccessUnlessGranted('CHILL_PERSON_DUPLICATE', $person1, + "You are not allowed to see this person."); + $personNotDuplicate = $this->getDoctrine()->getRepository(PersonNotDuplicate::class) ->findOneBy(['person1' => $person1, 'person2' => $person2]); @@ -138,6 +147,9 @@ class PersonDuplicateController extends Controller { [$person1, $person2] = $this->_getPersonsByPriority($person1_id, $person2_id); + $this->denyAccessUnlessGranted('CHILL_PERSON_DUPLICATE', $person1, + "You are not allowed to see this person."); + $personNotDuplicate = $this->getDoctrine()->getRepository(PersonNotDuplicate::class) ->findOneBy(['person1' => $person1, 'person2' => $person2]); diff --git a/src/Bundle/ChillPersonBundle/Export/Export/ListPersonDuplicate.php b/src/Bundle/ChillPersonBundle/Export/Export/ListPersonDuplicate.php new file mode 100644 index 000000000..366e952ba --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Export/Export/ListPersonDuplicate.php @@ -0,0 +1,202 @@ +entityManager = $em; + $this->translator = $translator; + $this->router = $router; + $this->baseUrl = $routeParameters['scheme']. + '://'.$routeParameters['host']; + } + + /** + * {@inheritDoc} + * + * @return string + */ + public function getTitle() + { + return "List duplicates"; + } + + /** + * {@inheritDoc} + * + * @return string + */ + public function getDescription() + { + return "Create a list of duplicate people."; + } + + /** + * {@inheritDoc} + * + * @param FormBuilderInterface $builder + */ + public function buildForm(FormBuilderInterface $builder) + { + $builder->add('precision', NumberType::class, [ + 'label' => 'Precision', + 'data' => self::PRECISION_DEFAULT_VALUE, + ]); + } + + public function validateForm($data, ExecutionContextInterface $context) + { + + } + + public function generate(array $acl, array $data = []): Response + { + $values = []; + $values[] = $this->getHeaders(); + + $result = $this->getResult($data); + foreach ($result as $row) { + $values[] = [ + $row['id1'], + $row['firstname1'], + $row['lastname1'], + $this->baseUrl.$this->router->generate('chill_person_view', ['person_id' => $row['id1']]), + $row['id2'], + $row['firstname2'], + $row['lastname2'], + $this->baseUrl.$this->router->generate('chill_person_view', ['person_id' => $row['id2']]), + ]; + } + + $spreadsheet = new Spreadsheet(); + $spreadsheet->getActiveSheet()->fromArray($values); + + // Make links clickable + for ($i = 1; $i <= $spreadsheet->getActiveSheet()->getHighestDataRow(); $i++) { + $spreadsheet->getActiveSheet()->getCell('D'.$i)->getHyperlink() + ->setUrl($spreadsheet->getActiveSheet()->getCell('D'.$i)->getValue()); + $spreadsheet->getActiveSheet()->getCell('H'.$i)->getHyperlink() + ->setUrl($spreadsheet->getActiveSheet()->getCell('H'.$i)->getValue()); + } + + $writer = new Xlsx($spreadsheet); + $temp_file = sys_get_temp_dir().'/'.uniqid('export_').'.xlsx'; + $writer->save($temp_file); + + $response = new BinaryFileResponse($temp_file); + $response->headers->set('Content-Type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'); + $response->setContentDisposition(ResponseHeaderBag::DISPOSITION_ATTACHMENT, 'export_duplicate.xlsx'); + + return $response; + } + + protected function getResult($data = []) + { + $precision = $data['precision'] ?? self::PRECISION_DEFAULT_VALUE; + + $sql = 'SELECT + p.id as id1, p.firstname as firstname1, p.lastname as lastname1, p.fullnamecanonical as fullnamecanonical1, + p2.id as id2, p2.firstname as firstname2, p2.lastname as lastname2, p2.fullnamecanonical as fullnamecanonical2, + SIMILARITY(p.fullnamecanonical, p2.fullnamecanonical) AS "similarity nom + prenom", + SIMILARITY(p.lastname, p2.lastname) AS "similarity nom", + SIMILARITY(p.firstname, p2.firstname) AS "similarity prenom" + FROM chill_person_person AS p + JOIN chill_person_person AS p2 + ON p.id != p2.id + AND (SIMILARITY(p.fullnamecanonical, p2.fullnamecanonical) > :precision + AND p.id < p2.id) + OR (UNACCENT(LOWER(p.firstname)) = UNACCENT(LOWER(p2.lastname)) + AND UNACCENT(LOWER(p.lastname)) = UNACCENT(LOWER(p2.firstname))) + JOIN centers AS p1center + ON p1center.id = p.center_id + JOIN centers AS p2center + ON p2center.id = p2.center_id + WHERE NOT EXISTS ( + SELECT id + FROM chill_person_not_duplicate as pnd + WHERE (pnd.person1_id = p.id + AND pnd.person2_id = p2.id) + OR (pnd.person2_id = p.id + AND pnd.person1_id = p2.id) + ) + ORDER BY p.fullnamecanonical, p.id, p2.id'; + + $statement = $this->entityManager->getConnection()->prepare($sql); + $statement->bindValue('precision', $precision); + $statement->execute(); + + return $statement->fetchAll(); + } + + protected function getHeaders(): array + { + return [ + $this->translator->trans('Departure folder number'), + $this->translator->trans('Last name'), + $this->translator->trans('First name'), + $this->translator->trans('Link'), + $this->translator->trans('Arrival folder number'), + $this->translator->trans('Last name'), + $this->translator->trans('First name'), + $this->translator->trans('Link'), + ]; + } + + public function requiredRole(): Role + { + return new Role(PersonVoter::DUPLICATE); + } +} diff --git a/src/Bundle/ChillPersonBundle/Search/SimilarPersonMatcher.php b/src/Bundle/ChillPersonBundle/Search/SimilarPersonMatcher.php index 89de2cd09..956f9a4d2 100644 --- a/src/Bundle/ChillPersonBundle/Search/SimilarPersonMatcher.php +++ b/src/Bundle/ChillPersonBundle/Search/SimilarPersonMatcher.php @@ -38,19 +38,16 @@ class SimilarPersonMatcher CONST SIMILAR_SEARCH_ORDER_BY_SIMILARITY = 'similarity'; /** - * * @var EntityManagerInterface */ protected $em; /** - * * @var AuthorizationHelper */ protected $authorizationHelper; /** - * * @var TokenStorageInterface */ protected $tokenStorage; @@ -65,7 +62,6 @@ class SimilarPersonMatcher $this->tokenStorage = $tokenStorage; } - public function matchPerson(Person $person, $precision = 0.15, $orderBy = self::SIMILAR_SEARCH_ORDER_BY_SIMILARITY) { $centers = $this->authorizationHelper->getReachableCenters( @@ -97,7 +93,6 @@ class SimilarPersonMatcher $dql .= ' ORDER BY SIMILARITY(p.fullnameCanonical, UNACCENT(LOWER(:fullName))) DESC '; } - $query = $this->em ->createQuery($dql) ->setParameter('fullName', $person->getFirstName() . ' ' . $person->getLastName()) diff --git a/src/Bundle/ChillPersonBundle/Security/Authorization/PersonVoter.php b/src/Bundle/ChillPersonBundle/Security/Authorization/PersonVoter.php index 1d3229567..f81a6efa1 100644 --- a/src/Bundle/ChillPersonBundle/Security/Authorization/PersonVoter.php +++ b/src/Bundle/ChillPersonBundle/Security/Authorization/PersonVoter.php @@ -40,6 +40,7 @@ class PersonVoter extends AbstractChillVoter implements ProvideRoleHierarchyInte const SEE = 'CHILL_PERSON_SEE'; const STATS = 'CHILL_PERSON_STATS'; const LISTS = 'CHILL_PERSON_LISTS'; + const DUPLICATE = 'CHILL_PERSON_DUPLICATE'; /** * @@ -56,11 +57,11 @@ class PersonVoter extends AbstractChillVoter implements ProvideRoleHierarchyInte { if ($subject instanceof Person) { return \in_array($attribute, [ - self::CREATE, self::UPDATE, self::SEE + self::CREATE, self::UPDATE, self::SEE, self::DUPLICATE ]); } elseif ($subject instanceof Center) { return \in_array($attribute, [ - self::STATS, self::LISTS + self::STATS, self::LISTS, self::DUPLICATE ]); } elseif ($subject === null) { return $attribute === self::CREATE; @@ -87,7 +88,7 @@ class PersonVoter extends AbstractChillVoter implements ProvideRoleHierarchyInte private function getAttributes() { - return array(self::CREATE, self::UPDATE, self::SEE, self::STATS, self::LISTS); + return array(self::CREATE, self::UPDATE, self::SEE, self::STATS, self::LISTS, self::DUPLICATE); } public function getRoles() diff --git a/src/Bundle/ChillPersonBundle/config/services/exports.yaml b/src/Bundle/ChillPersonBundle/config/services/exports.yaml index 9a20898e6..f6b5e8242 100644 --- a/src/Bundle/ChillPersonBundle/config/services/exports.yaml +++ b/src/Bundle/ChillPersonBundle/config/services/exports.yaml @@ -15,7 +15,17 @@ services: - "@chill.custom_field.provider" tags: - { name: chill.export, alias: list_person } - + + chill.person.export.list_person.duplicate: + class: Chill\PersonBundle\Export\Export\ListPersonDuplicate + arguments: + - "@doctrine.orm.entity_manager" + - "@translator" + - "@router" + - '%chill_main.notifications%' + tags: + - { name: chill.export, alias: list_person_duplicate } + chill.person.export.filter_gender: class: Chill\PersonBundle\Export\Filter\GenderFilter arguments: