Merge branch 'master' into CRUD-init

This commit is contained in:
Julien Fastré 2019-12-04 22:01:26 +01:00
commit 98b491063c
20 changed files with 603 additions and 62 deletions

View File

@ -49,7 +49,28 @@ Version 1.5.7
- add an icon when the file is opened / closed in result list, and in person rendering macro
- improve command to move person and all data: allow to delete some entities during move and add events
Version 1.5.8
=============
- add search by phonenumber, with a custom SearchInterface
This can be activated or desactivated by config:
```
chill_person:
enabled: true
search:
enabled: true
# enable search by phone. 'always' show the result on every result. 'on-domain' will show the result only if the domain is given in the search box. 'never' disable this feature
search_by_phone: on-domain # One of "always"; "on-domain"; "never"
```
- format phonenumber using twilio (if available) ;
- add `record_actions` in person search result list: users can click on a little eye to open person page ;
- add new fields (email, mobilenumber, gender) into importPeopleFromCSV command
- configure asset using a function
CRUD-init branch
================
- configure asset using a function

View File

@ -38,44 +38,39 @@ use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\EventDispatcher\Event;
/**
*
* Class ImportPeopleFromCSVCommand
*
* @package Chill\PersonBundle\Command
* @author Julien Fastré <julien.fastre@champs-libres.coop>
*/
class ImportPeopleFromCSVCommand extends ContainerAwareCommand
{
/**
*
* @var InputInterface
*/
protected $input;
/**
*
* @var OutputInterface
*/
protected $output;
/**
*
* @var \Psr\Log\LoggerInterface
*/
protected $logger;
/**
*
* @var \Chill\MainBundle\Templating\TranslatableStringHelper
*/
protected $helper;
/**
*
* @var \Doctrine\Common\Persistence\ObjectManager
*/
protected $em;
/**
*
* @var EventDispatcherInterface
*/
protected $eventDispatcher;
@ -88,13 +83,11 @@ class ImportPeopleFromCSVCommand extends ContainerAwareCommand
protected $line;
/**
*
* @var array where key are column names, and value the custom field slug
*/
protected $customFieldMapping = array();
/**
*
* @var CustomFieldProvider
*/
protected $customFieldProvider;
@ -112,10 +105,13 @@ class ImportPeopleFromCSVCommand extends ContainerAwareCommand
['firstname', 'The column header for firstname', 'firstname'],
['lastname', 'The column header for lastname', 'lastname'],
['birthdate', 'The column header for birthdate', 'birthdate'],
['gender', 'The column header for gender', 'gender'],
['opening_date', 'The column header for opening date', 'opening_date'],
['closing_date', 'The column header for closing date', 'closing_date'],
['memo', 'The column header for memo', 'memo'],
['email', 'The column header for email', 'email'],
['phonenumber', 'The column header for phonenumber', 'phonenumber'],
['mobilenumber', 'The column header for mobilenumber', 'mobilenumber'],
['street1', 'The column header for street 1', 'street1'],
['postalcode', 'The column header for postal code', 'postalcode'],
['locality', 'The column header for locality', 'locality'],
@ -144,8 +140,10 @@ class ImportPeopleFromCSVCommand extends ContainerAwareCommand
parent::__construct('chill:person:import');
}
/**
*
*/
protected function configure()
{
$this
@ -256,7 +254,10 @@ EOF
return $this;
}
/**
* @param InputInterface $input
* @param OutputInterface $output
*/
protected function interact(InputInterface $input, OutputInterface $output)
{
// preparing the basic
@ -286,6 +287,9 @@ EOF
$this->loadAnswerMatching();
}
/**
* @param $row
*/
protected function matchColumnToCustomField($row)
{
@ -359,6 +363,9 @@ EOF
}
}
/**
*
*/
protected function dumpAnswerMatching()
{
if ($this->input->hasOption('dump-choice-matching') && !empty($this->input->getOption('dump-choice-matching'))) {
@ -372,6 +379,12 @@ EOF
}
}
/**
* @param InputInterface $input
* @param OutputInterface $output
* @return int|null|void
* @throws \Exception
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
$this->input = $input;
@ -499,10 +512,11 @@ EOF
}
/**
*
*
* @param array $row
* @param array $headers the processed header : an array as prepared by self::processingHeaders
* @return Person
* @throws \Exception
*/
protected function createPerson($row, $headers)
{
@ -534,6 +548,9 @@ EOF
case 'birthdate':
$this->processBirthdate($person, $value);
break;
case 'gender':
$person->setGender($value);
break;
case 'opening_date':
// we have processed this when creating the person object, skipping;
break;
@ -543,9 +560,15 @@ EOF
case 'memo':
$person->setMemo($value);
break;
case 'email':
$person->setEmail($value);
break;
case 'phonenumber':
$person->setPhonenumber($value);
break;
case 'mobilenumber':
$person->setMobilenumber($value);
break;
// we just keep the column number for those data
case 'postalcode':
@ -563,26 +586,34 @@ EOF
// handle address
if (\in_array('postalcode', $headers)) {
$address = new Address();
$postalCode = $this->guessPostalCode($postalCodeValue, $localityValue ?? '');
if ($postalCode === null) {
throw new \Exception("The locality is not found");
if (! empty($postalCodeValue)) {
$address = new Address();
$postalCode = $this->guessPostalCode($postalCodeValue, $localityValue ?? '');
if ($postalCode === null) {
throw new \Exception("The locality is not found");
}
$address->setPostcode($postalCode);
if (\in_array('street1', $headers)) {
$address->setStreetAddress1($street1Value);
}
$address->setValidFrom(new \DateTime('today'));
$person->addAddress($address);
}
$address->setPostcode($postalCode);
if (\in_array('street1', $headers)) {
$address->setStreetAddress1($street1Value);
}
$address->setValidFrom(new \DateTime('today'));
$person->addAddress($address);
}
return $person;
}
/**
* @param $row
* @param $headers
* @return Center|mixed|null|object
*/
protected function getCenter($row, $headers)
{
if ($this->input->hasOption('force-center') && !empty($this->input->getOption('force-center'))) {
@ -606,6 +637,10 @@ EOF
}
}
/**
* @param $centerName
* @return Center|mixed|null|object
*/
protected function guessCenter($centerName)
{
if (!\array_key_exists('_center_picked', $this->cacheAnswersMapping)) {
@ -673,6 +708,11 @@ EOF
return $center;
}
/**
* @param $postalCode
* @param $locality
* @return mixed|null
*/
protected function guessPostalCode($postalCode, $locality)
{
if (!\array_key_exists('_postal_code_picked', $this->cacheAnswersMapping)) {
@ -738,9 +778,15 @@ EOF
return $pc;
}
/**
* @param Person $person
* @param $value
* @throws \Exception
*/
protected function processBirthdate(Person $person, $value)
{
if (empty($value)) { return; }
$date = $this->processDate($value, $this->input->getOption('birthdate_format'));
if ($date instanceof \DateTime) {
@ -763,8 +809,15 @@ EOF
}
/**
* @param Person $person
* @param $value
* @throws \Exception
*/
protected function processClosingDate(Person $person, $value)
{
if (empty($value)) { return; }
// we skip if the opening date is now (or after yesterday)
/* @var $period \Chill\PersonBundle\Entity\AccompanyingPeriod */
$period = $person->getCurrentAccompanyingPeriod();
@ -797,6 +850,11 @@ EOF
$value));
}
/**
* @param Person $person
* @param $row
* @throws \Exception
*/
protected function processingCustomFields(Person $person, $row)
{
/* @var $factory \Symfony\Component\Form\FormFactory */
@ -836,9 +894,10 @@ EOF
/**
* Process a text type on a custom field
*
*
* @param type $value
* @param \Chill\PersonBundle\Command\Symfony\Component\Form\FormInterface $form
* @param \Symfony\Component\Form\FormInterface $form
* @return type
*/
protected function processTextType(
$value,
@ -861,14 +920,15 @@ EOF
/**
* Process a custom field choice.
*
*
* The method try to guess if the result exists amongst the text of the possible
* choices. If the texts exists, then this is picked. Else, ask the user.
*
*
* @param string $value
* @param \Symfony\Component\Form\FormInterface $form
* @param \Chill\CustomFieldsBundle\Entity\CustomField $cf
* @return string
* @throws \Exception
*/
protected function processChoiceType(
$value,
@ -982,11 +1042,12 @@ EOF
}
/**
* Recursive method to collect the possibles answer from a ChoiceType (or
* Recursive method to collect the possibles answer from a ChoiceType (or
* its inherited types).
*
*
* @param \Symfony\Component\Form\FormInterface $form
* @return array where
* @return array where
* @throws \Exception
*/
private function collectChoicesAnswers($choices)
{
@ -1011,8 +1072,11 @@ EOF
return $answers;
}
/**
* @param $value
* @param $formats
* @return bool|\DateTime
*/
protected function processDate($value, $formats)
{
$possibleFormats = explode("|", $formats);
@ -1046,6 +1110,4 @@ EOF
}
}

View File

@ -44,10 +44,6 @@ class ChillPersonExtension extends Extension implements PrependExtensionInterfac
$configuration = new Configuration();
$config = $this->processConfiguration($configuration, $configs);
// set configuration for double metaphone
$container->setParameter('cl_chill_person.search.use_double_metaphone',
$config['search']['use_double_metaphone']);
// set configuration for validation
$container->setParameter('chill_person.validation.birtdate_not_before',
$config['validation']['birthdate_not_after']);
@ -67,6 +63,14 @@ class ChillPersonExtension extends Extension implements PrependExtensionInterfac
$loader->load('services/command.yml');
$loader->load('services/actions.yml');
$loader->load('services/form.yml');
$loader->load('services/repository.yml');
// load service advanced search only if configure
if ($config['search']['search_by_phone'] != 'never') {
$loader->load('services/search_by_phone.yml');
$container->setParameter('chill_person.search.search_by_phone',
$config['search']['search_by_phone']);
}
if ($container->getParameter('chill_person.accompanying_period') !== 'hidden') {
$loader->load('services/exports_accompanying_period.yml');

View File

@ -30,12 +30,14 @@ class Configuration implements ConfigurationInterface
->arrayNode('search')
->canBeDisabled()
->children()
->booleanNode('use_double_metaphone')
->defaultFalse()
->end() // use_double_metaphone, parent = children for 'search'
->booleanNode('use_trigrams')
->defaultFalse()
->end() // use_trigrams, parent = children of 'search'
->enumNode('search_by_phone')
->values(['always', 'on-domain', 'never'])
->defaultValue('on-domain')
->info('enable search by phone. \'always\' show the result '
. 'on every result. \'on-domain\' will show the result '
. 'only if the domain is given in the search box. '
. '\'never\' disable this feature')
->end()
->end() //children for 'search', parent = array node 'search'
->end() // array 'search', parent = children of root
->arrayNode('validation')

View File

@ -1,14 +1,32 @@
<?php
/*
* Copyright (C) 2014-2019 Julien Fastré <julien.fastre@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\Entity;
use Doctrine\ORM\EntityRepository;
@trigger_error(__CLASS__." is deprecated since 2019-10-30. Use "
.\Chill\PersonBundle\Repository\PersonRepository::class.' instead.',
E_USER_DEPRECATED);
/**
* PersonRepository
*
* This class was generated by the Doctrine ORM. Add your own custom
* repository methods below.
*
* @deprecated since 2019-10-30. Use \Chill\PersonBundle\Repository\PersonRepository instead.
*/
class PersonRepository extends EntityRepository
{

View File

@ -0,0 +1,100 @@
<?php
/*
* Copyright (C) 2019 Julien Fastré <julien.fastre@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\Repository;
use Doctrine\ORM\QueryBuilder;
/**
* PersonRepository
*
*/
class PersonRepository extends \Chill\PersonBundle\Entity\PersonRepository
{
public function findByPhone(
string $phonenumber,
$centers,
$firstResult,
$maxResults,
array $only = ['mobile', 'phone']
) {
$qb = $this->createQueryBuilder('p');
$qb->select('p');
$this->addByCenters($qb, $centers);
$this->addPhoneNumber($qb, $phonenumber, $only);
$qb->setFirstResult($firstResult)
->setMaxResults($maxResults)
;
return $qb->getQuery()->getResult();
}
public function countByPhone(
string $phonenumber,
$centers,
array $only = ['mobile', 'phone']
): int
{
$qb = $this->createQueryBuilder('p');
$qb->select('COUNT(p)');
$this->addByCenters($qb, $centers);
$this->addPhoneNumber($qb, $phonenumber, $only);
return $qb->getQuery()->getSingleScalarResult();
}
protected function addPhoneNumber(QueryBuilder $qb, string $phonenumber, array $only)
{
if (count($only) === 0) {
throw new \Exception("No array field to search");
}
$phonenumber = $this->parsePhoneNumber($phonenumber);
$orX = $qb->expr()->orX();
if (\in_array('mobile', $only)) {
$orX->add($qb->expr()->like("REPLACE(p.mobilenumber, ' ', '')", ':phonenumber'));
}
if (\in_array('phone', $only)) {
$orX->add($qb->expr()->like("REPLACE(p.phonenumber, ' ', '')", ':phonenumber'));
}
$qb->andWhere($orX);
$qb->setParameter('phonenumber', '%'.$phonenumber.'%');
}
protected function parsePhoneNumber($phonenumber): string
{
return \str_replace(' ', '', $phonenumber);
}
protected function addByCenters(QueryBuilder $qb, array $centers)
{
if (count($centers) > 0) {
$qb->andWhere($qb->expr()->in('p.center', ':centers'));
$qb->setParameter('centers', $centers);
}
}
}

View File

@ -4,7 +4,7 @@ Chill\PersonBundle\Entity\Person:
indexes:
person_names:
columns: [firstName, lastName]
repositoryClass: Chill\PersonBundle\Entity\PersonRepository
repositoryClass: Chill\PersonBundle\Repository\PersonRepository
fields:
id:
type: integer

View File

@ -47,9 +47,3 @@ services:
tags:
- { name: form.type, alias: chill_personbundle_person_creation }
chill.person.repository.person:
class: Chill\PersonBundle\Entity\PersonRepository
factory: ['@doctrine.orm.entity_manager', getRepository]
arguments:
- 'Chill\PersonBundle\Entity\Person'

View File

@ -0,0 +1,13 @@
services:
chill.person.repository.person:
class: Chill\PersonBundle\Person\PersonRepository
deprecated: the service '%service_id%' is deprecated since 2019-10-30 and will be removed soon. Use 'Chill\PersonBundle\Repository\PersonRepository' instead
factory: ['@doctrine.orm.entity_manager', getRepository]
arguments:
- 'Chill\PersonBundle\Entity\Person'
Chill\PersonBundle\Repository\PersonRepository:
class: Chill\PersonBundle\Person\PersonRepository
factory: ['@doctrine.orm.entity_manager', getRepository]
arguments:
- 'Chill\PersonBundle\Entity\Person'

View File

@ -0,0 +1,11 @@
services:
Chill\PersonBundle\Search\PersonSearchByPhone:
arguments:
- '@Chill\PersonBundle\Repository\PersonRepository'
- '@security.token_storage'
- '@chill.main.security.authorization.helper'
- '@chill_main.paginator_factory'
- '@Symfony\Component\Templating\EngineInterface'
- '%chill_person.search.search_by_phone%'
tags:
- { name: chill.search, alias: 'person_by_phone' }

View File

@ -0,0 +1,30 @@
<?php declare(strict_types=1);
namespace Application\Migrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Add an index for phonenumber and mobile number
*/
final class Version20191106103452 extends AbstractMigration
{
public function up(Schema $schema) : void
{
$this->addSql("CREATE INDEX phonenumber_trgm_idx
ON public.chill_person_person USING gin
(phonenumber gin_trgm_ops)");
$this->addSql("CREATE INDEX mobilenumber_trgm_idx
ON public.chill_person_person USING gin
(mobilenumber gin_trgm_ops)");
}
public function down(Schema $schema) : void
{
$this->addSql("DROP INDEX phonenumber_trgm_idx");
$this->addSql("DROP INDEX mobilenumber_trgm_idx");
}
}

View File

@ -0,0 +1,5 @@
require('./phone-alt-solid.svg');
require('./mobile-alt-solid.svg');
require('./person_by_phonenumber.scss');

View File

@ -0,0 +1 @@
<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="mobile-alt" class="svg-inline--fa fa-mobile-alt fa-w-10" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512"><path fill="currentColor" d="M272 0H48C21.5 0 0 21.5 0 48v416c0 26.5 21.5 48 48 48h224c26.5 0 48-21.5 48-48V48c0-26.5-21.5-48-48-48zM160 480c-17.7 0-32-14.3-32-32s14.3-32 32-32 32 14.3 32 32-14.3 32-32 32zm112-108c0 6.6-5.4 12-12 12H60c-6.6 0-12-5.4-12-12V60c0-6.6 5.4-12 12-12h200c6.6 0 12 5.4 12 12v312z"></path></svg>

After

Width:  |  Height:  |  Size: 516 B

View File

@ -0,0 +1,25 @@
.person-list__--by-phonenumber {
.person-list__--by-phonenumber__phones {
ul {
list-style: none inside;
padding: 0;
margin: 0;
li {
margin: 0.80rem;
img {
vertical-align: baseline;
height: 0.90rem;
margin-right: 0.20rem;
}
pre {
display: inline;
}
}
}
}
}
;

View File

@ -0,0 +1 @@
<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="phone-alt" class="svg-inline--fa fa-phone-alt fa-w-16" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M497.39 361.8l-112-48a24 24 0 0 0-28 6.9l-49.6 60.6A370.66 370.66 0 0 1 130.6 204.11l60.6-49.6a23.94 23.94 0 0 0 6.9-28l-48-112A24.16 24.16 0 0 0 122.6.61l-104 24A24 24 0 0 0 0 48c0 256.5 207.9 464 464 464a24 24 0 0 0 23.4-18.6l24-104a24.29 24.29 0 0 0-14.01-27.6z"></path></svg>

After

Width:  |  Height:  |  Size: 502 B

View File

@ -93,6 +93,7 @@ Reset: 'Remise à zéro'
'%nb% person with similar name. Please verify that this is a new person': '{1} Une personne a un nom similaire. Vérifiez qu''il ne s''agit pas d''elle. | ]1, Inf] %nb% personnes ont un nom similaire. Vérifiez qu''il ne s''agit pas de l''une d''elles.'
'The person has been created': 'Le dossier a été créé'
'Person search results': 'Recherche de personnes'
Person search results by phonenumber: Recherche de personnes par numéro de téléphone
'Search within persons': 'Recherche parmi les personnes'
'%total% persons matching the search pattern:': '{0} Aucune personne ne correspond aux termes de recherche : | {1} Une personne a été trouvée par la recherche : | ]1,Inf] %total% personnes correspondent aux termes de recherche :'
'Last opening since %last_opening%': 'Dernière ouverture le %last_opening%.'

View File

@ -33,6 +33,7 @@
<th class="chill-red">{% trans %}Name{% endtrans %}</th>
<th class="chill-green">{% trans %}Date of birth{% endtrans %}</th>
<th class="chill-orange">{% trans %}Nationality{% endtrans %}</th>
<th>&nbsp;</th>
</tr>
</thead>
<tbody>
@ -63,6 +64,14 @@
{{ 'Without nationality'|trans }}
{% endif %}
</td>
<td>
<ul class="record_actions">
<li><a class="sc-button bt-show" href="{{ path('chill_person_view', { person_id : person.getId }) }}"></a></li>
{% if is_granted('CHILL_PERSON_UPDATE', person) %}
<li><a class="sc-button bt-update" href="{{ path('chill_person_general_edit', { person_id : person.getId }) }}"></a></li>
{% endif %}
</ul>
</td>
</tr>
{% endfor %}
</tbody>
@ -76,11 +85,14 @@
</a>
</li>
{% endif %}
{% if search_name != "person_similarity" %}
<li>
<a href="{{ path('chill_main_advanced_search', { "name": search_name, "q": pattern } ) }}" class="sc-button bt-action">
<i class="fa fa-search" aria-hidden="true"></i> {{ 'Advanced search'|trans }}
</a>
</li>
{% endif %}
{% if preview == true and persons|length < total %}
<li>
<a href="{{ path('chill_main_search', { "name": search_name|default('abcd'), "q" : pattern }) }}" class="sc-button">

View File

@ -0,0 +1,108 @@
{#
* 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/>.
#}
<h2>{{ title|default('Person search results by phonenumber')|trans }}</h2>
<p>
{{ '%total% persons matching the search pattern:'|transchoice( total, { '%total%' : total}) }}
<a href="{{ path('chill_main_advanced_search', { "name": search_name, "q": pattern } ) }}" class="sc-button button-small">
<i class="fa fa-search" aria-hidden="true"></i> {{ pattern }}
</a>
</p>
<p>{{ 'Results %start%-%end% of %total%'|trans({ '%start%' : start, '%end%': start + persons|length, '%total%' : total } ) }}</p>
{% if persons|length > 0 %}
<table class="person-list__--by-phonenumber">
<thead>
<tr>
<th class="chill-red">{% trans %}Name{% endtrans %}</th>
<th class="chill-green">{% trans %}Date of birth{% endtrans %}</th>
<th class="chill-orange">{% trans %}Phonenumber{% endtrans %}</th>
<th>&nbsp;</th>
</tr>
</thead>
<tbody>
{% for person in persons %}
<tr>
<td>
{% set is_open = person.isOpen() %}
<a href="{{ path('chill_person_view', { person_id : person.getId }) }}" {% if chill_person.fields.accompanying_period == 'visible' %}{% if is_open %} alt="{{ 'An accompanying period is open'|trans|e('html_attr') }}"{% else %} alt="{{ 'Any accompanying periods are open'|trans|e('html_attr') }}" {% endif %}{% endif %}>
{{person.firstName}} {{person.lastName}}
{% spaceless %}
{% if chill_person.fields.accompanying_period == 'visible' %}
{% if is_open == false %}
<i class="fa fa-folder" ></i>
{% else %}
<i class="fa fa-folder-open" ></i>
{% endif %}
{% endif %}
{% endspaceless %}
</a>
</td>
<td>
{% if person.birthdate is not null %}{{person.birthdate|localizeddate('long', 'none', app.request.locale) }}{% else %}{{ 'Unknown date of birth'|trans }}{% endif %}
</td>
<td class="person-list__--by-phonenumber__phones">
<ul>
{% if person.phonenumber is not empty %}
<li>
<a href="tel:{{ person.phonenumber }}"><img src="{{ asset('build/images/mobile-alt-solid.svg') }}">&nbsp;<pre>{{ person.phonenumber|chill_format_phonenumber }}</pre></a>
</li>
{% endif %}
{% if person.mobilenumber is not empty%}
<li>
<a href="tel:{{ person.mobilenumber }}"><img src="{{ asset('build/images/phone-alt-solid.svg') }}">&nbsp;<pre>{{ person.mobilenumber|chill_format_phonenumber }}</pre></a>
</li>
{% endif %}
</ul>
</td>
<td>
<ul class="record_actions">
<li><a class="sc-button bt-show" href="{{ path('chill_person_view', { person_id : person.getId }) }}"></a></li>
{% if is_granted('CHILL_PERSON_UPDATE', person) %}
<li><a class="sc-button bt-update" href="{{ path('chill_person_general_edit', { person_id : person.getId }) }}"></a></li>
{% endif %}
</ul>
</td>
</tr>
{% endfor %}
</tbody>
</table>
<ul class="record_actions">
{% if is_granted('CHILL_PERSON_CREATE') %}
<li>
<a href="{{ path('chill_person_new') }}" class="sc-button bt-create">
{{ 'Add a person'|trans }}
</a>
</li>
{% endif %}
{% if preview == true and persons|length < total %}
<li>
<a href="{{ path('chill_main_search', { "name": search_name|default('abcd'), "q" : pattern }) }}" class="sc-button">
{{ 'See all results'|trans }}
</a>
</li>
{% endif %}
</ul>
{% endif %}
{% if preview == false %}
{{ chill_pagination(paginator) }}
{% endif %}

View File

@ -206,13 +206,13 @@ This view should receive those arguments:
{%- if chill_person.fields.phonenumber == 'visible' -%}
<dl>
<dt>{{ 'Phonenumber'|trans }}&nbsp;:</dt>
<dd>{% if person.phonenumber is not empty %}<pre>{{ person.phonenumber}}</pre>{% else %}<span class="chill-no-data-statement">{{ 'No data given'|trans }}{% endif %}</dd>
<dd>{% if person.phonenumber is not empty %}<a href="tel:{{ person.phonenumber }}"><pre>{{ person.phonenumber|chill_format_phonenumber }}</pre></a>{% else %}<span class="chill-no-data-statement">{{ 'No data given'|trans }}{% endif %}</dd>
</dl>
{% endif %}
{%- if chill_person.fields.mobilenumber == 'visible' -%}
<dl>
<dt>{{ 'Mobilenumber'|trans }}&nbsp;:</dt>
<dd>{% if person.mobilenumber is not empty %}<pre>{{ person.mobilenumber}}</pre>{% else %}<span class="chill-no-data-statement">{{ 'No data given'|trans }}{% endif %}</dd>
<dd>{% if person.mobilenumber is not empty %}<a href="tel:{{ person.mobilenumber }}"><pre>{{ person.mobilenumber|chill_format_phonenumber }}</pre></a>{% else %}<span class="chill-no-data-statement">{{ 'No data given'|trans }}{% endif %}</dd>
</dl>
{% endif %}
{%- if chill_person.fields.contact_info == 'visible' -%}

View File

@ -0,0 +1,133 @@
<?php
/*
*
* Copyright (C) 2014-2019, 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 Chill\PersonBundle\Repository\PersonRepository;
use Chill\PersonBundle\Entity\Person;
use Chill\MainBundle\Search\SearchInterface;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Chill\PersonBundle\Security\Authorization\PersonVoter;
use Chill\MainBundle\Security\Authorization\AuthorizationHelper;
use Chill\MainBundle\Pagination\PaginatorFactory;
use Symfony\Component\Security\Core\Role\Role;
use Symfony\Component\Templating\EngineInterface;
/**
*
*
*/
class PersonSearchByPhone extends AbstractSearch
{
/**
*
* @var PersonRepository
*/
private $personRepository;
/**
*
* @var TokenStorageInterface
*/
private $tokenStorage;
/**
*
* @var AuthorizationHelper
*/
private $helper;
/**
*
* @var PaginatorFactory
*/
protected $paginatorFactory;
/**
*
* @var bool
*/
protected $activeByDefault;
/**
*
* @var Templating
*/
protected $engine;
const NAME = 'phone';
public function __construct(
PersonRepository $personRepository,
TokenStorageInterface $tokenStorage,
AuthorizationHelper $helper,
PaginatorFactory $paginatorFactory,
EngineInterface $engine,
$activeByDefault)
{
$this->personRepository = $personRepository;
$this->tokenStorage = $tokenStorage;
$this->helper = $helper;
$this->paginatorFactory = $paginatorFactory;
$this->engine = $engine;
$this->activeByDefault = $activeByDefault === 'always';
}
public function getOrder(): int
{
return 110;
}
public function isActiveByDefault(): bool
{
return $this->activeByDefault;
}
public function renderResult(array $terms, $start = 0, $limit = 50, $options = array(), $format = 'html')
{
$phonenumber = $terms['_default'];
$centers = $this->helper->getReachableCenters($this->tokenStorage
->getToken()->getUser(), new Role(PersonVoter::SEE));
$total = $this->personRepository
->countByPhone($phonenumber, $centers);
$persons = $this->personRepository
->findByPhone($phonenumber, $centers, $start, $limit)
;
$paginator = $this->paginatorFactory
->create($total);
return $this->engine->render('ChillPersonBundle:Person:list_by_phonenumber.html.twig',
array(
'persons' => $persons,
'pattern' => $this->recomposePattern($terms, array(), $terms['_domain'] ?? self::NAME),
'phonenumber' => $phonenumber,
'total' => $total,
'start' => $start,
'search_name' => self::NAME,
'preview' => $options[SearchInterface::SEARCH_PREVIEW_OPTION],
'paginator' => $paginator
));
}
public function supports($domain, $format): bool
{
return $domain === 'phone' && $format === 'html';
}
}