Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Internal: Implement catalogue filters and toggle icon for courses - refs #6154 #6159

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion assets/vue/composables/catalogue/catalogueCourseList.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export function useCatalogueCourseList() {
isLoading.value = true

try {
const { items } = await courseService.listAll()
const items = await courseService.listCatalogueCourses()

courses.value = items.map((course) => ({
...course,
Expand Down
8 changes: 8 additions & 0 deletions assets/vue/services/courseService.js
Original file line number Diff line number Diff line change
Expand Up @@ -170,4 +170,12 @@ export default {
return null
}
},
/**
* Loads public catalogue courses filtered by access_url and usergroup rules.
* @returns {Promise<{items: Array}>}
*/
listCatalogueCourses: async () => {
const response = await api.get("/catalogue/courses-list")
return response.data
},
}
2 changes: 1 addition & 1 deletion assets/vue/views/course/CatalogueCourses.vue
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,7 @@ const platformConfigStore = usePlatformConfig()
const showCourseDuration = "true" === platformConfigStore.getSetting("course.show_course_duration")
const isUserInCourse = (course) => {
return course.users.some((user) => user.user.id === securityStore.user.id)
return Array.isArray(course.users) && course.users.some((user) => user.user.id === securityStore.user.id)
}
load()
Expand Down
8 changes: 4 additions & 4 deletions assets/vue/views/course/CatalogueSessions.vue
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@
class="p-button-sm"
>
<BaseIcon icon="link-external" />
{{ t("Go to the session") }}
{{ $t("Go to the session") }}
</BaseAppLink>
</template>
</Column>
Expand Down Expand Up @@ -183,7 +183,7 @@
class="p-button-sm"
icon="link-external"
/>
{{ t("Go to the course") }}
{{ $t("Go to the course") }}
</BaseAppLink>
</template>
</Column>
Expand Down Expand Up @@ -232,14 +232,14 @@ export default {
load: function () {
this.status = true
axios
.get(ENTRYPOINT + "sessions.json")
.get("/catalogue/sessions-list")
.then((response) => {
this.status = false
if (Array.isArray(response.data)) {
this.sessions = response.data
}
})
.catch(function (error) {
.catch((error) => {
console.log(error)
})
},
Expand Down
66 changes: 66 additions & 0 deletions public/main/admin/course_list.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,10 @@
use Chamilo\CoreBundle\Component\Utils\ActionIcon;
use Chamilo\CoreBundle\Component\Utils\StateIcon;
use Chamilo\CoreBundle\Component\Utils\ToolIcon;
use Chamilo\CoreBundle\Entity\AccessUrl;
use Chamilo\CoreBundle\Entity\CatalogueCourseRelAccessUrlRelUsergroup;
use Chamilo\CoreBundle\Framework\Container;
use Chamilo\CoreBundle\Repository\CatalogueCourseRelAccessUrlRelUsergroupRepository;

$cidReset = true;

Expand Down Expand Up @@ -257,6 +260,35 @@ function get_course_data(
]
);

$em = Database::getManager();
/** @var CatalogueCourseRelAccessUrlRelUsergroupRepository $repo */
$repo = $em->getRepository(CatalogueCourseRelAccessUrlRelUsergroup::class);
$record = $repo->findOneBy([
'course' => $courseId,
'accessUrl' => api_get_current_access_url_id(),
'usergroup' => null,
]);

$isInCatalogue = null !== $record;
$catalogueUrl = api_get_self().'?toggle_catalogue='.$course['id'].'&sec_token='.Security::getTokenFromSession();

$actions[] = Display::url(
Display::getMdiIcon(
$isInCatalogue ? StateIcon::CATALOGUE_OFF : StateIcon::CATALOGUE_ON,
'ch-tool-icon',
null,
ICON_SIZE_SMALL,
$isInCatalogue ? get_lang('Remove from catalogue') : get_lang('Add to catalogue'),
[
'class' => $isInCatalogue ? 'text-warning' : 'text-muted',
]
),
$catalogueUrl,
[
'title' => $isInCatalogue ? get_lang('Remove from catalogue') : get_lang('Add to catalogue'),
]
);

$courseItem = [
$course['col0'],
$course['col1'],
Expand Down Expand Up @@ -340,6 +372,40 @@ function get_course_visibility_icon(int $visibility): string
api_location(api_get_self());
}
}

if (isset($_GET['toggle_catalogue']) && Security::check_token('get')) {
$courseId = (int) $_GET['toggle_catalogue'];
$accessUrlId = api_get_current_access_url_id();
$em = Database::getManager();
$repo = $em->getRepository(CatalogueCourseRelAccessUrlRelUsergroup::class);
$course = api_get_course_entity($courseId);
$accessUrl = $em->getRepository(AccessUrl::class)->find($accessUrlId);

if ($course && $accessUrl) {
$record = $repo->findOneBy([
'course' => $course,
'accessUrl' => $accessUrl,
'usergroup' => null,
]);

if ($record) {
$em->remove($record);
Display::addFlash(Display::return_message(get_lang('Removed from catalogue')));
} else {
$newRel = new CatalogueCourseRelAccessUrlRelUsergroup();
$newRel->setCourse($course);
$newRel->setAccessUrl($accessUrl);
$newRel->setUsergroup(null);

$em->persist($newRel);
Display::addFlash(Display::return_message(get_lang('Added to catalogue'), 'success'));
}

$em->flush();
}

api_location(api_get_self());
}
$content = '';
$message = '';
$actions = '';
Expand Down
4 changes: 4 additions & 0 deletions src/CoreBundle/Component/Utils/StateIcon.php
Original file line number Diff line number Diff line change
Expand Up @@ -62,4 +62,8 @@ enum StateIcon: string
case OFFLINE = 'account-off';
// Soft deleted (for a user)
case REJECT = 'cancel';
// Item is visible in the catalogue
case CATALOGUE_ON = 'book-plus';
// Item is hidden from the catalogue
case CATALOGUE_OFF = 'book-minus-outline';
}
125 changes: 125 additions & 0 deletions src/CoreBundle/Controller/CatalogueController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
<?php

/* For licensing terms, see /license.txt */

declare(strict_types=1);

namespace Chamilo\CoreBundle\Controller;

use Chamilo\CoreBundle\Entity\CatalogueCourseRelAccessUrlRelUsergroup;
use Chamilo\CoreBundle\Entity\CatalogueSessionRelAccessUrlRelUsergroup;
use Chamilo\CoreBundle\Entity\Course;
use Chamilo\CoreBundle\Entity\Session;
use Chamilo\CoreBundle\Entity\UsergroupRelUser;
use Chamilo\CoreBundle\Repository\Node\CourseRepository;
use Chamilo\CoreBundle\Repository\SessionRepository;
use Chamilo\CoreBundle\ServiceHelper\AccessUrlHelper;
use Chamilo\CoreBundle\ServiceHelper\UserHelper;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\Routing\Annotation\Route;

#[Route('/catalogue')]
class CatalogueController extends AbstractController
{
public function __construct(
private readonly EntityManagerInterface $em,
private readonly UserHelper $userHelper,
private readonly AccessUrlHelper $accessUrlHelper,
private readonly CourseRepository $courseRepository,
private readonly SessionRepository $sessionRepository
) {}

#[Route('/courses-list', name: 'chamilo_core_catalogue_courses_list', methods: ['GET'])]
public function listCourses(): JsonResponse
{
$user = $this->userHelper->getCurrent();
$accessUrl = $this->accessUrlHelper->getCurrent();

$relRepo = $this->em->getRepository(CatalogueCourseRelAccessUrlRelUsergroup::class);
$userGroupRepo = $this->em->getRepository(UsergroupRelUser::class);

$relations = $relRepo->findBy(['accessUrl' => $accessUrl]);

if (empty($relations)) {
$courses = $this->courseRepository->findAll();
} else {
$userGroups = $userGroupRepo->findBy(['user' => $user]);
$userGroupIds = array_map(fn($ug) => $ug->getUsergroup()->getId(), $userGroups);

$visibleCourses = [];

foreach ($relations as $rel) {
$course = $rel->getCourse();
$usergroup = $rel->getUsergroup();

if ($usergroup === null || in_array($usergroup->getId(), $userGroupIds)) {
$visibleCourses[$course->getId()] = $course;
}
}

$courses = array_values($visibleCourses);
}

$data = array_map(function (Course $course) {
return [
'id' => $course->getId(),
'code' => $course->getCode(),
'title' => $course->getTitle(),
'description' => $course->getDescription(),
'visibility' => $course->getVisibility(),
];
}, $courses);

return $this->json($data);
}

#[Route('/sessions-list', name: 'chamilo_core_catalogue_sessions_list', methods: ['GET'])]
public function listSessions(): JsonResponse
{
$user = $this->userHelper->getCurrent();
$accessUrl = $this->accessUrlHelper->getCurrent();

$relRepo = $this->em->getRepository(CatalogueSessionRelAccessUrlRelUsergroup::class);
$userGroupRepo = $this->em->getRepository(UsergroupRelUser::class);

$relations = $relRepo->findBy(['accessUrl' => $accessUrl]);

if (empty($relations)) {
$sessions = $this->sessionRepository->findAll();
} else {
$userGroups = $userGroupRepo->findBy(['user' => $user]);
$userGroupIds = array_map(fn($ug) => $ug->getUsergroup()->getId(), $userGroups);

$visibleSessions = [];

foreach ($relations as $rel) {
$session = $rel->getSession();
$usergroup = $rel->getUsergroup();

if ($usergroup === null || in_array($usergroup->getId(), $userGroupIds)) {
$visibleSessions[$session->getId()] = $session;
}
}

$sessions = array_values($visibleSessions);
}

$data = array_map(function (Session $session) {
return [
'id' => $session->getId(),
'title' => $session->getTitle(),
'description' => $session->getDescription(),
'imageUrl' => $session->getImageUrl(),
'visibility' => $session->getVisibility(),
'nbrUsers' => $session->getNbrUsers(),
'nbrCourses' => $session->getNbrCourses(),
'startDate' => $session->getAccessStartDate()?->format('Y-m-d'),
'endDate' => $session->getAccessEndDate()?->format('Y-m-d'),
];
}, $sessions);

return $this->json($data);
}
}
74 changes: 74 additions & 0 deletions src/CoreBundle/Entity/CatalogueCourseRelAccessUrlRelUsergroup.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
<?php

/* For licensing terms, see /license.txt */

declare(strict_types=1);

namespace Chamilo\CoreBundle\Entity;

use Chamilo\CoreBundle\Repository\CatalogueCourseRelAccessUrlRelUsergroupRepository;
use Doctrine\ORM\Mapping as ORM;

#[ORM\Table(name: 'catalogue_course_rel_access_url_rel_usergroup')]
#[ORM\Entity(repositoryClass: CatalogueCourseRelAccessUrlRelUsergroupRepository::class)]
class CatalogueCourseRelAccessUrlRelUsergroup
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column(type: 'integer')]
private ?int $id = null;

#[ORM\ManyToOne(targetEntity: Course::class)]
#[ORM\JoinColumn(name: 'course_id', referencedColumnName: 'id', onDelete: 'CASCADE')]
private Course $course;

#[ORM\ManyToOne(targetEntity: AccessUrl::class)]
#[ORM\JoinColumn(name: 'access_url_id', referencedColumnName: 'id', onDelete: 'CASCADE')]
private AccessUrl $accessUrl;

#[ORM\ManyToOne(targetEntity: Usergroup::class)]
#[ORM\JoinColumn(name: 'usergroup_id', referencedColumnName: 'id', nullable: true, onDelete: 'SET NULL')]
private ?Usergroup $usergroup = null;


public function getId(): ?int
{
return $this->id;
}

public function getCourse(): Course
{
return $this->course;
}

public function setCourse(Course $course): self
{
$this->course = $course;

return $this;
}

public function getAccessUrl(): AccessUrl
{
return $this->accessUrl;
}

public function setAccessUrl(AccessUrl $accessUrl): self
{
$this->accessUrl = $accessUrl;

return $this;
}

public function getUsergroup(): ?Usergroup
{
return $this->usergroup;
}

public function setUsergroup(?Usergroup $usergroup): self
{
$this->usergroup = $usergroup;

return $this;
}
}
Loading
Loading