diff --git a/composer.json b/composer.json index 88e3c520a..747747290 100644 --- a/composer.json +++ b/composer.json @@ -1,12 +1,70 @@ { "name": "chill-project/chill-bundles", - "license": "AGPL-3.0-only", "type": "library", "description": "Most used bundles for chill-project", "keywords": [ "chill", "social worker" ], + "license": "AGPL-3.0-only", + "require": { + "champs-libres/async-uploader-bundle": "dev-sf4", + "champs-libres/wopi-bundle": "dev-master", + "composer/package-versions-deprecated": "^1.10", + "doctrine/doctrine-bundle": "^2.1", + "doctrine/doctrine-migrations-bundle": "^3.0", + "doctrine/orm": "^2.7", + "erusev/parsedown": "^1.7", + "graylog2/gelf-php": "^1.5", + "knplabs/knp-menu": "^3.1", + "knplabs/knp-menu-bundle": "^3.0", + "knplabs/knp-time-bundle": "^1.12", + "league/csv": "^9.7.1", + "nyholm/psr7": "^1.4", + "phpoffice/phpspreadsheet": "^1.16", + "sensio/framework-extra-bundle": "^5.5", + "symfony/asset": "4.*", + "symfony/browser-kit": "^5.2", + "symfony/css-selector": "^5.2", + "symfony/expression-language": "4.*", + "symfony/form": "4.*", + "symfony/intl": "4.*", + "symfony/monolog-bundle": "^3.5", + "symfony/security-bundle": "4.*", + "symfony/serializer": "^5.2", + "symfony/swiftmailer-bundle": "^3.5", + "symfony/templating": "4.*", + "symfony/translation": "4.*", + "symfony/twig-bundle": "^4.4", + "symfony/validator": "4.*", + "symfony/webpack-encore-bundle": "^1.11", + "symfony/workflow": "4.*", + "symfony/yaml": "4.*", + "twig/extra-bundle": "^2.12 || ^3.0", + "twig/intl-extra": "^3.0", + "twig/markdown-extra": "^3.3", + "twig/twig": "^2.12 || ^3.0" + }, + "conflict": { + "symfony/symfony": "*" + }, + "require-dev": { + "doctrine/doctrine-fixtures-bundle": "^3.3", + "fakerphp/faker": "^1.13", + "nelmio/alice": "^3.8", + "phpunit/phpunit": "^7.0", + "symfony/debug-bundle": "^5.1", + "symfony/dotenv": "^5.1", + "symfony/maker-bundle": "^1.20", + "symfony/phpunit-bridge": "^5.2", + "symfony/stopwatch": "^5.1", + "symfony/var-dumper": "4.*", + "symfony/web-profiler-bundle": "^5.0" + }, + "config": { + "bin-dir": "bin", + "vendor-dir": "tests/app/vendor" + }, "autoload": { "psr-4": { "Chill\\ActivityBundle\\": "src/Bundle/ChillActivityBundle", @@ -33,68 +91,10 @@ }, "minimum-stability": "dev", "prefer-stable": true, - "require": { - "champs-libres/async-uploader-bundle": "dev-sf4", - "champs-libres/wopi-bundle": "dev-master", - "nyholm/psr7": "^1.4", - "graylog2/gelf-php": "^1.5", - "symfony/form": "4.*", - "symfony/twig-bundle": "^4.4", - "twig/extra-bundle": "^2.12|^3.0", - "twig/twig": "^2.12|^3.0", - "composer/package-versions-deprecated": "^1.10", - "doctrine/doctrine-bundle": "^2.1", - "doctrine/doctrine-migrations-bundle": "^3.0", - "doctrine/orm": "^2.7", - "symfony/asset": "4.*", - "symfony/monolog-bundle": "^3.5", - "symfony/security-bundle": "4.*", - "symfony/translation": "4.*", - "symfony/validator": "4.*", - "sensio/framework-extra-bundle": "^5.5", - "symfony/yaml": "4.*", - "symfony/webpack-encore-bundle": "^1.11", - "knplabs/knp-menu": "^3.1", - "knplabs/knp-menu-bundle": "^3.0", - "symfony/templating": "4.*", - "twig/intl-extra": "^3.0", - "symfony/workflow": "4.*", - "symfony/expression-language": "4.*", - "knplabs/knp-time-bundle": "^1.12", - "symfony/intl": "4.*", - "symfony/swiftmailer-bundle": "^3.5", - "league/csv": "^9.7.1", - "phpoffice/phpspreadsheet": "^1.16", - "symfony/browser-kit": "^5.2", - "symfony/css-selector": "^5.2", - "twig/markdown-extra": "^3.3", - "erusev/parsedown": "^1.7", - "symfony/serializer": "^5.2", - "symfony/webpack-encore-bundle": "^1.11" - }, - "conflict": { - "symfony/symfony": "*" - }, - "require-dev": { - "fakerphp/faker": "^1.13", - "phpunit/phpunit": "^7.0", - "symfony/dotenv": "^5.1", - "symfony/maker-bundle": "^1.20", - "doctrine/doctrine-fixtures-bundle": "^3.3", - "symfony/stopwatch": "^5.1", - "symfony/web-profiler-bundle": "^5.0", - "symfony/var-dumper": "4.*", - "symfony/debug-bundle": "^5.1", - "symfony/phpunit-bridge": "^5.2", - "nelmio/alice": "^3.8" - }, "scripts": { "auto-scripts": { "cache:clear": "symfony-cmd", "assets:install %PUBLIC_DIR%": "symfony-cmd" } - }, - "config": { - "bin-dir": "bin" } } diff --git a/src/Bundle/ChillActivityBundle/Controller/ActivityController.php b/src/Bundle/ChillActivityBundle/Controller/ActivityController.php index fe515918a..a454626b7 100644 --- a/src/Bundle/ChillActivityBundle/Controller/ActivityController.php +++ b/src/Bundle/ChillActivityBundle/Controller/ActivityController.php @@ -22,6 +22,9 @@ namespace Chill\ActivityBundle\Controller; +use Chill\ActivityBundle\Repository\ActivityACLAwareRepository; +use Chill\ActivityBundle\Repository\ActivityACLAwareRepositoryInterface; +use Chill\ActivityBundle\Security\Authorization\ActivityVoter; use Chill\MainBundle\Security\Authorization\AuthorizationHelper; use Chill\PersonBundle\Entity\AccompanyingPeriod; use Chill\PersonBundle\Entity\Person; @@ -36,6 +39,7 @@ use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Security\Core\Role\Role; use Chill\ActivityBundle\Entity\Activity; use Chill\ActivityBundle\Form\ActivityType; +use Chill\MainBundle\Entity\Embeddable\CommentEmbeddable; use Symfony\Component\Serializer\SerializerInterface; /** @@ -53,12 +57,16 @@ class ActivityController extends AbstractController protected SerializerInterface $serializer; + protected ActivityACLAwareRepositoryInterface $activityACLAwareRepository; + public function __construct( + ActivityACLAwareRepositoryInterface $activityACLAwareRepository, EventDispatcherInterface $eventDispatcher, AuthorizationHelper $authorizationHelper, LoggerInterface $logger, SerializerInterface $serializer ) { + $this->activityACLAwareRepository = $activityACLAwareRepository; $this->eventDispatcher = $eventDispatcher; $this->authorizationHelper = $authorizationHelper; $this->logger = $logger; @@ -77,13 +85,9 @@ class ActivityController extends AbstractController [$person, $accompanyingPeriod] = $this->getEntity($request); if ($person instanceof Person) { - $reachableScopes = $this->authorizationHelper - ->getReachableCircles($this->getUser(), new Role('CHILL_ACTIVITY_SEE'), - $person->getCenter()); - - $activities = $em->getRepository(Activity::class) - ->findByPersonImplied($person, $reachableScopes) - ; + $this->denyAccessUnlessGranted(ActivityVoter::SEE, $person); + $activities = $this->activityACLAwareRepository + ->findByPerson($person, ActivityVoter::SEE, 0, null); $event = new PrivacyEvent($person, array( 'element_class' => Activity::class, @@ -93,10 +97,10 @@ class ActivityController extends AbstractController $view = 'ChillActivityBundle:Activity:listPerson.html.twig'; } elseif ($accompanyingPeriod instanceof AccompanyingPeriod) { - $activities = $em->getRepository('ChillActivityBundle:Activity')->findBy( - ['accompanyingPeriod' => $accompanyingPeriod], - ['date' => 'DESC'], - ); + $this->denyAccessUnlessGranted(ActivityVoter::SEE, $accompanyingPeriod); + + $activities = $this->activityACLAwareRepository + ->findByAccompanyingPeriod($accompanyingPeriod, ActivityVoter::SEE); $view = 'ChillActivityBundle:Activity:listAccompanyingCourse.html.twig'; } @@ -136,6 +140,12 @@ class ActivityController extends AbstractController ]; } + if ($request->query->has('activityData')) { + $activityData = $request->query->get('activityData'); + } else { + $activityData = []; + } + if ($view === null) { throw $this->createNotFoundException('Template not found'); } @@ -144,6 +154,7 @@ class ActivityController extends AbstractController 'person' => $person, 'accompanyingCourse' => $accompanyingPeriod, 'data' => $data, + 'activityData' => $activityData ]); } @@ -163,10 +174,19 @@ class ActivityController extends AbstractController $activityType = $em->getRepository(\Chill\ActivityBundle\Entity\ActivityType::class) ->find($activityType_id); + $activityData = null; + if ($request->query->has('activityData')) { + $activityData = $request->query->get('activityData'); + } + if (!$activityType instanceof \Chill\ActivityBundle\Entity\ActivityType || !$activityType->isActive()) { $params = $this->buildParamsToUrl($person, $accompanyingPeriod); + + if (null !== $activityData) { + $params['activityData'] = $activityData; + } return $this->redirectToRoute('chill_activity_activity_select_type', $params); } @@ -184,6 +204,50 @@ class ActivityController extends AbstractController $entity->setType($activityType); $entity->setDate(new \DateTime('now')); + if ($request->query->has('activityData')) { + $activityData = $request->query->get('activityData'); + + if (array_key_exists('durationTime', $activityData)) { + $durationTimeInMinutes = $activityData['durationTime']; + $hours = floor($durationTimeInMinutes / 60); + $minutes = $durationTimeInMinutes % 60; + $duration = \DateTime::createFromFormat("H:i", $hours.':'.$minutes); + if ($duration) { + $entity->setDurationTime($duration); + } + } + + if (array_key_exists('date', $activityData)) { + $date = \DateTime::createFromFormat('Y-m-d', $activityData['date']); + if ($date) { + $entity->setDate($date); + } + } + + if (array_key_exists('personsId', $activityData)) { + foreach($activityData['personsId'] as $personId){ + $concernedPerson = $em->getRepository(\Chill\PersonBundle\Entity\Person::class)->find($personId); + $entity->addPerson($concernedPerson); + } + } + + if (array_key_exists('professionalsId', $activityData)) { + foreach($activityData['professionalsId'] as $professionalsId){ + $professional = $em->getRepository(\Chill\ThirdPartyBundle\Entity\ThirdParty::class)->find($professionalsId); + $entity->addThirdParty($professional); + } + } + + if (array_key_exists('comment', $activityData)) { + $comment = new CommentEmbeddable(); + $comment->setComment($activityData['comment']); + $comment->setUserId($this->getUser()->getid()); + $comment->setDate(new \DateTime('now')); + $entity->setComment($comment); + } + + } + // TODO revoir le Voter de Activity pour tenir compte qu'une activité peut appartenir a une période // $this->denyAccessUnlessGranted('CHILL_ACTIVITY_CREATE', $entity); @@ -201,6 +265,7 @@ class ActivityController extends AbstractController $this->addFlash('success', $this->get('translator')->trans('Success : activity created!')); $params = $this->buildParamsToUrl($person, $accompanyingPeriod); + $params['id'] = $entity->getId(); return $this->redirectToRoute('chill_activity_activity_show', $params); @@ -238,7 +303,7 @@ class ActivityController extends AbstractController if (!$entity) { throw $this->createNotFoundException('Unable to find Activity entity.'); } - + if (null !== $accompanyingPeriod) { $entity->personsAssociated = $entity->getPersonsAssociated(); $entity->personsNotAssociated = $entity->getPersonsNotAssociated(); diff --git a/src/Bundle/ChillActivityBundle/Repository/ActivityACLAwareRepository.php b/src/Bundle/ChillActivityBundle/Repository/ActivityACLAwareRepository.php index b198875c5..b498a6090 100644 --- a/src/Bundle/ChillActivityBundle/Repository/ActivityACLAwareRepository.php +++ b/src/Bundle/ChillActivityBundle/Repository/ActivityACLAwareRepository.php @@ -23,6 +23,8 @@ namespace Chill\ActivityBundle\Repository; use Chill\ActivityBundle\Entity\Activity; +use Chill\MainBundle\Security\Resolver\CenterResolverDispatcher; +use Chill\PersonBundle\Entity\AccompanyingPeriod; use Chill\PersonBundle\Entity\Person; use Chill\ActivityBundle\Repository\ActivityRepository; use Chill\ActivityBundle\Security\Authorization\ActivityVoter; @@ -33,9 +35,10 @@ use Chill\MainBundle\Security\Authorization\AuthorizationHelper; use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; use Symfony\Component\Security\Core\Role\Role; use Doctrine\ORM\EntityManagerInterface; +use Symfony\Component\Security\Core\Security; -final class ActivityACLAwareRepository +final class ActivityACLAwareRepository implements ActivityACLAwareRepositoryInterface { private AuthorizationHelper $authorizationHelper; @@ -45,16 +48,63 @@ final class ActivityACLAwareRepository private EntityManagerInterface $em; + private Security $security; + + private CenterResolverDispatcher $centerResolverDispatcher; + public function __construct( AuthorizationHelper $authorizationHelper, + CenterResolverDispatcher $centerResolverDispatcher, TokenStorageInterface $tokenStorage, ActivityRepository $repository, - EntityManagerInterface $em + EntityManagerInterface $em, + Security $security ) { $this->authorizationHelper = $authorizationHelper; + $this->centerResolverDispatcher = $centerResolverDispatcher; $this->tokenStorage = $tokenStorage; $this->repository = $repository; $this->em = $em; + $this->security = $security; + } + + /** + * @param Person $person + * @param string $role + * @param int|null $start + * @param int|null $limit + * @param array $orderBy + * @return array|Activity[] + */ + public function findByPerson(Person $person, string $role, ?int $start = 0, ?int $limit = 1000, ?array $orderBy = []): array + { + $user = $this->security->getUser(); + $center = $this->centerResolverDispatcher->resolveCenter($person); + if (0 === count($orderBy)) { + $orderBy = ['date' => 'DESC']; + } + + $reachableScopes = $this->authorizationHelper + ->getReachableCircles($user, $role, $center); + + return $this->em->getRepository(Activity::class) + ->findByPersonImplied($person, $reachableScopes, $orderBy, $limit, $start); + ; + } + + public function findByAccompanyingPeriod(AccompanyingPeriod $period, string $role, ?int $start = 0, ?int $limit = 1000, ?array $orderBy = []): array + { + $user = $this->security->getUser(); + $center = $this->centerResolverDispatcher->resolveCenter($period); + if (0 === count($orderBy)) { + $orderBy = ['date' => 'DESC']; + } + + $scopes = $this->authorizationHelper + ->getReachableCircles($user, $role, $center); + + return $this->em->getRepository(Activity::class) + ->findByAccompanyingPeriod($period, $scopes, true, $limit, $start, $orderBy); } public function queryTimelineIndexer(string $context, array $args = []): array @@ -81,7 +131,7 @@ final class ActivityACLAwareRepository $metadataActivity = $this->em->getClassMetadata(Activity::class); $metadataPerson = $this->em->getClassMetadata(Person::class); $associationMapping = $metadataActivity->getAssociationMapping('person'); - + return $metadataActivity->getTableName().' JOIN ' .$metadataPerson->getTableName().' ON ' .$metadataPerson->getTableName().'.'. @@ -95,7 +145,7 @@ final class ActivityACLAwareRepository { $where = ''; $parameters = []; - + $metadataActivity = $this->em->getClassMetadata(Activity::class); $metadataPerson = $this->em->getClassMetadata(Person::class); $activityToPerson = $metadataActivity->getAssociationMapping('person')['joinColumns'][0]['name']; @@ -105,20 +155,20 @@ final class ActivityACLAwareRepository // acls: $role = new Role(ActivityVoter::SEE); - $reachableCenters = $this->authorizationHelper->getReachableCenters($this->tokenStorage->getToken()->getUser(), + $reachableCenters = $this->authorizationHelper->getReachableCenters($this->tokenStorage->getToken()->getUser(), $role); - + if (count($reachableCenters) === 0) { // insert a dummy condition return 'FALSE = TRUE'; } - if ($context === 'person') { - // we start with activities having the person_id linked to person + if ($context === 'person') { + // we start with activities having the person_id linked to person $where .= sprintf('%s = ? AND ', $activityToPerson); $parameters[] = $person->getId(); } - + // we add acl (reachable center and scopes) $where .= '('; // first loop for the for centers $centersI = 0; // like centers#i @@ -131,7 +181,7 @@ final class ActivityACLAwareRepository $reachableScopes = $this->authorizationHelper->getReachableScopes($this->tokenStorage->getToken()->getUser(), $role, $center); // we get the ids for those scopes $reachablesScopesId = array_map( - function(Scope $scope) { return $scope->getId(); }, + function(Scope $scope) { return $scope->getId(); }, $reachableScopes ); @@ -162,7 +212,7 @@ final class ActivityACLAwareRepository } // close loop for centers $where .= ')'; - + return [$where, $parameters]; } diff --git a/src/Bundle/ChillActivityBundle/Repository/ActivityACLAwareRepositoryInterface.php b/src/Bundle/ChillActivityBundle/Repository/ActivityACLAwareRepositoryInterface.php new file mode 100644 index 000000000..0b646f7c5 --- /dev/null +++ b/src/Bundle/ChillActivityBundle/Repository/ActivityACLAwareRepositoryInterface.php @@ -0,0 +1,19 @@ + 'DESC'], $limit = 100, $offset = 0) + /** + * @param $person + * @param array $scopes + * @param string[] $orderBy + * @param int $limit + * @param int $offset + * @return array|Activity[] + */ + public function findByPersonImplied(Person $person, array $scopes, ?array $orderBy = [ 'date' => 'DESC'], ?int $limit = 100, ?int $offset = 0): array { $qb = $this->createQueryBuilder('a'); $qb->select('a'); $qb - // TODO add acl - //->where($qb->expr()->in('a.scope', ':scopes')) - //->setParameter('scopes', $scopes) + ->where($qb->expr()->in('a.scope', ':scopes')) + ->setParameter('scopes', $scopes) ->andWhere( $qb->expr()->orX( $qb->expr()->eq('a.person', ':person'), @@ -61,7 +70,56 @@ class ActivityRepository extends ServiceEntityRepository $qb->addOrderBy('a.'.$k, $dir); } + $qb->setMaxResults($limit)->setFirstResult($offset); + return $qb->getQuery() ->getResult(); - } + } + + /** + * @param AccompanyingPeriod $period + * @param array $scopes + * @param int|null $limit + * @param int|null $offset + * @param array|string[] $orderBy + * @return array|Activity[] + */ + public function findByAccompanyingPeriod(AccompanyingPeriod $period, array $scopes, ?bool $allowNullScope = false, ?int $limit = 100, ?int $offset = 0, array $orderBy = ['date' => 'desc']): array + { + $qb = $this->createQueryBuilder('a'); + $qb->select('a'); + + if (!$allowNullScope) { + $qb + ->where($qb->expr()->in('a.scope', ':scopes')) + ->setParameter('scopes', $scopes) + ; + } else { + $qb + ->where( + $qb->expr()->orX( + $qb->expr()->in('a.scope', ':scopes'), + $qb->expr()->isNull('a.scope') + ) + ) + ->setParameter('scopes', $scopes) + ; + } + + $qb + ->andWhere( + $qb->expr()->eq('a.accompanyingPeriod', ':period') + ) + ->setParameter('period', $period) + ; + + foreach ($orderBy as $k => $dir) { + $qb->addOrderBy('a.'.$k, $dir); + } + + $qb->setMaxResults($limit)->setFirstResult($offset); + + return $qb->getQuery() + ->getResult(); + } } diff --git a/src/Bundle/ChillActivityBundle/Resources/views/Activity/list.html.twig b/src/Bundle/ChillActivityBundle/Resources/views/Activity/list.html.twig index 07b59c7e7..c227f5539 100644 --- a/src/Bundle/ChillActivityBundle/Resources/views/Activity/list.html.twig +++ b/src/Bundle/ChillActivityBundle/Resources/views/Activity/list.html.twig @@ -19,7 +19,7 @@ {% endif %}
- {% if t.durationTimeVisible > 0 %} + {% if activity.durationTime and t.durationTimeVisible %}

{{ activity.durationTime|date('H:i') }} diff --git a/src/Bundle/ChillActivityBundle/Resources/views/Activity/selectType.html.twig b/src/Bundle/ChillActivityBundle/Resources/views/Activity/selectType.html.twig index 996d95b0f..4120d8285 100644 --- a/src/Bundle/ChillActivityBundle/Resources/views/Activity/selectType.html.twig +++ b/src/Bundle/ChillActivityBundle/Resources/views/Activity/selectType.html.twig @@ -18,7 +18,12 @@ {% set accompanying_course_id = accompanyingCourse.id %} {% endif %} - +

{{ activityType.name|localize_translatable_string }} diff --git a/src/Bundle/ChillActivityBundle/Resources/views/Activity/show.html.twig b/src/Bundle/ChillActivityBundle/Resources/views/Activity/show.html.twig index 81ebddc05..1f54692d9 100644 --- a/src/Bundle/ChillActivityBundle/Resources/views/Activity/show.html.twig +++ b/src/Bundle/ChillActivityBundle/Resources/views/Activity/show.html.twig @@ -64,12 +64,22 @@ {% if t.durationTimeVisible %}
{{ 'Duration Time'|trans }}
-
{{ entity.durationTime|date('H:i') }}
+
{% if entity.durationTime is not null %} + {{ entity.durationTime|date('H:i') }} + {% else %} + {{ 'None'|trans|capitalize }} + {% endif %} +
{% endif %} {% if t.travelTimeVisible %}
{{ 'Travel Time'|trans }}
-
{{ entity.travelTime|date('H:i') }}
+
{% if entity.travelTime is not null %} + {{ entity.travelTime|date('H:i') }} + {% else %} + {{ 'None'|trans|capitalize }} + {% endif %} +
{% endif %} {% if t.commentVisible %} diff --git a/src/Bundle/ChillActivityBundle/Security/Authorization/ActivityVoter.php b/src/Bundle/ChillActivityBundle/Security/Authorization/ActivityVoter.php index 79cb6d852..cc9cecf52 100644 --- a/src/Bundle/ChillActivityBundle/Security/Authorization/ActivityVoter.php +++ b/src/Bundle/ChillActivityBundle/Security/Authorization/ActivityVoter.php @@ -19,6 +19,11 @@ namespace Chill\ActivityBundle\Security\Authorization; +use Chill\MainBundle\Security\Authorization\VoterHelperFactoryInterface; +use Chill\MainBundle\Security\Authorization\VoterHelperInterface; +use Chill\PersonBundle\Entity\AccompanyingPeriod; +use Chill\PersonBundle\Security\Authorization\AccompanyingPeriodVoter; +use Chill\PersonBundle\Security\Authorization\PersonVoter; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use Chill\MainBundle\Security\Authorization\AbstractChillVoter; @@ -28,11 +33,10 @@ use Chill\MainBundle\Entity\User; use Chill\ActivityBundle\Entity\Activity; use Chill\PersonBundle\Entity\Person; use Symfony\Component\Security\Core\Role\Role; +use Symfony\Component\Security\Core\Security; /** - * - * - * @author Julien Fastré + * Voter for Activity class */ class ActivityVoter extends AbstractChillVoter implements ProvideRoleHierarchyInterface { @@ -41,30 +45,37 @@ class ActivityVoter extends AbstractChillVoter implements ProvideRoleHierarchyIn const SEE_DETAILS = 'CHILL_ACTIVITY_SEE_DETAILS'; const UPDATE = 'CHILL_ACTIVITY_UPDATE'; const DELETE = 'CHILL_ACTIVITY_DELETE'; + const FULL = 'CHILL_ACTIVITY_FULL'; - /** - * - * @var AuthorizationHelper - */ - protected $helper; + private const ALL = [ + self::CREATE, + self::SEE, + self::UPDATE, + self::DELETE, + self::SEE_DETAILS, + self::FULL + ]; - public function __construct(AuthorizationHelper $helper) - { - $this->helper = $helper; + protected VoterHelperInterface $voterHelper; + + protected Security $security; + + public function __construct( + Security $security, + VoterHelperFactoryInterface $voterHelperFactory + ) { + $this->security = $security; + $this->voterHelper = $voterHelperFactory->generate(self::class) + ->addCheckFor(Person::class, [self::SEE, self::CREATE]) + ->addCheckFor(AccompanyingPeriod::class, [self::SEE, self::CREATE]) + ->addCheckFor(Activity::class, self::ALL) + ->build(); } protected function supports($attribute, $subject) { - if ($subject instanceof Activity) { - return \in_array($attribute, $this->getAttributes()); - } elseif ($subject instanceof Person) { - return $attribute === self::SEE - || - $attribute === self::CREATE; - } else { - return false; - } + return $this->voterHelper->supports($attribute, $subject); } protected function voteOnAttribute($attribute, $subject, TokenInterface $token) @@ -72,32 +83,34 @@ class ActivityVoter extends AbstractChillVoter implements ProvideRoleHierarchyIn if (!$token->getUser() instanceof User) { return false; } - - if ($subject instanceof Person) { - $centers = $this->helper->getReachableCenters($token->getUser(), new Role($attribute)); - - return \in_array($subject->getCenter(), $centers); + + if ($subject instanceof Activity) { + if ($subject->getPerson() instanceof Person) { + // the context is person: we must have the right to see the person + if (!$this->security->isGranted(PersonVoter::SEE, $subject->getPerson())) { + return false; + } + } elseif ($subject->getAccompanyingPeriod() instanceof AccompanyingPeriod) { + if (!$this->security->isGranted(AccompanyingPeriodVoter::SEE, $subject->getAccompanyingPeriod())) { + return false; + } + } else { + throw new \RuntimeException("could not determine context of activity"); + } } - - /* @var $subject Activity */ - return $this->helper->userHasAccess($token->getUser(), $subject, $attribute); - } - - private function getAttributes() - { - return [ self::CREATE, self::SEE, self::UPDATE, self::DELETE, - self::SEE_DETAILS ]; + + return $this->voterHelper->voteOnAttribute($attribute, $subject, $token); } public function getRoles() { - return $this->getAttributes(); + return self::ALL; } public function getRolesWithoutScope() { - return array(); + return []; } diff --git a/src/Bundle/ChillActivityBundle/config/services.yaml b/src/Bundle/ChillActivityBundle/config/services.yaml index 86168101a..0a65e08c8 100644 --- a/src/Bundle/ChillActivityBundle/config/services.yaml +++ b/src/Bundle/ChillActivityBundle/config/services.yaml @@ -1,20 +1,4 @@ services: - chill.activity.security.authorization.activity_voter: - class: Chill\ActivityBundle\Security\Authorization\ActivityVoter - arguments: - - "@chill.main.security.authorization.helper" - tags: - - { name: security.voter } - - { name: chill.role } - - chill.activity.security.authorization.activity_stats_voter: - class: Chill\ActivityBundle\Security\Authorization\ActivityStatsVoter - arguments: - - "@chill.main.security.authorization.helper" - tags: - - { name: security.voter } - - { name: chill.role } - chill.activity.timeline: class: Chill\ActivityBundle\Timeline\TimelineActivityProvider @@ -38,3 +22,8 @@ services: autowire: true autoconfigure: true resource: '../Notification' + + Chill\ActivityBundle\Security\Authorization\: + resource: '../Security/Authorization/' + autowire: true + autoconfigure: true diff --git a/src/Bundle/ChillActivityBundle/config/services/controller.yaml b/src/Bundle/ChillActivityBundle/config/services/controller.yaml index 106b2c6e4..96ace0a64 100644 --- a/src/Bundle/ChillActivityBundle/config/services/controller.yaml +++ b/src/Bundle/ChillActivityBundle/config/services/controller.yaml @@ -1,8 +1,4 @@ services: Chill\ActivityBundle\Controller\ActivityController: - arguments: - $eventDispatcher: '@Symfony\Component\EventDispatcher\EventDispatcherInterface' - $authorizationHelper: '@Chill\MainBundle\Security\Authorization\AuthorizationHelper' - $logger: '@chill.main.logger' - $serializer: '@Symfony\Component\Serializer\SerializerInterface' + autowire: true tags: ['controller.service_arguments'] diff --git a/src/Bundle/ChillActivityBundle/config/services/repositories.yaml b/src/Bundle/ChillActivityBundle/config/services/repositories.yaml index 2f0a9b83c..26bc58ac9 100644 --- a/src/Bundle/ChillActivityBundle/config/services/repositories.yaml +++ b/src/Bundle/ChillActivityBundle/config/services/repositories.yaml @@ -24,9 +24,7 @@ services: - '@Doctrine\Persistence\ManagerRegistry' Chill\ActivityBundle\Repository\ActivityACLAwareRepository: - arguments: - $tokenStorage: '@Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface' - $authorizationHelper: '@Chill\MainBundle\Security\Authorization\AuthorizationHelper' - $repository: '@Chill\ActivityBundle\Repository\ActivityRepository' - $em: '@Doctrine\ORM\EntityManagerInterface' + autowire: true + autoconfigure: true + Chill\ActivityBundle\Repository\ActivityACLAwareRepositoryInterface: '@Chill\ActivityBundle\Repository\ActivityACLAwareRepository' diff --git a/src/Bundle/ChillCalendarBundle/Controller/CalendarController.php b/src/Bundle/ChillCalendarBundle/Controller/CalendarController.php index fff20940a..71e461464 100644 --- a/src/Bundle/ChillCalendarBundle/Controller/CalendarController.php +++ b/src/Bundle/ChillCalendarBundle/Controller/CalendarController.php @@ -36,7 +36,9 @@ use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Security\Core\Role\Role; use Chill\CalendarBundle\Entity\Calendar; use Chill\CalendarBundle\Form\CalendarType; +use Chill\CalendarBundle\Repository\CalendarRepository; use Chill\MainBundle\Entity\User; +use Chill\MainBundle\Pagination\PaginatorFactory; use Symfony\Component\Serializer\SerializerInterface; use Symfony\Component\Routing\Annotation\Route; @@ -55,16 +57,24 @@ class CalendarController extends AbstractController protected SerializerInterface $serializer; + protected PaginatorFactory $paginator; + + private CalendarRepository $calendarRepository; + public function __construct( EventDispatcherInterface $eventDispatcher, AuthorizationHelper $authorizationHelper, LoggerInterface $logger, - SerializerInterface $serializer + SerializerInterface $serializer, + PaginatorFactory $paginator, + CalendarRepository $calendarRepository ) { $this->eventDispatcher = $eventDispatcher; $this->authorizationHelper = $authorizationHelper; $this->logger = $logger; $this->serializer = $serializer; + $this->paginator = $paginator; + $this->calendarRepository = $calendarRepository; } /** @@ -73,31 +83,40 @@ class CalendarController extends AbstractController */ public function listAction(Request $request): Response { - $em = $this->getDoctrine()->getManager(); $view = null; [$user, $accompanyingPeriod] = $this->getEntity($request); if ($user instanceof User) { - $calendarItems = $em->getRepository(Calendar::class) - ->findByUser($user) - ; + $calendarItems = $this->calendarRepository->findByUser($user); + $view = '@ChillCalendar/Calendar/listByUser.html.twig'; + + return $this->render($view, [ + 'calendarItems' => $calendarItems, + 'user' => $user + ]); + } elseif ($accompanyingPeriod instanceof AccompanyingPeriod) { - $calendarItems = $em->getRepository(Calendar::class)->findBy( + + $total = $this->calendarRepository->countByAccompanyingPeriod($accompanyingPeriod); + $paginator = $this->paginator->create($total); + $calendarItems = $this->calendarRepository->findBy( ['accompanyingPeriod' => $accompanyingPeriod], - ['startDate' => 'DESC'] + ['startDate' => 'DESC'], + $paginator->getItemsPerPage(), + $paginator->getCurrentPageFirstItemNumber() ); $view = '@ChillCalendar/Calendar/listByAccompanyingCourse.html.twig'; - } - return $this->render($view, [ - 'calendarItems' => $calendarItems, - 'user' => $user, - 'accompanyingCourse' => $accompanyingPeriod, - ]); + return $this->render($view, [ + 'calendarItems' => $calendarItems, + 'accompanyingCourse' => $accompanyingPeriod, + 'paginator' => $paginator + ]); + } } /** @@ -111,7 +130,7 @@ class CalendarController extends AbstractController [$user, $accompanyingPeriod] = $this->getEntity($request); if ($accompanyingPeriod instanceof AccompanyingPeriod) { - $view = '@ChillCalendar/Calendar/newAccompanyingCourse.html.twig'; + $view = '@ChillCalendar/Calendar/newByAccompanyingCourse.html.twig'; } // elseif ($user instanceof User) { // $view = '@ChillCalendar/Calendar/newUser.html.twig'; @@ -196,10 +215,33 @@ class CalendarController extends AbstractController throw $this->createNotFoundException('Template not found'); } + $personsId = []; + foreach ($entity->getPersons() as $p) { + array_push($personsId, $p->getId()); + } + + $professionalsId = []; + foreach ($entity->getProfessionals() as $p) { + array_push($professionalsId, $p->getId()); + } + + $durationTime = $entity->getEndDate()->diff($entity->getStartDate()); + $durationTimeInMinutes = $durationTime->days*1440 + $durationTime->h*60 + $durationTime->i; + + $activityData = [ + 'calendarId' => $id, + 'personsId' => $personsId, + 'professionalsId' => $professionalsId, + 'date' => $entity->getStartDate()->format('Y-m-d'), + 'durationTime' => $durationTimeInMinutes, + 'comment' => $entity->getComment()->getComment(), + ]; + return $this->render($view, [ 'accompanyingCourse' => $accompanyingPeriod, 'entity' => $entity, - 'user' => $user + 'user' => $user, + 'activityData' => $activityData //'delete_form' => $deleteForm->createView(), ]); } diff --git a/src/Bundle/ChillCalendarBundle/DependencyInjection/ChillCalendarExtension.php b/src/Bundle/ChillCalendarBundle/DependencyInjection/ChillCalendarExtension.php index 8660fac52..eaa8a1416 100644 --- a/src/Bundle/ChillCalendarBundle/DependencyInjection/ChillCalendarExtension.php +++ b/src/Bundle/ChillCalendarBundle/DependencyInjection/ChillCalendarExtension.php @@ -29,6 +29,7 @@ class ChillCalendarExtension extends Extension implements PrependExtensionInterf $loader->load('services/controller.yml'); $loader->load('services/fixtures.yml'); $loader->load('services/form.yml'); + $loader->load('services/event.yml'); } public function prepend(ContainerBuilder $container) diff --git a/src/Bundle/ChillCalendarBundle/Event/ListenToActivityCreate.php b/src/Bundle/ChillCalendarBundle/Event/ListenToActivityCreate.php new file mode 100644 index 000000000..76a47015d --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/Event/ListenToActivityCreate.php @@ -0,0 +1,40 @@ +requestStack = $requestStack; + } + + public function postPersist(Activity $activity, LifecycleEventArgs $event): void + { + // Get the calendarId from the request + $request = $this->requestStack->getCurrentRequest(); + if ($request->query->has('activityData')) { + $activityData = $request->query->get('activityData'); + if (array_key_exists('calendarId', $activityData)) { + $calendarId = $activityData['calendarId']; + } + } + + // Attach the activity to the calendar + $em = $event->getObjectManager(); + + $calendar = $em->getRepository(\Chill\CalendarBundle\Entity\Calendar::class)->find($calendarId); + $calendar->setActivity($activity); + + $em->persist($calendar); + $em->flush(); + + } +} \ No newline at end of file diff --git a/src/Bundle/ChillCalendarBundle/Repository/CalendarRepository.php b/src/Bundle/ChillCalendarBundle/Repository/CalendarRepository.php index edb51bb35..b87f4f525 100644 --- a/src/Bundle/ChillCalendarBundle/Repository/CalendarRepository.php +++ b/src/Bundle/ChillCalendarBundle/Repository/CalendarRepository.php @@ -3,7 +3,9 @@ namespace Chill\CalendarBundle\Repository; use Chill\CalendarBundle\Entity\Calendar; +use Chill\PersonBundle\Entity\AccompanyingPeriod; use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; +use Doctrine\ORM\EntityRepository; use Doctrine\Persistence\ManagerRegistry; /** @@ -14,9 +16,13 @@ use Doctrine\Persistence\ManagerRegistry; */ class CalendarRepository extends ServiceEntityRepository { + + // private EntityRepository $repository; + public function __construct(ManagerRegistry $registry) { parent::__construct($registry, Calendar::class); + // $this->repository = $entityManager->getRepository(AccompanyingPeriodWork::class); } // /** diff --git a/src/Bundle/ChillCalendarBundle/Resources/config/services/event.yml b/src/Bundle/ChillCalendarBundle/Resources/config/services/event.yml new file mode 100644 index 000000000..348677174 --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/Resources/config/services/event.yml @@ -0,0 +1,10 @@ +services: + Chill\CalendarBundle\Event\ListenToActivityCreate: + autowire: true + autoconfigure: true + tags: + - + name: 'doctrine.orm.entity_listener' + event: 'postPersist' + entity: 'Chill\ActivityBundle\Entity\Activity' + \ No newline at end of file diff --git a/src/Bundle/ChillCalendarBundle/Resources/views/Calendar/list.html.twig b/src/Bundle/ChillCalendarBundle/Resources/views/Calendar/list.html.twig deleted file mode 100644 index 44295e192..000000000 --- a/src/Bundle/ChillCalendarBundle/Resources/views/Calendar/list.html.twig +++ /dev/null @@ -1,135 +0,0 @@ -{% set user_id = null %} -{% if user %} - {% set user_id = user.id %} -{% endif %} - -{% set accompanying_course_id = null %} -{% if accompanyingCourse %} - {% set accompanying_course_id = accompanyingCourse.id %} -{% endif %} - -{% if context == 'user' %} -

{{ 'My calendar list' |trans }}

-{% else %} -

{{ 'Calendar list' |trans }}

-{% endif %} - -{% if context == 'user' %} -
-{% else %} - - {% if calendarItems|length == 0 %} -

- {{ "There is no calendar items."|trans }} - -

- {% else %} - -
- - {% for calendar in calendarItems %} - -
-
-
- - - {% if calendar.startDate and calendar.endDate %} - {% if calendar.endDate.diff(calendar.startDate).days >= 1 %} -

{{ "From the day"|trans }} {{ calendar.startDate|format_datetime('medium', 'short') }}

-

{{ "to the day"|trans }} {{ calendar.endDate|format_datetime('medium', 'short') }}

- {% else %} -

{{ calendar.startDate|format_date('full') }}

-

{{ calendar.startDate|format_datetime('none', 'short', locale='fr') }} - {{ calendar.endDate|format_datetime('none', 'short', locale='fr') }}

- -
-

- - {{ calendar.endDate.diff(calendar.startDate)|date("%H:%M")}} -

-
- {% endif %} - - {% endif %} - - -
-
-
    - {% if calendar.user %} -
  • - {{ 'by'|trans }}{{ calendar.user.usernameCanonical }} -
  • - {% endif %} - - {% if calendar.mainUser is not empty %} -
  • - {{ 'main user concerned'|trans }}: {{ calendar.mainUser.usernameCanonical }} -
  • - {% endif %} - -
-
    -
  • - -
  • - {# TOOD - {% if is_granted('CHILL_ACTIVITY_UPDATE', calendar) %} - #} -
  • - -
  • - {# TOOD - {% endif %} - {% if is_granted('CHILL_ACTIVITY_DELETE', calendar) %} - #} -
  • - -
  • - {# - {% endif %} - #} -
-
-
- - {% - if calendar.comment.comment is not empty - or calendar.users|length > 0 - or calendar.thirdParties|length > 0 - or calendar.users|length > 0 - %} -
-
- - {% include 'ChillActivityBundle:Activity:concernedGroups.html.twig' with {'context': context, 'with_display': 'row', 'entity': calendar } %} -
- - {% if calendar.comment.comment is not empty %} -
- {{ calendar.comment|chill_entity_render_box( { 'limit_lines': 3, 'metadata': false } ) }} -
- {% endif %} -
- {% endif %} - -
- {% endfor %} -
- {% endif %} - - {% if context != 'user' %} - {# TODO set this condition in configuration #} - - {% endif %} - - - -{% endif %} - diff --git a/src/Bundle/ChillCalendarBundle/Resources/views/Calendar/listByAccompanyingCourse.html.twig b/src/Bundle/ChillCalendarBundle/Resources/views/Calendar/listByAccompanyingCourse.html.twig index c0230344d..07aed8686 100644 --- a/src/Bundle/ChillCalendarBundle/Resources/views/Calendar/listByAccompanyingCourse.html.twig +++ b/src/Bundle/ChillCalendarBundle/Resources/views/Calendar/listByAccompanyingCourse.html.twig @@ -4,6 +4,123 @@ {% block title %}{{ 'Calendar list' |trans }}{% endblock title %} +{% set user_id = null %} +{% set accompanying_course_id = accompanyingCourse.id %} + {% block content %} - {% include 'ChillCalendarBundle:Calendar:list.html.twig' with {'context': 'accompanyingCourse'} %} -{% endblock %} + +

{{ 'Calendar list' |trans }}

+ +{% if calendarItems|length == 0 %} +

+ {{ "There is no calendar items."|trans }} + +

+{% else %} + +
+ + {% for calendar in calendarItems %} + +
+
+
+ + {% if calendar.startDate and calendar.endDate %} + {% if calendar.endDate.diff(calendar.startDate).days >= 1 %} +

{{ "From the day"|trans }} {{ calendar.startDate|format_datetime('medium', 'short') }}

+

{{ "to the day"|trans }} {{ calendar.endDate|format_datetime('medium', 'short') }}

+ {% else %} +

{{ calendar.startDate|format_date('full') }}

+

{{ calendar.startDate|format_datetime('none', 'short', locale='fr') }} - {{ calendar.endDate|format_datetime('none', 'short', locale='fr') }}

+ +
+

+ + {{ calendar.endDate.diff(calendar.startDate)|date("%H:%M")}} +

+
+ {% endif %} + + {% endif %} + +
+
+
    + {% if calendar.user %} +
  • + {{ 'by'|trans }}{{ calendar.user.usernameCanonical }} +
  • + {% endif %} + + {% if calendar.mainUser is not empty %} +
  • + {{ 'main user concerned'|trans }}: {{ calendar.mainUser.usernameCanonical }} +
  • + {% endif %} + +
+
    +
  • + +
  • + {# TOOD + {% if is_granted('CHILL_ACTIVITY_UPDATE', calendar) %} + #} +
  • + +
  • + {# TOOD + {% endif %} + {% if is_granted('CHILL_ACTIVITY_DELETE', calendar) %} + #} +
  • + +
  • + {# + {% endif %} + #} +
+
+
+ + {% + if calendar.comment.comment is not empty + or calendar.users|length > 0 + or calendar.thirdParties|length > 0 + or calendar.users|length > 0 + %} +
+
+ + {% include 'ChillActivityBundle:Activity:concernedGroups.html.twig' with {'context': accompanyingCourse, 'with_display': 'row', 'entity': calendar } %} +
+ + {% if calendar.comment.comment is not empty %} +
+ {{ calendar.comment|chill_entity_render_box( { 'limit_lines': 3, 'metadata': false } ) }} +
+ {% endif %} +
+ {% endif %} + +
+ {% endfor %} + + {% if calendarItems|length < paginator.getTotalItems %} + {{ chill_pagination(paginator) }} + {% endif %} + +
+{% endif %} + + + + +{% endblock %} \ No newline at end of file diff --git a/src/Bundle/ChillCalendarBundle/Resources/views/Calendar/listByUser.html.twig b/src/Bundle/ChillCalendarBundle/Resources/views/Calendar/listByUser.html.twig index 004000fc6..055d967a7 100644 --- a/src/Bundle/ChillCalendarBundle/Resources/views/Calendar/listByUser.html.twig +++ b/src/Bundle/ChillCalendarBundle/Resources/views/Calendar/listByUser.html.twig @@ -5,7 +5,10 @@ {% block title %}{{ 'My calendar list' |trans }}{% endblock title %} {% block content %} - {% include 'ChillCalendarBundle:Calendar:list.html.twig' with {'context': 'user'} %} + +

{{ 'My calendar list' |trans }}

+
+ {% endblock %} {% block js %} diff --git a/src/Bundle/ChillCalendarBundle/Resources/views/Calendar/newAccompanyingCourse.html.twig b/src/Bundle/ChillCalendarBundle/Resources/views/Calendar/newByAccompanyingCourse.html.twig similarity index 100% rename from src/Bundle/ChillCalendarBundle/Resources/views/Calendar/newAccompanyingCourse.html.twig rename to src/Bundle/ChillCalendarBundle/Resources/views/Calendar/newByAccompanyingCourse.html.twig diff --git a/src/Bundle/ChillCalendarBundle/Resources/views/Calendar/show.html.twig b/src/Bundle/ChillCalendarBundle/Resources/views/Calendar/show.html.twig index 971abe67d..e8830a33d 100644 --- a/src/Bundle/ChillCalendarBundle/Resources/views/Calendar/show.html.twig +++ b/src/Bundle/ChillCalendarBundle/Resources/views/Calendar/show.html.twig @@ -65,13 +65,21 @@ diff --git a/src/Bundle/ChillPersonBundle/Resources/views/Person/banner_custom.html.twig b/src/Bundle/ChillPersonBundle/Resources/views/Person/banner_custom.html.twig index d01a53c22..564f1803e 100644 --- a/src/Bundle/ChillPersonBundle/Resources/views/Person/banner_custom.html.twig +++ b/src/Bundle/ChillPersonBundle/Resources/views/Person/banner_custom.html.twig @@ -17,11 +17,11 @@ {%- endif -%}
- {%- if chill_person.fields.spoken_languages == 'visible' -%} + {% if person|chill_resolve_center is not null%} {{ 'Center'|trans|upper}} : - {{ person.center.name|upper }} + {{ person|chill_resolve_center.name|upper }} {%- endif -%}
diff --git a/src/Bundle/ChillPersonBundle/Resources/views/Person/create.html.twig b/src/Bundle/ChillPersonBundle/Resources/views/Person/create.html.twig index 7056f25a3..bd1042dd7 100644 --- a/src/Bundle/ChillPersonBundle/Resources/views/Person/create.html.twig +++ b/src/Bundle/ChillPersonBundle/Resources/views/Person/create.html.twig @@ -76,10 +76,9 @@ {{ form_row(form.gender, { 'label' : 'Gender'|trans }) }} -
- {# TODO remove this field (vendee) #} - {{ form_row(form.center, { 'label' : 'Center'|trans }) }} -
+ {% if form.center is defined %} + {{ form_row(form.center) }} + {% endif %}