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/phpunit.xml.dist b/phpunit.xml.dist index 25c5f0ff0..5655e23c8 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -10,7 +10,7 @@ - + @@ -31,6 +31,11 @@ src/Bundle/ChillPersonBundle/Tests/Controller/PersonDuplicateControllerViewTest.php + + src/Bundle/ChillAsideActivityBundle/src/Tests/ + + src/Bundle/ChillCalendarBundle/Tests/ + 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 906a28d78..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') }} @@ -47,68 +47,12 @@

-
    - {% if activity.user and t.userVisible %} -
  • - ref: - {{ activity.user.usernameCanonical }} -
  • - {% endif %} +
    +
    -
  • - {{ activity.type.name | localize_translatable_string }} - - {% if activity.attendee is not null and t.attendeeVisible %} - {% if activity.attendee %} - {{ '→ ' ~ 'present'|trans|capitalize }} - {% else %} - {{ '→ ' ~ 'not present'|trans|capitalize }} - {% endif %} - {% endif %} -
  • - -
  • - {{ 'location'|trans ~ ': ' }} - Domicile de l'usager - {# TODO - {% if activity.location %}{{ activity.location }}{% endif %} - #} -
  • - - {%- if t.reasonsVisible -%} -
  • - {%- if activity.reasons is not empty -%} - {% for r in activity.reasons %} - {{ r|chill_entity_render_box }} - {% endfor %} - {%- endif -%} -
  • - {% endif %} - - {%- if t.socialIssuesVisible %} - - {% endif %} - - {%- if t.socialActionsVisible -%} - - {% endif %} - - -
-
    +
    +
    • @@ -134,6 +78,77 @@ #} {% endif %}
    +
    + +
      + + {% if activity.user and t.userVisible %} +
    • + ref: + {{ activity.user.usernameCanonical }} +
    • + {% endif %} + +
    • + {{ activity.type.name | localize_translatable_string }} + + {% if activity.attendee is not null and t.attendeeVisible %} + {% if activity.attendee %} + {{ '→ ' ~ 'present'|trans|capitalize }} + {% else %} + {{ '→ ' ~ 'not present'|trans|capitalize }} + {% endif %} + {% endif %} +
    • + +
    • + {{ 'location'|trans ~ ': ' }} + Domicile de l'usager + + {# TODO {% if activity.location %}{{ activity.location }}{% endif %} + #} + +
    • + + {%- if t.reasonsVisible -%} +
    • + {%- if activity.reasons is not empty -%} + {% for r in activity.reasons %} + {{ r|chill_entity_render_box }} + {% endfor %} + {%- endif -%} +
    • + {% endif %} + + {%- if t.socialIssuesVisible %} + + {% endif %} + + {%- if t.socialActionsVisible -%} + + {% endif %} + + +
    + + + + +
+ + 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 f46e80cd2..1f54692d9 100644 --- a/src/Bundle/ChillActivityBundle/Resources/views/Activity/show.html.twig +++ b/src/Bundle/ChillActivityBundle/Resources/views/Activity/show.html.twig @@ -64,16 +64,26 @@ {% 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 %} -
{{ 'Comment'|trans }}
+
{{ 'activity.comment'|trans }}
{%- if entity.comment.empty -%}
{{ 'No comment associated'|trans }}
{%- else -%} @@ -120,17 +130,17 @@ {{ 'Edit'|trans }}
- + {# TODO {% if is_granted('CHILL_ACTIVITY_DELETE', entity) %} #} - +
  • {{ 'Delete'|trans }}
  • - + {# {% endif %} #} 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/ChillAsideActivityBundle/src/Controller/AdminController.php b/src/Bundle/ChillAsideActivityBundle/src/Controller/AdminController.php new file mode 100644 index 000000000..ae04b2110 --- /dev/null +++ b/src/Bundle/ChillAsideActivityBundle/src/Controller/AdminController.php @@ -0,0 +1,20 @@ + + * @author Champs Libres + */ +class AdminController extends AbstractController +{ + + public function redirectToAdminIndexAction() + { + return $this->redirectToRoute('chill_main_admin_central'); + } +} \ No newline at end of file diff --git a/src/Bundle/ChillAsideActivityBundle/src/Controller/AsideActivityController.php b/src/Bundle/ChillAsideActivityBundle/src/Controller/AsideActivityController.php index 55af4a6ab..88e53c8d2 100644 --- a/src/Bundle/ChillAsideActivityBundle/src/Controller/AsideActivityController.php +++ b/src/Bundle/ChillAsideActivityBundle/src/Controller/AsideActivityController.php @@ -1,14 +1,41 @@ andWhere($qb->expr()->eq('e.agent', ':user')); + $qb->setParameter('user', $this->getUser()); + } + + return $qb; + } + + protected function orderQuery( + string $action, + $query, + Request $request, + PaginatorInterface $paginator + ) { + if ('index' === $action) { + return $query->orderBy('e.date', 'DESC'); + } + + return parent::orderQuery($action, $query, $request, $paginator); + } + + } diff --git a/src/Bundle/ChillAsideActivityBundle/src/DataFixtures/ORM/LoadAsideActivity.php b/src/Bundle/ChillAsideActivityBundle/src/DataFixtures/ORM/LoadAsideActivity.php new file mode 100644 index 000000000..2764db282 --- /dev/null +++ b/src/Bundle/ChillAsideActivityBundle/src/DataFixtures/ORM/LoadAsideActivity.php @@ -0,0 +1,53 @@ +userRepository = $userRepository; + } + + public function getDependencies(): array + { + return [ + LoadUsers::class, + LoadAsideActivityCategory::class + ]; + } + + public function load(ObjectManager $manager) + { + $user = $this->userRepository->findOneBy(['username' => 'center a_social']); + + for ($i = 0; $i < 50; $i++) { + $activity = new AsideActivity(); + $activity + ->setAgent($user) + ->setCreatedAt(new \DateTimeImmutable('now')) + ->setCreatedBy($user) + ->setUpdatedAt(new \DateTimeImmutable('now')) + ->setUpdatedBy($user) + ->setType( + $this->getReference('aside_activity_category_0') + ) + ->setDate((new \DateTimeImmutable('today')) + ->sub(new \DateInterval('P'.\random_int(1, 100).'D'))) + ; + + $manager->persist($activity); + } + + $manager->flush(); + } +} diff --git a/src/Bundle/ChillAsideActivityBundle/src/DataFixtures/ORM/LoadAsideActivityCategory.php b/src/Bundle/ChillAsideActivityBundle/src/DataFixtures/ORM/LoadAsideActivityCategory.php new file mode 100644 index 000000000..497b260ad --- /dev/null +++ b/src/Bundle/ChillAsideActivityBundle/src/DataFixtures/ORM/LoadAsideActivityCategory.php @@ -0,0 +1,24 @@ + $label) { + $category = new AsideActivityCategory(); + $category->setTitle(['fr' => $label]); + $manager->persist($category); + $this->setReference('aside_activity_category_'.$key, $category); + } + + $manager->flush(); + } +} diff --git a/src/Bundle/ChillAsideActivityBundle/src/DependencyInjection/ChillAsideActivityExtension.php b/src/Bundle/ChillAsideActivityBundle/src/DependencyInjection/ChillAsideActivityExtension.php index bbc4c8f8b..712649653 100644 --- a/src/Bundle/ChillAsideActivityBundle/src/DependencyInjection/ChillAsideActivityExtension.php +++ b/src/Bundle/ChillAsideActivityBundle/src/DependencyInjection/ChillAsideActivityExtension.php @@ -25,8 +25,9 @@ final class ChillAsideActivityExtension extends Extension implements PrependExte public function load(array $configs, ContainerBuilder $container): void { $loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__.'/../config')); - // $loader->load('services.yaml'); + $loader->load('services.yaml'); $loader->load('services/form.yaml'); + $loader->load('services/menu.yaml'); } public function prepend(ContainerBuilder $container) diff --git a/src/Bundle/ChillAsideActivityBundle/src/Entity/AsideActivity.php b/src/Bundle/ChillAsideActivityBundle/src/Entity/AsideActivity.php index 92478d787..af4baa6d3 100644 --- a/src/Bundle/ChillAsideActivityBundle/src/Entity/AsideActivity.php +++ b/src/Bundle/ChillAsideActivityBundle/src/Entity/AsideActivity.php @@ -200,10 +200,4 @@ class AsideActivity implements TrackUpdateInterface, TrackCreationInterface return $this; } - - // public function __toString() - // { - // // dump($this->type->getTitle()); - // return $this->type->getTitle(); - // } } diff --git a/src/Bundle/ChillAsideActivityBundle/src/Form/AsideActivityFormType.php b/src/Bundle/ChillAsideActivityBundle/src/Form/AsideActivityFormType.php index 2dcce3baa..4bf5bbfdc 100644 --- a/src/Bundle/ChillAsideActivityBundle/src/Form/AsideActivityFormType.php +++ b/src/Bundle/ChillAsideActivityBundle/src/Form/AsideActivityFormType.php @@ -8,7 +8,9 @@ use Chill\MainBundle\Entity\User; use Chill\MainBundle\Form\Type\ChillDateType; use Chill\MainBundle\Form\Type\ChillTextareaType; use Chill\MainBundle\Templating\TranslatableStringHelper; +use Doctrine\ORM\EntityRepository; use Symfony\Bridge\Doctrine\Form\Type\EntityType; +use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToTimestampTransformer; use Symfony\Component\Form\Extension\Core\Type\ChoiceType; @@ -26,8 +28,12 @@ final class AsideActivityFormType extends AbstractType private TranslatableStringHelper $translatableStringHelper; private TokenStorageInterface $storage; - public function __construct (TranslatableStringHelper $translatableStringHelper, array $timeChoices, TokenStorageInterface $storage){ - $this->timeChoices = $timeChoices; + public function __construct ( + TranslatableStringHelper $translatableStringHelper, + ParameterBagInterface $parameterBag, + TokenStorageInterface $storage + ){ + $this->timeChoices = $parameterBag->get('chill_activity.form.time_duration'); $this->translatableStringHelper = $translatableStringHelper; $this->storage = $storage; } @@ -47,12 +53,15 @@ final class AsideActivityFormType extends AbstractType ]; $builder - ->add('agent', EntityType::class, + ->add('agent', EntityType::class, [ 'label' => 'Agent', 'required' => true, 'class' => User::class, 'data' => $this->storage->getToken()->getUser(), + 'query_builder' => function(EntityRepository $er){ + return $er->createQueryBuilder('u')->where('u.enabled = true'); + }, 'attr' => array('class' => 'select2 '), 'placeholder' => 'Choose the agent for whom this activity is created', 'choice_label' => 'username' @@ -85,7 +94,7 @@ final class AsideActivityFormType extends AbstractType 'required' => false, ]); - foreach (['duration'] as $fieldName) + foreach (['duration'] as $fieldName) { $builder->get($fieldName) ->addModelTransformer($durationTimeTransformer); @@ -108,7 +117,6 @@ final class AsideActivityFormType extends AbstractType $seconds = $data->getTimezone()->getOffset($data); $data->setTimeZone($timezoneUTC); $data->add(new \DateInterval('PT'.$seconds.'S')); - dump($data); // test if the timestamp is in the choices. // If not, recreate the field with the new timestamp @@ -131,7 +139,7 @@ final class AsideActivityFormType extends AbstractType public function configureOptions(OptionsResolver $resolver): void { $resolver->setDefaults([ - 'data_class' => AsideActivity::class, + 'data_class' => AsideActivity::class, ]); } @@ -139,4 +147,4 @@ final class AsideActivityFormType extends AbstractType { return 'chill_asideactivitybundle_asideactivity'; } -} \ No newline at end of file +} diff --git a/src/Bundle/ChillAsideActivityBundle/src/Menu/SectionMenuBuilder.php b/src/Bundle/ChillAsideActivityBundle/src/Menu/SectionMenuBuilder.php new file mode 100644 index 000000000..b9776b92b --- /dev/null +++ b/src/Bundle/ChillAsideActivityBundle/src/Menu/SectionMenuBuilder.php @@ -0,0 +1,46 @@ +translator = $translator; + } + + /** + * @param $menuId + * @param MenuItem $menu + * @param array $parameters + */ + public function buildMenu($menuId, MenuItem $menu, array $parameters) + { + $menu->addChild($this->translator->trans('Create an aside activity'), [ + 'route' => 'chill_crud_aside_activity_new' + ]) + ->setExtras([ + 'order' => 11, + 'icons' => [ 'plus' ] + ]); + } + + /** + * @return array + */ + public static function getMenuIds(): array + { + return [ 'section' ]; + } +} diff --git a/src/Bundle/ChillAsideActivityBundle/src/Resources/views/Admin/layout_asideactivity.html.twig b/src/Bundle/ChillAsideActivityBundle/src/Resources/views/Admin/layout_asideactivity.html.twig new file mode 100644 index 000000000..f0242c085 --- /dev/null +++ b/src/Bundle/ChillAsideActivityBundle/src/Resources/views/Admin/layout_asideactivity.html.twig @@ -0,0 +1,14 @@ +{% extends "@ChillMain/Admin/layoutWithVerticalMenu.html.twig" %} + +{% block vertical_menu_content %} + {{ chill_menu('admin_aside_activity', { + 'layout': '@ChillAsideActivity/Admin/menu_asideactivity.html.twig', + }) }} +{% endblock %} + +{% block layout_wvm_content %} + {% block admin_content %} + +

    {{ 'Aside activity configuration' |trans }}

    + {% endblock %} +{% endblock %} diff --git a/src/Bundle/ChillAsideActivityBundle/src/Resources/views/Admin/menu_asideactivity.html.twig b/src/Bundle/ChillAsideActivityBundle/src/Resources/views/Admin/menu_asideactivity.html.twig new file mode 100644 index 000000000..34ea309e5 --- /dev/null +++ b/src/Bundle/ChillAsideActivityBundle/src/Resources/views/Admin/menu_asideactivity.html.twig @@ -0,0 +1,4 @@ +{% extends "@ChillMain/Menu/verticalMenu.html.twig" %} +{% block v_menu_title %} + {{ 'Aside activity configuration menu'|trans }} +{% endblock %} diff --git a/src/Bundle/ChillAsideActivityBundle/src/Resources/views/asideActivity/_delete.html.twig b/src/Bundle/ChillAsideActivityBundle/src/Resources/views/asideActivity/_delete.html.twig index fdbccb838..2f30b14cf 100644 --- a/src/Bundle/ChillAsideActivityBundle/src/Resources/views/asideActivity/_delete.html.twig +++ b/src/Bundle/ChillAsideActivityBundle/src/Resources/views/asideActivity/_delete.html.twig @@ -1,37 +1,30 @@
    - {% block crud_content_header %} -

    {{ ('crud.'~crud_name~'.title_delete')|trans({ '%as_string%': 'Aside Activity' }) }}

    - {% endblock crud_content_header %} + {% block crud_content_header %} +

    {{ ('crud.'~crud_name~'.title_delete')|trans({ '%as_string%': 'Aside Activity' }) }}

    + {% endblock crud_content_header %} -

    {{ ('crud.'~crud_name~'.confirm_message_delete')|trans({ '%as_string%': 'Aside Activity' }) }}

    +

    {{ ('crud.'~crud_name~'.confirm_message_delete')|trans({ '%as_string%': 'Aside Activity' }) }}

    - {{ form_start(form) }} + {{ form_start(form) }} -
      - {% block content_form_actions_back %} -
    • - - {{ 'Cancel'|trans }} - -
    • - {% endblock %} - {% block content_form_actions_before %}{% endblock %} - {% block content_form_actions_view %} - {% if is_granted(chill_crud_config('role', crud_name, 'view'), entity) %} -
    • - - {{ 'crud.edit.back_to_view'|trans }} - -
    • - {% endif %} - {% endblock %} - {% block content_form_actions_confirm_delete %} -
    • - -
    • - {% endblock content_form_actions_confirm_delete %} - {% block content_form_actions_after %}{% endblock %} -
    +
      + {% block content_form_actions_back %} +
    • + + {{ 'Cancel'|trans }} + +
    • + {% endblock %} + {% block content_form_actions_before %} - {{ form_end(form) }} -
    \ No newline at end of file + {% endblock %} + {% block content_form_actions_confirm_delete %} +
  • + +
  • + {% endblock content_form_actions_confirm_delete %} + {% block content_form_actions_after %}{% endblock %} + + + {{ form_end(form) }} +
    diff --git a/src/Bundle/ChillAsideActivityBundle/src/Resources/views/asideActivity/delete.html.twig b/src/Bundle/ChillAsideActivityBundle/src/Resources/views/asideActivity/delete.html.twig index 9c8a1ff59..4dd0a8415 100644 --- a/src/Bundle/ChillAsideActivityBundle/src/Resources/views/asideActivity/delete.html.twig +++ b/src/Bundle/ChillAsideActivityBundle/src/Resources/views/asideActivity/delete.html.twig @@ -3,6 +3,6 @@ {# {% block title %}{{ ('crud.' ~ crud_name ~ '.delete.title')|trans({'%crud_name%': crud_name}) }}{% endblock %} #} {% block content %} - {% embed '@ChillAsideActivity/AsideActivity/_delete.html.twig' %} + {% embed '@ChillAsideActivity/asideActivity/_delete.html.twig' %} {% endembed %} -{% endblock content %} \ No newline at end of file +{% endblock content %} diff --git a/src/Bundle/ChillAsideActivityBundle/src/Resources/views/asideActivity/edit.html.twig b/src/Bundle/ChillAsideActivityBundle/src/Resources/views/asideActivity/edit.html.twig index b0b6fed0f..ad0a38f73 100644 --- a/src/Bundle/ChillAsideActivityBundle/src/Resources/views/asideActivity/edit.html.twig +++ b/src/Bundle/ChillAsideActivityBundle/src/Resources/views/asideActivity/edit.html.twig @@ -9,5 +9,6 @@ {% embed '@ChillMain/CRUD/_edit_content.html.twig' %} {# we do not have "view" page. We empty the corresponding block #} {% block content_form_actions_view %}{% endblock %} + {% block content_form_actions_save_and_show %}{% endblock %} {% endembed %} {% endblock %} diff --git a/src/Bundle/ChillAsideActivityBundle/src/Resources/views/asideActivity/list.html.twig b/src/Bundle/ChillAsideActivityBundle/src/Resources/views/asideActivity/list.html.twig index 4d2bb9110..d0e02e3d2 100644 --- a/src/Bundle/ChillAsideActivityBundle/src/Resources/views/asideActivity/list.html.twig +++ b/src/Bundle/ChillAsideActivityBundle/src/Resources/views/asideActivity/list.html.twig @@ -1,107 +1,97 @@ {% extends "@ChillMain/layout.html.twig" %} -{% block title %}{{ 'Aside activity list' |trans }}{% endblock title %} +{% block title %} + {{ 'Aside activity list' |trans }} +{% endblock title %} {% block content %} -
    -

    {{ 'My aside activities' |trans }}

    +
    +

    {{ 'My aside activities' |trans }}

    -{% if entities|length == 0 %} -

    - {{ "There aren't any aside activities."|trans }} - -

    -{% else %} + {% if entities|length == 0 %} +

    + {{ "There aren't any aside activities."|trans }} + +

    + {% else %} -
    - {# Sort activities according to date in descending order #} - {% for entity in entities|sort ((a, b) => b.date <=> a.date) %} - {% set t = entity.type %} +
    + {# Sort activities according to date in descending order #} + {% for entity in entities %} + {% set t = entity.type %} - {# only load aside activities of current user. #} - {% if entity.agent == app.user %} +
    +
    +
    -
    -
    -
    +

    + {{ entity.type.title | localize_translatable_string }} +

    -

    - {{ entity.type.title | localize_translatable_string }} -

    + {% if entity.date %} +

    {{ entity.date|format_date('long') }}

    + {% endif %} - {% if entity.date %} -

    {{ entity.date|format_date('long') }}

    - {% endif %} +
    +

    + + {{ entity.duration|date('H:i') }} +

    +
    -
    -

    - - {{ entity.duration|date('H:i') }} -

    -
    +
    +
    +
      + {% if entity.createdBy %} +
    • + {{ 'Created by: '|trans }}{{ entity.createdBy.usernameCanonical }} +
    • + {% endif %} +
    +
    +
    -
    -
    -
      - {% if entity.createdBy %} -
    • - {{ 'Created by: '|trans }}{{ entity.createdBy.usernameCanonical }} -
    • - {% endif %} -
    -
    -
    + {# {% + if entity.note is not empty + or entity.createdBy|length > 0 + %} +
    + {% if entity.note is not empty %} +
    + {{ entity.note|chill_markdown_to_html }} +
    + {% endif %} - {# {% - if entity.note is not empty - or entity.createdBy|length > 0 - %} -
    - {% if entity.note is not empty %} -
    - {{ entity.note|chill_markdown_to_html }} -
    - {% endif %} +
    + {% endif %} #} +
    +
      +
        +
      • + +
      • +
      • + +
      • +
      +
    +
    +
    -
    - {% endif %} #} -
    -
      -
        - {#
      • - -
      • #} - {# TOOD - {% if is_granted('CHILL_ACTIVITY_UPDATE', activity) %} - #} -
      • - -
      • - {# TOOD - {% endif %} - {% if is_granted('CHILL_ACTIVITY_DELETE', activity) %} - #} -
      • - -
      • - {# - {% endif %} - #} -
      -
    -
    -
    - {% endif %} - {% endfor %} -
    - -
    -{% endif %} + {% endfor %} +
    + + {{ chill_pagination(paginator) }} + + +
    + {% endif %} {% endblock %} diff --git a/src/Bundle/ChillAsideActivityBundle/src/Resources/views/asideActivityCategory/index.html.twig b/src/Bundle/ChillAsideActivityBundle/src/Resources/views/asideActivityCategory/index.html.twig index e9d5a0869..d8cb67716 100644 --- a/src/Bundle/ChillAsideActivityBundle/src/Resources/views/asideActivityCategory/index.html.twig +++ b/src/Bundle/ChillAsideActivityBundle/src/Resources/views/asideActivityCategory/index.html.twig @@ -1,44 +1,44 @@ -{% extends "@ChillActivity/Admin/layout_activity.html.twig" %} +{% extends "@ChillAsideActivity/Admin/layout_asideactivity.html.twig" %} {% block admin_content %} -

    {{ 'ActivityType list'|trans }}

    +

    {{ 'ActivityType list'|trans }}

    - - - - - - - - - - {% for entity in entities %} - - - - - - {% endfor %} - -
    {{ 'Name'|trans }}{{ 'Active'|trans }}{{ 'Actions'|trans }}
    {{ entity.title|localize_translatable_string }} - {%- if entity.isActive -%} - - {%- else -%} - - {%- endif -%} - -
      -
    • - -
    • -
    -
    + + + + + + + + + + {% for entity in entities %} + + + + + + {% endfor %} + +
    {{ 'Name'|trans }}{{ 'Active'|trans }}{{ 'Actions'|trans }}
    {{ entity.title|localize_translatable_string }} + {%- if entity.isActive -%} + + {%- else -%} + + {%- endif -%} + +
      +
    • + +
    • +
    +
    - - {% endblock %} + +{% endblock %} diff --git a/src/Bundle/ChillAsideActivityBundle/src/Tests/Controller/AsideActivityControllerTest.php b/src/Bundle/ChillAsideActivityBundle/src/Tests/Controller/AsideActivityControllerTest.php new file mode 100644 index 000000000..301da25e1 --- /dev/null +++ b/src/Bundle/ChillAsideActivityBundle/src/Tests/Controller/AsideActivityControllerTest.php @@ -0,0 +1,73 @@ +client = $this->getClientAuthenticated(); + } + + public function testIndexWithoutUsers() + { + $this->client->request('GET', '/fr/asideactivity'); + + $this->assertEquals(200, $this->client->getResponse()->getStatusCode()); + } + + public function testNewWithoutUsers() + { + $this->client->request('GET', '/fr/asideactivity/new'); + + $this->assertEquals(200, $this->client->getResponse()->getStatusCode()); + } + + /** + * @dataProvider generateAsideActivityId + */ + + public function testEditWithoutUsers(int $asideActivityId) + { + $this->client->request('GET', "/fr/asideactivity/{$asideActivityId}/edit"); + + $this->assertEquals(200, $this->client->getResponse()->getStatusCode()); + } + + public function generateAsideActivityId() + { + self::bootKernel(); + + $qb = self::$container->get(EntityManagerInterface::class) + ->createQueryBuilder(); + + $asideActivityIds = $qb + ->select('DISTINCT asideactivity.id') + ->from(AsideActivity::class, 'asideactivity') + ->innerJoin('asideactivity.agent', 'agent') + ->where($qb->expr()->eq('agent.username', ':center_name')) + ->setParameter('center_name', 'center a_social') + ->setMaxResults(100) + ->getQuery() + ->getResult() + ; + + \shuffle($asideActivityIds); + + yield [ \array_pop($asideActivityIds)['id'] ]; + yield [ \array_pop($asideActivityIds)['id'] ]; + yield [ \array_pop($asideActivityIds)['id'] ]; + } + + +} \ No newline at end of file diff --git a/src/Bundle/ChillAsideActivityBundle/src/config/routes.yaml b/src/Bundle/ChillAsideActivityBundle/src/config/routes.yaml index 622102e34..ddb635084 100644 --- a/src/Bundle/ChillAsideActivityBundle/src/config/routes.yaml +++ b/src/Bundle/ChillAsideActivityBundle/src/config/routes.yaml @@ -1,3 +1,12 @@ chill_asideactivities_controllers: - resource: "@ChillAsideActivityBundle/Controller" - type: annotation \ No newline at end of file + resource: "@ChillAsideActivityBundle/Controller" + type: annotation + +chill_admin_aside_activity_redirect_to_admin_index: + path: /{_locale}/admin/activity_redirect_to_main + controller: Chill\ActivityBundle\Controller\AdminController::redirectToAdminIndexAction + options: + menus: + admin_aside_activity: + order: 0 + label: Main admin menu diff --git a/src/Bundle/ChillAsideActivityBundle/src/config/services.yaml b/src/Bundle/ChillAsideActivityBundle/src/config/services.yaml index 727efaa38..8ff656fdc 100644 --- a/src/Bundle/ChillAsideActivityBundle/src/config/services.yaml +++ b/src/Bundle/ChillAsideActivityBundle/src/config/services.yaml @@ -1,8 +1,5 @@ -# services: -# chill.asideactivity.form.type.asideactivity: -# class: Chill\AsideActivityBundle\Form\AsideActivityFormType -# arguments: -# - "@chill.main.helper.translatable_string" -# # - "%chill_activity.form.time_duration%" -# tags: -# - { name: form.type, alias: chill_asideactivitybundle_asideactivity } \ No newline at end of file +services: + Chill\AsideActivityBundle\DataFixtures\: + resource: './../DataFixtures' + autowire: true + autoconfigure: true diff --git a/src/Bundle/ChillAsideActivityBundle/src/config/services/form.yaml b/src/Bundle/ChillAsideActivityBundle/src/config/services/form.yaml index ceb9dffc7..23c614937 100644 --- a/src/Bundle/ChillAsideActivityBundle/src/config/services/form.yaml +++ b/src/Bundle/ChillAsideActivityBundle/src/config/services/form.yaml @@ -1,10 +1,6 @@ --- services: - chill.asideactivity.form.type.asideactivity: - class: Chill\AsideActivityBundle\Form\AsideActivityFormType - arguments: - - "@chill.main.helper.translatable_string" - - "%chill_activity.form.time_duration%" - - "@security.token_storage" - tags: - - { name: form.type, alias: chill_asideactivitybundle_asideactivity } \ No newline at end of file + Chill\AsideActivityBundle\Form\: + resource: './../../Form' + autowire: true + autoconfigure: true diff --git a/src/Bundle/ChillAsideActivityBundle/src/config/services/menu.yaml b/src/Bundle/ChillAsideActivityBundle/src/config/services/menu.yaml new file mode 100644 index 000000000..614391a0e --- /dev/null +++ b/src/Bundle/ChillAsideActivityBundle/src/config/services/menu.yaml @@ -0,0 +1,5 @@ +services: + Chill\AsideActivityBundle\Menu\: + resource: './../../Menu' + autowire: true + autoconfigure: true diff --git a/src/Bundle/ChillAsideActivityBundle/src/translations/messages.fr.yml b/src/Bundle/ChillAsideActivityBundle/src/translations/messages.fr.yml index f01fd07fa..fa62befea 100644 --- a/src/Bundle/ChillAsideActivityBundle/src/translations/messages.fr.yml +++ b/src/Bundle/ChillAsideActivityBundle/src/translations/messages.fr.yml @@ -1,9 +1,10 @@ #general Show the aside activity: Voir l'activité annexe Edit the aside activity: Modifier l'activité annexe +Remove aside activity: Supprimer l'activité annexe Aside activity: Activité annexe Duration time: Durée -durationTime: durée +durationTime: durée user_username: nom de l'utilisateur Remark: Commentaire No comments: Aucun commentaire @@ -25,7 +26,7 @@ Required: Obligatoire Persons: Personnes Users: Utilisateurs Emergency: Urgent -by: 'Par ' +by: "Par " location: Lieu # Crud @@ -33,50 +34,42 @@ crud: aside_activity: title_view: Détail de l'activité annexe title_new: Nouvelle activité annexe - title_edit: Edition d'une activité annexe - title_delete: Supprimation d'une activité annexe + title_edit: Édition d'une activité annexe + title_delete: Supprimer une activité annexe button_delete: Supprimer - confirm_message_delete: Êtes-vous sûr de vouloir supprimer cet activité annexe? + confirm_message_delete: Êtes-vous sûr de vouloir supprimer cette activité annexe? aside_activity_category: title_new: Nouvelle catégorie d'activité annexe title_edit: Edition d'une catégorie de type d'activité #forms Activity creation: Nouvelle activité annexe -Create a new aside activity type: Nouvelle categorie d'activité annexe +Create a new aside activity type: Nouvelle catégorie d'activité annexe Create: Créer Back to the list: Retour à la liste Save activity: Sauver l'activité Reset form: Remise à zéro du formulaire -Choose the agent for whom this activity is created: Choissisez l'utilisateur pour qui l'activité est creéé. -Choose the activity category: Choissisez le type d'activité +Choose the agent for whom this activity is created: Choisissez l'utilisateur pour qui l'activité est créée. +Choose the activity category: Choisissez le type d'activité Choose the duration: Choisir la durée -Choose a category: Choisir un categorie +Choose a category: Choisir une catégorie Is active: Actif Agent: Utilisateur date: Date Duration: Durée Note: Note -5 minutes: 5 minutes -10 minutes: 10 minutes -15 minutes: 15 minutes -20 minutes: 20 minutes -25 minutes: 25 minutes -30 minutes: 30 minutes -45 minutes: 45 minutes -1 hour: 1 heure -1 hour 15: 1 heure 15 -1 hour 30: 1 heure 30 -1 hour 45: 1 heure 45 -2 hours: 2 heures #list My aside activities: Mes activités annexes Date: Date -Created by: Creér par - +Created by: Créée par #Aside activity delete Delete aside activity: Supprimer une activité annexe Are you sure you want to remove the aside activity concerning "%name%" ?: Êtes-vous sûr de vouloir supprimer une activité annexe qui concerne "%name%" ? The activity has been successfully removed.: L'activité a été supprimée. + +#Menu +Create an aside activity: "Créer une activité annexe" +Aside activity configuration menu: "Menu de configuration des activités annexes" +Aside activity configuration: "Configuration des activités annexes" diff --git a/src/Bundle/ChillCalendarBundle/Controller/CalendarController.php b/src/Bundle/ChillCalendarBundle/Controller/CalendarController.php index a894099e4..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,55 +57,71 @@ 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; } /** * Lists all Calendar entities. - * @Route("/{_locale}/calendar/", name="chill_calendar_calendar") + * @Route("/{_locale}/calendar/calendar/", name="chill_calendar_calendar_list") */ public function listAction(Request $request): Response { - $em = $this->getDoctrine()->getManager(); $view = null; [$user, $accompanyingPeriod] = $this->getEntity($request); if ($user instanceof User) { - // $calendar = $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 + ]); - // $view = 'ChillCalendarBundle:Calendar:listByUser.html.twig'; } 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 = 'ChillCalendarBundle:Calendar:listByAccompanyingCourse.html.twig'; - } + $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 + ]); + } } /** * Create a new calendar item - * @Route("/{_locale}/calendar/new", name="chill_calendar_calendar_new") + * @Route("/{_locale}/calendar/calendar/new", name="chill_calendar_calendar_new") */ public function newAction(Request $request): Response { @@ -112,10 +130,10 @@ class CalendarController extends AbstractController [$user, $accompanyingPeriod] = $this->getEntity($request); if ($accompanyingPeriod instanceof AccompanyingPeriod) { - $view = 'ChillCalendarBundle:Calendar:newAccompanyingCourse.html.twig'; + $view = '@ChillCalendar/Calendar/newByAccompanyingCourse.html.twig'; } // elseif ($user instanceof User) { - // $view = 'ChillCalendarBundle:Calendar:newUser.html.twig'; + // $view = '@ChillCalendar/Calendar/newUser.html.twig'; // } $entity = new Calendar(); @@ -142,7 +160,7 @@ class CalendarController extends AbstractController $params = $this->buildParamsToUrl($user, $accompanyingPeriod); - return $this->redirectToRoute('chill_calendar_calendar', $params); + return $this->redirectToRoute('chill_calendar_calendar_list', $params); } elseif ($form->isSubmitted() and !$form->isValid()) { $this->addFlash('error', $this->get('translator')->trans('This form contains errors')); } @@ -165,7 +183,7 @@ class CalendarController extends AbstractController /** * Show a calendar item - * @Route("/{_locale}/calendar/{id}/show", name="chill_calendar_calendar_show") + * @Route("/{_locale}/calendar/calendar/{id}/show", name="chill_calendar_calendar_show") */ public function showAction(Request $request, $id): Response { @@ -174,11 +192,11 @@ class CalendarController extends AbstractController [$user, $accompanyingPeriod] = $this->getEntity($request); if ($accompanyingPeriod instanceof AccompanyingPeriod) { - $view = 'ChillCalendarBundle:Calendar:showAccompanyingCourse.html.twig'; + $view = '@ChillCalendar/Calendar/showByAccompanyingCourse.html.twig'; } - // elseif ($person instanceof Person) { - // $view = 'ChillCalendarBundle:Calendar:showPerson.html.twig'; - // } + elseif ($user instanceof User) { + $view = '@ChillCalendar/Calendar/showByUser.html.twig'; + } $entity = $em->getRepository('ChillCalendarBundle:Calendar')->find($id); @@ -197,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, [ - //'person' => $person, 'accompanyingCourse' => $accompanyingPeriod, 'entity' => $entity, + 'user' => $user, + 'activityData' => $activityData //'delete_form' => $deleteForm->createView(), ]); } @@ -209,7 +250,7 @@ class CalendarController extends AbstractController /** * Edit a calendar item - * @Route("/{_locale}/calendar/{id}/edit", name="chill_calendar_calendar_edit") + * @Route("/{_locale}/calendar/calendar/{id}/edit", name="chill_calendar_calendar_edit") */ public function editAction($id, Request $request): Response { @@ -218,11 +259,11 @@ class CalendarController extends AbstractController [$user, $accompanyingPeriod] = $this->getEntity($request); if ($accompanyingPeriod instanceof AccompanyingPeriod) { - $view = 'ChillCalendarBundle:Calendar:editAccompanyingCourse.html.twig'; + $view = '@ChillCalendar/Calendar/editByAccompanyingCourse.html.twig'; + } + elseif ($user instanceof User) { + $view = '@ChillCalendar/Calendar/editByUser.html.twig'; } - // elseif ($person instanceof Person) { - // $view = 'ChillCalendarBundle:Calendar:editPerson.html.twig'; - // } $entity = $em->getRepository('ChillCalendarBundle:Calendar')->find($id); @@ -241,7 +282,7 @@ class CalendarController extends AbstractController $this->addFlash('success', $this->get('translator')->trans('Success : calendar item updated!')); $params = $this->buildParamsToUrl($user, $accompanyingPeriod); - return $this->redirectToRoute('chill_calendar_calendar', $params); + return $this->redirectToRoute('chill_calendar_calendar_list', $params); } elseif ($form->isSubmitted() and !$form->isValid()) { $this->addFlash('error', $this->get('translator')->trans('This form contains errors')); } @@ -259,6 +300,7 @@ class CalendarController extends AbstractController 'form' => $form->createView(), 'delete_form' => $deleteForm->createView(), 'accompanyingCourse' => $accompanyingPeriod, + 'user' => $user, 'entity_json' => $entity_array ]); } @@ -274,11 +316,11 @@ class CalendarController extends AbstractController [$user, $accompanyingPeriod] = $this->getEntity($request); if ($accompanyingPeriod instanceof AccompanyingPeriod) { - $view = 'ChillCalendarBundle:Calendar:confirm_deleteAccompanyingCourse.html.twig'; + $view = '@ChillCalendar/Calendar/confirm_deleteByAccompanyingCourse.html.twig'; } - // elseif ($person instanceof Person) { - // $view = 'ChillCalendarBundle:Calendar:confirm_deletePerson.html.twig'; - // } + elseif ($user instanceof User) { + $view = '@ChillCalendar/Calendar/confirm_deleteByUser.html.twig'; + } /* @var $entity Calendar */ $entity = $em->getRepository('ChillCalendarBundle:Calendar')->find($id); @@ -306,7 +348,7 @@ class CalendarController extends AbstractController ->trans("The calendar item has been successfully removed.")); $params = $this->buildParamsToUrl($user, $accompanyingPeriod); - return $this->redirectToRoute('chill_calendar_calendar', $params); + return $this->redirectToRoute('chill_calendar_calendar_list', $params); } } @@ -324,9 +366,9 @@ class CalendarController extends AbstractController /** * Creates a form to delete a Calendar entity by id. */ - private function createDeleteForm(int $id, ?Person $person, ?AccompanyingPeriod $accompanyingPeriod): Form + private function createDeleteForm(int $id, ?User $user, ?AccompanyingPeriod $accompanyingPeriod): Form { - $params = $this->buildParamsToUrl($person, $accompanyingPeriod); + $params = $this->buildParamsToUrl($user, $accompanyingPeriod); $params['id'] = $id; return $this->createFormBuilder() @@ -350,7 +392,8 @@ class CalendarController extends AbstractController throw $this->createNotFoundException('User not found'); } - $this->denyAccessUnlessGranted('CHILL_PERSON_SEE', $user); + // TODO Add permission + // $this->denyAccessUnlessGranted('CHILL_PERSON_SEE', $user); } elseif ($request->query->has('accompanying_period_id')) { $accompanying_period_id = $request->get('accompanying_period_id'); $accompanyingPeriod = $em->getRepository(AccompanyingPeriod::class)->find($accompanying_period_id); diff --git a/src/Bundle/ChillCalendarBundle/Controller/CalendarRangeAPIController.php b/src/Bundle/ChillCalendarBundle/Controller/CalendarRangeAPIController.php index be279ed36..672e9527c 100644 --- a/src/Bundle/ChillCalendarBundle/Controller/CalendarRangeAPIController.php +++ b/src/Bundle/ChillCalendarBundle/Controller/CalendarRangeAPIController.php @@ -18,16 +18,19 @@ class CalendarRangeAPIController extends ApiController */ public function availableRanges(Request $request, string $_format): JsonResponse { - if ($request->query->has('user')) { - $user = $request->query->get('user'); - } - $em = $this->getDoctrine()->getManager(); - $query = $em->createQuery( - 'SELECT c FROM ChillCalendarBundle:CalendarRange c - WHERE NOT EXISTS (SELECT cal.id FROM ChillCalendarBundle:Calendar cal WHERE cal.calendarRange = c.id)') - ; + $sql = 'SELECT c FROM ChillCalendarBundle:CalendarRange c + WHERE NOT EXISTS (SELECT cal.id FROM ChillCalendarBundle:Calendar cal WHERE cal.calendarRange = c.id)'; + + if ($request->query->has('user')) { + $user = $request->query->get('user'); + $sql = $sql . ' AND c.user = :user'; + $query = $em->createQuery($sql) + ->setParameter('user', $user); + } else { + $query = $em->createQuery($sql); + } $results = $query->getResult(); diff --git a/src/Bundle/ChillCalendarBundle/DependencyInjection/ChillCalendarExtension.php b/src/Bundle/ChillCalendarBundle/DependencyInjection/ChillCalendarExtension.php index 2927d4910..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) @@ -66,13 +67,16 @@ class ChillCalendarExtension extends Extension implements PrependExtensionInterf '_index' => [ 'methods' => [ Request::METHOD_GET => true, - Request::METHOD_HEAD => true + Request::METHOD_HEAD => true, ], ], '_entity' => [ 'methods' => [ Request::METHOD_GET => true, - Request::METHOD_HEAD => true + Request::METHOD_HEAD => true, + Request::METHOD_POST => true, + Request::METHOD_PATCH => true, + Request::METHOD_DELETE => true, ] ], ] diff --git a/src/Bundle/ChillCalendarBundle/Entity/Calendar.php b/src/Bundle/ChillCalendarBundle/Entity/Calendar.php index 0663ca08b..b3e5971bd 100644 --- a/src/Bundle/ChillCalendarBundle/Entity/Calendar.php +++ b/src/Bundle/ChillCalendarBundle/Entity/Calendar.php @@ -37,14 +37,16 @@ class Calendar * @ORM\Id * @ORM\GeneratedValue * @ORM\Column(type="integer") + * @Serializer\Groups({"calendar:read"}) */ private ?int $id; /** * @ORM\ManyToOne(targetEntity="Chill\MainBundle\Entity\User") * @Groups({"read"}) + * @Serializer\Groups({"calendar:read"}) */ - private User $user; + private ?User $user = null; /** * @ORM\ManyToOne(targetEntity="Chill\PersonBundle\Entity\AccompanyingPeriod") @@ -64,6 +66,7 @@ class Calendar * cascade={"persist", "remove", "merge", "detach"}) * @ORM\JoinTable(name="chill_calendar.calendar_to_persons") * @Groups({"read"}) + * @Serializer\Groups({"calendar:read"}) */ private Collection $persons; @@ -74,6 +77,7 @@ class Calendar * cascade={"persist", "remove", "merge", "detach"}) * @ORM\JoinTable(name="chill_calendar.calendar_to_thirdparties") * @Groups({"read"}) + * @Serializer\Groups({"calendar:read"}) */ private Collection $professionals; @@ -89,6 +93,7 @@ class Calendar /** * @ORM\Embedded(class=CommentEmbeddable::class, columnPrefix="comment_") + * @Serializer\Groups({"calendar:read"}) */ private CommentEmbeddable $comment; @@ -96,20 +101,20 @@ class Calendar * @ORM\Column(type="datetimetz_immutable") * @Serializer\Groups({"calendar:read"}) */ - private \DateTimeImmutable $startDate; + private ?\DateTimeImmutable $startDate = null; /** * @ORM\Column(type="datetimetz_immutable") * @Serializer\Groups({"calendar:read"}) */ - private \DateTimeImmutable $endDate; + private ?\DateTimeImmutable $endDate = null; //TODO Lieu /** * @ORM\Column(type="string", length=255) */ - private string $status; + private ?string $status = null; /** * @ORM\ManyToOne(targetEntity="CancelReason") @@ -124,7 +129,7 @@ class Calendar /** * @ORM\ManyToOne(targetEntity="Chill\ActivityBundle\Entity\Activity") */ - private Activity $activity; + private ?Activity $activity = null; /** * @ORM\Column(type="boolean", nullable=true) diff --git a/src/Bundle/ChillCalendarBundle/Entity/CalendarRange.php b/src/Bundle/ChillCalendarBundle/Entity/CalendarRange.php index f9582c8c2..312e4bb41 100644 --- a/src/Bundle/ChillCalendarBundle/Entity/CalendarRange.php +++ b/src/Bundle/ChillCalendarBundle/Entity/CalendarRange.php @@ -25,21 +25,21 @@ class CalendarRange /** * @ORM\ManyToOne(targetEntity="Chill\MainBundle\Entity\User") - * @Groups({"read"}) + * @groups({"read", "write"}) */ - private User $user; + private ?User $user = null; /** * @ORM\Column(type="datetimetz_immutable") - * @groups({"read"}) + * @groups({"read", "write"}) */ - private \DateTimeImmutable $startDate; + private ?\DateTimeImmutable $startDate = null; /** * @ORM\Column(type="datetimetz_immutable") - * @groups({"read"}) + * @groups({"read", "write"}) */ - private \DateTimeImmutable $endDate; + private ?\DateTimeImmutable $endDate = null; /** * @ORM\OneToMany(targetEntity=Calendar::class, diff --git a/src/Bundle/ChillCalendarBundle/Event/ListenToActivityCreate.php b/src/Bundle/ChillCalendarBundle/Event/ListenToActivityCreate.php new file mode 100644 index 000000000..f3c4e7a59 --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/Event/ListenToActivityCreate.php @@ -0,0 +1,45 @@ +requestStack = $requestStack; + } + + public function postPersist(Activity $activity, LifecycleEventArgs $event): void + { + // Get the calendarId from the request + $request = $this->requestStack->getCurrentRequest(); + + if (null === $request) { + return; + } + + 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(); + + } +} diff --git a/src/Bundle/ChillCalendarBundle/Menu/AccompanyingCourseMenuBuilder.php b/src/Bundle/ChillCalendarBundle/Menu/AccompanyingCourseMenuBuilder.php index 2e06d9d2e..244d7db78 100644 --- a/src/Bundle/ChillCalendarBundle/Menu/AccompanyingCourseMenuBuilder.php +++ b/src/Bundle/ChillCalendarBundle/Menu/AccompanyingCourseMenuBuilder.php @@ -37,7 +37,7 @@ class AccompanyingCourseMenuBuilder implements LocalMenuBuilderInterface if (AccompanyingPeriod::STEP_DRAFT !== $period->getStep()) { $menu->addChild($this->translator->trans('Calendar'), [ - 'route' => 'chill_calendar_calendar', + 'route' => 'chill_calendar_calendar_list', 'routeParameters' => [ 'accompanying_period_id' => $period->getId(), ]]) 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/public/chill/scss/calendar.scss b/src/Bundle/ChillCalendarBundle/Resources/public/chill/scss/calendar.scss index b224125f1..a2c0c4b89 100644 --- a/src/Bundle/ChillCalendarBundle/Resources/public/chill/scss/calendar.scss +++ b/src/Bundle/ChillCalendarBundle/Resources/public/chill/scss/calendar.scss @@ -7,4 +7,14 @@ div#calendarControls { div#fullCalendar{ +} + +span.calendarRangeItems { + display: flex; + flex-direction: row; + justify-content: space-between; + a { + text-decoration: none; + padding: 3px; + } } \ No newline at end of file diff --git a/src/Bundle/ChillCalendarBundle/Resources/public/vuejs/MyCalendarRange/App.vue b/src/Bundle/ChillCalendarBundle/Resources/public/vuejs/MyCalendarRange/App.vue new file mode 100644 index 000000000..a30dd2000 --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/Resources/public/vuejs/MyCalendarRange/App.vue @@ -0,0 +1,468 @@ + + + diff --git a/src/Bundle/ChillCalendarBundle/Resources/public/vuejs/MyCalendarRange/i18n.js b/src/Bundle/ChillCalendarBundle/Resources/public/vuejs/MyCalendarRange/i18n.js new file mode 100644 index 000000000..2305b307b --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/Resources/public/vuejs/MyCalendarRange/i18n.js @@ -0,0 +1,21 @@ +const appMessages = { + fr: { + edit_your_calendar_range: "Planifiez vos plages de disponibilités", + show_my_calendar: "Afficher mon calendrier", + show_weekends: "Afficher les week-ends", + copy_range_to_next_day: "Copier les plages du jour au jour suivant", + copy_range_from_day: "Copier les plages du ", + to_the_next_day: " au jour suivant", + copy_range_to_next_week: "Copier les plages de la semaine à la semaine suivante", + copy_range_how_to: "Créez les plages de disponibilités durant une journée et copiez-les facilement au jour suivant avec ce bouton. Si les week-ends sont cachés, le jour suivant un vendredi sera le lundi.", + new_range_to_save: "Nouvelles plages à enregistrer", + update_range_to_save: "Plages à modifier", + delete_range_to_save: "Plages à supprimer", + by: "Par", + main_user_concerned: "Utilisateur concerné" + } +} + +export { + appMessages +}; diff --git a/src/Bundle/ChillCalendarBundle/Resources/public/vuejs/MyCalendarRange/index.js b/src/Bundle/ChillCalendarBundle/Resources/public/vuejs/MyCalendarRange/index.js new file mode 100644 index 000000000..a10a16601 --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/Resources/public/vuejs/MyCalendarRange/index.js @@ -0,0 +1,16 @@ +import { createApp } from 'vue'; +import { _createI18n } from 'ChillMainAssets/vuejs/_js/i18n' +import { appMessages } from './i18n' +import store from './store' + +import App from './App.vue'; + +const i18n = _createI18n(appMessages); + +const app = createApp({ + template: ``, +}) +.use(store) +.use(i18n) +.component('app', App) +.mount('#myCalendar'); diff --git a/src/Bundle/ChillCalendarBundle/Resources/public/vuejs/MyCalendarRange/store.js b/src/Bundle/ChillCalendarBundle/Resources/public/vuejs/MyCalendarRange/store.js new file mode 100644 index 000000000..997f95b11 --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/Resources/public/vuejs/MyCalendarRange/store.js @@ -0,0 +1,89 @@ +import 'es6-promise/auto'; +import { createStore } from 'vuex'; +import { postCalendarRange, patchCalendarRange, deleteCalendarRange } from '../_api/api'; + +const debug = process.env.NODE_ENV !== 'production'; + +const store = createStore({ + strict: debug, + state: { + newCalendarRanges: [], + updateCalendarRanges: [], + deleteCalendarRanges: [] + }, + mutations: { + updateRange(state, payload) { + state.updateCalendarRanges.push({ + id: payload.event.extendedProps.calendarRangeId, + start: payload.event.start, + end: payload.event.end + }); + }, + addRange(state, payload) { + state.newCalendarRanges.push({ + start: payload.start, + end: payload.end + }); + }, + deleteRange(state, payload) { + state.deleteCalendarRanges.push({ + id: payload.extendedProps.calendarRangeId, + start: payload.start, + end: payload.end + }); + }, + clearNewCalendarRanges(state) { + state.newCalendarRanges = []; + }, + clearUpdateCalendarRanges(state) { + state.updateCalendarRanges = []; + }, + clearDeleteCalendarRanges(state) { + state.deleteCalendarRanges = []; + }, + removeNewCalendarRanges(state, payload) { + let filteredCollection = state.newCalendarRanges.filter( + (e) => e.start.toString() !== payload.start.toString() && e.end.toString() !== payload.end.toString() + ) + state.newCalendarRanges = filteredCollection; + }, + removeFromDeleteRange(state, payload) { + let filteredCollection = state.deleteCalendarRanges.filter( + (e) => e.start.toString() !== payload.start.toString() && e.end.toString() !== payload.end.toString() + ) + state.deleteCalendarRanges = filteredCollection; + }, + }, + actions: { + createRange({ commit }, payload) { + console.log('### action createRange', payload); + commit('addRange', payload); + }, + updateRange({ commit }, payload) { + console.log('### action updateRange', payload); + commit('updateRange', payload); + }, + deleteRange({ commit }, payload) { + console.log('### action deleteRange', payload); + commit('deleteRange', payload); + }, + clearNewCalendarRanges({ commit }, payload) { + commit('clearNewCalendarRanges', payload); + }, + clearUpdateCalendarRanges({ commit }, payload) { + commit('clearUpdateCalendarRanges', payload); + }, + clearDeleteCalendarRanges({ commit }, payload) { + commit('clearDeleteCalendarRanges', payload); + }, + removeNewCalendarRanges({ commit }, payload) { + commit('removeNewCalendarRanges', payload); + }, + removeFromDeleteRange({ commit }, payload) { + commit('removeFromDeleteRange', payload); + }, + } +}); + + +export default store; diff --git a/src/Bundle/ChillCalendarBundle/Resources/public/vuejs/_api/api.js b/src/Bundle/ChillCalendarBundle/Resources/public/vuejs/_api/api.js new file mode 100644 index 000000000..7aad8ace2 --- /dev/null +++ b/src/Bundle/ChillCalendarBundle/Resources/public/vuejs/_api/api.js @@ -0,0 +1,100 @@ +/* +* Endpoint chill_api_single_calendar_range +* method GET, get Calendar ranges +* @returns {Promise} a promise containing all Calendar ranges objects +*/ +const fetchCalendarRanges = () => { + const url = `/api/1.0/calendar/calendar-range-available.json`; + return fetch(url) + .then(response => { + if (response.ok) { return response.json(); } + throw Error('Error with request resource response'); + }); +}; + +const fetchCalendarRangesByUser = (userId) => { + const url = `/api/1.0/calendar/calendar-range-available.json?user=${userId}`; + return fetch(url) + .then(response => { + if (response.ok) { return response.json(); } + throw Error('Error with request resource response'); + }); +}; + +/* +* Endpoint chill_api_single_calendar +* method GET, get Calendar events, can be filtered by mainUser +* @returns {Promise} a promise containing all Calendar objects +*/ +const fetchCalendar = (mainUserId) => { + const url = `/api/1.0/calendar/calendar.json?main_user=${mainUserId}&item_per_page=1000`; + return fetch(url) + .then(response => { + if (response.ok) { return response.json(); } + throw Error('Error with request resource response'); + }); +}; + + +/* +* Endpoint chill_api_single_calendar_range__entity_create +* method POST, post CalendarRange entity +*/ +const postCalendarRange = (body) => { + const url = `/api/1.0/calendar/calendar-range.json?`; + return fetch(url, { + method: 'POST', + headers: { + 'Content-Type': 'application/json;charset=utf-8' + }, + body: JSON.stringify(body) + }).then(response => { + if (response.ok) { return response.json(); } + throw Error('Error with request resource response'); + }); +}; + +/* +* Endpoint chill_api_single_calendar_range__entity +* method PATCH, patch CalendarRange entity +*/ +const patchCalendarRange = (id, body) => { + console.log(body) + const url = `/api/1.0/calendar/calendar-range/${id}.json`; + return fetch(url, { + method: 'PATCH', + headers: { + 'Content-Type': 'application/json;charset=utf-8' + }, + body: JSON.stringify(body) + }).then(response => { + if (response.ok) { return response.json(); } + throw Error('Error with request resource response'); + }); +}; + +/* +* Endpoint chill_api_single_calendar_range__entity +* method DELETE, delete CalendarRange entity +*/ +const deleteCalendarRange = (id) => { + const url = `/api/1.0/calendar/calendar-range/${id}.json`; + return fetch(url, { + method: 'DELETE', + headers: { + 'Content-Type': 'application/json;charset=utf-8' + }, + }).then(response => { + if (response.ok) { return response.json(); } + throw Error('Error with request resource response'); + }); +}; + +export { + fetchCalendarRanges, + fetchCalendar, + fetchCalendarRangesByUser, + postCalendarRange, + patchCalendarRange, + deleteCalendarRange +}; diff --git a/src/Bundle/ChillCalendarBundle/Resources/public/vuejs/_components/CalendarUserSelector/CalendarUserSelector.vue b/src/Bundle/ChillCalendarBundle/Resources/public/vuejs/_components/CalendarUserSelector/CalendarUserSelector.vue index 758b7bae8..da9a9eaf8 100644 --- a/src/Bundle/ChillCalendarBundle/Resources/public/vuejs/_components/CalendarUserSelector/CalendarUserSelector.vue +++ b/src/Bundle/ChillCalendarBundle/Resources/public/vuejs/_components/CalendarUserSelector/CalendarUserSelector.vue @@ -1,5 +1,5 @@