From 7ef3a084cfeca688eea867aa07f5d5ca410123cd Mon Sep 17 00:00:00 2001 From: Chamilo-UGA Date: Tue, 25 Nov 2025 17:13:15 +0100 Subject: [PATCH] require_extension_file_on_assignement --- .../assignments/AssignmentsForm.vue | 88 ++++++++++++++++++- .../views/assignments/AssignmentSubmit.vue | 33 +++++++ .../Entity/CStudentPublication.php | 1 + 3 files changed, 121 insertions(+), 1 deletion(-) diff --git a/assets/vue/components/assignments/AssignmentsForm.vue b/assets/vue/components/assignments/AssignmentsForm.vue index 4b2cdd4eff8..6169de1df82 100644 --- a/assets/vue/components/assignments/AssignmentsForm.vue +++ b/assets/vue/components/assignments/AssignmentsForm.vue @@ -102,6 +102,30 @@ name="allow_text_assignment" label="" /> + + + +
+ + + + +
@@ -123,6 +147,7 @@ import BaseAdvancedSettingsButton from "../basecomponents/BaseAdvancedSettingsBu import BaseButton from "../basecomponents/BaseButton.vue" import BaseCheckbox from "../basecomponents/BaseCheckbox.vue" import BaseSelect from "../basecomponents/BaseSelect.vue" +import BaseMultiSelect from "../basecomponents/BaseMultiSelect.vue"; import BaseInputNumber from "../basecomponents/BaseInputNumber.vue" import BaseTinyEditor from "../basecomponents/BaseTinyEditor.vue" import useVuelidate from "@vuelidate/core" @@ -162,6 +187,17 @@ const documentTypes = ref([ { label: t("Allow only files"), value: 2 }, ]) +const chkRequireExtension = ref(false) +const predefinedExtensions = ref ([ + {name: 'PDF', id: 'pdf'}, + {name: 'DOCX', id: 'docx'}, + {name: 'XLSX', id: 'xlsx'}, + {name: 'ZIP', id: 'zip'}, + {name: 'MP3', id: 'mp3'}, + {name: 'MP4', id: 'mp4'}, + {name: t('Other extensions'), id: 'other'}, +]) + const assignment = reactive({ title: "", description: "", @@ -172,6 +208,8 @@ const assignment = reactive({ endsOn: new Date(), addToCalendar: false, allowTextAssignment: 2, + allowedExtensions: [], + customExtensions:'', }) watchEffect(() => { @@ -198,13 +236,41 @@ watchEffect(() => { assignment.allowTextAssignment = def.allowTextAssignment + + if (def.extensions) { + const extensionsArray = def.extensions + .split(' ') + .map(ext => ext.trim()) + .filter(ext => ext.length > 0) + + if (extensionsArray.length > 0) { + chkRequireExtension.value = true + + const predefinedIds = predefinedExtensions.value + .map(e => e.id) + .filter(id => id !== 'other') + + const predefined = extensionsArray.filter(ext => predefinedIds.includes(ext)) + const custom = extensionsArray.filter(ext => !predefinedIds.includes(ext)) + if (assignment.allowedExtensions.length === 0) { + assignment.allowedExtensions = predefined + + if (custom.length > 0) { + assignment.allowedExtensions.push('other') + assignment.customExtensions = custom.join(' ') + } + } + } + } + if ( def.qualification || def.assignment.eventCalendarId || def.weight || def.assignment.expiresOn || def.assignment.endsOn || - def.allowTextAssignment !== undefined + def.allowTextAssignment !== undefined || + (def.allowedExtensions) ) { showAdvancedSettings.value = true } @@ -256,6 +322,26 @@ async function onSubmit() { if (chkEndsOn.value) { payload.endsOn = assignment.endsOn.toISOString() } + if (chkRequireExtension.value && assignment.allowedExtensions.length > 0) { + let extensions = [] + + assignment.allowedExtensions.forEach(ext => { + if (ext !== 'other') { + extensions.push(ext) + } + }) + if (assignment.allowedExtensions.includes('other') && assignment.customExtensions) { + const customExts = assignment.customExtensions + .split(' ') + .map(ext => ext.trim().toLowerCase().replace('.', '')) + .filter(ext => ext.length > 0) + extensions.push(...customExts) + } + + if (extensions.length > 0) { + payload.extensions = extensions.join(' ') // "pdf docx rar ai" + } + } if (props.defaultAssignment?.["@id"]) { payload["@id"] = props.defaultAssignment["@id"] } diff --git a/assets/vue/views/assignments/AssignmentSubmit.vue b/assets/vue/views/assignments/AssignmentSubmit.vue index 8d287dde522..f8c243a10bf 100644 --- a/assets/vue/views/assignments/AssignmentSubmit.vue +++ b/assets/vue/views/assignments/AssignmentSubmit.vue @@ -12,6 +12,14 @@

{{ t("Upload your assignment") }} – {{ publicationTitle }}

+

+ {{ t('Allowed file formats:') }} + {{ allowedExtensions.map(ext => '.' + ext).join(', ') }} +

+
ext.trim().toLowerCase()) + .filter(ext => ext.length > 0) } } catch (e) { console.error("Error loading publication metadata", e) } } +function isFileExtensionAllowed(filename) { + if (allowedExtensions.value.length === 0) { + return true + } + + const fileExtension = filename.split('.').pop().toLowerCase() + return allowedExtensions.value.includes(fileExtension) +} + + const queryParams = new URLSearchParams({ cid, ...(sid && { sid }), @@ -159,6 +184,14 @@ uppy.use(XHRUpload, { fieldName: "uploadFile", }) uppy.on("file-added", (file) => { + if (!isFileExtensionAllowed(file.name)) { + uppy.removeFile(file.id) + showErrorNotification( + t('File type not allowed. Allowed extensions') + ': ' + + allowedExtensions.value.map(ext => '.' + ext).join(', ') + ) + return + } uppy.setMeta({ title: file.name, filetype: "file", diff --git a/src/CourseBundle/Entity/CStudentPublication.php b/src/CourseBundle/Entity/CStudentPublication.php index 352559f2ea4..fc5eef6add5 100644 --- a/src/CourseBundle/Entity/CStudentPublication.php +++ b/src/CourseBundle/Entity/CStudentPublication.php @@ -207,6 +207,7 @@ class CStudentPublication extends AbstractResource implements ResourceInterface, #[ORM\Column(name: 'default_visibility', type: 'boolean', nullable: true, options: ['default' => 0])] protected ?bool $defaultVisibility = null; + #[Groups(['c_student_publication:write', 'student_publication:read'])] #[ORM\Column(name: 'extensions', type: 'text', nullable: true)] protected ?string $extensions = null;