Skip to content

Commit c5302a0

Browse files
Learnpath: Fix category deletion to remove category entity
1 parent 71aa1df commit c5302a0

File tree

3 files changed

+189
-69
lines changed

3 files changed

+189
-69
lines changed

assets/vue/components/lp/LpCategorySection.vue

Lines changed: 159 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -22,43 +22,60 @@ const props = defineProps({
2222
buildDates: { type: Function, required: false },
2323
})
2424
const emit = defineEmits([
25-
"open","edit","report","settings","build",
26-
"toggle-visible","toggle-publish","delete",
27-
"export-scorm","export-pdf",
28-
"reorder","toggle-auto-launch",
25+
"open",
26+
"edit",
27+
"report",
28+
"settings",
29+
"build",
30+
"toggle-visible",
31+
"toggle-publish",
32+
"delete",
33+
"export-scorm",
34+
"export-pdf",
35+
"reorder",
36+
"toggle-auto-launch",
2937
])
3038
3139
const displayTitle = computed(() => props.title || t("Learning path categories"))
3240
3341
const localList = ref([...(props.list ?? [])])
3442
const dragging = ref(false)
3543
36-
watch(() => props.list, (nv) => {
37-
if (dragging.value) return
38-
localList.value = [...(nv ?? [])]
39-
}, { immediate: true })
44+
watch(
45+
() => props.list,
46+
(nv) => {
47+
if (dragging.value) return
48+
localList.value = [...(nv ?? [])]
49+
},
50+
{ immediate: true },
51+
)
4052
4153
function onEndCat() {
4254
dragging.value = false
43-
emit("reorder", localList.value.map(i => i.iid))
55+
emit(
56+
"reorder",
57+
localList.value.map((i) => i.iid),
58+
)
4459
}
4560
4661
const route = useRoute()
47-
const cid = computed(() => Number(route.query?.cid ?? 0) || undefined)
48-
const sid = computed(() => Number(route.query?.sid ?? 0) || undefined)
62+
const cid = computed(() => Number(route.query?.cid ?? 0) || undefined)
63+
const sid = computed(() => Number(route.query?.sid ?? 0) || undefined)
4964
const node = computed(() => Number(route.params?.node ?? 0) || undefined)
5065
5166
const goCat = (action, extraParams = {}) => {
5267
const url = lpService.buildLegacyActionUrl(action, {
53-
cid: cid.value, sid: sid.value, node: node.value,
68+
cid: cid.value,
69+
sid: sid.value,
70+
node: node.value,
5471
params: { id: props.category.iid, ...extraParams },
5572
})
5673
window.location.assign(url)
5774
}
58-
const onCatEdit = () => goCat("add_lp_category")
59-
const onCatAddUsers = () => goCat("add_users_to_category")
75+
const onCatEdit = () => goCat("add_lp_category")
76+
const onCatAddUsers = () => goCat("add_users_to_category")
6077
const onCatToggleVisibility = () => {
61-
const vis = props.category.visibility ?? props.category.visible
78+
const vis = props.category.visibility ?? props.category.visible
6279
const next = typeof vis === "number" ? (vis ? 0 : 1) : 1
6380
goCat("toggle_category_visibility", { new_status: next })
6481
}
@@ -70,6 +87,12 @@ const onCatTogglePublish = () => {
7087
goCat("toggle_category_publish", { new_status: next })
7188
}
7289
const onCatDelete = () => {
90+
// Do not allow deletion if category is not empty
91+
if (localList.value.length > 0) {
92+
alert(t("You must move or remove all learning paths from this category before deleting it."))
93+
return
94+
}
95+
7396
const label = (props.category.title || "").trim() || t("Category")
7497
const msg = `${t("Are you sure you want to delete")} ${label}?`
7598
if (confirm(msg)) {
@@ -83,41 +106,82 @@ onMounted(() => {
83106
const saved = localStorage.getItem(storageKey.value)
84107
if (saved !== null) isOpen.value = saved === "1"
85108
})
86-
watch(isOpen, v => localStorage.setItem(storageKey.value, v ? "1" : "0"))
109+
watch(isOpen, (v) => localStorage.setItem(storageKey.value, v ? "1" : "0"))
87110
const panelId = computed(() => `cat-panel-${props.category?.iid || props.title}`)
88-
const toggleOpen = () => { if (localList.value.length) isOpen.value = !isOpen.value }
89-
function onChangeCat() {
90-
emit("reorder", localList.value.map(i => i.iid))
111+
const toggleOpen = () => {
112+
if (localList.value.length) isOpen.value = !isOpen.value
91113
}
92114
</script>
93115
94116
<template>
95117
<section class="relative ml-2 rounded-2xl shadow-lg">
96118
<header class="relative bg-support-6 rounded-t-2xl flex items-center justify-between pl-0 pr-4 py-3">
97-
<span class="pointer-events-none absolute inset-y-0 -left-1.5 w-1.5 bg-support-5 rounded-l-2xl" aria-hidden />
119+
<span
120+
class="pointer-events-none absolute inset-y-0 -left-1.5 w-1.5 bg-support-5 rounded-l-2xl"
121+
aria-hidden
122+
/>
98123
<div class="flex items-center gap-3">
99124
<template v-if="canEdit">
100125
<button
101126
class="w-8 h-8 grid place-content-center rounded-lg text-gray-50 hover:bg-gray-15 hover:text-gray-90"
102-
:title="t('Drag to reorder')" :aria-label="t('Drag to reorder')"
127+
:title="t('Drag to reorder')"
128+
:aria-label="t('Drag to reorder')"
103129
>
104-
<svg width="14" height="14" viewBox="0 0 14 14" fill="currentColor" aria-hidden>
105-
<circle cx="4" cy="3" r="1.2" /><circle cx="4" cy="7" r="1.2" /><circle cx="4" cy="11" r="1.2" />
106-
<circle cx="10" cy="3" r="1.2" /><circle cx="10" cy="7" r="1.2" /><circle cx="10" cy="11" r="1.2" />
130+
<svg
131+
width="14"
132+
height="14"
133+
viewBox="0 0 14 14"
134+
fill="currentColor"
135+
aria-hidden
136+
>
137+
<circle
138+
cx="4"
139+
cy="3"
140+
r="1.2"
141+
/>
142+
<circle
143+
cx="4"
144+
cy="7"
145+
r="1.2"
146+
/>
147+
<circle
148+
cx="4"
149+
cy="11"
150+
r="1.2"
151+
/>
152+
<circle
153+
cx="10"
154+
cy="3"
155+
r="1.2"
156+
/>
157+
<circle
158+
cx="10"
159+
cy="7"
160+
r="1.2"
161+
/>
162+
<circle
163+
cx="10"
164+
cy="11"
165+
r="1.2"
166+
/>
107167
</svg>
108168
</button>
109169
</template>
110170
<template v-else>
111-
<span class="inline-block w-8 h-8" aria-hidden></span>
171+
<span
172+
class="inline-block w-8 h-8"
173+
aria-hidden
174+
></span>
112175
</template>
113176
114177
<h2 class="text-body-1 font-semibold text-gray-90">{{ displayTitle }}</h2>
115178
</div>
116179
117180
<div class="flex items-center gap-2">
118-
<div class="text-tiny text-gray-50">{{ localList.length }} {{ t('Learning paths') }}</div>
181+
<div class="text-tiny text-gray-50">{{ localList.length }} {{ t("Learning paths") }}</div>
119182
120-
<BaseDropdownMenu v-if="canEdit"
183+
<BaseDropdownMenu
184+
v-if="canEdit"
121185
:dropdown-id="`category-${category.iid}`"
122186
class="relative z-30"
123187
>
@@ -127,18 +191,48 @@ function onChangeCat() {
127191
:title="t('Options')"
128192
:aria-label="t('Options')"
129193
>
130-
<i class="mdi mdi-dots-vertical text-lg" aria-hidden></i>
194+
<i
195+
class="mdi mdi-dots-vertical text-lg"
196+
aria-hidden
197+
></i>
131198
</span>
132199
</template>
133200
<template #menu>
134-
<div class="absolute right-0 top-full mt-2 w-60 bg-white border border-gray-25 rounded-xl shadow-xl p-1 z-50">
135-
<button class="w-full text-left px-3 py-2 rounded hover:bg-gray-15" @click="onCatEdit">{{ t('Edit category') }}</button>
136-
<button class="w-full text-left px-3 py-2 rounded hover:bg-gray-15" @click="onCatAddUsers">{{ t('Subscribe users to category') }}</button>
201+
<div
202+
class="absolute right-0 top-full mt-2 w-60 bg-white border border-gray-25 rounded-xl shadow-xl p-1 z-50"
203+
>
204+
<button
205+
class="w-full text-left px-3 py-2 rounded hover:bg-gray-15"
206+
@click="onCatEdit"
207+
>
208+
{{ t("Edit category") }}
209+
</button>
210+
<button
211+
class="w-full text-left px-3 py-2 rounded hover:bg-gray-15"
212+
@click="onCatAddUsers"
213+
>
214+
{{ t("Subscribe users to category") }}
215+
</button>
137216
<div class="my-1 h-px bg-gray-15"></div>
138-
<button class="w-full text-left px-3 py-2 rounded hover:bg-gray-15" @click="onCatToggleVisibility">{{ t('Toggle visibility') }}</button>
139-
<button class="w-full text-left px-3 py-2 rounded hover:bg-gray-15" @click="onCatTogglePublish">{{ t('Publish / Hide') }}</button>
217+
<button
218+
class="w-full text-left px-3 py-2 rounded hover:bg-gray-15"
219+
@click="onCatToggleVisibility"
220+
>
221+
{{ t("Toggle visibility") }}
222+
</button>
223+
<button
224+
class="w-full text-left px-3 py-2 rounded hover:bg-gray-15"
225+
@click="onCatTogglePublish"
226+
>
227+
{{ t("Publish / Hide") }}
228+
</button>
140229
<div class="my-1 h-px bg-gray-15"></div>
141-
<button class="w-full text-left px-3 py-2 rounded hover:bg-gray-15 text-danger" @click="onCatDelete">{{ t('Delete') }}</button>
230+
<button
231+
class="w-full text-left px-3 py-2 rounded hover:bg-gray-15 text-danger"
232+
@click="onCatDelete"
233+
>
234+
{{ t("Delete") }}
235+
</button>
142236
</div>
143237
</template>
144238
</BaseDropdownMenu>
@@ -151,16 +245,31 @@ function onChangeCat() {
151245
:title="t('Expand') / t('Collapse')"
152246
@click="toggleOpen"
153247
>
154-
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor"
155-
class="transition-transform duration-200"
156-
:class="isOpen ? 'rotate-180' : ''">
157-
<path d="M6 9l6 6 6-6" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
248+
<svg
249+
width="18"
250+
height="18"
251+
viewBox="0 0 24 24"
252+
fill="none"
253+
stroke="currentColor"
254+
class="transition-transform duration-200"
255+
:class="isOpen ? 'rotate-180' : ''"
256+
>
257+
<path
258+
d="M6 9l6 6 6-6"
259+
stroke-width="2"
260+
stroke-linecap="round"
261+
stroke-linejoin="round"
262+
/>
158263
</svg>
159264
</button>
160265
</div>
161266
</header>
162267
163-
<div v-if="isOpen && localList.length" :id="panelId" class="sm:px-4 sm:pb-4 px-2 pb-2 bg-white rounded-b-2xl">
268+
<div
269+
v-if="isOpen && localList.length"
270+
:id="panelId"
271+
class="sm:px-4 sm:pb-4 px-2 pb-2 bg-white rounded-b-2xl"
272+
>
164273
<Draggable
165274
v-model="localList"
166275
item-key="iid"
@@ -189,17 +298,17 @@ function onChangeCat() {
189298
:canExportPdf="canExportPdf"
190299
:canAutoLaunch="canAutoLaunch"
191300
:buildDates="buildDates"
192-
@toggle-auto-launch="$emit('toggle-auto-launch', element)"
193-
@open="$emit('open', element)"
194-
@edit="$emit('edit', element)"
195-
@report="$emit('report', element)"
196-
@settings="$emit('settings', element)"
197-
@build="$emit('build', element)"
198-
@toggle-visible="$emit('toggle-visible', element)"
199-
@toggle-publish="$emit('toggle-publish', element)"
200-
@delete="$emit('delete', element)"
201-
@export-scorm="$emit('export-scorm', element)"
202-
@export-pdf="$emit('export-pdf', element)"
301+
@toggle-auto-launch="$emit('toggle-auto-launch', element)"
302+
@open="$emit('open', element)"
303+
@edit="$emit('edit', element)"
304+
@report="$emit('report', element)"
305+
@settings="$emit('settings', element)"
306+
@build="$emit('build', element)"
307+
@toggle-visible="$emit('toggle-visible', element)"
308+
@toggle-publish="$emit('toggle-publish', element)"
309+
@delete="$emit('delete', element)"
310+
@export-scorm="$emit('export-scorm', element)"
311+
@export-pdf="$emit('export-pdf', element)"
203312
/>
204313
</template>
205314
</Draggable>

assets/vue/views/lp/LpList.vue

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -326,12 +326,12 @@ function isVisibleForStudent(lp) {
326326
const withCidSid = (url) => {
327327
if (!url) return url
328328
try {
329-
const isAbs = url.startsWith('http://') || url.startsWith('https://')
330-
const abs = isAbs ? url : (window.location.origin + url)
329+
const isAbs = url.startsWith("http://") || url.startsWith("https://")
330+
const abs = isAbs ? url : window.location.origin + url
331331
const u = new URL(abs)
332-
if (cid.value) u.searchParams.set('cid', String(cid.value))
333-
if (sid.value) u.searchParams.set('sid', String(sid.value))
334-
return isAbs ? u.toString() : (u.pathname + u.search)
332+
if (cid.value) u.searchParams.set("cid", String(cid.value))
333+
if (sid.value) u.searchParams.set("sid", String(sid.value))
334+
return isAbs ? u.toString() : u.pathname + u.search
335335
} catch {
336336
return url
337337
}

public/main/lp/learnpath.class.php

Lines changed: 25 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -7616,26 +7616,37 @@ public static function getCategorySessionId($id)
76167616
public static function deleteCategory(int $id): bool
76177617
{
76187618
$repo = Container::getLpCategoryRepository();
7619-
/** @var CLpCategory $category */
7619+
/** @var CLpCategory|null $category */
76207620
$category = $repo->find($id);
7621-
if ($category) {
7622-
$em = Database::getManager();
7623-
$lps = $category->getLps();
7624-
7625-
foreach ($lps as $lp) {
7626-
$lp->setCategory(null);
7627-
$em->persist($lp);
7628-
}
76297621

7630-
$course = api_get_course_entity();
7631-
$session = api_get_session_entity();
7622+
if (null === $category) {
7623+
return false;
7624+
}
76327625

7633-
$em->getRepository(ResourceLink::class)->removeByResourceInContext($category, $course, $session);
7626+
$em = Database::getManager();
7627+
$lps = $category->getLps();
76347628

7635-
return true;
7629+
// Detach all learning paths from this category
7630+
foreach ($lps as $lp) {
7631+
$lp->setCategory(null);
7632+
$em->persist($lp);
76367633
}
76377634

7638-
return false;
7635+
// Remove the resource link of this category in the current context
7636+
$course = api_get_course_entity();
7637+
$session = api_get_session_entity();
7638+
7639+
$em->getRepository(ResourceLink::class)->removeByResourceInContext(
7640+
$category,
7641+
$course,
7642+
$session
7643+
);
7644+
7645+
// Remove the category itself
7646+
$em->remove($category);
7647+
$em->flush();
7648+
7649+
return true;
76397650
}
76407651

76417652
/**

0 commit comments

Comments
 (0)