diff --git a/assets/vue/components/basecomponents/ChamiloIcons.js b/assets/vue/components/basecomponents/ChamiloIcons.js index 3a1cf24c5e2..35f5502170b 100644 --- a/assets/vue/components/basecomponents/ChamiloIcons.js +++ b/assets/vue/components/basecomponents/ChamiloIcons.js @@ -144,4 +144,5 @@ export const chamiloIconToClass = { "clear-all": "mdi mdi-broom", "qrcode": "mdi mdi-qrcode", "minus": "mdi mdi-minus", + "shield-check": "mdi mdi-shield-check", } diff --git a/assets/vue/components/course/CatalogueCourseCard.vue b/assets/vue/components/course/CatalogueCourseCard.vue index 2b66e02038d..d18a7b7e768 100644 --- a/assets/vue/components/course/CatalogueCourseCard.vue +++ b/assets/vue/components/course/CatalogueCourseCard.vue @@ -121,7 +121,7 @@
+ import Rating from "primevue/rating" import Button from "primevue/button" -import { computed, ref } from "vue" -import courseRelUserService from "../../services/courseRelUserService" +import Dialog from "primevue/dialog" +import { computed, ref, onMounted } from "vue" import { useRoute, useRouter } from "vue-router" import { useNotification } from "../../composables/notification" -import Dialog from "primevue/dialog" import { usePlatformConfig } from "../../store/platformConfig" +import CatalogueRequirementModal from "./CatalogueRequirementModal.vue" +import courseRelUserService from "../../services/courseRelUserService" +import { useCourseRequirementStatus } from "../../composables/course/useCourseRequirementStatus" import { useLocale } from "../../composables/locale" -const platformConfigStore = usePlatformConfig() -const showDescriptionDialog = ref(false) const { getOriginalLanguageName } = useLocale() -const allowDescription = computed( - () => platformConfigStore.getSetting("course.show_courses_descriptions_in_catalog") !== "false", -) - const props = defineProps({ course: Object, currentUserId: { @@ -212,6 +223,14 @@ const emit = defineEmits(["rate", "subscribed"]) const router = useRouter() const route = useRoute() const { showErrorNotification, showSuccessNotification } = useNotification() +const platformConfigStore = usePlatformConfig() + +const showDescriptionDialog = ref(false) +const showDependenciesModal = ref(false) + +const allowDescription = computed( + () => platformConfigStore.getSetting("course.show_courses_descriptions_in_catalog") !== "false", +) const isUserInCourse = computed(() => { if (!props.currentUserId) return false @@ -287,9 +306,7 @@ function routeExists(name) { const linkSettings = computed(() => { const settings = platformConfigStore.getSetting("course.course_catalog_settings") - const result = settings?.link_settings ?? {} - console.log("Link settings:", result) - return result + return settings?.link_settings ?? {} }) const imageLink = computed(() => { @@ -304,10 +321,6 @@ const imageLink = computed(() => { return { name: routeName, params: { id: props.course.id } } } - if (routeName) { - console.warn(`[CatalogueCourseCard] Route '${routeName}' does not exist.`) - } - return null }) @@ -318,20 +331,21 @@ const titleLink = computed(() => { return { name: routeName, params: { id: props.course.id } } } - if (routeName) { - console.warn(`[CatalogueCourseCard] Route '${routeName}' does not exist.`) - } - return null }) const showInfoPopup = computed(() => { const allowed = ["course_description_popup"] const value = linkSettings.value.info_url - if (value && !allowed.includes(value)) { - console.warn(`[CatalogueCourseCard] info_url '${value}' is not a recognized option.`) - return false - } - return value === "course_description_popup" + return value && allowed.includes(value) +}) + +const { isLocked, hasRequirements, requirementList, graphImage, fetchStatus } = useCourseRequirementStatus( + props.course.id, + props.course.sessionId || 0, +) + +onMounted(() => { + fetchStatus() }) diff --git a/assets/vue/components/course/CatalogueRequirementModal.vue b/assets/vue/components/course/CatalogueRequirementModal.vue new file mode 100644 index 00000000000..3f2aac1f430 --- /dev/null +++ b/assets/vue/components/course/CatalogueRequirementModal.vue @@ -0,0 +1,69 @@ + + + diff --git a/assets/vue/components/course/CourseCard.vue b/assets/vue/components/course/CourseCard.vue index e0778aa63e3..40ca1e447a0 100644 --- a/assets/vue/components/course/CourseCard.vue +++ b/assets/vue/components/course/CourseCard.vue @@ -2,9 +2,9 @@ + + diff --git a/assets/vue/components/course/NextCourseSequence.vue b/assets/vue/components/course/NextCourseSequence.vue new file mode 100644 index 00000000000..ce597352b9e --- /dev/null +++ b/assets/vue/components/course/NextCourseSequence.vue @@ -0,0 +1,97 @@ + + + diff --git a/assets/vue/components/session/CatalogueSessionCard.vue b/assets/vue/components/session/CatalogueSessionCard.vue index 27f4a420074..1c00eb97228 100644 --- a/assets/vue/components/session/CatalogueSessionCard.vue +++ b/assets/vue/components/session/CatalogueSessionCard.vue @@ -159,7 +159,16 @@ class="flex justify-between items-center border-b pb-1" > {{ item.title }} + + diff --git a/assets/vue/components/session/SessionCardSimple.vue b/assets/vue/components/session/SessionCardSimple.vue index ce3c61b7c91..c42db2975bc 100644 --- a/assets/vue/components/session/SessionCardSimple.vue +++ b/assets/vue/components/session/SessionCardSimple.vue @@ -10,19 +10,35 @@ const props = defineProps({ }) const { courses, isEnabled } = useSessionCard(props.session) + +function extractIdFromIri(iri) { + if (!iri) return undefined + const match = iri.match(/\/(\d+)$/) + return match ? parseInt(match[1], 10) : undefined +} + +function normalizeCourse(course) { + return { + ...course, + id: course.id || course._id || extractIdFromIri(course["@id"]), + } +} diff --git a/assets/vue/components/session/SessionCategoryView.vue b/assets/vue/components/session/SessionCategoryView.vue index 08ff18eeb57..4c258d5b0d5 100644 --- a/assets/vue/components/session/SessionCategoryView.vue +++ b/assets/vue/components/session/SessionCategoryView.vue @@ -1,27 +1,34 @@ - - + + diff --git a/assets/vue/components/session/SessionListView.vue b/assets/vue/components/session/SessionListView.vue new file mode 100644 index 00000000000..4113d779010 --- /dev/null +++ b/assets/vue/components/session/SessionListView.vue @@ -0,0 +1,98 @@ + + diff --git a/assets/vue/composables/course/useCourseRequirementStatus.js b/assets/vue/composables/course/useCourseRequirementStatus.js new file mode 100644 index 00000000000..253f6816a60 --- /dev/null +++ b/assets/vue/composables/course/useCourseRequirementStatus.js @@ -0,0 +1,52 @@ +import { ref } from "vue" +import courseService from "../../services/courseService" + +export function useCourseRequirementStatus(courseId, sessionId, onLockChange = null) { + const isLocked = ref(false) + const hasRequirements = ref(false) + const requirementList = ref([]) + const graphImage = ref(null) + const loading = ref(true) + + async function fetchStatus() { + if (!courseId || courseId === 0) { + loading.value = false + return + } + + loading.value = true + try { + const result = await courseService.getNextCourse(courseId, sessionId) + const locked = !(result?.allowSubscription ?? true) + + isLocked.value = locked + hasRequirements.value = result?.sequenceList?.length > 0 + requirementList.value = result?.sequenceList ?? [] + graphImage.value = result?.graph || null + + if (onLockChange) { + onLockChange(locked) + } + } catch (e) { + isLocked.value = false + hasRequirements.value = false + requirementList.value = [] + graphImage.value = null + + if (onLockChange) { + onLockChange(false) + } + } finally { + loading.value = false + } + } + + return { + isLocked, + hasRequirements, + requirementList, + graphImage, + loading, + fetchStatus, + } +} diff --git a/assets/vue/services/courseService.js b/assets/vue/services/courseService.js index 9e1bd779516..2686aca12b1 100644 --- a/assets/vue/services/courseService.js +++ b/assets/vue/services/courseService.js @@ -204,4 +204,14 @@ export default { return [] } }, + + getNextCourse: async (courseId, sessionId = 0, dependents = false) => { + const { data } = await api.get(`/course/${courseId}/next-course`, { + params: { + sid: sessionId, + dependents: dependents ? 1 : 0, + }, + }) + return data + } } diff --git a/assets/vue/views/course/CourseHome.vue b/assets/vue/views/course/CourseHome.vue index c0edd3c4063..5aca54c8b58 100644 --- a/assets/vue/views/course/CourseHome.vue +++ b/assets/vue/views/course/CourseHome.vue @@ -143,10 +143,20 @@ - +
+
+ +
+
+ +
+
{ + return platformConfigStore.getSetting("course.resource_sequence_show_dependency_in_course_intro") === "true" +}) onMounted(async () => { isAllowedToEdit.value = await checkIsAllowedToEdit() diff --git a/public/main/session/resume_session.php b/public/main/session/resume_session.php index 1df62218e25..1b9070a948d 100644 --- a/public/main/session/resume_session.php +++ b/public/main/session/resume_session.php @@ -7,10 +7,9 @@ use Chamilo\CoreBundle\Entity\SessionRelCourse; use Chamilo\CoreBundle\Entity\SessionRelCourseRelUser; use Chamilo\CoreBundle\Framework\Container; -use Chamilo\CoreBundle\Repository\SequenceRepository; use Chamilo\CoreBundle\Component\Utils\ActionIcon; use Chamilo\CoreBundle\Component\Utils\ObjectIcon; -use Chamilo\CoreBundle\Component\Utils\StateIcon; +use Chamilo\CoreBundle\Repository\SequenceResourceRepository; $cidReset = true; require_once __DIR__.'/../inc/global.inc.php'; @@ -373,7 +372,7 @@ $userListToShow .= $table->toHtml(); } -/** @var SequenceRepository $repo */ +/** @var SequenceResourceRepository $repo */ $repo = $em->getRepository(SequenceResource::class); $requirementAndDependencies = $repo->getRequirementAndDependencies( $sessionId, diff --git a/public/main/template/default/admin/resource_sequence.html.twig b/public/main/template/default/admin/resource_sequence.html.twig index 08cdabaa2fd..09e9e255da8 100644 --- a/public/main/template/default/admin/resource_sequence.html.twig +++ b/public/main/template/default/admin/resource_sequence.html.twig @@ -158,7 +158,7 @@ $('button[name="use_as_reference"]').on('click', function (e) { e.preventDefault(); $('button[name="set_requirement"]').prop('disabled', false); - $('#requirements').prop('disabled', false).selectpicker('refresh'); + $('#requirements').prop('disabled', false); $('button[name="save_resource"]').prop('disabled', false); useAsReference(type, sequenceId); diff --git a/src/CoreBundle/Controller/Admin/IndexBlocksController.php b/src/CoreBundle/Controller/Admin/IndexBlocksController.php index 995a45ebb0f..9acabf3634e 100644 --- a/src/CoreBundle/Controller/Admin/IndexBlocksController.php +++ b/src/CoreBundle/Controller/Admin/IndexBlocksController.php @@ -375,10 +375,7 @@ private function getItemsCourses(): array ]; $items[] = [ 'class' => 'item-resource-sequence', - 'url' => $this->generateUrl('legacy_main', [ - 'name' => 'admin/resource_sequence.php', - 'query' => ['type' => SequenceResource::COURSE_TYPE], - ]), + 'url' => $this->generateUrl('legacy_main', ['name' => 'admin/resource_sequence.php', 'type' => SequenceResource::COURSE_TYPE]), 'label' => $this->translator->trans('Resources sequencing'), ]; @@ -832,10 +829,7 @@ private function getItemsSessions(): array ]; $items[] = [ 'class' => 'item-resource-sequence', - 'url' => $this->generateUrl('legacy_main', [ - 'name' => 'admin/resource_sequence.php', - 'query' => ['type' => SequenceResource::SESSION_TYPE], - ]), + 'url' => $this->generateUrl('legacy_main', ['name' => 'admin/resource_sequence.php', 'type' => SequenceResource::SESSION_TYPE]), 'label' => $this->translator->trans('Resources sequencing'), ]; $items[] = [ diff --git a/src/CoreBundle/Controller/CourseController.php b/src/CoreBundle/Controller/CourseController.php index aa7d857ffcc..c75f4825acf 100644 --- a/src/CoreBundle/Controller/CourseController.php +++ b/src/CoreBundle/Controller/CourseController.php @@ -9,6 +9,7 @@ use Chamilo\CoreBundle\Entity\Course; use Chamilo\CoreBundle\Entity\CourseRelUser; use Chamilo\CoreBundle\Entity\ExtraField; +use Chamilo\CoreBundle\Entity\SequenceResource; use Chamilo\CoreBundle\Entity\Session; use Chamilo\CoreBundle\Entity\SessionRelUser; use Chamilo\CoreBundle\Entity\Tag; @@ -22,6 +23,7 @@ use Chamilo\CoreBundle\Repository\LegalRepository; use Chamilo\CoreBundle\Repository\Node\CourseRepository; use Chamilo\CoreBundle\Repository\Node\IllustrationRepository; +use Chamilo\CoreBundle\Repository\SequenceResourceRepository; use Chamilo\CoreBundle\Repository\TagRepository; use Chamilo\CoreBundle\Security\Authorization\Voter\CourseVoter; use Chamilo\CoreBundle\Service\CourseService; @@ -50,7 +52,10 @@ use Exception; use Exercise; use ExtraFieldValue; +use Fhaculty\Graph\Graph; +use Graphp\GraphViz\GraphViz; use Symfony\Bridge\Doctrine\Attribute\MapEntity; +use Symfony\Bundle\SecurityBundle\Security; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpFoundation\Request; @@ -256,6 +261,64 @@ public function indexJson( ] ); } + #[Route('/{courseId}/next-course', name: 'chamilo_course_next_course')] + public function getNextCourse( + int $courseId, + Request $request, + SequenceResourceRepository $repo, + Security $security, + SettingsManager $settingsManager, + EntityManagerInterface $em + ): JsonResponse { + $sessionId = $request->query->getInt('sid'); + $useDependents = $request->query->getBoolean('dependents', false); + $user = $security->getUser(); + $userId = $user->getId(); + + if ($useDependents) { + $sequences = $repo->getDependents($courseId, SequenceResource::COURSE_TYPE); + $checked = $repo->checkDependentsForUser($sequences, SequenceResource::COURSE_TYPE, $userId, $sessionId); + $isUnlocked = $repo->checkSequenceAreCompleted($checked); + $sequenceResource = $repo->findRequirementForResource($courseId, SequenceResource::COURSE_TYPE); + } else { + $sequences = $repo->getRequirements($courseId, SequenceResource::COURSE_TYPE); + + $hasValidRequirement = false; + foreach ($sequences as $sequence) { + foreach ($sequence['requirements'] ?? [] as $resource) { + if ($resource instanceof Course) { + $hasValidRequirement = true; + break 2; + } + } + } + + if (!$hasValidRequirement) { + return new JsonResponse([]); + } + + $checked = $repo->checkRequirementsForUser($sequences, SequenceResource::COURSE_TYPE, $userId, $sessionId); + $isUnlocked = $repo->checkSequenceAreCompleted($checked); + $sequenceResource = $repo->findRequirementForResource($courseId, SequenceResource::COURSE_TYPE); + } + + $graphImage = null; + + if ($sequenceResource && $sequenceResource->hasGraph()) { + $graph = $sequenceResource->getSequence()->getUnSerializeGraph(); + if ($graph !== null) { + $graph->setAttribute('graphviz.node.fontname', 'arial'); + $graphviz = new GraphViz(); + $graphImage = $graphviz->createImageSrc($graph); + } + } + + return new JsonResponse([ + 'sequenceList' => array_values($checked), + 'allowSubscription' => $isUnlocked, + 'graph' => $graphImage, + ]); + } /** * Redirects the page to a tool, following the tools settings. diff --git a/src/CoreBundle/Controller/PlatformConfigurationController.php b/src/CoreBundle/Controller/PlatformConfigurationController.php index 08f7dcb1232..913ea8d9fec 100644 --- a/src/CoreBundle/Controller/PlatformConfigurationController.php +++ b/src/CoreBundle/Controller/PlatformConfigurationController.php @@ -113,6 +113,8 @@ public function list(SettingsManager $settingsManager): Response 'session.session_automatic_creation_user_id', 'session.session_list_view_remaining_days', 'profile.use_users_timezone', + 'session.user_session_display_mode', + 'course.resource_sequence_show_dependency_in_course_intro', ]; $user = $this->userHelper->getCurrent(); diff --git a/src/CoreBundle/DataFixtures/SettingsCurrentFixtures.php b/src/CoreBundle/DataFixtures/SettingsCurrentFixtures.php index fc9c90feb19..3a1d19cb7ba 100644 --- a/src/CoreBundle/DataFixtures/SettingsCurrentFixtures.php +++ b/src/CoreBundle/DataFixtures/SettingsCurrentFixtures.php @@ -114,6 +114,11 @@ public static function getExistingSettings(): array ], ], 'session' => [ + [ + 'name' => 'user_session_display_mode', + 'title' => 'My Sessions display mode', + 'comment' => 'Choose how the "My Sessions" page is displayed: as a modern visual block (card) view or the classic list style.', + ], [ 'name' => 'session_list_view_remaining_days', 'title' => 'Show remaining days in My Sessions', @@ -248,6 +253,11 @@ public static function getExistingSettings(): array ], ], 'course' => [ + [ + 'name' => 'course_sequence_valid_only_in_same_session', + 'title' => 'Validate prerequisites only within the same session', + 'comment' => 'When enabled, a course will be considered validated only if passed within the current session. If disabled, courses passed in other sessions will also unlock dependent courses.', + ], [ 'name' => 'access_url_specific_files', 'title' => 'Enable URL-specific files', diff --git a/src/CoreBundle/Repository/SequenceResourceRepository.php b/src/CoreBundle/Repository/SequenceResourceRepository.php index e0fc1b74268..bae9a8b3144 100644 --- a/src/CoreBundle/Repository/SequenceResourceRepository.php +++ b/src/CoreBundle/Repository/SequenceResourceRepository.php @@ -11,7 +11,9 @@ use Chamilo\CoreBundle\Entity\GradebookCategory; use Chamilo\CoreBundle\Entity\SequenceResource; use Chamilo\CoreBundle\Entity\Session; +use Chamilo\CoreBundle\Entity\SessionRelCourse; use Chamilo\CoreBundle\Entity\SessionRelUser; +use Chamilo\CoreBundle\Settings\SettingsManager; use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; use Doctrine\Persistence\ManagerRegistry; use Fhaculty\Graph\Set\Vertices; @@ -20,8 +22,10 @@ class SequenceResourceRepository extends ServiceEntityRepository { - public function __construct(ManagerRegistry $registry) - { + public function __construct( + ManagerRegistry $registry, + private readonly SettingsManager $settingsManager + ) { parent::__construct($registry, SequenceResource::class); } @@ -259,8 +263,8 @@ public function checkRequirementsForUser(array $sequences, int $type, int $userI $course = $sessionCourse->getCourse(); $categories = $gradebookCategoryRepo->findBy( [ - 'courseCode' => $course->getCode(), - 'sessionId' => $resource->getId(), + 'course' => $course, + 'session' => $resource, 'isRequirement' => true, ] ); @@ -269,7 +273,10 @@ public function checkRequirementsForUser(array $sequences, int $type, int $userI if (!empty($userId)) { $resourceItem['status'] = $resourceItem['status'] && Category::userFinishedCourse( $userId, - $category + $category, + true, + $course->getId(), + $resource->getId() ); } } @@ -316,15 +323,23 @@ public function checkRequirementsForUser(array $sequences, int $type, int $userI public function checkCourseRequirements(int $userId, Course $course, int $sessionId = 0): bool { $em = $this->getEntityManager(); - + $session = $sessionId > 0 + ? $em->getRepository(Session::class)->find($sessionId) + : null; $gradebookCategoryRepo = $em->getRepository(GradebookCategory::class); - $categories = $gradebookCategoryRepo->findBy( - [ - 'courseCode' => $course->getCode(), - 'sessionId' => $sessionId, + $categories = $gradebookCategoryRepo->findBy([ + 'course' => $course, + 'session' => $session, + 'isRequirement' => true, + ]); + + if (empty($categories) && $sessionId > 0) { + $categories = $gradebookCategoryRepo->findBy([ + 'course' => $course, + 'session' => null, 'isRequirement' => true, - ] - ); + ]); + } if (empty($categories)) { return false; @@ -335,16 +350,12 @@ public function checkCourseRequirements(int $userId, Course $course, int $sessio $userFinishedCourse = Category::userFinishedCourse( $userId, $category, - true + true, + $course->getId(), + $sessionId ); - if (0 === $sessionId) { - if (!$userFinishedCourse) { - $status = false; - - break; - } - } elseif (!$userFinishedCourse) { + if (!$userFinishedCourse) { $status = false; break; @@ -364,6 +375,11 @@ public function checkCourseRequirements(int $userId, Course $course, int $sessio public function checkSequenceAreCompleted(array $sequences) { foreach ($sequences as $sequence) { + + if (!isset($sequence['requirements'])) { + continue; + } + $status = true; foreach ($sequence['requirements'] as $item) { @@ -414,4 +430,159 @@ protected function findVerticesEdges(Vertices $verticesEdges, int $type) return $sessionVertices; } + + public function getDependents(int $resourceId, int $type): array + { + return $this->getRequirementsOrDependents($resourceId, $type, 'dependents'); + } + + public function checkDependentsForUser(array $sequences, int $type, int $userId, int $sessionId = 0): array + { + return $this->checkRequirementsOrDependentsForUser( + $sequences, + $type, + 'dependents', + $userId, + $sessionId + ); + } + + private function getRequirementsOrDependents(int $resourceId, int $resourceType, string $itemType): array + { + $em = $this->getEntityManager(); + + $sequencesResource = $this->findBy(['resourceId' => $resourceId, 'type' => $resourceType]); + $result = []; + + foreach ($sequencesResource as $sequenceResource) { + if (!$sequenceResource->hasGraph()) { + continue; + } + + $sequence = $sequenceResource->getSequence(); + $graph = $sequence->getUnSerializeGraph(); + $vertex = $graph->getVertex($resourceId); + + $edges = $itemType === 'requirements' + ? $vertex->getVerticesEdgeFrom() + : $vertex->getVerticesEdgeTo(); + + $sequenceInfo = [ + 'name' => $sequence->getTitle(), + $itemType => [], + ]; + + foreach ($edges as $edge) { + $vertexId = $edge->getId(); + $resource = null; + + switch ($resourceType) { + case SequenceResource::SESSION_TYPE: + $resource = $em->getRepository(Session::class)->find($vertexId); + break; + case SequenceResource::COURSE_TYPE: + $resource = $em->getRepository(Course::class)->find($vertexId); + break; + } + + if (null === $resource) { + continue; + } + + $sequenceInfo[$itemType][$vertexId] = $resource; + } + + $result[$sequence->getId()] = $sequenceInfo; + } + + return $result; + } + + private function checkRequirementsOrDependentsForUser( + array $sequences, + int $resourceType, + string $itemType, + int $userId, + int $sessionId = 0 + ): array { + $sequenceList = []; + $em = $this->getEntityManager(); + $gradebookCategoryRepo = $em->getRepository(GradebookCategory::class); + + $sessionUserList = []; + $checkOnlySameSession = $this->settingsManager->getSetting('course.course_sequence_valid_only_in_same_session', true); + + if (SequenceResource::COURSE_TYPE === $resourceType) { + if ($checkOnlySameSession) { + $sessionUserList = [$sessionId]; + } else { + $sessions = $em->getRepository(SessionRelUser::class)->findBy(['user' => $userId]); + foreach ($sessions as $sessionRelUser) { + $sessionUserList[] = $sessionRelUser->getSession()->getId(); + } + } + } + + foreach ($sequences as $sequenceId => $sequence) { + $item = ['name' => $sequence['name'], $itemType => []]; + + foreach ($sequence[$itemType] as $resource) { + switch ($resourceType) { + case SequenceResource::SESSION_TYPE: + $id = $resource->getId(); + $resourceItem = ['name' => $resource->getName(), 'status' => true]; + + /* @var SessionRelCourse $sessionCourse */ + foreach ($resource->getCourses() as $sessionCourse) { + $course = $sessionCourse->getCourse(); + $session = $sessionCourse->getSession(); + $categories = $gradebookCategoryRepo->findBy([ + 'course' => $course, + 'session' => $session, + 'isRequirement' => true, + ]); + + foreach ($categories as $category) { + $resourceItem['status'] = $resourceItem['status'] && Category::userFinishedCourse( + $userId, + $category, + true, + $course->getId(), + $sessionId + ); + } + } + break; + + case SequenceResource::COURSE_TYPE: + $id = $resource->getId(); + $status = $this->checkCourseRequirements($userId, $resource, $sessionId); + + if (!$status) { + foreach (SessionManager::get_session_by_course($id) as $session) { + if (in_array($session['id'], $sessionUserList)) { + $status = $this->checkCourseRequirements($userId, $resource, $session['id']); + if ($status) break; + } + } + } + + $resourceItem = [ + 'name' => $resource->getTitle(), + 'code' => $resource->getCode(), + 'status' => $status, + ]; + break; + } + + if (!empty($id)) { + $item[$itemType][$id] = $resourceItem; + } + } + + $sequenceList[$sequenceId] = $item; + } + + return $sequenceList; + } } diff --git a/src/CoreBundle/Settings/CourseSettingsSchema.php b/src/CoreBundle/Settings/CourseSettingsSchema.php index 3bd07f202d4..538800315bd 100644 --- a/src/CoreBundle/Settings/CourseSettingsSchema.php +++ b/src/CoreBundle/Settings/CourseSettingsSchema.php @@ -109,6 +109,7 @@ public function buildSettings(AbstractSettingsBuilder $builder): void 'course_student_info' => '', 'course_catalog_settings' => '', 'resource_sequence_show_dependency_in_course_intro' => 'false', + 'course_sequence_valid_only_in_same_session' => 'false', 'course_catalog_display_in_home' => 'false', 'course_creation_form_set_course_category_mandatory' => 'false', 'course_creation_form_hide_course_code' => 'false', @@ -302,6 +303,7 @@ public function buildForm(FormBuilderInterface $builder): void ] ) ->add('resource_sequence_show_dependency_in_course_intro', YesNoType::class) + ->add('course_sequence_valid_only_in_same_session', YesNoType::class) ->add('course_catalog_display_in_home', YesNoType::class) ->add('course_creation_form_set_course_category_mandatory', YesNoType::class) ->add('course_creation_form_hide_course_code', YesNoType::class) diff --git a/src/CoreBundle/Settings/SessionSettingsSchema.php b/src/CoreBundle/Settings/SessionSettingsSchema.php index 6f8b9c39827..e610cee22d5 100644 --- a/src/CoreBundle/Settings/SessionSettingsSchema.php +++ b/src/CoreBundle/Settings/SessionSettingsSchema.php @@ -83,6 +83,7 @@ public function buildSettings(AbstractSettingsBuilder $builder): void 'enable_auto_reinscription' => 'false', 'enable_session_replication' => 'false', 'session_list_view_remaining_days' => 'false', + 'user_session_display_mode' => 'card', ] ) ; @@ -209,6 +210,12 @@ public function buildForm(FormBuilderInterface $builder): void ->add('session_model_list_field_ordered_by_id', YesNoType::class) ->add('duplicate_specific_session_content_on_session_copy', YesNoType::class) ->add('session_list_view_remaining_days', YesNoType::class) + ->add('user_session_display_mode', ChoiceType::class, [ + 'choices' => [ + 'Card (visual blocks)' => 'card', + 'List (classic)' => 'list', + ], + ]) ; $this->updateFormFieldsFromSettingsInfo($builder);