Merge branch 'master' into upgrade-php82

This commit is contained in:
2023-03-28 11:11:51 +02:00
30 changed files with 419 additions and 139 deletions

View File

@@ -30,6 +30,7 @@ use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
use Symfony\Component\Security\Core\Security;
use Symfony\Component\Validator\Validator\ValidatorInterface;
use Symfony\Component\Workflow\Registry;
use Symfony\Component\Workflow\TransitionBlocker;
@@ -48,11 +49,13 @@ class WorkflowController extends AbstractController
private Registry $registry;
private Security $security;
private TranslatorInterface $translator;
private ValidatorInterface $validator;
public function __construct(EntityWorkflowManager $entityWorkflowManager, EntityWorkflowRepository $entityWorkflowRepository, ValidatorInterface $validator, PaginatorFactory $paginatorFactory, Registry $registry, EntityManagerInterface $entityManager, TranslatorInterface $translator)
public function __construct(EntityWorkflowManager $entityWorkflowManager, EntityWorkflowRepository $entityWorkflowRepository, ValidatorInterface $validator, PaginatorFactory $paginatorFactory, Registry $registry, EntityManagerInterface $entityManager, TranslatorInterface $translator, Security $security)
{
$this->entityWorkflowManager = $entityWorkflowManager;
$this->entityWorkflowRepository = $entityWorkflowRepository;
@@ -61,6 +64,7 @@ class WorkflowController extends AbstractController
$this->registry = $registry;
$this->entityManager = $entityManager;
$this->translator = $translator;
$this->security = $security;
}
/**
@@ -291,10 +295,18 @@ class WorkflowController extends AbstractController
if (count($workflow->getEnabledTransitions($entityWorkflow)) > 0) {
// possible transition
$usersInvolved = $entityWorkflow->getUsersInvolved();
$currentUserFound = array_search($this->security->getUser(), $usersInvolved, true);
if (false !== $currentUserFound) {
unset($usersInvolved[$currentUserFound]);
}
$transitionForm = $this->createForm(
WorkflowStepType::class,
$entityWorkflow->getCurrentStep(),
['transition' => true, 'entity_workflow' => $entityWorkflow]
['transition' => true, 'entity_workflow' => $entityWorkflow, 'suggested_users' => $usersInvolved]
);
$transitionForm->handleRequest($request);

View File

@@ -348,6 +348,23 @@ class EntityWorkflow implements TrackCreationInterface, TrackUpdateInterface
return $this->transitionningStep;
}
/**
* @return User[]
*/
public function getUsersInvolved(): array
{
$usersInvolved = [];
$usersInvolved[spl_object_hash($this->getCreatedBy())] = $this->getCreatedBy();
foreach ($this->steps as $step) {
foreach ($step->getDestUser() as $u) {
$usersInvolved[spl_object_hash($u)] = $u;
}
}
return $usersInvolved;
}
public function getWorkflowName(): string
{
return $this->workflowName;

View File

@@ -56,12 +56,7 @@ class CommentType extends AbstractType
public function buildView(FormView $view, FormInterface $form, array $options)
{
$view->vars = array_replace(
$view->vars,
[
'fullWidth' => true,
]
);
$view->vars['fullWidth'] = true;
}
public function configureOptions(OptionsResolver $resolver)

View File

@@ -19,6 +19,7 @@ use Symfony\Component\Form\FormInterface;
use Symfony\Component\Form\FormView;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
use Symfony\Component\Serializer\SerializerInterface;
/**
@@ -28,12 +29,15 @@ class PickUserDynamicType extends AbstractType
{
private DenormalizerInterface $denormalizer;
private NormalizerInterface $normalizer;
private SerializerInterface $serializer;
public function __construct(DenormalizerInterface $denormalizer, SerializerInterface $serializer)
public function __construct(DenormalizerInterface $denormalizer, SerializerInterface $serializer, NormalizerInterface $normalizer)
{
$this->denormalizer = $denormalizer;
$this->serializer = $serializer;
$this->normalizer = $normalizer;
}
public function buildForm(FormBuilderInterface $builder, array $options)
@@ -46,6 +50,11 @@ class PickUserDynamicType extends AbstractType
$view->vars['multiple'] = $options['multiple'];
$view->vars['types'] = ['user'];
$view->vars['uniqid'] = uniqid('pick_user_dyn');
$view->vars['suggested'] = [];
foreach ($options['suggested'] as $user) {
$view->vars['suggested'][] = $this->normalizer->normalize($user, 'json', ['groups' => 'read']);
}
}
public function configureOptions(OptionsResolver $resolver)
@@ -53,7 +62,8 @@ class PickUserDynamicType extends AbstractType
$resolver
->setDefault('multiple', false)
->setAllowedTypes('multiple', ['bool'])
->setDefault('compound', false);
->setDefault('compound', false)
->setDefault('suggested', []);
}
public function getBlockPrefix()

View File

@@ -39,7 +39,7 @@ class PrivateCommentType extends AbstractType
$builder
->add('comments', ChillTextareaType::class, [
'disable_editor' => $options['disable_editor'],
'label' => false,
'label' => $options['label'],
])
->setDataMapper($this->dataMapper);
}

View File

@@ -95,12 +95,7 @@ class ScopePickerType extends AbstractType
public function buildView(FormView $view, FormInterface $form, array $options)
{
$view->vars = array_replace(
$view->vars,
[
'fullWidth' => true,
]
);
$view->vars['fullWidth'] = true;
}
public function configureOptions(OptionsResolver $resolver)

View File

@@ -154,6 +154,7 @@ class WorkflowStepType extends AbstractType
'label' => 'workflow.dest for next steps',
'multiple' => true,
'mapped' => false,
'suggested' => $options['suggested_users'],
])
->add('future_dest_emails', ChillCollectionType::class, [
'label' => 'workflow.dest by email',
@@ -200,6 +201,7 @@ class WorkflowStepType extends AbstractType
->setAllowedTypes('transition', 'bool')
->setRequired('entity_workflow')
->setAllowedTypes('entity_workflow', EntityWorkflow::class)
->setDefault('suggested_users', [])
->setDefault('constraints', [
new Callback(
function ($step, ExecutionContextInterface $context, $payload) {

View File

@@ -23,7 +23,7 @@ function loadDynamicPicker(element) {
(input.value === '[]' || input.value === '') ?
null : [ JSON.parse(input.value) ]
)
;
suggested = JSON.parse(el.dataset.suggested)
if (!isMultiple) {
if (input.value === '[]'){
@@ -37,6 +37,7 @@ function loadDynamicPicker(element) {
':types="types" ' +
':picked="picked" ' +
':uniqid="uniqid" ' +
':suggested="notPickedSuggested" ' +
'@addNewEntity="addNewEntity" ' +
'@removeEntity="removeEntity"></pick-entity>',
components: {
@@ -48,16 +49,31 @@ function loadDynamicPicker(element) {
types: JSON.parse(el.dataset.types),
picked: picked === null ? [] : picked,
uniqid: el.dataset.uniqid,
suggested: suggested
}
},
computed: {
notPickedSuggested() {
if (this.multiple) {
const pickedIds = new Set();
for (const p of this.picked) {
pickedIds.add(`${p.type}${p.id}`);
}
return this.suggested.filter(e => !pickedIds.has(`${e.type}${e.id}`))
}
return this.suggested.filter(e => e.type !== this.picked.type && e.id !== e.picked.id);
}
},
methods: {
addNewEntity(entity) {
addNewEntity({entity}) {
if (this.multiple) {
if (!this.picked.some(el => {
return el.type === entity.type && el.id === entity.id;
})) {
this.picked.push(entity);
input.value = JSON.stringify(this.picked);
console.log(entity)
}
} else {
if (!this.picked.some(el => {
@@ -69,7 +85,10 @@ function loadDynamicPicker(element) {
}
}
},
removeEntity(entity) {
removeEntity({entity}) {
if (-1 === this.suggested.findIndex(e => e.type === entity.type && e.id === entity.id)) {
this.suggested.push(entity);
}
this.picked = this.picked.filter(e => !(e.type === entity.type && e.id === entity.id));
input.value = JSON.stringify(this.picked);
},

View File

@@ -17,6 +17,9 @@
</add-persons>
</li>
</ul>
<ul class="list-suggest add-items inline">
<li v-for="s in suggested" :key="s.id" @click="addNewSuggested(s)"><span>{{ s.text }}</span></li>
</ul>
</template>
<script>
@@ -49,6 +52,10 @@ export default {
// display picked entities.
type: Boolean,
default: true,
},
suggested: {
type: Array,
default: []
}
},
emits: ['addNewEntity', 'removeEntity'],
@@ -61,55 +68,58 @@ export default {
};
},
computed: {
addPersonsOptions() {
return {
uniq: !this.multiple,
type: this.types,
priority: null,
button: {
size: 'btn-sm',
class: 'btn-submit',
},
};
},
translatedListOfTypes() {
let trans = [];
this.types.forEach(t => {
if (this.$props.multiple) {
trans.push(appMessages.fr.pick_entity[t].toLowerCase());
} else {
trans.push(appMessages.fr.pick_entity[t + '_one'].toLowerCase());
}
})
addPersonsOptions() {
return {
uniq: !this.multiple,
type: this.types,
priority: null,
button: {
size: 'btn-sm',
class: 'btn-submit',
},
};
},
translatedListOfTypes() {
let trans = [];
this.types.forEach(t => {
if (this.$props.multiple) {
trans.push(appMessages.fr.pick_entity[t].toLowerCase());
} else {
trans.push(appMessages.fr.pick_entity[t + '_one'].toLowerCase());
}
})
if (this.$props.multiple) {
return appMessages.fr.pick_entity.modal_title + trans.join(', ');
} else {
return appMessages.fr.pick_entity.modal_title_one + trans.join(', ');
}
},
listClasses() {
return {
'list-suggest': true,
'remove-items': this.$props.removableIfSet,
};
},
if (this.$props.multiple) {
return appMessages.fr.pick_entity.modal_title + trans.join(', ');
} else {
return appMessages.fr.pick_entity.modal_title_one + trans.join(', ');
}
},
listClasses() {
return {
'list-suggest': true,
'remove-items': this.$props.removableIfSet,
};
},
},
methods: {
addNewEntity({ selected, modal }) {
selected.forEach((item) => {
this.$emit('addNewEntity', item.result);
}, this
);
this.$refs.addPersons.resetSearch(); // to cast child method
modal.showModal = false;
},
removeEntity(entity) {
if (!this.$props.removableIfSet) {
return;
}
this.$emit('removeEntity', entity);
}
addNewSuggested(entity) {
this.$emit('addNewEntity', {entity: entity});
},
addNewEntity({ selected, modal }) {
selected.forEach((item) => {
this.$emit('addNewEntity', { entity: item.result});
}, this
);
this.$refs.addPersons.resetSearch(); // to cast child method
modal.showModal = false;
},
removeEntity(entity) {
if (!this.$props.removableIfSet) {
return;
}
this.$emit('removeEntity',{ entity: entity });
}
},
}
</script>

View File

@@ -68,7 +68,8 @@
{{- form_errors(form) -}}
</div>
{% else %}
<div class="col-sm">
<div class="col-12 clear">{{- form_label(form) -}}</div>
<div class="col-sm-12">
{{- form_widget(form, widget_attr) -}}
{{- form_help(form) -}}
{{- form_errors(form) -}}

View File

@@ -18,43 +18,48 @@
{% block form_row %}
{% apply spaceless %}
{% if form.vars.fullWidth is not defined or form.vars.fullWidth == false %}
<div class="mb-2">
<div class="row">
<div class="{% apply spaceless %}
{% if attr.class is defined and ('cf-title' in attr.class or 'cf-fields' in attr.class ) %}
col-sm-12
{% elseif attr.class is defined and 'multiple-cf-inline' in attr.class %}
col-sm-2 col-md-4 clear
{% else %}
col-sm-4 clear
{% if form.vars.fullWidth is not defined or form.vars.fullWidth == false %}
<div class="{% apply spaceless %}
{% if attr.class is defined and ('cf-title' in attr.class or 'cf-fields' in attr.class ) %}
col-sm-12
{% elseif attr.class is defined and 'multiple-cf-inline' in attr.class %}
col-sm-2 col-md-4 clear
{% else %}
col-sm-4 clear
{% endif %}
{% endapply %}">
{% if attr.class is not defined or ('cf-title' not in attr.class and 'cf-fields' not in attr.class ) %}
{{ form_label(form) }}
{% endif %}
{% endapply %}">
{% if attr.class is not defined or ('cf-title' not in attr.class and 'cf-fields' not in attr.class ) %}
{{ form_label(form) }}
</div>
<div class="{% apply spaceless %}
{% if attr.class is defined and 'cf-title' in attr.class %}
col-sm-12
{% elseif attr.class is defined and 'cf-fields' in attr.class %}
col-sm-12 parent
{% elseif attr.class is defined and 'multiple-cf-inline' in attr.class %}
col-sm-2 col-md-8 multiple-cf-inline
{% else %}
col-sm-8
{% endif %}
{% endapply %}">
{{ form_widget(form) }}
{{ form_errors(form) }}
</div>
{% else %}
<div class="col-12 clear">{{ form_label(form) }}</div>
<div class="col-12">{{ form_widget(form) }}</div>
{% endif %}
</div>
<div class="{% apply spaceless %}
{% if attr.class is defined and 'cf-title' in attr.class %}
col-sm-12
{% elseif attr.class is defined and 'cf-fields' in attr.class %}
col-sm-12 parent
{% elseif attr.class is defined and 'multiple-cf-inline' in attr.class %}
col-sm-2 col-md-8 multiple-cf-inline
{% else %}
col-sm-8
{% endif %}
{% endapply %}">
{{ form_widget(form) }}
{{ form_errors(form) }}
</div>
</div>
</div>
{% else %}
{{ form_widget(form) }}
{% endif %}
{% endapply %}
{% endblock form_row %}
{#
The block 'form_row' above may be removed !
Read this note: https://gitlab.com/Chill-Projet/chill-bundles/-/merge_requests/502#note_1311993084
#}
{% block choice_widget_expanded %}
{% apply spaceless %}
@@ -200,7 +205,6 @@
{% block private_comment_row %}
{{ form_label(form) }}
{{ form_row(form) }}
{% endblock %}
@@ -211,7 +215,6 @@
{% endblock %}
{% block comment_row %}
{{ form_label(form) }}
{{ form_row(form) }}
{% endblock %}
@@ -249,7 +252,11 @@
{% block pick_entity_dynamic_widget %}
<input type="hidden" {{ block('widget_attributes') }} {% if value is not empty %}value="{{ value|escape('html_attr') }}" {% endif %} data-input-uniqid="{{ form.vars['uniqid'] }}"/>
<div data-module="pick-dynamic" data-types="{{ form.vars['types']|json_encode }}" data-multiple="{{ form.vars['multiple'] }}" data-uniqid="{{ form.vars['uniqid'] }}"></div>
<div data-module="pick-dynamic"
data-types="{{ form.vars['types']|json_encode }}"
data-multiple="{{ form.vars['multiple'] }}"
data-uniqid="{{ form.vars['uniqid'] }}"
data-suggested="{{ form.vars['suggested']|json_encode|escape('html_attr') }}"></div>
{% endblock %}
{% block pick_postal_code_widget %}
@@ -269,4 +276,4 @@
{{ form_errors(form.fixedDate) }}
</div>
</div>
{% endblock %}
{% endblock %}

View File

@@ -36,6 +36,11 @@ interface EntityWorkflowHandlerInterface
*/
public function getRoleShow(EntityWorkflow $entityWorkflow): ?string;
/**
* @return User[]
*/
public function getSuggestedUsers(EntityWorkflow $entityWorkflow): array;
public function getTemplate(EntityWorkflow $entityWorkflow, array $options = []): string;
public function getTemplateData(EntityWorkflow $entityWorkflow, array $options = []): array;

View File

@@ -45,3 +45,12 @@ workflow:
few {# workflows}
other {# workflows}
}
duration:
minute: >-
{m, plural,
=0 {Aucune durée}
one {# minute}
few {# minutes}
other {# minutes}
}