, * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ namespace Chill\PersonBundle\Controller; use Chill\PersonBundle\Privacy\PrivacyEvent; use Psr\Log\LoggerInterface; use Chill\PersonBundle\Entity\Person; use Chill\PersonBundle\Form\PersonType; use Chill\PersonBundle\Form\CreationPersonType; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\Form\Extension\Core\Type\ButtonType; use Symfony\Component\Form\Extension\Core\Type\SubmitType; use Symfony\Component\Form\Form; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Session\SessionInterface; use Symfony\Component\Security\Core\Role\Role; use Chill\PersonBundle\Security\Authorization\PersonVoter; use Chill\PersonBundle\Search\SimilarPersonMatcher; use Symfony\Component\Security\Core\Security; use Symfony\Component\Translation\TranslatorInterface; use Chill\MainBundle\Search\SearchProvider; use Chill\PersonBundle\Repository\PersonRepository; use Chill\PersonBundle\Config\ConfigPersonAltNamesHelper; use Chill\PersonBundle\Repository\PersonNotDuplicateRepository; use Symfony\Component\Validator\Validator\ValidatorInterface; use Doctrine\ORM\EntityManagerInterface; use Symfony\Component\Routing\Annotation\Route; use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter; final class PersonController extends AbstractController { /** * @var SimilarPersonMatcher */ protected $similarPersonMatcher; /** * @var TranslatorInterface */ protected $translator; /** * @var EventDispatcherInterface */ protected $eventDispatcher; /** * @var PersonRepository; */ protected $personRepository; /** * @var ConfigPersonAltNamesHelper */ protected $configPersonAltNameHelper; /** * @var EntityManagerInterface */ private $em; /** * @var \Psr\Log\LoggerInterface */ private $logger; /** * @var ValidatorInterface */ private $validator; public function __construct( SimilarPersonMatcher $similarPersonMatcher, TranslatorInterface $translator, EventDispatcherInterface $eventDispatcher, PersonRepository $personRepository, ConfigPersonAltNamesHelper $configPersonAltNameHelper, LoggerInterface $logger, ValidatorInterface $validator, EntityManagerInterface $em, Security $security ) { $this->similarPersonMatcher = $similarPersonMatcher; $this->translator = $translator; $this->eventDispatcher = $eventDispatcher; $this->configPersonAltNameHelper = $configPersonAltNameHelper; $this->personRepository = $personRepository; $this->logger = $logger; $this->validator = $validator; $this->em = $em; $this->security = $security; } public function getCFGroup() { $cFGroup = null; $cFDefaultGroup = $this->em->getRepository("ChillCustomFieldsBundle:CustomFieldsDefaultGroup") ->findOneByEntity("Chill\PersonBundle\Entity\Person"); if($cFDefaultGroup) { $cFGroup = $cFDefaultGroup->getCustomFieldsGroup(); } return $cFGroup; } public function viewAction($person_id) { $person = $this->_getPerson($person_id); if ($person === null) { throw $this->createNotFoundException("Person with id $person_id not" . " found on this server"); } $this->denyAccessUnlessGranted('CHILL_PERSON_SEE', $person, "You are not allowed to see this person."); $event = new PrivacyEvent($person); $this->eventDispatcher->dispatch(PrivacyEvent::PERSON_PRIVACY_EVENT, $event); return $this->render('ChillPersonBundle:Person:view.html.twig', array( "person" => $person, "cFGroup" => $this->getCFGroup(), "alt_names" => $this->configPersonAltNameHelper->getChoices(), )); } public function editAction($person_id) { $person = $this->_getPerson($person_id); if ($person === null) { return $this->createNotFoundException(); } $this->denyAccessUnlessGranted('CHILL_PERSON_UPDATE', $person, 'You are not allowed to edit this person'); $form = $this->createForm(PersonType::class, $person, array( "action" => $this->generateUrl('chill_person_general_update', array("person_id" => $person_id)), "cFGroup" => $this->getCFGroup() ) ); return $this->render('ChillPersonBundle:Person:edit.html.twig', array('person' => $person, 'form' => $form->createView())); } public function updateAction($person_id, Request $request) { $person = $this->_getPerson($person_id); if ($person === null) { return $this->createNotFoundException(); } $this->denyAccessUnlessGranted('CHILL_PERSON_UPDATE', $person, 'You are not allowed to edit this person'); $form = $this->createForm(PersonType::class, $person, array("cFGroup" => $this->getCFGroup())); if ($request->getMethod() === 'POST') { $form->handleRequest($request); if ( ! $form->isValid() ) { $this->get('session') ->getFlashBag()->add('error', $this->translator ->trans('This form contains errors')); return $this->render('ChillPersonBundle:Person:edit.html.twig', array('person' => $person, 'form' => $form->createView())); } $this->get('session')->getFlashBag() ->add('success', $this->get('translator') ->trans('The person data has been updated') ); $this->em->flush(); $url = $this->generateUrl('chill_person_view', array( 'person_id' => $person->getId() )); return $this->redirect($url); } } /** * Method for creating a new person * *The controller register data from a previous post on the form, and * register it in the session. * * The next post compare the data with previous one and, if yes, show a * review page if there are "alternate persons". * * @param Request $request * @return \Symfony\Component\HttpFoundation\RedirectResponse|Response */ public function newAction(Request $request) { $defaultCenter = $this->security ->getUser() ->getGroupCenters()[0] ->getCenter(); $person = (new Person(new \DateTime('now'))) ->setCenter($defaultCenter); $form = $this->createForm(CreationPersonType::class, $person, [ 'validation_groups' => ['create'] ])->add('editPerson', SubmitType::class, [ 'label' => 'Add the person' ])->add('createPeriod', SubmitType::class, [ 'label' => 'Add the person and create an accompanying period' ]); $form->handleRequest($request); if ($request->getMethod() === Request::METHOD_GET) { $this->lastPostDataReset(); } elseif ($request->getMethod() === Request::METHOD_POST && $form->isValid()) { $alternatePersons = $this->similarPersonMatcher ->matchPerson($person); if ( FALSE === $this->isLastPostDataChanges($form, $request, true) || count($alternatePersons) === 0 ) { $this->em->persist($person); $this->em->flush(); $this->lastPostDataReset(); if ($form->get('createPeriod')->isClicked()) { return $this->redirectToRoute('chill_person_accompanying_course_new', [ 'person_id' => [ $person->getId() ] ]); } return $this->redirectToRoute('chill_person_general_edit', ['person_id' => $person->getId()]); } } elseif ($request->getMethod() === Request::METHOD_POST && !$form->isValid()) { $this->addFlash('error', $this->translator->trans('This form contains errors')); } return $this->render('@ChillPerson/Person/create.html.twig', [ 'form' => $form->createView(), 'alternatePersons' => $alternatePersons ?? [] ] ); } private function isLastPostDataChanges(Form $form, Request $request, bool $replace = false): bool { /** @var SessionInterface $session */ $session = $this->get('session'); if (!$session->has('last_person_data')) { return true; } $newPost = $this->lastPostDataBuildHash($form, $request); $isChanged = $newPost !== $session->get('last_person_data'); if ($replace) { $session->set('last_person_data', $newPost); } return $isChanged ; } private function lastPostDataReset(): void { $this->get('session')->set('last_person_data', ""); } /** * build the hash for posted data * * For privacy reasons, the data are hashed using sha512 * * @param Form $form * @param Request $request * @return string */ private function lastPostDataBuildHash(Form $form, Request $request): string { $fields = []; $ignoredFields = ['form_status', '_token']; foreach ($request->request->all()[$form->getName()] as $field => $value) { if (\in_array($field, $ignoredFields)) { continue; } $fields[$field] = \is_array($value) ? \implode(",", $value) : $value; } ksort($fields); return \hash('sha512', \implode("&", $fields)); } /** * * @param \Chill\PersonBundle\Entity\Person $person * @return \Symfony\Component\Validator\ConstraintViolationListInterface */ private function _validatePersonAndAccompanyingPeriod(Person $person) { $errors = $this->validator ->validate($person, null, array('creation')); //validate accompanying periods $periods = $person->getAccompanyingPeriods(); foreach ($periods as $period) { $period_errors = $this->validator ->validate($period); //group errors : foreach($period_errors as $error) { $errors->add($error); } } return $errors; } /** * easy getting a person by his id * @return \Chill\PersonBundle\Entity\Person */ private function _getPerson($id) { $person = $this->personRepository->find($id); return $person; } /** * * @Route( * "/{_locale}/person/household/{person_id}/history", * name="chill_person_household_person_history", * methods={"GET", "POST"} * ) * @ParamConverter("person", options={"id" = "person_id"}) */ public function householdHistoryByPerson(Request $request, Person $person): Response { $this->denyAccessUnlessGranted('CHILL_PERSON_SEE', $person, "You are not allowed to see this person."); $event = new PrivacyEvent($person); $this->eventDispatcher->dispatch(PrivacyEvent::PERSON_PRIVACY_EVENT, $event); return $this->render( '@ChillPerson/Person/household_history.html.twig', [ 'person' => $person ] ); } }