Skip to content

Commit 56aaa5a

Browse files
authored
Work: Add required file extension option - refs #7103
1 parent 525e895 commit 56aaa5a

File tree

3 files changed

+121
-1
lines changed

3 files changed

+121
-1
lines changed

assets/vue/components/assignments/AssignmentsForm.vue

Lines changed: 87 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,30 @@
102102
name="allow_text_assignment"
103103
label=""
104104
/>
105+
106+
<BaseCheckbox
107+
id="require_extension"
108+
v-model="chkRequireExtension"
109+
:label="t('Require specific file format')"
110+
name="require_extension"
111+
/>
112+
113+
<div v-if="chkRequireExtension">
114+
<BaseMultiSelect
115+
v-model="assignment.allowedExtensions"
116+
:options="predefinedExtensions"
117+
:label="t('Select allowed file formats')"
118+
input-id="allowed-file-extensions"
119+
/>
120+
121+
<BaseInputText
122+
v-if="assignment.allowedExtensions.includes('other')"
123+
id="custom-extensions"
124+
v-model="assignment.customExtensions"
125+
:label="t('Custom extensions (separated by space)')"
126+
/>
127+
128+
</div>
105129
</BaseAdvancedSettingsButton>
106130

107131
<div class="flex justify-end space-x-2 mt-4">
@@ -123,6 +147,7 @@ import BaseAdvancedSettingsButton from "../basecomponents/BaseAdvancedSettingsBu
123147
import BaseButton from "../basecomponents/BaseButton.vue"
124148
import BaseCheckbox from "../basecomponents/BaseCheckbox.vue"
125149
import BaseSelect from "../basecomponents/BaseSelect.vue"
150+
import BaseMultiSelect from "../basecomponents/BaseMultiSelect.vue";
126151
import BaseInputNumber from "../basecomponents/BaseInputNumber.vue"
127152
import BaseTinyEditor from "../basecomponents/BaseTinyEditor.vue"
128153
import useVuelidate from "@vuelidate/core"
@@ -162,6 +187,17 @@ const documentTypes = ref([
162187
{ label: t("Allow only files"), value: 2 },
163188
])
164189
190+
const chkRequireExtension = ref(false)
191+
const predefinedExtensions = ref ([
192+
{name: 'PDF', id: 'pdf'},
193+
{name: 'DOCX', id: 'docx'},
194+
{name: 'XLSX', id: 'xlsx'},
195+
{name: 'ZIP', id: 'zip'},
196+
{name: 'MP3', id: 'mp3'},
197+
{name: 'MP4', id: 'mp4'},
198+
{name: t('Other extensions'), id: 'other'},
199+
])
200+
165201
const assignment = reactive({
166202
title: "",
167203
description: "",
@@ -172,6 +208,8 @@ const assignment = reactive({
172208
endsOn: new Date(),
173209
addToCalendar: false,
174210
allowTextAssignment: 2,
211+
allowedExtensions: [],
212+
customExtensions:'',
175213
})
176214
177215
watchEffect(() => {
@@ -198,13 +236,41 @@ watchEffect(() => {
198236
199237
assignment.allowTextAssignment = def.allowTextAssignment
200238
239+
240+
if (def.extensions) {
241+
const extensionsArray = def.extensions
242+
.split(' ')
243+
.map(ext => ext.trim())
244+
.filter(ext => ext.length > 0)
245+
246+
if (extensionsArray.length > 0) {
247+
chkRequireExtension.value = true
248+
249+
const predefinedIds = predefinedExtensions.value
250+
.map(e => e.id)
251+
.filter(id => id !== 'other')
252+
253+
const predefined = extensionsArray.filter(ext => predefinedIds.includes(ext))
254+
const custom = extensionsArray.filter(ext => !predefinedIds.includes(ext))
255+
if (assignment.allowedExtensions.length === 0) {
256+
assignment.allowedExtensions = predefined
257+
258+
if (custom.length > 0) {
259+
assignment.allowedExtensions.push('other')
260+
assignment.customExtensions = custom.join(' ')
261+
}
262+
}
263+
}
264+
}
265+
201266
if (
202267
def.qualification ||
203268
def.assignment.eventCalendarId ||
204269
def.weight ||
205270
def.assignment.expiresOn ||
206271
def.assignment.endsOn ||
207-
def.allowTextAssignment !== undefined
272+
def.allowTextAssignment !== undefined ||
273+
(def.allowedExtensions)
208274
) {
209275
showAdvancedSettings.value = true
210276
}
@@ -256,6 +322,26 @@ async function onSubmit() {
256322
if (chkEndsOn.value) {
257323
payload.endsOn = assignment.endsOn.toISOString()
258324
}
325+
if (chkRequireExtension.value && assignment.allowedExtensions.length > 0) {
326+
let extensions = []
327+
328+
assignment.allowedExtensions.forEach(ext => {
329+
if (ext !== 'other') {
330+
extensions.push(ext)
331+
}
332+
})
333+
if (assignment.allowedExtensions.includes('other') && assignment.customExtensions) {
334+
const customExts = assignment.customExtensions
335+
.split(' ')
336+
.map(ext => ext.trim().toLowerCase().replace('.', ''))
337+
.filter(ext => ext.length > 0)
338+
extensions.push(...customExts)
339+
}
340+
341+
if (extensions.length > 0) {
342+
payload.extensions = extensions.join(' ') // "pdf docx rar ai"
343+
}
344+
}
259345
if (props.defaultAssignment?.["@id"]) {
260346
payload["@id"] = props.defaultAssignment["@id"]
261347
}

assets/vue/views/assignments/AssignmentSubmit.vue

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,14 @@
1212

1313
<h1 class="text-2xl font-bold">{{ t("Upload your assignment") }} – {{ publicationTitle }}</h1>
1414

15+
<p
16+
v-if="allowedExtensions.length > 0"
17+
class="text-gray-600"
18+
>
19+
<span class="font-semibold">{{ t('Allowed file formats:') }}</span>
20+
{{ allowedExtensions.map(ext => '.' + ext).join(', ') }}
21+
</p>
22+
1523
<div
1624
v-if="allowText && !allowFile"
1725
class="space-y-4"
@@ -129,6 +137,7 @@ const publicationTitle = ref("")
129137
const text = ref("")
130138
const submissionTitle = ref("")
131139
const activeTab = ref(allowText ? "text" : "file")
140+
const allowedExtensions = ref([])
132141
133142
onMounted(loadPublicationTitle)
134143
async function loadPublicationTitle() {
@@ -138,11 +147,27 @@ async function loadPublicationTitle() {
138147
})
139148
publicationTitle.value = data.title
140149
submissionTitle.value = data.title
150+
151+
if (data.extensions) {
152+
allowedExtensions.value = data.extensions
153+
.split(' ')
154+
.map(ext => ext.trim().toLowerCase())
155+
.filter(ext => ext.length > 0) }
141156
} catch (e) {
142157
console.error("Error loading publication metadata", e)
143158
}
144159
}
145160
161+
function isFileExtensionAllowed(filename) {
162+
if (allowedExtensions.value.length === 0) {
163+
return true
164+
}
165+
166+
const fileExtension = filename.split('.').pop().toLowerCase()
167+
return allowedExtensions.value.includes(fileExtension)
168+
}
169+
170+
146171
const queryParams = new URLSearchParams({
147172
cid,
148173
...(sid && { sid }),
@@ -159,6 +184,14 @@ uppy.use(XHRUpload, {
159184
fieldName: "uploadFile",
160185
})
161186
uppy.on("file-added", (file) => {
187+
if (!isFileExtensionAllowed(file.name)) {
188+
uppy.removeFile(file.id)
189+
showErrorNotification(
190+
t('File type not allowed. Allowed extensions') + ': ' +
191+
allowedExtensions.value.map(ext => '.' + ext).join(', ')
192+
)
193+
return
194+
}
162195
uppy.setMeta({
163196
title: file.name,
164197
filetype: "file",

src/CourseBundle/Entity/CStudentPublication.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,7 @@ class CStudentPublication extends AbstractResource implements ResourceInterface,
207207
#[ORM\Column(name: 'default_visibility', type: 'boolean', nullable: true, options: ['default' => 0])]
208208
protected ?bool $defaultVisibility = null;
209209

210+
#[Groups(['c_student_publication:write', 'student_publication:read'])]
210211
#[ORM\Column(name: 'extensions', type: 'text', nullable: true)]
211212
protected ?string $extensions = null;
212213

0 commit comments

Comments
 (0)