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(ActivityVoter::CREATE, $entity);
// $this->denyAccessUnlessGranted('CHILL_ACTIVITY_CREATE', $entity);
$form = $this->createForm(ActivityType::class, $entity, [ $form = $this->createForm(ActivityType::class, $entity, [
'center' => $entity->getCenter(), 'center' => $entity->getCenter(),

View File

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

View File

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

View File

@ -40,7 +40,32 @@ use Symfony\Component\Security\Core\Security;
*/ */
class ActivityVoter extends AbstractChillVoter implements ProvideRoleHierarchyInterface 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'; 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 = 'CHILL_ACTIVITY_SEE';
const SEE_DETAILS = 'CHILL_ACTIVITY_SEE_DETAILS'; const SEE_DETAILS = 'CHILL_ACTIVITY_SEE_DETAILS';
const UPDATE = 'CHILL_ACTIVITY_UPDATE'; const UPDATE = 'CHILL_ACTIVITY_UPDATE';
@ -90,10 +115,22 @@ class ActivityVoter extends AbstractChillVoter implements ProvideRoleHierarchyIn
if (!$this->security->isGranted(PersonVoter::SEE, $subject->getPerson())) { if (!$this->security->isGranted(PersonVoter::SEE, $subject->getPerson())) {
return false; return false;
} }
// change attribute CREATE
if (self::CREATE === $attribute) {
$attribute = self::CREATE_PERSON;
}
} elseif ($subject->getAccompanyingPeriod() instanceof AccompanyingPeriod) { } elseif ($subject->getAccompanyingPeriod() instanceof AccompanyingPeriod) {
if (!$this->security->isGranted(AccompanyingPeriodVoter::SEE, $subject->getAccompanyingPeriod())) { if (!$this->security->isGranted(AccompanyingPeriodVoter::SEE, $subject->getAccompanyingPeriod())) {
return false; return false;
} }
if (self::CREATE === $attribute) {
if (AccompanyingPeriod::STEP_CLOSED === $subject->getAccompanyingPeriod()->getStep()) {
return false;
}
$attribute = self::CREATE_ACCOMPANYING_COURSE;
}
} else { } else {
throw new \RuntimeException("could not determine context of activity"); throw new \RuntimeException("could not determine context of activity");
} }
@ -113,9 +150,16 @@ class ActivityVoter extends AbstractChillVoter implements ProvideRoleHierarchyIn
public function getRoles() public function getRoles()
{ {
return self::ALL; return [
self::CREATE_PERSON,
self::CREATE_ACCOMPANYING_COURSE,
self::UPDATE,
self::DELETE,
self::FULL
];
} }
public function getRolesWithoutScope() public function getRolesWithoutScope()
{ {
return []; return [];

View File

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

View File

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

View File

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

View File

@ -27,9 +27,17 @@
</span> </span>
</span> </span>
<span v-else class="text-md-end d-md-block"> <span v-else class="text-md-end d-md-block">
<span class="badge bg-primary"> <span class="badge bg-danger">
{{ $t('course.step.closed') }} {{ $t('course.step.closed') }}
</span> </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> </span>
</teleport> </teleport>