diff --git a/resources/vue/components/courseware/CoursewareAudioBlock.vue b/resources/vue/components/courseware/CoursewareAudioBlock.vue index 6d3619d2f89fcd4be36a796fc5b8b28a0b519fdc..70af59689bf719683c68cbc9d2832278d14dc600 100755 --- a/resources/vue/components/courseware/CoursewareAudioBlock.vue +++ b/resources/vue/components/courseware/CoursewareAudioBlock.vue @@ -9,6 +9,9 @@ @closeEdit="initCurrentData" > <template #content> + <div v-if="showInvalidFolderMessage" class="messagebox messagebox_error"> + {{ invalidFolderMessageText }} + </div> <div v-if="currentTitle !== ''" class="cw-block-title">{{ currentTitle }}</div> <audio :src="currentURL" @@ -60,7 +63,7 @@ <p><translate>Es ist keine Audio-Datei verfügbar</translate></p> </div> <div v-if="showRecorder && canGetMediaDevices" class="cw-audio-playlist-recorder"> - <button + <button v-show="!userRecorderEnabled" class="button" @click="enableRecorder" @@ -81,21 +84,21 @@ > <translate>Aufnahme wiederholen</translate> </button> - <button + <button v-show="isRecording" class="button" @click="stopRecording" > <translate>Aufnahme beenden</translate> </button> - <button + <button v-show="newRecording && !isRecording" class="button" @click="resetRecorder" > <translate>Aufnahme löschen</translate> </button> - <button + <button v-show="newRecording && !isRecording" class="button" @click="storeRecording" @@ -158,11 +161,13 @@ import CoursewareDefaultBlock from './CoursewareDefaultBlock.vue'; import CoursewareFileChooser from './CoursewareFileChooser.vue'; import CoursewareFolderChooser from './CoursewareFolderChooser.vue'; +import { blockFolderValidatorMixin } from './block-folder-validator-mixin.js'; import { mapActions, mapGetters } from 'vuex'; export default { name: 'courseware-audio-block', + mixins: [blockFolderValidatorMixin], components: { CoursewareDefaultBlock, CoursewareFileChooser, @@ -215,7 +220,7 @@ export default { .filter((file) => { if (this.relatedTermOfUse({parent: file, relationship: 'terms-of-use'}).attributes['download-condition'] !== 0) { return false; - } + } if (! file.attributes['mime-type'].includes('audio')) { return false; } @@ -578,7 +583,8 @@ export default { }, }, watch: { - currentFolderId() { + currentFolderId(value) { + this.validateFolderAccessibility(value); this.getFolderFiles(); }, }, diff --git a/resources/vue/components/courseware/CoursewareBeforeAfterBlock.vue b/resources/vue/components/courseware/CoursewareBeforeAfterBlock.vue index e8d7f91dd27c1543bfdec408d59314d7c65f8d8b..6a3c5979863fb82c4aea49ac30e5fc000ab328d1 100755 --- a/resources/vue/components/courseware/CoursewareBeforeAfterBlock.vue +++ b/resources/vue/components/courseware/CoursewareBeforeAfterBlock.vue @@ -9,6 +9,9 @@ @closeEdit="initCurrentData" > <template #content> + <div v-if="showInvalidFolderMessage" class="messagebox messagebox_error"> + {{ invalidFolderMessageText }} + </div> <TwentyTwenty :before="currentBeforeUrl" :after="currentAfterUrl" /> </template> <template v-if="canEdit" #edit> @@ -64,9 +67,11 @@ import CoursewareFileChooser from './CoursewareFileChooser.vue'; import TwentyTwenty from 'vue-twentytwenty'; import 'vue-twentytwenty/dist/vue-twentytwenty.css'; import { mapActions } from 'vuex'; +import { blockFolderValidatorMixin } from './block-folder-validator-mixin.js'; export default { name: 'courseware-before-after-block', + mixins: [blockFolderValidatorMixin], components: { CoursewareDefaultBlock, CoursewareFileChooser, @@ -227,5 +232,17 @@ export default { } }, }, + watch: { + currentBeforeFile(value) { + if (value?.relationships?.parent && value.relationships.parent.data.type == 'folders') { + this.validateFolderAccessibility(value.relationships.parent.data.id); + } + }, + currentAfterFile(value) { + if (value?.relationships?.parent && value.relationships.parent.data.type == 'folders') { + this.validateFolderAccessibility(value.relationships.parent.data.id); + } + } + } }; </script> diff --git a/resources/vue/components/courseware/CoursewareCanvasBlock.vue b/resources/vue/components/courseware/CoursewareCanvasBlock.vue index 008dcc75bff8b5f970d55a2f654d094a73baa1a0..6b290c6fd5dc1a915e804517c71af1dc56c8ad94 100755 --- a/resources/vue/components/courseware/CoursewareCanvasBlock.vue +++ b/resources/vue/components/courseware/CoursewareCanvasBlock.vue @@ -9,6 +9,9 @@ @closeEdit="initCurrentData" > <template #content> + <div v-if="showInvalidFolderMessage" class="messagebox messagebox_error"> + {{ invalidFolderMessageText }} + </div> <div v-if="currentTitle" class="cw-block-title"> {{ currentTitle }} </div> @@ -146,11 +149,13 @@ import CoursewareDefaultBlock from './CoursewareDefaultBlock.vue'; import CoursewareFileChooser from './CoursewareFileChooser.vue'; import CoursewareFolderChooser from './CoursewareFolderChooser.vue'; +import { blockFolderValidatorMixin } from './block-folder-validator-mixin.js'; import { mapActions, mapGetters } from 'vuex'; export default { name: 'courseware-canvas-block', + mixins: [blockFolderValidatorMixin], components: { CoursewareDefaultBlock, CoursewareFileChooser, @@ -595,5 +600,15 @@ export default { } }, }, + watch: { + currentFile(value) { + if (value?.relationships?.parent && value.relationships.parent.data.type == 'folders') { + this.validateFolderAccessibility(value.relationships.parent.data.id); + } + }, + currentUploadFolderId(value) { + this.validateFolderAccessibility(value); + } + }, }; </script> diff --git a/resources/vue/components/courseware/CoursewareDialogCardsBlock.vue b/resources/vue/components/courseware/CoursewareDialogCardsBlock.vue index 8feeb60c768e176dd72873c2c792c9d4d7c8f933..e50dea5bc20a493e89c36909204c09bc5e8f2570 100755 --- a/resources/vue/components/courseware/CoursewareDialogCardsBlock.vue +++ b/resources/vue/components/courseware/CoursewareDialogCardsBlock.vue @@ -9,8 +9,11 @@ @closeEdit="initCurrentData" > <template #content> + <div v-if="showInvalidFolderMessage" class="messagebox messagebox_error"> + {{ invalidFolderMessageText }} + </div> <div class="cw-block-dialog-cards-content"> - <button + <button class="cw-dialogcards-prev cw-dialogcards-navbutton" :class="{'cw-dialogcards-prev-disabled': hasNoPerv}" @click="prevCard" @@ -66,7 +69,7 @@ :name="$gettext('Karte') + ' ' + (index + 1).toString()" :selected="index === 0" canBeEmpty - > + > <form class="default" @submit.prevent=""> <label> <translate>Bild Vorderseite</translate>: @@ -113,12 +116,14 @@ import CoursewareDefaultBlock from './CoursewareDefaultBlock.vue'; import CoursewareFileChooser from './CoursewareFileChooser.vue'; import CoursewareTabs from './CoursewareTabs.vue'; import CoursewareTab from './CoursewareTab.vue'; +import { blockFolderValidatorMixin } from './block-folder-validator-mixin.js'; import { mapActions } from 'vuex'; import StudipIcon from '../StudipIcon.vue'; export default { name: 'courseware-dialog-cards-block', + mixins: [blockFolderValidatorMixin], components: { CoursewareDefaultBlock, CoursewareFileChooser, @@ -261,5 +266,20 @@ export default { }); }, }, + watch: { + currentCards: { + handler(value) { + value.forEach((card) => { + if (card?.front_file?.folder_id) { + this.validateFolderAccessibility(card.front_file.folder_id); + } + if (card?.back_file?.folder_id) { + this.validateFolderAccessibility(card.back_file.folder_id); + } + }); + }, + deep: true + } + }, }; </script> diff --git a/resources/vue/components/courseware/CoursewareDocumentBlock.vue b/resources/vue/components/courseware/CoursewareDocumentBlock.vue index 1967387990f0e76e7f297e817881984bd072c191..de7d57e9b3c75d9cabaa3383d303792c3381345d 100755 --- a/resources/vue/components/courseware/CoursewareDocumentBlock.vue +++ b/resources/vue/components/courseware/CoursewareDocumentBlock.vue @@ -9,6 +9,9 @@ @closeEdit="initCurrentData" > <template #content> + <div v-if="showInvalidFolderMessage" class="messagebox messagebox_error"> + {{ invalidFolderMessageText }} + </div> <div v-if="hasFile" class="cw-pdf-header cw-block-title"> <button class="cw-pdf-button-prev" :class="{ inactive: pageNum - 1 === 0 }" @click="prevPage" /> <span class="cw-pdf-title">{{ currentTitle }}</span> @@ -71,11 +74,13 @@ import CoursewareDefaultBlock from './CoursewareDefaultBlock.vue'; import CoursewareFileChooser from './CoursewareFileChooser.vue'; import * as pdfjsLib from 'pdfjs-dist'; import pdfjsWorker from 'pdfjs-dist/build/pdf.worker.entry'; +import { blockFolderValidatorMixin } from './block-folder-validator-mixin.js'; import { mapActions } from 'vuex'; export default { name: 'courseware-document-block', + mixins: [blockFolderValidatorMixin], components: { CoursewareDefaultBlock, CoursewareFileChooser, @@ -137,6 +142,11 @@ export default { this.evaluateBrowseAction(); } }, + currentFile(value) { + if (value?.relationships?.parent && value.relationships.parent.data.type == 'folders') { + this.validateFolderAccessibility(value.relationships.parent.data.id); + } + } }, mounted() { this.loadFileRefs(this.block.id).then((response) => { diff --git a/resources/vue/components/courseware/CoursewareDownloadBlock.vue b/resources/vue/components/courseware/CoursewareDownloadBlock.vue index 238e005bafe8ea4a255ea808404656184e9758a1..ea8d8ceb7cc972d35fabddfac13c35f69b9db506 100755 --- a/resources/vue/components/courseware/CoursewareDownloadBlock.vue +++ b/resources/vue/components/courseware/CoursewareDownloadBlock.vue @@ -9,6 +9,9 @@ @closeEdit="initCurrentData" > <template #content> + <div v-if="showInvalidFolderMessage" class="messagebox messagebox_error"> + {{ invalidFolderMessageText }} + </div> <div v-if="currentTitle !== ''" class="cw-block-title">{{ currentTitle }}</div> <div v-if="currentFile !== null" class="cw-block-download-content"> <div v-if="currentInfo !== '' && !userHasDownloaded" class="messagebox messagebox_info"> @@ -71,11 +74,13 @@ <script> import CoursewareDefaultBlock from './CoursewareDefaultBlock.vue'; import CoursewareFileChooser from './CoursewareFileChooser.vue'; +import { blockFolderValidatorMixin } from './block-folder-validator-mixin.js'; import { mapActions, mapGetters } from 'vuex'; export default { name: 'courseware-download-block', + mixins: [blockFolderValidatorMixin], components: { CoursewareDefaultBlock, CoursewareFileChooser, @@ -151,6 +156,8 @@ export default { { type: 0, file_id: fileRef.id, file_name: fileRef.attributes.name }, true ), + folder_id: fileRef?.relationships?.parent && fileRef.relationships.parent.data.type == 'folders' ? + fileRef.relationships.parent.data.id : null }); } }, @@ -221,5 +228,12 @@ export default { }); }, }, + watch: { + currentFile(value) { + if (value?.folder_id) { + this.validateFolderAccessibility(value.folder_id); + } + } + }, }; </script> diff --git a/resources/vue/components/courseware/CoursewareFolderBlock.vue b/resources/vue/components/courseware/CoursewareFolderBlock.vue index a1b97490d09da6b5c9e9cbed9df1e5e03fac1c21..bcf6ccc56679f88cdd15f9b2496ccbc8107fd0cc 100755 --- a/resources/vue/components/courseware/CoursewareFolderBlock.vue +++ b/resources/vue/components/courseware/CoursewareFolderBlock.vue @@ -9,6 +9,9 @@ @closeEdit="initCurrentData" > <template #content> + <div v-if="showInvalidFolderMessage" class="messagebox messagebox_error"> + {{ invalidFolderMessageText }} + </div> <div v-if="currentTitle !== ''" class="cw-block-title">{{ currentTitle }}</div> <ul class="cw-block-folder-list"> <li v-for="file in files" :key="file.id" class="cw-block-folder-file-item"> @@ -48,11 +51,13 @@ <script> import CoursewareDefaultBlock from './CoursewareDefaultBlock.vue'; import CoursewareFolderChooser from './CoursewareFolderChooser.vue'; +import { blockFolderValidatorMixin } from './block-folder-validator-mixin.js'; import { mapActions, mapGetters } from 'vuex'; export default { name: 'courseware-folder-block', + mixins: [blockFolderValidatorMixin], components: { CoursewareDefaultBlock, CoursewareFolderChooser, @@ -186,7 +191,8 @@ export default { }, }, watch: { - currentFolderId() { + currentFolderId(value) { + this.validateFolderAccessibility(value); this.getFolderFiles(); }, }, diff --git a/resources/vue/components/courseware/CoursewareFolderChooser.vue b/resources/vue/components/courseware/CoursewareFolderChooser.vue index 95df18b8f59a3928c5dfddb10345fb2cf911c630..30abf1c576dcabec134b606a35f6a533f1c674e7 100755 --- a/resources/vue/components/courseware/CoursewareFolderChooser.vue +++ b/resources/vue/components/courseware/CoursewareFolderChooser.vue @@ -48,21 +48,23 @@ export default { let loadedCourseFolders = []; let CourseFolders = this.relatedFolders({ parent: this.courseObject, relationship: 'folders' }) ?? []; CourseFolders.forEach(folder => { - switch (folder.attributes['folder-type']) { - case 'HiddenFolder': - if (folder.attributes['data-content']['download_allowed'] === 1) { + if (this.validateParentFolder(folder)) { + switch (folder.attributes['folder-type']) { + case 'HiddenFolder': + if (folder.attributes['data-content']['download_allowed'] === 1) { + loadedCourseFolders.push(folder); + } + break; + case 'HomeworkFolder': + if(this.allowHomeworkFolders) { + loadedCourseFolders.push(folder); + } + break; + default: loadedCourseFolders.push(folder); - } - break; - case 'HomeworkFolder': - if(this.allowHomeworkFolders) { - loadedCourseFolders.push(folder); - } - default: - loadedCourseFolders.push(folder); + } } }); - return loadedCourseFolders; }, loadedUserFolders() { @@ -79,7 +81,7 @@ export default { }, methods: { ...mapActions({ - loadRelatedFolders: 'folders/loadRelated' + loadRelatedFolders: 'folders/loadRelated' }), changeSelection() { @@ -101,6 +103,30 @@ export default { return this.loadRelatedFolders({ parent, relationship, options }); }, + + validateParentFolder(folder) { + let courseFolders = this.relatedFolders({ parent: this.courseObject, relationship: 'folders' }) ?? []; + var validation = true; + if (courseFolders.length > 0 && folder && folder.relationships && folder.relationships.parent) { + var parentId = folder.relationships.parent.data.id; + var parent = courseFolders.find(f => f.id == parentId); + if (parent) { + validation = this.hiddenParentFolderValidation(parent); + } + } + return validation; + }, + + hiddenParentFolderValidation(parentFolder) { + if (parentFolder.attributes['folder-type'] == 'HiddenFolder') { + return false; + } else if (parentFolder.relationships && parentFolder.relationships.parent) { + // Recursively validating the parents. + return this.validateParentFolder(parentFolder); + } else { + return true; + } + } }, mounted() { this.currentValue = this.value; diff --git a/resources/vue/components/courseware/CoursewareGalleryBlock.vue b/resources/vue/components/courseware/CoursewareGalleryBlock.vue index 9d0549b25c9f887f3c44de70246d0491b333e82c..cca5b2677807d1eeea1c563073dff2cb92fcad3a 100755 --- a/resources/vue/components/courseware/CoursewareGalleryBlock.vue +++ b/resources/vue/components/courseware/CoursewareGalleryBlock.vue @@ -9,6 +9,9 @@ @closeEdit="initCurrentData" > <template #content> + <div v-if="showInvalidFolderMessage" class="messagebox messagebox_error"> + {{ invalidFolderMessageText }} + </div> <div v-if="files.length !== 0" class="cw-block-gallery-content" :style="{ 'max-height': currentHeight + 'px' }"> <div v-for="(image, index) in files" @@ -83,11 +86,13 @@ <script> import CoursewareDefaultBlock from './CoursewareDefaultBlock.vue'; import CoursewareFolderChooser from './CoursewareFolderChooser.vue'; +import { blockFolderValidatorMixin } from './block-folder-validator-mixin.js'; import { mapActions, mapGetters } from 'vuex'; export default { name: 'courseware-gallery-block', + mixins: [blockFolderValidatorMixin], components: { CoursewareDefaultBlock, CoursewareFolderChooser, @@ -241,7 +246,8 @@ export default { }, }, watch: { - currentFolderId() { + currentFolderId(value) { + this.validateFolderAccessibility(value); this.getFolderFiles(); }, currentAutoplay(value) { diff --git a/resources/vue/components/courseware/CoursewareHeadlineBlock.vue b/resources/vue/components/courseware/CoursewareHeadlineBlock.vue index 349ac9545c262ff6147f5ba5e9dd906d9ed1431e..d49bb267962d11514f496ddb5fb272471cb7dce4 100755 --- a/resources/vue/components/courseware/CoursewareHeadlineBlock.vue +++ b/resources/vue/components/courseware/CoursewareHeadlineBlock.vue @@ -9,6 +9,9 @@ @closeEdit="closeEdit" > <template #content> + <div v-if="showInvalidFolderMessage" class="messagebox messagebox_error"> + {{ invalidFolderMessageText }} + </div> <div class="cw-block-headline-content" :class="[currentStyle, currentHeight === 'half' ? 'half' : 'full']" @@ -171,9 +174,11 @@ import CoursewareDefaultBlock from './CoursewareDefaultBlock.vue'; import CoursewareFileChooser from './CoursewareFileChooser.vue'; import { mapGetters, mapActions } from 'vuex'; import contentIcons from './content-icons.js'; +import { blockFolderValidatorMixin } from './block-folder-validator-mixin.js'; export default { name: 'courseware-headline-block', + mixins: [blockFolderValidatorMixin], components: { CoursewareDefaultBlock, CoursewareFileChooser, @@ -337,6 +342,8 @@ export default { { type: 0, file_id: fileRef.id, file_name: fileRef.attributes.name }, true ), + folder_id: fileRef?.relationships?.parent && fileRef.relationships.parent.data.type == 'folders' ? + fileRef.relationships.parent.data.id : null }); } }, @@ -410,5 +417,12 @@ export default { return hex.length === 1 ? '0' + hex : hex; }, }, + watch: { + currentBackgroundImage(value) { + if (value?.folder_id) { + this.validateFolderAccessibility(value.folder_id); + } + } + }, }; </script> diff --git a/resources/vue/components/courseware/CoursewareImageMapBlock.vue b/resources/vue/components/courseware/CoursewareImageMapBlock.vue index 082e2ac9ef33d68ef61bb848c6f6bb76f3d5636e..0a0cb3341fa1168cc3805005d88036f27f4f2d86 100755 --- a/resources/vue/components/courseware/CoursewareImageMapBlock.vue +++ b/resources/vue/components/courseware/CoursewareImageMapBlock.vue @@ -9,6 +9,9 @@ @closeEdit="initCurrentData" > <template #content> + <div v-if="showInvalidFolderMessage" class="messagebox messagebox_error"> + {{ invalidFolderMessageText }} + </div> <img :src="currentUrl" class="cw-image-map-original-img" ref="original_img" @load="buildCanvas" /> <canvas class="cw-image-map-canvas" ref="canvas"></canvas> <img @@ -160,11 +163,13 @@ import CoursewareDefaultBlock from './CoursewareDefaultBlock.vue'; import CoursewareFileChooser from './CoursewareFileChooser.vue'; import CoursewareTabs from './CoursewareTabs.vue'; import CoursewareTab from './CoursewareTab.vue'; +import { blockFolderValidatorMixin } from './block-folder-validator-mixin.js'; import { mapActions, mapGetters } from 'vuex'; export default { name: 'courseware-image-map-block', + mixins: [blockFolderValidatorMixin], components: { CoursewareDefaultBlock, CoursewareFileChooser, @@ -250,6 +255,8 @@ export default { { type: 0, file_id: fileRef.id, file_name: fileRef.attributes.name }, true ), + folder_id: fileRef?.relationships?.parent && fileRef.relationships.parent.data.type == 'folders' ? + fileRef.relationships.parent.data.id : null }); } }, @@ -376,7 +383,7 @@ export default { shapeWidth = shapeWidth || 0; let newText = []; - + if (shapeWidth <= 0) { return [text]; } @@ -519,5 +526,12 @@ export default { this.currentShapes[index].target_external = url; }, }, + watch: { + currentFile(value) { + if (value?.folder_id) { + this.validateFolderAccessibility(value.folder_id); + } + } + }, }; </script> diff --git a/resources/vue/components/courseware/CoursewareVideoBlock.vue b/resources/vue/components/courseware/CoursewareVideoBlock.vue index 71f5f77af57792865b676c31a8be691449c7b853..38b9b029ffd6799ccf640cbfbd6e2a28208da380 100755 --- a/resources/vue/components/courseware/CoursewareVideoBlock.vue +++ b/resources/vue/components/courseware/CoursewareVideoBlock.vue @@ -9,6 +9,9 @@ @closeEdit="initCurrentData" > <template #content> + <div v-if="showInvalidFolderMessage" class="messagebox messagebox_error"> + {{ invalidFolderMessageText }} + </div> <div v-if="currentTitle !== '' && currentURL" class="cw-block-title">{{ currentTitle }}</div> <video v-show="currentURL" @@ -75,10 +78,12 @@ <script> import CoursewareDefaultBlock from './CoursewareDefaultBlock.vue'; import CoursewareFileChooser from './CoursewareFileChooser.vue'; +import { blockFolderValidatorMixin } from './block-folder-validator-mixin.js'; import { mapActions, mapGetters } from 'vuex'; export default { name: 'courseware-video-block', + mixins: [blockFolderValidatorMixin], components: { CoursewareDefaultBlock, CoursewareFileChooser, @@ -202,6 +207,8 @@ export default { { type: 0, file_id: fileRef.id, file_name: fileRef.attributes.name }, true ), + folder_id: fileRef?.relationships?.parent && fileRef.relationships.parent.data.type == 'folders' ? + fileRef.relationships.parent.data.id : null }); } }, @@ -215,5 +222,12 @@ export default { } }, }, + watch: { + currentFile(value) { + if (value?.folder_id) { + this.validateFolderAccessibility(value.folder_id); + } + } + }, }; </script> diff --git a/resources/vue/components/courseware/block-folder-validator-mixin.js b/resources/vue/components/courseware/block-folder-validator-mixin.js new file mode 100644 index 0000000000000000000000000000000000000000..6650709943a4cdcc19686b766d4f4888a768d382 --- /dev/null +++ b/resources/vue/components/courseware/block-folder-validator-mixin.js @@ -0,0 +1,47 @@ +import { mapActions, mapGetters } from 'vuex'; + +export const blockFolderValidatorMixin = { + computed: { + ...mapGetters({ + folderById: 'folders/byId', + }), + invalidFolderMessageText() { + return this.$gettext('Der Zugriff auf ausgewählte Datei(en) könnte gesperrt sein!') + } + }, + data() { + return { + showInvalidFolderMessage: false + } + }, + methods: { + ...mapActions({ + loadFolder: 'folders/loadById', + }), + async validateFolderAccessibility(folderId) { + var valid = false; + if (folderId) { + try { + var id = folderId; + await this.loadFolder({ id }); + var folder = this.folderById({ id }); + if (folder && folder.relationships && folder.relationships.parent) { + var id = folder.relationships.parent.data.id; + await this.loadFolder({ id }); + var parent = this.folderById({ id }); + if (parent && parent.attributes['folder-type'] == 'HiddenFolder') { + valid = false; + } else if (parent.relationships && parent.relationships.parent) { + this.validateFolderAccessibility(parent.id); + } else { + valid = true; + } + } + } catch (err) { + valid = false; + } + } + this.showInvalidFolderMessage = this.isTeacher ? !valid : false; + }, + }, +};