Skip to content
Snippets Groups Projects
export.js 15.2 KiB
Newer Older
/* eslint-disable no-await-in-loop */
import { mapActions, mapGetters } from 'vuex';
import JSZip from 'jszip';
import FileSaver from 'file-saver';
import axios from 'axios';

export default {
    computed: {
        ...mapGetters({
            courseware: 'courseware',
            containerById: 'courseware-containers/byId',
            folderById: 'folders/byId',
            filesById: 'files/byId',
            structuralElementById: 'courseware-structural-elements/byId',
            allStructuralElements: 'courseware-structural-elements/all',
            allBlocks: 'courseware-blocks/all',
        }),
    },

    data() {
        return {
            exportFiles: {
                json: [],
                download: [],
            },
Ron Lucke's avatar
Ron Lucke committed
            elementCounter: 0,
            exportElementCounter: 0,
Ron Lucke's avatar
Ron Lucke committed
        initData() {
            this.exportFiles = { json: [], download: [] };
            this.elementCounter = 0;
            this.exportElementCounter = 0;
        },
        async sendExportZip(root_id = null, options) {
Ron Lucke's avatar
Ron Lucke committed
            this.initData();
Ron Lucke's avatar
Ron Lucke committed
            let view = this;
            let zip = await this.createExportFile(root_id, options);
Ron Lucke's avatar
Ron Lucke committed
            this.setExportState(this.$gettext('Erstelle Zip-Archiv'));
            this.setExportProgress(0);
            await zip.generateAsync({ type: 'blob' }, function updateCallback(metadata) {
                view.setExportProgress(metadata.percent.toFixed(0));
            }).then(function (content) {
                view.setExportState('');
                view.setExportProgress(0);
                FileSaver.saveAs(content, 'courseware-export-' + new Date().toISOString().slice(0, 10) + '.zip');
            });
        },

        async createExportFile(root_id = null, options) {
            if (!options || !options.completeExport) {
                options.completeExport = false;
            }

            if (!root_id) {
                root_id = this.courseware.relationships.root.data.id;
Ron Lucke's avatar
Ron Lucke committed
            this.setExportState(this.$gettext('Exportiere Elemente'));
            this.setExportProgress(0);
            let exportData = await this.exportCourseware(root_id, options);

            let zip = new JSZip();
            zip.file('courseware.json', JSON.stringify(exportData.json));
            zip.file('files.json', JSON.stringify(exportData.files.json));

                zip.file('settings.json', JSON.stringify(exportData.settings));
            }

            // add all additional files from blocks
Ron Lucke's avatar
Ron Lucke committed
            let i = 1;
            let filesCounter = Object.keys(exportData.files.download).length;
            this.setExportState(this.$gettext('Lade Dateien'));
            this.setExportProgress(0);
            for (let id in exportData.files.download) {
                zip.file(
                    id,
                    await fetch(exportData.files.download[id].url)
                        .then((response) => response.blob())
                        .then((textString) => {
                            return textString;
                        })
                );
Ron Lucke's avatar
Ron Lucke committed
                this.setExportProgress(parseInt(i / filesCounter * 100));
                i++;
            }

            return zip;
        },

        async exportCourseware(root_id, options) {
            let withChildren = false;

            if (options && options.withChildren === true) {
                withChildren = true;
            }
            await this.loadStructuralElement(root_id);
            let root_element = await this.structuralElementById({id: root_id});

            //prevent loss of data
            root_element = JSON.parse(JSON.stringify(root_element));

            // load whole courseware nonetheless, only export relevant elements
            let elements = await this.allStructuralElements;
Ron Lucke's avatar
Ron Lucke committed
            this.exportElementCounter = 0;
Ron Lucke's avatar
Ron Lucke committed
            if (withChildren) {
                this.elementCounter = this.countElements(elements);
            } else {
                this.elementCounter = root_element.relationships.containers.length;
            }

            root_element.containers = [];
            if (root_element.relationships.containers?.data?.length) {
                for (var j = 0; j < root_element.relationships.containers.data.length; j++) {
                    root_element.containers.push(
                        await this.exportContainer(
                            this.containerById({
                                id: root_element.relationships.containers.data[j].id,
                            })
                        )
                    );
Ron Lucke's avatar
Ron Lucke committed
                    this.exportElementCounter++;
                }
            }

            if (withChildren && elements !== []) {
                let children = await this.exportStructuralElement(root_id, elements);

Ron Lucke's avatar
Ron Lucke committed
                if (children?.length) {
                    root_element.children = children;
                }
            }
            [root_element.imageId, root_element.imageType ] = await this.exportStructuralElementImage(root_element);

            delete root_element.relationships;
            delete root_element.links;

            let settings = {
                'editing-permission-level': 'tutor',
                'sequential-progression': '0'
            if (this.courseware != null) {
                settings = {
                    'editing-permission-level': this.courseware.attributes['editing-permission-level'],
                    'sequential-progression': this.courseware.attributes['sequential-progression']
                };
            }
            if (options && options.settings) {
                settings = {
                    'editing-permission-level': options.settings['editing-permission-level'],
                    'sequential-progression': options.settings['sequential-progression']
                };
            }

            return {
                json: root_element,
                files: this.exportFiles,
                settings: settings
            };
        },

Ron Lucke's avatar
Ron Lucke committed
        countElements(elements) {
Ron Lucke's avatar
Ron Lucke committed
            let counter = 0;
Ron Lucke's avatar
Ron Lucke committed
            for (var i = 0; i < elements.length; i++) {
                counter++;
                counter += elements[i].relationships.containers.data.length;
        async exportToOER(element, options) {
            let formData = new FormData();

            let exportZip = await this.createExportFile(element.id, options);
            let zip = await exportZip.generateAsync({ type: 'blob' });

            let description = element.attributes.payload.description ? element.attributes.payload.description : '';
            let difficulty_start = element.attributes.payload.difficulty_start ? element.attributes.payload.difficulty_start : '1';
            let difficulty_end = element.attributes.payload.difficulty_end ? element.attributes.payload.difficulty_end : '12';

            if (element.relationships.image.data !== null) {
                let image = {};
                await axios.get(element.relationships.image.meta['download-url'] , {responseType: 'blob'}).then(response => { image = response.data });
                formData.append("image", image);
            }

            formData.append("data[name]", element.attributes.title);
            formData.append("tags[]", "Lernmaterial");
            formData.append("file", zip, (element.attributes.title).replace(/\s+/g, '_') + '.zip');
            formData.append("data[description]", description);
            formData.append("data[difficulty_start]", difficulty_start);
            formData.append("data[difficulty_end]", difficulty_end);
            formData.append("data[category]", 'elearning');

            axios({
                method: 'post',
                url: STUDIP.URLHelper.getURL('dispatch.php/oer/mymaterial/edit/'),
                data: formData,
                headers: { "Content-Type": "multipart/form-data"}
            }).then( () => {
                this.companionInfo({ info: this.$gettext('Die Seite wurde an den OER Campus gesendet.') });
Ron Lucke's avatar
Ron Lucke committed
            }).catch(error => {
                this.companionError({ info: this.$gettext('Beim Veröffentlichen der Seite ist ein Fehler aufgetreten.') });
            });
        },

        async exportStructuralElement(parentId, data) {
            let children = [];

            for (var i = 0; i < data.length; i++) {
Ron Lucke's avatar
Ron Lucke committed
                if (data[i].relationships.parent.data?.id === parentId && data[i].attributes['can-edit']) {
                    const content = { ...data[i] };
                    await this.loadStructuralElement(content.id);
                    let new_childs = await this.exportStructuralElement(data[i].id, data);
Ron Lucke's avatar
Ron Lucke committed
                    this.exportElementCounter++;
                    content.containers = [];

                    let element = this.structuralElementById({ id: content.id });
                    // load containers, if there are any for this struct
                    if (element.relationships.containers?.data?.length) {
                        for (var j = 0; j < element.relationships.containers.data.length; j++) {
                            content.containers.push(
                                await this.exportContainer(
                                    this.containerById({
                                        id: element.relationships.containers.data[j].id,
                                    })
                                )
                            );
Ron Lucke's avatar
Ron Lucke committed
                            this.exportElementCounter++;
Ron Lucke's avatar
Ron Lucke committed
                    // export file data (if any)
                    [content.imageId, content.imageType ] = await this.exportStructuralElementImage(element);
Ron Lucke's avatar
Ron Lucke committed

                    delete content.relationships;
                    content.children = new_childs;

                    children.push(content);
                }
            }

            return children;
        },

Ron Lucke's avatar
Ron Lucke committed
        async exportStructuralElementImage(element) {
            const fileId = element.relationships.image?.data?.id;
            const fileType = element.relationships.image?.data?.type;

Ron Lucke's avatar
Ron Lucke committed
            if (fileId) {
                if (fileType === 'file-refs') {
                    await this.loadFileRefsById({id: fileId});
                    let fileRef = this.fileRefsById({id: fileId});
                    
                    let fileRefData = {};
                    fileRefData.id = fileRef.id;
                    fileRefData.attributes = fileRef.attributes;
                    fileRefData.related_element_id = element.id;
                    fileRefData.folder = null;
    
                    this.exportFiles.json.push(fileRefData);
                    this.exportFiles.download[fileRef.id] = {
                        folder: null,
                        url: fileRef.meta['download-url']
                    };
                }
Ron Lucke's avatar
Ron Lucke committed
            }

            return [fileId, fileType];
Ron Lucke's avatar
Ron Lucke committed
        },

        async exportContainer(container_ref) {
            // make a local copy of the container
            let container = { ...container_ref };

            container.blocks = [];


            // now, load the blocks for this container, if there are any
            if (blocks.length) {
                for (var k = 0; k < blocks.length; k++) {
                    if (blocks[k].relationships.container?.data.id === container.id) {
                        container.blocks.push(await this.exportBlock(blocks[k]));
                    }
                }
            }

            delete container.relationships;

            return container;
        },

        async exportBlock(block_ref) {
            // make a local copy of the block
            let block = { ...block_ref };

            // export file data (if any)
            if (block_ref.relationships['file-refs']?.links?.related) {
                await this.exportFileRefs(block_ref.id);
            }

            delete block.relationships;

            return block;
        },

        async exportFileRefs(block_id) {
            // load file-ref data
Ron Lucke's avatar
Ron Lucke committed
            let refs =  []
            try {
                refs = await this.loadFileRefs(block_id);
            } catch(e) {
                //TODO: Companion explains error
            }

            // add infos to exportFiles JSON
            for (let ref_id in refs) {
Ron Lucke's avatar
Ron Lucke committed
                let fileref = {};
                let folderId = refs[ref_id].relationships.parent.data.id;
Ron Lucke's avatar
Ron Lucke committed
                let folder = null;
                fileref.attributes = refs[ref_id].attributes;
                fileref.related_block_id = block_id;
                fileref.id = refs[ref_id].id;
Ron Lucke's avatar
Ron Lucke committed

                // Create an empty relationships object to pick and hold selected relationships of the fileref. Because not all of
                // them are necessary.
                let relationships = {};
                // Get terms-of-use id from relationships.
                if (refs[ref_id].relationships?.['terms-of-use']?.data?.id) {
                    let terms = {'data' : refs[ref_id].relationships['terms-of-use'].data};
                    relationships['terms-of-use'] = terms;
                }
                // Add relationships to the fileref object if it has some values.
                if (Object.keys(relationships).length > 0) {
                    fileref.relationships = relationships;
                }

Ron Lucke's avatar
Ron Lucke committed
                try {
                    await this.loadFolder(folderId);
                    folder = this.folderById({id: folderId});
                } catch(e) {
                    //TODO: Companion explains error
                }

                if (folder) {
B. Sc Pius Gyamenah's avatar
B. Sc Pius Gyamenah committed
                    let folderName = 'Unnamed Folder';
                    if (folder?.attributes?.name) {
                        folderName =  folder.attributes.name;
                    }
Ron Lucke's avatar
Ron Lucke committed
                    fileref.folder = {
                        id: folder.id,
B. Sc Pius Gyamenah's avatar
B. Sc Pius Gyamenah committed
                        name: folderName,
Ron Lucke's avatar
Ron Lucke committed
                        type: folder.attributes['folder-type']
                    }
                } else {
                    fileref.folder = {
                        id: folderId,
                        name: 'Unknown',
                        type: 'StandardFolder'
                    }
                }

                this.exportFiles.json.push(fileref);

                // prevent multiple downloads of the same file
                this.exportFiles.download[refs[ref_id].id] = {
                    folder: folderId,
                    url: refs[ref_id].meta['download-url']
                };
            }
        },

        ...mapActions({
            loadStructuralElement: 'loadStructuralElement',
            loadFileRefs: 'loadFileRefs',
            loadFolder: 'loadFolder',
            companionInfo: 'companionInfo',
            setExportState: 'setExportState',
            setExportProgress: 'setExportProgress',
            loadFileRefsById: 'file-refs/loadById'
        }),
Ron Lucke's avatar
Ron Lucke committed
    watch: {
        exportElementCounter(counter) {
            if (this.elementCounter !== 0) {
                this.setExportProgress(parseInt(counter / this.elementCounter * 100));
            }
        }
    },