From c5302a0e0327ac20a8fa89c16fbcea20f1583a41 Mon Sep 17 00:00:00 2001 From: Christian Beeznest Date: Tue, 2 Dec 2025 21:33:19 -0500 Subject: [PATCH] Learnpath: Fix category deletion to remove category entity --- .../vue/components/lp/LpCategorySection.vue | 209 +++++++++++++----- assets/vue/views/lp/LpList.vue | 10 +- public/main/lp/learnpath.class.php | 39 ++-- 3 files changed, 189 insertions(+), 69 deletions(-) diff --git a/assets/vue/components/lp/LpCategorySection.vue b/assets/vue/components/lp/LpCategorySection.vue index e0bceeea96d..312f2ce7946 100644 --- a/assets/vue/components/lp/LpCategorySection.vue +++ b/assets/vue/components/lp/LpCategorySection.vue @@ -22,10 +22,18 @@ const props = defineProps({ buildDates: { type: Function, required: false }, }) const emit = defineEmits([ - "open","edit","report","settings","build", - "toggle-visible","toggle-publish","delete", - "export-scorm","export-pdf", - "reorder","toggle-auto-launch", + "open", + "edit", + "report", + "settings", + "build", + "toggle-visible", + "toggle-publish", + "delete", + "export-scorm", + "export-pdf", + "reorder", + "toggle-auto-launch", ]) const displayTitle = computed(() => props.title || t("Learning path categories")) @@ -33,32 +41,41 @@ const displayTitle = computed(() => props.title || t("Learning path categories") const localList = ref([...(props.list ?? [])]) const dragging = ref(false) -watch(() => props.list, (nv) => { - if (dragging.value) return - localList.value = [...(nv ?? [])] -}, { immediate: true }) +watch( + () => props.list, + (nv) => { + if (dragging.value) return + localList.value = [...(nv ?? [])] + }, + { immediate: true }, +) function onEndCat() { dragging.value = false - emit("reorder", localList.value.map(i => i.iid)) + emit( + "reorder", + localList.value.map((i) => i.iid), + ) } const route = useRoute() -const cid = computed(() => Number(route.query?.cid ?? 0) || undefined) -const sid = computed(() => Number(route.query?.sid ?? 0) || undefined) +const cid = computed(() => Number(route.query?.cid ?? 0) || undefined) +const sid = computed(() => Number(route.query?.sid ?? 0) || undefined) const node = computed(() => Number(route.params?.node ?? 0) || undefined) const goCat = (action, extraParams = {}) => { const url = lpService.buildLegacyActionUrl(action, { - cid: cid.value, sid: sid.value, node: node.value, + cid: cid.value, + sid: sid.value, + node: node.value, params: { id: props.category.iid, ...extraParams }, }) window.location.assign(url) } -const onCatEdit = () => goCat("add_lp_category") -const onCatAddUsers = () => goCat("add_users_to_category") +const onCatEdit = () => goCat("add_lp_category") +const onCatAddUsers = () => goCat("add_users_to_category") const onCatToggleVisibility = () => { - const vis = props.category.visibility ?? props.category.visible + const vis = props.category.visibility ?? props.category.visible const next = typeof vis === "number" ? (vis ? 0 : 1) : 1 goCat("toggle_category_visibility", { new_status: next }) } @@ -70,6 +87,12 @@ const onCatTogglePublish = () => { goCat("toggle_category_publish", { new_status: next }) } const onCatDelete = () => { + // Do not allow deletion if category is not empty + if (localList.value.length > 0) { + alert(t("You must move or remove all learning paths from this category before deleting it.")) + return + } + const label = (props.category.title || "").trim() || t("Category") const msg = `${t("Are you sure you want to delete")} ${label}?` if (confirm(msg)) { @@ -83,41 +106,82 @@ onMounted(() => { const saved = localStorage.getItem(storageKey.value) if (saved !== null) isOpen.value = saved === "1" }) -watch(isOpen, v => localStorage.setItem(storageKey.value, v ? "1" : "0")) +watch(isOpen, (v) => localStorage.setItem(storageKey.value, v ? "1" : "0")) const panelId = computed(() => `cat-panel-${props.category?.iid || props.title}`) -const toggleOpen = () => { if (localList.value.length) isOpen.value = !isOpen.value } -function onChangeCat() { - emit("reorder", localList.value.map(i => i.iid)) +const toggleOpen = () => { + if (localList.value.length) isOpen.value = !isOpen.value } @@ -151,16 +245,31 @@ function onChangeCat() { :title="t('Expand') / t('Collapse')" @click="toggleOpen" > - - + + -
+
diff --git a/assets/vue/views/lp/LpList.vue b/assets/vue/views/lp/LpList.vue index f43fff7746b..db13ccbae82 100644 --- a/assets/vue/views/lp/LpList.vue +++ b/assets/vue/views/lp/LpList.vue @@ -326,12 +326,12 @@ function isVisibleForStudent(lp) { const withCidSid = (url) => { if (!url) return url try { - const isAbs = url.startsWith('http://') || url.startsWith('https://') - const abs = isAbs ? url : (window.location.origin + url) + const isAbs = url.startsWith("http://") || url.startsWith("https://") + const abs = isAbs ? url : window.location.origin + url const u = new URL(abs) - if (cid.value) u.searchParams.set('cid', String(cid.value)) - if (sid.value) u.searchParams.set('sid', String(sid.value)) - return isAbs ? u.toString() : (u.pathname + u.search) + if (cid.value) u.searchParams.set("cid", String(cid.value)) + if (sid.value) u.searchParams.set("sid", String(sid.value)) + return isAbs ? u.toString() : u.pathname + u.search } catch { return url } diff --git a/public/main/lp/learnpath.class.php b/public/main/lp/learnpath.class.php index 795c7cf833f..89f24a1046d 100644 --- a/public/main/lp/learnpath.class.php +++ b/public/main/lp/learnpath.class.php @@ -7616,26 +7616,37 @@ public static function getCategorySessionId($id) public static function deleteCategory(int $id): bool { $repo = Container::getLpCategoryRepository(); - /** @var CLpCategory $category */ + /** @var CLpCategory|null $category */ $category = $repo->find($id); - if ($category) { - $em = Database::getManager(); - $lps = $category->getLps(); - - foreach ($lps as $lp) { - $lp->setCategory(null); - $em->persist($lp); - } - $course = api_get_course_entity(); - $session = api_get_session_entity(); + if (null === $category) { + return false; + } - $em->getRepository(ResourceLink::class)->removeByResourceInContext($category, $course, $session); + $em = Database::getManager(); + $lps = $category->getLps(); - return true; + // Detach all learning paths from this category + foreach ($lps as $lp) { + $lp->setCategory(null); + $em->persist($lp); } - return false; + // Remove the resource link of this category in the current context + $course = api_get_course_entity(); + $session = api_get_session_entity(); + + $em->getRepository(ResourceLink::class)->removeByResourceInContext( + $category, + $course, + $session + ); + + // Remove the category itself + $em->remove($category); + $em->flush(); + + return true; } /**