diff --git a/assets/vue/components/course/CatalogueCourseCard.vue b/assets/vue/components/course/CatalogueCourseCard.vue new file mode 100644 index 00000000000..13e08390e49 --- /dev/null +++ b/assets/vue/components/course/CatalogueCourseCard.vue @@ -0,0 +1,180 @@ + + diff --git a/assets/vue/components/session/CatalogueSessionCard.vue b/assets/vue/components/session/CatalogueSessionCard.vue new file mode 100644 index 00000000000..5c74722c2a3 --- /dev/null +++ b/assets/vue/components/session/CatalogueSessionCard.vue @@ -0,0 +1,296 @@ + + diff --git a/assets/vue/services/courseRelUserService.js b/assets/vue/services/courseRelUserService.js index 8e22a521854..841e52c0850 100644 --- a/assets/vue/services/courseRelUserService.js +++ b/assets/vue/services/courseRelUserService.js @@ -8,6 +8,23 @@ async function findAll(searchParams) { return await baseService.getCollection("/api/course_rel_users", searchParams) } +/** + * Subscribes a user to a course. + * @param {Object} params + * @param {number} params.userId + * @param {number} params.courseId + * @returns {Promise} + */ +async function subscribe({ userId, courseId }) { + return await baseService.post("/api/course_rel_users", { + user: `/api/users/${userId}`, + course: `/api/courses/${courseId}`, + relationType: 0, + status: 5, + }) +} + export default { findAll, + subscribe, } diff --git a/assets/vue/services/userRelCourseVoteService.js b/assets/vue/services/userRelCourseVoteService.js index 32756f23b2d..264da1df060 100644 --- a/assets/vue/services/userRelCourseVoteService.js +++ b/assets/vue/services/userRelCourseVoteService.js @@ -11,13 +11,16 @@ import baseService from "./baseService" * @returns {Promise} */ export async function saveVote({ courseIri, userId, vote, sessionId = null, urlId }) { - return await baseService.post("/api/user_rel_course_votes", { - course: courseIri, + const payload = { user: `/api/users/${userId}`, vote, - session: sessionId ? `/api/sessions/${sessionId}` : null, url: `/api/access_urls/${urlId}`, - }) + } + + if (courseIri) payload.course = courseIri + if (sessionId) payload.session = `/api/sessions/${sessionId}` + + return await baseService.post("/api/user_rel_course_votes", payload) } /** @@ -57,17 +60,19 @@ export async function updateVote({ iri, vote, sessionId = null, urlId }) { */ export async function getUserVote({ userId, courseId, sessionId = null, urlId }) { try { - let query = `/api/user_rel_course_votes?user.id=${userId}&course.id=${courseId}` + let query = `/api/user_rel_course_votes?user.id=${userId}` + if (urlId) query += `&url.id=${urlId}` - // Remove session.id if null - if (sessionId) { - query += `&session.id=${sessionId}` + if (courseId && courseId !== 0) { + query += `&course.id=${courseId}` + } else if (sessionId) { + query += `&session.id=${sessionId}&course=null` } const response = await baseService.get(query) - if (response && response["hydra:member"] && response["hydra:member"].length > 0) { + if (response?.["hydra:member"]?.length > 0) { return response["hydra:member"][0] } diff --git a/assets/vue/views/course/CatalogueCourses.vue b/assets/vue/views/course/CatalogueCourses.vue index 1d03746d6e8..b6a00ee701b 100644 --- a/assets/vue/views/course/CatalogueCourses.vue +++ b/assets/vue/views/course/CatalogueCourses.vue @@ -1,236 +1,129 @@ diff --git a/assets/vue/views/course/CatalogueSessions.vue b/assets/vue/views/course/CatalogueSessions.vue index 7b6998c120b..7b3267a527f 100644 --- a/assets/vue/views/course/CatalogueSessions.vue +++ b/assets/vue/views/course/CatalogueSessions.vue @@ -1,278 +1,239 @@ - + diff --git a/src/CoreBundle/Controller/CatalogueController.php b/src/CoreBundle/Controller/CatalogueController.php index 62afe2e49d5..c6a100f5da8 100644 --- a/src/CoreBundle/Controller/CatalogueController.php +++ b/src/CoreBundle/Controller/CatalogueController.php @@ -11,6 +11,7 @@ use Chamilo\CoreBundle\Entity\Course; use Chamilo\CoreBundle\Entity\Session; use Chamilo\CoreBundle\Entity\UsergroupRelUser; +use Chamilo\CoreBundle\Entity\UserRelCourseVote; use Chamilo\CoreBundle\Repository\Node\CourseRepository; use Chamilo\CoreBundle\Repository\SessionRepository; use Chamilo\CoreBundle\ServiceHelper\AccessUrlHelper; @@ -83,6 +84,7 @@ public function listSessions(): JsonResponse $relRepo = $this->em->getRepository(CatalogueSessionRelAccessUrlRelUsergroup::class); $userGroupRepo = $this->em->getRepository(UsergroupRelUser::class); + $voteRepo = $this->em->getRepository(UserRelCourseVote::class); $relations = $relRepo->findBy(['accessUrl' => $accessUrl]); @@ -98,7 +100,7 @@ public function listSessions(): JsonResponse $session = $rel->getSession(); $usergroup = $rel->getUsergroup(); - if (null === $usergroup || \in_array($usergroup->getId(), $userGroupIds)) { + if (null === $usergroup || in_array($usergroup->getId(), $userGroupIds)) { $visibleSessions[$session->getId()] = $session; } } @@ -106,7 +108,43 @@ public function listSessions(): JsonResponse $sessions = array_values($visibleSessions); } - $data = array_map(function (Session $session) { + $data = array_map(function (Session $session) use ($voteRepo, $user) { + $courses = []; + + foreach ($session->getCourses() as $rel) { + $course = $rel->getCourse(); + if (!$course) { + continue; + } + + $teachers = []; + foreach ($session->getGeneralCoachesSubscriptions() as $coachRel) { + $userObj = $coachRel->getUser(); + if ($userObj) { + $teachers[] = [ + 'id' => $userObj->getId(), + 'fullName' => $userObj->getFullname(), + ]; + } + } + + $courses[] = [ + 'id' => $course->getId(), + 'title' => $course->getTitle(), + 'duration' => $course->getDuration(), + 'courseLanguage' => $course->getCourseLanguage(), + 'teachers' => $teachers, + ]; + } + + $voteCount = (int) $voteRepo->createQueryBuilder('v') + ->select('COUNT(DISTINCT v.user)') + ->where('v.session = :session') + ->andWhere('v.course IS NULL') + ->setParameter('session', $session->getId()) + ->getQuery() + ->getSingleScalarResult(); + return [ 'id' => $session->getId(), 'title' => $session->getTitle(), @@ -117,6 +155,9 @@ public function listSessions(): JsonResponse 'nbrCourses' => $session->getNbrCourses(), 'startDate' => $session->getAccessStartDate()?->format('Y-m-d'), 'endDate' => $session->getAccessEndDate()?->format('Y-m-d'), + 'courses' => $courses, + 'popularity' => $voteCount, + 'isSubscribed' => $session->hasUserInSession($user, Session::STUDENT), ]; }, $sessions); diff --git a/src/CoreBundle/Entity/Course.php b/src/CoreBundle/Entity/Course.php index 4945c94f41a..3a45e745e0a 100644 --- a/src/CoreBundle/Entity/Course.php +++ b/src/CoreBundle/Entity/Course.php @@ -300,10 +300,12 @@ class Course extends AbstractResource implements ResourceInterface, ResourceWith protected ?DateTime $expirationDate = null; #[Assert\NotNull] + #[Groups(['course:read'])] #[ORM\Column(name: 'subscribe', type: 'boolean', unique: false, nullable: false)] protected bool $subscribe; #[Assert\NotNull] + #[Groups(['course:read'])] #[ORM\Column(name: 'unsubscribe', type: 'boolean', unique: false, nullable: false)] protected bool $unsubscribe; diff --git a/src/CoreBundle/Entity/CourseRelUser.php b/src/CoreBundle/Entity/CourseRelUser.php index cd08150e1c3..de59e9c518b 100644 --- a/src/CoreBundle/Entity/CourseRelUser.php +++ b/src/CoreBundle/Entity/CourseRelUser.php @@ -26,7 +26,7 @@ operations: [ new Get(security: "is_granted('ROLE_ADMIN') or object.user == user"), new GetCollection(security: "is_granted('ROLE_ADMIN')"), - new Post(security: "is_granted('ROLE_ADMIN')"), + new Post(security: "is_granted('ROLE_ADMIN') or is_granted('ROLE_USER')"), ], normalizationContext: [ 'groups' => ['course_rel_user:read'], diff --git a/src/CoreBundle/Entity/SessionRelUser.php b/src/CoreBundle/Entity/SessionRelUser.php index 5b4d104e726..5f56216787f 100644 --- a/src/CoreBundle/Entity/SessionRelUser.php +++ b/src/CoreBundle/Entity/SessionRelUser.php @@ -30,7 +30,7 @@ operations: [ new Get(security: "is_granted('ROLE_ADMIN') or object.user == user"), new GetCollection(security: "is_granted('ROLE_USER')"), - new Post(security: "is_granted('ROLE_ADMIN')"), + new Post(security: "is_granted('ROLE_ADMIN') or is_granted('ROLE_USER')"), ], normalizationContext: [ 'groups' => [ diff --git a/src/CoreBundle/Entity/UserRelCourseVote.php b/src/CoreBundle/Entity/UserRelCourseVote.php index 1f6e39b5446..efb34c66a38 100644 --- a/src/CoreBundle/Entity/UserRelCourseVote.php +++ b/src/CoreBundle/Entity/UserRelCourseVote.php @@ -62,9 +62,9 @@ class UserRelCourseVote protected User $user; #[ORM\ManyToOne(targetEntity: Course::class)] - #[ORM\JoinColumn(name: 'c_id', referencedColumnName: 'id', onDelete: 'CASCADE')] + #[ORM\JoinColumn(name: 'c_id', referencedColumnName: 'id', nullable: true, onDelete: 'CASCADE')] #[Groups(['userRelCourseVote:read', 'userRelCourseVote:write'])] - protected Course $course; + protected ?Course $course = null; #[ORM\ManyToOne(targetEntity: Session::class)] #[ORM\JoinColumn(name: 'session_id', referencedColumnName: 'id', nullable: true, onDelete: 'CASCADE')] @@ -109,12 +109,12 @@ public function setUser(User $user): self return $this; } - public function getCourse(): Course + public function getCourse(): ?Course { return $this->course; } - public function setCourse(Course $course): self + public function setCourse(?Course $course): self { $this->course = $course; diff --git a/src/CoreBundle/EventListener/UserRelCourseVoteListener.php b/src/CoreBundle/EventListener/UserRelCourseVoteListener.php index 44388703342..e041dc0fdb6 100644 --- a/src/CoreBundle/EventListener/UserRelCourseVoteListener.php +++ b/src/CoreBundle/EventListener/UserRelCourseVoteListener.php @@ -34,14 +34,17 @@ private function updateCoursePopularity(UserRelCourseVote $vote, EntityManagerIn { $course = $vote->getCourse(); + if (!$course) { + return; + } + $uniqueUsers = (int) $entityManager->createQueryBuilder() ->select('COUNT(DISTINCT v.user)') ->from(UserRelCourseVote::class, 'v') ->where('v.course = :course') ->setParameter('course', $course->getId()) ->getQuery() - ->getSingleScalarResult() - ; + ->getSingleScalarResult(); $course->setPopularity($uniqueUsers); $entityManager->persist($course);