Merge branch 'upgrade-sf3' into add_email_and_phone_to_person

This commit is contained in:
Julien Fastré 2018-08-20 11:31:35 +02:00
commit baf4b1f277
17 changed files with 199 additions and 59 deletions

View File

@ -26,6 +26,7 @@ use Symfony\Component\DependencyInjection\Loader;
use Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface;
use Chill\MainBundle\DependencyInjection\MissingBundleException;
use Chill\PersonBundle\Security\Authorization\PersonVoter;
use Chill\MainBundle\Security\Authorization\ChillExportVoter;
use Chill\PersonBundle\Doctrine\DQL\AddressPart;
/**
@ -60,6 +61,7 @@ class ChillPersonExtension extends Extension implements PrependExtensionInterfac
$loader->load('services/fixtures.yml');
$loader->load('services/controller.yml');
$loader->load('services/search.yml');
$loader->load('services/menu.yml');
}
private function handlePersonFieldsParameters(ContainerBuilder $container, $config)
@ -163,7 +165,9 @@ class ChillPersonExtension extends Extension implements PrependExtensionInterfac
$container->prependExtensionConfig('security', array(
'role_hierarchy' => array(
'CHILL_PERSON_UPDATE' => array('CHILL_PERSON_SEE'),
'CHILL_PERSON_CREATE' => array('CHILL_PERSON_SEE')
'CHILL_PERSON_CREATE' => array('CHILL_PERSON_SEE'),
PersonVoter::LISTS => [ ChillExportVoter::EXPORT ],
PersonVoter::STATS => [ ChillExportVoter::EXPORT ]
)
));
}

View File

@ -60,6 +60,7 @@ class Person implements HasCenterInterface {
const MALE_GENDER = 'man';
const FEMALE_GENDER = 'woman';
const BOTH_GENDER = 'both';
/** @var \Chill\PersonBundle\Entity\MaritalStatus The marital status of the person */
private $maritalStatus;
@ -778,11 +779,11 @@ class Person implements HasCenterInterface {
public function isAddressesValid(ExecutionContextInterface $context)
{
if ($this->hasTwoAdressWithSameValidFromDate()) {
$context->addViolationAt(
'addresses',
'Two addresses has the same validFrom date',
array()
);
$context
->buildViolation('Two addresses has the same validFrom date')
->atPath('addresses')
->addViolation()
;
}
}

View File

@ -21,6 +21,7 @@ use Chill\MainBundle\Templating\TranslatableStringHelper;
use Chill\CustomFieldsBundle\Service\CustomFieldProvider;
use Symfony\Component\Form\Extension\Core\Type\DateType;
use Chill\MainBundle\Export\ExportElementValidatedInterface;
use Chill\CustomFieldsBundle\CustomFields\CustomFieldChoice;
/**
* Render a list of peoples
@ -61,7 +62,9 @@ class ListPerson implements ListInterface, ExportElementValidatedInterface
'address_valid_from', 'address_postcode_label', 'address_postcode_code',
'address_country_name', 'address_country_code'
);
private $slugs = [];
public function __construct(
EntityManagerInterface $em,
TranslatorInterface $translator,
@ -264,25 +267,54 @@ class ListPerson implements ListInterface, ExportElementValidatedInterface
};
} else {
// for fields which are custom fields
/* @var $cf CustomField */
$cf = $this->entityManager
->getRepository(CustomField::class)
->findOneBy(array('slug' => $this->DQLToSlug($key)));
return function($value) use ($cf) {
if ($value === '_header') {
return $this->translatableStringHelper->localize($cf->getName());
}
return $this->customFieldProvider
->getCustomFieldByType($cf->getType())
->render(json_decode($value, true), $cf, 'csv');
};
return $this->getLabelForCustomField($key, $values, $data);
}
}
}
private function getLabelForCustomField($key, array $values, $data)
{
// for fields which are custom fields
/* @var $cf CustomField */
$cf = $this->entityManager
->getRepository(CustomField::class)
->findOneBy(array('slug' => $this->DQLToSlug($key)));
$cfType = $this->customFieldProvider->getCustomFieldByType($cf->getType());
$defaultFunction = function($value) use ($cf) {
if ($value === '_header') {
return $this->translatableStringHelper->localize($cf->getName());
}
return $this->customFieldProvider
->getCustomFieldByType($cf->getType())
->render(json_decode($value, true), $cf, 'csv');
};
if ($cfType instanceof CustomFieldChoice and $cfType->isMultiple($cf)) {
return function($value) use ($cf, $cfType, $key) {
$slugChoice = $this->extractInfosFromSlug($key)['additionnalInfos']['choiceSlug'];
$decoded = \json_decode($value, true);
if ($value === '_header') {
$label = $cfType->getChoices($cf)[$slugChoice];
return $this->translatableStringHelper->localize($cf->getName())
.' | '.$label;
}
if ($slugChoice === '_other' and $cfType->isChecked($cf, $choiceSlug, $decoded)) {
return $cfType->extractOtherValue($cf, $decoded);
} else {
return $cfType->isChecked($cf, $slugChoice, $decoded);
}
};
} else {
return $defaultFunction;
}
}
/**
* {@inheritDoc}
@ -297,29 +329,46 @@ class ListPerson implements ListInterface, ExportElementValidatedInterface
foreach ($data['fields'] as $key) {
if (in_array($key, $this->fields)) {
$fields[] = $key;
} else {
// this should be a slug from custom field, we have to clean it
$fields[] = $this->slugToDQL($key);
}
}
return $fields;
// add the key from slugs and return
return \array_merge($fields, \array_keys($this->slugs));
}
/**
* clean a slug to be usable by DQL
*
* @param string $slugsanitize
*
* @param string $slugsanitize
* @param string $type the type of the customfield, if required (currently only for choices)
* @return string
*/
private function slugToDQL($slug)
private function slugToDQL($slug, $type = "default", array $additionalInfos = [])
{
return "cf____".\str_replace("-", "____", $slug);
$uid = 'slug_'.\uniqid();
$this->slugs[$uid] = [
'slug' => $slug,
'type' => $type,
'additionnalInfos' => $additionalInfos
];
return $uid;
}
private function DQLToSlug($cleanedSlug)
{
return $this->slugs[$cleanedSlug]['slug'];
}
/**
*
* @param type $cleanedSlug
* @return an array with keys = 'slug', 'type', 'additionnalInfo'
*/
private function extractInfosFromSlug($slug)
{
return \str_replace("____", "-", \substr($cleanedSlug, 6));
return $this->slugs[$slug];
}
/**
@ -395,13 +444,21 @@ class ListPerson implements ListInterface, ExportElementValidatedInterface
}
foreach ($this->getCustomFields() as $cf) {
if (in_array($cf->getSlug(), $data['fields'])) {
$cfType = $this->customFieldProvider->getCustomFieldByType($cf->getType());
if ($cfType instanceof CustomFieldChoice and $cfType->isMultiple($cf)) {
foreach($cfType->getChoices($cf) as $choiceSlug => $label) {
$slug = $this->slugToDQL($cf->getSlug(), 'choice', [ 'choiceSlug' => $choiceSlug ]);
$qb->addSelect(
sprintf('GET_JSON_FIELD_BY_KEY(person.cFData, :slug%s) AS %s',
$slug, $slug));
$qb->setParameter(sprintf('slug%s', $slug), $cf->getSlug());
}
} else {
$slug = $this->slugToDQL($cf->getSlug());
$qb->addSelect(
sprintf('GET_JSON_FIELD_BY_KEY(person.cFData, :slug%s) AS %s',
$slug, $slug));
$qb->setParameter(sprintf('slug%s', $slug), $cf->getSlug());
//$qb->setParameter(sprintf('name%s', $slug), $cf->getSlug());
}
}

View File

@ -61,7 +61,7 @@ class GenderFilter implements FilterInterface,
public function validateForm($data, ExecutionContextInterface $context)
{
if (count($data['accepted_genders']) === 0) {
if ($data['accepted_genders'] === null) {
$context->buildViolation("You should select an option")
->addViolation();
}

View File

@ -25,7 +25,7 @@ class PersonToIdTransformer implements DataTransformerInterface
/**
* Transforms an object (issue) to a string (id).
*
* @param Issue|null $issue
* @param Person|null $issue
* @return string
*/
public function transform($issue)
@ -42,7 +42,7 @@ class PersonToIdTransformer implements DataTransformerInterface
*
* @param string $id
*
* @return Issue|null
* @return Person|null
*
* @throws TransformationFailedException if object (issue) is not found.
*/

View File

@ -23,7 +23,8 @@ class GenderType extends AbstractType {
$a = array(
Person::MALE_GENDER => Person::MALE_GENDER,
Person::FEMALE_GENDER => Person::FEMALE_GENDER
Person::FEMALE_GENDER => Person::FEMALE_GENDER,
Person::BOTH_GENDER => Person::BOTH_GENDER
);
$resolver->setDefaults(array(

View File

@ -0,0 +1,61 @@
<?php
/*
* Copyright (C) 2018 Champs-Libres <info@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\Menu;
use Chill\MainBundle\Routing\LocalMenuBuilderInterface;
use Knp\Menu\MenuItem;
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
use Chill\PersonBundle\Security\Authorization\PersonVoter;
/**
*
*
* @author Julien Fastré <julien.fastre@champs-libres.coop>
*/
class SectionMenuBuilder implements LocalMenuBuilderInterface
{
/**
*
* @var AuthorizationCheckerInterface
*/
protected $authorizationChecker;
public function __construct(AuthorizationCheckerInterface $authorizationChecker)
{
$this->authorizationChecker = $authorizationChecker;
}
public function buildMenu($menuId, MenuItem $menu, array $parameters)
{
if ($this->authorizationChecker->isGranted(PersonVoter::CREATE)) {
$menu->addChild('Add a person', [
'route' => 'chill_person_new'
])
->setExtras([
'order' => 10,
'icons' => [ 'plus' ]
]);
}
}
public static function getMenuIds(): array
{
return [ 'section' ];
}
}

View File

@ -18,12 +18,6 @@ chill_person_general_update:
chill_person_new:
path: /{_locale}/person/new
defaults: {_controller: ChillPersonBundle:Person:new }
options:
menus:
section:
order: 10
label: Add a person
icons: [plus]
chill_person_review:
path: /{_locale}/person/review

View File

@ -0,0 +1,6 @@
services:
Chill\PersonBundle\Menu\SectionMenuBuilder:
arguments:
$authorizationChecker: '@Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface'
tags:
- { name: 'chill.menu_builder' }

View File

@ -51,6 +51,8 @@ man: Homme
woman: Femme
Man: Homme
Woman: Femme
both: Indéterminé
Both: Indéterminé
Divorced: Divorcé(e)
Separated: Séparé(e)
Widow: Veuf(ve)
@ -141,6 +143,7 @@ Add an address: Ajouter une adresse
Back to the person details: Retour aux détails de la personne
#timeline
Timeline: Historique
Closing the accompanying period: Fermeture de la période d'accompagnement
Opening the accompanying period: Ouverture d'une période d'accompagnement

View File

@ -51,7 +51,7 @@
{% endspaceless %}
</a>
</td>
<td>{{ person.birthdate|localizeddate('long', 'none', app.request.locale) }}</td>
<td>{% if person.birthdate is not null %}{{ person.birthdate|localizeddate('long', 'none', app.request.locale) }}{% else %}&nbsp;{% endif %}</td>
<td>
{% if person.nationality is not null %}{{ person.nationality.name|localize_translatable_string }}{% else %}{{ 'Without nationality'|trans }}{% endif %}
</td>

View File

@ -64,11 +64,13 @@
</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 %}
<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 }}

View File

@ -1,3 +1,4 @@
{% if is_granted('CHILL_PERSON_CREATE') %}
<div class="grid-8 centered" style="text-align:center; margin-top: 1.5em;">
<a href="{{ path('chill_person_new') }}">
<div class="sc-button blue" style="float: inherit;">
@ -5,4 +6,4 @@
</div>
</a>
</div>
{% endif %}

View File

@ -45,7 +45,9 @@
<i class="fa fa-{% spaceless %}
{% if person.gender == "woman" %}
female
{% else %}
{% elseif person.gender == "both" %}
neuter
{% else %}
male
{% endif %}
{% endspaceless %}"></i>
@ -105,7 +107,7 @@
{% block vertical_menu_content %}
{{ chill_menu('person', {
'layout': 'ChillPersonBundle::menu.html.twig',
'args' : {'person_id': person.id },
'args' : {'person_id': person.id, 'person': person },
'activeRouteKey': activeRouteKey
}) }}

View File

@ -15,13 +15,9 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
#}
<ul class="tab-nav">
{% for route in routes %}
<li class="{% spaceless %}
{% if route.key == activeRouteKey %}
active
{% endif %}
{% endspaceless %} ">
<a href="{{ path(route.key, args ) }}" >{{ route.label|trans|upper }}</a>
{% for menu in menus %}
<li class="">
<a href="{{ menu.uri }}" >{{ menu.label|upper }}</a>
</li>
{% endfor %}
</ul>

View File

@ -70,10 +70,12 @@ class SimilarPersonMatcher
);
$dql = 'SELECT p from ChillPersonBundle:Person p WHERE'
. ' UNACCENT(LOWER(p.firstName)) LIKE UNACCENT(LOWER(:firstName))'
. ' OR UNACCENT(LOWER(p.lastName)) LIKE UNACCENT(LOWER(:lastName))'
. ' OR UNACCENT(LOWER(p.firstName)) LIKE UNACCENT(LOWER(:lastName))'
. ' OR UNACCENT(LOWER(p.lastName)) LIKE UNACCENT(LOWER(:firstName))'
. ' ('
. ' UNACCENT(LOWER(p.firstName)) LIKE UNACCENT(LOWER(:firstName)) '
. ' OR UNACCENT(LOWER(p.lastName)) LIKE UNACCENT(LOWER(:lastName)) '
. ' OR UNACCENT(LOWER(p.firstName)) LIKE UNACCENT(LOWER(:lastName)) '
. ' OR UNACCENT(LOWER(p.lastName)) LIKE UNACCENT(LOWER(:firstName)) '
. ' ) '
. ' AND p.center IN (:centers)';
$query =

View File

@ -26,6 +26,7 @@ use Chill\MainBundle\Security\ProvideRoleHierarchyInterface;
use Chill\PersonBundle\Entity\Person;
use Chill\MainBundle\Entity\Center;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Role\Role;
/**
*
@ -61,6 +62,8 @@ class PersonVoter extends AbstractChillVoter implements ProvideRoleHierarchyInte
return \in_array($attribute, [
self::STATS, self::LISTS
]);
} elseif ($subject === null) {
return $attribute === self::CREATE;
} else {
return false;
}
@ -72,6 +75,13 @@ class PersonVoter extends AbstractChillVoter implements ProvideRoleHierarchyInte
return false;
}
if ($subject === null) {
$centers = $this->helper->getReachableCenters($token->getUser(),
new Role($attribute));
return count($centers) > 0;
}
return $this->helper->userHasAccess($token->getUser(), $subject, $attribute);
}