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 %}
{{ 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 %}
-
+
- {{ "There is no calendar items."|trans }}
-
-
-
- {{ calendar.endDate.diff(calendar.startDate)|date("%H:%M")}}
-
+ {{ "There is no calendar items."|trans }}
+
+ {{ 'My calendar list' |trans }}
-{% else %}
- {{ 'Calendar list' |trans }}
-{% endif %}
-
-{% if context == 'user' %}
-
-{% else %}
-
- {% if calendarItems|length == 0 %}
- {{ "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') }}
-
-
- {% if calendar.user %}
-
-
- {{ 'Calendar list' |trans }}
+
+{% if calendarItems|length == 0 %}
+ {{ '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 -%}