mirror of
				https://gitlab.com/Chill-Projet/chill-bundles.git
				synced 2025-10-31 01:08:26 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			395 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
			
		
		
	
	
			395 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
| <?php
 | |
| 
 | |
| /*
 | |
|  *
 | |
|  * Copyright (C) 2014, Champs Libres Cooperative SCRLFS, <http://www.champs-libres.coop>
 | |
|  *
 | |
|  * 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 <http://www.gnu.org/licenses/>.
 | |
|  */
 | |
| namespace Chill\PersonBundle\Search;
 | |
| 
 | |
| use Chill\MainBundle\Search\AbstractSearch;
 | |
| use Doctrine\ORM\EntityManagerInterface;
 | |
| use Chill\PersonBundle\Entity\Person;
 | |
| use Chill\MainBundle\Search\SearchInterface;
 | |
| use Symfony\Component\DependencyInjection\ContainerAwareInterface;
 | |
| use Symfony\Component\DependencyInjection\ContainerAwareTrait;
 | |
| use Chill\MainBundle\Search\ParsingException;
 | |
| use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
 | |
| use Chill\MainBundle\Security\Authorization\AuthorizationHelper;
 | |
| use Symfony\Component\Security\Core\Role\Role;
 | |
| use Chill\MainBundle\Pagination\PaginatorFactory;
 | |
| use Symfony\Component\Form\Extension\Core\Type\TextType;
 | |
| use Chill\MainBundle\Form\Type\ChillDateType;
 | |
| use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
 | |
| use Symfony\Component\Form\FormBuilderInterface;
 | |
| use Chill\MainBundle\Search\HasAdvancedSearchFormInterface;
 | |
| use Doctrine\ORM\Query;
 | |
| 
 | |
| class PersonSearch extends AbstractSearch implements ContainerAwareInterface,
 | |
|     HasAdvancedSearchFormInterface
 | |
| {
 | |
|     use ContainerAwareTrait;
 | |
| 
 | |
|     /**
 | |
|      *
 | |
|      * @var EntityManagerInterface
 | |
|      */
 | |
|     private $em;
 | |
| 
 | |
|     /**
 | |
|      *
 | |
|      * @var \Chill\MainBundle\Entity\User
 | |
|      */
 | |
|     private $user;
 | |
| 
 | |
|     /**
 | |
|      *
 | |
|      * @var AuthorizationHelper
 | |
|      */
 | |
|     private $helper;
 | |
| 
 | |
|     /**
 | |
|      *
 | |
|      * @var PaginatorFactory
 | |
|      */
 | |
|     protected $paginatorFactory;
 | |
| 
 | |
|     const NAME = "person_regular";
 | |
| 
 | |
| 
 | |
|     public function __construct(
 | |
|           EntityManagerInterface $em,
 | |
|           TokenStorageInterface $tokenStorage,
 | |
|           AuthorizationHelper $helper,
 | |
|           PaginatorFactory $paginatorFactory)
 | |
|     {
 | |
|         $this->em = $em;
 | |
|         $this->user = $tokenStorage->getToken()->getUser();
 | |
|         $this->helper = $helper;
 | |
|         $this->paginatorFactory = $paginatorFactory;
 | |
| 
 | |
|         // throw an error if user is not a valid user
 | |
|         if (!$this->user instanceof \Chill\MainBundle\Entity\User) {
 | |
|             throw new \LogicException('The user provided must be an instance'
 | |
|                   . ' of Chill\MainBundle\Entity\User');
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     /*
 | |
|      * (non-PHPdoc)
 | |
|      * @see \Chill\MainBundle\Search\SearchInterface::getOrder()
 | |
|      */
 | |
|     public function getOrder()
 | |
|     {
 | |
|         return 100;
 | |
|     }
 | |
| 
 | |
|     /*
 | |
|      * (non-PHPdoc)
 | |
|      * @see \Chill\MainBundle\Search\SearchInterface::isActiveByDefault()
 | |
|      */
 | |
|     public function isActiveByDefault()
 | |
|     {
 | |
|         return true;
 | |
|     }
 | |
| 
 | |
|     public function supports($domain, $format)
 | |
|     {
 | |
|         return 'person' === $domain;
 | |
|     }
 | |
| 
 | |
|     /*
 | |
|      * (non-PHPdoc)
 | |
|      * @see \Chill\MainBundle\Search\SearchInterface::renderResult()
 | |
|      */
 | |
|     public function renderResult(array $terms, $start = 0, $limit = 50, array $options = array(), $format = 'html')
 | |
|     {
 | |
|         $total = $this->count($terms);
 | |
|         $paginator = $this->paginatorFactory->create($total);
 | |
| 
 | |
|         if ($format === 'html') {
 | |
|             return $this->container->get('templating')->render('ChillPersonBundle:Person:list.html.twig',
 | |
|                     array(
 | |
|                         'persons' => $this->search($terms, $start, $limit, $options),
 | |
|                         'pattern' => $this->recomposePattern($terms, array('nationality',
 | |
|                             'firstname', 'lastname', 'birthdate', 'gender',
 | |
|                             'birthdate-before','birthdate-after'), $terms['_domain']),
 | |
|                         'total' => $total,
 | |
|                         'start' => $start,
 | |
|                         'search_name' => self::NAME,
 | |
|                         'preview' => $options[SearchInterface::SEARCH_PREVIEW_OPTION],
 | |
|                         'paginator' => $paginator
 | |
|                     ));
 | |
|         } elseif ($format === 'json') {
 | |
|             return [
 | |
|                 'results' => $this->search($terms, $start, $limit, \array_merge($options, [ 'simplify' => true ])),
 | |
|                 'pagination' => [
 | |
|                     'more' => $paginator->hasNextPage()
 | |
|                 ]
 | |
|             ];
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      *
 | |
|      * @param string $pattern
 | |
|      * @param int $start
 | |
|      * @param int $limit
 | |
|      * @param array $options
 | |
|      * @return Person[]
 | |
|      */
 | |
|     protected function search(array $terms, $start, $limit, array $options = array())
 | |
|     {
 | |
|         $qb = $this->createQuery($terms, 'search');
 | |
|         
 | |
|         if ($options['simplify'] ?? false) {
 | |
|             $qb->select(
 | |
|                     'p.id',
 | |
|                     $qb->expr()->concat(
 | |
|                         'p.firstName',
 | |
|                         $qb->expr()->literal(' '),
 | |
|                         'p.lastName'
 | |
|                         ).'AS text'
 | |
|                 );
 | |
|         } else {
 | |
|             $qb->select('p');
 | |
|         }
 | |
| 
 | |
|         $qb
 | |
|             ->setMaxResults($limit)
 | |
|             ->setFirstResult($start);
 | |
| 
 | |
|         //order by firstname, lastname
 | |
| 
 | |
|         $qb
 | |
|             ->orderBy('p.firstName')
 | |
|             ->addOrderBy('p.lastName');
 | |
|         
 | |
|         if ($options['simplify'] ?? false) {
 | |
|             return $qb->getQuery()->getResult(Query::HYDRATE_ARRAY);
 | |
|         } else {
 | |
|             return $qb->getQuery()->getResult();
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     protected function count(array $terms)
 | |
|     {
 | |
|         $qb = $this->createQuery($terms);
 | |
| 
 | |
|         $qb->select('COUNT(p.id)');
 | |
| 
 | |
|         return $qb->getQuery()->getSingleScalarResult();
 | |
|     }
 | |
| 
 | |
| 
 | |
|     private $_cacheQuery = array();
 | |
| 
 | |
|     /**
 | |
|      *
 | |
|      * @param array $terms
 | |
|      * @return \Doctrine\ORM\QueryBuilder
 | |
|      */
 | |
|     public function createQuery(array $terms)
 | |
|     {
 | |
|         //get from cache
 | |
|         $cacheKey = md5(serialize($terms));
 | |
|         if (array_key_exists($cacheKey, $this->_cacheQuery)) {
 | |
|             return clone $this->_cacheQuery[$cacheKey];
 | |
|         }
 | |
| 
 | |
|         $qb = $this->em->createQueryBuilder();
 | |
| 
 | |
|         $qb->from('ChillPersonBundle:Person', 'p');
 | |
| 
 | |
|         if (array_key_exists('firstname', $terms)) {
 | |
|             $qb->andWhere($qb->expr()->like('UNACCENT(LOWER(p.firstName))', ':firstname'))
 | |
|                   ->setParameter('firstname', '%'.$terms['firstname'].'%');
 | |
|         }
 | |
| 
 | |
|         if (array_key_exists('lastname', $terms)) {
 | |
|             $qb->andWhere($qb->expr()->like('UNACCENT(LOWER(p.lastName))', ':lastname'))
 | |
|                   ->setParameter('lastname', '%'.$terms['lastname'].'%');
 | |
|         }
 | |
| 
 | |
|         foreach (['birthdate', 'birthdate-before', 'birthdate-after'] as $key)
 | |
|         if (array_key_exists($key, $terms)) {
 | |
|             try {
 | |
|                 $date = new \DateTime($terms[$key]);
 | |
|             } catch (\Exception $ex) {
 | |
|                 throw new ParsingException('The date is '
 | |
|                       . 'not parsable', 0, $ex);
 | |
|             }
 | |
| 
 | |
|             switch($key) {
 | |
|                 case 'birthdate':
 | |
|                     $qb->andWhere($qb->expr()->eq('p.birthdate', ':birthdate'))
 | |
|                         ->setParameter('birthdate', $date);
 | |
|                     break;
 | |
|                 case 'birthdate-before':
 | |
|                     $qb->andWhere($qb->expr()->lt('p.birthdate', ':birthdatebefore'))
 | |
|                         ->setParameter('birthdatebefore', $date);
 | |
|                     break;
 | |
|                 case 'birthdate-after':
 | |
|                     $qb->andWhere($qb->expr()->gt('p.birthdate', ':birthdateafter'))
 | |
|                         ->setParameter('birthdateafter', $date);
 | |
|                     break;
 | |
|                 default:
 | |
|                     throw new \LogicException("this case $key should not exists");
 | |
|             }
 | |
| 
 | |
|         }
 | |
| 
 | |
|         if (array_key_exists('gender', $terms)) {
 | |
|             if (!in_array($terms['gender'], array(Person::MALE_GENDER, Person::FEMALE_GENDER))) {
 | |
|                 throw new ParsingException('The gender '
 | |
|                       .$terms['gender'].' is not accepted. Should be "'.Person::MALE_GENDER
 | |
|                       .'" or "'.Person::FEMALE_GENDER.'"');
 | |
|             }
 | |
| 
 | |
|             $qb->andWhere($qb->expr()->eq('p.gender', ':gender'))
 | |
|               ->setParameter('gender', $terms['gender']);
 | |
|         }
 | |
| 
 | |
|         if (array_key_exists('nationality', $terms)) {
 | |
|             try {
 | |
|                 $country = $this->em->createQuery('SELECT c FROM '
 | |
|                       . 'ChillMainBundle:Country c WHERE '
 | |
|                       . 'LOWER(c.countryCode) LIKE :code')
 | |
|                       ->setParameter('code', $terms['nationality'])
 | |
|                       ->getSingleResult();
 | |
|             } catch (\Doctrine\ORM\NoResultException $ex)  {
 | |
|                 throw new ParsingException('The country code "'.$terms['nationality'].'" '
 | |
|                       . ', used in nationality, is unknow', 0, $ex);
 | |
|             }
 | |
| 
 | |
|             $qb->andWhere($qb->expr()->eq('p.nationality', ':nationality'))
 | |
|                   ->setParameter('nationality', $country);
 | |
|         }
 | |
| 
 | |
|         if ($terms['_default'] !== '') {
 | |
|             $grams = explode(' ', $terms['_default']);
 | |
| 
 | |
|             foreach($grams as $key => $gram) {
 | |
|                 $qb->andWhere($qb->expr()
 | |
|                       ->like('p.fullnameCanonical', 'UNACCENT(LOWER(:default_'.$key.'))'))
 | |
|                       ->setParameter('default_'.$key, '%'.$gram.'%');
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         //restraint center for security
 | |
|         $reachableCenters = $this->helper->getReachableCenters($this->user,
 | |
|                 new Role('CHILL_PERSON_SEE'));
 | |
|         $qb->andWhere($qb->expr()
 | |
|                 ->in('p.center', ':centers'))
 | |
|                 ->setParameter('centers', $reachableCenters)
 | |
|                 ;
 | |
| 
 | |
|         $this->_cacheQuery[$cacheKey] = $qb;
 | |
| 
 | |
|         return clone $qb;
 | |
|     }
 | |
| 
 | |
|     public function buildForm(FormBuilderInterface $builder)
 | |
|     {
 | |
|         $builder
 | |
|             ->add('_default', TextType::class, [
 | |
|                 'label' => 'First name or Last name',
 | |
|                 'required' => false
 | |
|             ])
 | |
|             ->add('firstname', TextType::class, [
 | |
|                 'label' => 'First name',
 | |
|                 'required' => false
 | |
|             ])
 | |
|             ->add('lastname', TextType::class, [
 | |
|                 'label' => 'Last name',
 | |
|                 'required' => false
 | |
|             ])
 | |
|             ->add('birthdate-after', ChillDateType::class, [
 | |
|                 'label' => 'Birthdate after',
 | |
|                 'required' => false
 | |
|             ])
 | |
|             ->add('birthdate', ChillDateType::class, [
 | |
|                 'label' => 'Birthdate',
 | |
|                 'required' => false
 | |
|             ])
 | |
|             ->add('birthdate-before', ChillDateType::class, [
 | |
|                 'label' => 'Birthdate before',
 | |
|                 'required' => false
 | |
|             ])
 | |
|             ->add('gender', ChoiceType::class, [
 | |
|                 'choices' => [
 | |
|                     'Man' => Person::MALE_GENDER,
 | |
|                     'Woman' => Person::FEMALE_GENDER
 | |
|                 ],
 | |
|                 'label' => 'Gender',
 | |
|                 'required' => false
 | |
|             ])
 | |
|             ;
 | |
|     }
 | |
| 
 | |
|     public function convertFormDataToQuery(array $data)
 | |
|     {
 | |
|         $string = '@person ';
 | |
| 
 | |
|         $string .= empty($data['_default']) ? '' : $data['_default'].' ';
 | |
| 
 | |
|         foreach(['firstname', 'lastname', 'gender'] as $key) {
 | |
|             $string .= empty($data[$key]) ? '' : $key.':'.
 | |
|                 // add quote if contains spaces
 | |
|                 (strpos($data[$key], ' ') !== false ?  '"'.$data[$key].'"': $data[$key])
 | |
|                 .' ';
 | |
|         }
 | |
| 
 | |
|         foreach (['birthdate', 'birthdate-before', 'birthdate-after'] as $key) {
 | |
|             $string .= empty($data[$key]) ?
 | |
|                 ''
 | |
|                 :
 | |
|                 $key.':'.$data[$key]->format('Y-m-d').' '
 | |
|                 ;
 | |
|         }
 | |
| 
 | |
|         return $string;
 | |
|     }
 | |
| 
 | |
|     public function convertTermsToFormData(array $terms)
 | |
|     {
 | |
|         foreach(['firstname', 'lastname', 'gender', '_default']
 | |
|             as $key) {
 | |
|             $data[$key] = $terms[$key] ?? null;
 | |
|         }
 | |
| 
 | |
|         // parse dates
 | |
|         foreach (['birthdate', 'birthdate-before', 'birthdate-after'] as $key) {
 | |
|             if (\array_key_exists($key, $terms)) {
 | |
|                 try {
 | |
|                         $date = new \DateTime($terms[$key]);
 | |
|                 } catch (\Exception $ex) {
 | |
|                         throw new ParsingException("The date for $key is "
 | |
|                               . 'not parsable', 0, $ex);
 | |
|                 }
 | |
|             }
 | |
|             $data[$key] = $date ?? null;
 | |
|         }
 | |
| 
 | |
|         return $data;
 | |
|     }
 | |
| 
 | |
|     public function getAdvancedSearchTitle()
 | |
|     {
 | |
|         return 'Search within persons';
 | |
|     }
 | |
| 
 | |
| }
 |