Forked from
Stud.IP / Stud.IP
855 commits behind the upstream repository.
-
Closes #3743 Merge request studip/studip!3175
Closes #3743 Merge request studip/studip!3175
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
export.js 15.15 KiB
/* 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',
fileRefsById: 'file-refs/byId',
structuralElementById: 'courseware-structural-elements/byId',
allStructuralElements: 'courseware-structural-elements/all',
allBlocks: 'courseware-blocks/all',
}),
},
data() {
return {
exportFiles: {
json: [],
download: [],
},
elementCounter: 0,
exportElementCounter: 0,
};
},
methods: {
initData() {
this.exportFiles = { json: [], download: [] };
this.elementCounter = 0;
this.exportElementCounter = 0;
},
async sendExportZip(root_id = null, options) {
this.initData();
let view = this;
let zip = await this.createExportFile(root_id, options);
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;
options.completeExport = true;
}
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));
if (options.completeExport) {
zip.file('settings.json', JSON.stringify(exportData.settings));
}
// add all additional files from blocks
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;
})
);
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;
this.exportElementCounter = 0;
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,
})
)
);
this.exportElementCounter++;
}
}
if (withChildren && elements !== []) {
let children = await this.exportStructuralElement(root_id, elements);
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
};
},
countElements(elements) {
let counter = 0;
for (var i = 0; i < elements.length; i++) {
counter++;
counter += elements[i].relationships.containers.data.length;
}
return counter;
},
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.') });
}).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++) {
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);
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,
})
)
);
this.exportElementCounter++;
}
}
// export file data (if any)
[content.imageId, content.imageType ] = await this.exportStructuralElementImage(element);
delete content.relationships;
content.children = new_childs;
children.push(content);
}
}
return children;
},
async exportStructuralElementImage(element) {
const fileId = element.relationships.image?.data?.id;
const fileType = element.relationships.image?.data?.type;
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']
};
}
}
return [fileId, fileType];
},
async exportContainer(container_ref) {
// make a local copy of the container
let container = { ...container_ref };
container.blocks = [];
let blocks = this.allBlocks;
// 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
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) {
let fileref = {};
let folderId = refs[ref_id].relationships.parent.data.id;
let folder = null;
fileref.attributes = refs[ref_id].attributes;
fileref.related_block_id = block_id;
fileref.id = refs[ref_id].id;
// 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;
}
try {
await this.loadFolder(folderId);
folder = this.folderById({id: folderId});
} catch(e) {
//TODO: Companion explains error
}
if (folder) {
let folderName = 'Unnamed Folder';
if (folder?.attributes?.name) {
folderName = folder.attributes.name;
}
fileref.folder = {
id: folder.id,
name: folderName,
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'
}),
},
watch: {
exportElementCounter(counter) {
if (this.elementCounter !== 0) {
this.setExportProgress(parseInt(counter / this.elementCounter * 100));
}
}
},
};