Merge branch 'master' of github.com:chamilo/chamilo-lms

pull/5946/head
Angel Fernando Quiroz Campos 10 months ago
commit 45498fec44
No known key found for this signature in database
GPG Key ID: B284841AE3E562CD
  1. 6
      assets/vue/components/Login.vue
  2. 24
      assets/vue/components/layout/TopbarNotLoggedIn.vue
  3. 9
      assets/vue/graphql/queries/CourseRelUser.js
  4. 109
      assets/vue/router/index.js
  5. 67
      assets/vue/services/courseService.js
  6. 28
      assets/vue/views/course/CourseHome.vue
  7. 98
      assets/vue/views/user/courses/List.vue
  8. 228
      public/main/course_info/infocours.php
  9. 20
      public/main/exercise/exercise.class.php
  10. 45
      public/main/lp/learnpath.class.php
  11. 4
      public/main/lp/lp_edit.php
  12. 2
      public/main/lp/lp_list.php
  13. 30
      src/CoreBundle/Command/SendEventRemindersCommand.php
  14. 46
      src/CoreBundle/Controller/CourseController.php
  15. 8
      src/CoreBundle/Controller/PlatformConfigurationController.php
  16. 16
      src/CourseBundle/Entity/CCalendarEvent.php
  17. 13
      src/CourseBundle/Repository/CLpRepository.php
  18. 16
      src/CourseBundle/Repository/CQuizRepository.php
  19. 7
      var/vue_templates/components/SidebarLogin.vue
  20. 24
      var/vue_templates/components/layout/SidebarNotLoggedIn.vue

@ -49,6 +49,7 @@
/>
<a
v-if="allowRegistration"
v-t="'Register oneself'"
class="btn btn--primary-outline"
href="/main/auth/inscription.php"
@ -72,7 +73,7 @@
</template>
<script setup>
import { ref } from "vue"
import { ref, computed } from "vue"
import Button from "primevue/button"
import InputText from "primevue/inputtext"
import Password from "primevue/password"
@ -80,8 +81,11 @@ import InputSwitch from "primevue/inputswitch"
import { useI18n } from "vue-i18n"
import { useLogin } from "../composables/auth/login"
import ExternalLoginButtons from "./login/LoginExternalButtons.vue"
import { usePlatformConfig } from "../store/platformConfig"
const { t } = useI18n()
const platformConfigStore = usePlatformConfig()
const allowRegistration = computed(() => "false" !== platformConfigStore.getSetting("registration.allow_registration"))
const { redirectNotAuthenticated, performLogin, isLoading } = useLogin()

@ -15,6 +15,7 @@ import { useI18n } from "vue-i18n"
import { useRouter } from "vue-router"
import { useLocale } from "../../composables/locale"
import PlatformLogo from "./PlatformLogo.vue"
import {usePlatformConfig} from "../../store/platformConfig"
const { t } = useI18n()
const router = useRouter()
@ -27,7 +28,11 @@ const languageItems = languageList.map((language) => ({
command: (event) => reloadWithLocale(event.item.isoCode),
}))
const menuItems = computed(() => [
const platformConfigStore = usePlatformConfig()
const allowRegistration = computed(() => "false" !== platformConfigStore.getSetting("registration.allow_registration"))
const menuItems = computed(() => {
const items = [
{
label: t("Home"),
url: router.resolve({ name: "Index" }).href,
@ -36,10 +41,6 @@ const menuItems = computed(() => [
label: t("FAQ"),
url: router.resolve({ name: "Faq" }).href,
},
{
label: t("Registration"),
url: "/main/auth/inscription.php",
},
{
label: t("Demo"),
url: router.resolve({ name: "Demo" }).href,
@ -53,5 +54,16 @@ const menuItems = computed(() => [
label: currentLanguageFromList.originalName,
items: languageItems,
},
])
]
if (allowRegistration.value) {
items.splice(2, 0, {
label: t("Registration"),
url: "/main/auth/inscription.php",
})
}
console.log("Menu Items:", items)
return items
})
</script>

@ -1,9 +1,10 @@
import gql from 'graphql-tag';
export const GET_COURSE_REL_USER = gql`
query getCourses($user: String!) {
courseRelUsers(user: $user) {
query getCourses($user: String!, $first: Int!, $after: String) {
courseRelUsers(user: $user, first: $first, after: $after) {
edges {
cursor
node {
course {
_id,
@ -26,6 +27,10 @@ export const GET_COURSE_REL_USER = gql`
}
}
}
pageInfo {
endCursor
hasNextPage
}
}
}
`;

@ -43,7 +43,9 @@ import courseService from "../services/courseService"
import catalogueCourses from "./cataloguecourses"
import catalogueSessions from "./cataloguesessions"
import {customVueTemplateEnabled} from "../config/env"
import { useUserSessionSubscription } from "../composables/userPermissions"
import {useCourseSettings} from "../store/courseSettingStore"
import {checkIsAllowedToEdit, useUserSessionSubscription} from "../composables/userPermissions"
import {usePlatformConfig} from "../store/platformConfig"
const router = createRouter({
history: createWebHistory(),
@ -97,17 +99,97 @@ const router = createRouter({
name: "CourseHome",
component: CourseHome,
beforeEnter: async (to) => {
try {
const check = await courseService.checkLegal(to.params.id, to.query?.sid)
const courseId = to.params.id
const sessionId = to.query?.sid
const autoLaunchKey = `course_autolaunch_${courseId}`
const hasAutoLaunched = sessionStorage.getItem(autoLaunchKey)
if (hasAutoLaunched === "true") {
return true
}
try {
const check = await courseService.checkLegal(courseId, sessionId)
if (check.redirect) {
window.location.href = check.url
return false
}
} catch (e) {
const course = await courseService.getCourseDetails(courseId)
if (!course) {
return false
}
const isAllowedToEdit = await checkIsAllowedToEdit(true, true, true)
if (isAllowedToEdit) {
return true
}
const courseSettingsStore = useCourseSettings()
await courseSettingsStore.loadCourseSettings(courseId, sessionId)
// Document auto-launch
const documentAutoLaunch = parseInt(courseSettingsStore.getSetting("enable_document_auto_launch"), 10) || 0
if (documentAutoLaunch === 1 && course.resourceNode?.id) {
sessionStorage.setItem(autoLaunchKey, "true")
window.location.href = `/resources/document/${course.resourceNode.id}/?cid=${courseId}`
+ (sessionId ? `&sid=${sessionId}` : '')
return false
}
// Exercise auto-launch
const platformConfigStore = usePlatformConfig()
const isExerciseAutoLaunchEnabled = "true" === platformConfigStore.getSetting("exercise.allow_exercise_auto_launch")
if (isExerciseAutoLaunchEnabled) {
const exerciseAutoLaunch = parseInt(courseSettingsStore.getSetting("enable_exercise_auto_launch"), 10) || 0
if (exerciseAutoLaunch === 2) {
sessionStorage.setItem(autoLaunchKey, "true")
window.location.href = `/main/exercise/exercise.php?cid=${courseId}`
+ (sessionId ? `&sid=${sessionId}` : '')
return false
} else if (exerciseAutoLaunch === 1) {
const exerciseId = await courseService.getAutoLaunchExerciseId(courseId, sessionId)
if (exerciseId) {
sessionStorage.setItem(autoLaunchKey, "true")
window.location.href = `/main/exercise/overview.php?exerciseId=${exerciseId}&cid=${courseId}`
+ (sessionId ? `&sid=${sessionId}` : '')
return false
}
}
}
// Learning path auto-launch
const lpAutoLaunch = parseInt(courseSettingsStore.getSetting("enable_lp_auto_launch"), 10) || 0
if (lpAutoLaunch === 2) {
sessionStorage.setItem(autoLaunchKey, "true")
window.location.href = `/main/lp/lp_controller.php?cid=${courseId}`
+ (sessionId ? `&sid=${sessionId}` : '')
return false
} else if (lpAutoLaunch === 1) {
const lpId = await courseService.getAutoLaunchLPId(courseId, sessionId)
if (lpId) {
sessionStorage.setItem(autoLaunchKey, "true")
window.location.href = `/main/lp/lp_controller.php?lp_id=${lpId}&cid=${courseId}&action=view&isStudentView=true`
+ (sessionId ? `&sid=${sessionId}` : '')
return false
}
}
// Forum auto-launch
const forumAutoLaunch = parseInt(courseSettingsStore.getSetting("enable_forum_auto_launch"), 10) || 0
if (forumAutoLaunch === 1) {
sessionStorage.setItem(autoLaunchKey, "true")
window.location.href = `/main/forum/index.php?cid=${courseId}`
+ (sessionId ? `&sid=${sessionId}` : '')
return false
}
} catch (error) {
console.error("Error during CourseHome route guard:", error)
}
return true
},
},
{
@ -175,6 +257,24 @@ const router = createRouter({
router.beforeEach(async (to, from, next) => {
const securityStore = useSecurityStore()
if (!securityStore.isAuthenticated) {
sessionStorage.clear()
}
let cid = parseInt(to.query?.cid ?? 0)
if ("CourseHome" === to.name) {
cid = parseInt(to.params?.id ?? 0)
}
if (!cid) {
for (const key in sessionStorage) {
if (key.startsWith('course_autolaunch_')) {
sessionStorage.removeItem(key)
}
}
}
if (to.matched.some((record) => record.meta.requiresAuth)) {
if (!securityStore.isLoading) {
await securityStore.checkSession()
@ -183,6 +283,7 @@ router.beforeEach(async (to, from, next) => {
if (securityStore.isAuthenticated) {
next()
} else {
sessionStorage.clear()
next({
path: "/login",
query: { redirect: to.fullPath },

@ -106,4 +106,71 @@ export default {
value: item.id,
}))
},
/**
* Fetches course details by course ID.
*
* @param {number} courseId - The ID of the course.
* @returns {Promise<Object|null>} - The course details or null if an error occurs.
*/
getCourseDetails: async (courseId) => {
try {
const response = await api.get(`/api/courses/${courseId}`)
return response.data
} catch (error) {
console.error("Error fetching course details:", error)
return null
}
},
/**
* Retrieves the ID of the auto-launchable exercise in a course, if configured.
*
* @param {number} courseId - The ID of the course.
* @param {number=} sessionId - The ID of the session (optional).
* @returns {Promise<number|null>} The ID of the auto-launchable exercise, or null if none exists.
*/
getAutoLaunchExerciseId: async (courseId, sessionId = 0) => {
try {
const { data } = await api.get(`/course/${courseId}/getAutoLaunchExerciseId`, {
params: {
sid: sessionId,
},
});
if (data && data.exerciseId) {
return data.exerciseId;
}
return null;
} catch (error) {
console.error("Error fetching auto-launch exercise ID:", error);
return null;
}
},
/**
* Retrieves the ID of the auto-launchable learnpaths in a course, if configured.
*
* @param {number} courseId - The ID of the course.
* @param {number=} sessionId - The ID of the session (optional).
* @returns {Promise<number|null>} The ID of the auto-launchable learnpath, or null if none exists.
*/
getAutoLaunchLPId: async (courseId, sessionId = 0) => {
try {
const { data } = await api.get(`/course/${courseId}/getAutoLaunchLPId`, {
params: {
sid: sessionId,
},
});
if (data && data.lpId) {
return data.lpId;
}
return null;
} catch (error) {
console.error("Error fetching auto-launch LP ID:", error);
return null;
}
},
}

@ -81,6 +81,22 @@
<small v-if="session"> ({{ session.title }}) </small>
</h2>
<p v-if="isAllowedToEdit && documentAutoLaunch === 1" class="text-sm text-gray-600">
{{ t('Document auto-launch is enabled for students') }}
</p>
<p v-if="isAllowedToEdit && (exerciseAutoLaunch === 1 || exerciseAutoLaunch === 2)" class="text-sm text-gray-600">
{{ t('Exercise auto-launch is enabled for students') }}
</p>
<p v-if="isAllowedToEdit && (lpAutoLaunch === 1 || lpAutoLaunch === 2)" class="text-sm text-gray-600">
{{ t('LP auto-launch is enabled for students') }}
</p>
<p v-if="isAllowedToEdit && (forumAutoLaunch === 1 || forumAutoLaunch === 2)" class="text-sm text-gray-600">
{{ t('Forum auto-launch is enabled for students') }}
</p>
<div class="grow-0">
<StudentViewButton
v-if="course"
@ -220,6 +236,7 @@ import courseService from "../../services/courseService"
import CourseIntroduction from "../../components/course/CourseIntroduction.vue"
import { usePlatformConfig } from "../../store/platformConfig"
import { useSecurityStore } from "../../store/securityStore"
import {useCourseSettings} from "../../store/courseSettingStore"
const { t } = useI18n()
const cidReqStore = useCidReqStore()
@ -244,6 +261,11 @@ provide("isCustomizing", isCustomizing)
const courseItems = ref([])
const routerTools = ["document", "link", "glossary", "agenda", "student_publication", "course_homepage"]
const documentAutoLaunch = ref(0)
const exerciseAutoLaunch = ref(0)
const lpAutoLaunch = ref(0)
const forumAutoLaunch = ref(0)
const courseSettingsStore = useCourseSettings()
courseService.loadCTools(course.value.id, session.value?.id).then((cTools) => {
tools.value = cTools.map((element) => {
@ -377,6 +399,12 @@ onMounted(async () => {
translateHtml()
}, 1000)
}
await courseSettingsStore.loadCourseSettings(course.value.id, session.value?.id)
documentAutoLaunch.value = parseInt(courseSettingsStore.getSetting("enable_document_auto_launch"), 10) || 0
exerciseAutoLaunch.value = parseInt(courseSettingsStore.getSetting("enable_exercise_auto_launch"), 10) || 0
lpAutoLaunch.value = parseInt(courseSettingsStore.getSetting("enable_lp_auto_launch"), 10) || 0
forumAutoLaunch.value = parseInt(courseSettingsStore.getSetting("enable_forum_auto_launch"), 10) || 0
})
const onStudentViewChanged = async () => {

@ -4,7 +4,7 @@
<hr />
<div
v-if="isLoading"
v-if="isLoading && courses.length === 0"
class="grid gap-4 grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4"
>
<Skeleton height="16rem" />
@ -23,10 +23,11 @@
</div>
<div
v-if="!isLoading && courses.length > 0"
v-if="courses.length > 0"
class="grid gap-4 grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4"
>
<CourseCardList :courses="courses" />
<div ref="lastCourseRef"></div>
</div>
<EmptyState
v-else-if="!isLoading && 0 === courses.length"
@ -37,7 +38,7 @@
</template>
<script setup>
import { onMounted, computed } from "vue"
import { ref, watch, onMounted, nextTick } from "vue"
import { useQuery } from "@vue/apollo-composable"
import { useI18n } from "vue-i18n"
import { GET_COURSE_REL_USER } from "../../../graphql/queries/CourseRelUser.js"
@ -50,15 +51,96 @@ import { useSecurityStore } from "../../../store/securityStore"
const securityStore = useSecurityStore()
const { t } = useI18n()
const { result, loading, refetch } = useQuery(GET_COURSE_REL_USER, () => ({
const courses = ref([])
const isLoading = ref(false)
const endCursor = ref(null)
const hasMore = ref(true)
const lastCourseRef = ref(null)
const { result, fetchMore } = useQuery(GET_COURSE_REL_USER, {
user: securityStore.user["@id"],
}))
first: 30,
after: null,
})
watch(result, (newResult) => {
if (newResult?.courseRelUsers) {
const newCourses = newResult.courseRelUsers.edges.map(({ node }) => node.course)
const isLoading = computed(() => loading.value)
const filteredCourses = newCourses.filter(
(newCourse) => !courses.value.some((existingCourse) => existingCourse._id === newCourse._id)
)
const courses = computed(() => result.value?.courseRelUsers.edges.map(({ node }) => node.course) ?? [])
courses.value.push(...filteredCourses)
endCursor.value = newResult.courseRelUsers.pageInfo.endCursor
hasMore.value = newResult.courseRelUsers.pageInfo.hasNextPage
nextTick(() => {
if (lastCourseRef.value) {
observer.observe(lastCourseRef.value)
}
})
}
isLoading.value = false
})
const loadMoreCourses = () => {
if (!hasMore.value || isLoading.value) return
isLoading.value = true
fetchMore({
variables: {
user: securityStore.user["@id"],
first: 10,
after: endCursor.value,
},
updateQuery: (previousResult, {fetchMoreResult}) => {
if (!fetchMoreResult) return previousResult
const newCourses = fetchMoreResult.courseRelUsers.edges.map(({ node }) => node.course)
const filteredCourses = newCourses.filter(
(newCourse) => !courses.value.some((existingCourse) => existingCourse._id === newCourse._id)
)
courses.value.push(...filteredCourses)
endCursor.value = fetchMoreResult.courseRelUsers.pageInfo.endCursor
hasMore.value = fetchMoreResult.courseRelUsers.pageInfo.hasNextPage
return {
...previousResult,
courseRelUsers: {
...fetchMoreResult.courseRelUsers,
edges: [...previousResult.courseRelUsers.edges, ...fetchMoreResult.courseRelUsers.edges],
},
}
},
}).finally(() => {
isLoading.value = false
})
}
let observer = new IntersectionObserver((entries) => {
if (entries[0].isIntersecting) {
loadMoreCourses();
}
}, {
rootMargin: '300px',
})
onMounted(() => {
refetch()
courses.value = []
endCursor.value = null
hasMore.value = true
isLoading.value = false
if (observer) observer.disconnect()
observer = new IntersectionObserver((entries) => {
if (entries[0].isIntersecting) {
loadMoreCourses()
}
}, {
rootMargin: '300px',
})
loadMoreCourses()
})
</script>

@ -290,19 +290,6 @@ if ('true' == api_get_setting('show_default_folders')) {
$myButton = $form->addButtonSave(get_lang('Save settings'), 'submit_save', true);
}
$group = [];
$group[] = $form->createElement(
'radio',
'enable_document_auto_launch',
get_lang('Auto-launch for documents'),
get_lang('Redirect to the document list'),
1
);
$group[] = $form->createElement('radio', 'enable_document_auto_launch', null, get_lang('Deactivate'), 0);
$globalGroup[get_lang('Auto-launch for documents')] = $group;
$globalGroup[] = $myButton;
$form->addPanelOption(
'documents',
get_lang('Documents'),
@ -567,24 +554,6 @@ $form->addPanelOption(
);
$globalGroup = [];
$group = [];
$group[] = $form->createElement(
'radio',
'enable_lp_auto_launch',
get_lang('Enable learning path auto-launch'),
get_lang('Redirect to a selected learning path'),
1
);
$group[] = $form->createElement(
'radio',
'enable_lp_auto_launch',
get_lang('Enable learning path auto-launch'),
get_lang('Redirect to the learning paths list'),
2
);
$group[] = $form->createElement('radio', 'enable_lp_auto_launch', null, get_lang('Disable'), 0);
$globalGroup[get_lang('Enable learning path auto-launch')] = $group;
if ('true' === api_get_setting('allow_course_theme')) {
// Allow theme into Learning path
@ -687,49 +656,6 @@ $form->addPanelOption(
false
);
if ('true' === api_get_setting('exercise.allow_exercise_auto_launch')) {
$globalGroup = [];
// Auto launch exercise
$group = [];
$group[] = $form->createElement(
'radio',
'enable_exercise_auto_launch',
get_lang('Auto-launch for exercises'),
get_lang('Redirect to the selected exercise'),
1
);
$group[] = $form->createElement(
'radio',
'enable_exercise_auto_launch',
get_lang('Auto-launch for exercises'),
get_lang('Redirect to the exercises list'),
2
);
$group[] = $form->createElement('radio', 'enable_exercise_auto_launch', null, get_lang('Disable'), 0);
$globalGroup[get_lang("Auto-launch for exercises")] = $group;
if ($isEditable) {
$myButton = $form->addButtonSave(get_lang('Save settings'), 'submit_save', true);
$globalGroup[] = $myButton;
} else {
// Is it allowed to edit the course settings?
if (!$isEditable) {
$disabled_output = "disabled";
}
$form->freeze();
}
$form->addPanelOption(
'config_exercise',
get_lang('Test'),
$globalGroup,
ToolIcon::QUIZ,
false
);
}
// START THEMATIC
$group = [];
$group[] = $form->createElement(
@ -802,10 +728,6 @@ if ('true' === api_get_setting('allow_public_certificates')) {
}
// Forum settings
$group = [
$form->createElement('radio', 'enable_forum_auto_launch', null, get_lang('Redirect to forums list'), 1),
$form->createElement('radio', 'enable_forum_auto_launch', null, get_lang('Disabled'), 2),
];
$myButton = $form->addButtonSave(get_lang('Save settings'), 'submit_save', true);
// Forum settings
@ -820,7 +742,6 @@ $addUsers = [
];
$globalGroup = [
get_lang('Enable forum auto-launch') => $group,
get_lang('Hide forum notifications') => $groupNotification,
get_lang('Subscribe automatically all users to all forum notifications') => $addUsers,
'' => $myButton,
@ -859,6 +780,83 @@ $form->addPanelOption(
false
);
// Auto-launch settings for documents, exercises, learning paths, and forums
$globalGroup = [];
$group = [];
// Auto-launch for documents
$group[] = $form->createElement(
'radio',
'auto_launch_option',
get_lang('Auto-launch for documents'),
get_lang('Redirect to the document list'),
'enable_document_auto_launch'
);
// Auto-launch for learning paths
$group[] = $form->createElement(
'radio',
'auto_launch_option',
get_lang('Enable learning path auto-launch'),
get_lang('Redirect to a selected learning path'),
'enable_lp_auto_launch'
);
$group[] = $form->createElement(
'radio',
'auto_launch_option',
get_lang('Enable learning path auto-launch'),
get_lang('Redirect to the learning paths list'),
'enable_lp_auto_launch_list'
);
// Auto-launch for exercises
$group[] = $form->createElement(
'radio',
'auto_launch_option',
get_lang('Auto-launch for exercises'),
get_lang('Redirect to the selected exercise'),
'enable_exercise_auto_launch'
);
$group[] = $form->createElement(
'radio',
'auto_launch_option',
get_lang('Auto-launch for exercises'),
get_lang('Redirect to the exercises list'),
'enable_exercise_auto_launch_list'
);
// Auto-launch for forums
$group[] = $form->createElement(
'radio',
'auto_launch_option',
get_lang('Auto-launch for forums'),
get_lang('Redirect to forums list'),
'enable_forum_auto_launch'
);
// Option to deactivate all auto-launch options
$group[] = $form->createElement(
'radio',
'auto_launch_option',
get_lang('Disable all auto-launch options'),
get_lang('Disable'),
'disable_auto_launch'
);
$myButton = $form->addButtonSave(get_lang('Save settings'), 'submit_save', true);
$globalGroup = [
get_lang('Auto-launch') => $group,
'' => $myButton,
];
$form->addPanelOption(
'autolaunch',
get_lang('Autolaunch settings'),
$globalGroup,
ToolIcon::COURSE,
false
);
$button = Display::toolbarButton(
get_lang('Configure external tools'),
$router->generate('chamilo_lti_configure', ['cid' => $courseId]).'?'.api_get_cidreq(),
@ -869,20 +867,6 @@ $html = [
$form->createElement('html', '<p>'.get_lang('LTI intro tool').'</p>'.$button),
];
/*$form->addPanelOption(
'lti_tool',
$translator->trans('External tools'),
$html,
ToolIcon::PLUGIN,
false
);*/
// Plugin course settings
//$appPlugin = new AppPlugin();
//$appPlugin->add_course_settings_form($form);
//$form->addHtml('</div>');
// Set the default values of the form
$values = [];
$values['title'] = $_course['name'];
@ -909,6 +893,29 @@ foreach ($courseSettings as $setting) {
if (!isset($values['student_delete_own_publication'])) {
$values['student_delete_own_publication'] = 0;
}
$documentAutoLaunch = api_get_course_setting('enable_document_auto_launch');
$lpAutoLaunch = api_get_course_setting('enable_lp_auto_launch');
$exerciseAutoLaunch = api_get_course_setting('enable_exercise_auto_launch');
$forumAutoLaunch = api_get_course_setting('enable_forum_auto_launch');
$defaultAutoLaunchOption = 'disable_auto_launch';
if ($documentAutoLaunch == 1) {
$defaultAutoLaunchOption = 'enable_document_auto_launch';
} elseif ($lpAutoLaunch == 1) {
$defaultAutoLaunchOption = 'enable_lp_auto_launch';
} elseif ($lpAutoLaunch == 2) {
$defaultAutoLaunchOption = 'enable_lp_auto_launch_list';
} elseif ($exerciseAutoLaunch == 1) {
$defaultAutoLaunchOption = 'enable_exercise_auto_launch';
} elseif ($exerciseAutoLaunch == 2) {
$defaultAutoLaunchOption = 'enable_exercise_auto_launch_list';
} elseif ($forumAutoLaunch == 1) {
$defaultAutoLaunchOption = 'enable_forum_auto_launch';
}
$values['auto_launch_option'] = $defaultAutoLaunchOption;
$form->setDefaults($values);
// Validate form
@ -990,10 +997,37 @@ if ($form->validate()) {
$activeLegal = $updateValues['activate_legal'] ?? 0;
/*$category = null;
if (!empty($updateValues['category_id'])) {
$category = $courseCategoryRepo->find($updateValues['category_id']);
}*/
$autoLaunchOption = $updateValues['auto_launch_option'] ?? 'disable_auto_launch';
$updateValues['enable_document_auto_launch'] = 0;
$updateValues['enable_lp_auto_launch'] = 0;
$updateValues['enable_lp_auto_launch_list'] = 0;
$updateValues['enable_exercise_auto_launch'] = 0;
$updateValues['enable_exercise_auto_launch_list'] = 0;
$updateValues['enable_forum_auto_launch'] = 0;
switch ($autoLaunchOption) {
case 'enable_document_auto_launch':
$updateValues['enable_document_auto_launch'] = 1;
break;
case 'enable_lp_auto_launch':
$updateValues['enable_lp_auto_launch'] = 1;
break;
case 'enable_lp_auto_launch_list':
$updateValues['enable_lp_auto_launch'] = 2;
break;
case 'enable_exercise_auto_launch':
$updateValues['enable_exercise_auto_launch'] = 1;
break;
case 'enable_exercise_auto_launch_list':
$updateValues['enable_exercise_auto_launch'] = 2;
break;
case 'enable_forum_auto_launch':
$updateValues['enable_forum_auto_launch'] = 1;
break;
case 'disable_auto_launch':
default:
break;
}
$courseEntity
->setTitle($updateValues['title'])

@ -8534,10 +8534,22 @@ class Exercise
*/
public function cleanCourseLaunchSettings()
{
$table = Database::get_course_table(TABLE_QUIZ_TEST);
$sql = "UPDATE $table SET autolaunch = 0
WHERE c_id = ".$this->course_id.' AND session_id = '.$this->sessionId;
Database::query($sql);
$em = Database::getManager();
$repo = Container::getQuizRepository();
$session = api_get_session_entity();
$course = api_get_course_entity();
$qb = $repo->getResourcesByCourse($course, $session);
$quizzes = $qb->getQuery()->getResult();
foreach ($quizzes as $quiz) {
$quiz->setAutoLaunch(false);
$em->persist($quiz);
}
$em->flush();
}
/**

@ -6960,31 +6960,30 @@ class learnpath
*/
public function set_autolaunch($lp_id, $status)
{
$course_id = api_get_course_int_id();
$lp_id = (int) $lp_id;
$status = (int) $status;
$lp_table = Database::get_course_table(TABLE_LP_MAIN);
$em = Database::getManager();
$repo = Container::getLpRepository();
// Setting everything to autolaunch = 0
$attributes['autolaunch'] = 0;
$where = [
'session_id = ? AND c_id = ? ' => [
api_get_session_id(),
$course_id,
],
];
Database::update($lp_table, $attributes, $where);
if (1 == $status) {
//Setting my lp_id to autolaunch = 1
$attributes['autolaunch'] = 1;
$where = [
'iid = ? AND session_id = ? AND c_id = ?' => [
$lp_id,
api_get_session_id(),
$course_id,
],
];
Database::update($lp_table, $attributes, $where);
$session = api_get_session_entity();
$course = api_get_course_entity();
$qb = $repo->getResourcesByCourse($course, $session);
$lps = $qb->getQuery()->getResult();
foreach ($lps as $lp) {
$lp->setAutoLaunch(0);
$em->persist($lp);
}
$em->flush();
if ($status === 1) {
$lp = $repo->find($lp_id);
if ($lp) {
$lp->setAutolaunch(1);
$em->persist($lp);
}
$em->flush();
}
}

@ -112,10 +112,10 @@ if ('true' === api_get_setting('allow_course_theme')) {
// Author
$form->addHtmlEditor(
'html_editor',
'lp_author',
get_lang('Author'),
['size' => 80],
false,
false,
['ToolbarSet' => 'LearningPathAuthor', 'Width' => '100%', 'Height' => '200px']
);
$form->applyFilter('lp_author', 'html_filter');

@ -684,7 +684,7 @@ foreach ($categories as $category) {
);
} else {
$lp_auto_launch_icon = Display::url(
Display::getMdiIcon('rocket-launch', 'ch-tool-icon', '', 22),
Display::getMdiIcon('rocket-launch', 'ch-tool-icon-disabled', '', 22),
api_get_self().'?'.$cidReq."&action=auto_launch&status=1&lp_id=$id",
['title' => htmlentities(get_lang('Enable learning path auto-launch'))]
);

@ -7,6 +7,7 @@ declare(strict_types=1);
namespace Chamilo\CoreBundle\Command;
use Chamilo\CoreBundle\Entity\AgendaReminder;
use Chamilo\CoreBundle\Entity\Session;
use Chamilo\CoreBundle\Entity\User;
use Chamilo\CoreBundle\Repository\Node\CourseRepository;
use Chamilo\CoreBundle\ServiceHelper\MessageHelper;
@ -160,11 +161,34 @@ class SendEventRemindersCommand extends Command
case 'session':
if ($session = $resourceLink->getSession()) {
foreach ($session->getUsers() as $sessionRelUser) {
$user = $sessionRelUser->getUser();
$course = $resourceLink->getCourse();
if (!$course) {
if ($debug) {
error_log("No course found for resource link in session ID: {$session->getId()}");
}
break;
}
$usersToNotify = [];
$studentSubscriptions = $session->getSessionRelCourseRelUsersByStatus($course, Session::STUDENT);
foreach ($studentSubscriptions as $studentSubscription) {
$usersToNotify[$studentSubscription->getUser()->getId()] = $studentSubscription->getUser();
}
$coachSubscriptions = $session->getSessionRelCourseRelUsersByStatus($course, Session::COURSE_COACH);
foreach ($coachSubscriptions as $coachSubscription) {
$usersToNotify[$coachSubscription->getUser()->getId()] = $coachSubscription->getUser();
}
$generalCoaches = $session->getGeneralCoaches();
foreach ($generalCoaches as $generalCoach) {
$usersToNotify[$generalCoach->getId()] = $generalCoach;
}
foreach ($usersToNotify as $user) {
$this->messageHelper->sendMessageSimple($user->getId(), $messageSubject, $messageContent, $senderId);
if ($debug) {
error_log("Message sent to user ID: {$user->getId()} for session event: ".$event->getTitle());
error_log("Message sent to user ID: {$user->getId()} ({$user->getUsername()}) for session event: {$event->getTitle()}");
}
$sentRemindersCount++;
}

@ -33,6 +33,8 @@ use Chamilo\CourseBundle\Entity\CCourseDescription;
use Chamilo\CourseBundle\Entity\CTool;
use Chamilo\CourseBundle\Entity\CToolIntro;
use Chamilo\CourseBundle\Repository\CCourseDescriptionRepository;
use Chamilo\CourseBundle\Repository\CLpRepository;
use Chamilo\CourseBundle\Repository\CQuizRepository;
use Chamilo\CourseBundle\Repository\CShortcutRepository;
use Chamilo\CourseBundle\Repository\CToolRepository;
use Chamilo\CourseBundle\Settings\SettingsCourseManager;
@ -756,6 +758,50 @@ class CourseController extends ToolBaseController
return new JsonResponse(['success' => false, 'message' => $translator->trans('An error occurred while creating the course.')]);
}
#[Route('/{id}/getAutoLaunchExerciseId', name: 'chamilo_core_course_get_auto_launch_exercise_id', methods: ['GET'])]
public function getAutoLaunchExerciseId(
Request $request,
Course $course,
CQuizRepository $quizRepository,
EntityManagerInterface $em
): JsonResponse {
$data = $request->getContent();
$data = json_decode($data);
$sessionId = $data->sid ?? 0;
$sessionRepo = $em->getRepository(Session::class);
$session = null;
if (!empty($sessionId)) {
$session = $sessionRepo->find($sessionId);
}
$autoLaunchExerciseId = $quizRepository->findAutoLaunchableQuizByCourseAndSession($course, $session);
return new JsonResponse(['exerciseId' => $autoLaunchExerciseId], Response::HTTP_OK);
}
#[Route('/{id}/getAutoLaunchLPId', name: 'chamilo_core_course_get_auto_launch_lp_id', methods: ['GET'])]
public function getAutoLaunchLPId(
Request $request,
Course $course,
CLPRepository $lpRepository,
EntityManagerInterface $em
): JsonResponse {
$data = $request->getContent();
$data = json_decode($data);
$sessionId = $data->sid ?? 0;
$sessionRepo = $em->getRepository(Session::class);
$session = null;
if (!empty($sessionId)) {
$session = $sessionRepo->find($sessionId);
}
$autoLaunchLPId = $lpRepository->findAutoLaunchableLPByCourseAndSession($course, $session);
return new JsonResponse(['lpId' => $autoLaunchLPId], Response::HTTP_OK);
}
private function autoLaunch(): void
{
$autoLaunchWarning = '';

@ -45,6 +45,9 @@ class PlatformConfigurationController extends AbstractController
'visual_theme' => $this->themeHelper->getVisualTheme(),
'external_authentication' => $this->authenticationConfigHelper->getEnabledProviders(),
];
$configuration['settings']['registration.allow_registration'] = $settingsManager->getSetting('registration.allow_registration', true);
$variables = [];
if ($this->isGranted('ROLE_USER')) {
@ -88,6 +91,7 @@ class PlatformConfigurationController extends AbstractController
'document.students_download_folders',
'social.hide_social_groups_block',
'course.show_course_duration',
'exercise.allow_exercise_auto_launch',
];
$user = $this->userHelper->getCurrent();
@ -142,6 +146,10 @@ class PlatformConfigurationController extends AbstractController
$settings = [
'show_course_in_user_language' => $courseSettingsManager->getCourseSettingValue('show_course_in_user_language'),
'allow_user_edit_agenda' => $courseSettingsManager->getCourseSettingValue('allow_user_edit_agenda'),
'enable_document_auto_launch' => $courseSettingsManager->getCourseSettingValue('enable_document_auto_launch'),
'enable_exercise_auto_launch' => $courseSettingsManager->getCourseSettingValue('enable_exercise_auto_launch'),
'enable_lp_auto_launch' => $courseSettingsManager->getCourseSettingValue('enable_lp_auto_launch'),
'enable_forum_auto_launch' => $courseSettingsManager->getCourseSettingValue('enable_forum_auto_launch'),
];
return new JsonResponse(['settings' => $settings]);

@ -513,6 +513,14 @@ class CCalendarEvent extends AbstractResource implements ResourceInterface, Stri
$resourceLinks = $this->resourceNode->getResourceLinks();
foreach ($resourceLinks as $link) {
if (null !== $link->getCourse() && null === $link->getSession()) {
return 'course';
}
if (null !== $link->getSession()) {
return 'session';
}
if (null === $link->getCourse()
&& null === $link->getSession()
&& null === $link->getGroup()
@ -520,14 +528,6 @@ class CCalendarEvent extends AbstractResource implements ResourceInterface, Stri
) {
return 'global';
}
if (null !== $link->getCourse()) {
return 'course';
}
if (null !== $link->getSession()) {
return 'session';
}
}
return 'personal';

@ -95,6 +95,19 @@ final class CLpRepository extends ResourceRepository implements ResourceWithLink
return $router->generate('legacy_main', $params);
}
public function findAutoLaunchableLPByCourseAndSession(Course $course, ?Session $session = null): ?int
{
$qb = $this->getResourcesByCourse($course, $session)
->select('resource.iid')
->andWhere('resource.autolaunch = 1');
$qb->setMaxResults(1);
$result = $qb->getQuery()->getOneOrNullResult();
return $result ? $result['iid'] : null;
}
protected function addNotDeletedQueryBuilder(?QueryBuilder $qb = null): QueryBuilder
{
$qb = $this->getOrCreateQueryBuilder($qb);

@ -133,4 +133,20 @@ final class CQuizRepository extends ResourceRepository implements ResourceWithLi
return $qb;
}
/**
* Finds the auto-launchable quiz for the given course and session.
*/
public function findAutoLaunchableQuizByCourseAndSession(Course $course, ?Session $session = null): ?int
{
$qb = $this->getResourcesByCourse($course, $session)
->select('resource.iid')
->andWhere('resource.autoLaunch = 1');
$qb->setMaxResults(1);
$result = $qb->getQuery()->getOneOrNullResult();
return $result ? $result['iid'] : null;
}
}

@ -1,11 +1,12 @@
<script setup>
import { ref } from "vue"
import {computed, ref} from "vue"
import { useI18n } from "vue-i18n"
import InputText from "primevue/inputtext"
import Password from "primevue/password"
import Button from "primevue/button"
import InputSwitch from "primevue/inputswitch"
import { useLogin } from "../../../assets/vue/composables/auth/login"
import {usePlatformConfig} from "../../../assets/vue/store/platformConfig"
const { t } = useI18n()
@ -15,6 +16,9 @@ const login = ref("")
const password = ref("")
const remember = ref(false)
const platformConfigStore = usePlatformConfig()
const allowRegistration = computed(() => "false" !== platformConfigStore.getSetting("registration.allow_registration"))
function onSubmitLoginForm() {
performLogin({
login: login.value,
@ -74,6 +78,7 @@ function onSubmitLoginForm() {
/>
<a
v-if="allowRegistration"
v-t="'Register oneself'"
class="btn btn--primary-outline"
href="/main/auth/inscription.php"

@ -7,6 +7,7 @@ import Dropdown from "primevue/dropdown"
import SidebarLogin from "../SidebarLogin.vue"
import PageList from "../../../../assets/vue/components/page/PageList.vue"
import { useLocale } from "../../../../assets/vue/composables/locale"
import { usePlatformConfig } from "../../../../assets/vue/store/platformConfig"
const { t } = useI18n()
const router = useRouter()
@ -22,7 +23,12 @@ const languageItems = languageList.map((language) => ({
isoCode: language.isocode,
}))
const menuItems = computed(() => [
const platformConfigStore = usePlatformConfig()
const allowRegistration = computed(() => "false" !== platformConfigStore.getSetting("registration.allow_registration"))
const menuItems = computed(() => {
const items = [
{
label: t("Home"),
url: router.resolve({ name: "Index" }).href,
@ -36,10 +42,6 @@ const menuItems = computed(() => [
},
],
},
{
label: t("Registration"),
url: "/main/auth/inscription.php",
},
{
label: t("Demo"),
url: router.resolve({ name: "Demo" }).href,
@ -52,7 +54,17 @@ const menuItems = computed(() => [
label: t("Contact"),
url: "/contact",
},
])
]
if (allowRegistration.value) {
items.splice(2, 0, {
label: t("Registration"),
url: "/main/auth/inscription.php",
})
}
return items
})
const sidebarIsOpen = ref(window.localStorage.getItem("sidebarIsOpen") === "true")

Loading…
Cancel
Save