Minor fixes for closing accompanying course:

* check that transition can be applyied in menu;
* change organisation for activityVoter, and check for authorization in
    Activity Controller
* fix label 'create' in accompanying course document
* minor fix in accompanying course document voter
* change color when course is closed and show old user, and startdate /
    enddate
This commit is contained in:
Julien Fastré 2021-11-22 20:24:08 +01:00
parent 4c1e416a14
commit 0867965d9c
8 changed files with 95 additions and 28 deletions

View File

@ -269,8 +269,7 @@ final class ActivityController extends AbstractController
}
// TODO revoir le Voter de Activity pour tenir compte qu'une activité peut appartenir a une période
// $this->denyAccessUnlessGranted('CHILL_ACTIVITY_CREATE', $entity);
$this->denyAccessUnlessGranted(ActivityVoter::CREATE, $entity);
$form = $this->createForm(ActivityType::class, $entity, [
'center' => $entity->getCenter(),

View File

@ -19,6 +19,7 @@
namespace Chill\ActivityBundle\DataFixtures\ORM;
use Chill\ActivityBundle\Security\Authorization\ActivityVoter;
use Doctrine\Common\DataFixtures\AbstractFixture;
use Doctrine\Common\DataFixtures\OrderedFixtureInterface;
use Doctrine\Persistence\ObjectManager;
@ -70,8 +71,11 @@ class LoadActivitytACL extends AbstractFixture implements OrderedFixtureInterfac
->setScope($scope);
$permissionsGroup->addRoleScope($roleScopeUpdate);
$roleScopeCreate = (new RoleScope())
->setRole('CHILL_ACTIVITY_CREATE')
->setRole(ActivityVoter::CREATE_ACCOMPANYING_COURSE)
->setScope($scope);
$roleScopeCreate = (new RoleScope())
->setRole(ActivityVoter::CREATE_PERSON)
->setScope($scope);
$permissionsGroup->addRoleScope($roleScopeCreate);
$roleScopeDelete = (new RoleScope())
->setRole('CHILL_ACTIVITY_DELETE')

View File

@ -85,7 +85,8 @@ class ChillActivityExtension extends Extension implements PrependExtensionInterf
$container->prependExtensionConfig('security', array(
'role_hierarchy' => array(
ActivityVoter::UPDATE => array(ActivityVoter::SEE_DETAILS),
ActivityVoter::CREATE => array(ActivityVoter::SEE_DETAILS),
ActivityVoter::CREATE_PERSON => array(ActivityVoter::SEE_DETAILS),
ActivityVoter::CREATE_ACCOMPANYING_COURSE => array(ActivityVoter::SEE_DETAILS),
ActivityVoter::DELETE => array(ActivityVoter::SEE_DETAILS),
ActivityVoter::SEE_DETAILS => array(ActivityVoter::SEE),
ActivityVoter::FULL => [ActivityVoter::CREATE, ActivityVoter::DELETE,

View File

@ -40,7 +40,32 @@ use Symfony\Component\Security\Core\Security;
*/
class ActivityVoter extends AbstractChillVoter implements ProvideRoleHierarchyInterface
{
/**
* allow to create an activity, which will either be associated to an
* accompanying course or person.
*
* It is safe for usage in template and controller
*/
const CREATE = 'CHILL_ACTIVITY_CREATE';
/**
* role to allow to create an activity associated win an accompanying course.
*
* Before using this, check if @link{self::CREATE} is not sufficiant
*
* @internal
*/
const CREATE_ACCOMPANYING_COURSE = 'CHILL_ACTIVITY_CREATE_ACCOMPANYING_COURSE';
/**
* role to allow to create an activity associated with a person
*
* Before using this, check if @link{self::CREATE} is not sufficiant
*
* @internal
*/
const CREATE_PERSON = 'CHILL_ACTIVITY_CREATE_PERSON';
const SEE = 'CHILL_ACTIVITY_SEE';
const SEE_DETAILS = 'CHILL_ACTIVITY_SEE_DETAILS';
const UPDATE = 'CHILL_ACTIVITY_UPDATE';
@ -90,10 +115,22 @@ class ActivityVoter extends AbstractChillVoter implements ProvideRoleHierarchyIn
if (!$this->security->isGranted(PersonVoter::SEE, $subject->getPerson())) {
return false;
}
// change attribute CREATE
if (self::CREATE === $attribute) {
$attribute = self::CREATE_PERSON;
}
} elseif ($subject->getAccompanyingPeriod() instanceof AccompanyingPeriod) {
if (!$this->security->isGranted(AccompanyingPeriodVoter::SEE, $subject->getAccompanyingPeriod())) {
return false;
}
if (self::CREATE === $attribute) {
if (AccompanyingPeriod::STEP_CLOSED === $subject->getAccompanyingPeriod()->getStep()) {
return false;
}
$attribute = self::CREATE_ACCOMPANYING_COURSE;
}
} else {
throw new \RuntimeException("could not determine context of activity");
}
@ -113,9 +150,16 @@ class ActivityVoter extends AbstractChillVoter implements ProvideRoleHierarchyIn
public function getRoles()
{
return self::ALL;
return [
self::CREATE_PERSON,
self::CREATE_ACCOMPANYING_COURSE,
self::UPDATE,
self::DELETE,
self::FULL
];
}
public function getRolesWithoutScope()
{
return [];

View File

@ -62,7 +62,7 @@
<ul class="record_actions">
<li class="create">
<a href="{{ path('accompanying_course_document_new', {'course': accompanyingCourse.id}) }}" class="btn btn-create">
{{ 'Create new document' | trans }}
{{ 'Create'|trans }}
</a>
</li>
</ul>

View File

@ -71,17 +71,21 @@ class AccompanyingCourseDocumentVoter extends AbstractChillVoter implements Prov
return false;
}
if ($subject instanceof AccompanyingCourseDocument
&& !$this->security->isGranted(AccompanyingPeriodVoter::SEE, $subject->getCourse())) {
return false;
}
if ($subject instanceof AccompanyingPeriod) {
if (AccompanyingPeriod::STEP_CLOSED === $subject->getStep()) {
if (\in_array($attribute, [self::UPDATE, self::CREATE, self::DELETE])) {
if ($attribute === self::CREATE) {
return false;
}
}
} elseif ($subject instanceof AccompanyingCourseDocument) {
if (!$this->security->isGranted(AccompanyingPeriodVoter::SEE, $subject->getCourse())) {
return false;
}
if (AccompanyingPeriod::STEP_CLOSED === $subject->getCourse()->getStep()
&& \in_array($attribute, [self::CREATE, self::DELETE, self::UPDATE])) {
return false;
}
}
return $this->voterHelper->voteOnAttribute($attribute, $subject, $token);

View File

@ -5,6 +5,7 @@ namespace Chill\PersonBundle\Menu;
use Chill\MainBundle\Routing\LocalMenuBuilderInterface;
use Chill\PersonBundle\Entity\AccompanyingPeriod;
use Knp\Menu\MenuItem;
use Symfony\Component\Workflow\Registry;
use Symfony\Contracts\Translation\TranslatorInterface;
/**
@ -20,10 +21,12 @@ class AccompanyingCourseMenuBuilder implements LocalMenuBuilderInterface
* @var TranslatorInterface
*/
protected $translator;
protected Registry $registry;
public function __construct(TranslatorInterface $translator)
public function __construct(TranslatorInterface $translator, Registry $registry)
{
$this->translator = $translator;
$this->registry = $registry;
}
public static function getMenuIds(): array
@ -33,6 +36,7 @@ class AccompanyingCourseMenuBuilder implements LocalMenuBuilderInterface
public function buildMenu($menuId, MenuItem $menu, array $parameters): void
{
/** @var AccompanyingPeriod $period */
$period = $parameters['accompanyingCourse'];
$menu->addChild($this->translator->trans('Resume Accompanying Course'), [
@ -68,13 +72,16 @@ class AccompanyingCourseMenuBuilder implements LocalMenuBuilderInterface
]])
->setExtras(['order' => 40]);
$menu->addChild($this->translator->trans('Close Accompanying Course'), [
'route' => 'chill_person_accompanying_course_close',
'routeParameters' => [
'accompanying_period_id' => $period->getId()
]])
->setExtras(['order' => 500]);
$workflow = $this->registry->get($period, 'accompanying_period_lifecycle');
if ($workflow->can($period, 'close')) {
$menu->addChild($this->translator->trans('Close Accompanying Course'), [
'route' => 'chill_person_accompanying_course_close',
'routeParameters' => [
'accompanying_period_id' => $period->getId()
]])
->setExtras(['order' => 99999]);
}
}

View File

@ -27,9 +27,17 @@
</span>
</span>
<span v-else class="text-md-end d-md-block">
<span class="badge bg-primary">
<span class="badge bg-danger">
{{ $t('course.step.closed') }}
</span>
<span class="d-md-block">
<span class="d-md-block ms-3 ms-md-0">
<i>{{ $d(accompanyingCourse.openingDate.datetime, 'text') }} - {{ $d(accompanyingCourse.closingDate.datetime, 'text') }}</i>
</span>
<span v-if="accompanyingCourse.user" class="d-md-block ms-3 ms-md-0">
<span class="item-key">{{ $t('course.referrer') }}:</span> <b>{{ accompanyingCourse.user.username }}</b>
</span>
</span>
</span>
</teleport>