From af9cf8f7cd9e97e851e7ea0a78cfe481896ea74e Mon Sep 17 00:00:00 2001 From: Angel Fernando Quiroz Campos <1697880+AngelFQC@users.noreply.github.com> Date: Mon, 18 Nov 2024 09:21:43 -0500 Subject: [PATCH] Internal: Fix StudentViewButton component for session coaches - refs BT#22166 --- assets/vue/components/StudentViewButton.vue | 9 ++-- assets/vue/composables/userPermissions.js | 44 +++++++++++++++++++ assets/vue/router/index.js | 10 +++-- assets/vue/store/cidReq.js | 41 +---------------- assets/vue/store/securityStore.js | 13 +++++- src/CoreBundle/Controller/IndexController.php | 4 -- src/CoreBundle/Entity/Session.php | 19 +++++--- .../Entity/SessionRelCourseRelUser.php | 7 +-- src/CoreBundle/Entity/SessionRelUser.php | 2 +- 9 files changed, 85 insertions(+), 64 deletions(-) diff --git a/assets/vue/components/StudentViewButton.vue b/assets/vue/components/StudentViewButton.vue index fc242fb149..7a6626e514 100644 --- a/assets/vue/components/StudentViewButton.vue +++ b/assets/vue/components/StudentViewButton.vue @@ -14,10 +14,10 @@ import BaseToggleButton from "./basecomponents/BaseToggleButton.vue" import { computed } from "vue" import { useI18n } from "vue-i18n" import { usePlatformConfig } from "../store/platformConfig" -import { storeToRefs } from "pinia" import { useCidReqStore } from "../store/cidReq" import { useSecurityStore } from "../store/securityStore" import permissionService from "../services/permissionService" +import { useUserSessionSubscription } from "../composables/userPermissions" const emit = defineEmits(["change"]) @@ -25,6 +25,7 @@ const { t } = useI18n() const platformConfigStore = usePlatformConfig() const cidReqStore = useCidReqStore() const securityStore = useSecurityStore() +const { isCoach } = useUserSessionSubscription() const isStudentView = computed({ async set() { @@ -39,13 +40,11 @@ const isStudentView = computed({ }, }) -const { course, userIsCoach } = storeToRefs(cidReqStore) - const showButton = computed(() => { return ( securityStore.isAuthenticated && - course.value && - (securityStore.isCourseAdmin || securityStore.isAdmin || userIsCoach.value(securityStore.user.id, 0, false)) && + cidReqStore.course && + (securityStore.isCourseAdmin || securityStore.isAdmin || isCoach.value) && "true" === platformConfigStore.getSetting("course.student_view_enabled") ) }) diff --git a/assets/vue/composables/userPermissions.js b/assets/vue/composables/userPermissions.js index f7d2c48571..f079c27a7c 100644 --- a/assets/vue/composables/userPermissions.js +++ b/assets/vue/composables/userPermissions.js @@ -1,6 +1,8 @@ import { storeToRefs } from "pinia" import { useCidReqStore } from "../store/cidReq" import api from "../config/api" +import { computed, ref, unref } from "vue" +import { useSecurityStore } from "../store/securityStore" /** * @param {boolean} tutor @@ -37,3 +39,45 @@ export async function checkIsAllowedToEdit( return false } + +export function useUserSessionSubscription(session = null, course = null) { + const isGeneralCoach = ref(false) + const isCurrentCourseCoach = ref(false) + const isCourseCoach = ref(false) + const isCoach = computed(() => isGeneralCoach.value || isCurrentCourseCoach.value || isCourseCoach.value) + + const cidReqStore = useCidReqStore() + const securityStore = useSecurityStore() + + session = session || unref(cidReqStore.session) + course = course || unref(cidReqStore.course) + + if (session) { + isGeneralCoach.value = session.generalCoachesSubscriptions.some( + (sessionRelUser) => sessionRelUser.user === securityStore.user["@id"], + ) + + for (const sessionRelCourseRelUser of session.courseCoachesSubscriptions) { + if (securityStore.user["@id"] === sessionRelCourseRelUser.user) { + isCourseCoach.value = true + + if (course) { + if (course["@id"] === sessionRelCourseRelUser.course) { + isCurrentCourseCoach.value = true + + break + } + } else { + break + } + } + } + } + + return { + isGeneralCoach, + isCurrentCourseCoach, + isCourseCoach, + isCoach, + } +} diff --git a/assets/vue/router/index.js b/assets/vue/router/index.js index 55b5c47c6c..f16a166db9 100644 --- a/assets/vue/router/index.js +++ b/assets/vue/router/index.js @@ -21,7 +21,6 @@ import assignments from "./assignments" import links from "./links" import glossary from "./glossary" import { useSecurityStore } from "../store/securityStore" -import securityService from "../services/securityService" import MyCourseList from "../views/user/courses/List.vue" import MySessionList from "../views/user/sessions/SessionsCurrent.vue" import MySessionListPast from "../views/user/sessions/SessionsPast.vue" @@ -44,6 +43,7 @@ import courseService from "../services/courseService" import catalogueCourses from "./cataloguecourses" import catalogueSessions from "./cataloguesessions" import { customVueTemplateEnabled } from "../config/env" +import { useUserSessionSubscription } from "../composables/userPermissions" const router = createRouter({ history: createWebHistory(), @@ -208,10 +208,12 @@ router.beforeResolve(async (to) => { await cidReqStore.setCourseAndSessionById(cid, sid) if (cidReqStore.session) { - const isGeneralCoach = cidReqStore.session.generalCoaches.includes(securityStore.user["@id"]) - const isCourseCoach = cidReqStore.session.courseCoaches.includes(securityStore.user["@id"]) + const { isGeneralCoach, isCourseCoach } = useUserSessionSubscription() - if (isGeneralCoach || isCourseCoach) { + securityStore.removeRole("ROLE_CURRENT_COURSE_SESSION_TEACHER") + securityStore.removeRole("ROLE_CURRENT_COURSE_SESSION_STUDENT") + + if (isGeneralCoach.value || isCourseCoach.value) { securityStore.user.roles.push("ROLE_CURRENT_COURSE_SESSION_TEACHER") } else { securityStore.user.roles.push("ROLE_CURRENT_COURSE_SESSION_STUDENT") diff --git a/assets/vue/store/cidReq.js b/assets/vue/store/cidReq.js index 094095c20f..7e8287f078 100644 --- a/assets/vue/store/cidReq.js +++ b/assets/vue/store/cidReq.js @@ -1,7 +1,6 @@ import { defineStore } from "pinia" -import { usePlatformConfig } from "./platformConfig" import courseService from "../services/courseService" -import { computed, ref } from "vue" +import { ref } from "vue" import sessionService from "../services/sessionService" import { useCourseSettings } from "./courseSettingStore" @@ -13,42 +12,6 @@ export const useCidReqStore = defineStore("cidReq", () => { const courseSettingsStore = useCourseSettings() - const userIsCoach = computed(() => { - const platformConfigStore = usePlatformConfig() - - return (userId, cId = 0, checkStudentView = true) => { - if (checkStudentView && platformConfigStore.isStudentViewActive) { - return false - } - - if (!session.value || !userId) { - return false - } - - const sessionIsCoach = [] - - if (cId) { - const courseCoachSubscription = session.value?.sessionRelCourseRelUsers?.find( - (srcru) => srcru.course.id === cId && srcru.user.id === userId && 2 === srcru.status, - ) - - if (courseCoachSubscription) { - sessionIsCoach.push(courseCoachSubscription) - } - } - - const generalCoachSubscription = session.value?.users?.find( - (sru) => sru.user.id === userId && 3 === sru.relationType, - ) - - if (generalCoachSubscription) { - sessionIsCoach.push(generalCoachSubscription) - } - - return sessionIsCoach.length > 0 - } - }) - const resetCid = () => { course.value = null session.value = null @@ -113,8 +76,6 @@ export const useCidReqStore = defineStore("cidReq", () => { session, group, - userIsCoach, - resetCid, setCourseAndSessionById, diff --git a/assets/vue/store/securityStore.js b/assets/vue/store/securityStore.js index 18530326ab..388310b2b4 100644 --- a/assets/vue/store/securityStore.js +++ b/assets/vue/store/securityStore.js @@ -16,6 +16,17 @@ export const useSecurityStore = defineStore("security", () => { return false }) + /** + * @param {string} role + */ + const removeRole = (role) => { + const index = user.value.roles.indexOf(role) + + if (index > -1) { + user.value.roles.splice(index, 1) + } + } + const isStudent = computed(() => hasRole.value("ROLE_STUDENT")) const isStudentBoss = computed(() => hasRole.value("ROLE_STUDENT_BOSS")) @@ -36,7 +47,6 @@ export const useSecurityStore = defineStore("security", () => { const isAdmin = computed(() => hasRole.value("ROLE_SUPER_ADMIN") || hasRole.value("ROLE_ADMIN")) - async function checkSession() { isLoading.value = true try { @@ -59,6 +69,7 @@ export const useSecurityStore = defineStore("security", () => { isLoading, isAuthenticated, hasRole, + removeRole, isStudent, isStudentBoss, isHRM, diff --git a/src/CoreBundle/Controller/IndexController.php b/src/CoreBundle/Controller/IndexController.php index e7c165fc73..5bd7460ccb 100644 --- a/src/CoreBundle/Controller/IndexController.php +++ b/src/CoreBundle/Controller/IndexController.php @@ -71,10 +71,6 @@ class IndexController extends BaseController #[Security("is_granted('ROLE_TEACHER')")] public function toggleStudentView(Request $request, SettingsManager $settingsManager): Response { - if (!api_is_allowed_to_edit(false, false, false, false)) { - throw $this->createAccessDeniedException(); - } - if ('true' !== $settingsManager->getSetting('course.student_view_enabled')) { throw $this->createAccessDeniedException(); } diff --git a/src/CoreBundle/Entity/Session.php b/src/CoreBundle/Entity/Session.php index 2101c5acfc..0bda39bde5 100644 --- a/src/CoreBundle/Entity/Session.php +++ b/src/CoreBundle/Entity/Session.php @@ -768,15 +768,20 @@ class Session implements ResourceWithAccessUrlInterface, Stringable return $this; } - #[Groups(['session:basic'])] - public function getGeneralCoaches(): ReadableCollection + /** + * @return Collection + */ + public function getGeneralCoaches(): Collection { return $this->getGeneralCoachesSubscriptions() ->map(fn (SessionRelUser $subscription) => $subscription->getUser()) ; } - #[Groups(['user_subscriptions:sessions'])] + /** + * @return Collection + */ + #[Groups(['session:basic', 'user_subscriptions:sessions'])] public function getGeneralCoachesSubscriptions(): Collection { $criteria = Criteria::create()->where(Criteria::expr()->eq('relationType', self::GENERAL_COACH)); @@ -1305,8 +1310,10 @@ class Session implements ResourceWithAccessUrlInterface, Stringable return !empty($end) && $now <= $end; } - #[Groups(['session:basic'])] - public function getCourseCoaches() + /** + * @return Collection + */ + public function getCourseCoaches(): Collection { return $this->getCourseCoachesSubscriptions() ->map(fn (SessionRelCourseRelUser $subscription) => $subscription->getUser()) @@ -1316,7 +1323,7 @@ class Session implements ResourceWithAccessUrlInterface, Stringable /** * @return Collection */ - #[Groups(['user_subscriptions:sessions'])] + #[Groups(['session:basic', 'user_subscriptions:sessions'])] public function getCourseCoachesSubscriptions(): Collection { return $this->getAllUsersFromCourse(self::COURSE_COACH); diff --git a/src/CoreBundle/Entity/SessionRelCourseRelUser.php b/src/CoreBundle/Entity/SessionRelCourseRelUser.php index 641785a474..e55b4c047e 100644 --- a/src/CoreBundle/Entity/SessionRelCourseRelUser.php +++ b/src/CoreBundle/Entity/SessionRelCourseRelUser.php @@ -10,7 +10,6 @@ use ApiPlatform\Doctrine\Orm\Filter\DateFilter; use ApiPlatform\Doctrine\Orm\Filter\SearchFilter; use ApiPlatform\Metadata\ApiFilter; use ApiPlatform\Metadata\ApiResource; -use Chamilo\CoreBundle\Entity\User as UserAlias; use Chamilo\CoreBundle\Traits\UserTrait; use Doctrine\ORM\Mapping as ORM; use Symfony\Component\Serializer\Annotation\Groups; @@ -75,10 +74,11 @@ class SessionRelCourseRelUser 'session:read', 'session_rel_course_rel_user:read', 'user_subscriptions:sessions', + 'session:basic', ])] - #[ORM\ManyToOne(targetEntity: UserAlias::class, cascade: ['persist'], inversedBy: 'sessionRelCourseRelUsers')] + #[ORM\ManyToOne(targetEntity: User::class, cascade: ['persist'], inversedBy: 'sessionRelCourseRelUsers')] #[ORM\JoinColumn(name: 'user_id', referencedColumnName: 'id', nullable: false, onDelete: 'CASCADE')] - protected UserAlias $user; + protected User $user; #[Groups([ 'session_rel_course_rel_user:read', @@ -92,6 +92,7 @@ class SessionRelCourseRelUser 'session_rel_course_rel_user:read', 'session_rel_user:read', 'user_subscriptions:sessions', + 'session:basic', ])] #[MaxDepth(1)] #[ORM\ManyToOne(targetEntity: Course::class, cascade: ['persist'], inversedBy: 'sessionRelCourseRelUsers')] diff --git a/src/CoreBundle/Entity/SessionRelUser.php b/src/CoreBundle/Entity/SessionRelUser.php index 9342c03dad..25b474e5c3 100644 --- a/src/CoreBundle/Entity/SessionRelUser.php +++ b/src/CoreBundle/Entity/SessionRelUser.php @@ -76,7 +76,7 @@ class SessionRelUser protected ?Session $session = null; #[Assert\NotNull] - #[Groups(['session_rel_user:read', 'session:item:read', 'user_subscriptions:sessions'])] + #[Groups(['session_rel_user:read', 'session:item:read', 'user_subscriptions:sessions', 'session:basic'])] #[ORM\ManyToOne(targetEntity: User::class, cascade: ['persist'], inversedBy: 'sessionsRelUser')] #[ORM\JoinColumn(name: 'user_id', referencedColumnName: 'id', onDelete: 'CASCADE')] protected User $user;