Skip to content

Commit c761a6d

Browse files
Merge remote-tracking branch 'upstream/master' into ofaj-22774
2 parents 48d63bf + f8a217e commit c761a6d

File tree

102 files changed

+4998
-1032
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

102 files changed

+4998
-1032
lines changed

.yarn/releases/yarn-4.9.1.cjs renamed to .yarn/releases/yarn-4.9.2.cjs

Lines changed: 273 additions & 279 deletions
Large diffs are not rendered by default.

.yarnrc.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
nodeLinker: node-modules
22

3-
yarnPath: .yarn/releases/yarn-4.9.1.cjs
3+
yarnPath: .yarn/releases/yarn-4.9.2.cjs

assets/vue/components/Login.vue

Lines changed: 23 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
<template>
2-
<div class="login-section">
3-
<h2
4-
v-t="'Sign in'"
5-
class="login-section__title"
6-
/>
2+
<div
3+
v-if="!isInIframe"
4+
class="login-section"
5+
>
6+
<h2 class="login-section__title">{{ t("Sign in") }}</h2>
77

88
<form
99
class="login-section__form"
@@ -87,8 +87,17 @@
8787
</template>
8888

8989
<script setup>
90+
const isInIframe = window.self !== window.top
91+
if (isInIframe) {
92+
try {
93+
const parentUrl = window.top.location.href
94+
window.top.location.href = "/login?redirect=" + encodeURIComponent(parentUrl)
95+
} catch (e) {
96+
window.top.location.href = "/login"
97+
}
98+
}
99+
90100
import { computed, ref } from "vue"
91-
import { useRouter } from "vue-router"
92101
import Button from "primevue/button"
93102
import InputText from "primevue/inputtext"
94103
import Password from "primevue/password"
@@ -97,45 +106,28 @@ import { useI18n } from "vue-i18n"
97106
import { useLogin } from "../composables/auth/login"
98107
import LoginOAuth2Buttons from "./login/LoginOAuth2Buttons.vue"
99108
import { usePlatformConfig } from "../store/platformConfig"
109+
import { useRouter } from "vue-router"
100110
101111
const { t } = useI18n()
102-
103112
const router = useRouter()
104-
105113
const platformConfigStore = usePlatformConfig()
106114
const allowRegistration = computed(() => "false" !== platformConfigStore.getSetting("registration.allow_registration"))
107115
108-
const { redirectNotAuthenticated, performLogin, isLoading } = useLogin()
116+
const { redirectNotAuthenticated, performLogin, isLoading, requires2FA } = useLogin()
109117
110118
const login = ref("")
111119
const password = ref("")
112120
const totp = ref("")
113121
const remember = ref(false)
114-
const requires2FA = ref(false)
115122
116123
redirectNotAuthenticated()
117124
118125
async function onSubmitLoginForm() {
119-
try {
120-
const response = await performLogin({
121-
login: login.value,
122-
password: password.value,
123-
totp: requires2FA.value ? totp.value : null,
124-
_remember_me: remember.value,
125-
})
126-
127-
if (!response) {
128-
console.warn("[Login] No response from performLogin.")
129-
return
130-
}
131-
132-
if (response.requires2FA) {
133-
requires2FA.value = true
134-
} else {
135-
await router.replace({ name: "Home" })
136-
}
137-
} catch (error) {
138-
console.error("[Login] performLogin failed:", error)
139-
}
126+
const response = await performLogin({
127+
login: login.value,
128+
password: password.value,
129+
totp: requires2FA.value ? totp.value : null,
130+
_remember_me: remember.value,
131+
})
140132
}
141133
</script>

assets/vue/components/basecomponents/ChamiloIcons.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,4 +144,5 @@ export const chamiloIconToClass = {
144144
"clear-all": "mdi mdi-broom",
145145
"qrcode": "mdi mdi-qrcode",
146146
"minus": "mdi mdi-minus",
147+
"shield-check": "mdi mdi-shield-check",
147148
}

assets/vue/components/course/CatalogueCourseCard.vue

Lines changed: 42 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@
121121

122122
<div class="mt-auto pt-2">
123123
<router-link
124-
v-if="course.visibility === 3 || (course.visibility === 2 && isUserInCourse)"
124+
v-if="isUserInCourse && (course.visibility === 2 || course.visibility === 3)"
125125
:to="{ name: 'CourseHome', params: { id: course.id } }"
126126
>
127127
<Button
@@ -132,15 +132,23 @@
132132
</router-link>
133133

134134
<Button
135-
v-else-if="course.visibility === 2 && course.subscribe && !isUserInCourse"
135+
v-else-if="isLocked && hasRequirements"
136+
:label="$t('Check requirements')"
137+
icon="mdi mdi-shield-check"
138+
class="w-full p-button-warning"
139+
@click="showDependenciesModal = true"
140+
/>
141+
142+
<Button
143+
v-else-if="course.subscribe && props.currentUserId"
136144
:label="$t('Subscribe')"
137145
icon="pi pi-sign-in"
138146
class="w-full"
139147
@click="subscribeToCourse"
140148
/>
141149

142150
<Button
143-
v-else-if="course.visibility === 2 && !course.subscribe && !isUserInCourse"
151+
v-else-if="course.visibility === 2 && !course.subscribe && props.currentUserId"
144152
:label="$t('Subscription not allowed')"
145153
icon="pi pi-ban"
146154
disabled
@@ -165,6 +173,13 @@
165173
</div>
166174
</div>
167175
</div>
176+
<CatalogueRequirementModal
177+
v-model="showDependenciesModal"
178+
:course-id="course.id"
179+
:session-id="course.sessionId || 0"
180+
:requirements="requirementList"
181+
:graph-image="graphImage"
182+
/>
168183
<Dialog
169184
v-model:visible="showDescriptionDialog"
170185
:header="course.title"
@@ -179,21 +194,17 @@
179194
<script setup>
180195
import Rating from "primevue/rating"
181196
import Button from "primevue/button"
182-
import { computed, ref } from "vue"
183-
import courseRelUserService from "../../services/courseRelUserService"
197+
import Dialog from "primevue/dialog"
198+
import { computed, ref, onMounted } from "vue"
184199
import { useRoute, useRouter } from "vue-router"
185200
import { useNotification } from "../../composables/notification"
186-
import Dialog from "primevue/dialog"
187201
import { usePlatformConfig } from "../../store/platformConfig"
202+
import CatalogueRequirementModal from "./CatalogueRequirementModal.vue"
203+
import courseRelUserService from "../../services/courseRelUserService"
204+
import { useCourseRequirementStatus } from "../../composables/course/useCourseRequirementStatus"
188205
import { useLocale } from "../../composables/locale"
189-
const platformConfigStore = usePlatformConfig()
190-
const showDescriptionDialog = ref(false)
191206
const { getOriginalLanguageName } = useLocale()
192207
193-
const allowDescription = computed(
194-
() => platformConfigStore.getSetting("course.show_courses_descriptions_in_catalog") !== "false",
195-
)
196-
197208
const props = defineProps({
198209
course: Object,
199210
currentUserId: {
@@ -212,6 +223,14 @@ const emit = defineEmits(["rate", "subscribed"])
212223
const router = useRouter()
213224
const route = useRoute()
214225
const { showErrorNotification, showSuccessNotification } = useNotification()
226+
const platformConfigStore = usePlatformConfig()
227+
228+
const showDescriptionDialog = ref(false)
229+
const showDependenciesModal = ref(false)
230+
231+
const allowDescription = computed(
232+
() => platformConfigStore.getSetting("course.show_courses_descriptions_in_catalog") !== "false",
233+
)
215234
216235
const isUserInCourse = computed(() => {
217236
if (!props.currentUserId) return false
@@ -287,9 +306,7 @@ function routeExists(name) {
287306
288307
const linkSettings = computed(() => {
289308
const settings = platformConfigStore.getSetting("course.course_catalog_settings")
290-
const result = settings?.link_settings ?? {}
291-
console.log("Link settings:", result)
292-
return result
309+
return settings?.link_settings ?? {}
293310
})
294311
295312
const imageLink = computed(() => {
@@ -304,10 +321,6 @@ const imageLink = computed(() => {
304321
return { name: routeName, params: { id: props.course.id } }
305322
}
306323
307-
if (routeName) {
308-
console.warn(`[CatalogueCourseCard] Route '${routeName}' does not exist.`)
309-
}
310-
311324
return null
312325
})
313326
@@ -318,20 +331,21 @@ const titleLink = computed(() => {
318331
return { name: routeName, params: { id: props.course.id } }
319332
}
320333
321-
if (routeName) {
322-
console.warn(`[CatalogueCourseCard] Route '${routeName}' does not exist.`)
323-
}
324-
325334
return null
326335
})
327336
328337
const showInfoPopup = computed(() => {
329338
const allowed = ["course_description_popup"]
330339
const value = linkSettings.value.info_url
331-
if (value && !allowed.includes(value)) {
332-
console.warn(`[CatalogueCourseCard] info_url '${value}' is not a recognized option.`)
333-
return false
334-
}
335-
return value === "course_description_popup"
340+
return value && allowed.includes(value)
341+
})
342+
343+
const { isLocked, hasRequirements, requirementList, graphImage, fetchStatus } = useCourseRequirementStatus(
344+
props.course.id,
345+
props.course.sessionId || 0,
346+
)
347+
348+
onMounted(() => {
349+
fetchStatus()
336350
})
337351
</script>
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
<template>
2+
<Dialog
3+
v-model:visible="visible"
4+
modal
5+
:header="t('Session requirements and dependencies')"
6+
class="w-[30rem] z-[99999] !block !opacity-100"
7+
>
8+
<div v-if="hasData">
9+
<div
10+
v-for="section in requirements"
11+
:key="section.name"
12+
class="mb-4"
13+
>
14+
<h4 class="font-semibold text-gray-700 mb-2">
15+
{{ section.name }}
16+
</h4>
17+
<ul class="list-disc pl-5 text-sm text-gray-700">
18+
<li
19+
v-for="req in section.requirements"
20+
:key="req.name"
21+
class="flex items-center gap-2"
22+
>
23+
<i
24+
v-if="req.status !== null"
25+
:class="req.status
26+
? 'mdi mdi-check-circle text-green-500'
27+
: 'mdi mdi-alert-circle text-red-500'"
28+
></i>
29+
<span v-html="req.adminLink || req.name"></span>
30+
</li>
31+
</ul>
32+
</div>
33+
34+
<div
35+
v-if="graphImage"
36+
class="mt-4 text-center"
37+
>
38+
<img
39+
:src="graphImage"
40+
alt="Graph"
41+
class="max-w-full max-h-96 mx-auto border rounded"
42+
/>
43+
</div>
44+
</div>
45+
46+
<div
47+
v-else
48+
class="text-sm text-gray-500"
49+
>
50+
{{ t("No dependencies") }}
51+
</div>
52+
</Dialog>
53+
</template>
54+
55+
<script setup>
56+
import Dialog from "primevue/dialog"
57+
import { computed } from "vue"
58+
import { useI18n } from "vue-i18n"
59+
60+
const { t } = useI18n()
61+
62+
const props = defineProps({
63+
modelValue: Boolean,
64+
courseId: Number,
65+
sessionId: Number,
66+
requirements: Array,
67+
dependencies: Array,
68+
graphImage: String,
69+
})
70+
71+
const emit = defineEmits(["update:modelValue"])
72+
73+
const visible = computed({
74+
get: () => props.modelValue,
75+
set: (value) => emit("update:modelValue", value),
76+
})
77+
78+
const requirements = computed(() => props.requirements || [])
79+
const dependencies = computed(() => props.dependencies || [])
80+
81+
const hasData = computed(() => {
82+
return requirements.value.length > 0 || dependencies.value.length > 0
83+
})
84+
</script>

0 commit comments

Comments
 (0)