Merge branch 'issue577_user_admin' into 'master'

main admin

See merge request Chill-Projet/chill-bundles!432
This commit is contained in:
Julien Fastré 2022-05-27 14:35:59 +00:00
commit 2cabe80bf6
16 changed files with 277 additions and 69 deletions

View File

@ -11,6 +11,12 @@ and this project adheres to
## Unreleased
<!-- write down unreleased development here -->
* [admin]: add select2 to Goal form type entity fields (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/702)
* [main] allow hide permissions group list menu (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/577)
* [main] allow hide change user password menu (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/577)
* [main] filter user jobs by active jobs (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/577)
* [main] add civility to User (entity, migration and form type) (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/577)
* [admin] refactorisation of the admin section: reorganisation of the menu, translations, form types, new entities (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/592)
* [admin] add admin section for languages and countries (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/596)
* [activity] activity admin: translations + remove label field for comment on admin activity type (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/587)

View File

@ -471,11 +471,6 @@ parameters:
count: 1
path: src/Bundle/ChillMainBundle/Form/Type/UserPickerType.php
-
message: "#^Only booleans are allowed in an if condition, mixed given\\.$#"
count: 1
path: src/Bundle/ChillMainBundle/Form/UserType.php
-
message: "#^Only booleans are allowed in an if condition, mixed given\\.$#"
count: 2

View File

@ -23,6 +23,7 @@ use Chill\MainBundle\Repository\UserRepository;
use Chill\MainBundle\Templating\Listing\FilterOrderHelper;
use Psr\Log\LoggerInterface;
use RuntimeException;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\Form;
use Symfony\Component\Form\FormInterface;
@ -37,6 +38,8 @@ class UserController extends CRUDController
{
public const FORM_GROUP_CENTER_COMPOSED = 'composed_groupcenter';
protected ParameterBagInterface $parameterBag;
private LoggerInterface $logger;
private UserPasswordEncoderInterface $passwordEncoder;
@ -49,12 +52,14 @@ class UserController extends CRUDController
LoggerInterface $chillLogger,
ValidatorInterface $validator,
UserPasswordEncoderInterface $passwordEncoder,
UserRepository $userRepository
UserRepository $userRepository,
ParameterBagInterface $parameterBag
) {
$this->logger = $chillLogger;
$this->userRepository = $userRepository;
$this->validator = $validator;
$this->passwordEncoder = $passwordEncoder;
$this->parameterBag = $parameterBag;
}
/**
@ -104,6 +109,7 @@ class UserController extends CRUDController
return $this->render('@ChillMain/User/edit.html.twig', [
'entity' => $user,
'access_permissions_group_list' => $this->parameterBag->get('chill_main.access_permissions_group_list'),
'edit_form' => $this->createEditForm($user)->createView(),
'add_groupcenter_form' => $this->createAddLinkGroupCenterForm($user, $request)->createView(),
'delete_groupcenter_form' => array_map(
@ -153,6 +159,73 @@ class UserController extends CRUDController
return $this->redirect($this->generateUrl('chill_crud_admin_user_edit', ['id' => $uid]));
}
public function edit(Request $request, $id): Response
{
$action = 'edit';
$entity = $this->getEntity($action, $id, $request);
if (null === $entity) {
throw $this->createNotFoundException(
sprintf(
'The %s with id %s is not found',
$this->getCrudName(),
$id
)
);
}
$response = $this->checkACL($action, $entity);
if ($response instanceof Response) {
return $response;
}
$response = $this->onPostCheckACL($action, $request, $entity);
if ($response instanceof Response) {
return $response;
}
$form = $this->createFormFor($action, $entity);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$this->onFormValid($action, $entity, $form, $request);
$em = $this->getDoctrine()->getManager();
$this->onPreFlush($action, $entity, $form, $request);
$em->flush();
$this->onPostFlush($action, $entity, $form, $request);
$this->addFlash('success', $this->generateFormSuccessMessage($action, $entity));
$result = $this->onBeforeRedirectAfterSubmission($action, $entity, $form, $request);
if ($result instanceof Response) {
return $result;
}
return $this->redirectToRoute('chill_crud_' . $this->getCrudName() . '_index');
}
if ($form->isSubmitted()) {
$this->addFlash('error', $this->generateFormErrorMessage($action, $form));
}
$defaultTemplateParameters = [
'form' => $form->createView(),
'entity' => $entity,
'crud_name' => $this->getCrudName(),
'access_permissions_group_list' => $this->parameterBag->get('chill_main.access_permissions_group_list'),
];
return $this->render(
$this->getTemplateFor($action, $entity, $request),
$this->generateTemplateParameter($action, $entity, $request, $defaultTemplateParameters)
);
}
/**
* Displays a form to edit the user current location.
*
@ -271,6 +344,11 @@ class UserController extends CRUDController
),
]
);
} elseif ('index' === $action) {
return array_merge(
['allow_change_password' => $this->parameterBag->get('chill_main.access_user_change_password')],
$defaultTemplateParameters
);
}
// default behaviour
@ -307,7 +385,7 @@ class UserController extends CRUDController
protected function onPrePersist(string $action, $entity, FormInterface $form, Request $request)
{
// for "new", encode the password
if ('new' === $action) {
if ('new' === $action && $this->parameterBag->get('chill_main.access_user_change_password')) {
$entity->setPassword($this->passwordEncoder
->encodePassword($entity, $form['plainPassword']->getData()));
}

View File

@ -129,6 +129,16 @@ class ChillMainExtension extends Extension implements
$config['access_global_history']
);
$container->setParameter(
'chill_main.access_user_change_password',
$config['access_user_change_password']
);
$container->setParameter(
'chill_main.access_permissions_group_list',
$config['access_permissions_group_list']
);
$container->setParameter(
'chill_main.routing.resources',
$config['routing']['resources']

View File

@ -116,6 +116,12 @@ class Configuration implements ConfigurationInterface
->booleanNode('access_global_history')
->defaultTrue()
->end()
->booleanNode('access_user_change_password')
->defaultTrue()
->end()
->booleanNode('access_permissions_group_list')
->defaultTrue()
->end()
->arrayNode('redis')
->children()
->scalarNode('host')

View File

@ -47,6 +47,11 @@ class User implements AdvancedUserInterface
*/
private array $attributes = [];
/**
* @ORM\ManyToOne(targetEntity=Civility::class)
*/
private ?Civility $civility = null;
/**
* @ORM\ManyToOne(targetEntity=Location::class)
*/
@ -184,6 +189,11 @@ class User implements AdvancedUserInterface
return $this->attributes;
}
public function getCivility(): ?Civility
{
return $this->civility;
}
public function getCurrentLocation(): ?Location
{
return $this->currentLocation;
@ -363,6 +373,13 @@ class User implements AdvancedUserInterface
return $this;
}
public function setCivility(?Civility $civility): User
{
$this->civility = $civility;
return $this;
}
public function setCurrentLocation(?Location $currentLocation): User
{
$this->currentLocation = $currentLocation;

View File

@ -15,9 +15,11 @@ use Chill\MainBundle\Entity\Center;
use Chill\MainBundle\Entity\Location;
use Chill\MainBundle\Entity\Scope;
use Chill\MainBundle\Entity\UserJob;
use Chill\MainBundle\Form\Type\PickCivilityType;
use Chill\MainBundle\Templating\TranslatableStringHelper;
use Doctrine\ORM\EntityRepository;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\EmailType;
@ -32,11 +34,16 @@ use Symfony\Component\Validator\Constraints\Regex;
class UserType extends AbstractType
{
protected ParameterBagInterface $parameterBag;
private TranslatableStringHelper $translatableStringHelper;
public function __construct(TranslatableStringHelper $translatableStringHelper)
{
public function __construct(
TranslatableStringHelper $translatableStringHelper,
ParameterBagInterface $parameterBag
) {
$this->translatableStringHelper = $translatableStringHelper;
$this->parameterBag = $parameterBag;
}
public function buildForm(FormBuilderInterface $builder, array $options)
@ -47,6 +54,11 @@ class UserType extends AbstractType
'required' => true,
])
->add('label', TextType::class)
->add('civility', PickCivilityType::class, [
'required' => false,
'label' => 'Civility',
'placeholder' => 'choose civility',
])
->add('mainCenter', EntityType::class, [
'label' => 'Main center',
'required' => false,
@ -76,6 +88,12 @@ class UserType extends AbstractType
'choice_label' => function (UserJob $c) {
return $this->translatableStringHelper->localize($c->getLabel());
},
'query_builder' => static function (EntityRepository $er) {
$qb = $er->createQueryBuilder('uj');
$qb->where('uj.active = TRUE');
return $qb;
},
])
->add('mainLocation', EntityType::class, [
'label' => 'Main location',
@ -94,7 +112,8 @@ class UserType extends AbstractType
},
]);
if ($options['is_creation']) {
// @phpstan-ignore-next-line
if ($options['is_creation'] && $this->parameterBag->get('chill_main.access_user_change_password')) {
$builder->add('plainPassword', RepeatedType::class, [
'mapped' => false,
'type' => PasswordType::class,

View File

@ -3,54 +3,56 @@
{% block admin_content -%}
{% embed '@ChillMain/CRUD/_edit_content.html.twig' %}
{% block crud_content_after_form %}
<h2>{{ 'Permissions granted'|trans }}</h2>
{% if access_permissions_group_list %}
<h2>{{ 'Permissions granted'|trans }}</h2>
{% if entity.groupcenters|length > 0 %}
<table>
<thead>
<tr>
<th>{{ 'Permission group'|trans }}</th>
<th>{{ 'Center'|trans }}</th>
<th>&nbsp;</th>
</tr>
</thead>
<tbody>
{% for groupcenter in entity.groupcenters %}
{% if entity.groupcenters|length > 0 %}
<table>
<thead>
<tr>
<td>
<span class="user_group permissionsgroup">
{{ groupcenter.permissionsgroup.name }}
</span>
</td>
<td>
<span class="user_group center">
{{ groupcenter.center.name }}
</span>
</td>
<td>
{{ form_start(delete_groupcenter_form[groupcenter.id]) }}
{{ form_row(delete_groupcenter_form[groupcenter.id].submit, { 'attr': { 'class': 'btn btn-chill-red' } } ) }}
{{ form_rest(delete_groupcenter_form[groupcenter.id]) }}
{{ form_end(delete_groupcenter_form[groupcenter.id]) }}
</td>
<th>{{ 'Permission group'|trans }}</th>
<th>{{ 'Center'|trans }}</th>
<th>&nbsp;</th>
</tr>
{% endfor %}
</tbody>
</table>
{% else %}
<p>{{ 'Any permissions granted to this user'|trans }}.</p>
</thead>
<tbody>
{% for groupcenter in entity.groupcenters %}
<tr>
<td>
<span class="user_group permissionsgroup">
{{ groupcenter.permissionsgroup.name }}
</span>
</td>
<td>
<span class="user_group center">
{{ groupcenter.center.name }}
</span>
</td>
<td>
{{ form_start(delete_groupcenter_form[groupcenter.id]) }}
{{ form_row(delete_groupcenter_form[groupcenter.id].submit, { 'attr': { 'class': 'btn btn-chill-red' } } ) }}
{{ form_rest(delete_groupcenter_form[groupcenter.id]) }}
{{ form_end(delete_groupcenter_form[groupcenter.id]) }}
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% else %}
<p>{{ 'Any permissions granted to this user'|trans }}.</p>
{% endif %}
<h3>{{ 'Grant new permissions'|trans }}</h3>
{{ form_start(add_groupcenter_form) }}
{{ form_row(add_groupcenter_form.composed_groupcenter.center) }}
{{ form_row(add_groupcenter_form.composed_groupcenter.permissionsgroup) }}
{{ form_row(add_groupcenter_form.submit, { 'attr' : { 'class': 'btn btn-chill-green' } } ) }}
{{ form_end(add_groupcenter_form) }}
{% endif %}
<h3>{{ 'Grant new permissions'|trans }}</h3>
{{ form_start(add_groupcenter_form) }}
{{ form_row(add_groupcenter_form.composed_groupcenter.center) }}
{{ form_row(add_groupcenter_form.composed_groupcenter.permissionsgroup) }}
{{ form_row(add_groupcenter_form.submit, { 'attr' : { 'class': 'btn btn-chill-green' } } ) }}
{{ form_end(add_groupcenter_form) }}
{% endblock %}
{% block content_form_actions_save_and_show %}{% endblock %}
{% endembed %}
{% endblock %}

View File

@ -11,6 +11,11 @@
<div class="item-bloc">
<div class="item-row">
<div class="item-col">
{% if entity.civility is not null %}
{% if entity.civility.name|length > 0 %}
{{ entity.civility.name|first }}
{% endif %}
{% endif %}
{{ entity.label }}
{% if entity.isEnabled %}
<i class="fa fa-check chill-green"></i>
@ -45,9 +50,13 @@
<li>
<a class="btn btn-edit" href="{{ path('chill_crud_admin_user_edit', { 'id': entity.id }) }}"></a>
</li>
{% if allow_change_password is same as(true) %}
<li>
<a class="btn btn-chill-red" href="{{ path('admin_user_edit_password', { 'id' : entity.id }) }}" title="{{ 'Edit password'|trans|e('html_attr') }}"><i class="fa fa-ellipsis-h"></i></a>
</li>
{% endif %}
{% if is_granted('ROLE_ALLOWED_TO_SWITCH') %}
<li>
<a class="btn btn-chill-blue" href="{{ path('chill_main_homepage', {'_switch_user': entity.username }) }}" title="{{ "Impersonate"|trans|e('html_attr') }}"><i class="fa fa-user-secret"></i></a>

View File

@ -13,6 +13,7 @@ namespace Chill\MainBundle\Routing\MenuBuilder;
use Chill\MainBundle\Routing\LocalMenuBuilderInterface;
use Knp\Menu\MenuItem;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
class AdminUserMenuBuilder implements LocalMenuBuilderInterface
@ -22,9 +23,14 @@ class AdminUserMenuBuilder implements LocalMenuBuilderInterface
*/
protected $authorizationChecker;
public function __construct(AuthorizationCheckerInterface $authorizationChecker)
{
protected ParameterBagInterface $parameterBag;
public function __construct(
AuthorizationCheckerInterface $authorizationChecker,
ParameterBagInterface $parameterBag
) {
$this->authorizationChecker = $authorizationChecker;
$this->parameterBag = $parameterBag;
}
public function buildMenu($menuId, MenuItem $menu, array $parameters)
@ -51,9 +57,11 @@ class AdminUserMenuBuilder implements LocalMenuBuilderInterface
'route' => 'admin_scope',
])->setExtras(['order' => 1020]);
$menu->addChild('Permissions group list', [
'route' => 'admin_permissionsgroup',
])->setExtras(['order' => 1030]);
if ($this->parameterBag->get('chill_main.access_permissions_group_list')) {
$menu->addChild('Permissions group list', [
'route' => 'admin_permissionsgroup',
])->setExtras(['order' => 1030]);
}
$menu->addChild('crud.admin_user.index.title', [
'route' => 'chill_crud_admin_user_index',

View File

@ -15,11 +15,14 @@ use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Notification\Counter\NotificationByUserCounter;
use Chill\MainBundle\Routing\LocalMenuBuilderInterface;
use Chill\MainBundle\Workflow\Counter\WorkflowByUserCounter;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
use Symfony\Component\Security\Core\Security;
use Symfony\Contracts\Translation\TranslatorInterface;
class UserMenuBuilder implements LocalMenuBuilderInterface
{
protected ParameterBagInterface $parameterBag;
private NotificationByUserCounter $notificationByUserCounter;
private Security $security;
@ -32,12 +35,14 @@ class UserMenuBuilder implements LocalMenuBuilderInterface
NotificationByUserCounter $notificationByUserCounter,
WorkflowByUserCounter $workflowByUserCounter,
Security $security,
TranslatorInterface $translator
TranslatorInterface $translator,
ParameterBagInterface $parameterBag
) {
$this->notificationByUserCounter = $notificationByUserCounter;
$this->workflowByUserCounter = $workflowByUserCounter;
$this->security = $security;
$this->translator = $translator;
$this->parameterBag = $parameterBag;
}
public function buildMenu($menuId, \Knp\Menu\MenuItem $menu, array $parameters)
@ -85,14 +90,16 @@ class UserMenuBuilder implements LocalMenuBuilderInterface
'order' => 700,
]);
$menu
->addChild(
'Change password',
['route' => 'change_my_password']
)
->setExtras([
'order' => 99999999998,
]);
if ($this->parameterBag->get('chill_main.access_user_change_password')) {
$menu
->addChild(
'Change password',
['route' => 'change_my_password']
)
->setExtras([
'order' => 99999999998,
]);
}
}
$menu

View File

@ -12,6 +12,7 @@ declare(strict_types=1);
namespace Chill\MainBundle\Serializer\Normalizer;
use Chill\MainBundle\Entity\Center;
use Chill\MainBundle\Entity\Civility;
use Chill\MainBundle\Entity\Location;
use Chill\MainBundle\Entity\Scope;
use Chill\MainBundle\Entity\User;
@ -60,9 +61,14 @@ class UserNormalizer implements ContextAwareNormalizerInterface, NormalizerAware
$context,
['docgen:expects' => Location::class, 'groups' => 'docgen:read']
);
$civilityContext = array_merge(
$context,
['docgen:expects' => Civility::class, 'groups' => 'docgen:read']
);
if (null === $user && 'docgen' === $format) {
return array_merge(self::NULL_USER, [
'civility' => $this->normalizer->normalize(null, $format, $civilityContext),
'user_job' => $this->normalizer->normalize(null, $format, $userJobContext),
'main_center' => $this->normalizer->normalize(null, $format, $centerContext),
'main_scope' => $this->normalizer->normalize(null, $format, $scopeContext),
@ -84,6 +90,7 @@ class UserNormalizer implements ContextAwareNormalizerInterface, NormalizerAware
];
if ('docgen' === $format) {
$data['civility'] = $this->normalizer->normalize($user->getCivility(), $format, $civilityContext);
$data['current_location'] = $this->normalizer->normalize($user->getCurrentLocation(), $format, $locationContext);
$data['main_location'] = $this->normalizer->normalize($user->getMainLocation(), $format, $locationContext);
}

View File

@ -0,0 +1,40 @@
<?php
/**
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
declare(strict_types=1);
namespace Chill\Migrations\Main;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Add civility to User.
*/
final class Version20220516085659 extends AbstractMigration
{
public function down(Schema $schema): void
{
$this->addSql('ALTER TABLE users DROP CONSTRAINT FK_1483A5E923D6A298');
$this->addSql('DROP INDEX IDX_1483A5E923D6A298');
$this->addSql('ALTER TABLE users DROP civility_id');
}
public function getDescription(): string
{
return 'Add civility to User';
}
public function up(Schema $schema): void
{
$this->addSql('ALTER TABLE users ADD civility_id INT DEFAULT NULL');
$this->addSql('ALTER TABLE users ADD CONSTRAINT FK_1483A5E923D6A298 FOREIGN KEY (civility_id) REFERENCES chill_main_civility (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
$this->addSql('CREATE INDEX IDX_1483A5E923D6A298 ON users (civility_id)');
}
}

View File

@ -51,6 +51,7 @@ class GoalType extends AbstractType
'choice_label' => function (SocialAction $issue) {
return $this->translatableStringHelper->localize($issue->getTitle());
},
'attr' => ['class' => 'select2 '],
])
->add('results', EntityType::class, [
@ -60,6 +61,7 @@ class GoalType extends AbstractType
'choice_label' => function (Result $r) {
return $this->translatableStringHelper->localize($r->getTitle());
},
'attr' => ['class' => 'select2 '],
])
->add('desactivationDate', ChillDateType::class, [

View File

@ -17,6 +17,7 @@ use Chill\DocGeneratorBundle\Context\Exception\UnexpectedTypeException;
use Chill\DocGeneratorBundle\Entity\DocGeneratorTemplate;
use Chill\DocGeneratorBundle\Service\Context\BaseContextData;
use Chill\DocStoreBundle\Entity\AccompanyingCourseDocument;
use Chill\DocStoreBundle\Entity\DocumentCategory;
use Chill\DocStoreBundle\Entity\StoredObject;
use Chill\DocStoreBundle\Repository\DocumentCategoryRepository;
use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
@ -132,7 +133,7 @@ class AccompanyingPeriodContext implements
])
->add('category', EntityType::class, [
'placeholder' => 'Choose a document category',
'class' => 'ChillDocStoreBundle:DocumentCategory',
'class' => DocumentCategory::class,
'query_builder' => static function (EntityRepository $er) {
return $er->createQueryBuilder('c')
->where('c.documentClass = :docClass')

View File

@ -15,6 +15,7 @@ use Chill\DocGeneratorBundle\Context\DocGeneratorContextWithAdminFormInterface;
use Chill\DocGeneratorBundle\Context\Exception\UnexpectedTypeException;
use Chill\DocGeneratorBundle\Entity\DocGeneratorTemplate;
use Chill\DocGeneratorBundle\Service\Context\BaseContextData;
use Chill\DocStoreBundle\Entity\DocumentCategory;
use Chill\DocStoreBundle\Entity\PersonDocument;
use Chill\DocStoreBundle\Entity\StoredObject;
use Chill\DocStoreBundle\Repository\DocumentCategoryRepository;
@ -92,7 +93,7 @@ class PersonContext implements DocGeneratorContextWithAdminFormInterface
$builder
->add('category', EntityType::class, [
'placeholder' => 'Choose a document category',
'class' => 'ChillDocStoreBundle:DocumentCategory',
'class' => DocumentCategory::class,
'query_builder' => static function (EntityRepository $er) {
return $er->createQueryBuilder('c')
->where('c.documentClass = :docClass')